@oscarpalmer/toretto 0.37.2 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,16 @@
1
1
  import { getAttributeValue } from "../internal/get-value.js";
2
2
  import { isHTMLOrSVGElement } from "../internal/is.js";
3
+ //#region src/attribute/get.ts
3
4
  function getAttribute(element, name, parseValues) {
4
5
  if (isHTMLOrSVGElement(element) && typeof name === "string") return getAttributeValue(element, name, parseValues !== false);
5
6
  }
7
+ /**
8
+ * Get specific attributes from an element
9
+ * @param element Element to get attributes from
10
+ * @param names Attribute names
11
+ * @param parseData Parse data values? _(defaults to `true`)_
12
+ * @returns Object of named attributes
13
+ */
6
14
  function getAttributes(element, names, parseData) {
7
15
  const attributes = {};
8
16
  if (!(isHTMLOrSVGElement(element) && Array.isArray(names))) return attributes;
@@ -14,4 +22,5 @@ function getAttributes(element, names, parseData) {
14
22
  }
15
23
  return attributes;
16
24
  }
25
+ //#endregion
17
26
  export { getAttribute, getAttributes };
@@ -1,6 +1,7 @@
1
1
  import { getAttribute, getAttributes } from "./get.js";
2
2
  import { _isBadAttribute, _isBooleanAttribute, _isEmptyNonBooleanAttribute, _isInvalidBooleanAttribute, booleanAttributes } from "../internal/attribute.js";
3
3
  import { setAttribute, setAttributes } from "./set.js";
4
+ //#region src/attribute/index.ts
4
5
  function isBadAttribute(first, second) {
5
6
  return _isBadAttribute(first, second, true);
6
7
  }
