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.
- package/CHANGELOG.md +86 -0
- package/COMPONENT-GOTCHAS.md +88 -2
- package/DESIGN-GUIDE.md +27 -4
- package/dist/Components/AppSwitcher/AppSwitcher.js +5 -1
- package/dist/Components/AppSwitcher/AppSwitcherProps.d.ts +2 -0
- package/dist/Components/AppSwitcher/AppSwitcherProps.js +7 -4
- package/dist/Components/Breadcrumbs/Breadcrumbs.d.ts +1 -1
- package/dist/Components/Breadcrumbs/Breadcrumbs.js +33 -10
- package/dist/Components/Breadcrumbs/BreadcrumbsProps.d.ts +16 -2
- package/dist/Components/Card/Card.d.ts +3 -1
- package/dist/Components/Card/Card.js +12 -2
- package/dist/Components/Card/CardProps.d.ts +5 -1
- package/dist/Components/Commerce/commerceTypes.d.ts +5 -0
- package/dist/Components/Editor/Editor.js +50 -2
- package/dist/Components/Editor/EditorFieldBar.d.ts +15 -0
- package/dist/Components/Editor/EditorFieldBar.js +19 -0
- package/dist/Components/Editor/EditorProps.d.ts +74 -1
- package/dist/Components/Editor/MakeDynamicDialog.d.ts +12 -0
- package/dist/Components/Editor/MakeDynamicDialog.js +59 -0
- package/dist/Components/Editor/editorLabels.d.ts +4 -0
- package/dist/Components/Editor/editorLabels.js +24 -0
- package/dist/Components/Editor/editorTokenStyles.d.ts +28 -0
- package/dist/Components/Editor/editorTokenStyles.js +30 -0
- package/dist/Components/Editor/editorTokens.d.ts +26 -0
- package/dist/Components/Editor/editorTokens.js +134 -0
- package/dist/Components/Editor/useEditorBridge.d.ts +40 -0
- package/dist/Components/Editor/useEditorBridge.js +141 -0
- package/dist/Components/Kbd/Kbd.d.ts +6 -0
- package/dist/Components/Kbd/Kbd.js +33 -0
- package/dist/Components/Kbd/KbdProps.d.ts +17 -0
- package/dist/Components/Kbd/KbdProps.js +2 -0
- package/dist/Components/LauncherTrigger/LauncherTrigger.d.ts +4 -0
- package/dist/Components/LauncherTrigger/LauncherTrigger.js +41 -0
- package/dist/Components/LauncherTrigger/LauncherTriggerProps.d.ts +16 -0
- package/dist/Components/LauncherTrigger/LauncherTriggerProps.js +2 -0
- package/dist/Components/MediaGallery/MediaGallery.js +1 -1
- package/dist/Components/NavLauncher/NavLauncher.d.ts +3 -0
- package/dist/Components/NavLauncher/NavLauncher.js +395 -0
- package/dist/Components/NavLauncher/NavLauncherProps.d.ts +187 -0
- package/dist/Components/NavLauncher/NavLauncherProps.js +2 -0
- package/dist/Components/NavLauncher/NewTabLink.d.ts +3 -0
- package/dist/Components/NavLauncher/NewTabLink.js +14 -0
- package/dist/Components/NavLauncher/PinToggle.d.ts +3 -0
- package/dist/Components/NavLauncher/PinToggle.js +21 -0
- package/dist/Components/NavLauncher/launcherParts.d.ts +93 -0
- package/dist/Components/NavLauncher/launcherParts.js +130 -0
- package/dist/Components/NavLauncher/match.d.ts +53 -0
- package/dist/Components/NavLauncher/match.js +178 -0
- package/dist/Components/NavLauncher/revealStyles.d.ts +4 -0
- package/dist/Components/NavLauncher/revealStyles.js +19 -0
- package/dist/Components/NavigationBar/NavigationBar.d.ts +1 -1
- package/dist/Components/NavigationBar/NavigationBar.js +2 -2
- package/dist/Components/NavigationBar/NavigationBarProps.d.ts +2 -0
- package/dist/Components/PageHeader/PageHeader.d.ts +1 -1
- package/dist/Components/PageHeader/PageHeader.js +9 -2
- package/dist/Components/PageHeader/PageHeaderProps.d.ts +8 -0
- package/dist/Components/PdfViewer/PdfViewer.d.ts +21 -4
- package/dist/Components/PdfViewer/PdfViewer.js +124 -3
- package/dist/Components/ProductCard/ProductCard.js +8 -3
- package/dist/Components/ProductCard/ProductCardMedia.js +3 -3
- package/dist/Components/ProductCard/QuickAddSheet.js +2 -2
- package/dist/Components/ProductCard/cardMetrics.d.ts +1 -0
- package/dist/Components/ProductCard/cardMetrics.js +3 -2
- package/dist/Components/ProductDetails/ProductDetails.d.ts +1 -1
- package/dist/Components/ProductDetails/ProductDetails.js +6 -5
- package/dist/Components/ProductDetails/ProductDetailsProps.d.ts +2 -0
- package/dist/Components/Search/Search.js +2 -1
- package/dist/Components/SideBar/SecondaryBar.js +4 -3
- package/dist/Components/SideBar/SideBar.d.ts +1 -1
- package/dist/Components/SideBar/SideBar.js +30 -9
- package/dist/Components/SideBar/SideBarProps.d.ts +32 -0
- package/dist/Components/SideBar/components/MenuItemBox.d.ts +6 -0
- package/dist/Components/SideBar/components/MenuItemBox.js +8 -4
- package/dist/Components/SideBar/components/MenuPopoverContent.js +3 -2
- package/dist/Components/SideBar/components/OtherApps.js +2 -1
- package/dist/Components/SideBar/components/PinnedSection.d.ts +17 -0
- package/dist/Components/SideBar/components/PinnedSection.js +98 -0
- package/dist/Components/SideBar/sidebarFocus.d.ts +23 -0
- package/dist/Components/SideBar/sidebarFocus.js +20 -0
- package/dist/Components/StageProgress/StageProgress.js +2 -2
- package/dist/Components/StageProgress/StageProgressProps.d.ts +2 -0
- package/dist/Components/StageProgress/StepperStage.js +5 -2
- package/dist/Components/WizardDialog/WizardDialog.d.ts +9 -0
- package/dist/Components/WizardDialog/WizardDialog.js +102 -0
- package/dist/Components/WizardDialog/WizardDialogProps.d.ts +48 -0
- package/dist/Components/WizardDialog/WizardDialogProps.js +2 -0
- package/dist/Components/WizardDialog/useWizardFlow.d.ts +22 -0
- package/dist/Components/WizardDialog/useWizardFlow.js +47 -0
- package/dist/Components/WizardDialog/wizardSteps.d.ts +31 -0
- package/dist/Components/WizardDialog/wizardSteps.js +40 -0
- package/dist/Hooks/useMatchMedia.d.ts +2 -0
- package/dist/Hooks/useMatchMedia.js +36 -0
- package/dist/Theme/chakra/motion.js +2 -16
- package/dist/Utils/price.js +11 -6
- package/dist/esm/Components/AppSwitcher/AppSwitcher.js +2 -1
- package/dist/esm/Components/AppSwitcher/AppSwitcherProps.js +5 -3
- package/dist/esm/Components/Breadcrumbs/Breadcrumbs.js +35 -12
- package/dist/esm/Components/Card/Card.js +12 -2
- package/dist/esm/Components/Editor/Editor.js +48 -3
- package/dist/esm/Components/Editor/EditorFieldBar.js +13 -0
- package/dist/esm/Components/Editor/MakeDynamicDialog.js +20 -0
- package/dist/esm/Components/Editor/editorLabels.js +20 -0
- package/dist/esm/Components/Editor/editorTokenStyles.js +25 -0
- package/dist/esm/Components/Editor/editorTokens.js +121 -0
- package/dist/esm/Components/Editor/useEditorBridge.js +138 -0
- package/dist/esm/Components/Kbd/Kbd.js +28 -0
- package/dist/esm/Components/Kbd/KbdProps.js +1 -0
- package/dist/esm/Components/LauncherTrigger/LauncherTrigger.js +37 -0
- package/dist/esm/Components/LauncherTrigger/LauncherTriggerProps.js +1 -0
- package/dist/esm/Components/MediaGallery/MediaGallery.js +1 -1
- package/dist/esm/Components/NavLauncher/NavLauncher.js +356 -0
- package/dist/esm/Components/NavLauncher/NavLauncherProps.js +1 -0
- package/dist/esm/Components/NavLauncher/NewTabLink.js +11 -0
- package/dist/esm/Components/NavLauncher/PinToggle.js +18 -0
- package/dist/esm/Components/NavLauncher/launcherParts.js +110 -0
- package/dist/esm/Components/NavLauncher/match.js +172 -0
- package/dist/esm/Components/NavLauncher/revealStyles.js +15 -0
- package/dist/esm/Components/NavigationBar/NavigationBar.js +3 -3
- package/dist/esm/Components/PageHeader/PageHeader.js +9 -2
- package/dist/esm/Components/PdfViewer/PdfViewer.js +123 -2
- package/dist/esm/Components/ProductCard/ProductCard.js +9 -4
- package/dist/esm/Components/ProductCard/ProductCardMedia.js +3 -3
- package/dist/esm/Components/ProductCard/QuickAddSheet.js +2 -2
- package/dist/esm/Components/ProductCard/cardMetrics.js +2 -1
- package/dist/esm/Components/ProductDetails/ProductDetails.js +6 -5
- package/dist/esm/Components/Search/Search.js +3 -2
- package/dist/esm/Components/SideBar/SecondaryBar.js +4 -3
- package/dist/esm/Components/SideBar/SideBar.js +31 -10
- package/dist/esm/Components/SideBar/components/MenuItemBox.js +4 -2
- package/dist/esm/Components/SideBar/components/MenuPopoverContent.js +3 -2
- package/dist/esm/Components/SideBar/components/OtherApps.js +2 -1
- package/dist/esm/Components/SideBar/components/PinnedSection.js +92 -0
- package/dist/esm/Components/SideBar/sidebarFocus.js +15 -0
- package/dist/esm/Components/StageProgress/StageProgress.js +2 -2
- package/dist/esm/Components/StageProgress/StepperStage.js +5 -2
- package/dist/esm/Components/WizardDialog/WizardDialog.js +58 -0
- package/dist/esm/Components/WizardDialog/WizardDialogProps.js +1 -0
- package/dist/esm/Components/WizardDialog/useWizardFlow.js +44 -0
- package/dist/esm/Components/WizardDialog/wizardSteps.js +35 -0
- package/dist/esm/Hooks/useMatchMedia.js +32 -0
- package/dist/esm/Theme/chakra/motion.js +2 -16
- package/dist/esm/Utils/price.js +11 -6
- package/dist/esm/index.js +7 -0
- package/dist/index.d.ts +19 -4
- package/dist/index.js +21 -1
- package/package.json +1 -1
- package/dist/Components/MediaGallery/useMatchMedia.d.ts +0 -2
- package/dist/Components/MediaGallery/useMatchMedia.js +0 -20
- package/dist/Components/ProductCard/useMinWidth.d.ts +0 -2
- package/dist/Components/ProductCard/useMinWidth.js +0 -20
- package/dist/esm/Components/MediaGallery/useMatchMedia.js +0 -16
- 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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
8
|
+
const escapeAttr = (text) => escapeHtml(text).replace(/"/g, """);
|
|
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| |\\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;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 "
|
|
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)";
|