pixelize-design-library 2.4.2-beta.57 → 2.4.2-beta.59

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 (152) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/COMPONENT-GOTCHAS.md +88 -2
  3. package/DESIGN-GUIDE.md +27 -4
  4. package/dist/Components/AppSwitcher/AppSwitcher.js +5 -1
  5. package/dist/Components/AppSwitcher/AppSwitcherProps.d.ts +2 -0
  6. package/dist/Components/AppSwitcher/AppSwitcherProps.js +7 -4
  7. package/dist/Components/Breadcrumbs/Breadcrumbs.d.ts +1 -1
  8. package/dist/Components/Breadcrumbs/Breadcrumbs.js +33 -10
  9. package/dist/Components/Breadcrumbs/BreadcrumbsProps.d.ts +16 -2
  10. package/dist/Components/Card/Card.d.ts +3 -1
  11. package/dist/Components/Card/Card.js +12 -2
  12. package/dist/Components/Card/CardProps.d.ts +5 -1
  13. package/dist/Components/Commerce/commerceTypes.d.ts +5 -0
  14. package/dist/Components/Editor/Editor.js +50 -2
  15. package/dist/Components/Editor/EditorFieldBar.d.ts +15 -0
  16. package/dist/Components/Editor/EditorFieldBar.js +19 -0
  17. package/dist/Components/Editor/EditorProps.d.ts +74 -1
  18. package/dist/Components/Editor/MakeDynamicDialog.d.ts +12 -0
  19. package/dist/Components/Editor/MakeDynamicDialog.js +59 -0
  20. package/dist/Components/Editor/editorLabels.d.ts +4 -0
  21. package/dist/Components/Editor/editorLabels.js +24 -0
  22. package/dist/Components/Editor/editorTokenStyles.d.ts +28 -0
  23. package/dist/Components/Editor/editorTokenStyles.js +30 -0
  24. package/dist/Components/Editor/editorTokens.d.ts +26 -0
  25. package/dist/Components/Editor/editorTokens.js +134 -0
  26. package/dist/Components/Editor/useEditorBridge.d.ts +40 -0
  27. package/dist/Components/Editor/useEditorBridge.js +141 -0
  28. package/dist/Components/Kbd/Kbd.d.ts +6 -0
  29. package/dist/Components/Kbd/Kbd.js +33 -0
  30. package/dist/Components/Kbd/KbdProps.d.ts +17 -0
  31. package/dist/Components/Kbd/KbdProps.js +2 -0
  32. package/dist/Components/LauncherTrigger/LauncherTrigger.d.ts +4 -0
  33. package/dist/Components/LauncherTrigger/LauncherTrigger.js +41 -0
  34. package/dist/Components/LauncherTrigger/LauncherTriggerProps.d.ts +16 -0
  35. package/dist/Components/LauncherTrigger/LauncherTriggerProps.js +2 -0
  36. package/dist/Components/MediaGallery/MediaGallery.js +1 -1
  37. package/dist/Components/NavLauncher/NavLauncher.d.ts +3 -0
  38. package/dist/Components/NavLauncher/NavLauncher.js +395 -0
  39. package/dist/Components/NavLauncher/NavLauncherProps.d.ts +187 -0
  40. package/dist/Components/NavLauncher/NavLauncherProps.js +2 -0
  41. package/dist/Components/NavLauncher/NewTabLink.d.ts +3 -0
  42. package/dist/Components/NavLauncher/NewTabLink.js +14 -0
  43. package/dist/Components/NavLauncher/PinToggle.d.ts +3 -0
  44. package/dist/Components/NavLauncher/PinToggle.js +21 -0
  45. package/dist/Components/NavLauncher/launcherParts.d.ts +93 -0
  46. package/dist/Components/NavLauncher/launcherParts.js +130 -0
  47. package/dist/Components/NavLauncher/match.d.ts +53 -0
  48. package/dist/Components/NavLauncher/match.js +178 -0
  49. package/dist/Components/NavLauncher/revealStyles.d.ts +4 -0
  50. package/dist/Components/NavLauncher/revealStyles.js +19 -0
  51. package/dist/Components/NavigationBar/NavigationBar.d.ts +1 -1
  52. package/dist/Components/NavigationBar/NavigationBar.js +2 -2
  53. package/dist/Components/NavigationBar/NavigationBarProps.d.ts +2 -0
  54. package/dist/Components/PageHeader/PageHeader.d.ts +1 -1
  55. package/dist/Components/PageHeader/PageHeader.js +9 -2
  56. package/dist/Components/PageHeader/PageHeaderProps.d.ts +8 -0
  57. package/dist/Components/PdfViewer/PdfViewer.d.ts +21 -4
  58. package/dist/Components/PdfViewer/PdfViewer.js +124 -3
  59. package/dist/Components/ProductCard/ProductCard.js +8 -3
  60. package/dist/Components/ProductCard/ProductCardMedia.js +3 -3
  61. package/dist/Components/ProductCard/QuickAddSheet.js +2 -2
  62. package/dist/Components/ProductCard/cardMetrics.d.ts +1 -0
  63. package/dist/Components/ProductCard/cardMetrics.js +3 -2
  64. package/dist/Components/ProductDetails/ProductDetails.d.ts +1 -1
  65. package/dist/Components/ProductDetails/ProductDetails.js +6 -5
  66. package/dist/Components/ProductDetails/ProductDetailsProps.d.ts +2 -0
  67. package/dist/Components/Search/Search.js +2 -1
  68. package/dist/Components/SideBar/SecondaryBar.js +4 -3
  69. package/dist/Components/SideBar/SideBar.d.ts +1 -1
  70. package/dist/Components/SideBar/SideBar.js +30 -9
  71. package/dist/Components/SideBar/SideBarProps.d.ts +32 -0
  72. package/dist/Components/SideBar/components/MenuItemBox.d.ts +6 -0
  73. package/dist/Components/SideBar/components/MenuItemBox.js +8 -4
  74. package/dist/Components/SideBar/components/MenuPopoverContent.js +3 -2
  75. package/dist/Components/SideBar/components/OtherApps.js +2 -1
  76. package/dist/Components/SideBar/components/PinnedSection.d.ts +17 -0
  77. package/dist/Components/SideBar/components/PinnedSection.js +98 -0
  78. package/dist/Components/SideBar/sidebarFocus.d.ts +23 -0
  79. package/dist/Components/SideBar/sidebarFocus.js +20 -0
  80. package/dist/Components/StageProgress/StageProgress.js +2 -2
  81. package/dist/Components/StageProgress/StageProgressProps.d.ts +2 -0
  82. package/dist/Components/StageProgress/StepperStage.js +5 -2
  83. package/dist/Components/WizardDialog/WizardDialog.d.ts +9 -0
  84. package/dist/Components/WizardDialog/WizardDialog.js +102 -0
  85. package/dist/Components/WizardDialog/WizardDialogProps.d.ts +48 -0
  86. package/dist/Components/WizardDialog/WizardDialogProps.js +2 -0
  87. package/dist/Components/WizardDialog/useWizardFlow.d.ts +22 -0
  88. package/dist/Components/WizardDialog/useWizardFlow.js +47 -0
  89. package/dist/Components/WizardDialog/wizardSteps.d.ts +31 -0
  90. package/dist/Components/WizardDialog/wizardSteps.js +40 -0
  91. package/dist/Hooks/useMatchMedia.d.ts +2 -0
  92. package/dist/Hooks/useMatchMedia.js +36 -0
  93. package/dist/Theme/chakra/motion.js +2 -16
  94. package/dist/Utils/price.js +11 -6
  95. package/dist/esm/Components/AppSwitcher/AppSwitcher.js +2 -1
  96. package/dist/esm/Components/AppSwitcher/AppSwitcherProps.js +5 -3
  97. package/dist/esm/Components/Breadcrumbs/Breadcrumbs.js +35 -12
  98. package/dist/esm/Components/Card/Card.js +12 -2
  99. package/dist/esm/Components/Editor/Editor.js +48 -3
  100. package/dist/esm/Components/Editor/EditorFieldBar.js +13 -0
  101. package/dist/esm/Components/Editor/MakeDynamicDialog.js +20 -0
  102. package/dist/esm/Components/Editor/editorLabels.js +20 -0
  103. package/dist/esm/Components/Editor/editorTokenStyles.js +25 -0
  104. package/dist/esm/Components/Editor/editorTokens.js +121 -0
  105. package/dist/esm/Components/Editor/useEditorBridge.js +138 -0
  106. package/dist/esm/Components/Kbd/Kbd.js +28 -0
  107. package/dist/esm/Components/Kbd/KbdProps.js +1 -0
  108. package/dist/esm/Components/LauncherTrigger/LauncherTrigger.js +37 -0
  109. package/dist/esm/Components/LauncherTrigger/LauncherTriggerProps.js +1 -0
  110. package/dist/esm/Components/MediaGallery/MediaGallery.js +1 -1
  111. package/dist/esm/Components/NavLauncher/NavLauncher.js +356 -0
  112. package/dist/esm/Components/NavLauncher/NavLauncherProps.js +1 -0
  113. package/dist/esm/Components/NavLauncher/NewTabLink.js +11 -0
  114. package/dist/esm/Components/NavLauncher/PinToggle.js +18 -0
  115. package/dist/esm/Components/NavLauncher/launcherParts.js +110 -0
  116. package/dist/esm/Components/NavLauncher/match.js +172 -0
  117. package/dist/esm/Components/NavLauncher/revealStyles.js +15 -0
  118. package/dist/esm/Components/NavigationBar/NavigationBar.js +3 -3
  119. package/dist/esm/Components/PageHeader/PageHeader.js +9 -2
  120. package/dist/esm/Components/PdfViewer/PdfViewer.js +123 -2
  121. package/dist/esm/Components/ProductCard/ProductCard.js +9 -4
  122. package/dist/esm/Components/ProductCard/ProductCardMedia.js +3 -3
  123. package/dist/esm/Components/ProductCard/QuickAddSheet.js +2 -2
  124. package/dist/esm/Components/ProductCard/cardMetrics.js +2 -1
  125. package/dist/esm/Components/ProductDetails/ProductDetails.js +6 -5
  126. package/dist/esm/Components/Search/Search.js +3 -2
  127. package/dist/esm/Components/SideBar/SecondaryBar.js +4 -3
  128. package/dist/esm/Components/SideBar/SideBar.js +31 -10
  129. package/dist/esm/Components/SideBar/components/MenuItemBox.js +4 -2
  130. package/dist/esm/Components/SideBar/components/MenuPopoverContent.js +3 -2
  131. package/dist/esm/Components/SideBar/components/OtherApps.js +2 -1
  132. package/dist/esm/Components/SideBar/components/PinnedSection.js +92 -0
  133. package/dist/esm/Components/SideBar/sidebarFocus.js +15 -0
  134. package/dist/esm/Components/StageProgress/StageProgress.js +2 -2
  135. package/dist/esm/Components/StageProgress/StepperStage.js +5 -2
  136. package/dist/esm/Components/WizardDialog/WizardDialog.js +58 -0
  137. package/dist/esm/Components/WizardDialog/WizardDialogProps.js +1 -0
  138. package/dist/esm/Components/WizardDialog/useWizardFlow.js +44 -0
  139. package/dist/esm/Components/WizardDialog/wizardSteps.js +35 -0
  140. package/dist/esm/Hooks/useMatchMedia.js +32 -0
  141. package/dist/esm/Theme/chakra/motion.js +2 -16
  142. package/dist/esm/Utils/price.js +11 -6
  143. package/dist/esm/index.js +7 -0
  144. package/dist/index.d.ts +19 -4
  145. package/dist/index.js +21 -1
  146. package/package.json +1 -1
  147. package/dist/Components/MediaGallery/useMatchMedia.d.ts +0 -2
  148. package/dist/Components/MediaGallery/useMatchMedia.js +0 -20
  149. package/dist/Components/ProductCard/useMinWidth.d.ts +0 -2
  150. package/dist/Components/ProductCard/useMinWidth.js +0 -20
  151. package/dist/esm/Components/MediaGallery/useMatchMedia.js +0 -16
  152. package/dist/esm/Components/ProductCard/useMinWidth.js +0 -16
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Box, Flex, Text } from "@chakra-ui/react";
4
+ import Button from "../Button/Button.js";
5
+ import Modal, { ModalBody, ModalFooter } from "../Modal/Modal.js";
6
+ import Select from "../Select/Select.js";
7
+ import { useCustomTheme } from "../../Theme/useCustomTheme.js";
8
+ export default function MakeDynamicDialog({ selectedText, options, labels, countFor, onReplace, onClose, }) {
9
+ const { colors } = useCustomTheme();
10
+ const [path, setPath] = useState("");
11
+ const count = selectedText ? countFor(selectedText) : 0;
12
+ const close = () => {
13
+ setPath("");
14
+ onClose();
15
+ };
16
+ return (_jsxs(Modal, { isOpen: selectedText !== null, onClose: close, size: "md", isCentered: true, title: labels.dynamicTitle, children: [_jsx(ModalBody, { children: selectedText ? (_jsxs(Flex, { direction: "column", gap: 4, "data-testid": "editor-dynamic-dialog", children: [_jsxs(Box, { children: [_jsx(Text, { fontSize: "xs", color: colors.textMuted, children: labels.dynamicSelected }), _jsxs(Text, { fontSize: "sm", fontWeight: 500, color: colors.text[800], noOfLines: 3, children: ["\u201C", selectedText, "\u201D"] })] }), _jsx(Select, { native: false, label: labels.dynamicField, placeholder: labels.dynamicFieldPlaceholder, options: options, value: path, onChange: (option) => setPath(option ? String(option.id) : ""), "data-testid": "editor-dynamic-field" }), _jsx(Text, { fontSize: "sm", color: colors.textMuted, "aria-live": "polite", children: labels.dynamicCount(count) })] })) : (_jsx(Text, { fontSize: "sm", color: colors.text[800], "data-testid": "editor-dynamic-empty", children: labels.dynamicNoSelection })) }), _jsx(ModalFooter, { children: _jsxs(Flex, { gap: 2, justify: "flex-end", w: "100%", children: [_jsx(Button, { variant: "ghost", colorScheme: "gray", label: labels.cancel, onClick: close }), selectedText ? (_jsx(Button, { variant: "solid", colorScheme: "primary", label: labels.dynamicReplace, onClick: () => {
17
+ onReplace(selectedText, path);
18
+ close();
19
+ }, isDisabled: !path || count === 0, "data-testid": "editor-dynamic-apply" })) : null] }) })] }));
20
+ }
@@ -0,0 +1,20 @@
1
+ export const DEFAULT_EDITOR_LABELS = {
2
+ insertField: "Insert field",
3
+ noFields: "No fields",
4
+ makeDynamic: "Make dynamic",
5
+ dynamicTitle: "Make dynamic",
6
+ dynamicField: "Field",
7
+ dynamicFieldPlaceholder: "Choose a field",
8
+ dynamicCount: (n) => `${n} ${n === 1 ? "occurrence" : "occurrences"} will be replaced`,
9
+ dynamicReplace: "Replace",
10
+ dynamicNoSelection: "Select some text in the editor first.",
11
+ unknownTokens: (list) => `Unknown fields: ${list}`,
12
+ cancel: "Cancel",
13
+ dynamicSelected: "Selected text",
14
+ toolbar: "Template fields",
15
+ fieldChip: (label) => `Field: ${label}`,
16
+ };
17
+ export const resolveEditorLabels = (labels) => ({
18
+ ...DEFAULT_EDITOR_LABELS,
19
+ ...labels,
20
+ });
@@ -0,0 +1,25 @@
1
+ import { CHIP_CLASS } from "./editorTokens.js";
2
+ export const chipColors = (colors) => ({
3
+ known: { bg: colors.primary.opacity[8], ink: colors.accentText },
4
+ unknown: { bg: "transparent", ink: colors.semanticText.error },
5
+ });
6
+ export const chipStyles = (colors) => {
7
+ const { known, unknown } = chipColors(colors);
8
+ return {
9
+ [`.${CHIP_CLASS}`]: {
10
+ display: "inline-block",
11
+ px: "0.25rem",
12
+ borderRadius: "sm",
13
+ border: "1px solid transparent",
14
+ bg: known.bg,
15
+ color: known.ink,
16
+ cursor: "default",
17
+ },
18
+ [`.${CHIP_CLASS}[data-known="false"]`]: {
19
+ bg: unknown.bg,
20
+ color: unknown.ink,
21
+ border: "1px dashed",
22
+ borderColor: unknown.ink,
23
+ },
24
+ };
25
+ };
@@ -0,0 +1,121 @@
1
+ export const DEFAULT_TOKEN_SYNTAX = { open: "${", close: "}" };
2
+ export const CHIP_CLASS = "pxl-token";
3
+ const PATH_CHARS = "[A-Za-z0-9_.]+";
4
+ const TAG_SPLIT = /(<[^>]*>)/;
5
+ const CHIP_PATTERN = /<span\b([^>]*\bdata-field=(["'])([^"']*)\2[^>]*)>[^<]*<\/span>/g;
6
+ const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7
+ const escapeHtml = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
8
+ const escapeAttr = (text) => escapeHtml(text).replace(/"/g, "&quot;");
9
+ const patternsFor = ({ open, close }) => {
10
+ const o = escapeRegExp(open);
11
+ const c = escapeRegExp(close);
12
+ return {
13
+ match: new RegExp(`${o}(${PATH_CHARS})${c}`, "g"),
14
+ split: new RegExp(`(${o}(?:(?!${c})[\\s\\S])*${c})`),
15
+ };
16
+ };
17
+ export const fieldToken = (path, syntax = DEFAULT_TOKEN_SYNTAX) => `${syntax.open}${path}${syntax.close}`;
18
+ export function fieldOptions(fields, syntax = DEFAULT_TOKEN_SYNTAX) {
19
+ const groups = [...new Set(fields.map((field) => field.group))];
20
+ return groups.flatMap((group) => fields
21
+ .filter((field) => field.group === group)
22
+ .map((field) => ({
23
+ id: field.path,
24
+ label: field.label,
25
+ group,
26
+ description: fieldToken(field.path, syntax),
27
+ })));
28
+ }
29
+ export function extractTokens(html, syntax = DEFAULT_TOKEN_SYNTAX) {
30
+ const { match } = patternsFor(syntax);
31
+ return [...new Set([...html.matchAll(match)].map((m) => m[1]))];
32
+ }
33
+ export function unknownTokens(html, fields, syntax = DEFAULT_TOKEN_SYNTAX) {
34
+ const known = new Set(fields.map((field) => field.path));
35
+ return extractTokens(html, syntax).filter((path) => !known.has(path));
36
+ }
37
+ function needlePattern(text) {
38
+ const words = text
39
+ .trim()
40
+ .split(/\s+/)
41
+ .map((word) => escapeRegExp(escapeHtml(word)));
42
+ return new RegExp(words.join("(?:\\s|&nbsp;|\\u00a0)+"), "g");
43
+ }
44
+ function mapTextOutsideTokens(html, syntax, mapText) {
45
+ const { split } = patternsFor(syntax);
46
+ return html
47
+ .split(TAG_SPLIT)
48
+ .map((segment) => segment.startsWith("<")
49
+ ? segment
50
+ : segment
51
+ .split(split)
52
+ .map((part, i) => (i % 2 === 1 ? part : mapText(part)))
53
+ .join(""))
54
+ .join("");
55
+ }
56
+ /** Replaces every whitespace-insensitive occurrence of `selectedText` outside tags and tokens. */
57
+ export function makeDynamic(html, selectedText, path, syntax = DEFAULT_TOKEN_SYNTAX) {
58
+ if (!selectedText.trim())
59
+ return { html, count: 0 };
60
+ const pattern = needlePattern(selectedText);
61
+ let count = 0;
62
+ const next = mapTextOutsideTokens(html, syntax, (text) => text.replace(pattern, () => {
63
+ count += 1;
64
+ return fieldToken(path, syntax);
65
+ }));
66
+ return { html: next, count };
67
+ }
68
+ export function countOccurrences(html, selectedText, syntax = DEFAULT_TOKEN_SYNTAX) {
69
+ return makeDynamic(html, selectedText, "_", syntax).count;
70
+ }
71
+ export function chipHtml(path, { fields, syntax, chipLabel }) {
72
+ const field = fields.find((f) => f.path === path);
73
+ const text = field ? field.label : fieldToken(path, syntax);
74
+ const title = (field === null || field === void 0 ? void 0 : field.sample) ? ` title="${escapeAttr(field.sample)}"` : "";
75
+ return (`<span class="${CHIP_CLASS}" data-field="${escapeAttr(path)}" data-known="${field ? "true" : "false"}"` +
76
+ ` contenteditable="false" role="img" aria-label="${escapeAttr(chipLabel(text))}"${title}>${escapeHtml(text)}</span>`);
77
+ }
78
+ /** Raw `${path}` tokens in text nodes become non-editable chip spans; tags are left alone. */
79
+ export function chipify(html, context) {
80
+ const { match } = patternsFor(context.syntax);
81
+ return html
82
+ .split(TAG_SPLIT)
83
+ .map((segment) => segment.startsWith("<") ? segment : segment.replace(match, (_t, path) => chipHtml(path, context)))
84
+ .join("");
85
+ }
86
+ /** Inverse of `chipify`: every chip span collapses back to its raw token. */
87
+ export function serializeChips(html, syntax = DEFAULT_TOKEN_SYNTAX) {
88
+ return html.replace(CHIP_PATTERN, (_m, _attrs, _q, path) => fieldToken(path, syntax));
89
+ }
90
+ const isChip = (node) => !!node && node.nodeType === 1 && node.hasAttribute("data-field");
91
+ const INVISIBLE = /[\u200b\ufeff]/g;
92
+ const isBlankText = (node) => !node.data.replace(INVISIBLE, "");
93
+ const skipEmptyText = (node, step) => {
94
+ let current = node;
95
+ while (current && current.nodeType === 3 && isBlankText(current))
96
+ current = current[step];
97
+ return current;
98
+ };
99
+ /** The chip a Backspace (backward) or Delete (forward) at a collapsed caret would eat, if any. */
100
+ export function adjacentChip(range, direction) {
101
+ var _a;
102
+ if (!range.collapsed)
103
+ return null;
104
+ const node = range.startContainer;
105
+ const offset = range.startOffset;
106
+ const step = direction === "backward" ? "previousSibling" : "nextSibling";
107
+ let candidate;
108
+ if (node.nodeType === 3) {
109
+ const text = node.data;
110
+ const beyond = direction === "backward" ? text.slice(0, offset) : text.slice(offset);
111
+ const atEdge = !beyond.replace(INVISIBLE, "");
112
+ if (!atEdge)
113
+ return null;
114
+ candidate = node[step];
115
+ }
116
+ else {
117
+ candidate = (_a = node.childNodes[direction === "backward" ? offset - 1 : offset]) !== null && _a !== void 0 ? _a : null;
118
+ }
119
+ const found = skipEmptyText(candidate, step);
120
+ return isChip(found) ? found : null;
121
+ }
@@ -0,0 +1,138 @@
1
+ import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
2
+ import { adjacentChip, chipHtml, chipify, countOccurrences, fieldToken, makeDynamic, serializeChips, unknownTokens, } from "./editorTokens.js";
3
+ export function useEditorBridge({ value, onChange, onBlur, mergeFields, syntax, chipLabel, apiRef, onReady, onUnknownTokens, }) {
4
+ const enabled = mergeFields !== undefined;
5
+ const fieldsKey = JSON.stringify(mergeFields);
6
+ const ctx = useMemo(() => ({ fields: mergeFields !== null && mergeFields !== void 0 ? mergeFields : [], syntax, chipLabel }),
7
+ // eslint-disable-next-line react-hooks/exhaustive-deps
8
+ [fieldsKey, syntax.open, syntax.close, chipLabel]);
9
+ const joditRef = useRef(null);
10
+ const rangeRef = useRef(null);
11
+ const lastRef = useRef(null);
12
+ const appliedFieldsKey = useRef(fieldsKey);
13
+ const live = useRef({ enabled, ctx, onChange, onBlur, onReady, fieldsKey });
14
+ live.current = { enabled, ctx, onChange, onBlur, onReady, fieldsKey };
15
+ const [instance, setInstance] = useState(null);
16
+ const [raw, setRaw] = useState(value !== null && value !== void 0 ? value : "");
17
+ useEffect(() => {
18
+ if (value !== undefined)
19
+ setRaw(value);
20
+ }, [value]);
21
+ const handleChange = useCallback((html) => {
22
+ const { enabled: on, ctx: c, onChange: emit } = live.current;
23
+ const next = on ? serializeChips(html, c.syntax) : html;
24
+ lastRef.current = { raw: next, jodit: html };
25
+ setRaw(next);
26
+ emit === null || emit === void 0 ? void 0 : emit(next);
27
+ }, []);
28
+ const handleBlur = useCallback((html, event) => {
29
+ const { enabled: on, ctx: c, onBlur: emit } = live.current;
30
+ emit === null || emit === void 0 ? void 0 : emit(on ? serializeChips(html, c.syntax) : html, event);
31
+ }, []);
32
+ const attachEditor = useCallback((jodit) => {
33
+ joditRef.current = jodit;
34
+ rangeRef.current = null;
35
+ appliedFieldsKey.current = live.current.fieldsKey;
36
+ const remember = () => {
37
+ if (jodit.s.isInsideArea)
38
+ rangeRef.current = jodit.s.range.cloneRange();
39
+ };
40
+ jodit.events.on("keyup mouseup touchend", remember);
41
+ jodit.events.on("change", handleChange);
42
+ jodit.events.on("blur", (event) => handleBlur(jodit.value, event));
43
+ const deleteChip = (event) => {
44
+ if (!live.current.enabled)
45
+ return false;
46
+ if (event.key !== "Backspace" && event.key !== "Delete")
47
+ return false;
48
+ const chip = adjacentChip(jodit.s.range, event.key === "Backspace" ? "backward" : "forward");
49
+ if (!chip)
50
+ return false;
51
+ chip.remove();
52
+ jodit.synchronizeValues();
53
+ event.preventDefault();
54
+ return true;
55
+ };
56
+ jodit.events.on("keydown", ((event) => (deleteChip(event) ? false : undefined)));
57
+ jodit.editor.addEventListener("keydown", (event) => {
58
+ if (deleteChip(event))
59
+ event.stopImmediatePropagation();
60
+ }, true);
61
+ jodit.waitForReady().then(() => {
62
+ var _a, _b;
63
+ if (joditRef.current !== jodit)
64
+ return;
65
+ setInstance(jodit);
66
+ (_b = (_a = live.current).onReady) === null || _b === void 0 ? void 0 : _b.call(_a);
67
+ });
68
+ }, [handleChange, handleBlur]);
69
+ useEffect(() => {
70
+ if (!instance || !enabled || appliedFieldsKey.current === fieldsKey)
71
+ return;
72
+ appliedFieldsKey.current = fieldsKey;
73
+ const html = chipify(serializeChips(instance.value, ctx.syntax), ctx);
74
+ instance.value = html;
75
+ lastRef.current = { raw: serializeChips(html, ctx.syntax), jodit: instance.value };
76
+ }, [instance, enabled, fieldsKey, ctx]);
77
+ const api = useMemo(() => {
78
+ const current = () => joditRef.current;
79
+ const currentRaw = (jodit) => live.current.enabled ? serializeChips(jodit.value, live.current.ctx.syntax) : jodit.value;
80
+ const insertHTML = (html) => {
81
+ const jodit = current();
82
+ if (!jodit)
83
+ return;
84
+ if (rangeRef.current)
85
+ jodit.s.selectRange(rangeRef.current, true);
86
+ jodit.s.insertHTML(html);
87
+ rangeRef.current = jodit.s.range.cloneRange();
88
+ };
89
+ return {
90
+ insertHTML,
91
+ insertField: (path) => {
92
+ const { enabled: on, ctx: c } = live.current;
93
+ insertHTML(on ? chipHtml(path, c) : fieldToken(path, c.syntax));
94
+ },
95
+ getSelectionText: () => (rangeRef.current ? rangeRef.current.toString().trim() : ""),
96
+ replaceAll: (text, path) => {
97
+ const jodit = current();
98
+ if (!jodit)
99
+ return 0;
100
+ const { enabled: on, ctx: c, onChange: emit } = live.current;
101
+ const result = makeDynamic(currentRaw(jodit), text, path, c.syntax);
102
+ if (result.count === 0)
103
+ return 0;
104
+ jodit.value = on ? chipify(result.html, c) : result.html;
105
+ lastRef.current = { raw: result.html, jodit: jodit.value };
106
+ rangeRef.current = null;
107
+ setRaw(result.html);
108
+ emit === null || emit === void 0 ? void 0 : emit(result.html);
109
+ return result.count;
110
+ },
111
+ countOccurrences: (text) => {
112
+ const jodit = current();
113
+ return jodit ? countOccurrences(currentRaw(jodit), text, live.current.ctx.syntax) : 0;
114
+ },
115
+ focus: () => {
116
+ var _a;
117
+ (_a = current()) === null || _a === void 0 ? void 0 : _a.s.focus();
118
+ },
119
+ };
120
+ }, []);
121
+ useImperativeHandle(apiRef, () => api, [api]);
122
+ const unknown = useMemo(() => (enabled ? unknownTokens(raw, ctx.fields, syntax) : []), [enabled, raw, ctx, syntax]);
123
+ const unknownKey = unknown.join("\n");
124
+ const reported = useRef("");
125
+ useEffect(() => {
126
+ if (!enabled || reported.current === unknownKey)
127
+ return;
128
+ reported.current = unknownKey;
129
+ onUnknownTokens === null || onUnknownTokens === void 0 ? void 0 : onUnknownTokens(unknown);
130
+ // eslint-disable-next-line react-hooks/exhaustive-deps
131
+ }, [enabled, unknownKey]);
132
+ const last = lastRef.current;
133
+ let joditValue = value;
134
+ if (enabled && value !== undefined) {
135
+ joditValue = last && last.raw === value ? last.jodit : chipify(value, ctx);
136
+ }
137
+ return { joditValue, attachEditor, api, instance, raw, unknown, ctx };
138
+ }
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box } from "@chakra-ui/react";
3
+ import { useCustomTheme } from "../../Theme/useCustomTheme.js";
4
+ import { shortcutKeys } from "../AppSwitcher/AppSwitcherProps.js";
5
+ const HEIGHT = { xs: "1rem", sm: "1.25rem" };
6
+ const SPOKEN = {
7
+ mod: "Command",
8
+ cmd: "Command",
9
+ ctrl: "Control",
10
+ alt: "Option",
11
+ shift: "Shift",
12
+ enter: "Enter",
13
+ esc: "Escape",
14
+ };
15
+ export const detectApple = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform);
16
+ /** One keyboard key cap; decorative hint rows should wrap it in `aria-hidden`. */
17
+ const Kbd = ({ children, size = "sm", "data-testid": testId }) => {
18
+ const { colors } = useCustomTheme();
19
+ return (_jsx(Box, { as: "kbd", "data-testid": testId, display: "inline-flex", alignItems: "center", justifyContent: "center", minW: HEIGHT[size], h: HEIGHT[size], px: 1.5, fontFamily: "inherit", fontSize: "0.6875rem", fontWeight: 600, lineHeight: 1, whiteSpace: "nowrap", borderWidth: "1px", borderColor: colors.boxborder[200], borderRadius: "base", bg: colors.backgroundColor.subtle, color: colors.textMuted, children: children }));
20
+ };
21
+ export const KbdShortcut = ({ shortcut, size, isApple, "data-testid": testId }) => {
22
+ const parts = shortcut.split("+").map((p) => p.trim()).filter(Boolean);
23
+ const apple = isApple !== null && isApple !== void 0 ? isApple : detectApple();
24
+ const spoken = parts.map((p) => { var _a; return (_a = SPOKEN[p.toLowerCase()]) !== null && _a !== void 0 ? _a : p.toUpperCase(); }).join(" ");
25
+ const keys = shortcutKeys(shortcut, apple);
26
+ return (_jsx(Box, { as: "span", display: "inline-flex", gap: 1, role: "img", "aria-label": spoken, "data-testid": testId, children: keys.map((key, i) => (_jsx(Kbd, { size: size, children: key }, `${key}-${i}`))) }));
27
+ };
28
+ export default Kbd;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box } from "@chakra-ui/react";
3
+ import { Compass } from "lucide-react";
4
+ import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
+ import { reducedMotionSx } from "../../Theme/chakra/motion.js";
6
+ import { FOCUS_VISIBLE_OVER_STATE, focusVisibleRing } from "../../Theme/chakra/focusRing.styles.js";
7
+ import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
8
+ import { useMatchMedia } from "../../Hooks/useMatchMedia.js";
9
+ import { detectApple } from "../Kbd/Kbd.js";
10
+ import { formatShortcut } from "../AppSwitcher/AppSwitcherProps.js";
11
+ const MD_QUERY = "(min-width: 48em)";
12
+ const KEY_NAMES = {
13
+ mod: ["Meta", "Control"],
14
+ cmd: ["Meta"],
15
+ ctrl: ["Control"],
16
+ alt: ["Alt"],
17
+ shift: ["Shift"],
18
+ esc: ["Escape"],
19
+ };
20
+ export const keyShortcutsFor = (shortcut) => {
21
+ const options = shortcut
22
+ .split("+")
23
+ .map((p) => p.trim())
24
+ .filter(Boolean)
25
+ .map((p) => { var _a; return (_a = KEY_NAMES[p.toLowerCase()]) !== null && _a !== void 0 ? _a : [p.length === 1 ? p.toUpperCase() : p]; });
26
+ const variants = Math.max(1, ...options.map((o) => o.length));
27
+ const chords = Array.from({ length: variants }, (_, v) => options.map((o) => o[Math.min(v, o.length - 1)]).join("+"));
28
+ return Array.from(new Set(chords)).join(" ");
29
+ };
30
+ const LauncherTrigger = ({ onClick, isOpen, label = "Menu", shortcut = "mod+k", icon: Icon = Compass, "aria-label": ariaLabel, "data-testid": testId, }) => {
31
+ const { colors } = useCustomTheme();
32
+ const showHint = useMatchMedia(MD_QUERY, true);
33
+ const ring = focusVisibleRing(colors);
34
+ const hint = shortcut ? formatShortcut(shortcut, detectApple()) : "";
35
+ return (_jsxs(Box, { as: "button", type: "button", "data-testid": testId, onClick: onClick, "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-label": ariaLabel, "aria-keyshortcuts": shortcut ? keyShortcutsFor(shortcut) : undefined, display: "inline-flex", alignItems: "center", gap: 2, flexShrink: 0, h: "2.25rem", pl: "0.625rem", pr: "0.375rem", borderRadius: "10px", borderWidth: "1px", borderColor: colors.primary[100], bg: colors.primary[50], color: colors.accentText, fontSize: "0.84375rem", fontWeight: 600, lineHeight: 1, cursor: "pointer", transition: transitionFor(["background-color", "border-color", "box-shadow"]), _hover: { borderColor: colors.primary[300] }, sx: { _focusVisible: ring, [FOCUS_VISIBLE_OVER_STATE]: ring, ...reducedMotionSx() }, children: [_jsxs(Box, { as: "span", display: "inline-flex", alignItems: "center", gap: 2, "aria-hidden": ariaLabel ? true : undefined, children: [_jsx(Icon, { size: 17, "aria-hidden": "true" }), _jsx("span", { children: label })] }), hint && showHint && (_jsx(Box, { as: "kbd", "aria-hidden": "true", fontFamily: "inherit", fontSize: "0.6875rem", fontWeight: 600, lineHeight: "normal", px: "6px", py: "2px", borderRadius: "6px", borderWidth: "1px", borderColor: colors.primary[100], bg: colors.surfaceRaised, children: hint }))] }));
36
+ };
37
+ export default LauncherTrigger;
@@ -14,7 +14,7 @@ import MediaLightbox from "./MediaLightbox.js";
14
14
  import { GlassIconButton, GlassPill, glassStyles } from "./glass.js";
15
15
  import { CROSSFADE_MS, fadeIn } from "./motion.js";
16
16
  import { useContainerWidth } from "../Commerce/useContainerWidth.js";
17
- import { useMatchMedia } from "./useMatchMedia.js";
17
+ import { useMatchMedia } from "../../Hooks/useMatchMedia.js";
18
18
  import { clampIndex, frameKind, frameSignature, indexForKey, keyBelongsToTarget, resolveFrames } from "./mediaGalleryModel.js";
19
19
  import { defaultMediaGalleryLabels } from "./MediaGalleryProps.js";
20
20
  const WIDE_QUERY = "(min-width: 768px)";