pixelize-design-library 2.4.2-beta.58 → 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 (123) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/COMPONENT-GOTCHAS.md +88 -2
  3. package/DESIGN-GUIDE.md +26 -3
  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/Editor/Editor.js +50 -2
  14. package/dist/Components/Editor/EditorFieldBar.d.ts +15 -0
  15. package/dist/Components/Editor/EditorFieldBar.js +19 -0
  16. package/dist/Components/Editor/EditorProps.d.ts +74 -1
  17. package/dist/Components/Editor/MakeDynamicDialog.d.ts +12 -0
  18. package/dist/Components/Editor/MakeDynamicDialog.js +59 -0
  19. package/dist/Components/Editor/editorLabels.d.ts +4 -0
  20. package/dist/Components/Editor/editorLabels.js +24 -0
  21. package/dist/Components/Editor/editorTokenStyles.d.ts +28 -0
  22. package/dist/Components/Editor/editorTokenStyles.js +30 -0
  23. package/dist/Components/Editor/editorTokens.d.ts +26 -0
  24. package/dist/Components/Editor/editorTokens.js +134 -0
  25. package/dist/Components/Editor/useEditorBridge.d.ts +40 -0
  26. package/dist/Components/Editor/useEditorBridge.js +141 -0
  27. package/dist/Components/Kbd/Kbd.d.ts +6 -0
  28. package/dist/Components/Kbd/Kbd.js +33 -0
  29. package/dist/Components/Kbd/KbdProps.d.ts +17 -0
  30. package/dist/Components/Kbd/KbdProps.js +2 -0
  31. package/dist/Components/LauncherTrigger/LauncherTrigger.d.ts +4 -0
  32. package/dist/Components/LauncherTrigger/LauncherTrigger.js +41 -0
  33. package/dist/Components/LauncherTrigger/LauncherTriggerProps.d.ts +16 -0
  34. package/dist/Components/LauncherTrigger/LauncherTriggerProps.js +2 -0
  35. package/dist/Components/NavLauncher/NavLauncher.d.ts +3 -0
  36. package/dist/Components/NavLauncher/NavLauncher.js +395 -0
  37. package/dist/Components/NavLauncher/NavLauncherProps.d.ts +187 -0
  38. package/dist/Components/NavLauncher/NavLauncherProps.js +2 -0
  39. package/dist/Components/NavLauncher/NewTabLink.d.ts +3 -0
  40. package/dist/Components/NavLauncher/NewTabLink.js +14 -0
  41. package/dist/Components/NavLauncher/PinToggle.d.ts +3 -0
  42. package/dist/Components/NavLauncher/PinToggle.js +21 -0
  43. package/dist/Components/NavLauncher/launcherParts.d.ts +93 -0
  44. package/dist/Components/NavLauncher/launcherParts.js +130 -0
  45. package/dist/Components/NavLauncher/match.d.ts +53 -0
  46. package/dist/Components/NavLauncher/match.js +178 -0
  47. package/dist/Components/NavLauncher/revealStyles.d.ts +4 -0
  48. package/dist/Components/NavLauncher/revealStyles.js +19 -0
  49. package/dist/Components/NavigationBar/NavigationBar.d.ts +1 -1
  50. package/dist/Components/NavigationBar/NavigationBar.js +2 -2
  51. package/dist/Components/NavigationBar/NavigationBarProps.d.ts +2 -0
  52. package/dist/Components/PageHeader/PageHeader.d.ts +1 -1
  53. package/dist/Components/PageHeader/PageHeader.js +9 -2
  54. package/dist/Components/PageHeader/PageHeaderProps.d.ts +8 -0
  55. package/dist/Components/PdfViewer/PdfViewer.d.ts +21 -4
  56. package/dist/Components/PdfViewer/PdfViewer.js +124 -3
  57. package/dist/Components/Search/Search.js +2 -1
  58. package/dist/Components/SideBar/SecondaryBar.js +4 -3
  59. package/dist/Components/SideBar/SideBar.d.ts +1 -1
  60. package/dist/Components/SideBar/SideBar.js +30 -9
  61. package/dist/Components/SideBar/SideBarProps.d.ts +32 -0
  62. package/dist/Components/SideBar/components/MenuItemBox.d.ts +6 -0
  63. package/dist/Components/SideBar/components/MenuItemBox.js +8 -4
  64. package/dist/Components/SideBar/components/MenuPopoverContent.js +3 -2
  65. package/dist/Components/SideBar/components/OtherApps.js +2 -1
  66. package/dist/Components/SideBar/components/PinnedSection.d.ts +17 -0
  67. package/dist/Components/SideBar/components/PinnedSection.js +98 -0
  68. package/dist/Components/SideBar/sidebarFocus.d.ts +23 -0
  69. package/dist/Components/SideBar/sidebarFocus.js +20 -0
  70. package/dist/Components/StageProgress/StageProgress.js +2 -2
  71. package/dist/Components/StageProgress/StageProgressProps.d.ts +2 -0
  72. package/dist/Components/StageProgress/StepperStage.js +5 -2
  73. package/dist/Components/WizardDialog/WizardDialog.d.ts +9 -0
  74. package/dist/Components/WizardDialog/WizardDialog.js +102 -0
  75. package/dist/Components/WizardDialog/WizardDialogProps.d.ts +48 -0
  76. package/dist/Components/WizardDialog/WizardDialogProps.js +2 -0
  77. package/dist/Components/WizardDialog/useWizardFlow.d.ts +22 -0
  78. package/dist/Components/WizardDialog/useWizardFlow.js +47 -0
  79. package/dist/Components/WizardDialog/wizardSteps.d.ts +31 -0
  80. package/dist/Components/WizardDialog/wizardSteps.js +40 -0
  81. package/dist/esm/Components/AppSwitcher/AppSwitcher.js +2 -1
  82. package/dist/esm/Components/AppSwitcher/AppSwitcherProps.js +5 -3
  83. package/dist/esm/Components/Breadcrumbs/Breadcrumbs.js +35 -12
  84. package/dist/esm/Components/Card/Card.js +12 -2
  85. package/dist/esm/Components/Editor/Editor.js +48 -3
  86. package/dist/esm/Components/Editor/EditorFieldBar.js +13 -0
  87. package/dist/esm/Components/Editor/MakeDynamicDialog.js +20 -0
  88. package/dist/esm/Components/Editor/editorLabels.js +20 -0
  89. package/dist/esm/Components/Editor/editorTokenStyles.js +25 -0
  90. package/dist/esm/Components/Editor/editorTokens.js +121 -0
  91. package/dist/esm/Components/Editor/useEditorBridge.js +138 -0
  92. package/dist/esm/Components/Kbd/Kbd.js +28 -0
  93. package/dist/esm/Components/Kbd/KbdProps.js +1 -0
  94. package/dist/esm/Components/LauncherTrigger/LauncherTrigger.js +37 -0
  95. package/dist/esm/Components/LauncherTrigger/LauncherTriggerProps.js +1 -0
  96. package/dist/esm/Components/NavLauncher/NavLauncher.js +356 -0
  97. package/dist/esm/Components/NavLauncher/NavLauncherProps.js +1 -0
  98. package/dist/esm/Components/NavLauncher/NewTabLink.js +11 -0
  99. package/dist/esm/Components/NavLauncher/PinToggle.js +18 -0
  100. package/dist/esm/Components/NavLauncher/launcherParts.js +110 -0
  101. package/dist/esm/Components/NavLauncher/match.js +172 -0
  102. package/dist/esm/Components/NavLauncher/revealStyles.js +15 -0
  103. package/dist/esm/Components/NavigationBar/NavigationBar.js +3 -3
  104. package/dist/esm/Components/PageHeader/PageHeader.js +9 -2
  105. package/dist/esm/Components/PdfViewer/PdfViewer.js +123 -2
  106. package/dist/esm/Components/Search/Search.js +3 -2
  107. package/dist/esm/Components/SideBar/SecondaryBar.js +4 -3
  108. package/dist/esm/Components/SideBar/SideBar.js +31 -10
  109. package/dist/esm/Components/SideBar/components/MenuItemBox.js +4 -2
  110. package/dist/esm/Components/SideBar/components/MenuPopoverContent.js +3 -2
  111. package/dist/esm/Components/SideBar/components/OtherApps.js +2 -1
  112. package/dist/esm/Components/SideBar/components/PinnedSection.js +92 -0
  113. package/dist/esm/Components/SideBar/sidebarFocus.js +15 -0
  114. package/dist/esm/Components/StageProgress/StageProgress.js +2 -2
  115. package/dist/esm/Components/StageProgress/StepperStage.js +5 -2
  116. package/dist/esm/Components/WizardDialog/WizardDialog.js +58 -0
  117. package/dist/esm/Components/WizardDialog/WizardDialogProps.js +1 -0
  118. package/dist/esm/Components/WizardDialog/useWizardFlow.js +44 -0
  119. package/dist/esm/Components/WizardDialog/wizardSteps.js +35 -0
  120. package/dist/esm/index.js +7 -0
  121. package/dist/index.d.ts +19 -4
  122. package/dist/index.js +21 -1
  123. package/package.json +1 -1
