Skip to content

Commit

Permalink
feat: remove a lot of code (#57)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: drop ie8 support
  • Loading branch information
jquense authored Aug 19, 2019
1 parent 64c6f95 commit a6b5893
Show file tree
Hide file tree
Showing 28 changed files with 205 additions and 292 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"extends": ["@react-bootstrap", "@react-bootstrap/eslint-config-typescript"],
"rules": {
"no-nested-ternary": "off",
"consistent-return": "off"
"consistent-return": "off",
"import/no-mutable-exports": "off"
}
}
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# dom-helpers

tiny modular DOM lib for ie8+
tiny modular DOM lib for ie9+

## Install

```sh
npm i -S dom-helpers
```

Mostly just naive wrappers around common DOM API inconsistencies, Cross browser work is minimal and mostly taken from jQuery. This library doesn't do a lot to normalize behavior across browsers, it mostly seeks to provide a common interface, and eliminate the need to write the same damn `if (ie8)` statements in every project.
Mostly just naive wrappers around common DOM API inconsistencies, Cross browser work is minimal and mostly taken from jQuery. This library doesn't do a lot to normalize behavior across browsers, it mostly seeks to provide a common interface, and eliminate the need to write the same damn `if (ie9)` statements in every project.

For example `on()` works in all browsers ie8+ but it uses the native event system so actual event oddities will continue to exist. If you need **robust** cross-browser support, use jQuery. If you are just tired of rewriting:
For example `on()` works in all browsers ie9+ but it uses the native event system so actual event oddities will continue to exist. If you need **robust** cross-browser support, use jQuery. If you are just tired of rewriting:

