@venizia/ardor-react 0.4.8-0 → 0.4.8-2

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 (85) hide show
  1. package/README.md +5 -4
  2. package/dist/contexts/application.d.ts +4 -0
  3. package/dist/contexts/application.js +1 -0
  4. package/dist/hooks/element-target.d.ts +9 -0
  5. package/dist/hooks/element-target.js +7 -0
  6. package/dist/hooks/index.d.ts +6 -1
  7. package/dist/hooks/index.js +5 -1
  8. package/dist/hooks/redux/use-app-dispatch.d.ts +5 -1
  9. package/dist/hooks/redux/use-app-dispatch.js +5 -1
  10. package/dist/hooks/redux/use-app-selector.js +27 -2
  11. package/dist/hooks/resize-observation.d.ts +19 -0
  12. package/dist/hooks/resize-observation.js +67 -0
  13. package/dist/hooks/use-application-context.d.ts +2 -0
  14. package/dist/hooks/use-application-context.js +2 -0
  15. package/dist/hooks/use-artifact.d.ts +26 -0
  16. package/dist/hooks/use-artifact.js +24 -2
  17. package/dist/hooks/use-autosave.d.ts +7 -0
  18. package/dist/hooks/use-autosave.js +4 -0
  19. package/dist/hooks/use-before-unload.d.ts +2 -0
  20. package/dist/hooks/use-before-unload.js +1 -0
  21. package/dist/hooks/use-confirm.d.ts +6 -0
  22. package/dist/hooks/use-confirm.js +1 -0
  23. package/dist/hooks/use-copy-to-clipboard.d.ts +21 -1
  24. package/dist/hooks/use-copy-to-clipboard.js +39 -15
  25. package/dist/hooks/use-debounce.d.ts +4 -0
  26. package/dist/hooks/use-debounce.js +1 -0
  27. package/dist/hooks/use-document-visibility.d.ts +3 -0
  28. package/dist/hooks/use-document-visibility.js +10 -0
  29. package/dist/hooks/use-element-size.d.ts +13 -1
  30. package/dist/hooks/use-element-size.js +11 -20
  31. package/dist/hooks/use-injectable.d.ts +32 -1
  32. package/dist/hooks/use-injectable.js +17 -3
  33. package/dist/hooks/use-intersection.d.ts +28 -0
  34. package/dist/hooks/use-intersection.js +50 -0
  35. package/dist/hooks/use-media-query.d.ts +5 -0
  36. package/dist/hooks/use-media-query.js +14 -0
  37. package/dist/hooks/use-online-status.d.ts +3 -0
  38. package/dist/hooks/use-online-status.js +14 -0
  39. package/dist/hooks/use-resize-effect.d.ts +31 -0
  40. package/dist/hooks/use-resize-effect.js +42 -0
  41. package/dist/hooks/use-stored-state.d.ts +28 -1
  42. package/dist/hooks/use-stored-state.js +38 -11
  43. package/dist/hooks/use-window-dimensions.d.ts +1 -0
  44. package/dist/hooks/use-window-dimensions.js +1 -0
  45. package/package.json +16 -16
  46. package/dist/contexts/application.d.ts.map +0 -1
  47. package/dist/contexts/application.js.map +0 -1
  48. package/dist/contexts/index.d.ts.map +0 -1
  49. package/dist/contexts/index.js.map +0 -1
  50. package/dist/hooks/index.d.ts.map +0 -1
  51. package/dist/hooks/index.js.map +0 -1
  52. package/dist/hooks/redux/index.d.ts.map +0 -1
  53. package/dist/hooks/redux/index.js.map +0 -1
  54. package/dist/hooks/redux/use-app-dispatch.d.ts.map +0 -1
  55. package/dist/hooks/redux/use-app-dispatch.js.map +0 -1
  56. package/dist/hooks/redux/use-app-selector.d.ts.map +0 -1
  57. package/dist/hooks/redux/use-app-selector.js.map +0 -1
  58. package/dist/hooks/use-application-context.d.ts.map +0 -1
  59. package/dist/hooks/use-application-context.js.map +0 -1
  60. package/dist/hooks/use-artifact.d.ts.map +0 -1
  61. package/dist/hooks/use-artifact.js.map +0 -1
  62. package/dist/hooks/use-autosave.d.ts.map +0 -1
  63. package/dist/hooks/use-autosave.js.map +0 -1
  64. package/dist/hooks/use-before-unload.d.ts.map +0 -1
  65. package/dist/hooks/use-before-unload.js.map +0 -1
  66. package/dist/hooks/use-confirm.d.ts.map +0 -1
  67. package/dist/hooks/use-confirm.js.map +0 -1
  68. package/dist/hooks/use-copy-to-clipboard.d.ts.map +0 -1
  69. package/dist/hooks/use-copy-to-clipboard.js.map +0 -1
  70. package/dist/hooks/use-debounce.d.ts.map +0 -1
  71. package/dist/hooks/use-debounce.js.map +0 -1
  72. package/dist/hooks/use-element-size.d.ts.map +0 -1
  73. package/dist/hooks/use-element-size.js.map +0 -1
  74. package/dist/hooks/use-injectable.d.ts.map +0 -1
  75. package/dist/hooks/use-injectable.js.map +0 -1
  76. package/dist/hooks/use-sizer.d.ts +0 -10
  77. package/dist/hooks/use-sizer.d.ts.map +0 -1
  78. package/dist/hooks/use-sizer.js +0 -23
  79. package/dist/hooks/use-sizer.js.map +0 -1
  80. package/dist/hooks/use-stored-state.d.ts.map +0 -1
  81. package/dist/hooks/use-stored-state.js.map +0 -1
  82. package/dist/hooks/use-window-dimensions.d.ts.map +0 -1
  83. package/dist/hooks/use-window-dimensions.js.map +0 -1
  84. package/dist/index.d.ts.map +0 -1
  85. package/dist/index.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { ResizeObservation } from './resize-observation.js';
