@design-edito/tools 0.5.2 → 0.5.3

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 (129) hide show
  1. package/agnostic/html/deep-select/index.js +5 -3
  2. package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +8 -8
  3. package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +8 -8
  4. package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +2 -2
  5. package/agnostic/html/hyper-json/smart-tags/isolated/index.js +2 -2
  6. package/agnostic/html/index.d.ts +1 -1
  7. package/agnostic/html/index.js +1 -1
  8. package/agnostic/misc/index.d.ts +3 -3
  9. package/agnostic/misc/index.js +3 -3
  10. package/agnostic/numbers/index.d.ts +1 -1
  11. package/agnostic/numbers/index.js +1 -1
  12. package/agnostic/optim/index.d.ts +1 -1
  13. package/agnostic/optim/index.js +1 -1
  14. package/agnostic/strings/index.d.ts +1 -1
  15. package/agnostic/strings/index.js +1 -1
  16. package/agnostic/time/dates/format-date/index.d.ts +4 -2
  17. package/agnostic/time/dates/format-date/index.js +43 -94
  18. package/agnostic/time/dates/format-date/index.test.js +154 -0
  19. package/agnostic/time/dates/format-date/parts.d.ts +28 -0
  20. package/agnostic/time/dates/format-date/parts.js +136 -0
  21. package/agnostic/time/dates/format-date/types.d.ts +84 -0
  22. package/agnostic/time/dates/format-date/types.js +1 -0
  23. package/agnostic/time/duration/format-duration/index.d.ts +42 -0
  24. package/agnostic/time/duration/format-duration/index.js +94 -0
  25. package/agnostic/time/duration/format-duration/index.test.d.ts +1 -0
  26. package/agnostic/time/duration/format-duration/index.test.js +148 -0
  27. package/agnostic/time/duration/format-duration/parts.d.ts +32 -0
  28. package/agnostic/time/duration/format-duration/parts.js +107 -0
  29. package/agnostic/time/duration/format-duration/types.d.ts +93 -0
  30. package/agnostic/time/duration/format-duration/types.js +1 -0
  31. package/agnostic/time/duration/index.d.ts +6 -1
  32. package/agnostic/time/duration/index.js +2 -2
  33. package/agnostic/time/duration/index.test.d.ts +1 -0
  34. package/agnostic/time/duration/index.test.js +168 -0
  35. package/agnostic/time/index.d.ts +1 -1
  36. package/agnostic/time/index.js +1 -1
  37. package/components/BeforeAfter/index.controlled.d.ts +25 -25
  38. package/components/BeforeAfter/index.controlled.js +47 -53
  39. package/components/BeforeAfter/index.d.ts +20 -23
  40. package/components/BeforeAfter/index.js +48 -40
  41. package/components/Button/index.js +1 -1
  42. package/components/Clippable/index.d.ts +17 -26
  43. package/components/Clippable/index.js +21 -23
  44. package/components/Disclaimer/index.d.ts +29 -22
  45. package/components/Disclaimer/index.js +29 -24
  46. package/components/Drawer/index.d.ts +45 -25
  47. package/components/Drawer/index.js +61 -52
  48. package/components/EventListener/index.d.ts +4 -5
  49. package/components/Gallery/index.d.ts +14 -16
  50. package/components/Gallery/index.js +25 -30
  51. package/components/Iframe/index.js +2 -2
  52. package/components/Image/index.d.ts +5 -12
  53. package/components/Image/index.js +11 -32
  54. package/components/Input/index.d.ts +23 -19
  55. package/components/Input/index.js +30 -20
  56. package/components/JsonEditor/index.d.ts +177 -9
  57. package/components/JsonEditor/index.js +133 -77
  58. package/components/JsonEditor/types.d.ts +11 -0
  59. package/components/JsonEditor/types.js +1 -0
  60. package/components/JsonEditor/utils.d.ts +8 -0
  61. package/components/JsonEditor/utils.js +23 -0
  62. package/components/ListLoader/index.controlled.d.ts +5 -5
  63. package/components/ListLoader/index.controlled.js +4 -4
  64. package/components/ListLoader/index.d.ts +11 -11
  65. package/components/ListLoader/index.js +12 -12
  66. package/components/Overlayer/index.js +4 -4
  67. package/components/Paginator/index.d.ts +7 -12
  68. package/components/Paginator/index.js +7 -11
  69. package/components/ResizeObserver/index.d.ts +14 -3
  70. package/components/ResizeObserver/index.js +49 -24
  71. package/components/Scrllgngn/index.d.ts +17 -13
  72. package/components/Scrllgngn/index.js +25 -84
  73. package/components/Scrllgngn/utils.d.ts +52 -0
  74. package/components/Scrllgngn/utils.js +102 -0
  75. package/components/ScrollListener/index.d.ts +43 -28
  76. package/components/ScrollListener/index.js +59 -79
  77. package/components/ScrollListener/utils.d.ts +37 -16
  78. package/components/ScrollListener/utils.js +141 -28
  79. package/components/Select/index.d.ts +24 -20
  80. package/components/Select/index.js +30 -20
  81. package/components/Sequencer/index.controlled.d.ts +16 -26
  82. package/components/Sequencer/index.controlled.js +15 -12
  83. package/components/Sequencer/index.d.ts +25 -30
  84. package/components/Sequencer/index.js +20 -34
  85. package/components/Subtitles/index.d.ts +29 -5
  86. package/components/Subtitles/index.js +90 -12
  87. package/components/Textarea/index.d.ts +26 -20
  88. package/components/Textarea/index.js +40 -21
  89. package/components/Theatre/index.d.ts +35 -39
  90. package/components/Theatre/index.js +46 -58
  91. package/components/UIModule/index.d.ts +25 -29
  92. package/components/UIModule/index.js +81 -110
  93. package/components/Video/index.controlled.d.ts +51 -51
  94. package/components/Video/index.controlled.js +75 -72
  95. package/components/Video/index.d.ts +12 -14
  96. package/components/Video/index.js +41 -86
  97. package/components/Video/utils.d.ts +35 -3
  98. package/components/Video/utils.js +37 -14
  99. package/components/index.d.ts +1 -1
  100. package/components/index.js +1 -1
  101. package/components/utils/index.d.ts +22 -0
  102. package/components/utils/index.js +31 -0
  103. package/index.d.ts +1 -1
  104. package/index.js +1 -1
  105. package/node/@aws-s3/storage/file/index.d.ts +1 -1
  106. package/node/@aws-s3/storage/file/index.js +1 -1
  107. package/node/@google-cloud/storage/index.d.ts +1 -1
  108. package/node/@google-cloud/storage/index.js +1 -1
  109. package/node/images/index.d.ts +1 -1
  110. package/node/images/index.js +1 -1
  111. package/node/images/transform/operations/index.d.ts +1 -1
  112. package/node/images/transform/operations/index.js +1 -1
  113. package/node/index.d.ts +1 -1
  114. package/node/index.js +1 -1
  115. package/package.json +20 -5
  116. package/TODO.md +0 -269
  117. package/components/BeforeAfter/utils.d.ts +0 -4
  118. package/components/BeforeAfter/utils.js +0 -9
  119. package/components/Input/index.controlled.d.ts +0 -42
  120. package/components/Input/index.controlled.js +0 -54
  121. package/components/Select/index.controlled.d.ts +0 -43
  122. package/components/Select/index.controlled.js +0 -54
  123. package/components/Subtitles/types.d.ts +0 -24
  124. package/components/Subtitles/utils.d.ts +0 -32
  125. package/components/Subtitles/utils.js +0 -107
  126. package/components/Textarea/index.controlled.d.ts +0 -50
  127. package/components/Textarea/index.controlled.js +0 -75
  128. /package/{components/Subtitles/types.js → agnostic/time/dates/format-date/index.test.d.ts} +0 -0
  129. /package/components/ResizeObserver/{style.module.css → styles.module.css} +0 -0