@@ -32,10 +32,23 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  const jsx_runtime_1 = require("react/jsx-runtime");
37
40
  const react_1 = __importStar(require("react"));
38
41
  const react_2 = require("@chakra-ui/react");
42
+ const FieldMessage_1 = __importDefault(require("../Common/FieldMessage"));
43
+ const ErrorMessage_1 = __importDefault(require("../Common/ErrorMessage"));
44
+ const FormLabel_1 = require("../Common/FormLabel");
45
+ const useFieldIds_1 = require("../Common/useFieldIds");
46
+ const EditorFieldBar_1 = __importDefault(require("./EditorFieldBar"));
47
+ const MakeDynamicDialog_1 = __importDefault(require("./MakeDynamicDialog"));
48
+ const editorTokens_1 = require("./editorTokens");
49
+ const editorTokenStyles_1 = require("./editorTokenStyles");
50
+ const editorLabels_1 = require("./editorLabels");
51
+ const useEditorBridge_1 = require("./useEditorBridge");
39
52
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
40
53
  const accentText_1 = require("../../Theme/tokens/builders/accentText");
41
54
  const joditThemeVars_1 = require("./joditThemeVars");
@@ -48,8 +61,25 @@ const JoditEditor = react_1.default.lazy(async () => {
48
61
  return { default: ((_a = mod.default) !== null && _a !== void 0 ? _a : mod) };
49
62
  });
