@mdbase-dev/ui 0.1.0-beta.125 → 0.1.0-beta.147

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 (77) hide show
  1. package/README.md +159 -0
  2. package/css/brand.css +10 -10
  3. package/css/collections.css +978 -0
  4. package/css/controls.css +220 -43
  5. package/css/feedback.css +20 -20
  6. package/css/fonts.css +2 -3
  7. package/css/palette.css +10 -10
  8. package/css/screens.css +58 -16
  9. package/css/tokens.css +43 -3
  10. package/dist/AppSwitcher.js +187 -33
  11. package/dist/Checkbox.d.ts +14 -0
  12. package/dist/Checkbox.js +32 -0
  13. package/dist/CommandPalette.d.ts +4 -2
  14. package/dist/CommandPalette.js +275 -103
  15. package/dist/Dialog.js +72 -25
  16. package/dist/Feedback.js +763 -229
  17. package/dist/FeedbackMarkup.d.ts +1 -1
  18. package/dist/FeedbackMarkup.js +326 -125
  19. package/dist/FeedbackVerification.js +115 -68
  20. package/dist/SaveNotice.js +71 -28
  21. package/dist/Select.js +183 -135
  22. package/dist/SelectList.js +59 -14
  23. package/dist/ThemeSelect.js +55 -4
  24. package/dist/apps.d.ts +1 -0
  25. package/dist/apps.js +50 -29
  26. package/dist/brand.js +251 -68
  27. package/dist/codemirror.js +116 -87
  28. package/dist/collections/CollectionGate.d.ts +52 -0
  29. package/dist/collections/CollectionGate.js +456 -0
  30. package/dist/collections/CollectionSaveWarning.d.ts +10 -0
  31. package/dist/collections/CollectionSaveWarning.js +28 -0
  32. package/dist/collections/CollectionSwitcher.d.ts +26 -0
  33. package/dist/collections/CollectionSwitcher.js +579 -0
  34. package/dist/collections/SetupReview.d.ts +9 -0
  35. package/dist/collections/SetupReview.js +175 -0
  36. package/dist/collections/copy.d.ts +100 -0
  37. package/dist/collections/copy.js +937 -0
  38. package/dist/collections/dialogs.d.ts +27 -0
  39. package/dist/collections/dialogs.js +169 -0
  40. package/dist/collections/model.d.ts +146 -0
  41. package/dist/collections/model.js +337 -0
  42. package/dist/collections/mount.d.ts +18 -0
  43. package/dist/collections/mount.js +56 -0
  44. package/dist/collections/parts.d.ts +86 -0
  45. package/dist/collections/parts.js +439 -0
  46. package/dist/collections/testing.d.ts +19 -0
  47. package/dist/collections/testing.js +39 -0
  48. package/dist/collections/use-collection-ui.d.ts +140 -0
  49. package/dist/collections/use-collection-ui.js +236 -0
  50. package/dist/collections.d.ts +19 -0
  51. package/dist/collections.js +13 -0
  52. package/dist/feedback-data.d.ts +13 -12
  53. package/dist/feedback-data.js +365 -168
  54. package/dist/flock.d.ts +63 -0
  55. package/dist/flock.js +140 -0
  56. package/dist/i18n.d.ts +6 -0
  57. package/dist/i18n.js +71 -0
  58. package/dist/locales/de/messages.json +1 -0
  59. package/dist/locales/en/messages.json +1 -0
  60. package/dist/locales/es/messages.json +1 -0
  61. package/dist/locales/fr/messages.json +1 -0
  62. package/dist/locales/it/messages.json +1 -0
  63. package/dist/locales/ja/messages.json +1 -0
  64. package/dist/locales/ko/messages.json +1 -0
  65. package/dist/locales/pt/messages.json +1 -0
  66. package/dist/locales/ru/messages.json +1 -0
  67. package/dist/locales/zh/messages.json +1 -0
  68. package/dist/mark-activity.js +82 -75
  69. package/dist/permission-selection.js +27 -17
  70. package/dist/popover.js +138 -130
  71. package/dist/screens.d.ts +20 -3
  72. package/dist/screens.js +275 -6
  73. package/dist/select-model.js +34 -28
  74. package/dist/text-diff.d.ts +1 -0
  75. package/dist/text-diff.js +106 -74
  76. package/dist/theme.js +34 -40
  77. package/package.json +53 -8
