@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.
Files changed (54) hide show
  1. package/README.md +24 -12
  2. package/dist/README.md +24 -12
  3. package/dist/a11y/focus/is-html-element-focusable.d.ts +8 -6
  4. package/dist/a11y/focus/move-focus-within-container.d.ts +1 -1
  5. package/dist/array/chunk.d.ts +4 -1
  6. package/dist/array/compose.d.ts +7 -3
  7. package/dist/array/get-adjacent-items.d.ts +1 -1
  8. package/dist/array/index.d.ts +1 -0
  9. package/dist/array/intersection.d.ts +2 -1
  10. package/dist/array/is-array.d.ts +6 -0
  11. package/dist/array/is-empty-array.d.ts +7 -0
  12. package/dist/array/pipe.d.ts +5 -2
  13. package/dist/array/to-map-by.d.ts +48 -0
  14. package/dist/async/async.d.ts +10 -1
  15. package/dist/async/retry.d.ts +4 -3
  16. package/dist/dom/data-transfer.d.ts +22 -4
  17. package/dist/dom/dom.d.ts +7 -5
  18. package/dist/dom/transform/parse-2d-matrix.d.ts +5 -0
  19. package/dist/encoding/decode-base64-json.d.ts +22 -1
  20. package/dist/encoding/decode-base64.d.ts +23 -1
  21. package/dist/encoding/encode-base64-json.d.ts +16 -0
  22. package/dist/encoding/encode-base64.d.ts +20 -0
  23. package/dist/env/storage/local-storage-json.d.ts +1 -1
  24. package/dist/env/storage/local-storage.d.ts +1 -6
  25. package/dist/file.d.ts +3 -2
  26. package/dist/function/function.d.ts +12 -0
  27. package/dist/geometry/apply-inertia-step.d.ts +1 -1
  28. package/dist/geometry/layout/calculate-center-offset.d.ts +11 -1
  29. package/dist/geometry/layout/clamp-offset-to-overflow.d.ts +6 -0
  30. package/dist/geometry/resolve-bounded-delta.d.ts +1 -1
  31. package/dist/guards.d.ts +24 -3
  32. package/dist/index.cjs +1 -1
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.dev.cjs +402 -97
  35. package/dist/index.dev.cjs.map +1 -1
  36. package/dist/index.mjs +1 -1
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/intersection/get-dom-rect-intersection-ratio.d.ts +8 -1
  39. package/dist/intersection/resolve-axis-delta.d.ts +7 -0
  40. package/dist/json/parse-json.d.ts +22 -1
  41. package/dist/math/hash-string.d.ts +1 -1
  42. package/dist/math/math.d.ts +17 -1
  43. package/dist/object.d.ts +7 -1
  44. package/dist/string/for-each-char.d.ts +1 -1
  45. package/dist/string/is-nil-or-empty-string.d.ts +1 -1
  46. package/dist/string/parse-file-name.d.ts +1 -1
  47. package/dist/tree/flatten-tree.d.ts +2 -2
  48. package/dist/tree/get-tree-children.d.ts +2 -2
  49. package/dist/tree/search-tree.d.ts +2 -2
  50. package/dist/tree/tree.types.d.ts +1 -1
  51. package/package.json +8 -7
  52. package/dist/geometry/apply-inertia-step.spec.d.ts +0 -1
  53. package/dist/geometry/resolve-bounded-delta.spec.d.ts +0 -1
  54. package/dist/id/generate-ephemeral-id.spec.d.ts +0 -1
package/README.md CHANGED
@@ -99,7 +99,7 @@ splitStringIntoWords('hello world');
99
99
  * Generate a hash from a string
100
100
  */
101
101
  const hash = hashString('background-color: red;');
