@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,20 +1,30 @@
1
- import { type FunctionComponent } from 'react';
2
- import { type Props as ControlledProps } from './index.controlled.js';
1
+ import { type FunctionComponent, type ReactNode, type TextareaHTMLAttributes } from 'react';
2
+ import type { WithClassName } from '../utils/types.js';
3
3
  /**
4
4
  * Props for the {@link Textarea} component.
5
5
  *
6
- * Alias of {@link ControlledProps}.
6
+ * Extends all native {@link TextareaHTMLAttributes} and {@link WithClassName}
7
+ * with optional label, error content, and automatic height adjustment.
7
8
  *
8
- * All native textarea attributes remain available, including `value`,
9
- * `defaultValue`, and `onChange`, allowing the component to operate
10
- * in either controlled or hybrid mode.
9
+ * @property label - Content rendered as an associated `<label>`. When omitted, no label is rendered.
10
+ * @property error - Content rendered as an error message below the textarea. When omitted, no error is rendered.
11
+ * @property autoHeight - When `true`, grows and shrinks the textarea to fit its
12
+ * content on every value change. Defaults to `false`.
13
+ * @property className - Additional class name(s) applied to the textarea element.
11
14
  */
12
- export type Props = ControlledProps;
15
+ export type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & WithClassName<{
16
+ label?: ReactNode;
17
+ error?: ReactNode;
18
+ autoHeight?: boolean;
19
+ }>;
13
20
  /**
14
- * Textarea field component supporting controlled and hybrid usage.
21
+ * Textarea field supporting controlled and uncontrolled usage.
15
22
  *
16
- * Wraps {@link ControlledTextarea} and automatically manages the textarea
17
- * value when no `value` prop is provided.
23
+ * Renders a native `<textarea>` with optional label and error feedback. All
24
+ * standard textarea attributes are forwarded to the underlying element.
25
+ *
26
+ * A stable auto-generated `id` is created on mount and used to associate the
27
+ * rendered label through the `htmlFor` attribute.
18
28
  *
19
29
  * ### CSS elements
20
30
  * - `label`
@@ -22,17 +32,13 @@ export type Props = ControlledProps;
22
32
  *
23
33
  * @param props - Component properties.
24
34
  * @see {@link Props}
25
- *
26
- * @returns A labelled textarea with optional internal value management.
35
+ * @returns A labelled textarea with optional error feedback.
27
36
  *
28
37
  * @remarks
29
- * - In controlled mode (`value` defined), the textarea value is fully driven
30
- * by the parent component and internal state is never updated.
31
- * - In hybrid mode, internal state is initialized from `defaultValue` and
32
- * subsequently manages value updates itself.
33
- * - In hybrid mode, the internal value is updated before forwarding the
34
- * `onChange` callback.
35
- * - `defaultValue` is only used to initialize internal state and is not
36
- * forwarded to the underlying controlled component.
38
+ * - In controlled mode (`value` defined), the displayed value is fully driven
39
+ * by the parent and internal state is never updated.
40
+ * - In uncontrolled mode, internal state is initialized from `defaultValue` and
41
+ * updated before `onChange` is forwarded.
42
+ * - `onChange` fires in both modes.
37
43
  */
38
44
  export declare const Textarea: FunctionComponent<Props>;