@@ -13,4 +14,5 @@ function isEmptyNonBooleanAttribute(first, second) {
13
14
  function isInvalidBooleanAttribute(first, second) {
14
15
  return _isInvalidBooleanAttribute(first, second, true);
15
16
  }
17
+ //#endregion
16
18
  export { booleanAttributes, getAttribute, getAttributes, isBadAttribute, isBooleanAttribute, isEmptyNonBooleanAttribute, isInvalidBooleanAttribute, setAttribute, setAttributes };
@@ -1,9 +1,11 @@
1
1
  import { setElementValue, setElementValues } from "../internal/element-value.js";
2
2
  import { updateAttribute } from "../internal/attribute.js";
3
+ //#region src/attribute/set.ts
3
4
  function setAttribute(element, first, second, third) {
4
5
  setElementValue(element, first, second, third, updateAttribute);
5
6
  }
6
7
  function setAttributes(element, attributes, dispatch) {
7
8
  setElementValues(element, attributes, null, dispatch, updateAttribute);
8
9
  }
10
+ //#endregion
9
11
  export { setAttribute, setAttributes };
package/dist/data.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { EXPRESSION_DATA_PREFIX } from "./internal/get-value.js";
2
2
  import { isHTMLOrSVGElement } from "./internal/is.js";
3
3
  import { setElementValues, updateElementValue } from "./internal/element-value.js";
4
- import { kebabCase, parse } from "@oscarpalmer/atoms/string";
4
+ import { parse } from "@oscarpalmer/atoms/string";
5
+ import { kebabCase } from "@oscarpalmer/atoms/string/case";
6
+ //#region src/data.ts
5
7
  function getData(element, keys, parseValues) {
6
8
  if (!isHTMLOrSVGElement(element)) return;
7
9
  const shouldParse = parseValues !== false;
@@ -30,4 +32,5 @@ function updateDataAttribute(element, key, value) {
30
32
  updateElementValue(element, getName(key), value, element.setAttribute, element.removeAttribute, false, true);
31
33
  }
32
34
  var ATTRIBUTE_DATA_PREFIX = "data-";
35
+ //#endregion
33
36
  export { getData, setData };
@@ -1,4 +1,5 @@
1
1
  import { isEventTarget } from "../internal/is.js";
2
+ //#region src/event/delegation.ts
2
3
  function addDelegatedHandler(doc, type, name, passive) {
3
4
  if (DELEGATED.has(name)) return;
4
5
  DELEGATED.add(name);
@@ -80,4 +81,5 @@ var EVENT_TYPES = new Set([
80
81
  ]);
81
82
  var HANDLER_ACTIVE = delegatedEventHandler.bind(false);
82
83
  var HANDLER_PASSIVE = delegatedEventHandler.bind(true);
84
+ //#endregion
83
85
  export { addDelegatedListener, getDelegatedName, removeDelegatedListener };
@@ -3,6 +3,7 @@ import { isEventTarget } from "../internal/is.js";
3
3
  import { addDelegatedListener, getDelegatedName, removeDelegatedListener } from "./delegation.js";
4
4
  import { isPlainObject } from "@oscarpalmer/atoms/is";
5
5
  import { noop } from "@oscarpalmer/atoms/function";
6
+ //#region src/event/index.ts
6
7
  function createDispatchOptions(options) {
7
8
  return {
8
9
  bubbles: getBoolean(options?.bubbles, true),
@@ -29,6 +30,11 @@ function createEventOptions(options) {
29
30
  function dispatch(target, type, options) {
30
31
  if (isEventTarget(target) && typeof type === "string") target.dispatchEvent(createEvent(type, options));
31
32
  }
33
+ /**
34
+ * Get the X- and Y-coordinates from a pointer event
35
+ * @param event Pointer event
36
+ * @returns X- and Y-coordinates
37
+ */
32
38
  function getPosition(event) {
33
39
  let x;
34
40
  let y;
@@ -44,6 +50,13 @@ function getPosition(event) {
44
50
  y
45
51
  } : void 0;
46
52
  }
53
+ /**
54
+ * Remove an event listener
55
+ * @param target Event target
56
+ * @param type Type of event
57
+ * @param listener Event listener
58
+ * @param options Options for event
59
+ */
47
60
  function off(target, type, listener, options) {
48
61
  if (!isEventTarget(target) || typeof type !== "string" || typeof listener !== "function") return;
49
62
  const extended = createEventOptions(options);
@@ -62,4 +75,5 @@ function on(target, type, listener, options) {
62
75
  };
63
76
  }
64
77
  var PROPERTY_DETAIL = "detail";
78
+ //#endregion
65
79
  export { dispatch, getPosition, off, on };
@@ -1,4 +1,5 @@
1
1
  import { findAncestor, findRelatives, getDistance } from "./relative.js";
2
+ //#region src/find/index.ts
2
3
  function findElement(selector, context) {
3
4
  return findElementOrElements(selector, context, true);
4
5
  }
@@ -40,6 +41,14 @@ function findElementOrElementsFromNodes(array, context, contexts) {
40
41
  function findElements(selector, context) {
41
42
  return findElementOrElements(selector, context, false);
42
43
  }
44
+ /**
45
+ * Get the most specific element under the pointer
46
+ *
47
+ * - Ignores elements with `pointer-events: none` and `visibility: hidden`
48
+ * - _(If `skipIgnore` is `true`, no elements are ignored)_
49
+ * @param skipIgnore Skip ignored elements?
50
+ * @returns Found element or `null`
51
+ */
43
52
  function getElementUnderPointer(skipIgnore) {
44
53
  const elements = [...document.querySelectorAll(SUFFIX_HOVER)];
45
54
  const { length } = elements;
@@ -61,4 +70,5 @@ var STYLE_HIDDEN = "hidden";
61
70
  var STYLE_NONE = "none";
62
71
  var SUFFIX_HOVER = ":hover";
63
72
  var TAG_HEAD = "HEAD";
73
+ //#endregion
64
74
  export { findElement as $, findElement, findElements as $$, findElements, findAncestor, findRelatives, getDistance, getElementUnderPointer };
@@ -1,3 +1,4 @@
1
+ //#region src/find/relative.ts
1
2
  function findAncestor(origin, selector) {
2
3
  const element = getElement(origin);
3
4
  if (element == null || selector == null) return null;
@@ -34,6 +35,12 @@ function findRelatives(origin, selector, context) {
34
35
  }
35
36
  return distances.filter((found) => found.distance === minimum).map((found) => found.element);
36
37
  }
38
+ /**
39
+ * Get the distance between two elements _(i.e., the amount of nodes of between them)_
40
+ * @param origin Origin element
41
+ * @param target Target element
42
+ * @returns Distance between elements, or `-1` if distance cannot be calculated
43
+ */
37
44
  function getDistance(origin, target) {
38
45
  if (origin === target) return 0;
39
46
  if (origin.parentElement === target || target.parentElement === origin) return 1;
@@ -68,4 +75,5 @@ function traverse(from, to) {
68
75
  parent = parent.parentElement;
69
76
  }
70
77
  }
78
+ //#endregion
71
79
  export { findAncestor, findRelatives, getDistance };
package/dist/focusable.js CHANGED
@@ -1,3 +1,9 @@
1
+ //#region src/focusable.ts
2
+ /**
3
+ * Get a list of focusable elements within a parent element
4
+ * @param parent Parent element
5
+ * @returns Focusable elements
6
+ */
1
7
  function getFocusable(parent) {
2
8
  return getValidElements(parent, FILTERS_FOCUSABLE, false);
3
9
  }
@@ -7,6 +13,11 @@ function getItem(element, tabbable) {
7
13
  tabIndex: tabbable ? getTabIndex(element) : TABINDEX_DEFAULT
8
14
  };
9
15
  }
16
+ /**
17
+ * Get a list of tabbable elements within a parent element
18
+ * @param parent Parent element
19
+ * @returns Tabbable elements
20
+ */
10
21
  function getTabbable(parent) {
11
22
  return getValidElements(parent, FILTERS_TABBABLE, true);
12
23
  }
@@ -61,6 +72,11 @@ function isDisabledFromFieldset(element) {
61
72
  function isEditable(element) {
62
73
  return EXPRESSION_TRUEISH.test(element.getAttribute(ATTRIBUTE_CONTENTEDITABLE));
63
74
  }
75
+ /**
76
+ * Is the element focusable?
77
+ * @param element Element to check
78
+ * @returns `true` if focusable, otherwise `false`
79
+ */
64
80
  function isFocusable(element) {
65
81
  return element instanceof Element ? isValidElement(element, FILTERS_FOCUSABLE, false) : false;
66
82
  }
@@ -91,6 +107,11 @@ function isNotTabbableRadio(item) {
91
107
  function isSummarised(item) {
92
108
  return item.element instanceof HTMLDetailsElement && [...item.element.children].some((child) => EXPRESSION_SUMMARY.test(child.tagName));
93
109
  }
110
+ /**
111
+ * Is the element tabbable?
112
+ * @param element Element to check
113
+ * @returns `true` if tabbable, otherwise `false`
114
+ */
94
115
  function isTabbable(element) {
95
116
  return element instanceof Element ? isValidElement(element, FILTERS_TABBABLE, true) : false;
96
117
  }
@@ -139,4 +160,5 @@ var STYLE_NONE = "none";
139
160
  var TABINDEX_BASE = 0;
140
161
  var TABINDEX_DEFAULT = -1;
141
162
  var TYPE_RADIO = "radio";
163
+ //#endregion
142
164
  export { getFocusable, getTabbable, isFocusable, isTabbable };
@@ -1,5 +1,6 @@
1
1
  import { sanitizeNodes } from "./sanitize.js";
2
2
  import { isPlainObject } from "@oscarpalmer/atoms/is";
3
+ //#region src/html/index.ts
3
4
  function createHtml(value) {
4
5
  const parsed = getParser().parseFromString(getHtml(value), PARSE_TYPE_HTML);
5
6
  parsed.body.normalize();
@@ -54,6 +55,12 @@ html.remove = (template) => {
54
55
  }
55
56
  templates = updated;
56
57
  };
58
+ /**
59
+ * Sanitize one or more nodes, recursively
60
+ * @param value Node or nodes to sanitize
61
+ * @param options Sanitization options
62
+ * @returns Sanitized nodes
63
+ */
57
64
  function sanitize(value) {
58
65
  return sanitizeNodes(Array.isArray(value) ? value : [value], 0);
59
66
  }
@@ -63,4 +70,5 @@ var TEMPLATE_TAG = "template";
63
70
  var TEMPORARY_ELEMENT = "<toretto-temporary></toretto-temporary>";
64
71
  var parser;
65
72
  var templates = {};
73
+ //#endregion
66
74
  export { html, sanitize };
@@ -1,5 +1,6 @@
1
1
  import { _isBadAttribute, _isEmptyNonBooleanAttribute, _isInvalidBooleanAttribute } from "../internal/attribute.js";
2
2
  import { setAttribute } from "../attribute/set.js";
3
+ //#region src/html/sanitize.ts
3
4
  function handleElement(element, depth) {
4
5
  if (depth === 0) {
5
6
  const removable = element.querySelectorAll(REMOVE_SELECTOR);
@@ -7,6 +8,11 @@ function handleElement(element, depth) {
7
8
  }
8
9
  sanitizeAttributes(element, [...element.attributes]);
9
10
  }
11
+ /**
12
+ * Is the element clobbered?
13
+ *
14
+ * Thanks, DOMPurify _(https://github.com/cure53/DOMPurify)_
15
+ */
10
16
  function isClobbered(value) {
11
17
  return value instanceof HTMLFormElement && (typeof value.nodeName !== "string" || typeof value.textContent !== "string" || typeof value.removeChild !== "function" || !(value.attributes instanceof NamedNodeMap) || typeof value.removeAttribute !== "function" || typeof value.setAttribute !== "function" || typeof value.namespaceURI !== "string" || typeof value.insertBefore !== "function" || typeof value.hasChildNodes !== "function");
12
18
  }
@@ -52,4 +58,5 @@ function sanitizeNodes(nodes, depth) {
52
58
  }
53
59
  var COMMENT_HARMFUL = /<[/\w]/g;
54
60
  var REMOVE_SELECTOR = "script, toretto-temporary";
61
+ //#endregion
55
62
  export { sanitizeAttributes, sanitizeNodes };
package/dist/index.js CHANGED
@@ -7,6 +7,6 @@ import { findAncestor, findRelatives, getDistance } from "./find/relative.js";
7
7
  import { $ as findElement, $$ as findElements, getElementUnderPointer } from "./find/index.js";
8
8
  import { getFocusable, getTabbable, isFocusable, isTabbable } from "./focusable.js";
9
9
  import { html, sanitize } from "./html/index.js";
10
- import touch_default from "./touch.js";
10
+ import supportsTouch from "./touch.js";
11
11
  import { getStyle, getStyles, getTextDirection, setStyle, setStyles, toggleStyles } from "./style.js";
12
- export { findElement as $, findElement, findElements as $$, findElements, dispatch, findAncestor, findRelatives, getData, getDistance, getElementUnderPointer, getFocusable, getPosition, getStyle, getStyles, getTabbable, getTextDirection, html, isBadAttribute, isBooleanAttribute, isChildNode, isEmptyNonBooleanAttribute, isEventTarget, isFocusable, isHTMLOrSVGElement, isInDocument, isInvalidBooleanAttribute, isTabbable, off, on, sanitize, setData, setStyle, setStyles, touch_default as supportsTouch, toggleStyles };
12
+ export { findElement as $, findElement, findElements as $$, findElements, dispatch, findAncestor, findRelatives, getData, getDistance, getElementUnderPointer, getFocusable, getPosition, getStyle, getStyles, getTabbable, getTextDirection, html, isBadAttribute, isBooleanAttribute, isChildNode, isEmptyNonBooleanAttribute, isEventTarget, isFocusable, isHTMLOrSVGElement, isInDocument, isInvalidBooleanAttribute, isTabbable, off, on, sanitize, setData, setStyle, setStyles, supportsTouch, toggleStyles };
@@ -1,5 +1,6 @@
1
1
  import { updateElementValue } from "./element-value.js";
2
2
  import { isPlainObject } from "@oscarpalmer/atoms/is";
3
+ //#region src/internal/attribute.ts
3
4
  function badAttributeHandler(name, value) {
4
5
  if (typeof name !== "string" || name.trim().length === 0 || typeof value !== "string") return true;
5
6
  if (EXPRESSION_CLOBBERED_NAME.test(name) && (value in document || value in formElement) || EXPRESSION_EVENT_NAME.test(name)) return true;
@@ -70,7 +71,10 @@ var EXPRESSION_SOURCE_NAME = /^src$/i;
70
71
  var EXPRESSION_SOURCE_VALUE = /^data:/i;
71
72
  var EXPRESSION_URI_VALUE = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
72
73
  var EXPRESSION_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
73
- const booleanAttributes = Object.freeze([
74
+ /**
75
+ * List of boolean attributes
76
+ */
77
+ var booleanAttributes = Object.freeze([
74
78
  "async",
75
79
  "autofocus",
76
80
  "autoplay",
@@ -108,4 +112,5 @@ var elementEvents = {
108
112
  };
109
113
  var formElement = document.createElement("form");
110
114
  var textArea;
115
+ //#endregion
111
116
  export { _isBadAttribute, _isBooleanAttribute, _isEmptyNonBooleanAttribute, _isInvalidBooleanAttribute, booleanAttributes, isAttribute, updateAttribute };
@@ -2,6 +2,7 @@ import { isHTMLOrSVGElement } from "./is.js";
2
2
  import "../is.js";
3
3
  import { isAttribute } from "./attribute.js";
4
4
  import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
5
+ //#region src/internal/element-value.ts
5
6
  function setElementValue(element, first, second, third, callback) {
6
7
  if (!isHTMLOrSVGElement(element)) return;
7
8
  if (typeof first === "string") setElementValues(element, first, second, third, callback);
@@ -29,4 +30,5 @@ function updateElementValue(element, key, value, set, remove, isBoolean, json) {
29
30
  if (isBoolean ? value == null : isNullableOrWhitespace(value)) remove.call(element, key);
30
31
  else set.call(element, key, json ? JSON.stringify(value) : String(value));
31
32
  }
33
+ //#endregion
32
34
  export { setElementValue, setElementValues, updateElementValue };
@@ -1,4 +1,6 @@
1
- import { camelCase, kebabCase, parse } from "@oscarpalmer/atoms/string";
1
+ import { parse } from "@oscarpalmer/atoms/string";
2
+ import { camelCase, kebabCase } from "@oscarpalmer/atoms/string/case";
3
+ //#region src/internal/get-value.ts
2
4
  function getBoolean(value, defaultValue) {
3
5
  return typeof value === "boolean" ? value : defaultValue ?? false;
4
6
  }
@@ -12,5 +14,6 @@ function getStyleValue(element, property, computed) {
12
14
  const name = camelCase(property);
13
15
  return computed ? getComputedStyle(element)[name] : element.style[name];
14
16
  }
15
- const EXPRESSION_DATA_PREFIX = /^data-/i;
17
+ var EXPRESSION_DATA_PREFIX = /^data-/i;
18
+ //#endregion
16
19
  export { EXPRESSION_DATA_PREFIX, getAttributeValue, getBoolean, getStyleValue };
@@ -1,7 +1,19 @@
1
+ //#region src/internal/is.ts
2
+ /**
3
+ * Is the value an event target?
4
+ * @param value Value to check
5
+ * @returns `true` if it's an event target, otherwise `false`
6
+ */
1
7
  function isEventTarget(value) {
2
8
  return typeof value === "object" && value != null && typeof value.addEventListener === "function" && typeof value.removeEventListener === "function" && typeof value.dispatchEvent === "function";
3
9
  }
10
+ /**
11
+ * Is the value an HTML or SVG element?
12
+ * @param value Value to check
13
+ * @returns `true` if it's an HTML or SVG element, otherwise `false`
14
+ */
4
15
  function isHTMLOrSVGElement(value) {
5
16
  return value instanceof HTMLElement || value instanceof SVGElement;
6
17
  }
18
+ //#endregion
7
19
  export { isEventTarget, isHTMLOrSVGElement };
package/dist/is.js CHANGED
@@ -1,4 +1,10 @@
1
1
  import { isEventTarget, isHTMLOrSVGElement } from "./internal/is.js";
2
+ //#region src/is.ts
3
+ /**
4
+ * Is the value a child node?
5
+ * @param value Value to check
6
+ * @returns `true` if it's a child node, otherwise `false`
7
+ */
2
8
  function isChildNode(value) {
3
9
  return value instanceof Node && CHILD_NODE_TYPES.has(value.nodeType);
4
10
  }
@@ -14,4 +20,5 @@ var CHILD_NODE_TYPES = new Set([
14
20
  Node.COMMENT_NODE,
15
21
  Node.DOCUMENT_TYPE_NODE
16
22
  ]);
23
+ //#endregion
17
24
  export { isChildNode, isEventTarget, isHTMLOrSVGElement, isInDocument };
package/dist/style.js CHANGED
@@ -1,10 +1,25 @@
1
1
  import { getStyleValue } from "./internal/get-value.js";
2
2
  import { isHTMLOrSVGElement } from "./internal/is.js";
3
3
  import { setElementValues, updateElementValue } from "./internal/element-value.js";
4
+ //#region src/style.ts
5
+ /**
6
+ * Get a style from an element
7
+ * @param element Element to get the style from
8
+ * @param property Style name
9
+ * @param computed Get the computed style? _(defaults to `false`)_
10
+ * @returns Style value
11
+ */
4
12
  function getStyle(element, property, computed) {
5
13
  if (!isHTMLOrSVGElement(element) || typeof property !== "string") return;
6
14
  return getStyleValue(element, property, computed === true);
7
15
  }
16
+ /**
17
+ * Get styles from an element
18
+ * @param element Element to get the styles from
19
+ * @param properties Styles to get
20
+ * @param computed Get the computed styles? _(defaults to `false`)_
21
+ * @returns Style values
22
+ */
8
23
  function getStyles(element, properties, computed) {
9
24
  const styles = {};
10
25
  if (!(isHTMLOrSVGElement(element) && Array.isArray(properties))) return styles;
@@ -15,6 +30,12 @@ function getStyles(element, properties, computed) {
15
30
  }
16
31
  return styles;
17
32
  }
33
+ /**
34
+ * Get the text direction of an element
35
+ * @param element Element to get the text direction from
36
+ * @param computed Get the computed text direction? _(defaults to `false`)_
37
+ * @returns Text direction
38
+ */
18
39
  function getTextDirection(element, computed) {
19
40
  if (!(element instanceof Element)) return;
20
41
  const direction = element.getAttribute(ATTRIBUTE_DIRECTION);
@@ -22,12 +43,29 @@ function getTextDirection(element, computed) {
22
43
  const value = getStyleValue(element, "direction", computed === true);
23
44
  return value === "rtl" ? value : "ltr";
24
45
  }
46
+ /**
47
+ * Set a style on an element
48
+ * @param element Element to set the style on
49
+ * @param property Style name
50
+ * @param value Style value
51
+ */
25
52
  function setStyle(element, property, value) {
26
53
  setElementValues(element, property, value, null, updateStyleProperty);
27
54
  }
55
+ /**
56
+ * Set styles on an element
57
+ * @param element Element to set the styles on
58
+ * @param styles Styles to set
59
+ */
28
60
  function setStyles(element, styles) {
29
61
  setElementValues(element, styles, null, null, updateStyleProperty);
30
62
  }
63
+ /**
64
+ * Toggle styles for an element
65
+ * @param element Element to style
66
+ * @param styles Styles to be set or removed
67
+ * @returns Style toggler
68
+ */
31
69
  function toggleStyles(element, styles) {
32
70
  function toggle(set) {
33
71
  hasSet = set;
@@ -63,4 +101,5 @@ function updateStyleProperty(element, key, value) {
63
101
  }
64
102
  var ATTRIBUTE_DIRECTION = "dir";
65
103
  var EXPRESSION_DIRECTION = /^(ltr|rtl)$/i;
104
+ //#endregion
66
105
  export { getStyle, getStyles, getTextDirection, setStyle, setStyles, toggleStyles };
@@ -9,7 +9,10 @@ function getSupport() {
9
9
  if (typeof navigator.msMaxTouchPoints === "number" && navigator.msMaxTouchPoints > 0) return true;
10
10
  return false;
11
11
  }
12
- var touch_default = (() => {
12
+ /**
13
+ * Does the device support touch events?
14
+ */
15
+ const supportsTouch = (() => {
13
16
  let support = getSupport();
14
17
  const instance = Object.create({
15
18
  get() {
@@ -25,9 +28,19 @@ var touch_default = (() => {
25
28
  } });
26
29
  return instance;
27
30
  })();
31
+ /**
32
+ * Is the value a number?
33
+ * @param value Value to check
34
+ * @returns `true` if the value is a `number`, otherwise `false`
35
+ */
28
36
  function isNumber(value) {
29
37
  return typeof value === "number" && !Number.isNaN(value);
30
38
  }
39
+ /**
40
+ * Is the value a plain object?
41
+ * @param value Value to check
42
+ * @returns `true` if the value is a plain object, otherwise `false`
43
+ */
31
44
  function isPlainObject(value) {
32
45
  if (value === null || typeof value !== "object") return false;
33
46
  if (Symbol.toStringTag in value || Symbol.iterator in value) return false;
@@ -45,6 +58,11 @@ function compact(array, strict) {
45
58
  }
46
59
  return compacted;
47
60
  }
61
+ /**
62
+ * Get the string value from any value
63
+ * @param value Original value
64
+ * @returns String representation of the value
65
+ */
48
66
  function getString(value) {
49
67
  if (typeof value === "string") return value;
50
68
  if (value == null) return "";
@@ -53,27 +71,54 @@ function getString(value) {
53
71
  const asString = String(value.valueOf?.() ?? value);
54
72
  return asString.startsWith("[object ") ? JSON.stringify(value) : asString;
55
73
  }
56
- function ignoreKey(key) {
57
- return EXPRESSION_IGNORED.test(key);
58
- }
74
+ /**
75
+ * Join an array of values into a string
76
+ * @param value Array of values
77
+ * @param delimiter Delimiter to use between values
78
+ * @returns Joined string
79
+ */
59
80
  function join(value, delimiter) {
60
81
  return compact(value).map(getString).join(typeof delimiter === "string" ? delimiter : "");
61
82
  }
83
+ /**
84
+ * Split a string into words _(and other readable parts)_
85
+ * @param value Original string
86
+ * @returns Array of words found in the string
87
+ */
62
88
  function words(value) {
63
89
  return typeof value === "string" ? value.match(EXPRESSION_WORDS) ?? [] : [];
64
90
  }
65
- var EXPRESSION_IGNORED = /(^|\.)(__proto__|constructor|prototype)(\.|$)/i;
66
91
  var EXPRESSION_WORDS = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;
92
+ /**
93
+ * Is the value `undefined`, `null`, or a whitespace-only string?
94
+ * @param value Value to check
95
+ * @returns `true` if the value is nullable or a whitespace-only string, otherwise `false`
96
+ */
67
97
  function isNullableOrWhitespace(value) {
68
98
  return value == null || EXPRESSION_WHITESPACE$1.test(getString(value));
69
99
  }
70
100
  var EXPRESSION_WHITESPACE$1 = /^\s*$/;
101
+ /**
102
+ * Is the value an event target?
103
+ * @param value Value to check
104
+ * @returns `true` if it's an event target, otherwise `false`
105
+ */
71
106
  function isEventTarget(value) {
72
107
  return typeof value === "object" && value != null && typeof value.addEventListener === "function" && typeof value.removeEventListener === "function" && typeof value.dispatchEvent === "function";
73
108
  }
109
+ /**
110
+ * Is the value an HTML or SVG element?
111
+ * @param value Value to check
112
+ * @returns `true` if it's an HTML or SVG element, otherwise `false`
113
+ */
74
114
  function isHTMLOrSVGElement(value) {
75
115
  return value instanceof HTMLElement || value instanceof SVGElement;
76
116
  }
117
+ /**
118
+ * Is the value a child node?
119
+ * @param value Value to check
120
+ * @returns `true` if it's a child node, otherwise `false`
121
+ */
77
122
  function isChildNode(value) {
78
123
  return value instanceof Node && CHILD_NODE_TYPES.has(value.nodeType);
79
124
  }
@@ -186,6 +231,9 @@ const EXPRESSION_SOURCE_NAME = /^src$/i;
186
231
  const EXPRESSION_SOURCE_VALUE = /^data:/i;
187
232
  const EXPRESSION_URI_VALUE = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
188
233
  const EXPRESSION_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
234
+ /**
235
+ * List of boolean attributes
236
+ */
189
237
  const booleanAttributes = Object.freeze([
190
238
  "async",
191
239
  "autofocus",
@@ -224,24 +272,27 @@ const elementEvents = {
224
272
  };
225
273
  const formElement = document.createElement("form");
226
274
  let textArea;
227
- function noop() {}
228
- function calculate() {
229
- return new Promise((resolve) => {
230
- const values = [];
231
- let last;
232
- function step(now) {
233
- if (last != null) values.push(now - last);
234
- last = now;
235
- if (values.length >= TOTAL) resolve(values.sort().slice(TRIM_PART, -TRIM_PART).reduce((first, second) => first + second, 0) / (values.length - TRIM_TOTAL));
236
- else requestAnimationFrame(step);
237
- }
238
- requestAnimationFrame(step);
239
- });
275
+ /**
276
+ * Parse a JSON string into its proper value _(or `undefined` if it fails)_
277
+ * @param value JSON string to parse
278
+ * @param reviver Reviver function to transform the parsed values
279
+ * @returns Parsed value or `undefined` if parsing fails
280
+ */
281
+ function parse(value, reviver) {
282
+ try {
283
+ return JSON.parse(value, reviver);
284
+ } catch {
285
+ return;
286
+ }
240
287
  }
241
- var TOTAL = 10;
242
- var TRIM_PART = 2;
243
- var TRIM_TOTAL = 4;
244
- calculate().then((value) => {});
288
+ /**
289
+ * Clamp a number between a minimum and maximum value
290
+ * @param value Value to clamp
291
+ * @param minimum Minimum value
292
+ * @param maximum Maximum value
293
+ * @param loop If `true`, the value will loop around when smaller than the minimum or larger than the maximum _(defaults to `false`)_
294
+ * @returns Clamped value
295
+ */
245
296
  function clamp(value, minimum, maximum, loop) {
246
297
  if (![
247
298
  value,
@@ -251,8 +302,27 @@ function clamp(value, minimum, maximum, loop) {
251
302
  if (value < minimum) return loop === true ? maximum : minimum;
252
303
  return value > maximum ? loop === true ? minimum : maximum : value;
253
304
  }
305
+ function getSizedMaximum(first, second) {
306
+ let actual;
307
+ if (typeof first === "number") actual = first;
308
+ else actual = typeof second === "number" ? second : MAXIMUM_DEFAULT;
309
+ return clamp(actual, 1, MAXIMUM_ABSOLUTE);
310
+ }
311
+ var MAXIMUM_ABSOLUTE = 16777216;
312
+ var MAXIMUM_DEFAULT = 1048576;
313
+ /**
314
+ * A Map with a maximum size
315
+ *
316
+ * Behavior is similar to a _LRU_-cache, where the least recently used entries are removed
317
+ */
254
318
  var SizedMap = class extends Map {
319
+ /**
320
+ * The maximum size of the Map
321
+ */
255
322
  #maximumSize;
323
+ /**
324
+ * Is the Map full?
325
+ */
256
326
  get full() {
257
327
  return this.size >= this.#maximumSize;
258
328
  }
@@ -260,7 +330,7 @@ var SizedMap = class extends Map {
260
330
  return this.#maximumSize;
261
331
  }
262
332
  constructor(first, second) {
263
- const maximum = getMaximum(first, second);
333
+ const maximum = getSizedMaximum(first, second);
264
334
  super();
265
335
  this.#maximumSize = maximum;
266
336
  if (Array.isArray(first)) {
@@ -269,37 +339,41 @@ var SizedMap = class extends Map {
269
339
  else for (let index = 0; index < maximum; index += 1) this.set(...first[length - maximum + index]);
270
340
  }
271
341
  }
342
+ /**
343
+ * @inheritdoc
344
+ */
272
345
  get(key) {
273
346
  const value = super.get(key);
274
347
  if (value !== void 0 || this.has(key)) this.set(key, value);
275
348
  return value;
276
349
  }
350
+ /**
351
+ * @inheritdoc
352
+ */
277
353
  set(key, value) {
278
354
  if (this.has(key)) this.delete(key);
279
355
  else if (this.size >= this.#maximumSize) this.delete(this.keys().next().value);
280
356
  return super.set(key, value);
281
357
  }
282
358
  };
283
- function getMaximum(first, second) {
284
- let actual;
285
- if (typeof first === "number") actual = first;
286
- else actual = typeof second === "number" ? second : MAXIMUM_DEFAULT;
287
- return clamp(actual, 1, MAXIMUM_ABSOLUTE);
288
- }
289
- var MAXIMUM_ABSOLUTE = 16777216;
290
- var MAXIMUM_DEFAULT = 1048576;
291
359
  var Memoized = class {
292
360
  #state;
361
+ /**
362
+ * Maximum cache size
363
+ */
293
364
  get maximum() {
294
365
  return this.#state.cache?.maximum ?? NaN;
295
366
  }
367
+ /**
368
+ * Current cache size
369
+ */
296
370
  get size() {
297
371
  return this.#state.cache?.size ?? NaN;
298
372
  }
299
373
  constructor(callback, options) {
300
374
  const cache = new SizedMap(options.cacheSize);
301
375
  const getter = (...parameters) => {
302
- const key = options.cacheKey?.(...parameters) ?? (parameters.length === 1 ? parameters[0] : join(parameters.map(getString), "_"));
376
+ const key = options.cacheKey?.(...parameters) ?? (parameters.length === 1 ? parameters[0] : join(parameters.map(getString), SEPARATOR));
303
377
  if (cache.has(key)) return cache.get(key);
304
378
  const value = callback(...parameters);
305
379
  cache.set(key, value);
@@ -310,23 +384,49 @@ var Memoized = class {
310
384
  getter
311
385
  };
312
386
  }
387
+ /**
388
+ * Clear the cache
389
+ */
313
390
  clear() {
314
391
  this.#state.cache?.clear();
315
392
  }
393
+ /**
394
+ * Delete a result from the cache
395
+ * @param key Key to delete
396
+ * @returns `true` if the key existed and was removed, otherwise `false`
397
+ */
316
398
  delete(key) {
317
399
  return this.#state.cache?.delete(key) ?? false;
318
400
  }
401
+ /**
402
+ * Destroy the instance _(clearing its cache and removing its callback)_
403
+ */
319
404
  destroy() {
320
405
  this.#state.cache?.clear();
321
406
  this.#state.cache = void 0;
322
407
  this.#state.getter = void 0;
323
408
  }
409
+ /**
410
+ * Get a result from the cache
411
+ * @param key Key to get
412
+ * @returns Cached result or `undefined` if it does not exist
413
+ */
324
414
  get(key) {
325
415
  return this.#state.cache?.get(key);
326
416
  }
417
+ /**
418
+ * Does the result exist?
419
+ * @param key Key to check
420
+ * @returns `true` if the result exists, otherwise `false`
421
+ */
327
422
  has(key) {
328
423
  return this.#state.cache?.has(key) ?? false;
329
424
  }
425
+ /**
426
+ * Run the callback with the provided parameters
427
+ * @param parameters Parameters to pass to the callback
428
+ * @returns Cached or computed _(then cached)_ result
429
+ */
330
430
  run(...parameters) {
331
431
  if (this.#state.cache == null || this.#state.getter == null) throw new Error("The Memoized instance has been destroyed");
332
432
  return this.#state.getter(...parameters);
@@ -339,20 +439,42 @@ function getMemoizationOptions(input) {
339
439
  cacheSize: typeof cacheSize === "number" && cacheSize > 0 ? cacheSize : DEFAULT_CACHE_SIZE
340
440
  };
341
441
  }
442
+ /**
443
+ * Memoize a function, caching and retrieving results based on the first parameter
444
+ * @param callback Callback to memoize
445
+ * @param options Memoization options
446
+ * @returns Memoized instance
447
+ */
342
448
  function memoize(callback, options) {
343
449
  return new Memoized(callback, getMemoizationOptions(options));
344
450
  }
345
451
  var DEFAULT_CACHE_SIZE = 1024;
452
+ var SEPARATOR = "_";
453
+ /**
454
+ * Convert a string to camel case _(thisIsCamelCase)_
455
+ * @param value String to convert
456
+ * @returns Camel-cased string
457
+ */
346
458
  function camelCase(value) {
347
- return toCase("camel", value, true, false);
459
+ return toCase(CASE_CAMEL, value, true, false);
348
460
  }
461
+ /**
462
+ * Capitalize the first letter of a string _(and lowercase the rest)_
463
+ * @param value String to capitalize
464
+ * @returns Capitalized string
465
+ */
349
466
  function capitalize(value) {
350
467
  if (typeof value !== "string" || value.length === 0) return "";
351
468
  memoizedCapitalize ??= memoize((v) => v.length === 1 ? v.toLocaleUpperCase() : `${v.charAt(0).toLocaleUpperCase()}${v.slice(1).toLocaleLowerCase()}`);
352
469
  return memoizedCapitalize.run(value);
353
470
  }
471
+ /**
472
+ * Convert a string to kebab case _(this-is-kebab-case)_
473
+ * @param value String to convert
474
+ * @returns Kebab-cased string
475
+ */
354
476
  function kebabCase(value) {
355
- return toCase("kebab", value, false, false);
477
+ return toCase(CASE_KEBAB, value, false, false);
356
478
  }
357
479
  function toCase(type, value, capitalizeAny, capitalizeFirst) {
358
480
  caseMemoizers[type] ??= memoize(toCaseCallback.bind({
@@ -370,7 +492,7 @@ function toCaseCallback(value) {
370
492
  const partsLength = parts.length;
371
493
  const cased = [];
372
494
  for (let partIndex = 0; partIndex < partsLength; partIndex += 1) {
373
- const items = parts[partIndex].replace(EXPRESSION_ACRONYM, (full, one, two, three) => three === "s" ? full : `${one}-${two}${three}`).replace(EXPRESSION_CAMEL_CASE, REPLACEMENT_CAMEL_CASE).split("-");
495
+ const items = parts[partIndex].replace(EXPRESSION_ACRONYM, (full, one, two, three) => three === S ? full : `${one}-${two}${three}`).replace(EXPRESSION_CAMEL_CASE, REPLACEMENT_CAMEL_CASE).split("-");
374
496
  const itemsLength = items.length;
375
497
  const partResult = [];
376
498
  let itemCount = 0;
@@ -385,85 +507,25 @@ function toCaseCallback(value) {
385
507
  }
386
508
  return join(cased, delimiters[type]);
387
509
  }
388
- var delimiters = {
389
- camel: "",
390
- kebab: "-",
391
- pascal: "",
392
- snake: "_"
393
- };
510
+ var CASE_CAMEL = "camel";
511
+ var CASE_KEBAB = "kebab";
512
+ var CASE_PASCAL = "pascal";
513
+ var CASE_SNAKE = "snake";
514
+ var DELIMTER_EMPTY = "";
515
+ var DELIMITER_HYPHEN = "-";
516
+ var DELIMITER_UNDERSCORE = "_";
394
517
  var EXPRESSION_CAMEL_CASE = /(\p{Ll})(\p{Lu})/gu;
395
518
  var EXPRESSION_ACRONYM = /(\p{Lu}*)(\p{Lu})(\p{Ll}+)/gu;
396
- var caseMemoizers = {};
397
519
  var REPLACEMENT_CAMEL_CASE = "$1-$2";
398
- var memoizedCapitalize;
399
- function parse(value, reviver) {
400
- try {
401
- return JSON.parse(value, reviver);
402
- } catch {
403
- return;
404
- }
405
- }
406
- function findKey(needle, haystack) {
407
- const keys = Object.keys(haystack);
408
- const index = keys.map((key) => key.toLowerCase()).indexOf(needle.toLowerCase());
409
- return index > -1 ? keys[index] : needle;
410
- }
411
- function getPaths(path, lowercase) {
412
- const normalized = lowercase ? path.toLowerCase() : path;
413
- if (!EXPRESSION_NESTED.test(normalized)) return normalized;
414
- return normalized.replace(EXPRESSION_BRACKET, ".$1").replace(EXPRESSION_DOTS, "").split(".");
415
- }
416
- function handleValue(data, path, value, get, ignoreCase) {
417
- if (typeof data === "object" && data !== null && !ignoreKey(path)) {
418
- const key = ignoreCase ? findKey(path, data) : path;
419
- if (get) return data[key];
420
- data[key] = typeof value === "function" ? value(data[key]) : value;
421
- }
422
- }
423
- var EXPRESSION_BRACKET = /\[(\w+)\]/g;
424
- var EXPRESSION_DOTS = /^\.|\.$/g;
425
- var EXPRESSION_NESTED = /\.|\[\w+\]/;
426
- function getValue(data, path, ignoreCase) {
427
- if (typeof data !== "object" || data === null || typeof path !== "string" || path.trim().length === 0) return;
428
- const shouldIgnoreCase = ignoreCase === true;
429
- const paths = getPaths(path, shouldIgnoreCase);
430
- if (typeof paths === "string") return handleValue(data, paths, null, true, shouldIgnoreCase);
431
- const { length } = paths;
432
- let index = 0;
433
- let value = data;
434
- while (index < length && value != null) value = handleValue(value, paths[index++], null, true, shouldIgnoreCase);
435
- return value;
436
- }
437
- function getTemplateOptions(input) {
438
- const options = isPlainObject(input) ? input : {};
439
- return {
440
- ignoreCase: options.ignoreCase === true,
441
- pattern: options.pattern instanceof RegExp ? options.pattern : EXPRESSION_VARIABLE
442
- };
443
- }
444
- function handleTemplate(value, pattern, ignoreCase, variables) {
445
- if (typeof value !== "string") return "";
446
- if (typeof variables !== "object" || variables === null) return value;
447
- const values = {};
448
- return value.replace(pattern, (_, key) => {
449
- if (values[key] == null) {
450
- const templateValue = getValue(variables, key, ignoreCase);
451
- values[key] = templateValue == null ? "" : getString(templateValue);
452
- }
453
- return values[key];
454
- });
455
- }
456
- function template(value, variables, options) {
457
- const { ignoreCase, pattern } = getTemplateOptions(options);
458
- return handleTemplate(value, pattern, ignoreCase, variables);
459
- }
460
- template.initialize = function(options) {
461
- const { ignoreCase, pattern } = getTemplateOptions(options);
462
- return (value, variables) => {
463
- return handleTemplate(value, pattern, ignoreCase, variables);
464
- };
520
+ var S = "s";
521
+ var caseMemoizers = {};
522
+ var delimiters = {
523
+ [CASE_CAMEL]: DELIMTER_EMPTY,
524
+ [CASE_KEBAB]: DELIMITER_HYPHEN,
525
+ [CASE_PASCAL]: DELIMTER_EMPTY,
526
+ [CASE_SNAKE]: DELIMITER_UNDERSCORE
465
527
  };
466
- var EXPRESSION_VARIABLE = /{{([\s\S]+?)}}/g;
528
+ var memoizedCapitalize;
467
529
  function getBoolean(value, defaultValue) {
468
530
  return typeof value === "boolean" ? value : defaultValue ?? false;
469
531
  }
@@ -515,6 +577,163 @@ function updateDataAttribute(element, key, value) {
515
577
  updateElementValue(element, getName(key), value, element.setAttribute, element.removeAttribute, false, true);
516
578
  }
517
579
  const ATTRIBUTE_DATA_PREFIX = "data-";
580
+ /**
581
+ * A function that does nothing, which can be useful, I guess…
582
+ */
583
+ function noop() {}
584
+ /**
585
+ * Asserts that a condition is true, throwing an error if it is not
586
+ * @param condition Condition to assert
587
+ * @param message Error message
588
+ * @param error Error constructor
589
+ */
590
+ function assert(condition, message, error) {
591
+ if (!condition()) throw new (error ?? Error)(message);
592
+ }
593
+ assert.condition = assertCondition;
594
+ assert.defined = assertDefined;
595
+ assert.instanceOf = assertInstanceOf;
596
+ assert.is = assertIs;
597
+ /**
598
+ * Creates an asserter that asserts a condition is true, throwing an error if it is not
599
+ * @param condition Condition to assert
600
+ * @param message Error message
601
+ * @param error Error constructor
602
+ * @returns Asserter
603
+ */
604
+ function assertCondition(condition, message, error) {
605
+ return (value) => {
606
+ assert(() => condition(value), message, error);
607
+ };
608
+ }
609
+ /**
610
+ * Asserts that a value is defined throwing an error if it is not
611
+ * @param value Value to assert
612
+ * @param message Error message
613
+ */
614
+ function assertDefined(value, message) {
615
+ assert(() => value != null, message ?? MESSAGE_VALUE_DEFINED);
616
+ }
617
+ /**
618
+ * Creates an asserter that asserts a value is an instance of a constructor, throwing an error if it is not
619
+ * @param constructor Constructor to check against
620
+ * @param message Error message
621
+ * @param error Error constructor
622
+ * @returns Asserter
623
+ */
624
+ function assertInstanceOf(constructor, message, error) {
625
+ return (value) => {
626
+ assert(() => value instanceof constructor, message, error);
627
+ };
628
+ }
629
+ /**
630
+ * Creates an asserter that asserts a value is of a specific type, throwing an error if it is not
631
+ * @param condition Type guard function to check the value
632
+ * @param message Error message
633
+ * @param error Error constructor
634
+ * @returns Asserter
635
+ */
636
+ function assertIs(condition, message, error) {
637
+ return (value) => {
638
+ assert(() => condition(value), message, error);
639
+ };
640
+ }
641
+ var MESSAGE_VALUE_DEFINED = "Expected value to be defined";
642
+ /**
643
+ * Create an asynchronous function that can only be called once, rejecting or resolving the same result on subsequent calls
644
+ * @param callback Callback to use once
645
+ * @returns Once callback
646
+ */
647
+ function asyncOnce(callback) {
648
+ assert(() => typeof callback === "function", MESSAGE_EXPECTATION);
649
+ const state = {
650
+ called: false,
651
+ cleared: false,
652
+ error: false,
653
+ finished: false,
654
+ items: [],
655
+ value: void 0
656
+ };
657
+ const fn = (...parameters) => {
658
+ if (state.cleared) return Promise.reject(new Error(MESSAGE_CLEARED));
659
+ if (state.finished) return state.error ? Promise.reject(state.value) : Promise.resolve(state.value);
660
+ if (state.called) return new Promise((resolve, reject) => {
661
+ state.items.push({
662
+ reject,
663
+ resolve
664
+ });
665
+ });
666
+ state.called = true;
667
+ return new Promise((resolve, reject) => {
668
+ state.items.push({
669
+ reject,
670
+ resolve
671
+ });
672
+ callback(...parameters).then((value) => {
673
+ handleResult(state, value, false);
674
+ }).catch((error) => {
675
+ handleResult(state, error, true);
676
+ });
677
+ });
678
+ };
679
+ Object.defineProperties(fn, {
680
+ called: { get: () => state.called },
681
+ cleared: { get: () => state.cleared },
682
+ error: { get: () => state.error },
683
+ finished: { get: () => state.finished }
684
+ });
685
+ fn.clear = () => {
686
+ if (!state.called || !state.finished || state.cleared) return;
687
+ state.cleared = true;
688
+ state.value = void 0;
689
+ };
690
+ return fn;
691
+ }
692
+ function handleResult(state, value, error) {
693
+ state.error = error;
694
+ state.finished = true;
695
+ state.value = value;
696
+ const items = state.items.splice(0);
697
+ const { length } = items;
698
+ for (let index = 0; index < length; index += 1) {
699
+ const { reject, resolve } = items[index];
700
+ if (error) reject(value);
701
+ else resolve(value);
702
+ }
703
+ }
704
+ /**
705
+ * Create a function that can only be called once, returning the same value on subsequent calls
706
+ * @param callback Callback to use once
707
+ * @returns Once callback
708
+ */
709
+ function once(callback) {
710
+ assert(() => typeof callback === "function", MESSAGE_EXPECTATION);
711
+ const state = {
712
+ called: false,
713
+ cleared: false,
714
+ value: void 0
715
+ };
716
+ const fn = (...parameters) => {
717
+ if (state.cleared) throw new Error(MESSAGE_CLEARED);
718
+ if (state.called) return state.value;
719
+ state.called = true;
720
+ state.value = callback(...parameters);
721
+ return state.value;
722
+ };
723
+ Object.defineProperties(fn, {
724
+ called: { get: () => state.called },
725
+ cleared: { get: () => state.cleared }
726
+ });
727
+ fn.clear = () => {
728
+ if (!state.called || state.cleared) return;
729
+ state.cleared = true;
730
+ state.value = void 0;
731
+ };
732
+ return fn;
733
+ }
734
+ once.async = asyncOnce;
735
+ var MESSAGE_CLEARED = "Once has been cleared";
736
+ var MESSAGE_EXPECTATION = "Once expected a function";
518
737
  function addDelegatedHandler(doc, type, name, passive) {
519
738
  if (DELEGATED.has(name)) return;
520
739
  DELEGATED.add(name);
@@ -622,6 +841,11 @@ function createEventOptions(options) {
622
841
  function dispatch(target, type, options) {
623
842
  if (isEventTarget(target) && typeof type === "string") target.dispatchEvent(createEvent(type, options));
624
843
  }
844
+ /**
845
+ * Get the X- and Y-coordinates from a pointer event
846
+ * @param event Pointer event
847
+ * @returns X- and Y-coordinates
848
+ */
625
849
  function getPosition(event) {
626
850
  let x;
627
851
  let y;
@@ -637,6 +861,13 @@ function getPosition(event) {
637
861
  y
638
862
  } : void 0;
639
863
  }
864
+ /**
865
+ * Remove an event listener
866
+ * @param target Event target
867
+ * @param type Type of event
868
+ * @param listener Event listener
869
+ * @param options Options for event
870
+ */
640
871
  function off(target, type, listener, options) {
641
872
  if (!isEventTarget(target) || typeof type !== "string" || typeof listener !== "function") return;
642
873
  const extended = createEventOptions(options);
@@ -691,6 +922,12 @@ function findRelatives(origin, selector, context) {
691
922
  }
692
923
  return distances.filter((found) => found.distance === minimum).map((found) => found.element);
693
924
  }
925
+ /**
926
+ * Get the distance between two elements _(i.e., the amount of nodes of between them)_
927
+ * @param origin Origin element
928
+ * @param target Target element
929
+ * @returns Distance between elements, or `-1` if distance cannot be calculated
930
+ */
694
931
  function getDistance(origin, target) {
695
932
  if (origin === target) return 0;
696
933
  if (origin.parentElement === target || target.parentElement === origin) return 1;
@@ -766,6 +1003,14 @@ function findElementOrElementsFromNodes(array, context, contexts) {
766
1003
  function findElements(selector, context) {
767
1004
  return findElementOrElements(selector, context, false);
768
1005
  }
1006
+ /**
1007
+ * Get the most specific element under the pointer
1008
+ *
1009
+ * - Ignores elements with `pointer-events: none` and `visibility: hidden`
1010
+ * - _(If `skipIgnore` is `true`, no elements are ignored)_
1011
+ * @param skipIgnore Skip ignored elements?
1012
+ * @returns Found element or `null`
1013
+ */
769
1014
  function getElementUnderPointer(skipIgnore) {
770
1015
  const elements = [...document.querySelectorAll(SUFFIX_HOVER)];
771
1016
  const { length } = elements;
@@ -787,6 +1032,11 @@ const STYLE_HIDDEN$1 = "hidden";
787
1032
  const STYLE_NONE$1 = "none";
788
1033
  const SUFFIX_HOVER = ":hover";
789
1034
  const TAG_HEAD = "HEAD";
1035
+ /**
1036
+ * Get a list of focusable elements within a parent element
1037
+ * @param parent Parent element
1038
+ * @returns Focusable elements
1039
+ */
790
1040
  function getFocusable(parent) {
791
1041
  return getValidElements(parent, FILTERS_FOCUSABLE, false);
792
1042
  }
@@ -796,6 +1046,11 @@ function getItem(element, tabbable) {
796
1046
  tabIndex: tabbable ? getTabIndex(element) : TABINDEX_DEFAULT
797
1047
  };
798
1048
  }
1049
+ /**
1050
+ * Get a list of tabbable elements within a parent element
1051
+ * @param parent Parent element
1052
+ * @returns Tabbable elements
1053
+ */
799
1054
  function getTabbable(parent) {
800
1055
  return getValidElements(parent, FILTERS_TABBABLE, true);
801
1056
  }
@@ -850,6 +1105,11 @@ function isDisabledFromFieldset(element) {
850
1105
  function isEditable(element) {
851
1106
  return EXPRESSION_TRUEISH.test(element.getAttribute(ATTRIBUTE_CONTENTEDITABLE));
852
1107
  }
1108
+ /**
1109
+ * Is the element focusable?
1110
+ * @param element Element to check
1111
+ * @returns `true` if focusable, otherwise `false`
1112
+ */
853
1113
  function isFocusable(element) {
854
1114
  return element instanceof Element ? isValidElement(element, FILTERS_FOCUSABLE, false) : false;
855
1115
  }
@@ -880,6 +1140,11 @@ function isNotTabbableRadio(item) {
880
1140
  function isSummarised(item) {
881
1141
  return item.element instanceof HTMLDetailsElement && [...item.element.children].some((child) => EXPRESSION_SUMMARY.test(child.tagName));
882
1142
  }
1143
+ /**
1144
+ * Is the element tabbable?
1145
+ * @param element Element to check
1146
+ * @returns `true` if tabbable, otherwise `false`
1147
+ */
883
1148
  function isTabbable(element) {
884
1149
  return element instanceof Element ? isValidElement(element, FILTERS_TABBABLE, true) : false;
885
1150
  }
@@ -935,6 +1200,11 @@ function handleElement(element, depth) {
935
1200
  }
936
1201
  sanitizeAttributes(element, [...element.attributes]);
937
1202
  }
1203
+ /**
1204
+ * Is the element clobbered?
1205
+ *
1206
+ * Thanks, DOMPurify _(https://github.com/cure53/DOMPurify)_
1207
+ */
938
1208
  function isClobbered(value) {
939
1209
  return value instanceof HTMLFormElement && (typeof value.nodeName !== "string" || typeof value.textContent !== "string" || typeof value.removeChild !== "function" || !(value.attributes instanceof NamedNodeMap) || typeof value.removeAttribute !== "function" || typeof value.setAttribute !== "function" || typeof value.namespaceURI !== "string" || typeof value.insertBefore !== "function" || typeof value.hasChildNodes !== "function");
940
1210
  }
@@ -1034,6 +1304,12 @@ html.remove = (template) => {
1034
1304
  }
1035
1305
  templates = updated;
1036
1306
  };
1307
+ /**
1308
+ * Sanitize one or more nodes, recursively
1309
+ * @param value Node or nodes to sanitize
1310
+ * @param options Sanitization options
1311
+ * @returns Sanitized nodes
1312
+ */
1037
1313
  function sanitize(value) {
1038
1314
  return sanitizeNodes(Array.isArray(value) ? value : [value], 0);
1039
1315
  }
@@ -1043,10 +1319,24 @@ const TEMPLATE_TAG = "template";
1043
1319
  const TEMPORARY_ELEMENT = "<toretto-temporary></toretto-temporary>";
1044
1320
  let parser;
1045
1321
  let templates = {};
1322
+ /**
1323
+ * Get a style from an element
1324
+ * @param element Element to get the style from
1325
+ * @param property Style name
1326
+ * @param computed Get the computed style? _(defaults to `false`)_
1327
+ * @returns Style value
1328
+ */
1046
1329
  function getStyle(element, property, computed) {
1047
1330
  if (!isHTMLOrSVGElement(element) || typeof property !== "string") return;
1048
1331
  return getStyleValue(element, property, computed === true);
1049
1332
  }
1333
+ /**
1334
+ * Get styles from an element
1335
+ * @param element Element to get the styles from
1336
+ * @param properties Styles to get
1337
+ * @param computed Get the computed styles? _(defaults to `false`)_
1338
+ * @returns Style values
1339
+ */
1050
1340
  function getStyles(element, properties, computed) {
1051
1341
  const styles = {};
1052
1342
  if (!(isHTMLOrSVGElement(element) && Array.isArray(properties))) return styles;
@@ -1057,6 +1347,12 @@ function getStyles(element, properties, computed) {
1057
1347
  }
1058
1348
  return styles;
1059
1349
  }
1350
+ /**
1351
+ * Get the text direction of an element
1352
+ * @param element Element to get the text direction from
1353
+ * @param computed Get the computed text direction? _(defaults to `false`)_
1354
+ * @returns Text direction
1355
+ */
1060
1356
  function getTextDirection(element, computed) {
1061
1357
  if (!(element instanceof Element)) return;
1062
1358
  const direction = element.getAttribute(ATTRIBUTE_DIRECTION);
@@ -1064,12 +1360,29 @@ function getTextDirection(element, computed) {
1064
1360
  const value = getStyleValue(element, "direction", computed === true);
1065
1361
  return value === "rtl" ? value : "ltr";
1066
1362
  }
1363
+ /**
1364
+ * Set a style on an element
1365
+ * @param element Element to set the style on
1366
+ * @param property Style name
1367
+ * @param value Style value
1368
+ */
1067
1369
  function setStyle(element, property, value) {
1068
1370
  setElementValues(element, property, value, null, updateStyleProperty);
1069
1371
  }
1372
+ /**
1373
+ * Set styles on an element
1374
+ * @param element Element to set the styles on
1375
+ * @param styles Styles to set
1376
+ */
1070
1377
  function setStyles(element, styles) {
1071
1378
  setElementValues(element, styles, null, null, updateStyleProperty);
1072
1379
  }
1380
+ /**
1381
+ * Toggle styles for an element
1382
+ * @param element Element to style
1383
+ * @param styles Styles to be set or removed
1384
+ * @returns Style toggler
1385
+ */
1073
1386
  function toggleStyles(element, styles) {
1074
1387
  function toggle(set) {
1075
1388
  hasSet = set;
@@ -1105,4 +1418,4 @@ function updateStyleProperty(element, key, value) {
1105
1418
  }
1106
1419
  const ATTRIBUTE_DIRECTION = "dir";
1107
1420
  const EXPRESSION_DIRECTION = /^(ltr|rtl)$/i;
1108
- export { findElement as $, findElement, findElements as $$, findElements, dispatch, findAncestor, findRelatives, getData, getDistance, getElementUnderPointer, getFocusable, getPosition, getStyle, getStyles, getTabbable, getTextDirection, html, isBadAttribute, isBooleanAttribute, isChildNode, isEmptyNonBooleanAttribute, isEventTarget, isFocusable, isHTMLOrSVGElement, isInDocument, isInvalidBooleanAttribute, isTabbable, off, on, sanitize, setData, setStyle, setStyles, touch_default as supportsTouch, toggleStyles };
1421
+ export { findElement as $, findElement, findElements as $$, findElements, dispatch, findAncestor, findRelatives, getData, getDistance, getElementUnderPointer, getFocusable, getPosition, getStyle, getStyles, getTabbable, getTextDirection, html, isBadAttribute, isBooleanAttribute, isChildNode, isEmptyNonBooleanAttribute, isEventTarget, isFocusable, isHTMLOrSVGElement, isInDocument, isInvalidBooleanAttribute, isTabbable, off, on, sanitize, setData, setStyle, setStyles, supportsTouch, toggleStyles };
package/dist/touch.js CHANGED
@@ -1,3 +1,4 @@
1
+ //#region src/touch.ts
1
2
  function getSupport() {
2
3
  if (window == null || navigator == null) return false;
3
4
  if ("matchMedia" in window) {
@@ -9,7 +10,10 @@ function getSupport() {
9
10
  if (typeof navigator.msMaxTouchPoints === "number" && navigator.msMaxTouchPoints > 0) return true;
10
11
  return false;
11
12
  }
12
- var touch_default = (() => {
13
+ /**
14
+ * Does the device support touch events?
15
+ */
16
+ var supportsTouch = (() => {
13
17
  let support = getSupport();
14
18
  const instance = Object.create({
15
19
  get() {
@@ -25,4 +29,5 @@ var touch_default = (() => {
25
29
  } });
26
30
  return instance;
27
31
  })();
28
- export { touch_default as default };
32
+ //#endregion
33
+ export { supportsTouch as default };
package/package.json CHANGED
@@ -4,19 +4,19 @@
4
4
  "url": "https://oscarpalmer.se"
5
5
  },
6
6
  "dependencies": {
7
- "@oscarpalmer/atoms": "^0.130.0"
7
+ "@oscarpalmer/atoms": "^0.162"
8
8
  },
9
9
  "description": "A collection of badass DOM utilities.",
10
10
  "devDependencies": {
11
- "@types/node": "^25.1",
11
+ "@types/node": "^25.4",
12
12
  "@vitest/coverage-istanbul": "^4",
13
- "jsdom": "^27.4",
14
- "oxfmt": "^0.27",
15
- "oxlint": "^1.42",
16
- "rolldown": "1.0.0-rc.2",
13
+ "jsdom": "^28.1",
14
+ "oxfmt": "^0.39",
15
+ "oxlint": "^1.54",
16
+ "rolldown": "1.0.0-rc.9",
17
17
  "tslib": "^2.8",
18
18
  "typescript": "^5.9",
19
- "vite": "8.0.0-beta.11",
19
+ "vite": "8.0.0-beta.17",
20
20
  "vitest": "^4"
21
21
  },
22
22
  "exports": {
@@ -93,5 +93,5 @@
93
93
  },
94
94
  "type": "module",
95
95
  "types": "types/index.d.ts",
96
- "version": "0.37.2"
96
+ "version": "0.39.0"
97
97
  }
package/src/data.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type {PlainObject} from '@oscarpalmer/atoms';
2
- import {kebabCase, parse} from '@oscarpalmer/atoms/string';
2
+ import {parse} from '@oscarpalmer/atoms/string';
3
+ import {kebabCase} from '@oscarpalmer/atoms/string/case';
3
4
  import {setElementValues, updateElementValue} from './internal/element-value';
4
5
  import {EXPRESSION_DATA_PREFIX} from './internal/get-value';
5
6
  import {isHTMLOrSVGElement} from './internal/is';
@@ -1,4 +1,5 @@
1
- import {camelCase, kebabCase, parse} from '@oscarpalmer/atoms/string';
1
+ import {parse} from '@oscarpalmer/atoms/string';
2
+ import {camelCase, kebabCase} from '@oscarpalmer/atoms/string/case';
2
3
 
3
4
  export function getBoolean(value: unknown, defaultValue?: boolean): boolean {
4
5
  return typeof value === 'boolean' ? value : (defaultValue ?? false);