@oscarpalmer/toretto 0.37.1 → 0.38.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.
@@ -3,6 +3,13 @@ import { isHTMLOrSVGElement } from "../internal/is.js";
3
3
  function getAttribute(element, name, parseValues) {
4
4
  if (isHTMLOrSVGElement(element) && typeof name === "string") return getAttributeValue(element, name, parseValues !== false);
5
5
  }
6
+ /**
7
+ * Get specific attributes from an element
8
+ * @param element Element to get attributes from
9
+ * @param names Attribute names
10
+ * @param parseData Parse data values? _(defaults to `true`)_
11
+ * @returns Object of named attributes
12
+ */
6
13
  function getAttributes(element, names, parseData) {
7
14
  const attributes = {};
8
15
  if (!(isHTMLOrSVGElement(element) && Array.isArray(names))) return attributes;
package/dist/data.js CHANGED
@@ -1,7 +1,8 @@
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";
5
6
  function getData(element, keys, parseValues) {
6
7
  if (!isHTMLOrSVGElement(element)) return;
7
8
  const shouldParse = parseValues !== false;
@@ -29,6 +29,11 @@ function createEventOptions(options) {
29
29
  function dispatch(target, type, options) {
30
30
  if (isEventTarget(target) && typeof type === "string") target.dispatchEvent(createEvent(type, options));
31
31
  }
32
+ /**
33
+ * Get the X- and Y-coordinates from a pointer event
34
+ * @param event Pointer event
35
+ * @returns X- and Y-coordinates
36
+ */
32
37
  function getPosition(event) {
33
38
  let x;
34
39
  let y;
@@ -44,6 +49,13 @@ function getPosition(event) {
44
49
  y
45
50
  } : void 0;
46
51
  }
52
+ /**
53
+ * Remove an event listener
54
+ * @param target Event target
55
+ * @param type Type of event
56
+ * @param listener Event listener
57
+ * @param options Options for event
58
+ */
47
59
  function off(target, type, listener, options) {
48
60
  if (!isEventTarget(target) || typeof type !== "string" || typeof listener !== "function") return;
49
61
  const extended = createEventOptions(options);
@@ -40,6 +40,14 @@ function findElementOrElementsFromNodes(array, context, contexts) {
40
40
  function findElements(selector, context) {
41
41
  return findElementOrElements(selector, context, false);
42
42
  }
43
+ /**
44
+ * Get the most specific element under the pointer
45
+ *
46
+ * - Ignores elements with `pointer-events: none` and `visibility: hidden`
47
+ * - _(If `skipIgnore` is `true`, no elements are ignored)_
48
+ * @param skipIgnore Skip ignored elements?
49
+ * @returns Found element or `null`
50
+ */
43
51
  function getElementUnderPointer(skipIgnore) {
44
52
  const elements = [...document.querySelectorAll(SUFFIX_HOVER)];
45
53
  const { length } = elements;
@@ -1,10 +1,9 @@
1
1
  function findAncestor(origin, selector) {
2
2
  const element = getElement(origin);
3
3
  if (element == null || selector == null) return null;
4
- console.log(element);
5
4
  if (typeof selector === "string") {
6
- if (element.matches?.(selector)) return element;
7
- return element.closest(selector);
5
+ if (Element.prototype.matches.call(element, selector)) return element;
6
+ return Element.prototype.closest.call(element, selector);
8
7
  }
9
8
  if (typeof selector !== "function") return null;
10
9
  if (selector(element)) return element;
@@ -35,6 +34,12 @@ function findRelatives(origin, selector, context) {
35
34
  }
36
35
  return distances.filter((found) => found.distance === minimum).map((found) => found.element);
37
36
  }
37
+ /**
38
+ * Get the distance between two elements _(i.e., the amount of nodes of between them)_
39
+ * @param origin Origin element
40
+ * @param target Target element
41
+ * @returns Distance between elements, or `-1` if distance cannot be calculated
42
+ */
38
43
  function getDistance(origin, target) {
39
44
  if (origin === target) return 0;
40
45
  if (origin.parentElement === target || target.parentElement === origin) return 1;
package/dist/focusable.js CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Get a list of focusable elements within a parent element
3
+ * @param parent Parent element
4
+ * @returns Focusable elements
5
+ */
1
6
  function getFocusable(parent) {
2
7
  return getValidElements(parent, FILTERS_FOCUSABLE, false);
3
8
  }
@@ -7,6 +12,11 @@ function getItem(element, tabbable) {
7
12
  tabIndex: tabbable ? getTabIndex(element) : TABINDEX_DEFAULT
8
13
  };
9
14
  }
15
+ /**
16
+ * Get a list of tabbable elements within a parent element
17
+ * @param parent Parent element
18
+ * @returns Tabbable elements
19
+ */
10
20
  function getTabbable(parent) {
11
21
  return getValidElements(parent, FILTERS_TABBABLE, true);
12
22
  }
@@ -61,6 +71,11 @@ function isDisabledFromFieldset(element) {
61
71
  function isEditable(element) {
62
72
  return EXPRESSION_TRUEISH.test(element.getAttribute(ATTRIBUTE_CONTENTEDITABLE));
63
73
  }
74
+ /**
75
+ * Is the element focusable?
76
+ * @param element Element to check
77
+ * @returns `true` if focusable, otherwise `false`
78
+ */
64
79
  function isFocusable(element) {
65
80
  return element instanceof Element ? isValidElement(element, FILTERS_FOCUSABLE, false) : false;
66
81
  }
@@ -91,6 +106,11 @@ function isNotTabbableRadio(item) {
91
106
  function isSummarised(item) {
92
107
  return item.element instanceof HTMLDetailsElement && [...item.element.children].some((child) => EXPRESSION_SUMMARY.test(child.tagName));
93
108
  }
109
+ /**
110
+ * Is the element tabbable?
111
+ * @param element Element to check
112
+ * @returns `true` if tabbable, otherwise `false`
113
+ */
94
114
  function isTabbable(element) {
95
115
  return element instanceof Element ? isValidElement(element, FILTERS_TABBABLE, true) : false;
96
116
  }
@@ -54,6 +54,12 @@ html.remove = (template) => {
54
54
  }
55
55
  templates = updated;
56
56
  };
57
+ /**
58
+ * Sanitize one or more nodes, recursively
59
+ * @param value Node or nodes to sanitize
60
+ * @param options Sanitization options
61
+ * @returns Sanitized nodes
62
+ */
57
63
  function sanitize(value) {
58
64
  return sanitizeNodes(Array.isArray(value) ? value : [value], 0);
59
65
  }
@@ -7,6 +7,11 @@ function handleElement(element, depth) {
7
7
  }
8
8
  sanitizeAttributes(element, [...element.attributes]);
9
9
  }
10
+ /**
11
+ * Is the element clobbered?
12
+ *
13
+ * Thanks, DOMPurify _(https://github.com/cure53/DOMPurify)_
14
+ */
10
15
  function isClobbered(value) {
11
16
  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
17
  }
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 };
@@ -70,6 +70,9 @@ var EXPRESSION_SOURCE_NAME = /^src$/i;
70
70
  var EXPRESSION_SOURCE_VALUE = /^data:/i;
71
71
  var EXPRESSION_URI_VALUE = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
72
72
  var EXPRESSION_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
73
+ /**
74
+ * List of boolean attributes
75
+ */
73
76
  const booleanAttributes = Object.freeze([
74
77
  "async",
75
78
  "autofocus",
@@ -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
  function getBoolean(value, defaultValue) {
3
4
  return typeof value === "boolean" ? value : defaultValue ?? false;
4
5
  }
@@ -1,6 +1,16 @@
1
+ /**
2
+ * Is the value an event target?
3
+ * @param value Value to check
4
+ * @returns `true` if it's an event target, otherwise `false`
5
+ */
1
6
  function isEventTarget(value) {
2
7
  return typeof value === "object" && value != null && typeof value.addEventListener === "function" && typeof value.removeEventListener === "function" && typeof value.dispatchEvent === "function";
3
8
  }
9
+ /**
10
+ * Is the value an HTML or SVG element?
11
+ * @param value Value to check
12
+ * @returns `true` if it's an HTML or SVG element, otherwise `false`
13
+ */
4
14
  function isHTMLOrSVGElement(value) {
5
15
  return value instanceof HTMLElement || value instanceof SVGElement;
6
16
  }
package/dist/is.js CHANGED
@@ -1,4 +1,9 @@
1
1
  import { isEventTarget, isHTMLOrSVGElement } from "./internal/is.js";
2
+ /**
3
+ * Is the value a child node?
4
+ * @param value Value to check
5
+ * @returns `true` if it's a child node, otherwise `false`
6
+ */
2
7
  function isChildNode(value) {
3
8
  return value instanceof Node && CHILD_NODE_TYPES.has(value.nodeType);
4
9
  }
package/dist/style.js CHANGED
@@ -1,10 +1,24 @@
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
+ /**
5
+ * Get a style from an element
6
+ * @param element Element to get the style from
7
+ * @param property Style name
8
+ * @param computed Get the computed style? _(defaults to `false`)_
9
+ * @returns Style value
10
+ */
4
11
  function getStyle(element, property, computed) {
5
12
  if (!isHTMLOrSVGElement(element) || typeof property !== "string") return;
6
13
  return getStyleValue(element, property, computed === true);
7
14
  }
15
+ /**
16
+ * Get styles from an element
17
+ * @param element Element to get the styles from
18
+ * @param properties Styles to get
19
+ * @param computed Get the computed styles? _(defaults to `false`)_
20
+ * @returns Style values
21
+ */
8
22
  function getStyles(element, properties, computed) {
9
23
  const styles = {};
10
24
  if (!(isHTMLOrSVGElement(element) && Array.isArray(properties))) return styles;
@@ -15,6 +29,12 @@ function getStyles(element, properties, computed) {
15
29
  }
16
30
  return styles;
17
31
  }
32
+ /**
33
+ * Get the text direction of an element
34
+ * @param element Element to get the text direction from
35
+ * @param computed Get the computed text direction? _(defaults to `false`)_
36
+ * @returns Text direction
37
+ */
18
38
  function getTextDirection(element, computed) {
19
39
  if (!(element instanceof Element)) return;
20
40
  const direction = element.getAttribute(ATTRIBUTE_DIRECTION);
@@ -22,12 +42,29 @@ function getTextDirection(element, computed) {
22
42
  const value = getStyleValue(element, "direction", computed === true);
23
43
  return value === "rtl" ? value : "ltr";
24
44
  }
45
+ /**
46
+ * Set a style on an element
47
+ * @param element Element to set the style on
48
+ * @param property Style name
49
+ * @param value Style value
50
+ */
25
51
  function setStyle(element, property, value) {
26
52
  setElementValues(element, property, value, null, updateStyleProperty);
27
53
  }
54
+ /**
55
+ * Set styles on an element
56
+ * @param element Element to set the styles on
57
+ * @param styles Styles to set
58
+ */
28
59
  function setStyles(element, styles) {
29
60
  setElementValues(element, styles, null, null, updateStyleProperty);
30
61
  }
62
+ /**
63
+ * Toggle styles for an element
64
+ * @param element Element to style
65
+ * @param styles Styles to be set or removed
66
+ * @returns Style toggler
67
+ */
31
68
  function toggleStyles(element, styles) {
32
69
  function toggle(set) {
33
70
  hasSet = set;
@@ -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,30 +339,34 @@ 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
  }
@@ -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,18 +439,39 @@ 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
+ /**
453
+ * Convert a string to camel case _(thisIsCamelCase)_
454
+ * @param value String to convert
455
+ * @returns Camel-cased string
456
+ */
346
457
  function camelCase(value) {
347
458
  return toCase("camel", value, true, false);
348
459
  }
460
+ /**
461
+ * Capitalize the first letter of a string _(and lowercase the rest)_
462
+ * @param value String to capitalize
463
+ * @returns Capitalized string
464
+ */
349
465
  function capitalize(value) {
350
466
  if (typeof value !== "string" || value.length === 0) return "";
351
467
  memoizedCapitalize ??= memoize((v) => v.length === 1 ? v.toLocaleUpperCase() : `${v.charAt(0).toLocaleUpperCase()}${v.slice(1).toLocaleLowerCase()}`);
352
468
  return memoizedCapitalize.run(value);
353
469
  }
470
+ /**
471
+ * Convert a string to kebab case _(this-is-kebab-case)_
472
+ * @param value String to convert
473
+ * @returns Kebab-cased string
474
+ */
354
475
  function kebabCase(value) {
355
476
  return toCase("kebab", value, false, false);
356
477
  }
@@ -385,6 +506,7 @@ function toCaseCallback(value) {
385
506
  }
386
507
  return join(cased, delimiters[type]);
387
508
  }
509
+ var caseMemoizers = {};
388
510
  var delimiters = {
389
511
  camel: "",
390
512
  kebab: "-",
@@ -393,77 +515,8 @@ var delimiters = {
393
515
  };
394
516
  var EXPRESSION_CAMEL_CASE = /(\p{Ll})(\p{Lu})/gu;
395
517
  var EXPRESSION_ACRONYM = /(\p{Lu}*)(\p{Lu})(\p{Ll}+)/gu;
396
- var caseMemoizers = {};
397
518
  var REPLACEMENT_CAMEL_CASE = "$1-$2";
398
519
  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
- };
465
- };
466
- var EXPRESSION_VARIABLE = /{{([\s\S]+?)}}/g;
467
520
  function getBoolean(value, defaultValue) {
468
521
  return typeof value === "boolean" ? value : defaultValue ?? false;
469
522
  }
@@ -515,6 +568,10 @@ function updateDataAttribute(element, key, value) {
515
568
  updateElementValue(element, getName(key), value, element.setAttribute, element.removeAttribute, false, true);
516
569
  }
517
570
  const ATTRIBUTE_DATA_PREFIX = "data-";
571
+ /**
572
+ * A function that does nothing, which can be useful, I guess…
573
+ */
574
+ function noop() {}
518
575
  function addDelegatedHandler(doc, type, name, passive) {
519
576
  if (DELEGATED.has(name)) return;
520
577
  DELEGATED.add(name);
@@ -622,6 +679,11 @@ function createEventOptions(options) {
622
679
  function dispatch(target, type, options) {
623
680
  if (isEventTarget(target) && typeof type === "string") target.dispatchEvent(createEvent(type, options));
624
681
  }
682
+ /**
683
+ * Get the X- and Y-coordinates from a pointer event
684
+ * @param event Pointer event
685
+ * @returns X- and Y-coordinates
686
+ */
625
687
  function getPosition(event) {
626
688
  let x;
627
689
  let y;
@@ -637,6 +699,13 @@ function getPosition(event) {
637
699
  y
638
700
  } : void 0;
639
701
  }
702
+ /**
703
+ * Remove an event listener
704
+ * @param target Event target
705
+ * @param type Type of event
706
+ * @param listener Event listener
707
+ * @param options Options for event
708
+ */
640
709
  function off(target, type, listener, options) {
641
710
  if (!isEventTarget(target) || typeof type !== "string" || typeof listener !== "function") return;
642
711
  const extended = createEventOptions(options);
@@ -658,10 +727,9 @@ const PROPERTY_DETAIL = "detail";
658
727
  function findAncestor(origin, selector) {
659
728
  const element = getElement(origin);
660
729
  if (element == null || selector == null) return null;
661
- console.log(element);
662
730
  if (typeof selector === "string") {
663
- if (element.matches?.(selector)) return element;
664
- return element.closest(selector);
731
+ if (Element.prototype.matches.call(element, selector)) return element;
732
+ return Element.prototype.closest.call(element, selector);
665
733
  }
666
734
  if (typeof selector !== "function") return null;
667
735
  if (selector(element)) return element;
@@ -692,6 +760,12 @@ function findRelatives(origin, selector, context) {
692
760
  }
693
761
  return distances.filter((found) => found.distance === minimum).map((found) => found.element);
694
762
  }
763
+ /**
764
+ * Get the distance between two elements _(i.e., the amount of nodes of between them)_
765
+ * @param origin Origin element
766
+ * @param target Target element
767
+ * @returns Distance between elements, or `-1` if distance cannot be calculated
768
+ */
695
769
  function getDistance(origin, target) {
696
770
  if (origin === target) return 0;
697
771
  if (origin.parentElement === target || target.parentElement === origin) return 1;
@@ -767,6 +841,14 @@ function findElementOrElementsFromNodes(array, context, contexts) {
767
841
  function findElements(selector, context) {
768
842
  return findElementOrElements(selector, context, false);
769
843
  }
844
+ /**
845
+ * Get the most specific element under the pointer
846
+ *
847
+ * - Ignores elements with `pointer-events: none` and `visibility: hidden`
848
+ * - _(If `skipIgnore` is `true`, no elements are ignored)_
849
+ * @param skipIgnore Skip ignored elements?
850
+ * @returns Found element or `null`
851
+ */
770
852
  function getElementUnderPointer(skipIgnore) {
771
853
  const elements = [...document.querySelectorAll(SUFFIX_HOVER)];
772
854
  const { length } = elements;
@@ -788,6 +870,11 @@ const STYLE_HIDDEN$1 = "hidden";
788
870
  const STYLE_NONE$1 = "none";
789
871
  const SUFFIX_HOVER = ":hover";
790
872
  const TAG_HEAD = "HEAD";
873
+ /**
874
+ * Get a list of focusable elements within a parent element
875
+ * @param parent Parent element
876
+ * @returns Focusable elements
877
+ */
791
878
  function getFocusable(parent) {
792
879
  return getValidElements(parent, FILTERS_FOCUSABLE, false);
793
880
  }
@@ -797,6 +884,11 @@ function getItem(element, tabbable) {
797
884
  tabIndex: tabbable ? getTabIndex(element) : TABINDEX_DEFAULT
798
885
  };
799
886
  }
887
+ /**
888
+ * Get a list of tabbable elements within a parent element
889
+ * @param parent Parent element
890
+ * @returns Tabbable elements
891
+ */
800
892
  function getTabbable(parent) {
801
893
  return getValidElements(parent, FILTERS_TABBABLE, true);
802
894
  }
@@ -851,6 +943,11 @@ function isDisabledFromFieldset(element) {
851
943
  function isEditable(element) {
852
944
  return EXPRESSION_TRUEISH.test(element.getAttribute(ATTRIBUTE_CONTENTEDITABLE));
853
945
  }
946
+ /**
947
+ * Is the element focusable?
948
+ * @param element Element to check
949
+ * @returns `true` if focusable, otherwise `false`
950
+ */
854
951
  function isFocusable(element) {
855
952
  return element instanceof Element ? isValidElement(element, FILTERS_FOCUSABLE, false) : false;
856
953
  }
@@ -881,6 +978,11 @@ function isNotTabbableRadio(item) {
881
978
  function isSummarised(item) {
882
979
  return item.element instanceof HTMLDetailsElement && [...item.element.children].some((child) => EXPRESSION_SUMMARY.test(child.tagName));
883
980
  }
981
+ /**
982
+ * Is the element tabbable?
983
+ * @param element Element to check
984
+ * @returns `true` if tabbable, otherwise `false`
985
+ */
884
986
  function isTabbable(element) {
885
987
  return element instanceof Element ? isValidElement(element, FILTERS_TABBABLE, true) : false;
886
988
  }
@@ -936,6 +1038,11 @@ function handleElement(element, depth) {
936
1038
  }
937
1039
  sanitizeAttributes(element, [...element.attributes]);
938
1040
  }
1041
+ /**
1042
+ * Is the element clobbered?
1043
+ *
1044
+ * Thanks, DOMPurify _(https://github.com/cure53/DOMPurify)_
1045
+ */
939
1046
  function isClobbered(value) {
940
1047
  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");
941
1048
  }
@@ -1035,6 +1142,12 @@ html.remove = (template) => {
1035
1142
  }
1036
1143
  templates = updated;
1037
1144
  };
1145
+ /**
1146
+ * Sanitize one or more nodes, recursively
1147
+ * @param value Node or nodes to sanitize
1148
+ * @param options Sanitization options
1149
+ * @returns Sanitized nodes
1150
+ */
1038
1151
  function sanitize(value) {
1039
1152
  return sanitizeNodes(Array.isArray(value) ? value : [value], 0);
1040
1153
  }
@@ -1044,10 +1157,24 @@ const TEMPLATE_TAG = "template";
1044
1157
  const TEMPORARY_ELEMENT = "<toretto-temporary></toretto-temporary>";
1045
1158
  let parser;
1046
1159
  let templates = {};
1160
+ /**
1161
+ * Get a style from an element
1162
+ * @param element Element to get the style from
1163
+ * @param property Style name
1164
+ * @param computed Get the computed style? _(defaults to `false`)_
1165
+ * @returns Style value
1166
+ */
1047
1167
  function getStyle(element, property, computed) {
1048
1168
  if (!isHTMLOrSVGElement(element) || typeof property !== "string") return;
1049
1169
  return getStyleValue(element, property, computed === true);
1050
1170
  }
1171
+ /**
1172
+ * Get styles from an element
1173
+ * @param element Element to get the styles from
1174
+ * @param properties Styles to get
1175
+ * @param computed Get the computed styles? _(defaults to `false`)_
1176
+ * @returns Style values
1177
+ */
1051
1178
  function getStyles(element, properties, computed) {
1052
1179
  const styles = {};
1053
1180
  if (!(isHTMLOrSVGElement(element) && Array.isArray(properties))) return styles;
@@ -1058,6 +1185,12 @@ function getStyles(element, properties, computed) {
1058
1185
  }
1059
1186
  return styles;
1060
1187
  }
1188
+ /**
1189
+ * Get the text direction of an element
1190
+ * @param element Element to get the text direction from
1191
+ * @param computed Get the computed text direction? _(defaults to `false`)_
1192
+ * @returns Text direction
1193
+ */
1061
1194
  function getTextDirection(element, computed) {
1062
1195
  if (!(element instanceof Element)) return;
1063
1196
  const direction = element.getAttribute(ATTRIBUTE_DIRECTION);
@@ -1065,12 +1198,29 @@ function getTextDirection(element, computed) {
1065
1198
  const value = getStyleValue(element, "direction", computed === true);
1066
1199
  return value === "rtl" ? value : "ltr";
1067
1200
  }
1201
+ /**
1202
+ * Set a style on an element
1203
+ * @param element Element to set the style on
1204
+ * @param property Style name
1205
+ * @param value Style value
1206
+ */
1068
1207
  function setStyle(element, property, value) {
1069
1208
  setElementValues(element, property, value, null, updateStyleProperty);
1070
1209
  }
1210
+ /**
1211
+ * Set styles on an element
1212
+ * @param element Element to set the styles on
1213
+ * @param styles Styles to set
1214
+ */
1071
1215
  function setStyles(element, styles) {
1072
1216
  setElementValues(element, styles, null, null, updateStyleProperty);
1073
1217
  }
1218
+ /**
1219
+ * Toggle styles for an element
1220
+ * @param element Element to style
1221
+ * @param styles Styles to be set or removed
1222
+ * @returns Style toggler
1223
+ */
1074
1224
  function toggleStyles(element, styles) {
1075
1225
  function toggle(set) {
1076
1226
  hasSet = set;
@@ -1106,4 +1256,4 @@ function updateStyleProperty(element, key, value) {
1106
1256
  }
1107
1257
  const ATTRIBUTE_DIRECTION = "dir";
1108
1258
  const EXPRESSION_DIRECTION = /^(ltr|rtl)$/i;
1109
- 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 };
1259
+ 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
@@ -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
+ var supportsTouch = (() => {
13
16
  let support = getSupport();
14
17
  const instance = Object.create({
15
18
  get() {
@@ -25,4 +28,4 @@ var touch_default = (() => {
25
28
  } });
26
29
  return instance;
27
30
  })();
28
- export { touch_default as default };
31
+ 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.157"
8
8
  },
9
9
  "description": "A collection of badass DOM utilities.",
10
10
  "devDependencies": {
11
- "@types/node": "^25.1",
11
+ "@types/node": "^25.3",
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.36",
15
+ "oxlint": "^1.51",
16
+ "rolldown": "1.0.0-rc.7",
17
17
  "tslib": "^2.8",
18
18
  "typescript": "^5.9",
19
- "vite": "8.0.0-beta.11",
19
+ "vite": "8.0.0-beta.16",
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.1"
96
+ "version": "0.38.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';
package/src/find/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type {PlainObject} from '@oscarpalmer/atoms';
1
+ import type {PlainObject} from '@oscarpalmer/atoms/models';
2
2
  import type {Selector} from '../models';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import type {PlainObject} from '@oscarpalmer/atoms';
1
+ import type {PlainObject} from '@oscarpalmer/atoms/models';
2
2
  import {isPlainObject} from '@oscarpalmer/atoms/is';
3
3
  import type {Attribute} from '../models';
4
4
  import {updateElementValue} from './element-value';
@@ -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);