@design-edito/tools 0.5.37 → 0.5.39

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 (115) hide show
  1. package/agnostic/@react/effects/index.d.ts +1 -0
  2. package/agnostic/@react/effects/index.js +1 -0
  3. package/agnostic/@react/effects/use-state-change-dispatch/index.d.ts +22 -0
  4. package/agnostic/@react/effects/use-state-change-dispatch/index.js +31 -0
  5. package/agnostic/@react/index.d.ts +1 -0
  6. package/agnostic/@react/index.js +1 -0
  7. package/agnostic/arrays/index.d.ts +3 -2
  8. package/agnostic/arrays/index.js +3 -2
  9. package/agnostic/arrays/integers-between/index.d.ts +8 -0
  10. package/agnostic/arrays/integers-between/index.js +11 -0
  11. package/agnostic/arrays/integers-between/index.test.d.ts +1 -0
  12. package/agnostic/arrays/integers-between/index.test.js +16 -0
  13. package/agnostic/colors/index.d.ts +2 -2
  14. package/agnostic/colors/index.js +2 -2
  15. package/agnostic/css/index.d.ts +2 -1
  16. package/agnostic/css/index.js +2 -1
  17. package/agnostic/css/merge-class-names/index.d.ts +8 -0
  18. package/agnostic/css/merge-class-names/index.js +23 -0
  19. package/agnostic/css/merge-class-names/index.test.d.ts +1 -0
  20. package/agnostic/css/merge-class-names/index.test.js +52 -0
  21. package/agnostic/html/hyper-json/smart-tags/coalesced/index.d.ts +11 -11
  22. package/agnostic/html/hyper-json/smart-tags/coalesced/index.js +11 -11
  23. package/agnostic/html/hyper-json/smart-tags/isolated/index.d.ts +2 -2
  24. package/agnostic/html/hyper-json/smart-tags/isolated/index.js +2 -2
  25. package/agnostic/html/index.d.ts +2 -2
  26. package/agnostic/html/index.js +2 -2
  27. package/agnostic/index.d.ts +2 -1
  28. package/agnostic/index.js +2 -1
  29. package/agnostic/misc/index.d.ts +1 -1
  30. package/agnostic/misc/index.js +1 -1
  31. package/agnostic/numbers/index.d.ts +2 -2
  32. package/agnostic/numbers/index.js +2 -2
  33. package/agnostic/objects/index.d.ts +2 -2
  34. package/agnostic/objects/index.js +2 -2
  35. package/agnostic/random/index.d.ts +1 -1
  36. package/agnostic/random/index.js +1 -1
  37. package/agnostic/time/dates/index.d.ts +1 -1
  38. package/agnostic/time/dates/index.js +1 -1
  39. package/components/BeforeAfter/index.controlled.js +1 -1
  40. package/components/BeforeAfter/index.js +2 -2
  41. package/components/Button/index.js +1 -1
  42. package/components/Clippable/index.js +1 -1
  43. package/components/Disclaimer/index.d.ts +2 -2
  44. package/components/Disclaimer/index.js +4 -3
  45. package/components/Drawer/index.js +4 -3
  46. package/components/EventListener/index.js +1 -1
  47. package/components/Gallery/index.js +5 -4
  48. package/components/Iframe/index.js +1 -1
  49. package/components/Image/index.d.ts +1 -1
  50. package/components/Image/index.js +3 -2
  51. package/components/Input/index.js +1 -1
  52. package/components/IntersectionObserver/index.js +1 -1
  53. package/components/JsonEditor/index.js +1 -1
  54. package/components/JsonEditor/types.d.ts +0 -6
  55. package/components/JsonEditor/utils.d.ts +0 -5
  56. package/components/JsonEditor/utils.js +0 -5
  57. package/components/Lightbox/index.d.ts +12 -12
  58. package/components/Lightbox/index.js +37 -35
  59. package/components/Lightbox/store.d.ts +9 -43
  60. package/components/Lightbox/store.js +51 -73
  61. package/components/Lightbox/types.d.ts +16 -0
  62. package/components/Lightbox/types.js +1 -0
  63. package/components/ListLoader/index.controlled.js +19 -13
  64. package/components/ListLoader/index.js +19 -3
  65. package/components/Overlayer/index.js +1 -1
  66. package/components/Paginator/index.js +4 -3
  67. package/components/ResizeObserver/index.js +1 -1
  68. package/components/Scrllgngn/index.d.ts +4 -4
  69. package/components/Scrllgngn/index.js +4 -3
  70. package/components/Scrllgngn/types.d.ts +55 -0
  71. package/components/Scrllgngn/types.js +24 -0
  72. package/components/Scrllgngn/utils.d.ts +2 -38
  73. package/components/ScrollListener/index.d.ts +1 -6
  74. package/components/ScrollListener/index.js +10 -9
  75. package/components/ScrollListener/types.d.ts +0 -3
  76. package/components/Select/index.js +1 -1
  77. package/components/Sequencer/index.d.ts +2 -9
  78. package/components/Sequencer/index.js +26 -128
  79. package/components/Sequencer/types.d.ts +30 -0
  80. package/components/Sequencer/types.js +30 -0
  81. package/components/Sequencer/utils.d.ts +4 -0
  82. package/components/Sequencer/utils.js +25 -0
  83. package/components/ShadowRoot/index.js +1 -1
  84. package/components/Subtitles/index.js +3 -5
  85. package/components/Subtitles/utils.d.ts +0 -51
  86. package/components/Subtitles/utils.js +0 -41
  87. package/components/Textarea/index.js +1 -1
  88. package/components/UIModule/index.d.ts +8 -6
  89. package/components/UIModule/index.js +11 -14
  90. package/components/Video/index.controlled.d.ts +1 -7
  91. package/components/Video/index.controlled.js +13 -12
  92. package/components/Video/index.d.ts +0 -8
  93. package/components/Video/index.js +11 -15
  94. package/components/Video/types.d.ts +33 -0
  95. package/components/Video/utils.d.ts +1 -67
  96. package/components/Video/utils.js +25 -44
  97. package/components/index.d.ts +3 -3
  98. package/components/index.js +3 -3
  99. package/components/utils/index.d.ts +1 -44
  100. package/components/utils/index.js +1 -83
  101. package/components/utils/viewport-behaviours/index.d.ts +8 -14
  102. package/components/utils/viewport-behaviours/index.js +15 -37
  103. package/components/utils/viewport-behaviours/types.d.ts +3 -9
  104. package/components/utils/viewport-behaviours/types.js +3 -4
  105. package/node/ftps/directory/index.d.ts +1 -1
  106. package/node/ftps/directory/index.js +1 -1
  107. package/node/images/index.d.ts +1 -1
  108. package/node/images/index.js +1 -1
  109. package/node/images/transform/operations/index.d.ts +3 -3
  110. package/node/images/transform/operations/index.js +3 -3
  111. package/node/index.d.ts +2 -2
  112. package/node/index.js +2 -2
  113. package/package.json +44 -1
  114. package/components/ListLoader/utils.d.ts +0 -8
  115. package/components/ListLoader/utils.js +0 -10
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRef, useState } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames } from '../utils/index.js';
4
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
5
5
  import { clippable as publicClassName } from '../public-classnames.js';