@@ -1,54 +1,62 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
- import { BeforeAfterControlled } from './index.controlled.js';
3
+ import { useChangeDispatch } from '../utils/index.js';
4
+ import { ControlledBeforeAfter } from './index.controlled.js';
4
5
  /**
5
- * Before/after comparison component with optional controlled and uncontrolled behavior.
6
- *
7
- * Wraps {@link BeforeAfterControlled} and manages internal divider position when
8
- * operating in uncontrolled mode. The active axis used to derive the ratio from
9
- * pointer position depends on `mode`: horizontal interactions use the x ratio,
10
- * vertical interactions use the y ratio.
6
+ * Pointer ratios are stretched by this much on each side before becoming the
7
+ * divider position, so the very edges stay reachable without having to land the
8
+ * pointer exactly on the component's border.
9
+ */
10
+ const edgeOvershoot = 0.01;
11
+ /** Turns a raw pointer ratio into a divider position, edges included. */
12
+ function toDividerRatio(pointerRatio) {
13
+ return pointerRatio * (1 + 2 * edgeOvershoot) - edgeOvershoot;
14
+ }
15
+ /**
16
+ * Before/after comparison component supporting controlled and uncontrolled usage.
11
17
  *
12
- * @remarks
13
- * - In controlled mode (`ratio` defined), divider position is fully driven by the prop
14
- * and internal state is never updated.
15
- * - In uncontrolled mode, internal state is initialized from `defaultRatio` and updated
16
- * on both drag and click interactions.
17
- * - `actionHandlers.dragged` and `actionHandlers.clicked` are always forwarded to the
18
- * underlying controlled component, regardless of mode.
18
+ * Turns the pointer ratios reported by {@link ControlledBeforeAfter} into a
19
+ * divider position, reading the axis that matches `mode`: the x ratio when
20
+ * horizontal, the y ratio when vertical.
19
21
  *
20
22
  * @param props - Component properties.
21
23
  * @see {@link Props}
22
- * @returns A {@link BeforeAfterControlled} instance with ratio state managed internally when uncontrolled.
24
+ * @see {@link ControlledBeforeAfter} for the rendered markup and CSS elements.
25
+ * @returns A {@link ControlledBeforeAfter} with the divider position managed
26
+ * internally when uncontrolled.
27
+ *
28
+ * @remarks
29
+ * - In controlled mode (`ratio` defined), the divider position is fully driven
30
+ * by the parent and internal state is never updated.
31
+ * - `onDragged` and `onClicked` fire in both modes — a controlled parent needs
32
+ * them to know where the pointer went.
33
+ * - `onRatioChanged` fires in both modes too, and never on mount.
23
34
  */