```js
if (document.addEventListener)
Expand All @@ -23,7 +23,7 @@ else if (document.attachEvent)

over and over again, or you need a ok `getComputedStyle` polyfill but don't want to include all of jQuery, use this.

dom-helpers does expect certain, polyfillable, es5 features to be present for which you can use `es5-shim` for ie8
dom-helpers does expect certain, polyfillable, es5 features to be present for which you can use `es5-shim` where needed

The real advantage to this collection is that any method can be required individually, meaning bundlers like webpack will only include the exact methods you use. This is great for environments where jQuery doesn't make sense, such as `React` where you only occasionally need to do direct DOM manipulation.

Expand All @@ -45,7 +45,7 @@ require('dom-helpers/css')(node, { width: '40px' })
- `contains(container, element)`
- `height(element, useClientHeight)`
- `width(element, useClientWidth)`
- `matches(element, selector)`: `matches()` polyfill that works in ie8
- `matches(element, selector)`
- `offset(element)` -> `{ top: Number, left: Number, height: Number, width: Number}`
- `offsetParent(element)`: return the parent node that the element is offset from
- `position(element, [offsetParent]`: return "offset" of the node to its offsetParent, optionally you can specify the offset parent if different than the "real" one
Expand All @@ -61,9 +61,9 @@ require('dom-helpers/css')(node, { width: '40px' })
- `getComputedStyle(element)` -> `getPropertyValue(name)`
- `animate(node, properties, duration, easing, callback)` programmatically start css transitions
- `transitionEnd(node, handler, [duration])` listens for transition end, and ensures that the handler if called even if the transition fails to fire its end event. Will attempt to read duration from the element, otherwise one can be provided
- `on(node, eventName, handler, [capture])`: capture is silently ignored in ie8
- `off(node, eventName, handler, [capture])`: capture is silently ignored in ie8
- `listen(node, eventName, handler, [capture])`: wraps `on` and returns a function that calls `off` for you
- `addEventListener(node, eventName, handler, [options])`:
- `removeEventListener(node, eventName, handler, [options])`:
- `listen(node, eventName, handler, [options])`: wraps `addEventlistener` and returns a function that calls `removeEventListener` for you
- `filterEventHandler(selector, fn)`: returns a function handler that only fires when the target matches or is contained in the selector ex: `on(list, 'click', filterEventHandler('li > a', handler))`
- `animationFrame.request(cb)` returns an ID for canceling
- `animationFrame.cancel(id)`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "dom-helpers",
"version": "3.4.0",
"description": "tiny modular DOM lib for ie8+",
"description": "tiny modular DOM lib for ie9+",
"author": {
"name": "Jason Quense",
"email": "[email protected]"
Expand Down
15 changes: 13 additions & 2 deletions src/activeElement.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import ownerDocument from './ownerDocument'

/**
* Return the actively focused element safely.
*
* @param doc the document to checl
*/
export default function activeElement(doc = ownerDocument()) {
// Support: IE 9 only
// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
try {
return doc.activeElement
const active = doc.activeElement
// IE11 returns a seemingly empty object in some cases when accessing
// document.activeElement from an <iframe>
if (!active || !active.nodeName) return null
return active
} catch (e) {
/* ie throws if no active element */
return null
return doc.body
}
}
65 changes: 65 additions & 0 deletions src/addEventListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* eslint-disable no-return-assign */
import canUseDOM from './canUseDOM'

export let optionsSupported = false
export let onceSupported = false

try {
const options = {
get passive() {
return (optionsSupported = true)
},
get once() {
// eslint-disable-next-line no-multi-assign
return (onceSupported = optionsSupported = true)
},
}
if (canUseDOM) {
window.addEventListener('test', options as any, options)
window.removeEventListener('test', options as any, true)
}
} catch (e) {
/* */
}

export type EventHandler<K extends keyof HTMLElementEventMap> = (
this: HTMLElement,
event: HTMLElementEventMap[K]
) => any

export type TaggedEventHandler<
K extends keyof HTMLElementEventMap
> = EventHandler<K> & { __once?: EventHandler<K> }
/**
* An `addEventListener` ponyfill, supports the `once` option
*/
function addEventListener<K extends keyof HTMLElementEventMap>(
node: HTMLElement,
eventName: K,
handler: TaggedEventHandler<K>,
options?: boolean | AddEventListenerOptions
) {
if (options && typeof options !== 'boolean' && !onceSupported) {
const { once, capture } = options
let wrappedHandler = handler
if (!onceSupported && once) {
wrappedHandler =
handler.__once ||
function onceHandler(event) {
this.removeEventListener(eventName, onceHandler, capture)
handler.call(this, event)
}
handler.__once = wrappedHandler
}

node.addEventListener(
eventName,
wrappedHandler,
optionsSupported ? options : capture
)
}

node.addEventListener(eventName, handler, options)
}

export default addEventListener
17 changes: 7 additions & 10 deletions src/animate.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { EventHandler } from './addEventListener'
import css from './css'
import hyphenate from './hyphenate'
import isTransform, { TransformValue } from './isTransform'
import off from './off'
import on, { EventHandler } from './on'
import { TRANSITION_SUPPORTED, emulateTransitionEnd } from './transitionEnd'
import transitionEnd from './transitionEnd'
import { Property } from './types'

let reset: Partial<Record<Property, string>> = {
Expand Down Expand Up @@ -45,8 +44,6 @@ function _animate({

let transforms = ''

if (!TRANSITION_SUPPORTED) duration = 0

Object.keys(properties).forEach((key: Property) => {
const value = properties[key]

Expand All @@ -69,24 +66,24 @@ function _animate({
if (callback) callback.call(this, event)
}

let removeListener: () => void
if (duration > 0) {
cssValues.transition = cssProperties.join(', ')
cssValues['transition-duration'] = `${duration / 1000}s`
cssValues['transition-delay'] = `${0}s`
cssValues['transition-delay'] = '0s'
cssValues['transition-timing-function'] = easing || 'linear'

on(node, 'transitionend', done)
emulateTransitionEnd(node, duration)
}

removeListener = transitionEnd(node, done, duration)

// eslint-disable-next-line no-unused-expressions
node.clientLeft // trigger page reflow

css(node, cssValues)

return {
cancel() {
off(node, 'transitionend', done)
removeListener()
css(node, reset)
},
}
Expand Down
25 changes: 16 additions & 9 deletions src/closest.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import isDocument from './isDocument'
import matches from './matches'

let isDoc = (obj: any) => obj != null && obj.nodeType === document.DOCUMENT_NODE

export default function closest(
node: Element,
selector: string,
context: Element
): Element | undefined {
while (node && (isDoc(node) || !matches(node, selector))) {
// @ts-ignore
node = node !== context && !isDoc(node) ? node.parentNode : undefined
}
return node
stopAt?: Element
): Element | null {
if (node.closest && !stopAt) node.closest(selector)

let nextNode: Element | null = node
do {
if (matches(nextNode, selector)) return nextNode
nextNode = nextNode.parentElement
} while (
nextNode &&
nextNode !== stopAt &&
nextNode.nodeType === document.ELEMENT_NODE
)

return null
}
33 changes: 6 additions & 27 deletions src/contains.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,9 @@
/* eslint-disable no-bitwise, no-cond-assign */
import canUseDOM from './canUseDOM'

function fallback(context: Element, node: Element) {
if (node)
do {
if (node === context) return true
// @ts-ignore
} while ((node = node.parentNode))

return false
// HTML DOM and SVG DOM may have different support levels,
// so we need to check on context instead of a document root element.
export default function contains(context: Element, node: Element) {
if (context.contains) return context.contains(node)
if (context.compareDocumentPosition)
return context === node || !!(context.compareDocumentPosition(node) & 16)
}

export default (function getContains() {
// HTML DOM and SVG DOM may have different support levels,
// so we need to check on context instead of a document root element.
return canUseDOM
? function contains(context: Element, node: Element) {
if (context.contains) {
return context.contains(node)
}
if (context.compareDocumentPosition) {
return (
context === node || !!(context.compareDocumentPosition(node) & 16)
)
}
return fallback(context, node)
}
: fallback
})()
11 changes: 2 additions & 9 deletions src/css.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import * as CSS from 'csstype'
import camelize from './camelizeStyle'
import getComputedStyle from './getComputedStyle'
import hyphenate from './hyphenateStyle'
import isTransform from './isTransform'
import removeStyle from './removeStyle'
import { CamelProperty, HyphenProperty, Property } from './types'

type Styles = keyof CSSStyleDeclaration

function style(
node: HTMLElement,
property: Partial<Record<Property, string>>
Expand All @@ -28,16 +24,13 @@ function style<T extends Property>(
let transforms = ''

if (typeof property === 'string') {
return (
node.style[camelize(property) as Styles] ||
getComputedStyle(node).getPropertyValue(hyphenate(property))
)
return getComputedStyle(node).getPropertyValue(hyphenate(property))
}

Object.keys(property).forEach((key: Property) => {
let value = property[key]
if (!value && value !== 0) {
removeStyle(node, hyphenate(key))
node.style.removeProperty(hyphenate(key))
} else if (isTransform(key)) {
transforms += `${key}(${value}) `
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/filterEventHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EventHandler } from './addEventListener'
import contains from './contains'
import { EventHandler } from './on'
import qsa from './querySelectorAll'

export default function filterEvents<K extends keyof HTMLElementEventMap>(
Expand Down
54 changes: 5 additions & 49 deletions src/getComputedStyle.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,8 @@
import camelize from './camelizeStyle'
import ownerWindow from './ownerWindow'
import { Property } from './types'

let rposition = /^(top|right|bottom|left)$/
let rnumnonpx = /^([+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/i

export default function _getComputedStyle(node: HTMLElement) {
if (!node) throw new TypeError('No Element passed to `getComputedStyle()`')
let window = ownerWindow(node)

return 'getComputedStyle' in window
? window.getComputedStyle(node, null)
: // eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
({
// ie 8 "magic" from: https://github.com/jquery/jquery/blob/1.11-stable/src/css/curCSS.js#L72
getPropertyValue(property: Property) {
let prop: string = property as string
let style = node.style

prop = camelize(prop)

if (prop === 'float') prop = 'styleFloat'
// @ts-ignore
let current = node.currentStyle[prop] || null
// @ts-ignore
if (current == null && style && style[prop]) current = style[prop]

if (rnumnonpx.test(current) && !rposition.test(prop)) {
// Remember the original values
let left = style.left
// @ts-ignore
let runStyle = node.runtimeStyle
let rsLeft = runStyle && runStyle.left

// Put in the new values to get a computed value out
// @ts-ignore
if (rsLeft) runStyle.left = node.currentStyle.left

style.left = prop === 'fontSize' ? '1em' : current
// @ts-ignore
current = `${style.pixelLeft}px`

// Revert the changed values
style.left = left
if (rsLeft) runStyle.left = rsLeft
}

return current
},
} as CSSStyleDeclaration)
export default function getComputedStyle(
node: HTMLElement,
psuedoElement?: string
) {
return ownerWindow(node).getComputedStyle(node, psuedoElement)
}
12 changes: 2 additions & 10 deletions src/getScrollAccessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,11 @@ export default function getscrollAccessor(
let win = isWindow(node)

if (val === undefined) {
if (!win) return node[prop]
return offset in win
? win[offset]
: (win as Window).document.documentElement[prop]
return win ? win[offset] : node[prop]
}

if (win) {
win.scrollTo(
val,
'offset' in win
? win[offset]
: (win as Window).document.documentElement[prop]
)
win.scrollTo(val, win[offset])
} else {
node[prop] = val
}
Expand Down
Loading

0 comments on commit a6b5893

Please sign in to comment.