102
- // ➜ 'e4k1z0x'
102
+ // ➜ '1s0s409'
103
103
  ```
104
104
 
105
105
  ### Encoding Utilities
@@ -230,6 +230,7 @@ import {
230
230
  intersection,
231
231
  difference,
232
232
  toggleArrayItem,
233
+ toMapBy,
233
234
  pipe,
234
235
  compose,
235
236
  } from '@react-hive/honey-utils';
@@ -290,6 +291,16 @@ toggleArrayItem(['a', 'b'], 'b', false);
290
291
  toggleArrayItem(['a', 'b'], 'a');
291
292
  // ➜ ['b']
292
293
 
294
+ /**
295
+ * Index an array by a key
296
+ */
297
+ const users = [{ id: 'u1', name: 'Ann' }, { id: 'u2', name: 'Bob' }];
298
+
299
+ toMapBy(users, user => user.id);
300
+ // ➜ Map { 'u1' => { id: 'u1', name: 'Ann' }, 'u2' => { id: 'u2', name: 'Bob' } }
301
+ toMapBy(users, user => user.id, user => user.name);
302
+ // ➜ Map { 'u1' => 'Ann', 'u2' => 'Bob' }
303
+
293
304
  /**
294
305
  * Compose functions from left to right
295
306
  */
@@ -709,7 +720,7 @@ function divide(a: number, b: number): number {
709
720
  ---
710
721
 
711
722
  - `isString(value: unknown): value is string` - Checks if a value is a `string`.
712
- - `isNilOrEmptyString(value: unknown): value is null | undefined` - Checks if a value is `null`, `undefined`, or an empty string.
723
+ - `isNilOrEmptyString(value: unknown): value is null | undefined | ''` - Checks if a value is `null`, `undefined`, or an empty string.
713
724
  - `toKebabCase(input: string): string` - Converts a string to kebab-case.
714
725
  - `camelToDashCase(input: string): string` - Converts camelCase to dash-case.
715
726
  - `splitStringIntoWords(input: string): string[]` - Splits a string into an array of words.
@@ -744,7 +755,7 @@ function divide(a: number, b: number): number {
744
755
 
745
756
  ---
746
757
 
747
- - `definedProps<T extends object>(obj: T): DefinedProps<T>` - Creates a new object by removing all properties whose values are `undefined`.
758
+ - `definedProps<T extends object>(obj: T): DefinedProps<T>` - Creates a new object by removing all properties whose values are `undefined`, symbol-keyed ones included. A key that may be `undefined` comes out optional.
748
759
 
749
760
  ### Array Utilities
750
761
 
@@ -754,8 +765,9 @@ function divide(a: number, b: number): number {
754
765
  - `isEmptyArray(value: unknown): value is []` - Checks if a value is an empty array.
755
766
  - `compact<T>(array: (T | Falsy)[]): T[]` – Returns a new array with all falsy values (false, null, undefined, 0, '', NaN) removed, preserving only truthy items of type `T`.
756
767
  - `unique<T>(array: T[]): T[]` - Returns a new array with all duplicate elements removed. Keeps only the first occurrence of each value.
757
- - `chunk<T>(array: T[], size: number): T[][]` - Splits an array into smaller arrays ("chunks") of the specified size.
758
- - `intersection<T>(...arrays: T[][]): T[]` - Returns an array of elements that exist in all provided arrays.
768
+ - `toMapBy<Item, Key, Value>(items: readonly Item[], getKey: (item: Item, index: number) => Key, getValue?: (item: Item, index: number) => Value): Map<Key, Item | Value>` - Indexes an array by a key: a `Map` from each item's key to the item, or to what `getValue` returns for it. Where two items share a key, the later one wins.
769
+ - `chunk<T>(array: T[], size: number): T[][]` - Splits an array into smaller arrays ("chunks") of the specified size. Throws unless `size` is a positive whole number.
770
+ - `intersection<T>(...arrays: T[][]): T[]` - Returns an array of elements that exist in all provided arrays, each once, in the order of the first array.
759
771
  - `difference<T>(array: T[], exclude: T[]): T[]` - Returns a new array that contains items from `array` that are not present in `exclude`.
760
772
  - `pipe(...fns: Function[]): Function` - Composes unary functions left-to-right. Returns a new function that applies all given functions in a sequence.
761
773
  - `compose(...fns: Function[]): Function` - Composes unary functions **right-to-left**. Same as `pipe`, but applies functions in reverse order.
@@ -770,7 +782,7 @@ function divide(a: number, b: number): number {
770
782
  - `noop(): void` - A no-operation function.
771
783
  - `not<Args extends any[]>(fn: (...args: Args) => any): (...args: Args) => boolean` - Creates a new function that negates the result of the given predicate function. Useful for logical inversions, e.g., turning `isEven` into `isOdd`.
772
784
  - `invokeIfFunction<Args extends any[], Result>(input: ((...args: Args) => Result) | Result, ...args: Args): Result` - Invokes the input if it's a function, otherwise returns it as-is.
773
- - `once<T extends (...args: any[]) => any>(fn: T): T` - Wraps a function so it can only be executed once. The result of the first invocation is cached and returned for all subsequent calls. Preserves both the original function’s parameter types and `this` binding.
785
+ - `once<T extends (...args: any[]) => any>(fn: T): T` - Wraps a function so it can only be executed once. The result of the first invocation is cached and returned for all subsequent calls; if it throws, every call throws that same error. Preserves both the original function’s parameter types and `this` binding.
774
786
 
775
787
  ### Type Guards
776
788
 
@@ -779,9 +791,9 @@ function divide(a: number, b: number): number {
779
791
  - `assert(condition: any, message: string): asserts condition` - Asserts that a condition is truthy, throwing an error with the provided message if it's not.
780
792
  - `isNumber(value: unknown): value is number` - Checks if a value is a `number`.
781
793
  - `isBool(value: unknown): value is boolean` - Checks if a value is a `boolean`.
782
- - `isObject(value: unknown): value is object` - Checks if a value is an `object`.
794
+ - `isObject(value: unknown): value is object` - Checks if a value is an `object`, `null` excluded.
783
795
  - `isNil(value: unknown): value is null | undefined` - Checks if a value is `null` or `undefined`.
784
- - `isEmptyObject(value: unknown): value is Record<string, never>` - Checks if a value is an empty object.
796
+ - `isEmptyObject(value: unknown): value is Record<string, never>` - Checks if a value is an empty object - no own enumerable properties, symbol-keyed ones included.
785
797
  - `isNull(value: unknown): value is null` - Checks if a value is `null`.
786
798
  - `isUndefined(value: unknown): value is undefined` - Checks if a value is `undefined`.
787
799
  - `isDefined<T>(value: T): value is NonNullable<T>` - Checks if a value is neither `null` nor `undefined`.
@@ -838,7 +850,7 @@ function divide(a: number, b: number): number {
838
850
  - `cloneBlob(blob: Blob): Blob` - Creates a clone of a Blob object with the same content and type as the original.
839
851
  - `getElementOffsetRect(element: HTMLElement): DOMRect` - Returns a `DOMRect` representing the element's layout position using `offsetLeft`, `offsetTop`, and `clientWidth`/`clientHeight`.
840
852
  - `isAnchorHtmlElement(element: HTMLElement): element is HTMLAnchorElement` - Determines whether the provided element is an `<a>` tag. Acts as a type guard that narrows the element to `HTMLAnchorElement`.
841
- - `isContentEditableHtmlElement(element: HTMLElement): boolean` - Returns `true` if the element has `contenteditable="true"`, making it user-editable and implicitly focusable.
853
+ - `isContentEditableHtmlElement(element: HTMLElement): boolean` - Returns `true` if the element's `contenteditable` is `""`, `"true"` or `"plaintext-only"` (in any case), making it user-editable and implicitly focusable.
842
854
 
843
855
  #### File
844
856
 
@@ -890,8 +902,8 @@ function divide(a: number, b: number): number {
890
902
  - `everyAsync<Item>(array: Item[], predicate): Promise<boolean>` - Returns `true` if **all** items in the array pass the async predicate.
891
903
  - `findAsync<Item>(array: Item[], predicate): Promise<Nullable<Item>>` - Returns the first array item that passes the async predicate, or `null` if no match is found.
892
904
  - `delay(delayMs: number): Promise<void>` - Creates a promise that resolves after the specified delay in milliseconds.
893
- - `timeout<T>(promise: Promise<T>, timeoutMs: number, message?: string): Promise<T>` - Wraps a promise with a timeout. If the promise does not settle within the given duration, it rejects with a timeout error.
894
- - `retry<Task, TaskResult>(task: Task, options?: RetryOptions): Function` - Wraps an asynchronous function with retry logic, with configurable max attempts, delay between retries, exponential backoff, and retry callbacks.
905
+ - `timeout<T>(promise: Promise<T>, timeoutMs: number, message?: string): Promise<T>` - Wraps a promise with a timeout. If the promise does not settle within the given duration, it rejects with a timeout error. Its timer is cleared however the race ends.
906
+ - `retry<Args, TaskResult>(task: (...args: Args) => Promise<TaskResult>, options?: RetryOptions): (...args: Args) => Promise<TaskResult>` - Wraps an asynchronous function with retry logic, with configurable max attempts (at least one), delay between retries, exponential backoff, and retry callbacks.
895
907
 
896
908
  ### Intersection Utilities
897
909
 
@@ -906,7 +918,7 @@ function divide(a: number, b: number): number {
906
918
 
907
919
  #### Focus
908
920
 
909
- - `isHtmlElementFocusable(element: Nullable<HTMLElement>): boolean` - Checks whether an element is considered focusable according to browser rules. Factors include: visibility, `display`, `disabled`, `tabindex`, native focusable tags, `contenteditable`, and presence of a non-null `tabindex`.
921
+ - `isHtmlElementFocusable(element: Nullable<HTMLElement>): boolean` - Checks whether an element is considered focusable according to browser rules. Factors include: visibility, `display` (an ancestor's too), `:disabled` (a disabled `<fieldset>`'s controls too), a valid `tabindex` (negative removes it, non-negative makes any element focusable), native focusable tags, and `contenteditable`.
910
922
  - `getFocusableHtmlElements(container: HTMLElement): HTMLElement[]` - Returns all focusable descendant elements within a container, using `isHtmlElementFocusable` to filter them.
911
923
  - `moveFocusWithinContainer(direction: FocusMoveDirection, container?: Nullable<HTMLElement>, options?: MoveFocusWithinContainerOptions): void` - Moves focus to the next or previous focusable element within a container. Supports cyclic navigation, optional wrapping control, and custom focus index resolution for advanced keyboard navigation patterns (e.g. roving tabindex).
912
924
 
package/dist/README.md CHANGED
@@ -99,7 +99,7 @@ splitStringIntoWords('hello world');
99
99
  * Generate a hash from a string
100
100
  */
101
101
  const hash = hashString('background-color: red;');
102
- // ➜ 'e4k1z0x'
102
+ // ➜ '1s0s409'
103
103
  ```