6
6
  import cssModule from './styles.module.css';
7
7
  /** How long the `clipped` modifier stays on after a successful write. */
@@ -3,7 +3,7 @@ import type { WithClassName } from '../utils/types.js';
3
3
  /**
4
4
  * Props for the {@link Disclaimer} component.
5
5
  *
6
- * @property children - The content the disclaimer gates, rendered below the panel.
6
+ * @property children - The content the disclaimer withholds, rendered below the panel.
7
7
  * @property undisclosedContent - Content displayed inside the disclaimer panel,
8
8
  * until it is dismissed.
9
9
  * @property togglerContent - Content rendered inside the dismiss toggler.
@@ -41,7 +41,7 @@ export type Props = PropsWithChildren<WithClassName<{
41
41
  *
42
42
  * @param props - Component properties.
43
43
  * @see {@link Props}
44
- * @returns A root `<div>` holding the disclaimer panel and the gated content.
44
+ * @returns A root `<div>` holding the disclaimer panel and the withheld content.
45
45
  *
46
46
  * @remarks
47
47
  * - In controlled mode (`isOn` defined), visibility is fully driven by the
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
+ import { useStateChangeDispatch } from '../../agnostic/@react/effects/use-state-change-dispatch/index.js';
3
4
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
5
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
5
6
  import { disclaimer as publicClassName } from '../public-classnames.js';
6
7
  import cssModule from './styles.module.css';
7
8
  /**
@@ -19,7 +20,7 @@ import cssModule from './styles.module.css';
19
20
  *
20
21
  * @param props - Component properties.
21
22
  * @see {@link Props}
22
- * @returns A root `<div>` holding the disclaimer panel and the gated content.
23
+ * @returns A root `<div>` holding the disclaimer panel and the withheld content.
23
24
  *
24
25
  * @remarks
25
26
  * - In controlled mode (`isOn` defined), visibility is fully driven by the
@@ -34,7 +35,7 @@ export const Disclaimer = ({ children, undisclosedContent, togglerContent, isOn:
34
35
  const isControlled = isOnProp !== undefined;
35
36
  const isOn = isOnProp ?? internalIsOn;
36
37
  // State dispatch
37
- useChangeDispatch(isOn, onIsOnChanged);
38
+ useStateChangeDispatch(isOn, onIsOnChanged);
38
39
  // User action handlers
39
40
  const handleDismissClick = () => {
40
41
  onDismissClicked?.(isOn);
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useLayoutEffect, useRef, useState } from 'react';
3
+ import { useStateChangeDispatch } from '../../agnostic/@react/effects/use-state-change-dispatch/index.js';
3
4
  import { clss } from '../../agnostic/css/clss/index.js';
5
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
4
6
  import { ResizeObserverComponent } from '../ResizeObserver/index.js';
5
- import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
6
7
  import { drawer as publicClassName } from '../public-classnames.js';
7
8
  import cssModule from './styles.module.css';
8
9
  /**
@@ -130,8 +131,8 @@ export const Drawer = ({ openerContent, closerContent, openerBtnSelector, closer
130
131
  useToggleMarking(openerRef, openerBtnSelector, openerBtnClss, openerContent);
131
132
  useToggleMarking(closerRef, resolvedCloserBtnSelector, closerBtnClss, resolvedCloserContent);
132
133
  // State dispatch
133
- useChangeDispatch(isOpened, onIsOpenedChanged);
134
- useChangeDispatch(contentDimensions, dimensions => { if (dimensions !== undefined)
134
+ useStateChangeDispatch(isOpened, onIsOpenedChanged);
135
+ useStateChangeDispatch(contentDimensions, dimensions => { if (dimensions !== undefined)
135
136
  onContentResized?.(dimensions); }, (a, b) => a?.width === b?.width && a?.height === b?.height);
136
137
  // User action handlers
137
138
  const handleOpenerClick = (event) => {
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useRef, useEffect } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames } from '../utils/index.js';
4
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
5
5
  import { eventListener as publicClassName } from '../public-classnames.js';
6
6
  import cssModule from './styles.module.css';
7
7
  /**
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Children, useCallback, useEffect, useRef, useState } from 'react';
3
+ import { useStateChangeDispatch } from '../../agnostic/@react/effects/use-state-change-dispatch/index.js';
3
4
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
5
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
5
6
  import { gallery as publicClassName } from '../public-classnames.js';
6
7
  import { EMPTY_GEOMETRY, measureGeometry, readActiveIndex, readReach } from './utils.js';
7
8
  import cssModule from './styles.module.css';
@@ -53,9 +54,9 @@ export const Gallery = ({ offsetLeft, offsetRight, offset, prevButtonContent, ne
53
54
  // gallery was told to be, rather than where a smooth scroll has got to so far.
54
55
  const activeIndex = active ?? scrolledIndex;
55
56
  // State dispatch
56
- useChangeDispatch(activeIndex, onActiveSlotChanged);
57
- useChangeDispatch(canGoLeft, onCanGoLeftChanged);
58
- useChangeDispatch(canGoRight, onCanGoRightChanged);
57
+ useStateChangeDispatch(activeIndex, onActiveSlotChanged);
58
+ useStateChangeDispatch(canGoLeft, onCanGoLeftChanged);
59
+ useStateChangeDispatch(canGoRight, onCanGoRightChanged);
59
60
  const readScroll = useCallback(() => {
60
61
  const scrollerElt = scrollerRef.current;
61
62
  if (scrollerElt === null)
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
4
  import { randomHash } from '../../agnostic/random/uuid/index.js';
5
- import { mergeClassNames } from '../utils/index.js';
5
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
6
6
  import { iframe as publicClassName } from '../public-classnames.js';
7
7
  import cssModule from './styles.module.css';
8
8
  const innerAutoHeightNotifier = (messageType) => `<script>
@@ -12,7 +12,7 @@ import type { WithClassName } from '../utils/types.js';
12
12
  * `<source>` inside a `<video>` carries `src` and nothing else useful, where this one
13
13
  * carries `srcSet`, `media` and `sizes`, and neither element accepts the other's
14
14
  * attributes. What the two do share is how a list of them is read — see
15
- * `parseSourceList` in `components/utils`, whose `stringKey` is exactly that difference.
15
+ * `parseSrcSetSourcesList` in `Video/utils`, whose `stringKey` is exactly that difference.
16
16
  */
