@venizia/ardor-react 0.4.8-1 → 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.
package/README.md CHANGED
@@ -15,15 +15,14 @@ which re-exports this package.
15
15
  | Area | Exports |
16
16
  |---|---|
17
17
  | Context | `ApplicationContext`, `useApplicationContext`, `useApplicationLogger` |
18
- | Injection | `useInjectable` - resolve a binding by key or by class |
18
+ | Injection | `useInjectable` - resolve a binding by key or by class; the artifact hooks `useApplicationContainer`, `useService`, `useProvider`, `useComponent`, `useConfiguration`, `useRepository` |
19
19
  | Redux | `DispatchHooks.create`, `SelectorHooks.create` - factories that bind your `RootState` and `AppDispatch` (the old `createAppDispatch` and `createAppSelectors` are removed; `bun run ardor codemod namespace` moves your code) |
20
- | UI hooks | `useDebounce`, `useAutosave`, `useConfirm`, `useBeforeUnload`, `useCopyToClipboard`, `useWindowDimensions` |
20
+ | UI hooks | `useDebounce`, `useAutosave`, `useConfirm`, `useBeforeUnload`, `useCopyToClipboard`, `useWindowDimensions`, `useElementSize`, `useResizeEffect`, `useIntersection`, `useStoredState`, `useMediaQuery`, `useDocumentVisibility`, `useOnlineStatus` |
21
21
  | Types | `TUseInjectableKeys` |
22
22
 
23
23
  ## Typing your own binding keys
24
24
 
25
- `IUseInjectableKeysOverrides` is declared here, so augment this package - an interface merges only
26
- into the module that declares it, never through a re-export:
25
+ `IUseInjectableKeysOverrides` is declared here, so augment this package when it is the one you depend on:
27
26
 
28
27
  ```typescript
29
28
  declare module '@venizia/ardor-react' {
@@ -33,6 +32,8 @@ declare module '@venizia/ardor-react' {
33
32
  }
34
33
  ```
35
34
 
35
+ With `@venizia/ardor`, write `declare module '@venizia/ardor'` instead.
36
+
36
37
  ## License
37
38
 
38
39
  MIT - see [LICENSE.md](./LICENSE.md).
@@ -1,9 +1,13 @@
1
1
  import React from 'react';
2
2
  import type { Container } from '@venizia/ignis-inversion';
3
3
  import type { ILogger } from '@venizia/ignis-helpers/core';
4
+ /** What `ApplicationContext` carries; both are `null` outside a provider. */
4
5
  export interface IApplicationContextValue {
6
+ /** The application's container. */
5
7
  container: Container | null;
8
+ /** The application's logger. */
6
9
  logger: ILogger | null;
7
10
  }
11
+ /** Read through `useApplicationContext` and `useApplicationLogger`; the default value holds `null`s. */
8
12
  export declare const ApplicationContext: React.Context<IApplicationContextValue>;
9
13
  //# sourceMappingURL=application.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ /** Read through `useApplicationContext` and `useApplicationLogger`; the default value holds `null`s. */