@@ -4,18 +4,63 @@ import { isOptionGroup, itemOffsets } from "./select-model.js";
4
4
  * The listbox half of a select. Focus stays on the listbox, which names its active option with
5
5
  * aria-activedescendant, so options take pointer input only; the keyboard goes to the list.
6
6
  */
7
- export function SelectList({ id, listRef, items, selected, active, open, label, labelledBy, onActivate, onChoose, onKeyDown, }) {
8
- const offsets = itemOffsets(items);
9
- const option = (value, index) => (_jsx("div", { id: `${id}-option-${String(index)}`, "data-index": index, "data-value": value.value, role: "option", "aria-selected": index === selected, "aria-disabled": value.disabled ? true : undefined, className: index === active ? "mdbase-select-option is-active" : "mdbase-select-option", onPointerMove: () => {
10
- if (!value.disabled && index !== active) {
11
- onActivate(index);
12
- }
13
- }, onClick: () => onChoose(index), children: value.label }, value.value));
14
- return (_jsx("div", { ref: listRef, id: `${id}-list`, className: "mdbase-select-list", popover: "manual", role: "listbox", tabIndex: -1, "aria-label": label, "aria-labelledby": labelledBy, "aria-activedescendant": open && active >= 0 ? `${id}-option-${String(active)}` : undefined, onKeyDown: onKeyDown, onClick: (event) => {
15
- event.stopPropagation();
16
- event.preventDefault();
17
- }, onDoubleClick: (event) => event.stopPropagation(), children: items.map((item, itemIndex) => {
18
- const first = offsets[itemIndex] ?? 0;
19
- return isOptionGroup(item) ? (_jsxs("div", { role: "group", "aria-label": item.label, children: [_jsx("div", { className: "mdbase-select-group", "aria-hidden": "true", children: item.label }), item.options.map((value, offset) => option(value, first + offset))] }, `group:${item.label}`)) : (option(item, first));
20
- }) }));
7
+ export function SelectList({
8
+ id,
9
+ listRef,
10
+ items,
11
+ selected,
12
+ active,
13
+ open,
14
+ label,
15
+ labelledBy,
16
+ onActivate,
17
+ onChoose,
18
+ onKeyDown
19
+ }) {
20
+ const offsets = itemOffsets(items);
21
+ const option = (value, index) => _jsx("div", {
22
+ id: `${id}-option-${String(index)}`,
23
+ "data-index": index,
24
+ "data-value": value.value,
25
+ role: "option",
26
+ "aria-selected": index === selected,
27
+ "aria-disabled": value.disabled ? true : undefined,
28
+ className: index === active ? "mdbase-select-option is-active" : "mdbase-select-option",
29
+ onPointerMove: () => {
30
+ if (!value.disabled && index !== active) {
31
+ onActivate(index);
32
+ }
33
+ },
34
+ onClick: () => onChoose(index),
35
+ children: value.label
36
+ }, value.value);
37
+ return _jsx("div", {
38
+ ref: listRef,
39
+ id: `${id}-list`,
40
+ className: "mdbase-select-list",
41
+ popover: "manual",
42
+ role: "listbox",
43
+ tabIndex: -1,
44
+ "aria-label": label,
45
+ "aria-labelledby": labelledBy,
46
+ "aria-activedescendant": open && active >= 0 ? `${id}-option-${String(active)}` : undefined,
47
+ onKeyDown: onKeyDown,
48
+ onClick: event => {
49
+ event.stopPropagation();
50
+ event.preventDefault();
51
+ },
52
+ onDoubleClick: event => event.stopPropagation(),
53
+ children: items.map((item, itemIndex) => {
54
+ const first = offsets[itemIndex] ?? 0;
55
+ return isOptionGroup(item) ? _jsxs("div", {
56
+ role: "group",
57
+ "aria-label": item.label,
58
+ children: [_jsx("div", {
59
+ className: "mdbase-select-group",
60
+ "aria-hidden": "true",
61
+ children: item.label
62
+ }), item.options.map((value, offset) => option(value, first + offset))]
63
+ }, `group:${item.label}`) : option(item, first);
64
+ })
65
+ });
21
66
  }
@@ -1,12 +1,63 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { i18n as _i18n } from "@lingui/core";
3
+ import { useLocaleRevision } from "./i18n.js";
2
4
  import { Select } from "./Select.js";
3
5
  import { themePreferences } from "./theme.js";
4
- const themeLabels = { system: "System", light: "Light", dark: "Dark" };
5
- const themeOptions = themePreferences.map((value) => ({ value, label: themeLabels[value] }));
6
+ const themeLabels = {
7
+ get system() {
8
+ return _i18n._(
9
+ /** i18n */
10
+ {
11
+ id: "XAe57G",
12
+ message: "System",
13
+ context: "Color theme"
14
+ });
15
+ },
16
+ get light() {
17
+ return _i18n._(
18
+ /** i18n */
19
+ {
20
+ id: "EbTBqo",
21
+ message: "Light",
22
+ context: "Color theme"
23
+ });
24
+ },
25
+ get dark() {
26
+ return _i18n._(
27
+ /** i18n */
28
+ {
29
+ id: "FPIkyE",
30
+ message: "Dark",
31
+ context: "Color theme"
32
+ });
33
+ }
34
+ };
35
+ const themeOptions = () => themePreferences.map(value => ({
36
+ value,
37
+ label: themeLabels[value]
38
+ }));
6
39
  /**
7
40
  * The one control for choosing System, Light or Dark. The app owns the preference (with
8
41
  * loadThemePreference and saveThemePreference) because commands and menus may change it too.
9
42
  */
10
- export function ThemeSelect({ value, onChange, className, label = "Color theme" }) {
11
- return _jsx(Select, { "aria-label": label, className: className, value: value, options: themeOptions, onChange: onChange });
43
+ export function ThemeSelect({
44
+ value,
45
+ onChange,
46
+ className,
47
+ label = _i18n._(
48
+ /** i18n */
49
+ {
50
+ id: "IspNTR",
51
+ message: "Color theme",
52
+ context: "Color theme"
53
+ })
54
+ }) {
55
+ useLocaleRevision();
56
+ return _jsx(Select, {
57
+ "aria-label": label,
58
+ className: className,
59
+ value: value,
60
+ options: themeOptions(),
61
+ onChange: onChange
62
+ });
12
63
  }
package/dist/apps.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import "./i18n.js";
1
2
  /**
2
3
  * The mdbase apps a collection can be opened in. Editor is the canonical app for a
3
4
  * collection; Reader and Writer serve particular workflows on the same records.
package/dist/apps.js CHANGED
@@ -1,32 +1,54 @@
1
- export const mdbaseApps = [
1
+ import { i18n as _i18n } from "@lingui/core";
2
+ import "./i18n.js";
3
+ export const mdbaseApps = [{
4
+ id: "editor",
5
+ name: "Editor",
6
+ get description() {
7
+ return _i18n._(
8
+ /** i18n */
2
9
  {
3
- id: "editor",
4
- name: "Editor",
5
- description: "Browse and edit records",
6
- url: "https://editor.mdbase.dev/"
7
- },
10
+ id: "IseQAg",
11
+ message: "Browse and edit records"
12
+ });
13
+ },
14
+ url: "https://editor.mdbase.dev/"
15
+ }, {
16
+ id: "reader",
17
+ name: "Reader",
18
+ get description() {
19
+ return _i18n._(
20
+ /** i18n */
8
21
  {
9
- id: "reader",
10
- name: "Reader",
11
- description: "Read and annotate sources",
12
- url: "https://reader.mdbase.dev/"
13
- },
22
+ id: "kHiGeJ",
23
+ message: "Read and annotate sources"
24
+ });
25
+ },
26
+ url: "https://reader.mdbase.dev/"
27
+ }, {
28
+ id: "writer",
29
+ name: "Writer",
30
+ get description() {
31
+ return _i18n._(
32
+ /** i18n */
14
33
  {
15
- id: "writer",
16
- name: "Writer",
17
- description: "Write manuscripts that cite your sources",
18
- url: "https://writer.mdbase.dev/"
19
- }
20
- ];
34
+ id: "wv1f8v",
35
+ message: "Write manuscripts that cite your sources"
36
+ });
37
+ },
38
+ url: "https://writer.mdbase.dev/"
39
+ }];
21
40
  /**
22
41
  * Replaces app URLs for a lab or local build, for example from
23
42
  * `import.meta.env.VITE_MDBASE_WRITER_URL`. Unset entries keep production.
24
43
  */
