@oscarpalmer/toretto 0.37.2 → 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;
@@ -34,6 +34,12 @@ function findRelatives(origin, selector, context) {
34
34
  }
35
35
  return distances.filter((found) => found.distance === minimum).map((found) => found.element);
36
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
+ */
37
43
  function getDistance(origin, target) {
38
44
  if (origin === target) return 0;
39
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);
@@ -691,6 +760,12 @@ function findRelatives(origin, selector, context) {
691
760
  }
692
761
  return distances.filter((found) => found.distance === minimum).map((found) => found.element);
693
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
+ */
694
769
  function getDistance(origin, target) {
695
770
  if (origin === target) return 0;
696
771
  if (origin.parentElement === target || target.parentElement === origin) return 1;
@@ -766,6 +841,14 @@ function findElementOrElementsFromNodes(array, context, contexts) {
766
841
  function findElements(selector, context) {
767
842
  return findElementOrElements(selector, context, false);
768
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
+ */
769
852
  function getElementUnderPointer(skipIgnore) {
770
853
  const elements = [...document.querySelectorAll(SUFFIX_HOVER)];
771
854
  const { length } = elements;
@@ -787,6 +870,11 @@ const STYLE_HIDDEN$1 = "hidden";
787
870
  const STYLE_NONE$1 = "none";
788
871
  const SUFFIX_HOVER = ":hover";
789
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
+ */
790
878
  function getFocusable(parent) {
791
879
  return getValidElements(parent, FILTERS_FOCUSABLE, false);
792
880
  }
@@ -796,6 +884,11 @@ function getItem(element, tabbable) {
796
884
  tabIndex: tabbable ? getTabIndex(element) : TABINDEX_DEFAULT
797
885
  };
798
886
  }
887
+ /**
888
+ * Get a list of tabbable elements within a parent element
889
+ * @param parent Parent element
890
+ * @returns Tabbable elements
891
+ */
799
892
  function getTabbable(parent) {
800
893
  return getValidElements(parent, FILTERS_TABBABLE, true);
801
894
  }
@@ -850,6 +943,11 @@ function isDisabledFromFieldset(element) {
850
943
  function isEditable(element) {
851
944
  return EXPRESSION_TRUEISH.test(element.getAttribute(ATTRIBUTE_CONTENTEDITABLE));
852
945
  }
946
+ /**
947
+ * Is the element focusable?
948
+ * @param element Element to check
949
+ * @returns `true` if focusable, otherwise `false`
950
+ */
853
951
  function isFocusable(element) {
854
952
  return element instanceof Element ? isValidElement(element, FILTERS_FOCUSABLE, false) : false;
855
953
  }
@@ -880,6 +978,11 @@ function isNotTabbableRadio(item) {
880
978
  function isSummarised(item) {
881
979
  return item.element instanceof HTMLDetailsElement && [...item.element.children].some((child) => EXPRESSION_SUMMARY.test(child.tagName));
882
980
  }
981
+ /**
982
+ * Is the element tabbable?
983
+ * @param element Element to check
984
+ * @returns `true` if tabbable, otherwise `false`
985
+ */
883
986
  function isTabbable(element) {
884
987
  return element instanceof Element ? isValidElement(element, FILTERS_TABBABLE, true) : false;
885
988
  }
@@ -935,6 +1038,11 @@ function handleElement(element, depth) {
935
1038
  }
936
1039
  sanitizeAttributes(element, [...element.attributes]);
937
1040
  }
1041
+ /**
1042
+ * Is the element clobbered?
1043
+ *
1044
+ * Thanks, DOMPurify _(https://github.com/cure53/DOMPurify)_
1045
+ */
938
1046
  function isClobbered(value) {
939
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");
940
1048
  }
@@ -1034,6 +1142,12 @@ html.remove = (template) => {
1034
1142
  }
1035
1143
  templates = updated;
1036
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
+ */
1037
1151
  function sanitize(value) {
1038
1152
  return sanitizeNodes(Array.isArray(value) ? value : [value], 0);
1039
1153
  }
@@ -1043,10 +1157,24 @@ const TEMPLATE_TAG = "template";
1043
1157
  const TEMPORARY_ELEMENT = "<toretto-temporary></toretto-temporary>";
1044
1158
  let parser;
1045
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
+ */
1046
1167
  function getStyle(element, property, computed) {
1047
1168
  if (!isHTMLOrSVGElement(element) || typeof property !== "string") return;
1048
1169
  return getStyleValue(element, property, computed === true);
1049
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
+ */
1050
1178
  function getStyles(element, properties, computed) {
1051
1179
  const styles = {};
1052
1180
  if (!(isHTMLOrSVGElement(element) && Array.isArray(properties))) return styles;
@@ -1057,6 +1185,12 @@ function getStyles(element, properties, computed) {
1057
1185
  }
1058
1186
  return styles;
1059
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
+ */
1060
1194
  function getTextDirection(element, computed) {
1061
1195
  if (!(element instanceof Element)) return;
1062
1196
  const direction = element.getAttribute(ATTRIBUTE_DIRECTION);
@@ -1064,12 +1198,29 @@ function getTextDirection(element, computed) {
1064
1198
  const value = getStyleValue(element, "direction", computed === true);
1065
1199
  return value === "rtl" ? value : "ltr";
1066
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
+ */
1067
1207
  function setStyle(element, property, value) {
1068
1208
  setElementValues(element, property, value, null, updateStyleProperty);
1069
1209
  }
1210
+ /**
1211
+ * Set styles on an element
1212
+ * @param element Element to set the styles on
1213
+ * @param styles Styles to set
1214
+ */
1070
1215
  function setStyles(element, styles) {
1071
1216
  setElementValues(element, styles, null, null, updateStyleProperty);
1072
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
+ */
1073
1224
  function toggleStyles(element, styles) {
1074
1225
  function toggle(set) {
1075
1226
  hasSet = set;
@@ -1105,4 +1256,4 @@ function updateStyleProperty(element, key, value) {
1105
1256
  }
1106
1257
  const ATTRIBUTE_DIRECTION = "dir";
1107
1258
  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 };
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.2"
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);