@@ -1,11 +1,19 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
- import { ControlledTextarea } from './index.controlled.js';
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useRef, useState } from 'react';
3
+ import { clss } from '../../agnostic/css/clss/index.js';
4
+ import { isNotFalsy } from '../../agnostic/booleans/is-falsy/index.js';
5
+ import { randomHash } from '../../agnostic/random/uuid/index.js';
6
+ import { mergeClassNames } from '../utils/index.js';
7
+ import { textarea as publicClassName } from '../public-classnames.js';
8
+ import cssModule from './styles.module.css';
4
9
  /**
5
- * Textarea field component supporting controlled and hybrid usage.
10
+ * Textarea field supporting controlled and uncontrolled usage.
6
11
  *
7
- * Wraps {@link ControlledTextarea} and automatically manages the textarea
8
- * value when no `value` prop is provided.
12
+ * Renders a native `<textarea>` with optional label and error feedback. All
13
+ * standard textarea attributes are forwarded to the underlying element.
14
+ *
15
+ * A stable auto-generated `id` is created on mount and used to associate the
16
+ * rendered label through the `htmlFor` attribute.
9
17
  *
10
18
  * ### CSS elements
11
19
  * - `label`
@@ -13,27 +21,38 @@ import { ControlledTextarea } from './index.controlled.js';
13
21
  *
14
22
  * @param props - Component properties.
15
23
  * @see {@link Props}
16
- *
17
- * @returns A labelled textarea with optional internal value management.
24
+ * @returns A labelled textarea with optional error feedback.
18
25
  *
19
26
  * @remarks
20
- * - In controlled mode (`value` defined), the textarea value is fully driven
21
- * by the parent component and internal state is never updated.
22
- * - In hybrid mode, internal state is initialized from `defaultValue` and
23
- * subsequently manages value updates itself.
24
- * - In hybrid mode, the internal value is updated before forwarding the
25
- * `onChange` callback.
26
- * - `defaultValue` is only used to initialize internal state and is not
27
- * forwarded to the underlying controlled component.
27
+ * - In controlled mode (`value` defined), the displayed value is fully driven
28
+ * by the parent and internal state is never updated.
29
+ * - In uncontrolled mode, internal state is initialized from `defaultValue` and
30
+ * updated before `onChange` is forwarded.
31
+ * - `onChange` fires in both modes.
28
32
  */