25
44
  export function withAppUrls(urls, apps = mdbaseApps) {
26
- return apps.map((app) => {
27
- const url = urls[app.id];
28
- return url ? { ...app, url } : app;
29
- });
45
+ return apps.map(app => {
46
+ const url = urls[app.id];
47
+ return url ? {
48
+ ...app,
49
+ url
50
+ } : app;
51
+ });
30
52
  }
31
53
  /**
32
54
  * Parameters every mdbase app reads the same way: `collection` is the Connect SDK's
@@ -36,12 +58,11 @@ export function withAppUrls(urls, apps = mdbaseApps) {
36
58
  const sharedParameters = ["collection", "server"];
37
59
  /** A link that opens the current page's collection in another app. */
38
60
  export function mdbaseAppHref(appUrl, currentHref) {
39
- const target = new URL(appUrl);
40
- const current = new URL(currentHref);
41
- for (const key of sharedParameters) {
42
- const value = current.searchParams.get(key);
43
- if (value)
44
- target.searchParams.set(key, value);
45
- }
46
- return target.href;
61
+ const target = new URL(appUrl);
62
+ const current = new URL(currentHref);
63
+ for (const key of sharedParameters) {
64
+ const value = current.searchParams.get(key);
65
+ if (value) target.searchParams.set(key, value);
66
+ }
67
+ return target.href;
47
68
  }