2
3
  export const ApplicationContext = React.createContext({
3
4
  container: null,
4
5
  logger: null,
@@ -1,6 +1,10 @@
1
1
  import type { Dispatch, UnknownAction } from '@reduxjs/toolkit';
2
+ /** Builds the typed dispatch hooks of a store. */
2
3
  export declare class DispatchHooks {
3
- /** The typed dispatch hooks of a store: bind your `AppDispatch` once, and export what comes back. */
4
+ /**
5
+ * The typed dispatch hooks of a store: bind your `AppDispatch` once, and export what comes back.
6
+ * `useMultipleAppDispatch` returns a function that dispatches each action in order and skips falsy ones.
7
+ */
4
8
  static create<TDispatch extends Dispatch<UnknownAction> = Dispatch<UnknownAction>>(): {
5
9
  useAppDispatch: import("react-redux").UseDispatch<TDispatch>;
6
10
  useMultipleAppDispatch: () => (...actions: Parameters<TDispatch>[0][]) => void;
@@ -1,7 +1,11 @@
1
1
  import React from 'react';
2
2
  import { useDispatch } from 'react-redux';
3
+ /** Builds the typed dispatch hooks of a store. */
3
4
  export class DispatchHooks {
4
- /** The typed dispatch hooks of a store: bind your `AppDispatch` once, and export what comes back. */
5
+ /**
6
+ * The typed dispatch hooks of a store: bind your `AppDispatch` once, and export what comes back.
7
+ * `useMultipleAppDispatch` returns a function that dispatches each action in order and skips falsy ones.
8
+ */
5
9
  static create() {
6
10
  const useAppDispatch = useDispatch.withTypes();
7
11
  const useMultipleAppDispatch = () => {
@@ -2,7 +2,7 @@
2
2
  export type TResizeBox = Exclude<ResizeObserverBoxOptions, 'device-pixel-content-box'>;
3
3
  export interface IResizeObservationOptions {
4
4
  box: TResizeBox;
5
- /** Also observe the direct children of each target, and again whenever they change. */
5
+ /** Also observe the direct children of each target, and again whenever they change. @default false */
6
6
  children?: boolean;
7
7
  /** Called at most once per animation frame, with the latest entry of each element that resized. */
8
8
  onFrame: (entries: ResizeObserverEntry[]) => void;
@@ -1,3 +1,5 @@
1
+ /** The application's `Container`. Throws when there is no `ApplicationContext` provider above. */
1
2
  export declare const useApplicationContext: () => import("@venizia/ignis-inversion").Container;
3
+ /** The application's logger. Throws when there is no `ApplicationContext` provider above. */
2
4
  export declare const useApplicationLogger: () => import("@venizia/ignis-helpers/core").ILogger;
3
5
  //# sourceMappingURL=use-application-context.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { getError } from '@venizia/ignis-inversion';
3
3
  import { ApplicationContext } from '../contexts/application.js';
4
+ /** The application's `Container`. Throws when there is no `ApplicationContext` provider above. */
4
5
  export const useApplicationContext = () => {
5
6
  const context = React.useContext(ApplicationContext);
6
7
  if (!context?.container) {
@@ -10,6 +11,7 @@ export const useApplicationContext = () => {
10
11
  }
11
12
  return context.container;
12
13
  };
14
+ /** The application's logger. Throws when there is no `ApplicationContext` provider above. */
13
15
  export const useApplicationLogger = () => {
14
16
  const context = React.useContext(ApplicationContext);
15
17
  if (!context?.logger) {
@@ -2,19 +2,45 @@ import { type Container, type TClass } from '@venizia/ignis-inversion';
2
2
  import { type TUseInjectableKeys } from './use-injectable.js';
3
3
  /** By class: the return type is inferred from the target. */
4
4
  export interface IUseArtifactByTarget<TInstance> {
5
+ /** The container to resolve from. @default The application's container (`ApplicationContext`) */
5
6
  container?: Container;
7
+ /** The class to resolve; its recorded binding key is used. */
6
8
  target: TClass<TInstance>;
7
9
  key?: never;
8
10
  }
9
11
  /** By key: the caller supplies the type. */
10
12
  export interface IUseArtifactByKey {
13
+ /** The container to resolve from. @default The application's container (`ApplicationContext`) */
11
14
  container?: Container;
15
+ /** The binding key to resolve. */
12
16
  key: TUseInjectableKeys;
13
17
  target?: never;
14
18
  }
19
+ /** Resolves the instance bound under the `service` namespace by `target` or `key`. A `target` bound under another namespace throws. */
15
20
  export declare const useService: <TInstance = unknown>(opts: IUseArtifactByTarget<TInstance> | IUseArtifactByKey) => TInstance;
21
+ /** Resolves the instance bound under the `provider` namespace by `target` or `key`. A `target` bound under another namespace throws. */
16
22
  export declare const useProvider: <TInstance = unknown>(opts: IUseArtifactByTarget<TInstance> | IUseArtifactByKey) => TInstance;
23
+ /** Resolves the instance bound under the `component` namespace by `target` or `key`. A `target` bound under another namespace throws. */
17
24
  export declare const useComponent: <TInstance = unknown>(opts: IUseArtifactByTarget<TInstance> | IUseArtifactByKey) => TInstance;
25
+ /** Resolves the instance bound under the `configuration` namespace by `target` or `key`. A `target` bound under another namespace throws. */
18
26
  export declare const useConfiguration: <TInstance = unknown>(opts: IUseArtifactByTarget<TInstance> | IUseArtifactByKey) => TInstance;
27
+ /**
28
+ * Resolves the instance bound under the `repository` namespace by `target` or `key`. A `target` bound under another namespace throws.
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * import { repository, useRepository } from '@venizia/ardor';
33
+ *
34
+ * @repository()
35
+ * class ProductRepository {
36
+ * findStock(productId: string) { return productId ? 42 : 0; }
37
+ * }
38
+ *
39
+ * export function StockDisplay() {
40
+ * const productRepository = useRepository({ target: ProductRepository });
41
+ * return <span>Stock: {productRepository.findStock('p1')}</span>;
42
+ * }
43
+ * ```
44
+ */
19
45
  export declare const useRepository: <TInstance = unknown>(opts: IUseArtifactByTarget<TInstance> | IUseArtifactByKey) => TInstance;
20
46
  //# sourceMappingURL=use-artifact.d.ts.map
@@ -22,26 +22,48 @@ const useArtifactInNamespace = (opts) => {
22
22
  }
23
23
  return useInjectable(options);
24
24
  };
25
+ /** Resolves the instance bound under the `service` namespace by `target` or `key`. A `target` bound under another namespace throws. */
25
26
  export const useService = (opts) => useArtifactInNamespace({
26
27
  options: opts,
27
28
  namespace: BindingNamespaces.SERVICE,
28
29
  hook: 'useService',
29
30
  });
31
+ /** Resolves the instance bound under the `provider` namespace by `target` or `key`. A `target` bound under another namespace throws. */
30
32
  export const useProvider = (opts) => useArtifactInNamespace({
31
33
  options: opts,
32
34
  namespace: BindingNamespaces.PROVIDER,
33
35
  hook: 'useProvider',
34
36
  });
37
+ /** Resolves the instance bound under the `component` namespace by `target` or `key`. A `target` bound under another namespace throws. */
35
38
  export const useComponent = (opts) => useArtifactInNamespace({
36
39
  options: opts,
37
40
  namespace: BindingNamespaces.COMPONENT,
38
41
  hook: 'useComponent',
39
42
  });
43
+ /** Resolves the instance bound under the `configuration` namespace by `target` or `key`. A `target` bound under another namespace throws. */
40
44
  export const useConfiguration = (opts) => useArtifactInNamespace({
41
45
  options: opts,
42
46
  namespace: BindingNamespaces.CONFIGURATION,
43
47
  hook: 'useConfiguration',
44
48
  });
49
+ /**
50
+ * Resolves the instance bound under the `repository` namespace by `target` or `key`. A `target` bound under another namespace throws.
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * import { repository, useRepository } from '@venizia/ardor';
55
+ *
56
+ * @repository()
57
+ * class ProductRepository {
58
+ * findStock(productId: string) { return productId ? 42 : 0; }
59
+ * }
60
+ *
61
+ * export function StockDisplay() {
62
+ * const productRepository = useRepository({ target: ProductRepository });
63
+ * return <span>Stock: {productRepository.findStock('p1')}</span>;
64
+ * }
65
+ * ```
66
+ */
45
67
  export const useRepository = (opts) => useArtifactInNamespace({
46
68
  options: opts,
47
69
  namespace: BindingNamespaces.REPOSITORY,
@@ -1,11 +1,18 @@
1
1
  export interface IUseAutosaveOptions<TData, TReturn> {
2
+ /** The value to save; a change starts the `interval` wait. */
2
3
  data: TData;
4
+ /** Called with the latest `data` once it has been still for `interval`. A rejection is logged, not thrown. */
3
5
  onSave: (data: TData) => Promise<TReturn> | TReturn | void;
4
6
  /** @default 2000 */
5
7
  interval?: number;
8
+ /** Save the latest `data` on unmount. */
6
9
  enableSaveOnUnmount?: boolean;
7
10
  /** @default true */
8
11
  enabled?: boolean;
9
12
  }
13
+ /**
14
+ * Calls `onSave` with `data` after it has stopped changing for `interval` milliseconds. The first
15
+ * render does not save. The latest `onSave` is always the one called.
16
+ */
10
17
  export declare const useAutosave: <TData, TReturn>(opts: IUseAutosaveOptions<TData, TReturn>) => void;
11
18
  //# sourceMappingURL=use-autosave.d.ts.map
@@ -2,6 +2,10 @@ import React from 'react';
2
2
  import { LoggerFactory } from '@venizia/ignis-helpers/core';
3
3
  import { useDebounce } from './use-debounce.js';
4
4
  const logger = LoggerFactory.getLogger(['useAutosave']);
5
+ /**
6
+ * Calls `onSave` with `data` after it has stopped changing for `interval` milliseconds. The first
7
+ * render does not save. The latest `onSave` is always the one called.
8
+ */
5
9
  export const useAutosave = (opts) => {
6
10
  const { data, onSave, interval = 2000, enableSaveOnUnmount, enabled = true } = opts;
7
11
  const valueOnCleanup = React.useRef(data);
@@ -1,7 +1,9 @@
1
1
  export interface IUseBeforeUnloadOptions {
2
2
  /** @default true */
3
3
  enabled?: boolean;
4
+ /** Set as the `returnValue` of the event; browsers may show their own text instead. */
4
5
  message?: string;
5
6
  }
7
+ /** While `enabled`, asks the browser to confirm before the page is left or reloaded. */
6
8
  export declare const useBeforeUnload: (opts: IUseBeforeUnloadOptions) => void;
7
9
  //# sourceMappingURL=use-before-unload.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ /** While `enabled`, asks the browser to confirm before the page is left or reloaded. */
2
3
  export const useBeforeUnload = (opts) => {
3
4
  const { enabled = true, message } = opts;
4
5
  const handleBeforeUnload = React.useCallback((event) => {
@@ -1,11 +1,17 @@
1
1
  export interface IUseConfirmReturn {
2
+ /** The pending prompt text; `undefined` when none is open. */
2
3
  message?: string;
4
+ /** Opens a prompt. Resolves `true` on `handleConfirm`, `false` on `handleAbort` or when another `confirm` replaces it. */
3
5
  confirm: (opts: {
4
6
  message: string;
5
7
  }) => Promise<boolean>;
8
+ /** Closes the prompt without settling it. */
6
9
  handleClose: () => void;
10
+ /** Resolves the pending `confirm` with `true` and closes. */
7
11
  handleConfirm: () => void;
12
+ /** Resolves the pending `confirm` with `false` and closes. */
8
13
  handleAbort: () => void;
9
14
  }
15
+ /** State for a confirmation prompt: `confirm` returns a promise that the prompt's buttons settle. */
10
16
  export declare const useConfirm: () => IUseConfirmReturn;
11
17
  //# sourceMappingURL=use-confirm.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ /** State for a confirmation prompt: `confirm` returns a promise that the prompt's buttons settle. */
2
3
  export const useConfirm = () => {
3
4
  const settle = React.useRef(undefined);
4
5
  const [message, setMessage] = React.useState();
@@ -10,5 +10,18 @@ export interface IUseCopyToClipboardReturn {
10
10
  /** The result of the last copy, until `resetMs` has passed. */
11
11
  isCopied: boolean;
12
12
  }
13
+ /**
14
+ * Copies text with `BrowserClipboard`; `isCopied` is the result of the last copy.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * import { useCopyToClipboard } from '@venizia/ardor';
19
+ *
20
+ * export function CopyButton() {
21
+ * const { copy, isCopied } = useCopyToClipboard({ resetMs: 2000 });
22
+ * return <button onClick={() => copy({ value: 'hello' })}>{isCopied ? 'Copied' : 'Copy'}</button>;
23
+ * }
24
+ * ```
25
+ */
13
26
  export declare const useCopyToClipboard: (opts?: IUseCopyToClipboardOptions) => IUseCopyToClipboardReturn;
14
27
  //# sourceMappingURL=use-copy-to-clipboard.d.ts.map
@@ -10,6 +10,19 @@ const copyToClipboard = async (opts) => {
10
10
  }
11
11
  return isCopied;
12
12
  };
13
+ /**
14
+ * Copies text with `BrowserClipboard`; `isCopied` is the result of the last copy.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * import { useCopyToClipboard } from '@venizia/ardor';
19
+ *
20
+ * export function CopyButton() {
21
+ * const { copy, isCopied } = useCopyToClipboard({ resetMs: 2000 });
22
+ * return <button onClick={() => copy({ value: 'hello' })}>{isCopied ? 'Copied' : 'Copy'}</button>;
23
+ * }
24
+ * ```
25
+ */
13
26
  export const useCopyToClipboard = (opts = {}) => {
14
27
  const { resetMs = 0 } = opts;
15
28
  const [isCopied, setCopied] = React.useState(false);
@@ -1,11 +1,15 @@
1
1
  export interface IUseDebounceOptions<TValue> {
2
+ /** The value to follow; the output changes once it has been still for `delay`. */
2
3
  value: TValue;
4
+ /** Milliseconds to wait after the last change. @default 500 */
3
5
  delay?: number;
4
6
  /** @default true */
5
7
  enabled?: boolean;
6
8
  }
7
9
  export interface IUseDebounceReturn<TValue> {
10
+ /** The last `value` that stayed unchanged for `delay`; the first `value` until then. */
8
11
  debouncedValue: TValue;
9
12
  }
13
+ /** Follows `value` after it has stopped changing for `delay`. Outside a browser, or when not `enabled`, it never updates. */
10
14
  export declare const useDebounce: <TValue>(opts: IUseDebounceOptions<TValue>) => IUseDebounceReturn<TValue>;
11
15
  //# sourceMappingURL=use-debounce.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { App, BrowserUtility } from '@venizia/ardor-kernel';
3
+ /** Follows `value` after it has stopped changing for `delay`. Outside a browser, or when not `enabled`, it never updates. */
3
4
  export const useDebounce = (opts) => {
4
5
  const { value, delay, enabled = true } = opts;
5
6
  const [debouncedValue, setDebouncedValue] = React.useState(value);
@@ -15,6 +15,7 @@ export interface IUseElementSizeReturn {
15
15
  measured: boolean;
16
16
  }
17
17
  export interface IUseElementSizeRefReturn extends IUseElementSizeReturn {
18
+ /** Give it to the element to measure. */
18
19
  ref: React.RefCallback<Element>;
19
20
  }
20
21
  /**
@@ -24,6 +25,16 @@ export interface IUseElementSizeRefReturn extends IUseElementSizeReturn {
24
25
  * is read when the effect runs, so it follows a new `ref`, `box` or `round`, not a `ref.current`
25
26
  * that is replaced later; pass a stable `ref`. A new element, `box` or `round` starts again from
26
27
  * 0 by 0 with `measured` false.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * import { useElementSize } from '@venizia/ardor';
32
+ *
33
+ * export function Panel() {
34
+ * const { ref, width, height, measured } = useElementSize();
35
+ * return <div ref={ref}>{measured ? `${width} x ${height}` : '...'}</div>;
36
+ * }
37
+ * ```
27
38
  */
28
39
  export declare function useElementSize(opts: IUseElementSizeOptions & {
29
40
  ref: React.RefObject<Element | null>;
@@ -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
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,6 +4,16 @@ 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
18
  export const useApplicationContainer = (opts) => {
9
19
  const applicationContext = React.useContext(ApplicationContext);
@@ -15,6 +25,10 @@ export const useApplicationContainer = (opts) => {
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
33
  const container = useApplicationContainer({ container: opts?.container });
20
34
  const { key, target } = opts;
@@ -4,13 +4,25 @@ export interface IUseIntersectionOptions {
4
4
  target: TElementTarget;
5
5
  /** @default '0px' */
6
6
  rootMargin?: string;
7
- /** The ratio of the element in view at which an entry is delivered. @default 0 */
7
+ /** The ratio of the element in view at which an entry is delivered; a single number, not an array. @default 0 */
8
8
  threshold?: number;
9
9
  }
10
10
  /**
11
11
  * The latest `IntersectionObserverEntry` of the target, or `undefined` before the first one and
12
12
  * where there is no `IntersectionObserver`. The state changes only when `isIntersecting` or the
13
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
+ * ```
14
26
  */
15
27
  export declare const useIntersection: (opts: IUseIntersectionOptions) => IntersectionObserverEntry | undefined;
16
28
  //# sourceMappingURL=use-intersection.d.ts.map
@@ -4,6 +4,18 @@ import { ElementTargets } from './element-target.js';
4
4
  * The latest `IntersectionObserverEntry` of the target, or `undefined` before the first one and
5
5
  * where there is no `IntersectionObserver`. The state changes only when `isIntersecting` or the
6
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
+ * ```
7
19
  */
8
20
  export const useIntersection = (opts) => {
9
21
  const { target, rootMargin, threshold } = opts;
@@ -14,6 +14,18 @@ export interface IUseResizeEffectOptions {
14
14
  * Calls `onResize` at most once per animation frame when any of the targets resizes. A new
15
15
  * `onResize` is used without observing again, and the targets are read after every render, so an
16
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
+ * ```
17
29
  */
18
30
  export declare const useResizeEffect: (opts: IUseResizeEffectOptions) => void;
19
31
  //# sourceMappingURL=use-resize-effect.d.ts.map
@@ -5,6 +5,18 @@ import { ResizeObservation } from './resize-observation.js';
5
5
  * Calls `onResize` at most once per animation frame when any of the targets resizes. A new
6
6
  * `onResize` is used without observing again, and the targets are read after every render, so an
7
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
+ * ```
8
20
  */
9
21
  export const useResizeEffect = (opts) => {
10
22
  const { targets, onResize, box = 'content-box', children = false } = opts;
@@ -1,17 +1,22 @@
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>;
9
- /** Write storage once, this many milliseconds after the last `setValue`; `value` changes at once. @default 0 */
11
+ /** Write storage once, this many milliseconds after the last `setValue`; `value` changes at once. Above 0, also pass a stable `codec`. @default 0 */
10
12
  writeDelayMs?: number;
11
13
  }
12
14
  export interface IUseStoredStateReturn<TValue> {
15
+ /** The stored value, or `defaultValue` when the key holds nothing. */
13
16
  value: TValue;
17
+ /** Stores a value, or the result of an updater called with the current one. */
14
18
  setValue: (next: TValue | ((current: TValue) => TValue)) => void;
19
+ /** Removes the key, so `value` is `defaultValue` again. */
15
20
  remove: () => void;
16
21
  }
17
22
  export interface IUseStoredState {
@@ -28,6 +33,16 @@ export interface IUseStoredState {
28
33
  * A write or a removal that the storage refuses is kept in this hook until the storage next changes.
29
34
  * With `writeDelayMs` the value waits in the same place for the delay: a change of the storage from
30
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
+ * ```
31
46
  */
32
47
  export declare const useStoredState: IUseStoredState;
33
48
  //# sourceMappingURL=use-stored-state.d.ts.map
@@ -8,6 +8,16 @@ const isUpdater = (next) => typeof next === 'function';
8
8
  * A write or a removal that the storage refuses is kept in this hook until the storage next changes.
9
9
  * With `writeDelayMs` the value waits in the same place for the delay: a change of the storage from
10
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
+ * ```
11
21
  */
12
22
  export const useStoredState = (opts) => {
13
23
  const { key, defaultValue, storage, codec, writeDelayMs = 0 } = opts;
@@ -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-1",
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",
@@ -59,7 +59,7 @@
59
59
  "prepublishOnly": "make rebuild"
60
60
  },
61
61
  "dependencies": {
62
- "@venizia/ardor-kernel": "0.4.8-1"
62
+ "@venizia/ardor-kernel": "0.4.8-2"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@happy-dom/global-registrator": "^21.0.4",
@@ -69,11 +69,11 @@
69
69
  "@types/bun": "^1.4.3",
70
70
  "@types/react": "^19",
71
71
  "@types/react-dom": "^19",
72
- "@venizia/dev-configs": "^0.4.8-0",
73
- "@venizia/ignis-filter": "^0.4.7-0",
74
- "@venizia/ignis-helpers": "^0.4.8-0",
75
- "@venizia/ignis-inversion": "^0.4.7-0",
76
- "@venizia/ignis-kernel": "^0.4.8-0",
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",
@@ -87,10 +87,10 @@
87
87
  },
88
88
  "peerDependencies": {
89
89
  "@reduxjs/toolkit": ">=2.13.0",
90
- "@venizia/ignis-filter": "^0.4.7-0",
91
- "@venizia/ignis-helpers": "^0.4.8-0",
92
- "@venizia/ignis-inversion": "^0.4.7-0",
93
- "@venizia/ignis-kernel": "^0.4.8-0",
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",
94
94
  "react": ">=19.3.1",
95
95
  "react-redux": ">=9.3.0",
96
96
  "reflect-metadata": ">=0.2.2"