50
63
  /** Lazy-loaded Jodit wrapper — a `config.theme` or `config.popupRoot` you pass overrides this library's own dark-mode/popup fix; omit both keys. */
51
- const Editor = react_1.default.memo(function Editor({ value, key, config, editorRef, className, onChange, onBlur, }) {
64
+ const Editor = react_1.default.memo(function Editor({ value, key, config, editorRef, className, onChange, onBlur, label, isRequired, helperText, error, errorMessage, mergeFields, tokenSyntax = editorTokens_1.DEFAULT_TOKEN_SYNTAX, showMakeDynamic, toolbarButtons, onUnknownTokens, apiRef, onReady, labels, "data-testid": testId, }) {
52
65
  const theme = (0, useCustomTheme_1.useCustomTheme)();
66
+ const text = (0, react_1.useMemo)(() => (0, editorLabels_1.resolveEditorLabels)(labels), [labels]);
67
+ const { joditValue, attachEditor, api, instance, unknown } = (0, useEditorBridge_1.useEditorBridge)({
68
+ value,
69
+ onChange,
70
+ onBlur,
71
+ mergeFields,
72
+ syntax: tokenSyntax,
73
+ chipLabel: text.fieldChip,
74
+ apiRef,
75
+ onReady,
76
+ onUnknownTokens,
77
+ });
78
+ const hasFields = mergeFields !== undefined;
79
+ const options = (0, react_1.useMemo)(() => (0, editorTokens_1.fieldOptions)(mergeFields !== null && mergeFields !== void 0 ? mergeFields : [], tokenSyntax),
80
+ // eslint-disable-next-line react-hooks/exhaustive-deps
81
+ [JSON.stringify(mergeFields), tokenSyntax.open, tokenSyntax.close]);
82
+ const [dynamicText, setDynamicText] = (0, react_1.useState)(null);
53
83
  // Jodit portals dialogs/toolbar-dropdown popups (search & replace, image/link properties,
54
84
  // font-size list, ...) to `document.body` by default (`Dialog.destination` /
55
85
  // `getPopupViewRoot`), outside this wrapper's DOM subtree — so the CSS custom properties
@@ -84,6 +114,24 @@ const Editor = react_1.default.memo(function Editor({ value, key, config, editor
84
114
  observer.observe(popupRoot, { childList: true, subtree: true });
85
115
  return () => observer.disconnect();
86
116
  }, [popupRoot]);
87
- return ((0, jsx_runtime_1.jsx)(react_2.Box, { ref: setPopupRoot, sx: themeVars, children: (0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: null, children: (0, jsx_runtime_1.jsx)(JoditEditor, { config: stableConfig, value: value, ref: editorRef, className: className, onChange: onChange, onBlur: onBlur }, key) }) }));
117
+ const unknownActive = hasFields && unknown.length > 0 && !(error && errorMessage);
118
+ const ids = (0, useFieldIds_1.useFieldIds)(undefined, { error, errorMessage, helperText });
119
+ const describedBy = unknownActive ? ids.errorId : ids.describedBy;
120
+ const invalid = Boolean(error) || unknownActive;
121
+ const isReadOnly = Boolean(stableConfig === null || stableConfig === void 0 ? void 0 : stableConfig.readonly);
122
+ (0, react_1.useEffect)(() => {
123
+ if (!instance)
124
+ return;
125
+ const el = instance.editor;
126
+ const set = (name, val) => val === undefined ? el.removeAttribute(name) : el.setAttribute(name, val);
127
+ set("role", "textbox");
128
+ set("aria-multiline", "true");
129
+ set("aria-labelledby", label ? ids.labelId : undefined);
130
+ set("aria-describedby", describedBy);
131
+ set("aria-invalid", invalid ? "true" : undefined);
132
+ set("aria-required", isRequired ? "true" : undefined);
133
+ }, [instance, label, ids.labelId, describedBy, invalid, isRequired]);
134
+ const showFieldBar = hasFields || Boolean(toolbarButtons === null || toolbarButtons === void 0 ? void 0 : toolbarButtons.length);
135
+ return ((0, jsx_runtime_1.jsxs)(react_2.Box, { "data-testid": testId, children: [label && ((0, jsx_runtime_1.jsxs)(react_2.Box, { as: "label", id: ids.labelId, display: "block", onClick: () => api.focus(), ...(0, FormLabel_1.fieldLabelStyles)(theme.colors), children: [label, isRequired && ((0, jsx_runtime_1.jsx)("span", { "aria-hidden": true, style: { color: theme.colors.semanticText.error, marginLeft: "0.125rem" }, children: "*" }))] })), showFieldBar && ((0, jsx_runtime_1.jsx)(EditorFieldBar_1.default, { options: options, labels: text, api: api, isDisabled: !instance || isReadOnly, showFieldMenu: hasFields, showMakeDynamic: hasFields && (showMakeDynamic !== null && showMakeDynamic !== void 0 ? showMakeDynamic : true), buttons: toolbarButtons !== null && toolbarButtons !== void 0 ? toolbarButtons : [], onMakeDynamic: () => setDynamicText(api.getSelectionText()) })), (0, jsx_runtime_1.jsx)(react_2.Box, { ref: setPopupRoot, sx: { ...themeVars, ...(0, editorTokenStyles_1.chipStyles)(theme.colors) }, children: (0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: null, children: popupRoot && ((0, jsx_runtime_1.jsx)(JoditEditor, { config: stableConfig, value: joditValue, ref: editorRef, editorRef: attachEditor, className: className }, key)) }) }), unknownActive ? ((0, jsx_runtime_1.jsx)(ErrorMessage_1.default, { id: ids.errorId, errorMessage: text.unknownTokens(unknown.join(", ")) })) : ((0, jsx_runtime_1.jsx)(FieldMessage_1.default, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })), hasFields && ((0, jsx_runtime_1.jsx)(MakeDynamicDialog_1.default, { selectedText: dynamicText, options: options, labels: text, countFor: api.countOccurrences, onReplace: api.replaceAll, onClose: () => setDynamicText(null) }))] }));
88
136
  });