29
- export const Textarea = ({ defaultValue, value, onChange, ...rest }) => {
33
+ export const Textarea = ({ label, error, autoHeight = false, value, defaultValue, onChange, className, ...rest }) => {
34
+ // State & refs
35
+ const [id] = useState(`_${randomHash(12)}`);
36
+ const [internalValue, setInternalValue] = useState(defaultValue ?? '');
37
+ const textareaRef = useRef(null);
30
38
  const isControlled = value !== undefined;
31
- const [internal, setInternal] = useState(defaultValue ?? '');
32
- const currentValue = isControlled ? value : internal;
39
+ const currentValue = isControlled ? value : internalValue;
40
+ // Fx. dep. `autoHeight`, `currentValue` - fit the height to the content
41
+ useLayoutEffect(() => {
42
+ const textarea = textareaRef.current;
43
+ if (!autoHeight || textarea === null)
44
+ return;
45
+ textarea.style.height = 'auto';
46
+ textarea.style.height = `${textarea.scrollHeight}px`;
47
+ }, [autoHeight, currentValue]);
48
+ // User action handlers
33
49
  const handleChange = (e) => {
34
50
  if (!isControlled)
35
- setInternal(e.target.value);
51
+ setInternalValue(e.target.value);
36
52
  onChange?.(e);
37
53
  };
38
- return _jsx(ControlledTextarea, { ...rest, value: currentValue, onChange: handleChange });
54
+ // Rendering
55
+ const c = clss(publicClassName, { cssModule });
56
+ const rootClss = mergeClassNames(c(), className);
57
+ return _jsxs(_Fragment, { children: [isNotFalsy(label) && _jsx("label", { className: c('label'), htmlFor: id, children: label }), _jsx("textarea", { ...rest, ref: textareaRef, id: id, className: rootClss, value: currentValue, onChange: handleChange }), isNotFalsy(error) && _jsx("span", { className: c('error'), children: error })] });
39
58
  };
@@ -3,23 +3,24 @@ import type { WithClassName } from '../utils/types.js';
3
3
  /**
4
4
  * Props for the {@link Theatre} component.
5
5
  *
6
- * @property closeBtnContent - Custom content rendered inside the close/exit button.
7
- * @property openBtnContent - Custom content rendered inside the open/enter button.
8
- * @property isOn - Controlled theatre mode state. When provided, overrides the
9
- * internal state. Use together with {@link Props.onToggleClick} for fully
10
- * controlled usage.
11
- * @property defaultIsOn - Default state for the theatre mode.
12
- * @property exitOnEscape When uncontrolled and on, toggles internal state to off when 'esc' key is pressed
13
- * @property exitOnBgClick When uncontrolled and on, toggles internal state to off when the background is clicked
14
- * @property stateHandlers - Callbacks called after the internal state changed
15
- * @property stateHandlers.toggled - Callback invoked after the state changed
16
- * @property actionHandlers - Callbacks called after a user action on children elements
17
- * @property actionHandlers.toggleClick - Callback invoked when either the open or close
18
- * button is clicked, the 'esc' key pressed or the background clicked. Receives the theatre state value (`isOn`) at the time of the click,
19
- * i.e. the previous state before the toggle.
6
+ * @property closeBtnContent - Content rendered inside the close/exit button.
7
+ * @property openBtnContent - Content rendered inside the open/enter button.
8
+ * @property isOn - Controlled theatre mode state. When defined, the component
9
+ * behaves as a controlled component and internal state is never updated.
10
+ * @property defaultIsOn - Initial theatre mode state in uncontrolled mode.
11
+ * Ignored when `isOn` is provided. Defaults to `false`.
12
+ * @property exitOnEscape - When `true`, pressing `Escape` while the stage is
13
+ * open counts as a toggle.
14
+ * @property exitOnBgClick - When `true`, clicking the stage background — and
15
+ * not its content counts as a toggle.
16
+ * @property onToggleClicked - Called whenever a toggle is requested: the open
17
+ * or close button, the `Escape` key, or the stage background. Fires before the
18
+ * theatre reacts, with the state as it was.
19
+ * @property onIsOnChanged - Called after the theatre mode changed, with the new
20
+ * value.
20
21
  * @property className - Optional additional class name(s) applied to the root element.
21
- * @property children - Content rendered both in the default slot and, when theatre
22
- * mode is active, duplicated inside the stage element.
22
+ * @property children - Content rendered both in the default slot and, when
23
+ * theatre mode is active, duplicated inside the stage element.
23
24
  */
24
25
  export type Props = PropsWithChildren<WithClassName<{
25
26
  closeBtnContent?: ReactNode;
@@ -28,37 +29,32 @@ export type Props = PropsWithChildren<WithClassName<{
28
29
  defaultIsOn?: boolean;
29
30
  exitOnEscape?: boolean;
30
31
  exitOnBgClick?: boolean;
31
- stateHandlers?: {
32
- toggled?: (isOn: boolean) => void;
33
- };
34
- actionHandlers?: {
35
- toggleClick?: (prevIsOn: boolean) => void;
36
- };
32
+ onToggleClicked?: (isOn: boolean) => void;
33
+ onIsOnChanged?: (isOn: boolean) => void;
37
34
  }>>;
38
35
  /**
39
36
  * Theatre mode component. Wraps content in a toggleable fullscreen-like "stage"
40
- * overlay. Supports both controlled and uncontrolled usage.
37
+ * overlay.
41
38
  *
42
- * When `isOn` is not provided the component manages its own open/closed state
43
- * internally. When `isOn` is provided it acts as the source of truth and the
44
- * internal state is ignored.
39
+ * ### CSS modifiers
40
+ * - `on` theatre mode is active.
41
+ * - `off` — theatre mode is inactive.
45
42
  *
46
- * ### Root element modifiers
47
- * The root `<div>` receives the public class name defined by `theatre` and the
48
- * following BEM-style modifier classes:
49
- * - `--on` — when theatre mode is active.
50
- * - `--off` — when theatre mode is inactive.
51
- *
52
- * ### Child elements
53
- * - `__stage` — container rendered inside the root that holds the duplicated
54
- * `children` when theatre mode is active. Only mounted when `isOn` is `true`.
55
- * - `__open-btn` — clickable element that activates theatre mode.
56
- * - `__close-btn` — clickable element that deactivates theatre mode.
43
+ * ### CSS elements
44
+ * - `stage` holds the duplicated `children`, mounted only when `isOn`.
45
+ * - `open-btn`
46
+ * - `close-btn`
57
47
  *
58
48
  * @param props - Component properties.
59
49
  * @see {@link Props}
60
50
  * @returns A root `<div>` containing the children in their original position,
61
- * a stage overlay with the duplicated children (when active), and the open/close
62
- * toggle buttons.
51
+ * a stage overlay with the duplicated children when active, and both toggles.
52
+ *
53
+ * @remarks
54
+ * - In controlled mode (`isOn` defined), the state is fully driven by the
55
+ * parent and internal state is never updated.
56
+ * - `onToggleClicked` fires in both modes, for all four toggle sources — a
57
+ * controlled parent needs it to know a toggle was requested at all.
58
+ * - `onIsOnChanged` fires in both modes too, and never on mount.
63
59
  */
64
60
  export declare const Theatre: FunctionComponent<Props>;
@@ -1,90 +1,78 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState, useRef } 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 { theatre as publicClassName } from '../public-classnames.js';
6
6
  import cssModule from './styles.module.css';
7
7
  /**
8
8
  * Theatre mode component. Wraps content in a toggleable fullscreen-like "stage"
9
- * overlay. Supports both controlled and uncontrolled usage.
9
+ * overlay.
10
10
  *
11
- * When `isOn` is not provided the component manages its own open/closed state
12
- * internally. When `isOn` is provided it acts as the source of truth and the
13
- * internal state is ignored.
11
+ * ### CSS modifiers
12
+ * - `on` theatre mode is active.
13
+ * - `off` — theatre mode is inactive.
14
14
  *
15
- * ### Root element modifiers
16
- * The root `<div>` receives the public class name defined by `theatre` and the
17
- * following BEM-style modifier classes:
18
- * - `--on` — when theatre mode is active.
19
- * - `--off` — when theatre mode is inactive.
20
- *
21
- * ### Child elements
22
- * - `__stage` — container rendered inside the root that holds the duplicated
23
- * `children` when theatre mode is active. Only mounted when `isOn` is `true`.
24
- * - `__open-btn` — clickable element that activates theatre mode.
25
- * - `__close-btn` — clickable element that deactivates theatre mode.
15
+ * ### CSS elements
16
+ * - `stage` holds the duplicated `children`, mounted only when `isOn`.
17
+ * - `open-btn`
18
+ * - `close-btn`
26
19
  *
27
20
  * @param props - Component properties.
28
21
  * @see {@link Props}
29
22
  * @returns A root `<div>` containing the children in their original position,
30
- * a stage overlay with the duplicated children (when active), and the open/close
31
- * toggle buttons.
23
+ * a stage overlay with the duplicated children when active, and both toggles.
24
+ *
25
+ * @remarks
26
+ * - In controlled mode (`isOn` defined), the state is fully driven by the
27
+ * parent and internal state is never updated.
28
+ * - `onToggleClicked` fires in both modes, for all four toggle sources — a
29
+ * controlled parent needs it to know a toggle was requested at all.
30
+ * - `onIsOnChanged` fires in both modes too, and never on mount.
32
31
  */
33
- export const Theatre = ({ closeBtnContent, openBtnContent, isOn, defaultIsOn, exitOnEscape, exitOnBgClick, stateHandlers, actionHandlers, children, className }) => {
32
+ export const Theatre = ({ closeBtnContent, openBtnContent, isOn: isOnProp, defaultIsOn = false, exitOnEscape, exitOnBgClick, onToggleClicked, onIsOnChanged, children, className }) => {
34
33
  // State & refs
35
- const [internalIsOn, setInternalIsOn] = useState(defaultIsOn ?? false);
34
+ const [internalIsOn, setInternalIsOn] = useState(defaultIsOn);
36
35
  const stageRef = useRef(null);
37
- const isTheatreOn = isOn ?? internalIsOn;
38
- const prevIsTheatreOnRef = useRef(isTheatreOn);
39
- // Handlers
40
- const handleCloseBtnClick = () => {
41
- actionHandlers?.toggleClick?.(isTheatreOn);
42
- if (isOn === undefined)
43
- setInternalIsOn(false);
44
- };
45
- const handleOpenBtnClick = () => {
46
- actionHandlers?.toggleClick?.(isTheatreOn);
47
- if (isOn === undefined)
48
- setInternalIsOn(true);
36
+ const isControlled = isOnProp !== undefined;
37
+ const isOn = isOnProp ?? internalIsOn;
38
+ // State dispatch
39
+ useChangeDispatch(isOn, onIsOnChanged);
40
+ // User action handlers
41
+ const requestToggle = (targetIsOn) => {
42
+ onToggleClicked?.(isOn);
43
+ if (isControlled)
44
+ return;
45
+ setInternalIsOn(targetIsOn);
49
46
  };
47
+ const handleOpenBtnClick = () => requestToggle(true);
48
+ const handleCloseBtnClick = () => requestToggle(false);
50
49
  const handleStageBgClick = e => {
51
50
  if (exitOnBgClick !== true)
52
51
  return;
53
52
  if (e.target !== stageRef.current)
54
53
  return;
55
- actionHandlers?.toggleClick?.(isTheatreOn);
56
- if (isOn === undefined)
57
- setInternalIsOn(false);
54
+ requestToggle(false);
58
55
  };
59
- // Effects
60
- useEffect(() => {
61
- if (prevIsTheatreOnRef.current !== isTheatreOn) {
62
- stateHandlers?.toggled?.(isTheatreOn);
63
- prevIsTheatreOnRef.current = isTheatreOn;
64
- }
65
- }, [isTheatreOn, stateHandlers]);
56
+ // Fx. dep. `exitOnEscape`, `isOn` - close the stage on the Escape key
66
57
  useEffect(() => {
67
- if (exitOnEscape === true
68
- || !isTheatreOn
69
- || isOn !== undefined)
58
+ if (exitOnEscape !== true || !isOn)
70
59
  return;
71
- const listener = (e) => {
60
+ const handleKeyDown = (e) => {
72
61
  if (e.key !== 'Escape')
73
62
  return;
74
- actionHandlers?.toggleClick?.(isTheatreOn);
75
- setInternalIsOn(false);
63
+ requestToggle(false);
76
64
  };
77
- window.addEventListener('keydown', listener);
78
- return () => window.removeEventListener('keydown', listener);
79
- }, [exitOnEscape, isTheatreOn, isOn]);
80
- // Render
65
+ window.addEventListener('keydown', handleKeyDown);
66
+ return () => window.removeEventListener('keydown', handleKeyDown);
67
+ }, [exitOnEscape, isOn, isControlled]);
68
+ // Rendering
81
69
  const c = clss(publicClassName, { cssModule });
82
70
  const rootClss = mergeClassNames(c(null, {
83
- 'on': isTheatreOn,
84
- 'off': !isTheatreOn
71
+ 'on': isOn,
72
+ 'off': !isOn
85
73
  }), className);
86
- const stageClass = c('stage');
87
- const openBtnClass = c('open-btn');
88
- const closeBtnClass = c('close-btn');
89
- return _jsxs("div", { className: rootClss, children: [children, _jsx("div", { className: stageClass, onClick: handleStageBgClick, ref: stageRef, children: isTheatreOn && children }), _jsx("div", { className: closeBtnClass, onClick: handleCloseBtnClick, children: closeBtnContent }), _jsx("div", { className: openBtnClass, onClick: handleOpenBtnClick, children: openBtnContent })] });
74
+ const stageClss = c('stage');
75
+ const openBtnClss = c('open-btn');
76
+ const closeBtnClss = c('close-btn');
77
+ return _jsxs("div", { className: rootClss, children: [children, _jsx("div", { className: stageClss, onClick: handleStageBgClick, ref: stageRef, children: isOn && children }), _jsx("div", { className: closeBtnClss, onClick: handleCloseBtnClick, children: closeBtnContent }), _jsx("div", { className: openBtnClss, onClick: handleOpenBtnClick, children: openBtnContent })] });
90
78
  };
@@ -31,49 +31,45 @@ type ModuleData = {
31
31
  * component stays in the `--no-module` state.
32
32
  * @property props - Arbitrary key-value object forwarded verbatim to the
33
33
  * module's `init` call and, on subsequent changes, to `update` (if exported).
34
- * @property stateHandlers - Optional callbacks invoked whenever internal state changes:
35
- * - `idChanged` called with the stable instance ID once on mount.
36
- * - `isLoadingChanged` called with the new loading state on every transition.
37
- * - `loadedModuleChanged` — called with the new module value (`ModuleData`, `Error`, or `null`)
38
- * after each load attempt or teardown.
39
- * - `moduleTargetChanged` called with the `Element` returned by `init`, or `null`
40
- * when the module is unloaded or errored.
34
+ * @property onIdGenerated - Called once on mount with the instance's generated
35
+ * `id`. The id never changes afterwards, so this fires exactly once.
36
+ * @property onIsLoadingChanged - Called after the loading state changed, with
37
+ * the new value.
38
+ * @property onLoadedModuleChanged - Called after the loaded module changed, with
39
+ * the new value: the validated {@link ModuleData}, an `Error`, or `null`.
40
+ * @property onModuleTargetChanged - Called after the hosted element changed,
41
+ * with the `Element` returned by `init`, or `null` once unloaded or errored.
41
42
  * @property className - Optional additional class name(s) applied to the root element.
42
43
  */
43
44
  export type Props = WithClassName<{
44
45
  src?: string;
45
46
  props?: Record<string, unknown>;
46
- stateHandlers?: {
47
- idChanged?: (id: string) => void;
48
- isLoadingChanged?: (isLoading: boolean) => void;
49
- loadedModuleChanged?: (loadedModule: ModuleData | Error | null) => void;
50
- moduleTargetChanged?: (moduleTarget: Element | null) => void;
51
- };
47
+ onIdGenerated?: (id: string) => void;
48
+ onIsLoadingChanged?: (isLoading: boolean) => void;
49
+ onLoadedModuleChanged?: (loadedModule: ModuleData | Error | null) => void;
50
+ onModuleTargetChanged?: (moduleTarget: Element | null) => void;
52
51
  }>;
53
52
  /**
54
- * Dynamic UI module host component. Asynchronously imports an ES module by URL,
55
- * validates its exported interface, calls its `init` lifecycle to obtain a DOM
56
- * `Element`, and appends that element to its own root `<div>`. Handles loading,
57
- * error, and teardown states automatically.
53
+ * Dynamic UI module host. Asynchronously imports an ES module by URL, validates
54
+ * its exported interface, calls its `init` lifecycle to obtain a DOM `Element`,
55
+ * and appends that element to its own root `<div>`.
58
56
  *
59
57
  * The imported module is expected to conform to the {@link ModuleData} interface.
60
58
  * Any violation (missing exports, wrong types, `init` not returning an `Element`)
61
59
  * transitions the component into the `--error` state and logs to `console.error`.
62
60
  *
63
- * ### Root element modifiers
64
- * The root `<div>` receives the public class name defined by `uiModule` and
65
- * the following BEM-style modifier classes reflecting the current load lifecycle:
66
- * - `--loading` — the module fetch is in progress.
67
- * - `--no-module` — no module has been loaded yet (`src` is undefined or the
68
- * effect has not run).
69
- * - `--error` — the import, validation, or `init` call failed.
70
- * - `--loaded` — the module passed validation and `init` returned successfully.
71
- * - `--initialized` — the `Element` returned by `init` has been appended to the
72
- * host `<div>`.
61
+ * ### CSS modifiers
62
+ * Reflecting the current load lifecycle:
63
+ * - `loading` the module fetch is in progress.
64
+ * - `no-module`nothing has been loaded yet (`src` is undefined, or the
65
+ * effect has not run).
66
+ * - `error` the import, the validation, or the `init` call failed.
67
+ * - `loaded` — the module passed validation and `init` returned successfully.
68
+ * - `initialized` — the `Element` returned by `init` has been appended.
73
69
  *
74
70
  * ### Root element attributes
75
- * - `id` — a stable randomly generated ID (prefixed `f`) assigned once on mount.
76
- * Used to scope the module's `css` entries to this specific instance.
71
+ * - `id` — a stable generated id, assigned once on mount and used to scope the
72
+ * module's `css` entries to this specific instance.
77
73
  *
78
74
  * @param props - Component properties.
79
75
  * @see {@link Props}