@react-hive/honey-utils 4.3.0 → 5.0.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.
- package/README.md +24 -12
- package/dist/README.md +24 -12
- package/dist/a11y/focus/is-html-element-focusable.d.ts +8 -6
- package/dist/a11y/focus/move-focus-within-container.d.ts +1 -1
- package/dist/array/chunk.d.ts +4 -1
- package/dist/array/compose.d.ts +7 -3
- package/dist/array/get-adjacent-items.d.ts +1 -1
- package/dist/array/index.d.ts +1 -0
- package/dist/array/intersection.d.ts +2 -1
- package/dist/array/is-array.d.ts +6 -0
- package/dist/array/is-empty-array.d.ts +7 -0
- package/dist/array/pipe.d.ts +5 -2
- package/dist/array/to-map-by.d.ts +48 -0
- package/dist/async/async.d.ts +10 -1
- package/dist/async/retry.d.ts +4 -3
- package/dist/dom/data-transfer.d.ts +22 -4
- package/dist/dom/dom.d.ts +7 -5
- package/dist/dom/transform/parse-2d-matrix.d.ts +5 -0
- package/dist/encoding/decode-base64-json.d.ts +22 -1
- package/dist/encoding/decode-base64.d.ts +23 -1
- package/dist/encoding/encode-base64-json.d.ts +16 -0
- package/dist/encoding/encode-base64.d.ts +20 -0
- package/dist/env/storage/local-storage-json.d.ts +1 -1
- package/dist/env/storage/local-storage.d.ts +1 -6
- package/dist/file.d.ts +3 -2
- package/dist/function/function.d.ts +12 -0
- package/dist/geometry/apply-inertia-step.d.ts +1 -1
- package/dist/geometry/layout/calculate-center-offset.d.ts +11 -1
- package/dist/geometry/layout/clamp-offset-to-overflow.d.ts +6 -0
- package/dist/geometry/resolve-bounded-delta.d.ts +1 -1
- package/dist/guards.d.ts +24 -3
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.dev.cjs +402 -97
- package/dist/index.dev.cjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/intersection/get-dom-rect-intersection-ratio.d.ts +8 -1
- package/dist/intersection/resolve-axis-delta.d.ts +7 -0
- package/dist/json/parse-json.d.ts +22 -1
- package/dist/math/hash-string.d.ts +1 -1
- package/dist/math/math.d.ts +17 -1
- package/dist/object.d.ts +7 -1
- package/dist/string/for-each-char.d.ts +1 -1
- package/dist/string/is-nil-or-empty-string.d.ts +1 -1
- package/dist/string/parse-file-name.d.ts +1 -1
- package/dist/tree/flatten-tree.d.ts +2 -2
- package/dist/tree/get-tree-children.d.ts +2 -2
- package/dist/tree/search-tree.d.ts +2 -2
- package/dist/tree/tree.types.d.ts +1 -1
- package/package.json +8 -7
- package/dist/geometry/apply-inertia-step.spec.d.ts +0 -1
- package/dist/geometry/resolve-bounded-delta.spec.d.ts +0 -1
- package/dist/id/generate-ephemeral-id.spec.d.ts +0 -1
package/dist/index.dev.cjs
CHANGED
|
@@ -12,7 +12,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
12
12
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
13
13
|
/* harmony export */ getFocusableHtmlElements: () => (/* binding */ getFocusableHtmlElements)
|
|
14
14
|
/* harmony export */ });
|
|
15
|
-
/* harmony import */ var
|
|
15
|
+
/* harmony import */ var _is_html_element_focusable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./is-html-element-focusable */ "./src/a11y/focus/is-html-element-focusable.ts");
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Collects all focusable descendant elements within a container.
|
|
@@ -25,7 +25,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
25
25
|
*
|
|
26
26
|
* @returns An array of focusable HTMLElements in DOM order.
|
|
27
27
|
*/
|
|
28
|
-
const getFocusableHtmlElements = (container) => Array.from(container.querySelectorAll('*')).filter(
|
|
28
|
+
const getFocusableHtmlElements = (container) => Array.from(container.querySelectorAll('*')).filter(_is_html_element_focusable__WEBPACK_IMPORTED_MODULE_0__.isHtmlElementFocusable);
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
/***/ },
|
|
@@ -67,16 +67,26 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
67
67
|
/* harmony import */ var _dom__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ~/dom */ "./src/dom/index.ts");
|
|
68
68
|
|
|
69
69
|
const FOCUSABLE_HTML_TAGS = ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON', 'A'];
|
|
70
|
+
/**
|
|
71
|
+
* A `tabindex` attribute's value, read by the HTML rules for parsing integers - leading whitespace
|
|
72
|
+
* and a sign allowed, anything after the digits ignored - or `null` where there is none to read.
|
|
73
|
+
*/
|
|
74
|
+
const parseTabIndex = (value) => {
|
|
75
|
+
const match = value === null ? null : /^\s*([+-]?\d+)/.exec(value);
|
|
76
|
+
return match ? parseInt(match[1], 10) : null;
|
|
77
|
+
};
|
|
70
78
|
/**
|
|
71
79
|
* Determines whether an HTMLElement is focusable under standard browser rules.
|
|
72
80
|
*
|
|
73
81
|
* The function checks a combination of factors:
|
|
74
|
-
* - The element must be rendered
|
|
75
|
-
*
|
|
76
|
-
* -
|
|
82
|
+
* - The element must be rendered: neither it nor an ancestor has `display: none`, and it is not
|
|
83
|
+
* `visibility: hidden`.
|
|
84
|
+
* - Disabled form controls are never focusable - including those inside a disabled `<fieldset>`.
|
|
85
|
+
* - Elements with a negative `tabindex` are intentionally removed from the focus order.
|
|
86
|
+
* - Any element with a valid, non-negative `tabindex` is focusable. A `tabindex` that is not an
|
|
87
|
+
* integer is ignored, as browsers ignore it.
|
|
77
88
|
* - Certain native HTML elements are inherently focusable (e.g. inputs, buttons, anchors with `href`).
|
|
78
|
-
* -
|
|
79
|
-
* - Any element with a valid `tabindex` (not null) is considered focusable.
|
|
89
|
+
* - Editable elements (`contenteditable`) are treated as focusable.
|
|
80
90
|
*
|
|
81
91
|
* This logic approximates how browsers and the accessibility tree
|
|
82
92
|
* determine real-world focusability—not just tabindex presence.
|
|
@@ -89,28 +99,30 @@ const isHtmlElementFocusable = (element) => {
|
|
|
89
99
|
if (!element) {
|
|
90
100
|
return false;
|
|
91
101
|
}
|
|
92
|
-
|
|
93
|
-
if (style.visibility === 'hidden' || style.display === 'none') {
|
|
102
|
+
if (window.getComputedStyle(element).visibility === 'hidden') {
|
|
94
103
|
return false;
|
|
95
104
|
}
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
// `display: none` does not inherit, so a hidden ancestor has to be looked for
|
|
106
|
+
for (let node = element; node; node = node.parentElement) {
|
|
107
|
+
if (window.getComputedStyle(node).display === 'none') {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
98
110
|
}
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
if (tabIndex === '-1') {
|
|
111
|
+
// `:disabled` covers a control inside a disabled `<fieldset>`, which `element.disabled` does not
|
|
112
|
+
if (element.matches(':disabled')) {
|
|
102
113
|
return false;
|
|
103
114
|
}
|
|
115
|
+
const tabIndex = parseTabIndex(element.getAttribute('tabindex'));
|
|
116
|
+
if (tabIndex !== null) {
|
|
117
|
+
return tabIndex >= 0;
|
|
118
|
+
}
|
|
104
119
|
if (FOCUSABLE_HTML_TAGS.includes(element.tagName)) {
|
|
105
120
|
if ((0,_dom__WEBPACK_IMPORTED_MODULE_0__.isAnchorHtmlElement)(element)) {
|
|
106
121
|
return element.href !== '';
|
|
107
122
|
}
|
|
108
123
|
return true;
|
|
109
124
|
}
|
|
110
|
-
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
return tabIndex !== null;
|
|
125
|
+
return (0,_dom__WEBPACK_IMPORTED_MODULE_0__.isContentEditableHtmlElement)(element);
|
|
114
126
|
};
|
|
115
127
|
|
|
116
128
|
|
|
@@ -126,7 +138,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
126
138
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
127
139
|
/* harmony export */ moveFocusWithinContainer: () => (/* binding */ moveFocusWithinContainer)
|
|
128
140
|
/* harmony export */ });
|
|
129
|
-
/* harmony import */ var
|
|
141
|
+
/* harmony import */ var _get_focusable_html_elements__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./get-focusable-html-elements */ "./src/a11y/focus/get-focusable-html-elements.ts");
|
|
130
142
|
|
|
131
143
|
/**
|
|
132
144
|
* Moves focus to the next or previous focusable element within a container.
|
|
@@ -158,7 +170,7 @@ const moveFocusWithinContainer = (direction, container = null, { wrap = true, ge
|
|
|
158
170
|
if (!activeElement || !scope) {
|
|
159
171
|
return;
|
|
160
172
|
}
|
|
161
|
-
const focusableElements = (0,
|
|
173
|
+
const focusableElements = (0,_get_focusable_html_elements__WEBPACK_IMPORTED_MODULE_0__.getFocusableHtmlElements)(scope);
|
|
162
174
|
if (focusableElements.length === 0) {
|
|
163
175
|
return;
|
|
164
176
|
}
|
|
@@ -232,11 +244,14 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
232
244
|
* @template T - The type of the items in the array.
|
|
233
245
|
*
|
|
234
246
|
* @param array - The input array to be chunked.
|
|
235
|
-
* @param size - The size of each chunk. Must be
|
|
247
|
+
* @param size - The size of each chunk. Must be a positive whole number.
|
|
236
248
|
*
|
|
237
249
|
* @returns An array of chunks, where each chunk is an array of the specified size
|
|
238
250
|
* (except possibly the last chunk, which may be smaller).
|
|
239
251
|
*
|
|
252
|
+
* @throws {Error} When `size` is not a positive whole number - a fractional size would give
|
|
253
|
+
* chunks of other sizes, and `Infinity` none at all.
|
|
254
|
+
*
|
|
240
255
|
* @example
|
|
241
256
|
* ```ts
|
|
242
257
|
* chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
|
|
@@ -244,7 +259,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
244
259
|
* ```
|
|
245
260
|
*/
|
|
246
261
|
const chunk = (array, size) => {
|
|
247
|
-
(0,_guards__WEBPACK_IMPORTED_MODULE_0__.assert)(size > 0, 'Chunk size must be
|
|
262
|
+
(0,_guards__WEBPACK_IMPORTED_MODULE_0__.assert)(Number.isInteger(size) && size > 0, 'Chunk size must be a positive whole number');
|
|
248
263
|
return Array.from({ length: Math.ceil(array.length / size) }, (_, index) => array.slice(index * size, (index + 1) * size));
|
|
249
264
|
};
|
|
250
265
|
|
|
@@ -298,7 +313,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
298
313
|
* Composes multiple unary functions into a single function, applying them from **right to left**.
|
|
299
314
|
*
|
|
300
315
|
* Often used for building functional pipelines where the innermost function runs first.
|
|
301
|
-
* Types are inferred up to 5 chained functions for full type safety.
|
|
316
|
+
* Types are inferred up to 5 chained functions for full type safety. Beyond that, it falls back to the unknown.
|
|
302
317
|
*
|
|
303
318
|
* @param fns - A list of unary functions to compose.
|
|
304
319
|
*
|
|
@@ -412,6 +427,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
412
427
|
/* harmony export */ isArray: () => (/* reexport safe */ _is_array__WEBPACK_IMPORTED_MODULE_7__.isArray),
|
|
413
428
|
/* harmony export */ isEmptyArray: () => (/* reexport safe */ _is_empty_array__WEBPACK_IMPORTED_MODULE_8__.isEmptyArray),
|
|
414
429
|
/* harmony export */ pipe: () => (/* reexport safe */ _pipe__WEBPACK_IMPORTED_MODULE_0__.pipe),
|
|
430
|
+
/* harmony export */ toMapBy: () => (/* reexport safe */ _to_map_by__WEBPACK_IMPORTED_MODULE_11__.toMapBy),
|
|
415
431
|
/* harmony export */ toggleArrayItem: () => (/* reexport safe */ _toggle_array_item__WEBPACK_IMPORTED_MODULE_10__.toggleArrayItem),
|
|
416
432
|
/* harmony export */ unique: () => (/* reexport safe */ _unique__WEBPACK_IMPORTED_MODULE_5__.unique)
|
|
417
433
|
/* harmony export */ });
|
|
@@ -426,6 +442,8 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
426
442
|
/* harmony import */ var _is_empty_array__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./is-empty-array */ "./src/array/is-empty-array.ts");
|
|
427
443
|
/* harmony import */ var _get_adjacent_items__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./get-adjacent-items */ "./src/array/get-adjacent-items.ts");
|
|
428
444
|
/* harmony import */ var _toggle_array_item__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./toggle-array-item */ "./src/array/toggle-array-item.ts");
|
|
445
|
+
/* harmony import */ var _to_map_by__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./to-map-by */ "./src/array/to-map-by.ts");
|
|
446
|
+
|
|
429
447
|
|
|
430
448
|
|
|
431
449
|
|
|
@@ -451,7 +469,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
451
469
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
452
470
|
/* harmony export */ intersection: () => (/* binding */ intersection)
|
|
453
471
|
/* harmony export */ });
|
|
454
|
-
/* harmony import */ var
|
|
472
|
+
/* harmony import */ var _unique__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./unique */ "./src/array/unique.ts");
|
|
455
473
|
|
|
456
474
|
/**
|
|
457
475
|
* Returns an array containing elements that exist in all provided arrays.
|
|
@@ -460,7 +478,8 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
460
478
|
*
|
|
461
479
|
* @param arrays - Two or more arrays to find common elements from.
|
|
462
480
|
*
|
|
463
|
-
* @returns A new array containing only the elements that exist in all input arrays
|
|
481
|
+
* @returns A new array containing only the elements that exist in all input arrays, each once, in
|
|
482
|
+
* the order of the first array.
|
|
464
483
|
*
|
|
465
484
|
* @example
|
|
466
485
|
* ```ts
|
|
@@ -472,11 +491,8 @@ const intersection = (...arrays) => {
|
|
|
472
491
|
if (arrays.length === 0) {
|
|
473
492
|
return [];
|
|
474
493
|
}
|
|
475
|
-
if (arrays.length === 1) {
|
|
476
|
-
return [...arrays[0]];
|
|
477
|
-
}
|
|
478
494
|
const [first, ...rest] = arrays;
|
|
479
|
-
const uniqueFirst = (0,
|
|
495
|
+
const uniqueFirst = (0,_unique__WEBPACK_IMPORTED_MODULE_0__.unique)(first);
|
|
480
496
|
return uniqueFirst.filter(item => rest.every(array => array.includes(item)));
|
|
481
497
|
};
|
|
482
498
|
|
|
@@ -499,6 +515,12 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
499
515
|
* @param value - The value to check.
|
|
500
516
|
*
|
|
501
517
|
* @returns `true` if the value is an array; otherwise, `false`.
|
|
518
|
+
*
|
|
519
|
+
* @example
|
|
520
|
+
* ```ts
|
|
521
|
+
* isArray([1, 2]); // true
|
|
522
|
+
* isArray('12'); // false
|
|
523
|
+
* ```
|
|
502
524
|
*/
|
|
503
525
|
const isArray = (value) => Array.isArray(value);
|
|
504
526
|
|
|
@@ -515,7 +537,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
515
537
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
516
538
|
/* harmony export */ isEmptyArray: () => (/* binding */ isEmptyArray)
|
|
517
539
|
/* harmony export */ });
|
|
518
|
-
/* harmony import */ var
|
|
540
|
+
/* harmony import */ var _is_array__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./is-array */ "./src/array/is-array.ts");
|
|
519
541
|
|
|
520
542
|
/**
|
|
521
543
|
* Checks if a value is an empty array.
|
|
@@ -523,8 +545,15 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
523
545
|
* @param value - The value to check.
|
|
524
546
|
*
|
|
525
547
|
* @returns `true` if the value is an empty array; otherwise, `false`.
|
|
548
|
+
*
|
|
549
|
+
* @example
|
|
550
|
+
* ```ts
|
|
551
|
+
* isEmptyArray([]); // true
|
|
552
|
+
* isEmptyArray([1]); // false
|
|
553
|
+
* isEmptyArray({}); // false
|
|
554
|
+
* ```
|
|
526
555
|
*/
|
|
527
|
-
const isEmptyArray = (value) => (0,
|
|
556
|
+
const isEmptyArray = (value) => (0,_is_array__WEBPACK_IMPORTED_MODULE_0__.isArray)(value) && value.length === 0;
|
|
528
557
|
|
|
529
558
|
|
|
530
559
|
/***/ },
|
|
@@ -563,6 +592,31 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
563
592
|
const pipe = (...fns) => (arg) => fns.reduce((prev, fn) => fn(prev), arg);
|
|
564
593
|
|
|
565
594
|
|
|
595
|
+
/***/ },
|
|
596
|
+
|
|
597
|
+
/***/ "./src/array/to-map-by.ts"
|
|
598
|
+
/*!********************************!*\
|
|
599
|
+
!*** ./src/array/to-map-by.ts ***!
|
|
600
|
+
\********************************/
|
|
601
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
602
|
+
|
|
603
|
+
__webpack_require__.r(__webpack_exports__);
|
|
604
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
605
|
+
/* harmony export */ toMapBy: () => (/* binding */ toMapBy)
|
|
606
|
+
/* harmony export */ });
|
|
607
|
+
/**
|
|
608
|
+
* Overloaded rather than given a default `Value`, which TypeScript would take from whatever type the
|
|
609
|
+
* result is assigned to - so a map of items could be declared a map of anything.
|
|
610
|
+
*/
|
|
611
|
+
function toMapBy(items, getKey, getValue) {
|
|
612
|
+
const map = new Map();
|
|
613
|
+
items.forEach((item, index) => {
|
|
614
|
+
map.set(getKey(item, index), getValue ? getValue(item, index) : item);
|
|
615
|
+
});
|
|
616
|
+
return map;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
|
|
566
620
|
/***/ },
|
|
567
621
|
|
|
568
622
|
/***/ "./src/array/toggle-array-item.ts"
|
|
@@ -663,9 +717,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
663
717
|
/* harmony export */ runSequential: () => (/* binding */ runSequential),
|
|
664
718
|
/* harmony export */ someAsync: () => (/* binding */ someAsync)
|
|
665
719
|
/* harmony export */ });
|
|
666
|
-
/* harmony import */ var
|
|
667
|
-
/* harmony import */ var _function__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ~/function */ "./src/function/index.ts");
|
|
668
|
-
|
|
720
|
+
/* harmony import */ var _function__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ~/function */ "./src/function/index.ts");
|
|
669
721
|
|
|
670
722
|
/**
|
|
671
723
|
* Checks if a value is a Promise.
|
|
@@ -676,7 +728,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
676
728
|
*
|
|
677
729
|
* @returns `true` if the value is a Promise; otherwise, `false`.
|
|
678
730
|
*/
|
|
679
|
-
const isPromise = (value) => (0,
|
|
731
|
+
const isPromise = (value) => (0,_function__WEBPACK_IMPORTED_MODULE_0__.isFunction)(value?.then);
|
|
680
732
|
/**
|
|
681
733
|
* Asynchronously iterates over an array and executes an async function on each item sequentially,
|
|
682
734
|
* collecting the results.
|
|
@@ -789,12 +841,15 @@ const filterSequential = async (array, predicate) => {
|
|
|
789
841
|
* ```
|
|
790
842
|
*/
|
|
791
843
|
const filterParallel = async (array, predicate) => {
|
|
792
|
-
const
|
|
793
|
-
return (
|
|
844
|
+
const keep = await runParallel(array, predicate);
|
|
845
|
+
return array.filter((_, index) => keep[index]);
|
|
794
846
|
};
|
|
795
847
|
/**
|
|
796
848
|
* Asynchronously checks if at least one element in the array satisfies the async condition.
|
|
797
849
|
*
|
|
850
|
+
* Runs one item at a time, in order, and stops at the first item that satisfies it - as
|
|
851
|
+
* `Array.prototype.some` does.
|
|
852
|
+
*
|
|
798
853
|
* @param array - The array of items to check.
|
|
799
854
|
* @param predicate - An async function that returns a boolean.
|
|
800
855
|
*
|
|
@@ -811,6 +866,9 @@ const someAsync = async (array, predicate) => {
|
|
|
811
866
|
/**
|
|
812
867
|
* Asynchronously checks if all elements in the array satisfy the async condition.
|
|
813
868
|
*
|
|
869
|
+
* Runs one item at a time, in order, and stops at the first item that fails it - as
|
|
870
|
+
* `Array.prototype.every` does.
|
|
871
|
+
*
|
|
814
872
|
* @param array - The array of items to check.
|
|
815
873
|
* @param predicate - An async function that returns a boolean.
|
|
816
874
|
*
|
|
@@ -846,6 +904,9 @@ const reduceAsync = async (array, fn, initialValue) => {
|
|
|
846
904
|
/**
|
|
847
905
|
* Asynchronously finds the first element that satisfies the async condition.
|
|
848
906
|
*
|
|
907
|
+
* Runs one item at a time, in order, and stops at the first match - as `Array.prototype.find`
|
|
908
|
+
* does.
|
|
909
|
+
*
|
|
849
910
|
* @param array - The array of items to search.
|
|
850
911
|
* @param predicate - An async function that returns a boolean.
|
|
851
912
|
*
|
|
@@ -946,7 +1007,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
946
1007
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
947
1008
|
/* harmony export */ retry: () => (/* binding */ retry)
|
|
948
1009
|
/* harmony export */ });
|
|
949
|
-
/* harmony import */ var
|
|
1010
|
+
/* harmony import */ var _delay__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./delay */ "./src/async/delay.ts");
|
|
950
1011
|
|
|
951
1012
|
/**
|
|
952
1013
|
* Wraps an asynchronous function with retry logic.
|
|
@@ -956,7 +1017,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
956
1017
|
*
|
|
957
1018
|
* Useful for operations that may fail intermittently, such as network requests.
|
|
958
1019
|
*
|
|
959
|
-
* @template
|
|
1020
|
+
* @template Args - The arguments the async function takes.
|
|
960
1021
|
* @template TaskResult - The result type of the async function.
|
|
961
1022
|
*
|
|
962
1023
|
* @param task - The async function to wrap with retry logic.
|
|
@@ -990,18 +1051,19 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
990
1051
|
* ```
|
|
991
1052
|
*/
|
|
992
1053
|
const retry = (task, { maxAttempts = 3, delayMs = 300, backoff = true, onRetry } = {}) => {
|
|
1054
|
+
const attempts = Math.max(1, maxAttempts);
|
|
993
1055
|
return async (...args) => {
|
|
994
1056
|
let lastError;
|
|
995
|
-
for (let attempt = 1; attempt <=
|
|
1057
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
996
1058
|
try {
|
|
997
1059
|
return await task(...args);
|
|
998
1060
|
}
|
|
999
1061
|
catch (e) {
|
|
1000
1062
|
lastError = e;
|
|
1001
|
-
if (attempt <
|
|
1063
|
+
if (attempt < attempts) {
|
|
1002
1064
|
onRetry?.(attempt, e);
|
|
1003
1065
|
const delayTime = backoff ? delayMs * 2 ** (attempt - 1) : delayMs;
|
|
1004
|
-
await (0,
|
|
1066
|
+
await (0,_delay__WEBPACK_IMPORTED_MODULE_0__.delay)(delayTime);
|
|
1005
1067
|
}
|
|
1006
1068
|
}
|
|
1007
1069
|
}
|
|
@@ -1022,8 +1084,6 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1022
1084
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
1023
1085
|
/* harmony export */ timeout: () => (/* binding */ timeout)
|
|
1024
1086
|
/* harmony export */ });
|
|
1025
|
-
/* harmony import */ var _async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ~/async */ "./src/async/index.ts");
|
|
1026
|
-
|
|
1027
1087
|
/**
|
|
1028
1088
|
* Wraps a promise with a timeout. If the promise does not settle within the specified time,
|
|
1029
1089
|
* it will reject with a timeout error.
|
|
@@ -1047,15 +1107,18 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1047
1107
|
* ```
|
|
1048
1108
|
*/
|
|
1049
1109
|
const timeout = async (promise, timeoutMs, errorMessage = 'Operation timed out') => {
|
|
1050
|
-
|
|
1110
|
+
let timeoutId = null;
|
|
1051
1111
|
try {
|
|
1052
1112
|
return await Promise.race([
|
|
1053
1113
|
promise,
|
|
1054
|
-
(
|
|
1114
|
+
new Promise((_, reject) => {
|
|
1115
|
+
timeoutId = setTimeout(() => reject(new Error(errorMessage)), timeoutMs);
|
|
1116
|
+
}),
|
|
1055
1117
|
]);
|
|
1056
1118
|
}
|
|
1057
1119
|
finally {
|
|
1058
|
-
|
|
1120
|
+
// Cleared however the race ends, so a settled promise leaves no timer behind
|
|
1121
|
+
if (timeoutId !== null) {
|
|
1059
1122
|
clearTimeout(timeoutId);
|
|
1060
1123
|
}
|
|
1061
1124
|
}
|
|
@@ -1157,8 +1220,17 @@ const DATA_TRANSFER_JSON_MIME = 'application/json';
|
|
|
1157
1220
|
*
|
|
1158
1221
|
* Useful for drag-and-drop communication between elements.
|
|
1159
1222
|
*
|
|
1160
|
-
* @
|
|
1161
|
-
*
|
|
1223
|
+
* @template T - The type of the data.
|
|
1224
|
+
*
|
|
1225
|
+
* @param dataTransfer - DataTransfer instance from a drag event.
|
|
1226
|
+
* @param data - Data to serialize.
|
|
1227
|
+
*
|
|
1228
|
+
* @example
|
|
1229
|
+
* ```ts
|
|
1230
|
+
* element.addEventListener('dragstart', event => {
|
|
1231
|
+
* setDataTransferJson(event.dataTransfer, { id: 'card-1' });
|
|
1232
|
+
* });
|
|
1233
|
+
* ```
|
|
1162
1234
|
*/
|
|
1163
1235
|
const setDataTransferJson = (dataTransfer, data) => {
|
|
1164
1236
|
dataTransfer.setData(DATA_TRANSFER_JSON_MIME, JSON.stringify(data));
|
|
@@ -1166,9 +1238,18 @@ const setDataTransferJson = (dataTransfer, data) => {
|
|
|
1166
1238
|
/**
|
|
1167
1239
|
* Retrieves and parses JSON data from a DataTransfer object.
|
|
1168
1240
|
*
|
|
1169
|
-
* @
|
|
1241
|
+
* @template Value - The type the JSON is expected to have.
|
|
1242
|
+
*
|
|
1243
|
+
* @param dataTransfer - DataTransfer instance from a drop event.
|
|
1170
1244
|
*
|
|
1171
1245
|
* @returns Parsed JSON value or null if not present or invalid.
|
|
1246
|
+
*
|
|
1247
|
+
* @example
|
|
1248
|
+
* ```ts
|
|
1249
|
+
* element.addEventListener('drop', event => {
|
|
1250
|
+
* const card = getDataTransferJson<{ id: string }>(event.dataTransfer);
|
|
1251
|
+
* });
|
|
1252
|
+
* ```
|
|
1172
1253
|
*/
|
|
1173
1254
|
const getDataTransferJson = (dataTransfer) => {
|
|
1174
1255
|
const rawData = dataTransfer.getData(DATA_TRANSFER_JSON_MIME);
|
|
@@ -1205,8 +1286,9 @@ const cloneBlob = (blob) => new Blob([blob], { type: blob.type });
|
|
|
1205
1286
|
/**
|
|
1206
1287
|
* Returns the bounding DOMRect of an element based on offset and client dimensions.
|
|
1207
1288
|
*
|
|
1208
|
-
*
|
|
1209
|
-
*
|
|
1289
|
+
* Unlike `getBoundingClientRect()`, the rect is the element's layout box, before any CSS
|
|
1290
|
+
* transform - so it stays put while the element or its container is being translated or scaled.
|
|
1291
|
+
* Reading it still needs layout, as `getBoundingClientRect()` does.
|
|
1210
1292
|
*
|
|
1211
1293
|
* @param element - The target HTML element.
|
|
1212
1294
|
* @returns A `DOMRect` representing the element’s offset position and size.
|
|
@@ -1228,14 +1310,18 @@ const isAnchorHtmlElement = (element) => element.tagName === 'A';
|
|
|
1228
1310
|
/**
|
|
1229
1311
|
* Checks whether an element is explicitly marked as contenteditable.
|
|
1230
1312
|
*
|
|
1231
|
-
* Browsers treat elements
|
|
1232
|
-
*
|
|
1313
|
+
* Browsers treat editable elements as focusable, even if they are not normally
|
|
1314
|
+
* keyboard-focusable. An element is editable with `contenteditable` set to `""` (the bare
|
|
1315
|
+
* attribute), `"true"` or `"plaintext-only"`, in any case.
|
|
1233
1316
|
*
|
|
1234
1317
|
* @param element - The element to inspect.
|
|
1235
1318
|
*
|
|
1236
|
-
* @returns True if `contenteditable
|
|
1319
|
+
* @returns True if the element's own `contenteditable` makes it editable.
|
|
1237
1320
|
*/
|
|
1238
|
-
const isContentEditableHtmlElement = (element) =>
|
|
1321
|
+
const isContentEditableHtmlElement = (element) => {
|
|
1322
|
+
const value = element.getAttribute('contenteditable');
|
|
1323
|
+
return value !== null && ['', 'true', 'plaintext-only'].includes(value.toLowerCase());
|
|
1324
|
+
};
|
|
1239
1325
|
|
|
1240
1326
|
|
|
1241
1327
|
/***/ },
|
|
@@ -1274,8 +1360,8 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1274
1360
|
* @param options - Optional configuration controlling filename and link target.
|
|
1275
1361
|
*/
|
|
1276
1362
|
const downloadFile = (file, { fileName, target } = {}) => {
|
|
1277
|
-
// Browser guard (SSR / non-DOM environments)
|
|
1278
|
-
if (
|
|
1363
|
+
// Browser guard (SSR / non-DOM environments) - `typeof`, as reading an undeclared `document` throws
|
|
1364
|
+
if (typeof document === 'undefined') {
|
|
1279
1365
|
return;
|
|
1280
1366
|
}
|
|
1281
1367
|
const link = document.createElement('a');
|
|
@@ -1415,7 +1501,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1415
1501
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
1416
1502
|
/* harmony export */ centerElementInContainer: () => (/* binding */ centerElementInContainer)
|
|
1417
1503
|
/* harmony export */ });
|
|
1418
|
-
/* harmony import */ var
|
|
1504
|
+
/* harmony import */ var _overflow__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./overflow */ "./src/dom/layout/overflow.ts");
|
|
1419
1505
|
/* harmony import */ var _geometry__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ~/geometry */ "./src/geometry/index.ts");
|
|
1420
1506
|
|
|
1421
1507
|
|
|
@@ -1447,7 +1533,7 @@ const centerElementInContainer = (containerElement, elementToCenter, { axis = 'b
|
|
|
1447
1533
|
elementSize: elementToCenter.clientWidth,
|
|
1448
1534
|
});
|
|
1449
1535
|
translateX = shouldClamp
|
|
1450
|
-
? (0,_geometry__WEBPACK_IMPORTED_MODULE_1__.clampOffsetToOverflow)(offsetX, (0,
|
|
1536
|
+
? (0,_geometry__WEBPACK_IMPORTED_MODULE_1__.clampOffsetToOverflow)(offsetX, (0,_overflow__WEBPACK_IMPORTED_MODULE_0__.getXOverflowWidth)(containerElement))
|
|
1451
1537
|
: offsetX;
|
|
1452
1538
|
}
|
|
1453
1539
|
if (axis === 'y' || axis === 'both') {
|
|
@@ -1457,7 +1543,7 @@ const centerElementInContainer = (containerElement, elementToCenter, { axis = 'b
|
|
|
1457
1543
|
elementSize: elementToCenter.clientHeight,
|
|
1458
1544
|
});
|
|
1459
1545
|
translateY = shouldClamp
|
|
1460
|
-
? (0,_geometry__WEBPACK_IMPORTED_MODULE_1__.clampOffsetToOverflow)(offsetY, (0,
|
|
1546
|
+
? (0,_geometry__WEBPACK_IMPORTED_MODULE_1__.clampOffsetToOverflow)(offsetY, (0,_overflow__WEBPACK_IMPORTED_MODULE_0__.getYOverflowHeight)(containerElement))
|
|
1461
1547
|
: offsetY;
|
|
1462
1548
|
}
|
|
1463
1549
|
containerElement.style.transform = `translate(${translateX}px, ${translateY}px)`;
|
|
@@ -1574,6 +1660,11 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1574
1660
|
*
|
|
1575
1661
|
* Only works with 2D transforms (i.e., `matrix(a, b, c, d, e, f)`).
|
|
1576
1662
|
*
|
|
1663
|
+
* The values are the matrix's own entries rather than decomposed angles and factors: `scaleX` is
|
|
1664
|
+
* `a`, `skewY` is `b`, `skewX` is `c`, `scaleY` is `d`, and the translation is `e` and `f`. They read
|
|
1665
|
+
* as scale and skew only for a transform with no rotation - `rotate(90deg)` gives `scaleX` and
|
|
1666
|
+
* `scaleY` of 0 - and a skew is its tangent (`skewX(30deg)` gives `skewX` ≈ 0.577), not its angle.
|
|
1667
|
+
*
|
|
1577
1668
|
* @param element - The element with a CSS transform applied.
|
|
1578
1669
|
* @returns An object with parsed transformation values.
|
|
1579
1670
|
*
|
|
@@ -1628,6 +1719,27 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1628
1719
|
/* harmony import */ var _decode_base64__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./decode-base64 */ "./src/encoding/decode-base64.ts");
|
|
1629
1720
|
|
|
1630
1721
|
|
|
1722
|
+
/**
|
|
1723
|
+
* Decodes a base64 string and parses the JSON it holds - the reverse of `encodeBase64Json`.
|
|
1724
|
+
*
|
|
1725
|
+
* Never throws: a value that is not valid base64, or does not decode to valid JSON, gives `null`.
|
|
1726
|
+
* The result is only cast to `Value`, not checked against it.
|
|
1727
|
+
*
|
|
1728
|
+
* @template Value - The type the JSON is expected to have.
|
|
1729
|
+
*
|
|
1730
|
+
* @param value - The base64 string to decode.
|
|
1731
|
+
*
|
|
1732
|
+
* @returns The parsed value, or `null` when the value cannot be decoded or parsed.
|
|
1733
|
+
*
|
|
1734
|
+
* @example
|
|
1735
|
+
* ```ts
|
|
1736
|
+
* decodeBase64Json<{ name: string; age: number }>('eyJuYW1lIjoiTWlrZSIsImFnZSI6MzN9');
|
|
1737
|
+
* // ➜ { name: 'Mike', age: 33 }
|
|
1738
|
+
*
|
|
1739
|
+
* decodeBase64Json('SGVsbG8=');
|
|
1740
|
+
* // ➜ null - "Hello" is not JSON
|
|
1741
|
+
* ```
|
|
1742
|
+
*/
|
|
1631
1743
|
const decodeBase64Json = (value) => {
|
|
1632
1744
|
const decodedValue = (0,_decode_base64__WEBPACK_IMPORTED_MODULE_1__.decodeBase64)(value);
|
|
1633
1745
|
if (decodedValue === null) {
|
|
@@ -1664,12 +1776,37 @@ const decodeBase64Bytes = (value) => {
|
|
|
1664
1776
|
}
|
|
1665
1777
|
return bytes;
|
|
1666
1778
|
};
|
|
1779
|
+
/**
|
|
1780
|
+
* Decodes a base64 string to the UTF-8 text it encodes - the reverse of `encodeBase64`.
|
|
1781
|
+
*
|
|
1782
|
+
* Never throws: a value that is not padded base64 of the standard alphabet, or whose bytes are not
|
|
1783
|
+
* valid UTF-8, decodes to `null`.
|
|
1784
|
+
*
|
|
1785
|
+
* Uses `Uint8Array.fromBase64` where the runtime has it, Node's `Buffer` otherwise, and `atob`
|
|
1786
|
+
* last.
|
|
1787
|
+
*
|
|
1788
|
+
* @param value - The base64 string to decode.
|
|
1789
|
+
*
|
|
1790
|
+
* @returns The decoded text, or `null` when the value is not valid base64 of UTF-8 text.
|
|
1791
|
+
*
|
|
1792
|
+
* @example
|
|
1793
|
+
* ```ts
|
|
1794
|
+
* decodeBase64('SGVsbG8=');
|
|
1795
|
+
* // ➜ 'Hello'
|
|
1796
|
+
*
|
|
1797
|
+
* decodeBase64('not base64!');
|
|
1798
|
+
* // ➜ null
|
|
1799
|
+
* ```
|
|
1800
|
+
*/
|
|
1667
1801
|
const decodeBase64 = (value) => {
|
|
1668
1802
|
if (!BASE64_PATTERN.test(value)) {
|
|
1669
1803
|
return null;
|
|
1670
1804
|
}
|
|
1671
1805
|
try {
|
|
1672
|
-
return new TextDecoder('utf-8', {
|
|
1806
|
+
return new TextDecoder('utf-8', {
|
|
1807
|
+
fatal: true,
|
|
1808
|
+
ignoreBOM: true,
|
|
1809
|
+
}).decode(decodeBase64Bytes(value));
|
|
1673
1810
|
}
|
|
1674
1811
|
catch {
|
|
1675
1812
|
return null;
|
|
@@ -1691,6 +1828,22 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1691
1828
|
/* harmony export */ });
|
|
1692
1829
|
/* harmony import */ var _encode_base64__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./encode-base64 */ "./src/encoding/encode-base64.ts");
|
|
1693
1830
|
|
|
1831
|
+
/**
|
|
1832
|
+
* Serialises a value as JSON and encodes it as base64 - a structured value carried in a URL query
|
|
1833
|
+
* parameter, a cookie or a header.
|
|
1834
|
+
*
|
|
1835
|
+
* @template Value - The type of the value.
|
|
1836
|
+
*
|
|
1837
|
+
* @param value - Any JSON-serialisable value.
|
|
1838
|
+
*
|
|
1839
|
+
* @returns The base64 encoding of the value's JSON.
|
|
1840
|
+
*
|
|
1841
|
+
* @example
|
|
1842
|
+
* ```ts
|
|
1843
|
+
* encodeBase64Json({ name: 'Mike', age: 33 });
|
|
1844
|
+
* // ➜ 'eyJuYW1lIjoiTWlrZSIsImFnZSI6MzN9'
|
|
1845
|
+
* ```
|
|
1846
|
+
*/
|
|
1694
1847
|
const encodeBase64Json = (value) => (0,_encode_base64__WEBPACK_IMPORTED_MODULE_0__.encodeBase64)(JSON.stringify(value));
|
|
1695
1848
|
|
|
1696
1849
|
|
|
@@ -1719,6 +1872,26 @@ const encodeBase64Bytes = (bytes) => {
|
|
|
1719
1872
|
}
|
|
1720
1873
|
return btoa(binary);
|
|
1721
1874
|
};
|
|
1875
|
+
/**
|
|
1876
|
+
* Encodes a string as base64, by its UTF-8 bytes - so text outside Latin-1 (accents, emoji, any
|
|
1877
|
+
* script), which `btoa` throws on, encodes as well.
|
|
1878
|
+
*
|
|
1879
|
+
* Uses `Uint8Array.prototype.toBase64` where the runtime has it, Node's `Buffer` otherwise, and
|
|
1880
|
+
* `btoa` last.
|
|
1881
|
+
*
|
|
1882
|
+
* @param value - The string to encode.
|
|
1883
|
+
*
|
|
1884
|
+
* @returns The base64 encoding of the string's UTF-8 bytes.
|
|
1885
|
+
*
|
|
1886
|
+
* @example
|
|
1887
|
+
* ```ts
|
|
1888
|
+
* encodeBase64('Hello');
|
|
1889
|
+
* // ➜ 'SGVsbG8='
|
|
1890
|
+
*
|
|
1891
|
+
* encodeBase64('Привіт');
|
|
1892
|
+
* // ➜ '0J/RgNC40LLRltGC'
|
|
1893
|
+
* ```
|
|
1894
|
+
*/
|
|
1722
1895
|
const encodeBase64 = (value) => encodeBase64Bytes(new TextEncoder().encode(value));
|
|
1723
1896
|
|
|
1724
1897
|
|
|
@@ -1873,10 +2046,14 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1873
2046
|
* throw; otherwise `false`.
|
|
1874
2047
|
*/
|
|
1875
2048
|
const isLocalStorageReadable = () => {
|
|
1876
|
-
if (typeof window === 'undefined'
|
|
2049
|
+
if (typeof window === 'undefined') {
|
|
1877
2050
|
return false;
|
|
1878
2051
|
}
|
|
2052
|
+
// Inside the `try`: in a sandboxed iframe, or with storage blocked, reading the property throws
|
|
1879
2053
|
try {
|
|
2054
|
+
if (!window.localStorage) {
|
|
2055
|
+
return false;
|
|
2056
|
+
}
|
|
1880
2057
|
window.localStorage.getItem('__non_existing_key__');
|
|
1881
2058
|
return true;
|
|
1882
2059
|
}
|
|
@@ -2025,7 +2202,8 @@ const readFileSystemDirectoryEntries = async (directoryEntry) => {
|
|
|
2025
2202
|
*
|
|
2026
2203
|
* This function walks through all subdirectories, resolving each file into a `File` object.
|
|
2027
2204
|
* Directories themselves are not returned. To avoid unnecessary noise, certain system or
|
|
2028
|
-
* OS-generated files can be excluded via the `skipFiles` option
|
|
2205
|
+
* OS-generated files and folders can be excluded via the `skipFiles` option - a folder named there
|
|
2206
|
+
* is not entered.
|
|
2029
2207
|
*
|
|
2030
2208
|
* @param directoryEntry - The starting directory entry to traverse.
|
|
2031
2209
|
* @param options - Optional settings that control traversal behavior.
|
|
@@ -2045,18 +2223,18 @@ const traverseFileSystemDirectory = async (directoryEntry, { skipFiles = [
|
|
|
2045
2223
|
const skipFilesSet = new Set(skipFiles);
|
|
2046
2224
|
const entries = await readFileSystemDirectoryEntries(directoryEntry);
|
|
2047
2225
|
const filePromises = await (0,_async__WEBPACK_IMPORTED_MODULE_0__.runParallel)(entries, async (entry) => {
|
|
2226
|
+
if (skipFilesSet.has(entry.name)) {
|
|
2227
|
+
return [];
|
|
2228
|
+
}
|
|
2048
2229
|
if (entry.isDirectory) {
|
|
2049
2230
|
return traverseFileSystemDirectory(entry, {
|
|
2050
2231
|
skipFiles,
|
|
2051
2232
|
});
|
|
2052
2233
|
}
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
return [file];
|
|
2058
|
-
}
|
|
2059
|
-
return [];
|
|
2234
|
+
const file = await new Promise((resolve, reject) => {
|
|
2235
|
+
entry.file(resolve, reject);
|
|
2236
|
+
});
|
|
2237
|
+
return [file];
|
|
2060
2238
|
});
|
|
2061
2239
|
return filePromises.flat();
|
|
2062
2240
|
};
|
|
@@ -2126,6 +2304,17 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
2126
2304
|
/* harmony export */ not: () => (/* binding */ not),
|
|
2127
2305
|
/* harmony export */ once: () => (/* binding */ once)
|
|
2128
2306
|
/* harmony export */ });
|
|
2307
|
+
/**
|
|
2308
|
+
* A function that does nothing and returns `undefined` - a default for an optional callback, so the
|
|
2309
|
+
* caller can call it without checking it is there.
|
|
2310
|
+
*
|
|
2311
|
+
* @example
|
|
2312
|
+
* ```ts
|
|
2313
|
+
* const { onClose = noop } = props;
|
|
2314
|
+
*
|
|
2315
|
+
* onClose();
|
|
2316
|
+
* ```
|
|
2317
|
+
*/
|
|
2129
2318
|
const noop = () => { };
|
|
2130
2319
|
/**
|
|
2131
2320
|
* Checks if a value is a function.
|
|
@@ -2134,6 +2323,7 @@ const noop = () => { };
|
|
|
2134
2323
|
*
|
|
2135
2324
|
* @returns `true` if the value is a function; otherwise, `false`.
|
|
2136
2325
|
*/
|
|
2326
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- what `typeof value === 'function'` narrows `unknown` to
|
|
2137
2327
|
const isFunction = (value) => typeof value === 'function';
|
|
2138
2328
|
/**
|
|
2139
2329
|
* Creates a function that negates the result of the given predicate function.
|
|
@@ -2158,6 +2348,7 @@ const not = (fn) => (...args) => !fn(...args);
|
|
|
2158
2348
|
* Wraps a function so that it can only be executed once.
|
|
2159
2349
|
* The wrapped function remembers (caches) the result of the first invocation
|
|
2160
2350
|
* and returns that same result for all subsequent calls, regardless of the arguments provided.
|
|
2351
|
+
* Where the first invocation throws, every call throws that same error - `fn` is not run again.
|
|
2161
2352
|
*
|
|
2162
2353
|
* Common use cases include:
|
|
2163
2354
|
* - initializing singletons
|
|
@@ -2175,10 +2366,21 @@ const not = (fn) => (...args) => !fn(...args);
|
|
|
2175
2366
|
const once = (fn) => {
|
|
2176
2367
|
let called = false;
|
|
2177
2368
|
let result;
|
|
2369
|
+
let hasThrown = false;
|
|
2370
|
+
let error;
|
|
2178
2371
|
return function (...args) {
|
|
2179
2372
|
if (!called) {
|
|
2180
2373
|
called = true;
|
|
2181
|
-
|
|
2374
|
+
try {
|
|
2375
|
+
result = fn.apply(this, args);
|
|
2376
|
+
}
|
|
2377
|
+
catch (e) {
|
|
2378
|
+
hasThrown = true;
|
|
2379
|
+
error = e;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
if (hasThrown) {
|
|
2383
|
+
throw error;
|
|
2182
2384
|
}
|
|
2183
2385
|
return result;
|
|
2184
2386
|
};
|
|
@@ -2246,7 +2448,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
2246
2448
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
2247
2449
|
/* harmony export */ applyInertiaStep: () => (/* binding */ applyInertiaStep)
|
|
2248
2450
|
/* harmony export */ });
|
|
2249
|
-
/* harmony import */ var
|
|
2451
|
+
/* harmony import */ var _resolve_bounded_delta__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./resolve-bounded-delta */ "./src/geometry/resolve-bounded-delta.ts");
|
|
2250
2452
|
|
|
2251
2453
|
/**
|
|
2252
2454
|
* Applies a **single step** of bounded, velocity-based inertia.
|
|
@@ -2291,7 +2493,7 @@ const applyInertiaStep = ({ value, min, max, velocityPxMs, deltaTimeMs, friction
|
|
|
2291
2493
|
if (Math.abs(velocityPxMs) < minVelocityPxMs) {
|
|
2292
2494
|
return null;
|
|
2293
2495
|
}
|
|
2294
|
-
const nextValue = (0,
|
|
2496
|
+
const nextValue = (0,_resolve_bounded_delta__WEBPACK_IMPORTED_MODULE_0__.resolveBoundedDelta)({
|
|
2295
2497
|
delta: velocityPxMs * deltaTimeMs,
|
|
2296
2498
|
value,
|
|
2297
2499
|
min,
|
|
@@ -2356,13 +2558,24 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
2356
2558
|
/**
|
|
2357
2559
|
* Calculates the raw offset required to center an element within a container along a single axis.
|
|
2358
2560
|
*
|
|
2359
|
-
* @
|
|
2561
|
+
* @param options - The container's visible size, and the element's offset and size, along the axis.
|
|
2562
|
+
*
|
|
2563
|
+
* @returns The offset to translate the container by: negative for an element past the container's
|
|
2564
|
+
* centre, positive for one before it, and `0` for one already centred. Clamp it with
|
|
2565
|
+
* `clampOffsetToOverflow` to keep the container within its overflow.
|
|
2566
|
+
*
|
|
2567
|
+
* @example
|
|
2568
|
+
* ```ts
|
|
2569
|
+
* calculateCenterOffset({ containerSize: 100, elementOffset: 200, elementSize: 20 });
|
|
2570
|
+
* // -160
|
|
2571
|
+
* ```
|
|
2360
2572
|
*/
|
|
2361
2573
|
const calculateCenterOffset = ({ containerSize, elementOffset, elementSize, }) => {
|
|
2362
2574
|
const containerCenter = containerSize / 2;
|
|
2363
2575
|
const elementCenter = elementOffset + elementSize / 2;
|
|
2364
2576
|
const targetOffset = elementCenter - containerCenter;
|
|
2365
|
-
|
|
2577
|
+
// `|| 0` turns the `-0` of an element already centred into `0`
|
|
2578
|
+
return -targetOffset || 0;
|
|
2366
2579
|
};
|
|
2367
2580
|
|
|
2368
2581
|
|
|
@@ -2385,6 +2598,12 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
2385
2598
|
* @param overflowSize - Maximum overflow size along the axis.
|
|
2386
2599
|
*
|
|
2387
2600
|
* @returns Clamped negative offset, or `0` when no overflow exists.
|
|
2601
|
+
*
|
|
2602
|
+
* @example
|
|
2603
|
+
* ```ts
|
|
2604
|
+
* clampOffsetToOverflow(-160, 100); // -100
|
|
2605
|
+
* clampOffsetToOverflow(40, 100); // 0
|
|
2606
|
+
* ```
|
|
2388
2607
|
*/
|
|
2389
2608
|
const clampOffsetToOverflow = (offset, overflowSize) => {
|
|
2390
2609
|
if (overflowSize <= 0) {
|
|
@@ -2522,6 +2741,26 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
2522
2741
|
/* harmony export */ isUndefined: () => (/* binding */ isUndefined),
|
|
2523
2742
|
/* harmony export */ isValidDate: () => (/* binding */ isValidDate)
|
|
2524
2743
|
/* harmony export */ });
|
|
2744
|
+
/**
|
|
2745
|
+
* Asserts that a condition holds, narrowing it for the code after the call.
|
|
2746
|
+
*
|
|
2747
|
+
* A `function` declaration rather than an arrow, which TypeScript needs for an `asserts` signature
|
|
2748
|
+
* to narrow at the call site.
|
|
2749
|
+
*
|
|
2750
|
+
* @param condition - The condition expected to be truthy.
|
|
2751
|
+
* @param message - The message of the error thrown when it is not.
|
|
2752
|
+
*
|
|
2753
|
+
* @throws {Error} When `condition` is falsy.
|
|
2754
|
+
*
|
|
2755
|
+
* @example
|
|
2756
|
+
* ```ts
|
|
2757
|
+
* const user = users.find(user => user.id === id);
|
|
2758
|
+
*
|
|
2759
|
+
* assert(user, `User ${id} not found`);
|
|
2760
|
+
*
|
|
2761
|
+
* user.name; // `user` is no longer `undefined` here
|
|
2762
|
+
* ```
|
|
2763
|
+
*/
|
|
2525
2764
|
function assert(condition, message) {
|
|
2526
2765
|
if (!condition) {
|
|
2527
2766
|
throw new Error(message);
|
|
@@ -2576,21 +2815,23 @@ const isNumber = (value) => typeof value === 'number';
|
|
|
2576
2815
|
*/
|
|
2577
2816
|
const isBool = (value) => typeof value === 'boolean';
|
|
2578
2817
|
/**
|
|
2579
|
-
* Checks if a value is an object
|
|
2818
|
+
* Checks if a value is an object - anything `typeof` calls `'object'` (plain objects, arrays,
|
|
2819
|
+
* dates, maps, class instances), except `null`.
|
|
2580
2820
|
*
|
|
2581
2821
|
* @param value - The value to check.
|
|
2582
2822
|
*
|
|
2583
|
-
* @returns `true` if the value is
|
|
2823
|
+
* @returns `true` if the value is a non-null object; otherwise, `false`.
|
|
2584
2824
|
*/
|
|
2585
|
-
const isObject = (value) => typeof value === 'object';
|
|
2825
|
+
const isObject = (value) => typeof value === 'object' && value !== null;
|
|
2586
2826
|
/**
|
|
2587
|
-
* Checks if a value is an empty object (no own enumerable properties).
|
|
2827
|
+
* Checks if a value is an empty object (no own enumerable properties, symbol-keyed ones included).
|
|
2588
2828
|
*
|
|
2589
2829
|
* @param value - The value to check.
|
|
2590
2830
|
*
|
|
2591
2831
|
* @returns `true` if the value is an empty object; otherwise, `false`.
|
|
2592
2832
|
*/
|
|
2593
|
-
const isEmptyObject = (value) => isObject(value) &&
|
|
2833
|
+
const isEmptyObject = (value) => isObject(value) &&
|
|
2834
|
+
Reflect.ownKeys(value).every(key => !Object.prototype.propertyIsEnumerable.call(value, key));
|
|
2594
2835
|
/**
|
|
2595
2836
|
* Checks if a value is a Date object.
|
|
2596
2837
|
*
|
|
@@ -2829,13 +3070,23 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
2829
3070
|
* @param sourceRect - The rectangle used to measure overlap against the target.
|
|
2830
3071
|
* @param targetRect - The rectangle whose covered area is measured.
|
|
2831
3072
|
*
|
|
2832
|
-
* @returns A number between `0` and `1` representing the intersection ratio
|
|
3073
|
+
* @returns A number between `0` and `1` representing the intersection ratio - `0` for a target
|
|
3074
|
+
* with no area, which nothing can cover.
|
|
3075
|
+
*
|
|
3076
|
+
* @example
|
|
3077
|
+
* ```ts
|
|
3078
|
+
* getDOMRectIntersectionRatio(new DOMRect(0, 0, 100, 100), new DOMRect(50, 0, 100, 100));
|
|
3079
|
+
* // 0.5
|
|
3080
|
+
* ```
|
|
2833
3081
|
*/
|
|
2834
3082
|
const getDOMRectIntersectionRatio = (sourceRect, targetRect) => {
|
|
2835
3083
|
const xOverlap = Math.max(0, Math.min(sourceRect.right, targetRect.right) - Math.max(sourceRect.left, targetRect.left));
|
|
2836
3084
|
const yOverlap = Math.max(0, Math.min(sourceRect.bottom, targetRect.bottom) - Math.max(sourceRect.top, targetRect.top));
|
|
2837
3085
|
const intersectionArea = xOverlap * yOverlap;
|
|
2838
3086
|
const targetArea = targetRect.width * targetRect.height;
|
|
3087
|
+
if (targetArea <= 0) {
|
|
3088
|
+
return 0;
|
|
3089
|
+
}
|
|
2839
3090
|
return intersectionArea / targetArea;
|
|
2840
3091
|
};
|
|
2841
3092
|
|
|
@@ -2897,26 +3148,35 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
2897
3148
|
* @param options - Resolution behavior configuration.
|
|
2898
3149
|
*
|
|
2899
3150
|
* @returns A normalized, axis-aligned delta suitable for further scroll or movement processing.
|
|
3151
|
+
*
|
|
3152
|
+
* @example
|
|
3153
|
+
* ```ts
|
|
3154
|
+
* // A vertical wheel scrolling a horizontal list
|
|
3155
|
+
* resolveAxisDelta({ deltaX: 0, deltaY: 40 }, 'x');
|
|
3156
|
+
* // { deltaX: -40, deltaY: 0 }
|
|
3157
|
+
* ```
|
|
2900
3158
|
*/
|
|
2901
3159
|
const resolveAxisDelta = (delta, axis, { allowFallback = true, invert = true } = {}) => {
|
|
2902
3160
|
const sign = invert ? -1 : 1;
|
|
3161
|
+
// `|| 0` keeps an inverted zero `0` rather than `-0`
|
|
3162
|
+
const resolve = (value) => sign * value || 0;
|
|
2903
3163
|
switch (axis) {
|
|
2904
3164
|
case 'x': {
|
|
2905
3165
|
const value = delta.deltaX !== 0 ? delta.deltaX : allowFallback ? delta.deltaY : 0;
|
|
2906
3166
|
return {
|
|
2907
|
-
deltaX:
|
|
3167
|
+
deltaX: resolve(value),
|
|
2908
3168
|
deltaY: 0,
|
|
2909
3169
|
};
|
|
2910
3170
|
}
|
|
2911
3171
|
case 'y':
|
|
2912
3172
|
return {
|
|
2913
3173
|
deltaX: 0,
|
|
2914
|
-
deltaY:
|
|
3174
|
+
deltaY: resolve(delta.deltaY),
|
|
2915
3175
|
};
|
|
2916
3176
|
default:
|
|
2917
3177
|
return {
|
|
2918
|
-
deltaX:
|
|
2919
|
-
deltaY:
|
|
3178
|
+
deltaX: resolve(delta.deltaX),
|
|
3179
|
+
deltaY: resolve(delta.deltaY),
|
|
2920
3180
|
};
|
|
2921
3181
|
}
|
|
2922
3182
|
};
|
|
@@ -2950,6 +3210,27 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
2950
3210
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
2951
3211
|
/* harmony export */ parseJson: () => (/* binding */ parseJson)
|
|
2952
3212
|
/* harmony export */ });
|
|
3213
|
+
/**
|
|
3214
|
+
* Parses a JSON string without throwing.
|
|
3215
|
+
*
|
|
3216
|
+
* The result is only cast to `Value`, not checked against it - check its shape before relying on
|
|
3217
|
+
* it where the string comes from outside the program.
|
|
3218
|
+
*
|
|
3219
|
+
* @template Value - The type the JSON is expected to have.
|
|
3220
|
+
*
|
|
3221
|
+
* @param value - The JSON string to parse.
|
|
3222
|
+
*
|
|
3223
|
+
* @returns The parsed value, or `null` when the string is not valid JSON.
|
|
3224
|
+
*
|
|
3225
|
+
* @example
|
|
3226
|
+
* ```ts
|
|
3227
|
+
* parseJson<{ name: string }>('{"name":"Mike"}');
|
|
3228
|
+
* // ➜ { name: 'Mike' }
|
|
3229
|
+
*
|
|
3230
|
+
* parseJson('{name:"Mike"}');
|
|
3231
|
+
* // ➜ null
|
|
3232
|
+
* ```
|
|
3233
|
+
*/
|
|
2953
3234
|
const parseJson = (value) => {
|
|
2954
3235
|
try {
|
|
2955
3236
|
return JSON.parse(value);
|
|
@@ -3025,7 +3306,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
3025
3306
|
* @example
|
|
3026
3307
|
* ```ts
|
|
3027
3308
|
* const className = hashString('background-color: red;');
|
|
3028
|
-
* // → '
|
|
3309
|
+
* // → '1s0s409'
|
|
3029
3310
|
* ```
|
|
3030
3311
|
*/
|
|
3031
3312
|
const hashString = (input) => {
|
|
@@ -3084,6 +3365,11 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
3084
3365
|
* @param endY - The Y coordinate of the ending point.
|
|
3085
3366
|
*
|
|
3086
3367
|
* @returns The Euclidean distance between the two points.
|
|
3368
|
+
*
|
|
3369
|
+
* @example
|
|
3370
|
+
* ```ts
|
|
3371
|
+
* calculateEuclideanDistance(0, 0, 3, 4); // 5
|
|
3372
|
+
* ```
|
|
3087
3373
|
*/
|
|
3088
3374
|
const calculateEuclideanDistance = (startX, startY, endX, endY) => {
|
|
3089
3375
|
const deltaX = endX - startX;
|
|
@@ -3096,7 +3382,13 @@ const calculateEuclideanDistance = (startX, startY, endX, endY) => {
|
|
|
3096
3382
|
* @param distance - The distance.
|
|
3097
3383
|
* @param elapsedTime - The time taken to move the distance.
|
|
3098
3384
|
*
|
|
3099
|
-
* @returns The calculated speed, which is the absolute value of delta divided by elapsed time
|
|
3385
|
+
* @returns The calculated speed, which is the absolute value of delta divided by elapsed time -
|
|
3386
|
+
* `Infinity` for a distance covered in no time, and `NaN` for none covered in none.
|
|
3387
|
+
*
|
|
3388
|
+
* @example
|
|
3389
|
+
* ```ts
|
|
3390
|
+
* calculateMovingSpeed(-120, 60); // 2
|
|
3391
|
+
* ```
|
|
3100
3392
|
*/
|
|
3101
3393
|
const calculateMovingSpeed = (distance, elapsedTime) => Math.abs(distance / elapsedTime);
|
|
3102
3394
|
/**
|
|
@@ -3106,6 +3398,11 @@ const calculateMovingSpeed = (distance, elapsedTime) => Math.abs(distance / elap
|
|
|
3106
3398
|
* @param percentage - The percentage to calculate.
|
|
3107
3399
|
*
|
|
3108
3400
|
* @returns The calculated percentage of the value.
|
|
3401
|
+
*
|
|
3402
|
+
* @example
|
|
3403
|
+
* ```ts
|
|
3404
|
+
* calculatePercentage(200, 15); // 30
|
|
3405
|
+
* ```
|
|
3109
3406
|
*/
|
|
3110
3407
|
const calculatePercentage = (value, percentage) => (value * percentage) / 100;
|
|
3111
3408
|
|
|
@@ -3137,12 +3434,19 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
3137
3434
|
*
|
|
3138
3435
|
* @returns A new object containing only keys whose runtime values are not `undefined`.
|
|
3139
3436
|
*/
|
|
3140
|
-
const definedProps = (obj) =>
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3437
|
+
const definedProps = (obj) => {
|
|
3438
|
+
const result = {};
|
|
3439
|
+
Reflect.ownKeys(obj).forEach(key => {
|
|
3440
|
+
if (!Object.prototype.propertyIsEnumerable.call(obj, key)) {
|
|
3441
|
+
return;
|
|
3442
|
+
}
|
|
3443
|
+
const value = obj[key];
|
|
3444
|
+
if (value !== undefined) {
|
|
3445
|
+
result[key] = value;
|
|
3446
|
+
}
|
|
3447
|
+
});
|
|
3144
3448
|
return result;
|
|
3145
|
-
}
|
|
3449
|
+
};
|
|
3146
3450
|
|
|
3147
3451
|
|
|
3148
3452
|
/***/ },
|
|
@@ -3183,7 +3487,7 @@ const camelToDashCase = (input) => {
|
|
|
3183
3487
|
// Convert the first character to the lowercase without adding a hyphen
|
|
3184
3488
|
const firstCharProcessed = firstChar.toLowerCase();
|
|
3185
3489
|
// Process the rest of the string normally, adding hyphens before uppercase letters
|
|
3186
|
-
const restProcessed = restOfString.replace(/
|
|
3490
|
+
const restProcessed = restOfString.replace(/\p{Lu}/gu, letter => `-${letter.toLowerCase()}`);
|
|
3187
3491
|
return firstCharProcessed + restProcessed;
|
|
3188
3492
|
};
|
|
3189
3493
|
|
|
@@ -3218,7 +3522,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
3218
3522
|
* camelToWords('userIDNumber'); // → 'user ID Number'
|
|
3219
3523
|
* ```
|
|
3220
3524
|
*/
|
|
3221
|
-
const camelToWords = (input) => input.replace(/([
|
|
3525
|
+
const camelToWords = (input) => input.replace(/([\p{Ll}\p{N}])(\p{Lu})/gu, '$1 $2').replace(/(\p{Lu})(\p{Lu}\p{Ll})/gu, '$1 $2');
|
|
3222
3526
|
|
|
3223
3527
|
|
|
3224
3528
|
/***/ },
|
|
@@ -3335,7 +3639,7 @@ const getWordsInitials = (input, maxWords = Infinity) => {
|
|
|
3335
3639
|
}
|
|
3336
3640
|
return (0,_split_string_into_words__WEBPACK_IMPORTED_MODULE_0__.splitStringIntoWords)(input)
|
|
3337
3641
|
.slice(0, maxWords)
|
|
3338
|
-
.map(word => word
|
|
3642
|
+
.map(word => String.fromCodePoint(word.codePointAt(0)))
|
|
3339
3643
|
.join('')
|
|
3340
3644
|
.toUpperCase();
|
|
3341
3645
|
};
|
|
@@ -3582,7 +3886,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
3582
3886
|
* toKebabCase('hello123World'); // → 'hello123-world'
|
|
3583
3887
|
* ```
|
|
3584
3888
|
*/
|
|
3585
|
-
const toKebabCase = (input) => input.replace(/([
|
|
3889
|
+
const toKebabCase = (input) => input.replace(/([\p{Ll}\p{N}])(\p{Lu})/gu, '$1-$2').toLowerCase();
|
|
3586
3890
|
|
|
3587
3891
|
|
|
3588
3892
|
/***/ },
|
|
@@ -4064,6 +4368,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
4064
4368
|
/* harmony export */ splitStringIntoWords: () => (/* reexport safe */ _string__WEBPACK_IMPORTED_MODULE_13__.splitStringIntoWords),
|
|
4065
4369
|
/* harmony export */ timeout: () => (/* reexport safe */ _async__WEBPACK_IMPORTED_MODULE_2__.timeout),
|
|
4066
4370
|
/* harmony export */ toKebabCase: () => (/* reexport safe */ _string__WEBPACK_IMPORTED_MODULE_13__.toKebabCase),
|
|
4371
|
+
/* harmony export */ toMapBy: () => (/* reexport safe */ _array__WEBPACK_IMPORTED_MODULE_1__.toMapBy),
|
|
4067
4372
|
/* harmony export */ toggleArrayItem: () => (/* reexport safe */ _array__WEBPACK_IMPORTED_MODULE_1__.toggleArrayItem),
|
|
4068
4373
|
/* harmony export */ traverseFileSystemDirectory: () => (/* reexport safe */ _file__WEBPACK_IMPORTED_MODULE_5__.traverseFileSystemDirectory),
|
|
4069
4374
|
/* harmony export */ unique: () => (/* reexport safe */ _array__WEBPACK_IMPORTED_MODULE_1__.unique),
|