89
137
  exports.default = Editor;
@@ -0,0 +1,15 @@
1
+ import type { OptionProp } from "../Select/SelectProps";
2
+ import type { EditorApi, EditorToolbarButton } from "./EditorProps";
3
+ import type { ResolvedEditorLabels } from "./editorLabels";
4
+ type EditorFieldBarProps = {
5
+ options: OptionProp[];
6
+ labels: ResolvedEditorLabels;
7
+ api: EditorApi;
8
+ isDisabled: boolean;
9
+ showFieldMenu: boolean;
10
+ showMakeDynamic: boolean;
11
+ buttons: EditorToolbarButton[];
12
+ onMakeDynamic: () => void;
13
+ };
14
+ export default function EditorFieldBar({ options, labels, api, isDisabled, showFieldMenu, showMakeDynamic, buttons, onMakeDynamic, }: EditorFieldBarProps): import("react/jsx-runtime").JSX.Element;
15
+ export {};
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = EditorFieldBar;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("@chakra-ui/react");
9
+ const lucide_react_1 = require("lucide-react");
10
+ const Button_1 = __importDefault(require("../Button/Button"));
11
+ const Select_1 = __importDefault(require("../Select/Select"));
12
+ function EditorFieldBar({ options, labels, api, isDisabled, showFieldMenu, showMakeDynamic, buttons, onMakeDynamic, }) {
13
+ return ((0, jsx_runtime_1.jsxs)(react_1.Flex, { role: "toolbar", "aria-label": labels.toolbar, gap: 2, wrap: "wrap", align: "center", mb: 2, children: [showFieldMenu && ((0, jsx_runtime_1.jsx)(react_1.Box, { width: { base: "100%", sm: "16rem" }, children: (0, jsx_runtime_1.jsx)(Select_1.default, { native: false, "aria-label": labels.insertField, placeholder: labels.insertField, options: options, value: "", noOptionsMessage: labels.noFields, isDisabled: isDisabled, onChange: (option) => {
14
+ if (!option)
15
+ return;
16
+ api.insertField(String(option.id));
17
+ window.setTimeout(api.focus, 0);
18
+ }, "data-testid": "editor-insert-field" }) })), showMakeDynamic && ((0, jsx_runtime_1.jsx)(Button_1.default, { variant: "outline", colorScheme: "gray", leftIcon: (0, jsx_runtime_1.jsx)(lucide_react_1.WandSparkles, { size: 16, "aria-hidden": true }), label: labels.makeDynamic, onClick: onMakeDynamic, isDisabled: isDisabled, "data-testid": "editor-make-dynamic" })), buttons.map((button) => ((0, jsx_runtime_1.jsx)(Button_1.default, { variant: "ghost", colorScheme: "gray", leftIcon: button.icon ? (0, jsx_runtime_1.jsx)(react_1.Icon, { as: button.icon, boxSize: 4, "aria-hidden": true }) : undefined, label: button.label, onClick: () => button.onClick(api), isDisabled: isDisabled || button.isDisabled, "data-testid": `editor-button-${button.id}` }, button.id)))] }));
19
+ }
@@ -1,10 +1,83 @@
1
1
  import type JoditEditor from "jodit-react";
2
- import type { ComponentProps } from "react";
2
+ import type { ComponentProps, ElementType, Ref } from "react";
3
3
  type JoditEditorProps = ComponentProps<typeof JoditEditor>;
4
+ /** A field a template can reference; `path` is what the stored token carries. */
5
+ export type EditorMergeField = {
6
+ /** Dotted path the backend resolves, e.g. `candidate.name`. */
7
+ path: string;
8
+ label: string;
9
+ /** Heading the field is listed under in the Insert field menu. */
10
+ group?: string;
11
+ /** Example value, shown on the chip's hover title. */
12
+ sample?: string;
13
+ };
14
+ /** How a token is written in the stored HTML. */
15
+ export type EditorTokenSyntax = {
16
+ open: string;
17
+ close: string;
18
+ };
19
+ /** Imperative handle for toolbar buttons and `apiRef`; every method works on the live editor. */
20
+ export interface EditorApi {
21
+ insertHTML(html: string): void;
22
+ insertField(path: string): void;
23
+ getSelectionText(): string;
24
+ /** Replaces every plain-text occurrence outside tokens and tags; returns the count. */
25
+ replaceAll(text: string, path: string): number;
26
+ countOccurrences(text: string): number;
27
+ focus(): void;
28
+ }
29
+ /** An extra button in the strip above the editor. */
30
+ export type EditorToolbarButton = {
31
+ id: string;
32
+ label: string;
33
+ icon?: ElementType;
34
+ /** Read through a ref — a new function each render never re-creates the editor. */
35
+ onClick: (api: EditorApi) => void;
36
+ isDisabled?: boolean;
37
+ };
38
+ /** Every visible string of the merge-field UI; omit a key to keep its English default. */
39
+ export type EditorLabels = Partial<{
40
+ insertField: string;
41
+ noFields: string;
42
+ makeDynamic: string;
43
+ dynamicTitle: string;
44
+ dynamicField: string;
45
+ dynamicFieldPlaceholder: string;
46
+ dynamicCount: (n: number) => string;
47
+ dynamicReplace: string;
48
+ dynamicNoSelection: string;
49
+ unknownTokens: (list: string) => string;
50
+ cancel: string;
51
+ dynamicSelected: string;
52
+ toolbar: string;
53
+ fieldChip: (label: string) => string;
54
+ }>;
4
55
  /** Props for the lazy-loaded Jodit wrapper; a `config.theme`/`config.popupRoot` you pass overrides this library's own dark-mode fix. */