24
- export const BeforeAfter = ({ mode = 'horizontal', actionHandlers, stateHandlers, ratio, defaultRatio = 0.5, ...controlledProps }) => {
35
+ export const BeforeAfter = ({ mode = 'horizontal', ratio, defaultRatio = 0.5, onDragged, onClicked, onRatioChanged, ...controlledProps }) => {
36
+ // State
25
37
  const [internalRatio, setInternalRatio] = useState(defaultRatio);
26
38
  const isControlled = ratio !== undefined;
27
39
  const effectiveRatio = isControlled ? ratio : internalRatio;
28
- const handleDrag = (x, y) => {
29
- // eslint-disable-next-line no-param-reassign
30
- x = 1.02 * x - 0.01;
31
- // eslint-disable-next-line no-param-reassign
32
- y = 1.02 * y - 0.01;
33
- actionHandlers?.dragged?.(x, y);
34
- if (!isControlled) {
35
- setInternalRatio(mode === 'horizontal' ? x : y);
36
- stateHandlers?.ratioChanged?.(mode === 'horizontal' ? x : y);
37
- }
40
+ // State dispatch
41
+ useChangeDispatch(effectiveRatio, onRatioChanged);
42
+ // User action handlers
43
+ const moveDividerTo = (xRatio, yRatio) => {
44
+ if (isControlled)
45
+ return;
46
+ setInternalRatio(mode === 'horizontal' ? xRatio : yRatio);
47
+ };
48
+ const handleDrag = (xRatio, yRatio) => {
49
+ const x = toDividerRatio(xRatio);
50
+ const y = toDividerRatio(yRatio);
51
+ onDragged?.(x, y);
52
+ moveDividerTo(x, y);
38
53
  };
39
- const handleClick = (x, y) => {
40
- // eslint-disable-next-line no-param-reassign
41
- x = 1.02 * x - 0.01;
42
- // eslint-disable-next-line no-param-reassign
43
- y = 1.02 * y - 0.01;
44
- actionHandlers?.clicked?.(x, y);
45
- if (!isControlled) {
46
- setInternalRatio(mode === 'horizontal' ? x : y);
47
- stateHandlers?.ratioChanged?.(mode === 'horizontal' ? x : y);
48
- }
54
+ const handleClick = (xRatio, yRatio) => {
55
+ const x = toDividerRatio(xRatio);
56
+ const y = toDividerRatio(yRatio);
57
+ onClicked?.(x, y);
58
+ moveDividerTo(x, y);
49
59
  };
50
- return _jsx(BeforeAfterControlled, { ...controlledProps, mode: mode, ratio: effectiveRatio, actionHandlers: {
51
- dragged: handleDrag,
52
- clicked: handleClick
53
- } });
60
+ // Rendering
61
+ return _jsx(ControlledBeforeAfter, { ...controlledProps, mode: mode, ratio: effectiveRatio, onDragged: handleDrag, onClicked: handleClick });
54
62
  };
@@ -15,6 +15,6 @@ import cssModule from './styles.module.css';
15
15
  */
16
16
  export const Button = ({ className, ...rest }) => {
17
17
  const c = clss(publicClassName, { cssModule });
18
- const rootClss = mergeClassNames(c(null), className);
18
+ const rootClss = mergeClassNames(c(), className);
19
19
  return _jsx("button", { ...rest, className: rootClss });
20
20
  };
@@ -3,40 +3,27 @@ import type { WithClassName } from '../utils/types.js';
3
3
  /**
4
4
  * Props for the {@link Clippable} component.
5
5
  *
6
- * Extends {@link WithClassName} with clipboard-related configuration and callbacks.
7
- *
8
- * @property toClip - Content written to the clipboard. When omitted, the current
9
- * content container's `innerHTML` is used. A function may be provided to transform
10
- * the current content before it is written.
11
- * @property actionHandlers - Optional user action callbacks:
12
- * - `clicked` called when the copy button is clicked, before clipboard content is resolved.
13
- * @property stateHandlers - Optional callbacks invoked when derived state changes:
14
- * - `clipped` — called after content has been successfully written to the clipboard.
6
+ * @property toClip - Content written to the clipboard. When omitted, the
7
+ * content container's `innerHTML` is used. A function may be provided to
8
+ * transform that current content before it is written.
9
+ * @property onCopyClicked - Called when the copy button is clicked, before the
10
+ * clipboard content is resolved, with the container's raw HTML.
11
+ * @property onClipped - Called once content has been written to the clipboard.
12
+ * Not called when the write fails.
15
13
  * @property className - Additional class name(s) applied to the root element.
16
14
  * @property children - Content rendered inside the copyable container.
17
15
  */
18
16
  export type Props = PropsWithChildren<WithClassName<{
19
17
  toClip?: string | ((curr: string | undefined) => string | undefined);
20
- actionHandlers?: {
21
- clicked?: (e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>, rawContent: string | undefined) => void;
22
- };
23
- stateHandlers?: {
24
- clipped?: (content: string) => void;
25
- };
18
+ onCopyClicked?: (e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>, rawContent: string | undefined) => void;
19
+ onClipped?: (content: string) => void;
26
20
  }>>;
27
21
  /**
28
- * Clipboard-enabled container component.
29
- *
30
- * Renders arbitrary content alongside a copy control. When activated,
31
- * the component writes HTML content to the clipboard using the
32
- * `text/html` MIME type.
33
- *
34
- * Supports content overriding and transformation through the `toClip`
35
- * prop, as well as action and state callbacks.
22
+ * Clipboard-enabled container. Renders arbitrary content alongside a copy
23
+ * control that writes it to the clipboard, as both `text/html` and `text/plain`.
36
24
  *
37
25
  * ### CSS modifiers
38
- * The following modifiers are applied automatically:
39
- * - `clipped` — `true` during the 3 seconds following a successful clipboard write.
26
+ * - `clipped` — on during the 3 seconds following a successful write.
40
27
  *
41
28
  * ### CSS elements
42
29
  * - `copy`
@@ -44,6 +31,10 @@ export type Props = PropsWithChildren<WithClassName<{
44
31
  *
45
32
  * @param props - Component properties.
46
33
  * @see {@link Props}
47
- * @returns A copy-enabled content container with clipboard state modifiers applied.
34
+ * @returns A copy-enabled content container.
35
+ *
36
+ * @remarks
37
+ * A failed clipboard write is logged and leaves the component untouched:
38
+ * neither `onClipped` nor the `clipped` modifier fires.
48
39
  */
49
40
  export declare const Clippable: FunctionComponent<Props>;
@@ -4,19 +4,14 @@ import { clss } from '../../agnostic/css/clss/index.js';
4
4
  import { mergeClassNames } from '../utils/index.js';
5
5
  import { clippable as publicClassName } from '../public-classnames.js';
6
6
  import cssModule from './styles.module.css';
7
+ /** How long the `clipped` modifier stays on after a successful write. */
8
+ const clippedModifierDurationMs = 3000;
7
9
  /**
8
- * Clipboard-enabled container component.
9
- *
10
- * Renders arbitrary content alongside a copy control. When activated,
11
- * the component writes HTML content to the clipboard using the
12
- * `text/html` MIME type.
13
- *
14
- * Supports content overriding and transformation through the `toClip`
15
- * prop, as well as action and state callbacks.
10
+ * Clipboard-enabled container. Renders arbitrary content alongside a copy
11
+ * control that writes it to the clipboard, as both `text/html` and `text/plain`.
16
12
  *
17
13
  * ### CSS modifiers
18
- * The following modifiers are applied automatically:
19
- * - `clipped` — `true` during the 3 seconds following a successful clipboard write.
14
+ * - `clipped` — on during the 3 seconds following a successful write.
20
15
  *
21
16
  * ### CSS elements
22
17
  * - `copy`
@@ -24,22 +19,26 @@ import cssModule from './styles.module.css';
24
19
  *
25
20
  * @param props - Component properties.
26
21
  * @see {@link Props}
27
- * @returns A copy-enabled content container with clipboard state modifiers applied.
22
+ * @returns A copy-enabled content container.
23
+ *
24
+ * @remarks
25
+ * A failed clipboard write is logged and leaves the component untouched:
26
+ * neither `onClipped` nor the `clipped` modifier fires.
28
27
  */
29
- export const Clippable = ({ className, children, toClip, actionHandlers, stateHandlers }) => {
28
+ export const Clippable = ({ className, children, toClip, onCopyClicked, onClipped }) => {
30
29
  // State & refs
31
- const [beenRecentlyClipped, setBeenRecentlyClipped] = useState(false);
30
+ const [hasBeenRecentlyClipped, setHasBeenRecentlyClipped] = useState(false);
32
31
  const contentRef = useRef(null);
33
32
  const clippedTimeoutRef = useRef(null);
34
33
  // User action handlers
35
34
  const handleCopyClick = async (e) => {
36
- const rawhHtml = contentRef.current?.innerHTML;
37
- actionHandlers?.clicked?.(e, rawhHtml);
35
+ const rawHtml = contentRef.current?.innerHTML;
36
+ onCopyClicked?.(e, rawHtml);
38
37
  const html = typeof toClip === 'string'
39
38
  ? toClip
40
39
  : typeof toClip === 'function'
41
- ? toClip(rawhHtml)
42
- : rawhHtml;
40
+ ? toClip(rawHtml)
41
+ : rawHtml;
43
42
  if (html === undefined)
44
43
  return;
45
44
  try {
@@ -53,19 +52,18 @@ export const Clippable = ({ className, children, toClip, actionHandlers, stateHa
53
52
  catch (err) {
54
53
  // eslint-disable-next-line no-console
55
54
  console.error(err);
55
+ return;
56
56
  }
57
- stateHandlers?.clipped?.(html);
58
- setBeenRecentlyClipped(true);
57
+ onClipped?.(html);
58
+ setHasBeenRecentlyClipped(true);
59
59
  if (clippedTimeoutRef.current !== null)
60
60
  clearTimeout(clippedTimeoutRef.current);
61
- clippedTimeoutRef.current = setTimeout(() => {
62
- setBeenRecentlyClipped(false);
63
- }, 3000);
61
+ clippedTimeoutRef.current = setTimeout(() => setHasBeenRecentlyClipped(false), clippedModifierDurationMs);
64
62
  };
65
63
  // Rendering
66
64
  const c = clss(publicClassName, { cssModule });
67
65
  const rootClss = mergeClassNames(c(null, {
68
- clipped: beenRecentlyClipped
66
+ clipped: hasBeenRecentlyClipped
69
67
  }), className);
70
68
  const copyClss = c('copy');
71
69
  const contentClss = c('content');
@@ -1,45 +1,52 @@
1
1
  import { type ReactNode, type PropsWithChildren, type FunctionComponent } from 'react';
2
2
  import type { WithClassName } from '../utils/types.js';
3
3
  /**
4
- * Props for the Disclaimer component.
4
+ * Props for the {@link Disclaimer} component.
5
5
  *
6
6
  * @property content - Content displayed inside the disclaimer panel.
7
7
  * @property togglerContent - Content rendered inside the dismiss toggler.
8
- * If not provided, the toggler is not rendered.
9
- * @property isOn - Controls the visibility state. When defined, the component
10
- * behaves as a controlled component.
11
- * @property defaultIsOn - Default visibility state for uncontrolled mode.
12
- * @property stateHandlers - Callbacks invoked after state changes.
13
- * @property stateHandlers.toggled - Callback invoked after the disclaimer state changes.
14
- * @property actionHandlers - Callbacks invoked before actions are committed.
15
- * @property actionHandlers.dismissClick - Callback invoked before the disclaimer is dismissed.
8
+ * When omitted, the toggler is not rendered.
9
+ * @property isOn - Controlled visibility state. When defined, the component
10
+ * behaves as a controlled component and internal state is never updated.
11
+ * @property defaultIsOn - Initial visibility state in uncontrolled mode.
12
+ * Ignored when `isOn` is provided. Defaults to `true`.
13
+ * @property onDismissClicked - Called when the toggler is clicked, before the
14
+ * disclaimer reacts, with the visibility state as it was.
15
+ * @property onIsOnChanged - Called after the visibility state changed, with the
16
+ * new value.
16
17
  * @property className - Optional additional class name(s) applied to the root element.
17
- * @property children - Additional content rendered below the disclaimer panel.
18
+ * @property children - The content the disclaimer gates, rendered below the panel.
18
19
  */
19
20
  export type Props = PropsWithChildren<WithClassName<{
20
21
  content?: ReactNode;
21
22
  togglerContent?: ReactNode;
22
23
  isOn?: boolean;
23
24
  defaultIsOn?: boolean;
24
- stateHandlers?: {
25
- toggled?: (isOn: boolean) => void;
26
- };
27
- actionHandlers?: {
28
- dismissClick?: (prevIsOn: boolean) => void;
29
- };
25
+ onDismissClicked?: (isOn: boolean) => void;
26
+ onIsOnChanged?: (isOn: boolean) => void;
30
27
  }>>;
31
28
  /**
32
- * Component that displays a dismissible disclaimer panel.
29
+ * Dismissible disclaimer panel, gating the content it wraps.
33
30
  *
34
- * Supports both controlled (`isOn` provided) and uncontrolled modes.
31
+ * ### CSS modifiers
32
+ * - `on` — the disclaimer is showing.
33
+ * - `off` — the disclaimer has been dismissed.
34
+ *
35
+ * ### CSS elements
36
+ * - `panel`
37
+ * - `content`
38
+ * - `toggler`
39
+ * - `sensitive` — wraps `children`.
35
40
  *
36
41
  * @param props - Component properties.
37
42
  * @see {@link Props}
43
+ * @returns A root `<div>` holding the disclaimer panel and the gated content.
38
44
  *
39
45
  * @remarks
40
- * - In controlled mode, visibility is driven by `isOn` and internal state
41
- * does not toggle automatically.
42
- * - In uncontrolled mode, the component manages its own visibility state.
43
- * - Applies `on` and `off` modifier classes depending on visibility state.
46
+ * - In controlled mode (`isOn` defined), visibility is fully driven by the
47
+ * parent and internal state is never updated.
48
+ * - `onDismissClicked` fires in both modes a controlled parent needs it to
49
+ * know a click happened at all.
50
+ * - `onIsOnChanged` fires in both modes too, and never on mount.
44
51
  */
45
52
  export declare const Disclaimer: FunctionComponent<Props>;
@@ -1,39 +1,44 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect, useRef } from 'react';
2
+ import { useState } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames } from '../utils/index.js';
4
+ import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
5
5
  import { disclaimer as publicClassName } from '../public-classnames.js';
6
6
  import cssModule from './styles.module.css';
7
7
  /**
8
- * Component that displays a dismissible disclaimer panel.
8
+ * Dismissible disclaimer panel, gating the content it wraps.
9
9
  *
10
- * Supports both controlled (`isOn` provided) and uncontrolled modes.
10
+ * ### CSS modifiers
11
+ * - `on` — the disclaimer is showing.
12
+ * - `off` — the disclaimer has been dismissed.
13
+ *
14
+ * ### CSS elements
15
+ * - `panel`
16
+ * - `content`
17
+ * - `toggler`
18
+ * - `sensitive` — wraps `children`.
11
19
  *
12
20
  * @param props - Component properties.
13
21
  * @see {@link Props}
22
+ * @returns A root `<div>` holding the disclaimer panel and the gated content.
14
23
  *
15
24
  * @remarks
16
- * - In controlled mode, visibility is driven by `isOn` and internal state
17
- * does not toggle automatically.
18
- * - In uncontrolled mode, the component manages its own visibility state.
19
- * - Applies `on` and `off` modifier classes depending on visibility state.
25
+ * - In controlled mode (`isOn` defined), visibility is fully driven by the
26
+ * parent and internal state is never updated.
27
+ * - `onDismissClicked` fires in both modes a controlled parent needs it to
28
+ * know a click happened at all.
29
+ * - `onIsOnChanged` fires in both modes too, and never on mount.
20
30
  */
21
- export const Disclaimer = ({ content, togglerContent, isOn: isOnProp, defaultIsOn: defaultIsOnProp, stateHandlers, actionHandlers, children, className }) => {
22
- // State & refs
23
- const [internalIsOn, setInternalIsOn] = useState(isOnProp ?? defaultIsOnProp ?? true);
31
+ export const Disclaimer = ({ content, togglerContent, isOn: isOnProp, defaultIsOn = true, onDismissClicked, onIsOnChanged, children, className }) => {
32
+ // State
33
+ const [internalIsOn, setInternalIsOn] = useState(defaultIsOn);
34
+ const isControlled = isOnProp !== undefined;
24
35
  const isOn = isOnProp ?? internalIsOn;
25
- const pIsOn = useRef(isOn);
26
- // State change handlers
27
- useEffect(() => {
28
- if (pIsOn.current === isOn)
29
- return;
30
- stateHandlers?.toggled?.(isOn);
31
- pIsOn.current = isOn;
32
- }, [isOn]);
33
- // User actions handlers
36
+ // State dispatch
37
+ useChangeDispatch(isOn, onIsOnChanged);
38
+ // User action handlers
34
39
  const handleDismissClick = () => {
35
- actionHandlers?.dismissClick?.(isOn);
36
- if (isOnProp !== undefined)
40
+ onDismissClicked?.(isOn);
41
+ if (isControlled)
37
42
  return;
38
43
  setInternalIsOn(false);
39
44
  };
@@ -45,7 +50,7 @@ export const Disclaimer = ({ content, togglerContent, isOn: isOnProp, defaultIsO
45
50
  }), className);
46
51
  const panelClss = c('panel');
47
52
  const contentClss = c('content');
48
- const btnClss = c('toggler');
53
+ const togglerClss = c('toggler');
49
54
  const sensitiveClss = c('sensitive');
50
- return _jsxs("div", { className: rootClss, children: [_jsxs("div", { className: panelClss, children: [content !== undefined && _jsx("div", { className: contentClss, children: content }), togglerContent !== undefined && _jsx("div", { className: btnClss, onClick: handleDismissClick, children: togglerContent })] }), _jsx("div", { className: sensitiveClss, children: children })] });
55
+ return _jsxs("div", { className: rootClss, children: [_jsxs("div", { className: panelClss, children: [content !== undefined && _jsx("div", { className: contentClss, children: content }), togglerContent !== undefined && _jsx("div", { className: togglerClss, onClick: handleDismissClick, children: togglerContent })] }), _jsx("div", { className: sensitiveClss, children: children })] });
51
56
  };
@@ -1,45 +1,65 @@
1
1
  import { type PropsWithChildren, type FunctionComponent, type ReactNode } from 'react';
2
2
  import type { WithClassName } from '../utils/types.js';
3
3
  /**
4
- * Props for the Drawer component.
4
+ * Props for the {@link Drawer} component.
5
5
  *
6
6
  * @property openerContent - Content rendered inside the opener control.
7
7
  * @property closerContent - Content rendered inside the closer control.
8
- * @property initialIsOpened - Initial open state in uncontrolled mode.
9
- * Ignored when `isOpened` is provided.
8
+ * @property defaultIsOpened - Initial open state in uncontrolled mode.
9
+ * Ignored when `isOpened` is provided. Defaults to `false`.
10
10
  * @property isOpened - Controlled open state. When defined, the component
11
- * behaves as a controlled component and internal state is ignored.
12
- * @property stateHandlers - Callbacks invoked after the state changes
13
- * @property stateHandlers.toggled - Callbacks invoked after the isOpened state has changed
11
+ * behaves as a controlled component and internal state is never updated.
12
+ * @property onOpenerClicked - Called when the opener is clicked, before the
13
+ * drawer reacts, with the open state as it was.
14
+ * @property onCloserClicked - Called when the closer is clicked, before the
15
+ * drawer reacts, with the open state as it was.
16
+ * @property onIsOpenedChanged - Called after the open state changed, with the
17
+ * new value.
14
18
  * @property className - Additional class name(s) applied to the root element.
15
19
  * @property children - Drawer content.
16
20
  */
17
21
  export type Props = PropsWithChildren<WithClassName<{
18
22
  openerContent?: ReactNode;
19
23
  closerContent?: ReactNode;
20
- initialIsOpened?: boolean;
24
+ defaultIsOpened?: boolean;
21
25
  isOpened?: boolean;
22
- stateHandlers?: {
23
- toggled?: (isOpen: boolean) => void;
24
- };
26
+ onOpenerClicked?: (isOpened: boolean) => void;
27
+ onCloserClicked?: (isOpened: boolean) => void;
28
+ onIsOpenedChanged?: (isOpened: boolean) => void;
25
29
  }>>;
26
30
  /**
27
- * Drawer component with optional controlled and uncontrolled behavior.
31
+ * Drawer component supporting controlled and uncontrolled usage.
32
+ *
33
+ * The content is measured through a {@link ResizeObserverComponent} so its
34
+ * dimensions can drive the open/close transition from CSS alone.
35
+ *
36
+ * ### CSS modifiers
37
+ * - `opened` — the drawer is open.
38
+ * - `closed` — the drawer is closed.
39
+ *
40
+ * ### CSS elements
41
+ * - `opener`
42
+ * - `closer`
43
+ * - `content`
44
+ *
45
+ * ### CSS custom properties on the root element
46
+ * - `--lm-drawer-content-width` / `--lm-drawer-content-width-raw`
47
+ * - `--lm-drawer-content-height` / `--lm-drawer-content-height-raw`
48
+ * Absent until the first measurement lands.
49
+ *
50
+ * ### Data attributes on the root element
51
+ * - `data-content-width`, `data-content-height` — the measured content size.
52
+ * Absent until the first measurement lands.
53
+ *
54
+ * @param props - Component properties.
55
+ * @see {@link Props}
56
+ * @returns A root `<div>` holding the opener, the closer and the measured content.
28
57
  *
29
58
  * @remarks
30
- * - In controlled mode (`isOpened` defined), visibility is fully driven by the prop.
31
- * - In uncontrolled mode, internal state is initialized from `initialIsOpened`.
32
- * - The component measures its content using `ResizeObserverComponent`
33
- * and exposes the dimensions:
34
- * - As CSS custom properties:
35
- * --{prefix}-content-height
36
- * --{prefix}-content-height-px
37
- * --{prefix}-content-width
38
- * --{prefix}-content-width-px
39
- * - As `data-content-width` and `data-content-height` attributes.
40
- *
41
- * CSS modifier classes:
42
- * - `opened` when open
43
- * - `closed` when closed
59
+ * - In controlled mode (`isOpened` defined), the open state is fully driven by
60
+ * the parent and internal state is never updated.
61
+ * - `onOpenerClicked` and `onCloserClicked` fire in both modes — a controlled
62
+ * parent needs them to know a click happened at all.
63
+ * - `onIsOpenedChanged` fires in both modes too, and never on mount.
44
64
  */
45
65
  export declare const Drawer: FunctionComponent<Props>;