package/dist/brand.js CHANGED
@@ -1,63 +1,134 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useId, useRef, useState } from "react";
3
3
  /** Indeterminate waits. Each repeats until the motion is removed. */
4
- export const mdbaseMarkLoops = [
5
- "orbit",
6
- "scan",
7
- "bounce",
8
- "stream",
9
- "sort",
10
- "hop"
11
- ];
4
+ export const mdbaseMarkLoops = ["orbit", "scan", "bounce", "stream", "sort", "hop"];
12
5
  /** Played once when the mark appears; the mark then rests. */
13
- export const mdbaseMarkEntrances = [
14
- "keys-first",
15
- "drop",
16
- "assemble"
17
- ];
6
+ export const mdbaseMarkEntrances = ["keys-first", "drop", "assemble"];
18
7
  export const mdbaseMarkMotions = [...mdbaseMarkLoops, ...mdbaseMarkEntrances];
19
8
  /** Long enough for each entrance in `motion.css` to finish. */
20
9
  const ENTRANCE_MS = 1300;
21
10
  export function mdbaseMarkMotionClass(motion) {
22
- return motion ? ` mdbase-motion-${motion}` : "";
11
+ return motion ? ` mdbase-motion-${motion}` : "";
23
12
  }
24
13
  function isEntrance(motion) {
25
- return mdbaseMarkEntrances.includes(motion ?? "");
14
+ return mdbaseMarkEntrances.includes(motion ?? "");
26
15
  }
27
16
  export const MDBASE_MARK_VIEW_BOX = "18 18 84 84";