5
56
  export type EditorProps = Pick<JoditEditorProps, "value" | "config" | "className" | "onChange" | "onBlur"> & {
6
57
  /** `key` is dead (React reserves it, never reaches Jodit); `editorRef.current` stays null until the lazy chunk loads. */
7
58
  editorRef?: JoditEditorProps["ref"];
8
59
  key?: string | number;
60
+ label?: string;
61
+ isRequired?: boolean;
62
+ /** Hint under the editor; replaced by `errorMessage` while `error` is true. */
63
+ helperText?: string;
64
+ /** Marks the editor invalid; `errorMessage` shows only while this is true. */
65
+ error?: boolean;
66
+ errorMessage?: string;
67
+ /** Enables the Insert field menu, token chips and Make dynamic. */
68
+ mergeFields?: EditorMergeField[];
69
+ /** Token syntax in the stored HTML; default `{ open: "${", close: "}" }`. */
70
+ tokenSyntax?: EditorTokenSyntax;
71
+ /** Shows the Make dynamic button; defaults to true when `mergeFields` is set. */
72
+ showMakeDynamic?: boolean;
73
+ /** Extra buttons beside Insert field; handlers are read through refs. */
74
+ toolbarButtons?: EditorToolbarButton[];
75
+ /** Fires with the paths of tokens that match no merge field, when that set changes. */
76
+ onUnknownTokens?: (paths: string[]) => void;
77
+ /** Imperative API, available after the lazy chunk loads (`onReady` fires then). */
78
+ apiRef?: Ref<EditorApi>;
79
+ onReady?: () => void;
80
+ labels?: EditorLabels;
81
+ "data-testid"?: string;
9
82
  };
10
83
  export {};