2
3
  const ZERO_SIZE = { width: 0, height: 0, measured: false };
3
4
  const readSize = (opts) => {
4
5
  const { entry, box, round } = opts;
@@ -16,36 +17,26 @@ export function useElementSize(opts = {}) {
16
17
  React.useEffect(() => {
17
18
  setSize((current) => (current.measured ? ZERO_SIZE : current));
18
19
  const target = ref ? ref.current : element;
19
- if (!target || typeof ResizeObserver === 'undefined') {
20
+ if (!target) {
20
21
  return () => { };
21
22
  }
22
- let frame;
23
- let latest;
24
- const observer = new ResizeObserver((entries) => {
25
- latest = entries[entries.length - 1];
26
- if (frame !== undefined) {
27
- return;
28
- }
29
- frame = requestAnimationFrame(() => {
30
- frame = undefined;
31
- if (!latest) {
23
+ const observation = ResizeObservation.observe({
24
+ box,
25
+ onFrame: (entries) => {
26
+ const entry = entries.at(-1);
27
+ if (!entry) {
32
28
  return;
33
29
  }
34
- const next = readSize({ entry: latest, box, round });
30
+ const next = readSize({ entry, box, round });
35
31
  setSize((current) => {
36
32
  return current.measured && current.width === next.width && current.height === next.height
37
33
  ? current
38
34
  : next;
39
35
  });
40
- });
36
+ },
41
37
  });
42
- observer.observe(target, { box });
43
- return () => {
44
- observer.disconnect();
45
- if (frame !== undefined) {
46
- cancelAnimationFrame(frame);
47
- }
48
- };
38
+ observation.sync([target]);
39
+ return observation.stop;
49
40
  }, [ref, element, box, round]);
50
41
  return ref ? size : { ...size, ref: setElement };
51
42
  }
@@ -1,14 +1,31 @@
1
1
  import { type Container, type TClass } from '@venizia/ignis-inversion';
2
2
  import type { CoreBindings, ValueOf } from '@venizia/ardor-kernel';
3
+ /** Declaration-merge string keys into this interface to make them valid `key` values of the injectable hooks. */
3
4
  export interface IUseInjectableKeysOverrides {
4
5
  }
6
+ /** The binding keys the kernel defines (the string values of `CoreBindings`). */
5
7
  export type TUseInjectableKeysDefault = Extract<ValueOf<typeof CoreBindings>, string>;
8
+ /** A key the injectable hooks accept: a kernel binding key or one added to `IUseInjectableKeysOverrides`. */
6
9
  export type TUseInjectableKeys = TUseInjectableKeysDefault | keyof IUseInjectableKeysOverrides;
10
+ /**
11
+ * Resolve by `key` or by `target` class, never both.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { CoreBindings, type TUseInjectableOptions } from '@venizia/ardor';
16
+ *
17
+ * const byKey: TUseInjectableOptions = { key: CoreBindings.DEFAULT_REST_DATA_PROVIDER };
18
+ * class Greeter {}
19
+ * const byTarget: TUseInjectableOptions = { target: Greeter };
20
+ * ```
21
+ */
7
22
  export type TUseInjectableOptions = {
23
+ /** The container to resolve from. @default The application's container (`ApplicationContext`) */
8
24
  container?: Container;
9
25
  key: TUseInjectableKeys;
10
26
  target?: never;
11
27
  } | {
28
+ /** The container to resolve from. @default The application's container (`ApplicationContext`) */
12
29
  container?: Container;
13
30
  key?: never;
14
31
  target: TClass<unknown>;
@@ -16,9 +33,23 @@ export type TUseInjectableOptions = {
16
33
  /**
17
34
  * The container an injectable hook resolves from: the one passed in, else the application's. Shared,
18
35
  * so a hook never checks one container and resolves from another.
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * import { CoreBindings, useApplicationContainer } from '@venizia/ardor';
40
+ *
41
+ * export function BindingCheck() {
42
+ * const container = useApplicationContainer();
43
+ * return <span>{container.isBound({ key: CoreBindings.DEFAULT_REST_DATA_PROVIDER }) ? 'bound' : 'unbound'}</span>;
44
+ * }
45
+ * ```
19
46
  */
20
- export declare const useInjectableContainer: (opts?: {
47
+ export declare const useApplicationContainer: (opts?: {
21
48
  container?: Container;
22
49
  }) => Container;
50
+ /**
51
+ * Resolves an instance from the container by `key` or by `target` class. Throws when the key is not
52
+ * bound, when the class has no recorded binding key, or when neither `key` nor `target` is given.
53
+ */
23
54
  export declare const useInjectable: <TInstance>(opts: TUseInjectableOptions) => TInstance;
24
55
  //# sourceMappingURL=use-injectable.d.ts.map
@@ -4,19 +4,33 @@ import { ApplicationContext } from '../contexts/application.js';
4
4
  /**
5
5
  * The container an injectable hook resolves from: the one passed in, else the application's. Shared,
6
6
  * so a hook never checks one container and resolves from another.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * import { CoreBindings, useApplicationContainer } from '@venizia/ardor';
11
+ *
12
+ * export function BindingCheck() {
13
+ * const container = useApplicationContainer();
14
+ * return <span>{container.isBound({ key: CoreBindings.DEFAULT_REST_DATA_PROVIDER }) ? 'bound' : 'unbound'}</span>;
15
+ * }
16
+ * ```
7
17
  */
8
- export const useInjectableContainer = (opts) => {
18
+ export const useApplicationContainer = (opts) => {
9
19
  const applicationContext = React.useContext(ApplicationContext);
10
20
  const container = opts?.container ?? applicationContext.container;
11
21
  if (!container) {
12
22
  throw getError({
13
- message: '[useInjectable] Failed to determine injectable container!',
23
+ message: '[useApplicationContainer] Failed to determine the application container!',
14
24
  });
15
25
  }
16
26
  return container;
17
27
  };
28
+ /**
29
+ * Resolves an instance from the container by `key` or by `target` class. Throws when the key is not
30
+ * bound, when the class has no recorded binding key, or when neither `key` nor `target` is given.
31
+ */
18
32
  export const useInjectable = (opts) => {
19
- const container = useInjectableContainer({ container: opts?.container });
33
+ const container = useApplicationContainer({ container: opts?.container });
20
34
  const { key, target } = opts;
21
35
  if (key) {
22
36
  if (!container.isBound({ key })) {
@@ -0,0 +1,28 @@
1
+ import { type TElementTarget } from './element-target.js';
2
+ export interface IUseIntersectionOptions {
3
+ /** Read after every render: an element, a ref to one, or `null`. */
4
+ target: TElementTarget;
5
+ /** @default '0px' */
6
+ rootMargin?: string;
7
+ /** The ratio of the element in view at which an entry is delivered; a single number, not an array. @default 0 */
8
+ threshold?: number;
9
+ }
10
+ /**
11
+ * The latest `IntersectionObserverEntry` of the target, or `undefined` before the first one and
12
+ * where there is no `IntersectionObserver`. The state changes only when `isIntersecting` or the
13
+ * ratio changed. A new element, `rootMargin` or `threshold` starts again from `undefined`.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * import React from 'react';
18
+ * import { useIntersection } from '@venizia/ardor';
19
+ *
20
+ * export function Lazy() {
21
+ * const ref = React.useRef<HTMLDivElement>(null);
22
+ * const entry = useIntersection({ target: ref, threshold: 0.5 });
23
+ * return <div ref={ref}>{entry?.isIntersecting ? 'visible' : 'hidden'}</div>;
24
+ * }
25
+ * ```
26
+ */
27
+ export declare const useIntersection: (opts: IUseIntersectionOptions) => IntersectionObserverEntry | undefined;
28
+ //# sourceMappingURL=use-intersection.d.ts.map
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import { ElementTargets } from './element-target.js';
3
+ /**
4
+ * The latest `IntersectionObserverEntry` of the target, or `undefined` before the first one and
5
+ * where there is no `IntersectionObserver`. The state changes only when `isIntersecting` or the
6
+ * ratio changed. A new element, `rootMargin` or `threshold` starts again from `undefined`.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * import React from 'react';
11
+ * import { useIntersection } from '@venizia/ardor';
12
+ *
13
+ * export function Lazy() {
14
+ * const ref = React.useRef<HTMLDivElement>(null);
15
+ * const entry = useIntersection({ target: ref, threshold: 0.5 });
16
+ * return <div ref={ref}>{entry?.isIntersecting ? 'visible' : 'hidden'}</div>;
17
+ * }
18
+ * ```
19
+ */
20
+ export const useIntersection = (opts) => {
21
+ const { target, rootMargin, threshold } = opts;
22
+ const [element, setElement] = React.useState(null);
23
+ const [entry, setEntry] = React.useState();
24
+ // oxlint-disable-next-line react-hooks/exhaustive-deps -- the target is read after every render, and a state set to the same element renders nothing
25
+ React.useEffect(() => {
26
+ setElement(ElementTargets.resolve({ target }));
27
+ });
28
+ React.useEffect(() => {
29
+ setEntry(undefined);
30
+ if (!element || typeof IntersectionObserver === 'undefined') {
31
+ return () => { };
32
+ }
33
+ const observer = new IntersectionObserver((entries) => {
34
+ const latest = entries.at(-1);
35
+ if (!latest) {
36
+ return;
37
+ }
38
+ setEntry((current) => {
39
+ return current?.isIntersecting === latest.isIntersecting &&
40
+ current.intersectionRatio === latest.intersectionRatio
41
+ ? current
42
+ : latest;
43
+ });
44
+ }, { rootMargin, threshold });
45
+ observer.observe(element);
46
+ return () => observer.disconnect();
47
+ }, [element, rootMargin, threshold]);
48
+ return entry;
49
+ };
50
+ //# sourceMappingURL=use-intersection.js.map
@@ -0,0 +1,5 @@
1
+ /** Whether the media query matches now, and again after each change. `false` in a server render. */
2
+ export declare const useMediaQuery: (opts: {
3
+ query: string;
4
+ }) => boolean;
5
+ //# sourceMappingURL=use-media-query.d.ts.map
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ /** Whether the media query matches now, and again after each change. `false` in a server render. */
3
+ export const useMediaQuery = (opts) => {
4
+ const { query } = opts;
5
+ const subscribe = React.useMemo(() => {
6
+ return (listener) => {
7
+ const list = window.matchMedia(query);
8
+ list.addEventListener('change', listener);
9
+ return () => list.removeEventListener('change', listener);
10
+ };
11
+ }, [query]);
12
+ return React.useSyncExternalStore(subscribe, () => window.matchMedia(query).matches, () => false);
13
+ };
14
+ //# sourceMappingURL=use-media-query.js.map
@@ -0,0 +1,3 @@
1
+ /** Whether the browser is online, kept by the `online` and `offline` events. `true` in a server render. */
2
+ export declare const useOnlineStatus: () => boolean;
3
+ //# sourceMappingURL=use-online-status.d.ts.map
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ const subscribe = (listener) => {
3
+ window.addEventListener('online', listener);
4
+ window.addEventListener('offline', listener);
5
+ return () => {
6
+ window.removeEventListener('online', listener);
7
+ window.removeEventListener('offline', listener);
8
+ };
9
+ };
10
+ /** Whether the browser is online, kept by the `online` and `offline` events. `true` in a server render. */
11
+ export const useOnlineStatus = () => {
12
+ return React.useSyncExternalStore(subscribe, () => navigator.onLine, () => true);
13
+ };
14
+ //# sourceMappingURL=use-online-status.js.map
@@ -0,0 +1,31 @@
1
+ import { type TElementTarget } from './element-target.js';
2
+ import { type TResizeBox } from './resize-observation.js';
3
+ export interface IUseResizeEffectOptions {
4
+ /** Read after every render: an element, a ref to one, or `null`, which is skipped. */
5
+ targets: TElementTarget[];
6
+ /** Receives the latest entry of each element that resized since the last call. */
7
+ onResize: (entries: ResizeObserverEntry[]) => void;
8
+ /** @default 'content-box' */
9
+ box?: TResizeBox;
10
+ /** Also observe the direct children of each target, and again whenever they change. @default false */
11
+ children?: boolean;
12
+ }
13
+ /**
14
+ * Calls `onResize` at most once per animation frame when any of the targets resizes. A new
15
+ * `onResize` is used without observing again, and the targets are read after every render, so an
16
+ * element held in state, a ref whose element changed and a list that grows or shrinks are followed.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * import React from 'react';
21
+ * import { useResizeEffect } from '@venizia/ardor';
22
+ *
23
+ * export function Panel() {
24
+ * const ref = React.useRef<HTMLDivElement>(null);
25
+ * useResizeEffect({ targets: [ref], onResize: (entries) => console.log(entries.length) });
26
+ * return <div ref={ref} />;
27
+ * }
28
+ * ```
29
+ */
30
+ export declare const useResizeEffect: (opts: IUseResizeEffectOptions) => void;
31
+ //# sourceMappingURL=use-resize-effect.d.ts.map
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { ElementTargets } from './element-target.js';
3
+ import { ResizeObservation } from './resize-observation.js';
4
+ /**
5
+ * Calls `onResize` at most once per animation frame when any of the targets resizes. A new
6
+ * `onResize` is used without observing again, and the targets are read after every render, so an
7
+ * element held in state, a ref whose element changed and a list that grows or shrinks are followed.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * import React from 'react';
12
+ * import { useResizeEffect } from '@venizia/ardor';
13
+ *
14
+ * export function Panel() {
15
+ * const ref = React.useRef<HTMLDivElement>(null);
16
+ * useResizeEffect({ targets: [ref], onResize: (entries) => console.log(entries.length) });
17
+ * return <div ref={ref} />;
18
+ * }
19
+ * ```
20
+ */
21
+ export const useResizeEffect = (opts) => {
22
+ const { targets, onResize, box = 'content-box', children = false } = opts;
23
+ const handleResize = React.useRef(onResize);
24
+ const observation = React.useRef(undefined);
25
+ React.useEffect(() => {
26
+ handleResize.current = onResize;
27
+ return () => { };
28
+ }, [onResize]);
29
+ React.useEffect(() => {
30
+ const created = ResizeObservation.observe({
31
+ box,
32
+ children,
33
+ onFrame: (entries) => handleResize.current(entries),
34
+ });
35
+ observation.current = created;
36
+ return created.stop;
37
+ }, [box, children]);
38
+ React.useEffect(() => {
39
+ observation.current?.sync(targets.flatMap((target) => ElementTargets.resolve({ target }) ?? []));
40
+ });
41
+ };
42
+ //# sourceMappingURL=use-resize-effect.js.map
@@ -1,21 +1,48 @@
1
1
  import { type IStorageCodec, type TStorageArea } from '@venizia/ardor-kernel';
2
2
  export interface IUseStoredStateOptions<TValue> {
3
+ /** The storage key. */
3
4
  key: string;
5
+ /** The value while the key holds nothing. */
4
6
  defaultValue: TValue;
5
7
  /** @default 'local' */
6
8
  storage?: TStorageArea;
7
9
  /** @default JSON */
8
10
  codec?: IStorageCodec<TValue>;
11
+ /** Write storage once, this many milliseconds after the last `setValue`; `value` changes at once. Above 0, also pass a stable `codec`. @default 0 */
12
+ writeDelayMs?: number;
9
13
  }
10
14
  export interface IUseStoredStateReturn<TValue> {
15
+ /** The stored value, or `defaultValue` when the key holds nothing. */
11
16
  value: TValue;
17
+ /** Stores a value, or the result of an updater called with the current one. */
12
18
  setValue: (next: TValue | ((current: TValue) => TValue)) => void;
19
+ /** Removes the key, so `value` is `defaultValue` again. */
13
20
  remove: () => void;
14
21
  }
22
+ export interface IUseStoredState {
23
+ /** With a codec, the value type is the codec's: a literal `defaultValue` is checked against it, not widened past it. */
24
+ <TValue>(opts: Omit<IUseStoredStateOptions<TValue>, 'defaultValue' | 'codec'> & {
25
+ defaultValue: NoInfer<TValue>;
26
+ codec: IStorageCodec<TValue>;
27
+ }): IUseStoredStateReturn<TValue>;
28
+ <TValue>(opts: IUseStoredStateOptions<TValue>): IUseStoredStateReturn<TValue>;
29
+ }
15
30
  /**
16
31
  * A value kept in `localStorage` or `sessionStorage`, in step with other hooks on the key, with code
17
32
  * that writes through `BrowserStorage`, and with other tabs. Pass a stable `codec` and `defaultValue`.
18
33
  * A write or a removal that the storage refuses is kept in this hook until the storage next changes.
34
+ * With `writeDelayMs` the value waits in the same place for the delay: a change of the storage from
35
+ * outside drops it, and an unmount or a new key or storage writes it at once.
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * import { useStoredState } from '@venizia/ardor';
40
+ *
41
+ * export function ThemeToggle() {
42
+ * const { value, setValue } = useStoredState({ key: 'theme', defaultValue: 'light' });
43
+ * return <button onClick={() => setValue(value === 'light' ? 'dark' : 'light')}>{value}</button>;
44
+ * }
45
+ * ```
19
46
  */
20
- export declare const useStoredState: <TValue>(opts: IUseStoredStateOptions<TValue>) => IUseStoredStateReturn<TValue>;
47
+ export declare const useStoredState: IUseStoredState;
21
48
  //# sourceMappingURL=use-stored-state.d.ts.map
@@ -1,25 +1,48 @@
1
1
  import React from 'react';
2
- import { BrowserStorage } from '@venizia/ardor-kernel';
3
- /** Reads the stored text as it is, `null` when there is none. */
4
- const RAW_TEXT = {
5
- encode: (text) => text ?? '',
6
- decode: (text) => text,
7
- };
2
+ import { BrowserStorage, StorageCodecs, } from '@venizia/ardor-kernel';
3
+ const RAW_TEXT = StorageCodecs.text();
8
4
  const isUpdater = (next) => typeof next === 'function';
9
5
  /**
10
6
  * A value kept in `localStorage` or `sessionStorage`, in step with other hooks on the key, with code
11
7
  * that writes through `BrowserStorage`, and with other tabs. Pass a stable `codec` and `defaultValue`.
12
8
  * A write or a removal that the storage refuses is kept in this hook until the storage next changes.
9
+ * With `writeDelayMs` the value waits in the same place for the delay: a change of the storage from
10
+ * outside drops it, and an unmount or a new key or storage writes it at once.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { useStoredState } from '@venizia/ardor';
15
+ *
16
+ * export function ThemeToggle() {
17
+ * const { value, setValue } = useStoredState({ key: 'theme', defaultValue: 'light' });
18
+ * return <button onClick={() => setValue(value === 'light' ? 'dark' : 'light')}>{value}</button>;
19
+ * }
20
+ * ```
13
21
  */
14
22
  export const useStoredState = (opts) => {
15
- const { key, defaultValue, storage, codec } = opts;
23
+ const { key, defaultValue, storage, codec, writeDelayMs = 0 } = opts;
16
24
  const decoded = React.useRef(undefined);
17
25
  const keptRef = React.useRef(undefined);
26
+ const timerRef = React.useRef(undefined);
18
27
  const [kept, setKept] = React.useState();
28
+ // Every change of the kept value ends a pending write; only a delayed setValue starts one.
19
29
  const keep = React.useCallback((next) => {
30
+ clearTimeout(timerRef.current);
31
+ timerRef.current = undefined;
20
32
  keptRef.current = next;
21
33
  setKept(next);
22
34
  }, []);
35
+ const write = React.useCallback((entry) => {
36
+ keep(BrowserStorage.set(entry) ? undefined : entry);
37
+ }, [keep]);
38
+ // A new key or storage, and an unmount, write the pending value to the key it was set for.
39
+ React.useEffect(() => {
40
+ return () => {
41
+ if (timerRef.current !== undefined && keptRef.current) {
42
+ write(keptRef.current);
43
+ }
44
+ };
45
+ }, [key, storage, write]);
23
46
  const subscribe = React.useCallback((listener) => {
24
47
  return BrowserStorage.subscribe({
25
48
  key,
@@ -48,10 +71,14 @@ export const useStoredState = (opts) => {
48
71
  const setValue = React.useCallback((next) => {
49
72
  const hit = keptRef.current;
50
73
  const current = hit?.key === key && hit.storage === storage ? hit.value : getSnapshot();
51
- const resolved = isUpdater(next) ? next(current) : next;
52
- const isStored = BrowserStorage.set({ key, value: resolved, storage, codec });
53
- keep(isStored ? undefined : { key, storage, value: resolved });
54
- }, [key, storage, codec, getSnapshot, keep]);
74
+ const entry = { key, storage, codec, value: isUpdater(next) ? next(current) : next };
75
+ if (!(writeDelayMs > 0)) {
76
+ write(entry);
77
+ return;
78
+ }
79
+ keep(entry);
80
+ timerRef.current = setTimeout(() => write(entry), writeDelayMs);
81
+ }, [key, storage, codec, writeDelayMs, getSnapshot, keep, write]);
55
82
  const remove = React.useCallback(() => {
56
83
  const isRemoved = BrowserStorage.remove({ key, storage });
57
84
  keep(isRemoved ? undefined : { key, storage, value: defaultValue });
@@ -1,3 +1,4 @@
1
+ /** The window's `innerWidth` and `innerHeight`, updated on resize; 0 x 0 where there is no window. */
1
2
  export declare const useWindowDimensions: () => {
2
3
  width: number;
3
4
  height: number;
@@ -6,6 +6,7 @@ const readWindowDimensions = () => {
6
6
  ? { width: window.innerWidth, height: window.innerHeight }
7
7
  : { width: 0, height: 0 };
8
8
  };
9
+ /** The window's `innerWidth` and `innerHeight`, updated on resize; 0 x 0 where there is no window. */
9
10
  export const useWindowDimensions = () => {
10
11
  const [windowDimensions, setWindowDimensions] = React.useState(readWindowDimensions);
11
12
  React.useEffect(() => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@venizia/ardor-react",
3
- "version": "0.4.8-0",
4
- "description": "ARDOR React bindings - the application context, the injectable and logger hooks that read it, typed Redux hook factories, and the framework-agnostic UI hooks (debounce, autosave, confirm, clipboard, sizer, window dimensions). No react-admin.",
3
+ "version": "0.4.8-2",
4
+ "description": "ARDOR React bindings - the application context, the injectable and logger hooks that read it, typed Redux hook factories, and the framework-agnostic UI hooks (debounce, autosave, confirm, clipboard, element size, window dimensions). No react-admin.",
5
5
  "keywords": [
6
6
  "ardor",
7
7
  "ardor-react",
@@ -35,6 +35,7 @@
35
35
  "LICENSE.md",
36
36
  "dist",
37
37
  "!/**/__tests__",
38
+ "!**/*.map",
38
39
  "!**/*.tsbuildinfo"
39
40
  ],
40
41
  "type": "module",
@@ -58,8 +59,7 @@
58
59
  "prepublishOnly": "make rebuild"
59
60
  },
60
61
  "dependencies": {
61
- "@venizia/ardor-kernel": "0.4.8-0",
62
- "lodash": "^4.18.1"
62
+ "@venizia/ardor-kernel": "0.4.8-2"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@happy-dom/global-registrator": "^21.0.4",
@@ -67,17 +67,18 @@
67
67
  "@size-limit/preset-small-lib": "^14.2.0",
68
68
  "@testing-library/react": "^16.3.3",
69
69
  "@types/bun": "^1.4.3",
70
- "@types/lodash": "^4.17.27",
71
70
  "@types/react": "^19",
72
- "@venizia/dev-configs": "^0.4.7",
73
- "@venizia/ignis-filter": "^0.4.6",
74
- "@venizia/ignis-helpers": "^0.4.7",
75
- "@venizia/ignis-inversion": "^0.4.6",
76
- "@venizia/ignis-kernel": "^0.4.7",
71
+ "@types/react-dom": "^19",
72
+ "@venizia/dev-configs": "^0.4.8-1",
73
+ "@venizia/ignis-filter": "^0.4.7-1",
74
+ "@venizia/ignis-helpers": "^0.4.8-1",
75
+ "@venizia/ignis-inversion": "^0.4.7-1",
76
+ "@venizia/ignis-kernel": "^0.4.8-1",
77
77
  "eslint": "^10.13.0",
78
78
  "oxlint": "1.89.0",
79
79
  "oxlint-tsgolint": "7.0.2004",
80
80
  "react": "^19.3.1",
81
+ "react-dom": "^19.3.1",
81
82
  "react-redux": "^9.3.0",
82
83
  "reflect-metadata": "^0.2.2",
83
84
  "size-limit": "^14.2.0",
@@ -86,10 +87,10 @@
86
87
  },
87
88
  "peerDependencies": {
88
89
  "@reduxjs/toolkit": ">=2.13.0",
89
- "@venizia/ignis-filter": "^0.4.6",
90
- "@venizia/ignis-helpers": "^0.4.7",
91
- "@venizia/ignis-inversion": "^0.4.6",
92
- "@venizia/ignis-kernel": "^0.4.7",
90
+ "@venizia/ignis-filter": "^0.4.7-1",
91
+ "@venizia/ignis-helpers": "^0.4.8-1",
92
+ "@venizia/ignis-inversion": "^0.4.7-1",
93
+ "@venizia/ignis-kernel": "^0.4.8-1",
93
94
  "react": ">=19.3.1",
94
95
  "react-redux": ">=9.3.0",
95
96
  "reflect-metadata": ">=0.2.2"
@@ -107,11 +108,10 @@
107
108
  "@reduxjs/toolkit",
108
109
  "@venizia/ardor-kernel",
109
110
  "@venizia/ignis-inversion",
110
- "lodash",
111
111
  "react",
112
112
  "react-redux"
113
113
  ],
114
- "limit": "2.55 KB"
114
+ "limit": "3450 B"
115
115
  }
116
116
  ],
117
117
  "engines": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"application.d.ts","sourceRoot":"","sources":["../../src/contexts/application.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAE3D,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CACxB;AAED,eAAO,MAAM,kBAAkB,yCAG7B,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"application.js","sourceRoot":"","sources":["../../src/contexts/application.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAW1B,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC,aAAa,CAA2B;IAC9E,SAAS,EAAE,IAAI;IACf,MAAM,EAAE,IAAI;CACb,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contexts/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contexts/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/redux/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hooks/redux/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-app-dispatch.d.ts","sourceRoot":"","sources":["../../../src/hooks/redux/use-app-dispatch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGhE,qBAAa,aAAa;IACxB,qGAAqG;IACrG,MAAM,CAAC,MAAM,CAAC,SAAS,SAAS,QAAQ,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC;;mDAS9D,0BAAoB;;CAkBxC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-app-dispatch.js","sourceRoot":"","sources":["../../../src/hooks/redux/use-app-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,OAAO,aAAa;IACxB,qGAAqG;IACrG,MAAM,CAAC,MAAM;QACX,MAAM,cAAc,GAAG,WAAW,CAAC,SAAS,EAAa,CAAC;QAE1D,MAAM,sBAAsB,GAAG,GAAG,EAAE;YAClC,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;YAIlC,MAAM,gBAAgB,GAAG,KAAK,CAAC,WAAW,CACxC,CAAC,GAAG,OAA6B,EAAE,EAAE;gBACnC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;oBACzB,IAAI,MAAM,EAAE,CAAC;wBACX,QAAQ,CAAC,MAAM,CAAC,CAAC;oBACnB,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAC;YAEF,OAAO,gBAAgB,CAAC;QAC1B,CAAC,CAAC;QAEF,OAAO;YACL,cAAc;YACd,sBAAsB;SACvB,CAAC;IACJ,CAAC;CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-app-selector.d.ts","sourceRoot":"","sources":["../../../src/hooks/redux/use-app-selector.ts"],"names":[],"mappings":"AAGA,qBAAa,aAAa;IACxB,yGAAyG;IACzG,MAAM,CAAC,MAAM,CAAC,MAAM;;kCAGe,SAAS,YAAY,CAAC,KAAK,EAAE,MAAM,KAAK,SAAS;+BAIpD,SAAS,YAAY,CAAC,KAAK,EAAE,MAAM,KAAK,SAAS;;CAUlF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-app-selector.js","sourceRoot":"","sources":["../../../src/hooks/redux/use-app-selector.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,mBAAmB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAExD,MAAM,OAAO,aAAa;IACxB,yGAAyG;IACzG,MAAM,CAAC,MAAM;QACX,MAAM,cAAc,GAAG,WAAW,CAAC,SAAS,EAAU,CAAC;QAEvD,MAAM,uBAAuB,GAAG,CAAY,QAAsC,EAAE,EAAE;YACpF,OAAO,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAChD,CAAC,CAAC;QAEF,MAAM,oBAAoB,GAAG,CAAY,QAAsC,EAAE,EAAE;YACjF,OAAO,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC3C,CAAC,CAAC;QAEF,OAAO;YACL,cAAc;YACd,uBAAuB;YACvB,oBAAoB;SACrB,CAAC;IACJ,CAAC;CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-application-context.d.ts","sourceRoot":"","sources":["../../src/hooks/use-application-context.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,qBAAqB,oDAUjC,CAAC;AAEF,eAAO,MAAM,oBAAoB,qDAUhC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-application-context.js","sourceRoot":"","sources":["../../src/hooks/use-application-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,EAAE;IACxC,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAErD,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC;QACxB,MAAM,QAAQ,CAAC;YACb,OAAO,EAAE,0EAA0E;SACpF,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC,SAAS,CAAC;AAC3B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,EAAE;IACvC,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAErD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QACrB,MAAM,QAAQ,CAAC;YACb,OAAO,EAAE,yEAAyE;SACnF,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,CAAC;AACxB,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-artifact.d.ts","sourceRoot":"","sources":["../../src/hooks/use-artifact.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAY,KAAK,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAEjF,OAAO,EAGL,KAAK,kBAAkB,EAExB,MAAM,kBAAkB,CAAC;AAE1B,6DAA6D;AAC7D,MAAM,WAAW,oBAAoB,CAAC,SAAS;IAC7C,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IAC1B,GAAG,CAAC,EAAE,KAAK,CAAC;CACb;AAED,4CAA4C;AAC5C,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,GAAG,EAAE,kBAAkB,CAAC;IACxB,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB;AAgCD,eAAO,MAAM,UAAU,GAAI,SAAS,GAAG,OAAO,EAC5C,MAAM,oBAAoB,CAAC,SAAS,CAAC,GAAG,iBAAiB,KACxD,SAKC,CAAC;AAEL,eAAO,MAAM,WAAW,GAAI,SAAS,GAAG,OAAO,EAC7C,MAAM,oBAAoB,CAAC,SAAS,CAAC,GAAG,iBAAiB,KACxD,SAKC,CAAC;AAEL,eAAO,MAAM,YAAY,GAAI,SAAS,GAAG,OAAO,EAC9C,MAAM,oBAAoB,CAAC,SAAS,CAAC,GAAG,iBAAiB,KACxD,SAKC,CAAC;AAEL,eAAO,MAAM,gBAAgB,GAAI,SAAS,GAAG,OAAO,EAClD,MAAM,oBAAoB,CAAC,SAAS,CAAC,GAAG,iBAAiB,KACxD,SAKC,CAAC;AAEL,eAAO,MAAM,aAAa,GAAI,SAAS,GAAG,OAAO,EAC/C,MAAM,oBAAoB,CAAC,SAAS,CAAC,GAAG,iBAAiB,KACxD,SAKC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-artifact.js","sourceRoot":"","sources":["../../src/hooks/use-artifact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAkB,QAAQ,EAAe,MAAM,0BAA0B,CAAC;AAEjF,OAAO,EACL,aAAa,EACb,sBAAsB,GAGvB,MAAM,kBAAkB,CAAC;AAgB1B;;;GAGG;AACH,MAAM,sBAAsB,GAAG,CAAY,IAI1C,EAAa,EAAE;IACd,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IAE1C,mFAAmF;IACnF,MAAM,SAAS,GAAG,sBAAsB,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAE3E,2EAA2E;IAC3E,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,SAAS,CAAC,mBAAmB,EAAE,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACtF,4EAA4E;QAC5E,MAAM,KAAK,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAE1D,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC;YAChD,MAAM,QAAQ,CAAC;gBACb,OAAO,EAAE,IAAI,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,IAAI,iBAAiB,KAAK,iBAAiB,SAAS,4EAA4E;aACtK,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,aAAa,CAAY,OAAO,CAAC,CAAC;AAC3C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,IAAyD,EAC9C,EAAE,CACb,sBAAsB,CAAY;IAChC,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,iBAAiB,CAAC,OAAO;IACpC,IAAI,EAAE,YAAY;CACnB,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,IAAyD,EAC9C,EAAE,CACb,sBAAsB,CAAY;IAChC,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,iBAAiB,CAAC,QAAQ;IACrC,IAAI,EAAE,aAAa;CACpB,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,IAAyD,EAC9C,EAAE,CACb,sBAAsB,CAAY;IAChC,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,iBAAiB,CAAC,SAAS;IACtC,IAAI,EAAE,cAAc;CACrB,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,IAAyD,EAC9C,EAAE,CACb,sBAAsB,CAAY;IAChC,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,iBAAiB,CAAC,aAAa;IAC1C,IAAI,EAAE,kBAAkB;CACzB,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,IAAyD,EAC9C,EAAE,CACb,sBAAsB,CAAY;IAChC,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,iBAAiB,CAAC,UAAU;IACvC,IAAI,EAAE,eAAe;CACtB,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-autosave.d.ts","sourceRoot":"","sources":["../../src/hooks/use-autosave.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,mBAAmB,CAAC,KAAK,EAAE,OAAO;IACjD,IAAI,EAAE,KAAK,CAAC;IACZ,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;IAC3D,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAID,eAAO,MAAM,WAAW,GAAI,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,SA4CpF,CAAC"}