104
104
 
105
105
  ### Encoding Utilities
@@ -230,6 +230,7 @@ import {
230
230
  intersection,
231
231
  difference,
232
232
  toggleArrayItem,
233
+ toMapBy,
233
234
  pipe,
234
235
  compose,
235
236
  } from '@react-hive/honey-utils';
@@ -290,6 +291,16 @@ toggleArrayItem(['a', 'b'], 'b', false);
290
291
  toggleArrayItem(['a', 'b'], 'a');
291
292
  // ➜ ['b']
292
293
 
294
+ /**
295
+ * Index an array by a key
296
+ */
297
+ const users = [{ id: 'u1', name: 'Ann' }, { id: 'u2', name: 'Bob' }];
298
+
299
+ toMapBy(users, user => user.id);
300
+ // ➜ Map { 'u1' => { id: 'u1', name: 'Ann' }, 'u2' => { id: 'u2', name: 'Bob' } }
301
+ toMapBy(users, user => user.id, user => user.name);
302
+ // ➜ Map { 'u1' => 'Ann', 'u2' => 'Bob' }
303
+
293
304
  /**
294
305
  * Compose functions from left to right
295
306
  */
@@ -709,7 +720,7 @@ function divide(a: number, b: number): number {
709
720
  ---
710
721
 
711
722
  - `isString(value: unknown): value is string` - Checks if a value is a `string`.
712
- - `isNilOrEmptyString(value: unknown): value is null | undefined` - Checks if a value is `null`, `undefined`, or an empty string.
723
+ - `isNilOrEmptyString(value: unknown): value is null | undefined | ''` - Checks if a value is `null`, `undefined`, or an empty string.
713
724
  - `toKebabCase(input: string): string` - Converts a string to kebab-case.
714
725
  - `camelToDashCase(input: string): string` - Converts camelCase to dash-case.
715
726
  - `splitStringIntoWords(input: string): string[]` - Splits a string into an array of words.
@@ -744,7 +755,7 @@ function divide(a: number, b: number): number {
744
755
 
745
756
  ---
746
757
 
747
- - `definedProps<T extends object>(obj: T): DefinedProps<T>` - Creates a new object by removing all properties whose values are `undefined`.
758
+ - `definedProps<T extends object>(obj: T): DefinedProps<T>` - Creates a new object by removing all properties whose values are `undefined`, symbol-keyed ones included. A key that may be `undefined` comes out optional.
748
759
 
749
760
  ### Array Utilities
750
761
 
@@ -754,8 +765,9 @@ function divide(a: number, b: number): number {
754
765
  - `isEmptyArray(value: unknown): value is []` - Checks if a value is an empty array.
755
766
  - `compact<T>(array: (T | Falsy)[]): T[]` – Returns a new array with all falsy values (false, null, undefined, 0, '', NaN) removed, preserving only truthy items of type `T`.
756
767
  - `unique<T>(array: T[]): T[]` - Returns a new array with all duplicate elements removed. Keeps only the first occurrence of each value.
757
- - `chunk<T>(array: T[], size: number): T[][]` - Splits an array into smaller arrays ("chunks") of the specified size.
758
- - `intersection<T>(...arrays: T[][]): T[]` - Returns an array of elements that exist in all provided arrays.
768
+ - `toMapBy<Item, Key, Value>(items: readonly Item[], getKey: (item: Item, index: number) => Key, getValue?: (item: Item, index: number) => Value): Map<Key, Item | Value>` - Indexes an array by a key: a `Map` from each item's key to the item, or to what `getValue` returns for it. Where two items share a key, the later one wins.
769
+ - `chunk<T>(array: T[], size: number): T[][]` - Splits an array into smaller arrays ("chunks") of the specified size. Throws unless `size` is a positive whole number.
770
+ - `intersection<T>(...arrays: T[][]): T[]` - Returns an array of elements that exist in all provided arrays, each once, in the order of the first array.
759
771
  - `difference<T>(array: T[], exclude: T[]): T[]` - Returns a new array that contains items from `array` that are not present in `exclude`.
760
772
  - `pipe(...fns: Function[]): Function` - Composes unary functions left-to-right. Returns a new function that applies all given functions in a sequence.
761
773
  - `compose(...fns: Function[]): Function` - Composes unary functions **right-to-left**. Same as `pipe`, but applies functions in reverse order.
@@ -770,7 +782,7 @@ function divide(a: number, b: number): number {
770
782
  - `noop(): void` - A no-operation function.
771
783
  - `not<Args extends any[]>(fn: (...args: Args) => any): (...args: Args) => boolean` - Creates a new function that negates the result of the given predicate function. Useful for logical inversions, e.g., turning `isEven` into `isOdd`.
772
784
  - `invokeIfFunction<Args extends any[], Result>(input: ((...args: Args) => Result) | Result, ...args: Args): Result` - Invokes the input if it's a function, otherwise returns it as-is.
773
- - `once<T extends (...args: any[]) => any>(fn: T): T` - Wraps a function so it can only be executed once. The result of the first invocation is cached and returned for all subsequent calls. Preserves both the original function’s parameter types and `this` binding.
785
+ - `once<T extends (...args: any[]) => any>(fn: T): T` - Wraps a function so it can only be executed once. The result of the first invocation is cached and returned for all subsequent calls; if it throws, every call throws that same error. Preserves both the original function’s parameter types and `this` binding.
774
786
 
775
787
  ### Type Guards
776
788
 
@@ -779,9 +791,9 @@ function divide(a: number, b: number): number {
779
791
  - `assert(condition: any, message: string): asserts condition` - Asserts that a condition is truthy, throwing an error with the provided message if it's not.
780
792
  - `isNumber(value: unknown): value is number` - Checks if a value is a `number`.
781
793
  - `isBool(value: unknown): value is boolean` - Checks if a value is a `boolean`.
782
- - `isObject(value: unknown): value is object` - Checks if a value is an `object`.
794
+ - `isObject(value: unknown): value is object` - Checks if a value is an `object`, `null` excluded.
783
795
  - `isNil(value: unknown): value is null | undefined` - Checks if a value is `null` or `undefined`.
784
- - `isEmptyObject(value: unknown): value is Record<string, never>` - Checks if a value is an empty object.
796
+ - `isEmptyObject(value: unknown): value is Record<string, never>` - Checks if a value is an empty object - no own enumerable properties, symbol-keyed ones included.
785
797
  - `isNull(value: unknown): value is null` - Checks if a value is `null`.
786
798
  - `isUndefined(value: unknown): value is undefined` - Checks if a value is `undefined`.
787
799
  - `isDefined<T>(value: T): value is NonNullable<T>` - Checks if a value is neither `null` nor `undefined`.
@@ -838,7 +850,7 @@ function divide(a: number, b: number): number {
838
850
  - `cloneBlob(blob: Blob): Blob` - Creates a clone of a Blob object with the same content and type as the original.
839
851
  - `getElementOffsetRect(element: HTMLElement): DOMRect` - Returns a `DOMRect` representing the element's layout position using `offsetLeft`, `offsetTop`, and `clientWidth`/`clientHeight`.
840
852
  - `isAnchorHtmlElement(element: HTMLElement): element is HTMLAnchorElement` - Determines whether the provided element is an `<a>` tag. Acts as a type guard that narrows the element to `HTMLAnchorElement`.
841
- - `isContentEditableHtmlElement(element: HTMLElement): boolean` - Returns `true` if the element has `contenteditable="true"`, making it user-editable and implicitly focusable.
853
+ - `isContentEditableHtmlElement(element: HTMLElement): boolean` - Returns `true` if the element's `contenteditable` is `""`, `"true"` or `"plaintext-only"` (in any case), making it user-editable and implicitly focusable.
842
854
 
843
855
  #### File
844
856
 
@@ -890,8 +902,8 @@ function divide(a: number, b: number): number {
890
902
  - `everyAsync<Item>(array: Item[], predicate): Promise<boolean>` - Returns `true` if **all** items in the array pass the async predicate.
891
903
  - `findAsync<Item>(array: Item[], predicate): Promise<Nullable<Item>>` - Returns the first array item that passes the async predicate, or `null` if no match is found.
892
904
  - `delay(delayMs: number): Promise<void>` - Creates a promise that resolves after the specified delay in milliseconds.
893
- - `timeout<T>(promise: Promise<T>, timeoutMs: number, message?: string): Promise<T>` - Wraps a promise with a timeout. If the promise does not settle within the given duration, it rejects with a timeout error.
894
- - `retry<Task, TaskResult>(task: Task, options?: RetryOptions): Function` - Wraps an asynchronous function with retry logic, with configurable max attempts, delay between retries, exponential backoff, and retry callbacks.
905
+ - `timeout<T>(promise: Promise<T>, timeoutMs: number, message?: string): Promise<T>` - Wraps a promise with a timeout. If the promise does not settle within the given duration, it rejects with a timeout error. Its timer is cleared however the race ends.
906
+ - `retry<Args, TaskResult>(task: (...args: Args) => Promise<TaskResult>, options?: RetryOptions): (...args: Args) => Promise<TaskResult>` - Wraps an asynchronous function with retry logic, with configurable max attempts (at least one), delay between retries, exponential backoff, and retry callbacks.
895
907
 
896
908
  ### Intersection Utilities
897
909
 
@@ -906,7 +918,7 @@ function divide(a: number, b: number): number {
906
918
 
907
919
  #### Focus
908
920
 
909
- - `isHtmlElementFocusable(element: Nullable<HTMLElement>): boolean` - Checks whether an element is considered focusable according to browser rules. Factors include: visibility, `display`, `disabled`, `tabindex`, native focusable tags, `contenteditable`, and presence of a non-null `tabindex`.
921
+ - `isHtmlElementFocusable(element: Nullable<HTMLElement>): boolean` - Checks whether an element is considered focusable according to browser rules. Factors include: visibility, `display` (an ancestor's too), `:disabled` (a disabled `<fieldset>`'s controls too), a valid `tabindex` (negative removes it, non-negative makes any element focusable), native focusable tags, and `contenteditable`.
910
922
  - `getFocusableHtmlElements(container: HTMLElement): HTMLElement[]` - Returns all focusable descendant elements within a container, using `isHtmlElementFocusable` to filter them.
911
923
  - `moveFocusWithinContainer(direction: FocusMoveDirection, container?: Nullable<HTMLElement>, options?: MoveFocusWithinContainerOptions): void` - Moves focus to the next or previous focusable element within a container. Supports cyclic navigation, optional wrapping control, and custom focus index resolution for advanced keyboard navigation patterns (e.g. roving tabindex).
912
924
 
@@ -1,15 +1,17 @@
1
- import type { Nullable } from '~/types';
1
+ import type { Nullable } from '../../types';
2
2
  export declare const FOCUSABLE_HTML_TAGS: string[];
3
3
  /**
4
4
  * Determines whether an HTMLElement is focusable under standard browser rules.
5
5
  *
6
6
  * The function checks a combination of factors:
7
- * - The element must be rendered (not `display: none` or `visibility: hidden`).
8
- * - Disabled form controls are never focusable.
9
- * - Elements with `tabindex="-1"` are intentionally removed from the focus order.
7
+ * - The element must be rendered: neither it nor an ancestor has `display: none`, and it is not
8
+ * `visibility: hidden`.
9
+ * - Disabled form controls are never focusable - including those inside a disabled `<fieldset>`.
10
+ * - Elements with a negative `tabindex` are intentionally removed from the focus order.
11
+ * - Any element with a valid, non-negative `tabindex` is focusable. A `tabindex` that is not an
12
+ * integer is ignored, as browsers ignore it.
10
13
  * - Certain native HTML elements are inherently focusable (e.g. inputs, buttons, anchors with `href`).
11
- * - Elements with `contenteditable="true"` are treated as focusable.
12
- * - Any element with a valid `tabindex` (not null) is considered focusable.
14
+ * - Editable elements (`contenteditable`) are treated as focusable.
13
15
  *
14
16
  * This logic approximates how browsers and the accessibility tree
15
17
  * determine real-world focusability—not just tabindex presence.
@@ -1,4 +1,4 @@
1
- import type { Nullable } from '~/types';
1
+ import type { Nullable } from '../../types';
2
2
  export type FocusMoveDirection = 'next' | 'previous';
3
3
  export interface MoveFocusWithinContainerOptions {
4
4
  /**
@@ -6,11 +6,14 @@
6
6
  * @template T - The type of the items in the array.
7
7
  *
8
8
  * @param array - The input array to be chunked.
9
- * @param size - The size of each chunk. Must be greater than 0.
9
+ * @param size - The size of each chunk. Must be a positive whole number.
10
10
  *
11
11
  * @returns An array of chunks, where each chunk is an array of the specified size
12
12
  * (except possibly the last chunk, which may be smaller).
13
13
  *
14
+ * @throws {Error} When `size` is not a positive whole number - a fractional size would give
15
+ * chunks of other sizes, and `Infinity` none at all.
16
+ *
14
17
  * @example
15
18
  * ```ts
16
19
  * chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
@@ -1,17 +1,21 @@
1
- type ComposeFn = (arg: unknown) => unknown;
1
+ /**
2
+ * Any unary function: one taking a typed argument is assignable here, where it is not to
3
+ * `ComposeFn`.
4
+ */
5
+ type AnyUnaryFn = (arg: never) => unknown;
2
6
  type Compose = {
3
7
  <A, R>(fn1: (a: A) => R): (a: A) => R;
4
8
  <A, B, R>(fn1: (b: B) => R, fn2: (a: A) => B): (a: A) => R;
5
9
  <A, B, C, R>(fn1: (c: C) => R, fn2: (b: B) => C, fn3: (a: A) => B): (a: A) => R;
6
10
  <A, B, C, D, R>(fn1: (d: D) => R, fn2: (c: C) => D, fn3: (b: B) => C, fn4: (a: A) => B): (a: A) => R;
7
11
  <A, B, C, D, E, R>(fn1: (e: E) => R, fn2: (d: D) => E, fn3: (c: C) => D, fn4: (b: B) => C, fn5: (a: A) => B): (a: A) => R;
8
- (...fns: ComposeFn[]): (arg: unknown) => unknown;
12
+ (...fns: AnyUnaryFn[]): (arg: unknown) => unknown;
9
13
  };
10
14
  /**
11
15
  * Composes multiple unary functions into a single function, applying them from **right to left**.
12
16
  *
13
17
  * Often used for building functional pipelines where the innermost function runs first.
14
- * Types are inferred up to 5 chained functions for full type safety.
18
+ * Types are inferred up to 5 chained functions for full type safety. Beyond that, it falls back to the unknown.
15
19
  *
16
20
  * @param fns - A list of unary functions to compose.
17
21
  *
@@ -1,4 +1,4 @@
1
- import type { Nullable } from '~/types';
1
+ import type { Nullable } from '../types';
2
2
  export interface AdjacentItems<T> {
3
3
  previousItem: Nullable<T>;
4
4
  currentItem: Nullable<T>;
@@ -9,3 +9,4 @@ export * from './is-array';
9
9
  export * from './is-empty-array';
10
10
  export * from './get-adjacent-items';
11
11
  export * from './toggle-array-item';
12
+ export * from './to-map-by';
@@ -5,7 +5,8 @@
5
5
  *
6
6
  * @param arrays - Two or more arrays to find common elements from.
7
7
  *
8
- * @returns A new array containing only the elements that exist in all input arrays.
8
+ * @returns A new array containing only the elements that exist in all input arrays, each once, in
9
+ * the order of the first array.
9
10
  *
10
11
  * @example
11
12
  * ```ts
@@ -4,5 +4,11 @@
4
4
  * @param value - The value to check.
5
5
  *
6
6
  * @returns `true` if the value is an array; otherwise, `false`.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * isArray([1, 2]); // true
11
+ * isArray('12'); // false
12
+ * ```
7
13
  */
8
14
  export declare const isArray: (value: unknown) => value is unknown[];
@@ -4,5 +4,12 @@
4
4
  * @param value - The value to check.
5
5
  *
6
6
  * @returns `true` if the value is an empty array; otherwise, `false`.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * isEmptyArray([]); // true
11
+ * isEmptyArray([1]); // false
12
+ * isEmptyArray({}); // false
13
+ * ```
7
14
  */
8
15
  export declare const isEmptyArray: (value: unknown) => value is [];
@@ -1,11 +1,14 @@
1
- type PipeFn = (arg: unknown) => unknown;
1
+ /**
2
+ * Any unary function: one taking a typed argument is assignable here, where it is not to `PipeFn`.
3
+ */
4
+ type AnyUnaryFn = (arg: never) => unknown;
2
5
  type Pipe = {
3
6
  <A, B>(fn1: (a: A) => B): (a: A) => B;
4
7
  <A, B, C>(fn1: (a: A) => B, fn2: (b: B) => C): (a: A) => C;
5
8
  <A, B, C, D>(fn1: (a: A) => B, fn2: (b: B) => C, fn3: (c: C) => D): (a: A) => D;
6
9
  <A, B, C, D, E>(fn1: (a: A) => B, fn2: (b: B) => C, fn3: (c: C) => D, fn4: (d: D) => E): (a: A) => E;
7
10
  <A, B, C, D, E, F>(fn1: (a: A) => B, fn2: (b: B) => C, fn3: (c: C) => D, fn4: (d: D) => E, fn5: (e: E) => F): (a: A) => F;
8
- (...fns: PipeFn[]): (arg: unknown) => unknown;
11
+ (...fns: AnyUnaryFn[]): (arg: unknown) => unknown;
9
12
  };
10
13
  /**
11
14
  * Composes multiple unary functions into a single function, applying them from left to right.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Indexes an array by a key: a `Map` from each item's key to the item itself.
3
+ *
4
+ * Items are added in order, so where two share a key the later one wins, as with
5
+ * `new Map(entries)`.
6
+ *
7
+ * @template Item - The type of the items in the array.
8
+ * @template Key - The type of the keys.
9
+ *
10
+ * @param items - The items to index.
11
+ * @param getKey - Returns the key an item is stored under.
12
+ *
13
+ * @returns A new `Map` from each key to its item.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const users = [{ id: 'u1', name: 'Ann' }, { id: 'u2', name: 'Bob' }];
18
+ *
19
+ * toMapBy(users, user => user.id);
20
+ * // Map { 'u1' => { id: 'u1', name: 'Ann' }, 'u2' => { id: 'u2', name: 'Bob' } }
21
+ * ```
22
+ */
23
+ export declare function toMapBy<Item, Key>(items: readonly Item[], getKey: (item: Item, index: number) => Key): Map<Key, Item>;
24
+ /**
25
+ * Indexes an array by a key: a `Map` from each item's key to what `getValue` returns for the item.
26
+ *
27
+ * Items are added in order, so where two share a key the later one wins, as with
28
+ * `new Map(entries)`.
29
+ *
30
+ * @template Item - The type of the items in the array.
31
+ * @template Key - The type of the keys.
32
+ * @template Value - The type of the values.
33
+ *
34
+ * @param items - The items to index.
35
+ * @param getKey - Returns the key an item is stored under.
36
+ * @param getValue - Returns the value stored for an item.
37
+ *
38
+ * @returns A new `Map` from each key to its value.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * const users = [{ id: 'u1', name: 'Ann' }, { id: 'u2', name: 'Bob' }];
43
+ *
44
+ * toMapBy(users, user => user.id, user => user.name);
45
+ * // Map { 'u1' => 'Ann', 'u2' => 'Bob' }
46
+ * ```
47
+ */
48
+ export declare function toMapBy<Item, Key, Value>(items: readonly Item[], getKey: (item: Item, index: number) => Key, getValue: (item: Item, index: number) => Value): Map<Key, Value>;
@@ -1,4 +1,4 @@
1
- import type { Nullable } from '~/types';
1
+ import type { Nullable } from '../types';
2
2
  /**
3
3
  * Checks if a value is a Promise.
4
4
  *
@@ -109,6 +109,9 @@ export declare const filterParallel: <Item>(array: Item[], predicate: (item: Ite
109
109
  /**
110
110
  * Asynchronously checks if at least one element in the array satisfies the async condition.
111
111
  *
112
+ * Runs one item at a time, in order, and stops at the first item that satisfies it - as
113
+ * `Array.prototype.some` does.
114
+ *
112
115
  * @param array - The array of items to check.
113
116
  * @param predicate - An async function that returns a boolean.
114
117
  *
@@ -118,6 +121,9 @@ export declare const someAsync: <Item>(array: Item[], predicate: (item: Item, in
118
121
  /**
119
122
  * Asynchronously checks if all elements in the array satisfy the async condition.
120
123
  *
124
+ * Runs one item at a time, in order, and stops at the first item that fails it - as
125
+ * `Array.prototype.every` does.
126
+ *
121
127
  * @param array - The array of items to check.
122
128
  * @param predicate - An async function that returns a boolean.
123
129
  *
@@ -140,6 +146,9 @@ export declare const reduceAsync: <Item, Accumulator>(array: Item[], fn: (accumu
140
146
  /**
141
147
  * Asynchronously finds the first element that satisfies the async condition.
142
148
  *
149
+ * Runs one item at a time, in order, and stops at the first match - as `Array.prototype.find`
150
+ * does.
151
+ *
143
152
  * @param array - The array of items to search.
144
153
  * @param predicate - An async function that returns a boolean.
145
154
  *
@@ -1,6 +1,7 @@
1
1
  interface RetryOptions {
2
2
  /**
3
- * Maximum number of retry attempts before failing.
3
+ * The total number of attempts, the first one included, before the last error is thrown. Taken
4
+ * as at least 1.
4
5
  *
5
6
  * @default 3
6
7
  */
@@ -35,7 +36,7 @@ interface RetryOptions {
35
36
  *
36
37
  * Useful for operations that may fail intermittently, such as network requests.
37
38
  *
38
- * @template Task - The type of the async function to wrap.
39
+ * @template Args - The arguments the async function takes.
39
40
  * @template TaskResult - The result type of the async function.
40
41
  *
41
42
  * @param task - The async function to wrap with retry logic.
@@ -68,5 +69,5 @@ interface RetryOptions {
68
69
  * .catch(error => console.error('Failed after retries:', error));
69
70
  * ```
70
71
  */
71
- export declare const retry: <Task extends (...args: unknown[]) => Promise<TaskResult>, TaskResult>(task: Task, { maxAttempts, delayMs, backoff, onRetry }?: RetryOptions) => ((...args: Parameters<Task>) => Promise<TaskResult>);
72
+ export declare const retry: <Args extends unknown[], TaskResult>(task: (...args: Args) => Promise<TaskResult>, { maxAttempts, delayMs, backoff, onRetry }?: RetryOptions) => ((...args: Args) => Promise<TaskResult>);
72
73
  export {};
@@ -1,19 +1,37 @@
1
- import type { Nullable } from '~/types';
1
+ import type { Nullable } from '../types';
2
2
  export declare const DATA_TRANSFER_JSON_MIME = "application/json";
3
3
  /**
4
4
  * Serializes and stores JSON data in a DataTransfer object.
5
5
  *
6
6
  * Useful for drag-and-drop communication between elements.
7
7
  *
8
- * @param dataTransfer DataTransfer instance from a drag event.
9
- * @param data Data to serialize.
8
+ * @template T - The type of the data.
9
+ *
10
+ * @param dataTransfer - DataTransfer instance from a drag event.
11
+ * @param data - Data to serialize.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * element.addEventListener('dragstart', event => {
16
+ * setDataTransferJson(event.dataTransfer, { id: 'card-1' });
17
+ * });
18
+ * ```
10
19
  */
11
20
  export declare const setDataTransferJson: <T>(dataTransfer: DataTransfer, data: T) => void;
12
21
  /**
13
22
  * Retrieves and parses JSON data from a DataTransfer object.
14
23
  *
15
- * @param dataTransfer DataTransfer instance from a drop event.
24
+ * @template Value - The type the JSON is expected to have.
25
+ *
26
+ * @param dataTransfer - DataTransfer instance from a drop event.
16
27
  *
17
28
  * @returns Parsed JSON value or null if not present or invalid.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * element.addEventListener('drop', event => {
33
+ * const card = getDataTransferJson<{ id: string }>(event.dataTransfer);
34
+ * });
35
+ * ```
18
36
  */
19
37
  export declare const getDataTransferJson: <Value>(dataTransfer: DataTransfer) => Nullable<Value>;
package/dist/dom/dom.d.ts CHANGED
@@ -9,8 +9,9 @@ export declare const cloneBlob: (blob: Blob) => Blob;
9
9
  /**
10
10
  * Returns the bounding DOMRect of an element based on offset and client dimensions.
11
11
  *
12
- * This utility is useful when you need a stable, layout-based rect
13
- * without triggering a reflow via `getBoundingClientRect()`.
12
+ * Unlike `getBoundingClientRect()`, the rect is the element's layout box, before any CSS
13
+ * transform - so it stays put while the element or its container is being translated or scaled.
14
+ * Reading it still needs layout, as `getBoundingClientRect()` does.
14
15
  *
15
16
  * @param element - The target HTML element.
16
17
  * @returns A `DOMRect` representing the element’s offset position and size.
@@ -32,11 +33,12 @@ export declare const isAnchorHtmlElement: (element: HTMLElement) => element is H
32
33
  /**
33
34
  * Checks whether an element is explicitly marked as contenteditable.
34
35
  *
35
- * Browsers treat elements with `contenteditable="true"` as focusable,
36
- * even if they are not normally keyboard-focusable.
36
+ * Browsers treat editable elements as focusable, even if they are not normally
37
+ * keyboard-focusable. An element is editable with `contenteditable` set to `""` (the bare
38
+ * attribute), `"true"` or `"plaintext-only"`, in any case.
37
39
  *
38
40
  * @param element - The element to inspect.
39
41
  *
40
- * @returns True if `contenteditable="true"` is set.
42
+ * @returns True if the element's own `contenteditable` makes it editable.
41
43
  */
42
44
  export declare const isContentEditableHtmlElement: (element: HTMLElement) => boolean;
@@ -11,6 +11,11 @@ interface HTMLElementTransformationValues {
11
11
  *
12
12
  * Only works with 2D transforms (i.e., `matrix(a, b, c, d, e, f)`).
13
13
  *
14
+ * The values are the matrix's own entries rather than decomposed angles and factors: `scaleX` is
15
+ * `a`, `skewY` is `b`, `skewX` is `c`, `scaleY` is `d`, and the translation is `e` and `f`. They read
16
+ * as scale and skew only for a transform with no rotation - `rotate(90deg)` gives `scaleX` and
17
+ * `scaleY` of 0 - and a skew is its tangent (`skewX(30deg)` gives `skewX` ≈ 0.577), not its angle.
18
+ *
14
19
  * @param element - The element with a CSS transform applied.
15
20
  * @returns An object with parsed transformation values.
16
21
  *
@@ -1,2 +1,23 @@
1
- import type { Nullable } from '~/types';
1
+ import type { Nullable } from '../types';
2
+ /**
3
+ * Decodes a base64 string and parses the JSON it holds - the reverse of `encodeBase64Json`.
4
+ *
5
+ * Never throws: a value that is not valid base64, or does not decode to valid JSON, gives `null`.
6
+ * The result is only cast to `Value`, not checked against it.
7
+ *
8
+ * @template Value - The type the JSON is expected to have.
9
+ *
10
+ * @param value - The base64 string to decode.
11
+ *
12
+ * @returns The parsed value, or `null` when the value cannot be decoded or parsed.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * decodeBase64Json<{ name: string; age: number }>('eyJuYW1lIjoiTWlrZSIsImFnZSI6MzN9');
17
+ * // ➜ { name: 'Mike', age: 33 }
18
+ *
19
+ * decodeBase64Json('SGVsbG8=');
20
+ * // ➜ null - "Hello" is not JSON
21
+ * ```
22
+ */
2
23
  export declare const decodeBase64Json: <Value>(value: string) => Nullable<Value>;
@@ -1,2 +1,24 @@
1
- import type { Nullable } from '~/types';
1
+ import type { Nullable } from '../types';
2
+ /**
3
+ * Decodes a base64 string to the UTF-8 text it encodes - the reverse of `encodeBase64`.
4
+ *
5
+ * Never throws: a value that is not padded base64 of the standard alphabet, or whose bytes are not
6
+ * valid UTF-8, decodes to `null`.
7
+ *
8
+ * Uses `Uint8Array.fromBase64` where the runtime has it, Node's `Buffer` otherwise, and `atob`
9
+ * last.
10
+ *
11
+ * @param value - The base64 string to decode.
12
+ *
13
+ * @returns The decoded text, or `null` when the value is not valid base64 of UTF-8 text.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * decodeBase64('SGVsbG8=');
18
+ * // ➜ 'Hello'
19
+ *
20
+ * decodeBase64('not base64!');
21
+ * // ➜ null
22
+ * ```
23
+ */
2
24
  export declare const decodeBase64: (value: string) => Nullable<string>;