@@ -0,0 +1,12 @@
1
+ import type { OptionProp } from "../Select/SelectProps";
2
+ import type { ResolvedEditorLabels } from "./editorLabels";
3
+ type MakeDynamicDialogProps = {
4
+ selectedText: string | null;
5
+ options: OptionProp[];
6
+ labels: ResolvedEditorLabels;
7
+ countFor: (text: string) => number;
8
+ onReplace: (text: string, path: string) => void;
9
+ onClose: () => void;
10
+ };
11
+ export default function MakeDynamicDialog({ selectedText, options, labels, countFor, onReplace, onClose, }: MakeDynamicDialogProps): import("react/jsx-runtime").JSX.Element;
12
+ export {};
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.default = MakeDynamicDialog;
40
+ const jsx_runtime_1 = require("react/jsx-runtime");
41
+ const react_1 = require("react");
42
+ const react_2 = require("@chakra-ui/react");
43
+ const Button_1 = __importDefault(require("../Button/Button"));
44
+ const Modal_1 = __importStar(require("../Modal/Modal"));
45
+ const Select_1 = __importDefault(require("../Select/Select"));
46
+ const useCustomTheme_1 = require("../../Theme/useCustomTheme");
47
+ function MakeDynamicDialog({ selectedText, options, labels, countFor, onReplace, onClose, }) {
48
+ const { colors } = (0, useCustomTheme_1.useCustomTheme)();
49
+ const [path, setPath] = (0, react_1.useState)("");
50
+ const count = selectedText ? countFor(selectedText) : 0;
51
+ const close = () => {
52
+ setPath("");
53
+ onClose();
54
+ };
55
+ return ((0, jsx_runtime_1.jsxs)(Modal_1.default, { isOpen: selectedText !== null, onClose: close, size: "md", isCentered: true, title: labels.dynamicTitle, children: [(0, jsx_runtime_1.jsx)(Modal_1.ModalBody, { children: selectedText ? ((0, jsx_runtime_1.jsxs)(react_2.Flex, { direction: "column", gap: 4, "data-testid": "editor-dynamic-dialog", children: [(0, jsx_runtime_1.jsxs)(react_2.Box, { children: [(0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "xs", color: colors.textMuted, children: labels.dynamicSelected }), (0, jsx_runtime_1.jsxs)(react_2.Text, { fontSize: "sm", fontWeight: 500, color: colors.text[800], noOfLines: 3, children: ["\u201C", selectedText, "\u201D"] })] }), (0, jsx_runtime_1.jsx)(Select_1.default, { native: false, label: labels.dynamicField, placeholder: labels.dynamicFieldPlaceholder, options: options, value: path, onChange: (option) => setPath(option ? String(option.id) : ""), "data-testid": "editor-dynamic-field" }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "sm", color: colors.textMuted, "aria-live": "polite", children: labels.dynamicCount(count) })] })) : ((0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "sm", color: colors.text[800], "data-testid": "editor-dynamic-empty", children: labels.dynamicNoSelection })) }), (0, jsx_runtime_1.jsx)(Modal_1.ModalFooter, { children: (0, jsx_runtime_1.jsxs)(react_2.Flex, { gap: 2, justify: "flex-end", w: "100%", children: [(0, jsx_runtime_1.jsx)(Button_1.default, { variant: "ghost", colorScheme: "gray", label: labels.cancel, onClick: close }), selectedText ? ((0, jsx_runtime_1.jsx)(Button_1.default, { variant: "solid", colorScheme: "primary", label: labels.dynamicReplace, onClick: () => {
56
+ onReplace(selectedText, path);
57
+ close();
58
+ }, isDisabled: !path || count === 0, "data-testid": "editor-dynamic-apply" })) : null] }) })] }));
59
+ }
@@ -0,0 +1,4 @@
1
+ import type { EditorLabels } from "./EditorProps";
2
+ export type ResolvedEditorLabels = Required<EditorLabels>;
3
+ export declare const DEFAULT_EDITOR_LABELS: ResolvedEditorLabels;
4
+ export declare const resolveEditorLabels: (labels?: EditorLabels) => ResolvedEditorLabels;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveEditorLabels = exports.DEFAULT_EDITOR_LABELS = void 0;
4
+ exports.DEFAULT_EDITOR_LABELS = {
5
+ insertField: "Insert field",
6
+ noFields: "No fields",
7
+ makeDynamic: "Make dynamic",
8
+ dynamicTitle: "Make dynamic",
9
+ dynamicField: "Field",
10
+ dynamicFieldPlaceholder: "Choose a field",
11
+ dynamicCount: (n) => `${n} ${n === 1 ? "occurrence" : "occurrences"} will be replaced`,
12
+ dynamicReplace: "Replace",
13
+ dynamicNoSelection: "Select some text in the editor first.",
14
+ unknownTokens: (list) => `Unknown fields: ${list}`,
15
+ cancel: "Cancel",
16
+ dynamicSelected: "Selected text",
17
+ toolbar: "Template fields",
18
+ fieldChip: (label) => `Field: ${label}`,
19
+ };
20
+ const resolveEditorLabels = (labels) => ({
21
+ ...exports.DEFAULT_EDITOR_LABELS,
22
+ ...labels,
23
+ });
24
+ exports.resolveEditorLabels = resolveEditorLabels;
@@ -0,0 +1,28 @@
1
+ import type { PaletteProps } from "../../Theme/tokens/types";
2
+ export declare const chipColors: (colors: PaletteProps) => {
3
+ known: {
4
+ bg: string;
5
+ ink: string;
6
+ };
7
+ unknown: {
8
+ bg: string;
9
+ ink: string;
10
+ };
11
+ };
12
+ export declare const chipStyles: (colors: PaletteProps) => {
13
+ ".pxl-token": {
14
+ display: string;
15
+ px: string;
16
+ borderRadius: string;
17
+ border: string;
18
+ bg: string;
19
+ color: string;
20
+ cursor: string;
21
+ };
22
+ ".pxl-token[data-known=\"false\"]": {
23
+ bg: string;
24
+ color: string;
25
+ border: string;
26
+ borderColor: string;
27
+ };
28
+ };
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.chipStyles = exports.chipColors = void 0;
4
+ const editorTokens_1 = require("./editorTokens");
5
+ const chipColors = (colors) => ({
6
+ known: { bg: colors.primary.opacity[8], ink: colors.accentText },
7
+ unknown: { bg: "transparent", ink: colors.semanticText.error },
8
+ });
9
+ exports.chipColors = chipColors;
10
+ const chipStyles = (colors) => {
11
+ const { known, unknown } = (0, exports.chipColors)(colors);
12
+ return {
13
+ [`.${editorTokens_1.CHIP_CLASS}`]: {
14
+ display: "inline-block",
15
+ px: "0.25rem",
16
+ borderRadius: "sm",
17
+ border: "1px solid transparent",
18
+ bg: known.bg,
19
+ color: known.ink,
20
+ cursor: "default",
21
+ },
22
+ [`.${editorTokens_1.CHIP_CLASS}[data-known="false"]`]: {
23
+ bg: unknown.bg,
24
+ color: unknown.ink,
25
+ border: "1px dashed",
26
+ borderColor: unknown.ink,
27
+ },
28
+ };
29
+ };
30
+ exports.chipStyles = chipStyles;
@@ -0,0 +1,26 @@
1
+ import type { EditorMergeField, EditorTokenSyntax } from "./EditorProps";
2
+ import type { OptionProp } from "../Select/SelectProps";
3
+ export declare const DEFAULT_TOKEN_SYNTAX: EditorTokenSyntax;
4
+ export declare const CHIP_CLASS = "pxl-token";
5
+ export declare const fieldToken: (path: string, syntax?: EditorTokenSyntax) => string;
6
+ export declare function fieldOptions(fields: EditorMergeField[], syntax?: EditorTokenSyntax): OptionProp[];
7
+ export declare function extractTokens(html: string, syntax?: EditorTokenSyntax): string[];
8
+ export declare function unknownTokens(html: string, fields: EditorMergeField[], syntax?: EditorTokenSyntax): string[];
9
+ /** Replaces every whitespace-insensitive occurrence of `selectedText` outside tags and tokens. */
10
+ export declare function makeDynamic(html: string, selectedText: string, path: string, syntax?: EditorTokenSyntax): {
11
+ html: string;
12
+ count: number;
13
+ };
14
+ export declare function countOccurrences(html: string, selectedText: string, syntax?: EditorTokenSyntax): number;
15
+ export type ChipContext = {
16
+ fields: EditorMergeField[];
17
+ syntax: EditorTokenSyntax;
18
+ chipLabel: (label: string) => string;
19
+ };
20
+ export declare function chipHtml(path: string, { fields, syntax, chipLabel }: ChipContext): string;
21
+ /** Raw `${path}` tokens in text nodes become non-editable chip spans; tags are left alone. */
22
+ export declare function chipify(html: string, context: ChipContext): string;
23
+ /** Inverse of `chipify`: every chip span collapses back to its raw token. */
24
+ export declare function serializeChips(html: string, syntax?: EditorTokenSyntax): string;
25
+ /** The chip a Backspace (backward) or Delete (forward) at a collapsed caret would eat, if any. */
26
+ export declare function adjacentChip(range: Range, direction: "backward" | "forward"): HTMLElement | null;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fieldToken = exports.CHIP_CLASS = exports.DEFAULT_TOKEN_SYNTAX = void 0;
4
+ exports.fieldOptions = fieldOptions;
5
+ exports.extractTokens = extractTokens;
6
+ exports.unknownTokens = unknownTokens;
7
+ exports.makeDynamic = makeDynamic;
8
+ exports.countOccurrences = countOccurrences;
9
+ exports.chipHtml = chipHtml;
10
+ exports.chipify = chipify;
11
+ exports.serializeChips = serializeChips;
12
+ exports.adjacentChip = adjacentChip;
13
+ exports.DEFAULT_TOKEN_SYNTAX = { open: "${", close: "}" };
14
+ exports.CHIP_CLASS = "pxl-token";
15
+ const PATH_CHARS = "[A-Za-z0-9_.]+";
16
+ const TAG_SPLIT = /(<[^>]*>)/;
17
+ const CHIP_PATTERN = /<span\b([^>]*\bdata-field=(["'])([^"']*)\2[^>]*)>[^<]*<\/span>/g;
18
+ const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
19
+ const escapeHtml = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
20
+ const escapeAttr = (text) => escapeHtml(text).replace(/"/g, "&quot;");
21
+ const patternsFor = ({ open, close }) => {
22
+ const o = escapeRegExp(open);
23
+ const c = escapeRegExp(close);
24
+ return {
25
+ match: new RegExp(`${o}(${PATH_CHARS})${c}`, "g"),
26
+ split: new RegExp(`(${o}(?:(?!${c})[\\s\\S])*${c})`),
27
+ };
28
+ };
29
+ const fieldToken = (path, syntax = exports.DEFAULT_TOKEN_SYNTAX) => `${syntax.open}${path}${syntax.close}`;
30
+ exports.fieldToken = fieldToken;
31
+ function fieldOptions(fields, syntax = exports.DEFAULT_TOKEN_SYNTAX) {
32
+ const groups = [...new Set(fields.map((field) => field.group))];
33
+ return groups.flatMap((group) => fields
34
+ .filter((field) => field.group === group)
35
+ .map((field) => ({
36
+ id: field.path,
37
+ label: field.label,
38
+ group,
39
+ description: (0, exports.fieldToken)(field.path, syntax),
40
+ })));
41
+ }
42
+ function extractTokens(html, syntax = exports.DEFAULT_TOKEN_SYNTAX) {
43
+ const { match } = patternsFor(syntax);
44
+ return [...new Set([...html.matchAll(match)].map((m) => m[1]))];
45
+ }
46
+ function unknownTokens(html, fields, syntax = exports.DEFAULT_TOKEN_SYNTAX) {
47
+ const known = new Set(fields.map((field) => field.path));
48
+ return extractTokens(html, syntax).filter((path) => !known.has(path));
49
+ }
50
+ function needlePattern(text) {
51
+ const words = text
52
+ .trim()
53
+ .split(/\s+/)
54
+ .map((word) => escapeRegExp(escapeHtml(word)));
55
+ return new RegExp(words.join("(?:\\s|&nbsp;|\\u00a0)+"), "g");
56
+ }
57
+ function mapTextOutsideTokens(html, syntax, mapText) {
58
+ const { split } = patternsFor(syntax);
59
+ return html
60
+ .split(TAG_SPLIT)
61
+ .map((segment) => segment.startsWith("<")
62
+ ? segment
63
+ : segment
64
+ .split(split)
65
+ .map((part, i) => (i % 2 === 1 ? part : mapText(part)))
66
+ .join(""))
67
+ .join("");
68
+ }
69
+ /** Replaces every whitespace-insensitive occurrence of `selectedText` outside tags and tokens. */
70
+ function makeDynamic(html, selectedText, path, syntax = exports.DEFAULT_TOKEN_SYNTAX) {
71
+ if (!selectedText.trim())
72
+ return { html, count: 0 };
73
+ const pattern = needlePattern(selectedText);
74
+ let count = 0;
75
+ const next = mapTextOutsideTokens(html, syntax, (text) => text.replace(pattern, () => {
76
+ count += 1;
77
+ return (0, exports.fieldToken)(path, syntax);
78
+ }));
79
+ return { html: next, count };
80
+ }
81
+ function countOccurrences(html, selectedText, syntax = exports.DEFAULT_TOKEN_SYNTAX) {
82
+ return makeDynamic(html, selectedText, "_", syntax).count;
83
+ }
84
+ function chipHtml(path, { fields, syntax, chipLabel }) {
85
+ const field = fields.find((f) => f.path === path);
86
+ const text = field ? field.label : (0, exports.fieldToken)(path, syntax);
87
+ const title = (field === null || field === void 0 ? void 0 : field.sample) ? ` title="${escapeAttr(field.sample)}"` : "";
88
+ return (`<span class="${exports.CHIP_CLASS}" data-field="${escapeAttr(path)}" data-known="${field ? "true" : "false"}"` +
89
+ ` contenteditable="false" role="img" aria-label="${escapeAttr(chipLabel(text))}"${title}>${escapeHtml(text)}</span>`);
90
+ }
91
+ /** Raw `${path}` tokens in text nodes become non-editable chip spans; tags are left alone. */
92
+ function chipify(html, context) {
93
+ const { match } = patternsFor(context.syntax);
94
+ return html
95
+ .split(TAG_SPLIT)
96
+ .map((segment) => segment.startsWith("<") ? segment : segment.replace(match, (_t, path) => chipHtml(path, context)))
97
+ .join("");
98
+ }
99
+ /** Inverse of `chipify`: every chip span collapses back to its raw token. */
100
+ function serializeChips(html, syntax = exports.DEFAULT_TOKEN_SYNTAX) {
101
+ return html.replace(CHIP_PATTERN, (_m, _attrs, _q, path) => (0, exports.fieldToken)(path, syntax));
102
+ }
103
+ const isChip = (node) => !!node && node.nodeType === 1 && node.hasAttribute("data-field");
104
+ const INVISIBLE = /[\u200b\ufeff]/g;
105
+ const isBlankText = (node) => !node.data.replace(INVISIBLE, "");
106
+ const skipEmptyText = (node, step) => {
107
+ let current = node;
108
+ while (current && current.nodeType === 3 && isBlankText(current))
109
+ current = current[step];
110
+ return current;
111
+ };
112
+ /** The chip a Backspace (backward) or Delete (forward) at a collapsed caret would eat, if any. */
113
+ function adjacentChip(range, direction) {
114
+ var _a;
115
+ if (!range.collapsed)
116
+ return null;
117
+ const node = range.startContainer;
118
+ const offset = range.startOffset;
119
+ const step = direction === "backward" ? "previousSibling" : "nextSibling";
120
+ let candidate;
121
+ if (node.nodeType === 3) {
122
+ const text = node.data;
123
+ const beyond = direction === "backward" ? text.slice(0, offset) : text.slice(offset);
124
+ const atEdge = !beyond.replace(INVISIBLE, "");
125
+ if (!atEdge)
126
+ return null;
127
+ candidate = node[step];
128
+ }
129
+ else {
130
+ candidate = (_a = node.childNodes[direction === "backward" ? offset - 1 : offset]) !== null && _a !== void 0 ? _a : null;
131
+ }
132
+ const found = skipEmptyText(candidate, step);
133
+ return isChip(found) ? found : null;
134
+ }
@@ -0,0 +1,40 @@
1
+ import type { Ref } from "react";
2
+ import type { EditorApi, EditorMergeField, EditorTokenSyntax } from "./EditorProps";
3
+ import { type ChipContext } from "./editorTokens";
4
+ export type JoditLike = {
5
+ value: string;
6
+ editor: HTMLElement;
7
+ s: {
8
+ isInsideArea: boolean;
9
+ range: Range;
10
+ selectRange: (range: Range, focus?: boolean) => unknown;
11
+ insertHTML: (html: string) => void;
12
+ focus: () => unknown;
13
+ };
14
+ events: {
15
+ on: (events: string, handler: (event: never) => unknown) => unknown;
16
+ };
17
+ synchronizeValues: () => void;
18
+ waitForReady: () => Promise<unknown>;
19
+ };
20
+ type BridgeArgs = {
21
+ value?: string;
22
+ onChange?: (value: string) => void;
23
+ onBlur?: (value: string, event: never) => void;
24
+ mergeFields?: EditorMergeField[];
25
+ syntax: EditorTokenSyntax;
26
+ chipLabel: (label: string) => string;
27
+ apiRef?: Ref<EditorApi>;
28
+ onReady?: () => void;
29
+ onUnknownTokens?: (paths: string[]) => void;
30
+ };
31
+ export declare function useEditorBridge({ value, onChange, onBlur, mergeFields, syntax, chipLabel, apiRef, onReady, onUnknownTokens, }: BridgeArgs): {
32
+ joditValue: string | undefined;
33
+ attachEditor: (jodit: JoditLike) => void;
34
+ api: EditorApi;
35
+ instance: JoditLike | null;
36
+ raw: string;
37
+ unknown: string[];
38
+ ctx: ChipContext;
39
+ };
40
+ export {};