17
17
  type SourceData = {
18
18
  srcSet?: string;
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames, parseSourceList } from '../utils/index.js';
4
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
5
+ import { parseSrcSetSourcesList } from '../Video/utils.js';
5
6
  import { image as publicClassName } from '../public-classnames.js';
6
7
  import cssModule from './styles.module.css';
7
8
  /**
@@ -19,7 +20,7 @@ import cssModule from './styles.module.css';
19
20
  */
20
21
  export const Image = ({ sources, className, ...intrinsicImgAttributes }) => {
21
22
  // State
22
- const parsedSources = useMemo(() => parseSourceList(sources, 'srcSet'), [sources]);
23
+ const parsedSources = useMemo(() => parseSrcSetSourcesList(sources, 'srcSet'), [sources]);
23
24
  // Rendering
24
25
  const c = clss(publicClassName, { cssModule });
25
26
  const rootClss = mergeClassNames(c(), className);
@@ -3,7 +3,7 @@ import { useState } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
4
  import { isNotFalsy } from '../../agnostic/booleans/is-falsy/index.js';
5
5
  import { randomHash } from '../../agnostic/random/uuid/index.js';
6
- import { mergeClassNames } from '../utils/index.js';
6
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
7
7
  import { input as publicClassName } from '../public-classnames.js';
8
8
  import cssModule from './styles.module.css';
9
9
  /**
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames } from '../utils/index.js';
4
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
5
5
  import { intersectionObserver as publicClassName } from '../public-classnames.js';
6
6
  import cssModule from './styles.module.css';
7
7
  /**
@@ -3,10 +3,10 @@ import { useState } from 'react';
3
3
  import { ZodError } from 'zod';
4
4
  import { randomHash } from '../../agnostic/random/uuid/index.js';
5
5
  import { isNonNullObject } from '../../agnostic/objects/is-object/index.js';
6
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
6
7
  import { Input } from '../Input/index.js';
7
8
  import { Select } from '../Select/index.js';
8
9
  import { Textarea } from '../Textarea/index.js';
9
- import { mergeClassNames } from '../utils/index.js';
10
10
  import { c, getValueType } from './utils.js';
11
11
  /**
12
12
  * Interactive JSON editor component.
@@ -1,11 +1,5 @@
1
- /** A JSON value that contains no other value. */
2
1
  export type JsonPrimitive = string | number | boolean | null;
3
- /** Any value `JSON.parse` can produce, and the type the editor reads and emits. */
4
2
  export type JsonValue = JsonPrimitive | JsonValue[] | {
5
3
  [k: string]: JsonValue;
6
4
  };
7
- /**
8
- * The editor's name for a value's kind. It is what the type selector offers, and
9
- * what modifies the `value` class name on each node.
10
- */
11
5
  export type ValueType = 'string' | 'number' | 'boolean' | 'null' | 'array' | 'record';
@@ -1,8 +1,3 @@
1
1
  import type { JsonValue, ValueType } from './types.js';
2
- /**
3
- * Shared by every editor in the tree — they all emit class names under the same
4
- * public prefix, so a single instance is what keeps them consistent.
5
- */
6
2
  export declare const c: import("../../agnostic/css/clss/index.js").ClssMaker;
7
- /** Which {@link ValueType} a value belongs to. */
8
3
  export declare function getValueType(value: JsonValue): ValueType;
@@ -2,12 +2,7 @@ import { clss } from '../../agnostic/css/clss/index.js';
2
2
  import { isNonNullObject } from '../../agnostic/objects/is-object/index.js';
3
3
  import { jsonEditor as publicClassName } from '../public-classnames.js';
4
4
  import cssModule from './styles.module.css';
5
- /**
6
- * Shared by every editor in the tree — they all emit class names under the same
7
- * public prefix, so a single instance is what keeps them consistent.
8
- */
9
5
  export const c = clss(publicClassName, { cssModule });
10
- /** Which {@link ValueType} a value belongs to. */
11
6
  export function getValueType(value) {
12
7
  if (value === null)
13
8
  return 'null';
@@ -3,10 +3,10 @@ import type { WithClassName } from '../utils/types.js';
3
3
  /**
4
4
  * Props for the {@link Lightbox} component.
5
5
  *
6
- * @property group - The group(s) this lightbox belongs to. Members of a group share
7
- * one lightbox and are navigable from it, in document order. The **first** group is the
8
- * one a click on this member opens; the others only matter when another member opens
9
- * them. Declaring none makes it a group of its own.
6
+ * @property group - The group(s) this lightbox belongs to. The lightboxes of a group
7
+ * are navigable from one another, in document order. The **first** group is the one a
8
+ * click on this lightbox opens; the others only matter when another lightbox of them is
9
+ * opened. Declaring none makes it a group of its own.
10
10
  * @property closeBtnContent - Content rendered inside the close button.
11
11
  * @property openBtnContent - Content rendered inside the open button. Left empty, the
12
12
  * button is still rendered: a stylesheet hides an empty control with `:empty`, and the
@@ -14,13 +14,13 @@ import type { WithClassName } from '../utils/types.js';
14
14
  * @property prevBtnContent - Content rendered inside the previous button. The button is
15
15
  * always rendered; the root's `navigable` modifier says whether it has anywhere to go.
16
16
  * @property nextBtnContent - Content rendered inside the next button, same.
17
- * @property isOn - Controlled state. When defined, this member is driven by the
18
- * parent and the shared registry never opens or closes it.
17
+ * @property isOn - Controlled state. When defined, this lightbox is driven by the
18
+ * parent and the shared store never opens or closes it.
19
19
  * @property defaultIsOn - Whether the lightbox starts open, in uncontrolled mode.
20
20
  * Ignored when `isOn` is provided. Defaults to `false`.
21
21
  * @property exitOnEscape - When `true`, pressing `Escape` closes the lightbox.
22
22
  * @property navOnArrowKeys - When `true`, the left and right arrow keys move through
23
- * the open group. Only has an effect on a group of more than one member.
23
+ * the open group. Only has an effect on a group of more than one lightbox.
24
24
  * @property exitOnBgClick - When `true`, clicking the backdrop, and not its
25
25
  * content, closes it.
26
26
  * @property openOnClick - When `true`, clicking the content opens the lightbox. An
@@ -40,7 +40,7 @@ import type { WithClassName } from '../utils/types.js';
40
40
  * @property onPrevArrowPressed - Called when the left arrow is pressed while the lightbox
41
41
  * is open (only while `navOnArrowKeys` is `true`), before the lightbox reacts.
42
42
  * @property onNextArrowPressed - Same, for the right arrow.
43
- * @property onIsOnChanged - Called after this member's state changed, with the new
43
+ * @property onIsOnChanged - Called after this lightbox's state changed, with the new
44
44
  * value.
45
45
  * @property className - Optional additional class name(s) applied to the root element.
46
46
  * @property children - The content, rendered in place while closed and moved onto the
@@ -74,7 +74,7 @@ export type Props = PropsWithChildren<WithClassName<{
74
74
  * when opened.
75
75
  *
76
76
  * ### CSS modifiers
77
- * - `on` - this member is open.
77
+ * - `on` - this lightbox is open.
78
78
  * - `off` - it is not.
79
79
  * - `grouped` - it belongs to a group it did not invent.
80
80
  * - `navigable` - that group holds someone else, so the previous and next controls
@@ -113,9 +113,9 @@ export type Props = PropsWithChildren<WithClassName<{
113
113
  * the popover API - would do the opposite, being document-wide and above
114
114
  * everything, and paint over whichever article is being read.
115
115
  * - **Groups are held outside React**, in `./store.ts`: a consumer may render each
116
- * component in a root of its own, in which case members of a group share no tree
117
- * and a context could not reach from one to the other.
118
- * - Every member holds its own backdrop; only the open one is lifted over the page.
116
+ * component in a root of its own, in which case the lightboxes of a group share no
117
+ * tree and a context could not reach from one to the other.
118
+ * - Every lightbox holds its own backdrop; only the open one is lifted over the page.
119
119
  * Navigating a group therefore lowers one and lifts another, and neither one's
120
120
  * content is touched.
121
121
  * - This component ships **no appearance**: covering the page, the backdrop and the
@@ -1,16 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';
2
+ import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from 'react';
3
+ import { useStateChangeDispatch } from '../../agnostic/@react/effects/use-state-change-dispatch/index.js';
3
4
  import { clss } from '../../agnostic/css/clss/index.js';
4
- import { mergeClassNames, useChangeDispatch } from '../utils/index.js';
5
+ import { mergeClassNames } from '../../agnostic/css/merge-class-names/index.js';
5
6
  import { lightbox as publicClassName } from '../public-classnames.js';
6
- import { close as closeLightbox, getState, membersOf, open as openLightbox, register, soloGroupOf, step, subscribe, unregister } from './store.js';
7
+ import { closeLightbox, getGroupLightboxes, getLightboxesStoreState, navigateOpenGroupBy, openLightbox, registerLightbox, unregisterLightbox, watchLightboxesStoreState } from './store.js';
7
8
  import cssModule from './styles.module.css';
8
9
  /**
9
10
  * Lightbox component. Holds content in a box that a stylesheet lifts over the page
10
11
  * when opened.
11
12
  *
12
13
  * ### CSS modifiers
13
- * - `on` - this member is open.
14
+ * - `on` - this lightbox is open.
14
15
  * - `off` - it is not.
15
16
  * - `grouped` - it belongs to a group it did not invent.
16
17
  * - `navigable` - that group holds someone else, so the previous and next controls
@@ -49,9 +50,9 @@ import cssModule from './styles.module.css';
49
50
  * the popover API - would do the opposite, being document-wide and above
50
51
  * everything, and paint over whichever article is being read.
51
52
  * - **Groups are held outside React**, in `./store.ts`: a consumer may render each
52
- * component in a root of its own, in which case members of a group share no tree
53
- * and a context could not reach from one to the other.
54
- * - Every member holds its own backdrop; only the open one is lifted over the page.
53
+ * component in a root of its own, in which case the lightboxes of a group share no
54
+ * tree and a context could not reach from one to the other.
55
+ * - Every lightbox holds its own backdrop; only the open one is lifted over the page.
55
56
  * Navigating a group therefore lowers one and lifts another, and neither one's
56
57
  * content is touched.
57
58
  * - This component ships **no appearance**: covering the page, the backdrop and the
@@ -66,26 +67,27 @@ export const Lightbox = ({ group, closeBtnContent, openBtnContent, prevBtnConten
66
67
  const sizeRef = useRef(null);
67
68
  const [hasAppliedDefault, setHasAppliedDefault] = useState(false);
68
69
  const isControlled = isOnProp !== undefined;
69
- const declaredGroups = group === undefined
70
- ? []
71
- : (Array.isArray(group) ? group : [group]);
70
+ const declaredGroups = group === undefined ? [] : (Array.isArray(group) ? group : [group]);
72
71
  const isGrouped = declaredGroups.length > 0;
73
- // A member without a group still needs one to be opened through: its own.
74
- const groups = isGrouped ? declaredGroups : [soloGroupOf(id)];
75
- // `groups` is never empty: a member without a declared group gets its own.
72
+ const groups = isGrouped ? declaredGroups : [`solo:${id}`];
76
73
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
77
- const ownGroup = groups[0];
74
+ const primaryGroup = groups[0];
78
75
  const groupsKey = groups.join(' ');
79
- const storeState = useSyncExternalStore(subscribe, getState, getState);
80
- const isOn = isOnProp ?? (storeState.openMemberId === id);
76
+ const [storeState, setStoreState] = useState(getLightboxesStoreState);
77
+ // Fx. dep. none - The read catches a store that moved before this effect ran.
78
+ useEffect(() => {
79
+ setStoreState(getLightboxesStoreState());
80
+ return watchLightboxesStoreState(() => setStoreState(getLightboxesStoreState()));
81
+ }, []);
82
+ const isOn = isOnProp ?? (storeState.openLightboxId === id);
81
83
  const openGroup = storeState.openGroup;
82
- const siblingsCount = isOn && openGroup !== null ? membersOf(openGroup).length : 0;
83
- // Fx. dep. id, groupsKey - Joins the shared registry, which is what lets another
84
- // member reach this one. `getElement` rather than the element itself: the registry
84
+ const openGroupSize = isOn && openGroup !== null ? getGroupLightboxes(openGroup).length : 0;
85
+ // Fx. dep. id, groupsKey - Joins the store, which is what lets another lightbox of
86
+ // the group reach this one. `getElement` rather than the element itself: the store
85
87
  // reads it when it needs to order the group, and never holds it.
86
88
  useEffect(() => {
87
- register({ id, groups, getElement: () => rootRef.current });
88
- return () => unregister(id);
89
+ registerLightbox({ id, groups, getElement: () => rootRef.current });
90
+ return () => unregisterLightbox(id);
89
91
  }, [id, groupsKey]);
90
92
  // Keeps the last size the content had in place, so the placeholder can hold it once
91
93
  // the content is gone. Measured while closed only, on render rather than through a
@@ -103,13 +105,13 @@ export const Lightbox = ({ group, closeBtnContent, openBtnContent, prevBtnConten
103
105
  sizeRef.current = { width, height };
104
106
  });
105
107
  // State dispatch
106
- useChangeDispatch(isOn, onIsOnChanged);
108
+ useStateChangeDispatch(isOn, onIsOnChanged);
107
109
  // User action handlers
108
110
  const requestOpen = useCallback(() => {
109
111
  if (isControlled)
110
112
  return;
111
- openLightbox(ownGroup, id);
112
- }, [isControlled, ownGroup, id]);
113
+ openLightbox(primaryGroup, id);
114
+ }, [isControlled, primaryGroup, id]);
113
115
  const requestClose = useCallback(() => {
114
116
  if (isControlled)
115
117
  return;
@@ -125,11 +127,11 @@ export const Lightbox = ({ group, closeBtnContent, openBtnContent, prevBtnConten
125
127
  };
126
128
  const handlePrevButtonClick = () => {
127
129
  onPrevButtonClicked?.(isOn);
128
- step(-1);
130
+ navigateOpenGroupBy(-1);
129
131
  };
130
132
  const handleNextButtonClick = () => {
131
133
  onNextButtonClicked?.(isOn);
132
- step(1);
134
+ navigateOpenGroupBy(1);
133
135
  };
134
136
  const handleContentClick = () => {
135
137
  if (openOnClick !== true || isOn)
@@ -160,42 +162,42 @@ export const Lightbox = ({ group, closeBtnContent, openBtnContent, prevBtnConten
160
162
  window.addEventListener('keydown', handleKeyDown);
161
163
  return () => window.removeEventListener('keydown', handleKeyDown);
162
164
  }, [exitOnEscape, isOn, requestClose, onEscapePressed]);
163
- // Fx. dep. navOnArrowKeys, isOn, siblingsCount, onPrevArrowPressed,
165
+ // Fx. dep. navOnArrowKeys, isOn, openGroupSize, onPrevArrowPressed,
164
166
  // onNextArrowPressed - The arrow keys walk the open group. Bound only while the
165
167
  // lightbox is open and the group holds someone else, so the keys stay the page's the
166
168
  // rest of the time.
167
169
  useEffect(() => {
168
- if (navOnArrowKeys !== true || !isOn || siblingsCount < 2)
170
+ if (navOnArrowKeys !== true || !isOn || openGroupSize < 2)
169
171
  return;
170
172
  const handleKeyDown = (e) => {
171
173
  if (e.key === 'ArrowLeft') {
172
174
  onPrevArrowPressed?.(isOn);
173
- step(-1);
175
+ navigateOpenGroupBy(-1);
174
176
  }
175
177
  else if (e.key === 'ArrowRight') {
176
178
  onNextArrowPressed?.(isOn);
177
- step(1);
179
+ navigateOpenGroupBy(1);
178
180
  }
179
181
  };
180
182
  window.addEventListener('keydown', handleKeyDown);
181
183
  return () => window.removeEventListener('keydown', handleKeyDown);
182
- }, [navOnArrowKeys, isOn, siblingsCount, onPrevArrowPressed, onNextArrowPressed]);
183
- // Fx. dep. hasAppliedDefault, isControlled, defaultIsOn, ownGroup, id - An
184
+ }, [navOnArrowKeys, isOn, openGroupSize, onPrevArrowPressed, onNextArrowPressed]);
185
+ // Fx. dep. hasAppliedDefault, isControlled, defaultIsOn, primaryGroup, id - An
184
186
  // uncontrolled lightbox asked to start open says so once, and never again: the
185
187
  // registry owns the state from then on.
186
188
  useEffect(() => {
187
189
  if (hasAppliedDefault || isControlled || !defaultIsOn)
188
190
  return;
189
191
  setHasAppliedDefault(true);
190
- openLightbox(ownGroup, id);
191
- }, [hasAppliedDefault, isControlled, defaultIsOn, ownGroup, id]);
192
+ openLightbox(primaryGroup, id);
193
+ }, [hasAppliedDefault, isControlled, defaultIsOn, primaryGroup, id]);
192
194
  // Rendering
193
195
  const c = clss(publicClassName, { cssModule });
194
196
  const rootClss = mergeClassNames(c(null, {
195
197
  on: isOn,
196
198
  off: !isOn,
197
199
  grouped: isGrouped,
198
- navigable: siblingsCount > 1,
200
+ navigable: openGroupSize > 1,
199
201
  'open-on-click': openOnClick === true && !isOn
200
202
  }), className);
201
203
  const size = sizeRef.current;
@@ -1,43 +1,9 @@
1
- /**
2
- * The registry of lightbox groups, shared by every {@link Lightbox} on the page.
3
- *
4
- * Deliberately outside React. lm-link renders each component in its **own React
5
- * root** — `renderInTarget` calls `createRoot` per component — so two `<lm-image>`
6
- * in the same article never share a tree, and a context could not reach from one to
7
- * the other. A module-level store can.
8
- *
9
- * Members are kept unordered here; the order that matters is the **document's**, and
10
- * it is read from the DOM when a group opens. React's mount order is not it.
11
- */
12
- /** What a member hands the store so the group can find and place it. */
13
- export type LightboxMember = {
14
- id: string;
15
- groups: string[];
16
- /** The member's anchor in the page, for ordering. `null` before it mounts. */
17
- getElement: () => HTMLElement | null;
18
- };
19
- export type LightboxState = {
20
- /** The group currently open, `null` when nothing is. */
21
- openGroup: string | null;
22
- /** The member currently shown. */
23
- openMemberId: string | null;
24
- };
25
- export declare function subscribe(listener: () => void): () => void;
26
- export declare function getState(): LightboxState;
27
- /** A member's own group, when it declared none: it is a group of one. */
28
- export declare function soloGroupOf(id: string): string;
29
- export declare function register(member: LightboxMember): void;
30
- export declare function unregister(id: string): void;
31
- /**
32
- * The members of a group, in **document order**.
33
- *
34
- * `compareDocumentPosition` rather than mount order: React mounts a tree in its own
35
- * order, and lm-link mounts each component in a root of its own, so nothing
36
- * guarantees the two match. Only runs when a group opens or navigates, over a
37
- * handful of members.
38
- */
39
- export declare function membersOf(group: string): LightboxMember[];
40
- export declare function open(group: string, memberId: string): void;
41
- export declare function close(): void;
42
- /** Moves to another member of the open group by `offset`, clamped at both ends. */
43
- export declare function step(offset: number): void;
1
+ import type { LightboxesStoreState, RegisteredLightbox } from './types.js';
2
+ export declare const getLightboxesStoreState: () => LightboxesStoreState;
3
+ export declare function watchLightboxesStoreState(onStoreStateChange: () => void): () => void;
4
+ export declare function registerLightbox(lightbox: RegisteredLightbox): void;
5
+ export declare function unregisterLightbox(lightboxId: string): void;
6
+ export declare function getGroupLightboxes(group: string): RegisteredLightbox[];
7
+ export declare function openLightbox(group: string, lightboxId: string): void;
8
+ export declare function closeLightbox(): void;
9
+ export declare function navigateOpenGroupBy(offset: number): void;