28
- export const mdbaseMarkInkRects = [
29
- { x: 22, y: 22, width: 20, height: 10 },
30
- { x: 50, y: 22, width: 20, height: 10 },
31
- { x: 78, y: 22, width: 20, height: 10 },
32
- { x: 22, y: 44, width: 12, height: 10 },
33
- { x: 22, y: 66, width: 28, height: 10 },
34
- { x: 58, y: 66, width: 40, height: 10 },
35
- { x: 22, y: 88, width: 20, height: 10 },
36
- { x: 50, y: 88, width: 20, height: 10 },
37
- { x: 78, y: 88, width: 20, height: 10 }
38
- ];
17
+ export const mdbaseMarkInkRects = [{
18
+ x: 22,
19
+ y: 22,
20
+ width: 20,
21
+ height: 10
22
+ }, {
23
+ x: 50,
24
+ y: 22,
25
+ width: 20,
26
+ height: 10
27
+ }, {
28
+ x: 78,
29
+ y: 22,
30
+ width: 20,
31
+ height: 10
32
+ }, {
33
+ x: 22,
34
+ y: 44,
35
+ width: 12,
36
+ height: 10
37
+ }, {
38
+ x: 22,
39
+ y: 66,
40
+ width: 28,
41
+ height: 10
42
+ }, {
43
+ x: 58,
44
+ y: 66,
45
+ width: 40,
46
+ height: 10
47
+ }, {
48
+ x: 22,
49
+ y: 88,
50
+ width: 20,
51
+ height: 10
52
+ }, {
53
+ x: 50,
54
+ y: 88,
55
+ width: 20,
56
+ height: 10
57
+ }, {
58
+ x: 78,
59
+ y: 88,
60
+ width: 20,
61
+ height: 10
62
+ }];
39
63
  export const mdbaseMarkAccentRect = {
40
- x: 42,
41
- y: 44,
42
- width: 56,
43
- height: 10
64
+ x: 42,
65
+ y: 44,
66
+ width: 56,
67
+ height: 10
44
68
  };
45
69
  const lineYs = [22, 44, 66, 88];
46
70
  const orbitXs = [-34, -6, 22, 50, 78, 106];
47
71
  function classes(...names) {
48
- return names.filter(Boolean).join(" ");
72
+ return names.filter(Boolean).join(" ");
49
73
  }
50
74
  /** How much of each line is filled when `progress` of the whole is done, in reading order. */
51
75
  export function mdbaseMarkLineFill(progress) {
52
- const clamped = Math.min(1, Math.max(0, progress));
53
- return lineYs.map((_, line) => Math.min(1, Math.max(0, clamped * lineYs.length - line)));
76
+ const clamped = Math.min(1, Math.max(0, progress));
77
+ return lineYs.map((_, line) => Math.min(1, Math.max(0, clamped * lineYs.length - line)));
54
78
  }
55
- function Segment({ rect, index, accent = false }) {
56
- return _jsx("rect", { className: `mdbase-mark-segment mdbase-mark-segment-${index} ${accent ? "mdbase-mark-accent" : "mdbase-mark-ink"}`, ...rect });
79
+ function Segment({
80
+ rect,
81
+ index,
82
+ accent = false
83
+ }) {
84
+ return _jsx("rect", {
85
+ className: `mdbase-mark-segment mdbase-mark-segment-${index} ${accent ? "mdbase-mark-accent" : "mdbase-mark-ink"}`,
86
+ ...rect
87
+ });
57
88
  }
58
- function Lines({ clipRows }) {
59
- const rows = _jsxs(_Fragment, { children: [_jsxs("g", { className: "mdbase-mark-row mdbase-mark-row-top", children: [_jsx(Segment, { rect: mdbaseMarkInkRects[3], index: 4 }), _jsx(Segment, { rect: mdbaseMarkAccentRect, index: 5, accent: true })] }), _jsxs("g", { className: "mdbase-mark-row mdbase-mark-row-bottom", children: [_jsx(Segment, { rect: mdbaseMarkInkRects[4], index: 6 }), _jsx(Segment, { rect: mdbaseMarkInkRects[5], index: 7 })] })] });
60
- return _jsxs(_Fragment, { children: [_jsx("g", { className: "mdbase-mark-fence mdbase-mark-fence-top", children: mdbaseMarkInkRects.slice(0, 3).map((rect, index) => _jsx(Segment, { rect: rect, index: index + 1 }, `${rect.x}-${rect.y}`)) }), clipRows ? _jsx("g", { clipPath: `url(#${clipRows})`, children: rows }) : rows, _jsx("g", { className: "mdbase-mark-fence mdbase-mark-fence-bottom", children: mdbaseMarkInkRects.slice(6).map((rect, index) => _jsx(Segment, { rect: rect, index: index + 8 }, `${rect.x}-${rect.y}`)) })] });
89
+ function Lines({
90
+ clipRows
91
+ }) {
92
+ const rows = _jsxs(_Fragment, {
93
+ children: [_jsxs("g", {
94
+ className: "mdbase-mark-row mdbase-mark-row-top",
95
+ children: [_jsx(Segment, {
96
+ rect: mdbaseMarkInkRects[3],
97
+ index: 4
98
+ }), _jsx(Segment, {
99
+ rect: mdbaseMarkAccentRect,
100
+ index: 5,
101
+ accent: true
102
+ })]
103
+ }), _jsxs("g", {
104
+ className: "mdbase-mark-row mdbase-mark-row-bottom",
105
+ children: [_jsx(Segment, {
106
+ rect: mdbaseMarkInkRects[4],
107
+ index: 6
108
+ }), _jsx(Segment, {
109
+ rect: mdbaseMarkInkRects[5],
110
+ index: 7
111
+ })]
112
+ })]
113
+ });
114
+ return _jsxs(_Fragment, {
115
+ children: [_jsx("g", {
116
+ className: "mdbase-mark-fence mdbase-mark-fence-top",
117
+ children: mdbaseMarkInkRects.slice(0, 3).map((rect, index) => _jsx(Segment, {
118
+ rect: rect,
119
+ index: index + 1
120
+ }, `${rect.x}-${rect.y}`))
121
+ }), clipRows ? _jsx("g", {
122
+ clipPath: `url(#${clipRows})`,
123
+ children: rows
124
+ }) : rows, _jsx("g", {
125
+ className: "mdbase-mark-fence mdbase-mark-fence-bottom",
126
+ children: mdbaseMarkInkRects.slice(6).map((rect, index) => _jsx(Segment, {
127
+ rect: rect,
128
+ index: index + 8
129
+ }, `${rect.x}-${rect.y}`))
130
+ })]
131
+ });
61
132
  }
62
133
  /**
63
134
  * The Frontmatter mark. Without `app` it is the platform's own; with `app` it is
@@ -67,41 +138,153 @@ function Lines({ clipRows }) {
67
138
  * whatever else is running, and `progress` (0 to 1) fills the lines in reading
68
139
  * order over a faint copy of the mark. Animations live in `motion.css`.
69
140
  */
70
- export function MdbaseMark({ app, motion, signal, progress, className }) {
71
- const uid = useId().replaceAll(":", "");
72
- // An entrance plays once per mark. Dropping its class afterwards stops it replaying when a signal
73
- // remounts the lines, and remembering it stops a replay when a loop or progress hands back to it.
74
- const [entering, setEntering] = useState(isEntrance(motion));
75
- const played = useRef(undefined);
76
- useEffect(() => {
77
- if (!isEntrance(motion) || played.current === motion)
78
- return setEntering(false);
79
- setEntering(true);
80
- const timer = setTimeout(() => {
81
- played.current = motion;
82
- setEntering(false);
83
- }, ENTRANCE_MS);
84
- return () => clearTimeout(timer);
85
- }, [motion]);
86
- const showsProgress = typeof progress === "number" && Number.isFinite(progress);
87
- const active = signal ? undefined
88
- : showsProgress ? undefined
89
- : isEntrance(motion) ? (entering ? motion : undefined)
90
- : motion;
91
- const lines = _jsx(Lines, { clipRows: active === "stream" ? `mdbase-rows-${uid}` : undefined });
92
- return _jsxs("svg", { className: classes("mdbase-mark", app && "mdbase-app-mark", app && `is-${app}`, className, active && `mdbase-motion-${active}`, signal && `mdbase-signal-${signal.kind}`, showsProgress && "mdbase-mark-progress", !active && !signal && !showsProgress && "mdbase-mark-at-rest"), viewBox: MDBASE_MARK_VIEW_BOX, "aria-hidden": "true", focusable: "false", children: [active === "stream" && _jsx("defs", { children: _jsx("clipPath", { id: `mdbase-rows-${uid}`, children: _jsx("rect", { x: "22", y: "40", width: "76", height: "40" }) }) }), active === "orbit" && _jsxs("defs", { children: [_jsx("clipPath", { id: `mdbase-fence-top-${uid}`, children: _jsx("rect", { x: "22", y: "22", width: "76", height: "10" }) }), _jsx("clipPath", { id: `mdbase-fence-bottom-${uid}`, children: _jsx("rect", { x: "22", y: "88", width: "76", height: "10" }) })] }), showsProgress
93
- ? _jsxs(_Fragment, { children: [_jsx("defs", { children: _jsx("clipPath", { id: `mdbase-progress-${uid}`, children: mdbaseMarkLineFill(progress).map((fill, line) => _jsx("rect", { className: "mdbase-mark-progress-fill", x: "22", y: lineYs[line] - 1, height: "12", style: { width: `${(76 * fill).toFixed(2)}px` } }, line)) }) }), _jsx("g", { className: "mdbase-mark-progress-ghost", children: _jsx(Lines, {}) }), _jsx("g", { clipPath: `url(#mdbase-progress-${uid})`, children: _jsx(Lines, {}) })] })
94
- // Keyed by the signal so the same reaction can restart without replaying an entrance.
95
- : _jsx("g", { className: "mdbase-mark-lines", children: lines }, signal?.id ?? 0), active === "orbit" && _jsxs(_Fragment, { children: [_jsx("g", { clipPath: `url(#mdbase-fence-top-${uid})`, children: _jsx("g", { className: "mdbase-mark-track mdbase-mark-track-top", children: orbitXs.map((x) => _jsx("rect", { x: x, y: "22", width: "20", height: "10" }, x)) }) }), _jsx("g", { clipPath: `url(#mdbase-fence-bottom-${uid})`, children: _jsx("g", { className: "mdbase-mark-track mdbase-mark-track-bottom", children: orbitXs.map((x) => _jsx("rect", { x: x, y: "88", width: "20", height: "10" }, x)) }) })] })] });
141
+ export function MdbaseMark({
142
+ app,
143
+ motion,
144
+ signal,
145
+ progress,
146
+ className
147
+ }) {
148
+ const uid = useId().replaceAll(":", "");
149
+ // An entrance plays once per mark. Dropping its class afterwards stops it replaying when a signal
150
+ // remounts the lines, and remembering it stops a replay when a loop or progress hands back to it.
151
+ const [entering, setEntering] = useState(isEntrance(motion));
152
+ const played = useRef(undefined);
153
+ useEffect(() => {
154
+ if (!isEntrance(motion) || played.current === motion) return setEntering(false);
155
+ setEntering(true);
156
+ const timer = setTimeout(() => {
157
+ played.current = motion;
158
+ setEntering(false);
159
+ }, ENTRANCE_MS);
160
+ return () => clearTimeout(timer);
161
+ }, [motion]);
162
+ const showsProgress = typeof progress === "number" && Number.isFinite(progress);
163
+ const active = signal ? undefined : showsProgress ? undefined : isEntrance(motion) ? entering ? motion : undefined : motion;
164
+ const lines = _jsx(Lines, {
165
+ clipRows: active === "stream" ? `mdbase-rows-${uid}` : undefined
166
+ });
167
+ return _jsxs("svg", {
168
+ className: classes("mdbase-mark", app && "mdbase-app-mark", app && `is-${app}`, className, active && `mdbase-motion-${active}`, signal && `mdbase-signal-${signal.kind}`, showsProgress && "mdbase-mark-progress", !active && !signal && !showsProgress && "mdbase-mark-at-rest"),
169
+ viewBox: MDBASE_MARK_VIEW_BOX,
170
+ "aria-hidden": "true",
171
+ focusable: "false",
172
+ children: [active === "stream" && _jsx("defs", {
173
+ children: _jsx("clipPath", {
174
+ id: `mdbase-rows-${uid}`,
175
+ children: _jsx("rect", {
176
+ x: "22",
177
+ y: "40",
178
+ width: "76",
179
+ height: "40"
180
+ })
181
+ })
182
+ }), active === "orbit" && _jsxs("defs", {
183
+ children: [_jsx("clipPath", {
184
+ id: `mdbase-fence-top-${uid}`,
185
+ children: _jsx("rect", {
186
+ x: "22",
187
+ y: "22",
188
+ width: "76",
189
+ height: "10"
190
+ })
191
+ }), _jsx("clipPath", {
192
+ id: `mdbase-fence-bottom-${uid}`,
193
+ children: _jsx("rect", {
194
+ x: "22",
195
+ y: "88",
196
+ width: "76",
197
+ height: "10"
198
+ })
199
+ })]
200
+ }), showsProgress ? _jsxs(_Fragment, {
201
+ children: [_jsx("defs", {
202
+ children: _jsx("clipPath", {
203
+ id: `mdbase-progress-${uid}`,
204
+ children: mdbaseMarkLineFill(progress).map((fill, line) => _jsx("rect", {
205
+ className: "mdbase-mark-progress-fill",
206
+ x: "22",
207
+ y: lineYs[line] - 1,
208
+ height: "12",
209
+ style: {
210
+ width: `${(76 * fill).toFixed(2)}px`
211
+ }
212
+ }, line))
213
+ })
214
+ }), _jsx("g", {
215
+ className: "mdbase-mark-progress-ghost",
216
+ children: _jsx(Lines, {})
217
+ }), _jsx("g", {
218
+ clipPath: `url(#mdbase-progress-${uid})`,
219
+ children: _jsx(Lines, {})
220
+ })]
221
+ })
222
+ // Keyed by the signal so the same reaction can restart without replaying an entrance.
223
+ : _jsx("g", {
224
+ className: "mdbase-mark-lines",
225
+ children: lines
226
+ }, signal?.id ?? 0), active === "orbit" && _jsxs(_Fragment, {
227
+ children: [_jsx("g", {
228
+ clipPath: `url(#mdbase-fence-top-${uid})`,
229
+ children: _jsx("g", {
230
+ className: "mdbase-mark-track mdbase-mark-track-top",
231
+ children: orbitXs.map(x => _jsx("rect", {
232
+ x: x,
233
+ y: "22",
234
+ width: "20",
235
+ height: "10"
236
+ }, x))
237
+ })
238
+ }), _jsx("g", {
239
+ clipPath: `url(#mdbase-fence-bottom-${uid})`,
240
+ children: _jsx("g", {
241
+ className: "mdbase-mark-track mdbase-mark-track-bottom",
242
+ children: orbitXs.map(x => _jsx("rect", {
243
+ x: x,
244
+ y: "88",
245
+ width: "20",
246
+ height: "10"
247
+ }, x))
248
+ })
249
+ })]
250
+ })]
251
+ });
96
252
  }
97
253
  /** An app's mark. Kept for callers that only ever draw an app's still mark. */
98
- export function MdbaseAppMark({ app, className }) {
99
- return _jsx(MdbaseMark, { app: app, className: className });
254
+ export function MdbaseAppMark({
255
+ app,
256
+ className
257
+ }) {
258
+ return _jsx(MdbaseMark, {
259
+ app: app,
260
+ className: className
261
+ });
100
262
  }
101
263
  /**
102
264
  * `mdbase` followed by the product's quieter name. Editor carries the platform mark
103
265
  * as the canonical app; Reader and Writer carry their app marks.
104
266
  */
105
- export function Wordmark({ app, motion, signal, progress }) {
106
- return _jsxs("span", { className: "wordmark", children: [_jsx(MdbaseMark, { app: app === "editor" ? undefined : app, motion: motion, signal: signal, progress: progress, className: "wordmark-mark" }), _jsxs("span", { className: "wordmark-label", children: [_jsx("span", { children: "mdbase" }), _jsx("strong", { children: app })] })] });
267
+ export function Wordmark({
268
+ app,
269
+ motion,
270
+ signal,
271
+ progress
272
+ }) {
273
+ return _jsxs("span", {
274
+ className: "wordmark",
275
+ children: [_jsx(MdbaseMark, {
276
+ app: app === "editor" ? undefined : app,
277
+ motion: motion,
278
+ signal: signal,
279
+ progress: progress,
280
+ className: "wordmark-mark"
281
+ }), _jsxs("span", {
282
+ className: "wordmark-label",
283
+ children: [_jsx("span", {
284
+ children: "mdbase"
285
+ }), _jsx("strong", {
286
+ children: app
287
+ })]
288
+ })]
289
+ });
107
290
  }