@yurikilian/lex4 1.4.1 → 1.5.1

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 (42) hide show
  1. package/README.md +6 -4
  2. package/dist/ast/inline-mapper.d.ts.map +1 -1
  3. package/dist/ast/types.d.ts +1 -1
  4. package/dist/ast/types.d.ts.map +1 -1
  5. package/dist/components/CanvasControls.d.ts.map +1 -1
  6. package/dist/components/HeaderFooterToggle.d.ts +1 -0
  7. package/dist/components/HeaderFooterToggle.d.ts.map +1 -1
  8. package/dist/components/Lex4Editor.d.ts.map +1 -1
  9. package/dist/components/Toolbar.d.ts.map +1 -1
  10. package/dist/context/document-context.d.ts +1 -0
  11. package/dist/context/document-context.d.ts.map +1 -1
  12. package/dist/context/document-provider.d.ts.map +1 -1
  13. package/dist/context/toolbar-config.d.ts +18 -0
  14. package/dist/context/toolbar-config.d.ts.map +1 -0
  15. package/dist/extensions/variables-extension.d.ts.map +1 -1
  16. package/dist/i18n/defaults.d.ts.map +1 -1
  17. package/dist/i18n/pt-BR.d.ts.map +1 -1
  18. package/dist/i18n/types.d.ts +12 -0
  19. package/dist/i18n/types.d.ts.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/lex4-editor.cjs +730 -157
  23. package/dist/lex4-editor.cjs.map +1 -1
  24. package/dist/lex4-editor.js +714 -141
  25. package/dist/lex4-editor.js.map +1 -1
  26. package/dist/lexical/commands/block-commands.d.ts +5 -0
  27. package/dist/lexical/commands/block-commands.d.ts.map +1 -0
  28. package/dist/lexical/theme.d.ts.map +1 -1
  29. package/dist/lexical/utils/import-document-content.d.ts +4 -0
  30. package/dist/lexical/utils/import-document-content.d.ts.map +1 -0
  31. package/dist/style.css +51 -23
  32. package/dist/types/editor-handle.d.ts +2 -0
  33. package/dist/types/editor-handle.d.ts.map +1 -1
  34. package/dist/types/editor-props.d.ts +16 -0
  35. package/dist/types/editor-props.d.ts.map +1 -1
  36. package/dist/utils/text-style.d.ts +7 -0
  37. package/dist/utils/text-style.d.ts.map +1 -0
  38. package/dist/variables/variable-formatting.d.ts +11 -0
  39. package/dist/variables/variable-formatting.d.ts.map +1 -0
  40. package/dist/variables/variable-node.d.ts +10 -2
  41. package/dist/variables/variable-node.d.ts.map +1 -1
  42. package/package.json +1 -1
@@ -8,13 +8,15 @@ const React = require("react");
8
8
  const lexical = require("lexical");
9
9
  const LexicalComposerContext = require("@lexical/react/LexicalComposerContext");
10
10
  const list = require("@lexical/list");
11
+ const selection = require("@lexical/selection");
12
+ const richText = require("@lexical/rich-text");
13
+ const useLexicalNodeSelection = require("@lexical/react/useLexicalNodeSelection");
11
14
  const LexicalComposer = require("@lexical/react/LexicalComposer");
12
15
  const LexicalRichTextPlugin = require("@lexical/react/LexicalRichTextPlugin");
13
16
  const LexicalContentEditable = require("@lexical/react/LexicalContentEditable");
14
17
  const LexicalListPlugin = require("@lexical/react/LexicalListPlugin");
15
18
  const LexicalErrorBoundary = require("@lexical/react/LexicalErrorBoundary");
16
19
  const LexicalOnChangePlugin = require("@lexical/react/LexicalOnChangePlugin");
17
- const richText = require("@lexical/rich-text");
18
20
  function createEmptyPage(id) {
19
21
  return {
20
22
  id: id ?? crypto.randomUUID(),
@@ -473,6 +475,15 @@ const DEFAULT_TRANSLATIONS = {
473
475
  bulletList: "Bullet List",
474
476
  indent: "Indent",
475
477
  outdent: "Outdent",
478
+ history: "History",
479
+ blockType: "Block type",
480
+ paragraph: "Paragraph",
481
+ heading1: "Heading 1",
482
+ heading2: "Heading 2",
483
+ heading3: "Heading 3",
484
+ heading4: "Heading 4",
485
+ heading5: "Heading 5",
486
+ heading6: "Heading 6",
476
487
  openHistory: "Open History",
477
488
  closeHistory: "Close History"
478
489
  },
@@ -504,7 +515,9 @@ const DEFAULT_TRANSLATIONS = {
504
515
  indentedContent: "Indented content",
505
516
  outdentedContent: "Outdented content",
506
517
  fontChanged: "Font changed to {{value}}",
507
- fontSizeChanged: "Font size changed to {{value}}pt"
518
+ fontSizeChanged: "Font size changed to {{value}}pt",
519
+ blockTypeChanged: "Block type changed to {{value}}",
520
+ insertedDocumentContent: "Inserted document content"
508
521
  }
509
522
  },
510
523
  variables: {
@@ -604,6 +617,15 @@ const PT_BR_TRANSLATIONS = {
604
617
  bulletList: "Lista com Marcadores",
605
618
  indent: "Aumentar Recuo",
606
619
  outdent: "Diminuir Recuo",
620
+ history: "Histórico",
621
+ blockType: "Tipo de bloco",
622
+ paragraph: "Parágrafo",
623
+ heading1: "Título 1",
624
+ heading2: "Título 2",
625
+ heading3: "Título 3",
626
+ heading4: "Título 4",
627
+ heading5: "Título 5",
628
+ heading6: "Título 6",
607
629
  openHistory: "Abrir Histórico",
608
630
  closeHistory: "Fechar Histórico"
609
631
  },
@@ -635,7 +657,9 @@ const PT_BR_TRANSLATIONS = {
635
657
  indentedContent: "Conteúdo recuado",
636
658
  outdentedContent: "Recuo reduzido",
637
659
  fontChanged: "Fonte alterada para {{value}}",
638
- fontSizeChanged: "Tamanho da fonte alterado para {{value}}pt"
660
+ fontSizeChanged: "Tamanho da fonte alterado para {{value}}pt",
661
+ blockTypeChanged: "Tipo de bloco alterado para {{value}}",
662
+ insertedDocumentContent: "Conteúdo do documento inserido"
639
663
  }
640
664
  },
641
665
  variables: {
@@ -797,25 +821,25 @@ function captureCaretSelection(editor) {
797
821
  };
798
822
  let caretSelection = null;
799
823
  editor.getEditorState().read(() => {
800
- const selection = lexical.$createRangeSelectionFromDom(window.getSelection(), editor) ?? lexical.$getSelection();
801
- if (!lexical.$isRangeSelection(selection)) {
824
+ const selection2 = lexical.$createRangeSelectionFromDom(window.getSelection(), editor) ?? lexical.$getSelection();
825
+ if (!lexical.$isRangeSelection(selection2)) {
802
826
  return;
803
827
  }
804
828
  caretSelection = {
805
829
  anchor: {
806
- key: selection.anchor.key,
807
- offset: selection.anchor.offset,
808
- type: selection.anchor.type
830
+ key: selection2.anchor.key,
831
+ offset: selection2.anchor.offset,
832
+ type: selection2.anchor.type
809
833
  },
810
834
  focus: {
811
- key: selection.focus.key,
812
- offset: selection.focus.offset,
813
- type: selection.focus.type
835
+ key: selection2.focus.key,
836
+ offset: selection2.focus.offset,
837
+ type: selection2.focus.type
814
838
  },
815
839
  anchorTextOffset: domSelection && selectionInRoot(domSelection.anchorNode) ? getTextOffset(domSelection.anchorNode, domSelection.anchorOffset) : 0,
816
840
  focusTextOffset: domSelection && selectionInRoot(domSelection.focusNode) ? getTextOffset(domSelection.focusNode, domSelection.focusOffset) : 0,
817
- format: selection.format,
818
- style: selection.style
841
+ format: selection2.format,
842
+ style: selection2.style
819
843
  };
820
844
  });
821
845
  return caretSelection;
@@ -1286,6 +1310,7 @@ const DocumentProvider = ({
1286
1310
  activePageId,
1287
1311
  setActivePageId,
1288
1312
  activeEditor: activeEditorRef.current,
1313
+ activeCaretPosition: activeCaretPositionRef.current,
1289
1314
  consumePendingCaretPosition,
1290
1315
  consumePendingFocusAtEnd,
1291
1316
  requestFocusAtEnd,
@@ -1892,6 +1917,36 @@ const HistorySidebar = () => {
1892
1917
  }
1893
1918
  );
1894
1919
  };
1920
+ const DEFAULT_TOOLBAR_CONTROL_CONFIG = {
1921
+ visible: true,
1922
+ showLabel: true
1923
+ };
1924
+ const DEFAULT_TOOLBAR_CONFIG = {
1925
+ history: DEFAULT_TOOLBAR_CONTROL_CONFIG,
1926
+ variables: DEFAULT_TOOLBAR_CONTROL_CONFIG,
1927
+ headerFooter: DEFAULT_TOOLBAR_CONTROL_CONFIG
1928
+ };
1929
+ const ToolbarConfigContext = React.createContext(DEFAULT_TOOLBAR_CONFIG);
1930
+ function resolveControlConfig(config) {
1931
+ return {
1932
+ visible: (config == null ? void 0 : config.visible) ?? true,
1933
+ showLabel: (config == null ? void 0 : config.showLabel) ?? true
1934
+ };
1935
+ }
1936
+ function normalizeToolbarConfig(config) {
1937
+ return {
1938
+ history: resolveControlConfig(config == null ? void 0 : config.history),
1939
+ variables: resolveControlConfig(config == null ? void 0 : config.variables),
1940
+ headerFooter: resolveControlConfig(config == null ? void 0 : config.headerFooter)
1941
+ };
1942
+ }
1943
+ const ToolbarConfigProvider = ({ toolbar, children }) => {
1944
+ const resolvedConfig = React.useMemo(() => normalizeToolbarConfig(toolbar), [toolbar]);
1945
+ return /* @__PURE__ */ jsxRuntime.jsx(ToolbarConfigContext.Provider, { value: resolvedConfig, children });
1946
+ };
1947
+ function useToolbarConfig() {
1948
+ return React.useContext(ToolbarConfigContext);
1949
+ }
1895
1950
  function resolveExtensions(extensions) {
1896
1951
  const resolved = {
1897
1952
  nodes: [],
@@ -1997,9 +2052,9 @@ const SUPPORTED_FONTS = [
1997
2052
  ];
1998
2053
  function applyFontFamily(editor, fontFamily) {
1999
2054
  editor.update(() => {
2000
- const selection = lexical.$getSelection();
2001
- if (!lexical.$isRangeSelection(selection)) return;
2002
- const nodes = selection.getNodes();
2055
+ const selection2 = lexical.$getSelection();
2056
+ if (!lexical.$isRangeSelection(selection2)) return;
2057
+ const nodes = selection2.getNodes();
2003
2058
  for (const node of nodes) {
2004
2059
  if (lexical.$isTextNode(node)) {
2005
2060
  node.setStyle(`font-family: ${fontFamily}`);
@@ -2024,11 +2079,12 @@ const SUPPORTED_FONT_SIZES = [
2024
2079
  48,
2025
2080
  72
2026
2081
  ];
2082
+ const DEFAULT_FONT_SIZE = 12;
2027
2083
  function applyFontSize(editor, size) {
2028
2084
  editor.update(() => {
2029
- const selection = lexical.$getSelection();
2030
- if (!lexical.$isRangeSelection(selection)) return;
2031
- const nodes = selection.getNodes();
2085
+ const selection2 = lexical.$getSelection();
2086
+ if (!lexical.$isRangeSelection(selection2)) return;
2087
+ const nodes = selection2.getNodes();
2032
2088
  for (const node of nodes) {
2033
2089
  if (lexical.$isTextNode(node)) {
2034
2090
  const existing = node.getStyle();
@@ -2074,9 +2130,366 @@ function indentContent(editor) {
2074
2130
  function outdentContent(editor) {
2075
2131
  editor.dispatchCommand(lexical.OUTDENT_CONTENT_COMMAND, void 0);
2076
2132
  }
2133
+ function setBlockType(editor, blockType) {
2134
+ editor.update(() => {
2135
+ const selection$1 = lexical.$getSelection();
2136
+ if (!lexical.$isRangeSelection(selection$1)) {
2137
+ return;
2138
+ }
2139
+ if (blockType === "paragraph") {
2140
+ selection.$setBlocksType(selection$1, () => lexical.$createParagraphNode());
2141
+ return;
2142
+ }
2143
+ selection.$setBlocksType(selection$1, () => richText.$createHeadingNode(blockType));
2144
+ });
2145
+ }
2146
+ function getActiveBlockType(editor) {
2147
+ let blockType = "paragraph";
2148
+ editor.getEditorState().read(() => {
2149
+ const selection2 = lexical.$getSelection();
2150
+ if (!lexical.$isRangeSelection(selection2)) {
2151
+ return;
2152
+ }
2153
+ const anchorNode = selection2.anchor.getNode();
2154
+ const topLevelElement = anchorNode.getTopLevelElementOrThrow();
2155
+ if (richText.$isHeadingNode(topLevelElement)) {
2156
+ blockType = topLevelElement.getTag();
2157
+ }
2158
+ });
2159
+ return blockType;
2160
+ }
2161
+ function extractStyleValue(style, property) {
2162
+ const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2163
+ const match = style.match(new RegExp(`${escapedProperty}:\\s*([^;]+)`));
2164
+ return match ? match[1].trim().replace(/['"]/g, "") : void 0;
2165
+ }
2166
+ function mergeStyleDeclaration(existingStyle, property, value) {
2167
+ const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2168
+ const stripped = existingStyle.replace(
2169
+ new RegExp(`${escapedProperty}:\\s*[^;]+;?\\s*`, "g"),
2170
+ ""
2171
+ ).trim();
2172
+ const declaration = `${property}: ${value}`;
2173
+ return stripped ? `${stripped}; ${declaration}` : declaration;
2174
+ }
2175
+ function extractFontFamilyFromStyle(style) {
2176
+ return extractStyleValue(style, "font-family");
2177
+ }
2178
+ function extractFontSizePtFromStyle(style) {
2179
+ const value = extractStyleValue(style, "font-size");
2180
+ if (!value) {
2181
+ return void 0;
2182
+ }
2183
+ const match = value.match(/^(\d+(?:\.\d+)?)\s*pt$/);
2184
+ return match ? parseFloat(match[1]) : void 0;
2185
+ }
2186
+ function mergeFontFamilyStyle(existingStyle, fontFamily) {
2187
+ return mergeStyleDeclaration(existingStyle, "font-family", fontFamily);
2188
+ }
2189
+ function mergeFontSizeStyle(existingStyle, size) {
2190
+ return mergeStyleDeclaration(existingStyle, "font-size", `${size}pt`);
2191
+ }
2192
+ const EMPTY_CONTEXT = {
2193
+ definitions: [],
2194
+ refreshDefinitions: () => {
2195
+ },
2196
+ getDefinition: () => void 0
2197
+ };
2198
+ const VariableContext = React.createContext(EMPTY_CONTEXT);
2199
+ const VariableProvider = ({
2200
+ initialDefinitions = [],
2201
+ children
2202
+ }) => {
2203
+ const [definitions, setDefinitions] = React.useState(initialDefinitions);
2204
+ const refresh = React.useCallback((newDefinitions) => {
2205
+ setDefinitions(newDefinitions);
2206
+ }, []);
2207
+ const getDefinition = React.useCallback(
2208
+ (key) => {
2209
+ return definitions.find((d) => d.key === key);
2210
+ },
2211
+ [definitions]
2212
+ );
2213
+ const value = React.useMemo(
2214
+ () => ({ definitions, refreshDefinitions: refresh, getDefinition }),
2215
+ [definitions, refresh, getDefinition]
2216
+ );
2217
+ return /* @__PURE__ */ jsxRuntime.jsx(VariableContext.Provider, { value, children });
2218
+ };
2219
+ function useVariables() {
2220
+ return React.useContext(VariableContext);
2221
+ }
2222
+ class VariableNode extends lexical.DecoratorNode {
2223
+ constructor(variableKey, format = 0, style = "", key) {
2224
+ super(key);
2225
+ __publicField(this, "__variableKey");
2226
+ __publicField(this, "__format");
2227
+ __publicField(this, "__style");
2228
+ this.__variableKey = variableKey;
2229
+ this.__format = format;
2230
+ this.__style = style;
2231
+ }
2232
+ static getType() {
2233
+ return "variable-node";
2234
+ }
2235
+ static clone(node) {
2236
+ return new VariableNode(node.__variableKey, node.__format, node.__style, node.__key);
2237
+ }
2238
+ getVariableKey() {
2239
+ return this.getLatest().__variableKey;
2240
+ }
2241
+ getFormat() {
2242
+ return this.getLatest().__format;
2243
+ }
2244
+ setFormat(format) {
2245
+ const writable = this.getWritable();
2246
+ writable.__format = format;
2247
+ return writable;
2248
+ }
2249
+ getStyle() {
2250
+ return this.getLatest().__style;
2251
+ }
2252
+ setStyle(style) {
2253
+ const writable = this.getWritable();
2254
+ writable.__style = style;
2255
+ return writable;
2256
+ }
2257
+ // -- Serialization --
2258
+ static importJSON(serializedNode) {
2259
+ return $createVariableNode(
2260
+ serializedNode.variableKey,
2261
+ serializedNode.format ?? 0,
2262
+ serializedNode.style ?? ""
2263
+ );
2264
+ }
2265
+ exportJSON() {
2266
+ return {
2267
+ type: "variable-node",
2268
+ version: 1,
2269
+ variableKey: this.__variableKey,
2270
+ format: this.__format,
2271
+ style: this.__style
2272
+ };
2273
+ }
2274
+ // -- DOM --
2275
+ createDOM() {
2276
+ const span = document.createElement("span");
2277
+ span.className = "lex4-variable";
2278
+ span.setAttribute("data-variable-key", this.__variableKey);
2279
+ span.setAttribute("data-testid", `variable-${this.__variableKey}`);
2280
+ span.contentEditable = "false";
2281
+ return span;
2282
+ }
2283
+ updateDOM() {
2284
+ return false;
2285
+ }
2286
+ exportDOM() {
2287
+ const span = document.createElement("span");
2288
+ span.setAttribute("data-variable-key", this.__variableKey);
2289
+ span.textContent = `{{${this.__variableKey}}}`;
2290
+ return { element: span };
2291
+ }
2292
+ static importDOM() {
2293
+ return null;
2294
+ }
2295
+ // -- Behavior --
2296
+ isInline() {
2297
+ return true;
2298
+ }
2299
+ isKeyboardSelectable() {
2300
+ return true;
2301
+ }
2302
+ getTextContent() {
2303
+ return `{{${this.__variableKey}}}`;
2304
+ }
2305
+ // -- Rendering --
2306
+ decorate() {
2307
+ return /* @__PURE__ */ jsxRuntime.jsx(
2308
+ VariableChip,
2309
+ {
2310
+ nodeKey: this.__key,
2311
+ variableKey: this.__variableKey,
2312
+ format: this.__format,
2313
+ styleValue: this.__style
2314
+ }
2315
+ );
2316
+ }
2317
+ }
2318
+ function VariableChip({
2319
+ nodeKey,
2320
+ variableKey,
2321
+ format,
2322
+ styleValue
2323
+ }) {
2324
+ const { getDefinition } = useVariables();
2325
+ const [editor] = LexicalComposerContext.useLexicalComposerContext();
2326
+ const [isSelected, setSelected, clearOtherSelections] = useLexicalNodeSelection.useLexicalNodeSelection(nodeKey);
2327
+ const def = getDefinition(variableKey);
2328
+ const label = (def == null ? void 0 : def.label) ?? variableKey;
2329
+ const group = def == null ? void 0 : def.group;
2330
+ const style = React.useMemo(() => {
2331
+ const fontFamily = extractFontFamilyFromStyle(styleValue);
2332
+ const fontSize = extractFontSizePtFromStyle(styleValue);
2333
+ return {
2334
+ ...fontFamily ? { fontFamily } : {},
2335
+ ...fontSize ? { fontSize: `${fontSize}pt` } : {}
2336
+ };
2337
+ }, [styleValue]);
2338
+ const className = [
2339
+ "lex4-variable-chip",
2340
+ isSelected && "lex4-variable-chip-selected",
2341
+ format & 1 ? "lex4-text-bold" : "",
2342
+ format & 2 ? "lex4-text-italic" : "",
2343
+ format & 8 ? "lex4-text-underline" : "",
2344
+ format & 4 ? "lex4-text-strikethrough" : ""
2345
+ ].filter(Boolean).join(" ");
2346
+ const handleClick = React.useCallback((event) => {
2347
+ event.preventDefault();
2348
+ if (!event.shiftKey) {
2349
+ clearOtherSelections();
2350
+ }
2351
+ setSelected(!isSelected);
2352
+ }, [clearOtherSelections, isSelected, setSelected]);
2353
+ React.useEffect(() => {
2354
+ const removeSelectedNodes = () => {
2355
+ editor.update(() => {
2356
+ const selection2 = lexical.$getSelection();
2357
+ if (!lexical.$isNodeSelection(selection2)) {
2358
+ const node = lexical.$getNodeByKey(nodeKey);
2359
+ if ($isVariableNode(node)) {
2360
+ node.remove();
2361
+ }
2362
+ return;
2363
+ }
2364
+ for (const node of selection2.getNodes()) {
2365
+ if ($isVariableNode(node)) {
2366
+ node.remove();
2367
+ }
2368
+ }
2369
+ });
2370
+ };
2371
+ const unregisterBackspace = editor.registerCommand(
2372
+ lexical.KEY_BACKSPACE_COMMAND,
2373
+ () => {
2374
+ if (!isSelected) {
2375
+ return false;
2376
+ }
2377
+ removeSelectedNodes();
2378
+ return true;
2379
+ },
2380
+ lexical.COMMAND_PRIORITY_LOW
2381
+ );
2382
+ const unregisterDelete = editor.registerCommand(
2383
+ lexical.KEY_DELETE_COMMAND,
2384
+ () => {
2385
+ if (!isSelected) {
2386
+ return false;
2387
+ }
2388
+ removeSelectedNodes();
2389
+ return true;
2390
+ },
2391
+ lexical.COMMAND_PRIORITY_LOW
2392
+ );
2393
+ return () => {
2394
+ unregisterBackspace();
2395
+ unregisterDelete();
2396
+ };
2397
+ }, [editor, isSelected, nodeKey]);
2398
+ return /* @__PURE__ */ jsxRuntime.jsx(
2399
+ "span",
2400
+ {
2401
+ className,
2402
+ "data-testid": `variable-chip-${variableKey}`,
2403
+ "data-variable-group": group,
2404
+ title: variableKey,
2405
+ style,
2406
+ onMouseDown: (event) => event.preventDefault(),
2407
+ onClick: handleClick,
2408
+ children: label
2409
+ }
2410
+ );
2411
+ }
2412
+ function $createVariableNode(variableKey, format = 0, style = "") {
2413
+ return lexical.$applyNodeReplacement(new VariableNode(variableKey, format, style));
2414
+ }
2415
+ function $isVariableNode(node) {
2416
+ return node instanceof VariableNode;
2417
+ }
2418
+ const FORMAT_MASKS = {
2419
+ bold: 1,
2420
+ italic: 2,
2421
+ strikethrough: 4,
2422
+ underline: 8
2423
+ };
2424
+ function withSelectedVariableNodes(editor, updater) {
2425
+ let updated = false;
2426
+ editor.update(() => {
2427
+ const selection2 = lexical.$getSelection();
2428
+ if (!lexical.$isNodeSelection(selection2)) {
2429
+ return;
2430
+ }
2431
+ const nodes = selection2.getNodes().filter($isVariableNode);
2432
+ if (nodes.length === 0) {
2433
+ return;
2434
+ }
2435
+ updater(nodes);
2436
+ updated = true;
2437
+ });
2438
+ return updated;
2439
+ }
2440
+ function getSelectedVariableNodes(editor) {
2441
+ let nodes = [];
2442
+ editor.getEditorState().read(() => {
2443
+ const selection2 = lexical.$getSelection();
2444
+ if (!lexical.$isNodeSelection(selection2)) {
2445
+ return;
2446
+ }
2447
+ nodes = selection2.getNodes().filter($isVariableNode);
2448
+ });
2449
+ return nodes;
2450
+ }
2451
+ function toggleSelectedVariableFormat(editor, format) {
2452
+ const mask = FORMAT_MASKS[format];
2453
+ if (!mask) {
2454
+ return false;
2455
+ }
2456
+ return withSelectedVariableNodes(editor, (nodes) => {
2457
+ const shouldEnable = nodes.some((node) => (node.getFormat() & mask) === 0);
2458
+ for (const node of nodes) {
2459
+ const nextFormat = shouldEnable ? node.getFormat() | mask : node.getFormat() & ~mask;
2460
+ node.setFormat(nextFormat);
2461
+ }
2462
+ });
2463
+ }
2464
+ function applyFontFamilyToSelectedVariables(editor, fontFamily) {
2465
+ return withSelectedVariableNodes(editor, (nodes) => {
2466
+ for (const node of nodes) {
2467
+ node.setStyle(mergeFontFamilyStyle(node.getStyle(), fontFamily));
2468
+ }
2469
+ });
2470
+ }
2471
+ function applyFontSizeToSelectedVariables(editor, size) {
2472
+ return withSelectedVariableNodes(editor, (nodes) => {
2473
+ for (const node of nodes) {
2474
+ node.setStyle(mergeFontSizeStyle(node.getStyle(), size));
2475
+ }
2476
+ });
2477
+ }
2478
+ function readSelectedVariableFormatting(editor) {
2479
+ const firstNode = getSelectedVariableNodes(editor)[0];
2480
+ if (!firstNode) {
2481
+ return {};
2482
+ }
2483
+ const style = firstNode.getStyle();
2484
+ return {
2485
+ fontFamily: extractFontFamilyFromStyle(style),
2486
+ fontSize: extractFontSizePtFromStyle(style)
2487
+ };
2488
+ }
2077
2489
  const HeaderFooterToggle = ({
2078
2490
  enabled,
2079
- onToggle
2491
+ onToggle,
2492
+ showLabel = true
2080
2493
  }) => {
2081
2494
  const t = useTranslations();
2082
2495
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -2086,18 +2499,20 @@ const HeaderFooterToggle = ({
2086
2499
  "data-testid": "header-footer-toggle",
2087
2500
  children: [
2088
2501
  /* @__PURE__ */ jsxRuntime.jsx(FileText, { size: 14, className: "lex4-hf-toggle-icon" }),
2089
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-hf-toggle-label", children: t.headerFooter.label }),
2502
+ showLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-hf-toggle-label", children: t.headerFooter.label }),
2090
2503
  /* @__PURE__ */ jsxRuntime.jsx(
2091
2504
  "button",
2092
2505
  {
2093
2506
  type: "button",
2094
2507
  role: "switch",
2095
2508
  "aria-checked": enabled,
2509
+ "aria-label": t.headerFooter.label,
2096
2510
  onMouseDown: (e) => e.preventDefault(),
2097
2511
  onClick: () => onToggle(!enabled),
2098
2512
  className: "lex4-hf-switch",
2099
2513
  style: { backgroundColor: enabled ? "var(--color-primary)" : "var(--color-muted)" },
2100
2514
  "data-testid": "header-footer-switch",
2515
+ title: t.headerFooter.label,
2101
2516
  children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-hf-switch-knob" })
2102
2517
  }
2103
2518
  )
@@ -2261,6 +2676,7 @@ const MenuItem = ({ icon, label, onClick, disabled, testId }) => /* @__PURE__ */
2261
2676
  const MenuDivider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-settings-divider" });
2262
2677
  const CanvasControls = () => {
2263
2678
  const { document: document2, dispatch, activePageId, runHistoryAction } = useDocument();
2679
+ const toolbarConfig = useToolbarConfig();
2264
2680
  const t = useTranslations();
2265
2681
  const runCanvasAction = React.useCallback((label, callback) => {
2266
2682
  runHistoryAction(
@@ -2327,12 +2743,16 @@ const CanvasControls = () => {
2327
2743
  dispatch({ type: "SET_PAGE_COUNTER_MODE", mode });
2328
2744
  });
2329
2745
  }, [dispatch, runCanvasAction, t.history.actions.pageCounterSet]);
2746
+ if (!toolbarConfig.headerFooter.visible) {
2747
+ return null;
2748
+ }
2330
2749
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group lex4-toolbar-group-gap", "data-testid": "header-footer-controls", children: [
2331
2750
  /* @__PURE__ */ jsxRuntime.jsx(
2332
2751
  HeaderFooterToggle,
2333
2752
  {
2334
2753
  enabled: document2.headerFooterEnabled,
2335
- onToggle: handleToggle
2754
+ onToggle: handleToggle,
2755
+ showLabel: toolbarConfig.headerFooter.showLabel
2336
2756
  }
2337
2757
  ),
2338
2758
  document2.headerFooterEnabled && /* @__PURE__ */ jsxRuntime.jsx(
@@ -2365,7 +2785,17 @@ const Toolbar = () => {
2365
2785
  undo
2366
2786
  } = useDocument();
2367
2787
  const { toolbarItems, toolbarEndItems } = useExtensions();
2788
+ const toolbarConfig = useToolbarConfig();
2368
2789
  const t = useTranslations();
2790
+ const [activeBlockType, setActiveBlockType] = React.useState("paragraph");
2791
+ const [activeFontFamily, setActiveFontFamily] = React.useState("Calibri");
2792
+ const [activeFontSize, setActiveFontSize] = React.useState(DEFAULT_FONT_SIZE);
2793
+ const normalizeFontFamily = React.useCallback((fontFamily) => {
2794
+ if (fontFamily && SUPPORTED_FONTS.includes(fontFamily)) {
2795
+ return fontFamily;
2796
+ }
2797
+ return "Calibri";
2798
+ }, []);
2369
2799
  const withBodySelection = React.useCallback(
2370
2800
  (editor, action) => {
2371
2801
  editor.update(() => {
@@ -2401,27 +2831,91 @@ const Toolbar = () => {
2401
2831
  },
2402
2832
  [runHistoryAction]
2403
2833
  );
2834
+ React.useEffect(() => {
2835
+ if (!activeEditor) {
2836
+ setActiveBlockType("paragraph");
2837
+ setActiveFontFamily("Calibri");
2838
+ setActiveFontSize(DEFAULT_FONT_SIZE);
2839
+ return;
2840
+ }
2841
+ const updateSelectionState = () => {
2842
+ const selectedVariables = getSelectedVariableNodes(activeEditor);
2843
+ if (selectedVariables.length > 0) {
2844
+ const formatting = readSelectedVariableFormatting(activeEditor);
2845
+ setActiveBlockType("paragraph");
2846
+ setActiveFontFamily(normalizeFontFamily(formatting.fontFamily));
2847
+ setActiveFontSize(formatting.fontSize ?? DEFAULT_FONT_SIZE);
2848
+ return;
2849
+ }
2850
+ setActiveBlockType(getActiveBlockType(activeEditor));
2851
+ let nextFontFamily = "Calibri";
2852
+ let nextFontSize = DEFAULT_FONT_SIZE;
2853
+ activeEditor.getEditorState().read(() => {
2854
+ const selection2 = lexical.$getSelection();
2855
+ if (!lexical.$isRangeSelection(selection2)) {
2856
+ return;
2857
+ }
2858
+ const textNode = selection2.getNodes().find(lexical.$isTextNode);
2859
+ if (!textNode) {
2860
+ return;
2861
+ }
2862
+ const style = textNode.getStyle();
2863
+ nextFontFamily = normalizeFontFamily(extractFontFamilyFromStyle(style));
2864
+ nextFontSize = extractFontSizePtFromStyle(style) ?? DEFAULT_FONT_SIZE;
2865
+ });
2866
+ setActiveFontFamily(nextFontFamily);
2867
+ setActiveFontSize(nextFontSize);
2868
+ };
2869
+ updateSelectionState();
2870
+ const unregisterSelectionChange = activeEditor.registerCommand(
2871
+ lexical.SELECTION_CHANGE_COMMAND,
2872
+ () => {
2873
+ updateSelectionState();
2874
+ return false;
2875
+ },
2876
+ lexical.COMMAND_PRIORITY_LOW
2877
+ );
2878
+ const unregisterUpdateListener = activeEditor.registerUpdateListener(() => {
2879
+ updateSelectionState();
2880
+ });
2881
+ return () => {
2882
+ unregisterSelectionChange();
2883
+ unregisterUpdateListener();
2884
+ };
2885
+ }, [activeEditor, normalizeFontFamily]);
2404
2886
  const handleBold = React.useCallback(() => {
2405
2887
  debug("toolbar", `bold (globalSelection=${globalSelectionActive}, editors=${editorRegistry.all().length}, hasEditor=${!!activeEditor})`);
2406
2888
  runToolbarAction(t.history.actions.boldApplied, () => {
2889
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "bold")) {
2890
+ return;
2891
+ }
2407
2892
  applyToBodyEditors(toggleBold);
2408
2893
  });
2409
2894
  }, [activeEditor, applyToBodyEditors, editorRegistry, globalSelectionActive, runToolbarAction, t.history.actions.boldApplied]);
2410
2895
  const handleItalic = React.useCallback(() => {
2411
2896
  debug("toolbar", `italic (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
2412
2897
  runToolbarAction(t.history.actions.italicApplied, () => {
2898
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "italic")) {
2899
+ return;
2900
+ }
2413
2901
  applyToBodyEditors(toggleItalic);
2414
2902
  });
2415
2903
  }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.italicApplied]);
2416
2904
  const handleUnderline = React.useCallback(() => {
2417
2905
  debug("toolbar", `underline (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
2418
2906
  runToolbarAction(t.history.actions.underlineApplied, () => {
2907
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "underline")) {
2908
+ return;
2909
+ }
2419
2910
  applyToBodyEditors(toggleUnderline);
2420
2911
  });
2421
2912
  }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.underlineApplied]);
2422
2913
  const handleStrikethrough = React.useCallback(() => {
2423
2914
  debug("toolbar", `strikethrough (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
2424
2915
  runToolbarAction(t.history.actions.strikethroughApplied, () => {
2916
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "strikethrough")) {
2917
+ return;
2918
+ }
2425
2919
  applyToBodyEditors(toggleStrikethrough);
2426
2920
  });
2427
2921
  }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.strikethroughApplied]);
@@ -2467,20 +2961,59 @@ const Toolbar = () => {
2467
2961
  }, [applyToBodyEditors, runToolbarAction, t.history.actions.outdentedContent]);
2468
2962
  const handleFontChange = React.useCallback(
2469
2963
  (e) => {
2470
- runToolbarAction(interpolate(t.history.actions.fontChanged, { value: e.target.value }), () => {
2471
- applyToBodyEditors((editor) => applyFontFamily(editor, e.target.value));
2964
+ const fontFamily = e.target.value;
2965
+ runToolbarAction(interpolate(t.history.actions.fontChanged, { value: fontFamily }), () => {
2966
+ if (activeEditor && applyFontFamilyToSelectedVariables(activeEditor, fontFamily)) {
2967
+ return;
2968
+ }
2969
+ applyToBodyEditors((editor) => applyFontFamily(editor, fontFamily));
2472
2970
  });
2473
2971
  },
2474
- [applyToBodyEditors, runToolbarAction, t.history.actions.fontChanged]
2972
+ [activeEditor, applyToBodyEditors, runToolbarAction, t.history.actions.fontChanged]
2475
2973
  );
2476
2974
  const handleFontSizeChange = React.useCallback(
2477
2975
  (e) => {
2478
2976
  const size = parseInt(e.target.value, 10);
2479
2977
  runToolbarAction(interpolate(t.history.actions.fontSizeChanged, { value: String(size) }), () => {
2978
+ if (activeEditor && applyFontSizeToSelectedVariables(activeEditor, size)) {
2979
+ return;
2980
+ }
2480
2981
  applyToBodyEditors((editor) => applyFontSize(editor, size));
2481
2982
  });
2482
2983
  },
2483
- [applyToBodyEditors, runToolbarAction, t.history.actions.fontSizeChanged]
2984
+ [activeEditor, applyToBodyEditors, runToolbarAction, t.history.actions.fontSizeChanged]
2985
+ );
2986
+ const handleBlockTypeChange = React.useCallback(
2987
+ (e) => {
2988
+ const blockType = e.target.value;
2989
+ const labelMap = {
2990
+ paragraph: t.toolbar.paragraph,
2991
+ h1: t.toolbar.heading1,
2992
+ h2: t.toolbar.heading2,
2993
+ h3: t.toolbar.heading3,
2994
+ h4: t.toolbar.heading4,
2995
+ h5: t.toolbar.heading5,
2996
+ h6: t.toolbar.heading6
2997
+ };
2998
+ runToolbarAction(
2999
+ interpolate(t.history.actions.blockTypeChanged, { value: labelMap[blockType] }),
3000
+ () => {
3001
+ applyToBodyEditors((editor) => setBlockType(editor, blockType));
3002
+ }
3003
+ );
3004
+ },
3005
+ [
3006
+ applyToBodyEditors,
3007
+ runToolbarAction,
3008
+ t.history.actions.blockTypeChanged,
3009
+ t.toolbar.heading1,
3010
+ t.toolbar.heading2,
3011
+ t.toolbar.heading3,
3012
+ t.toolbar.heading4,
3013
+ t.toolbar.heading5,
3014
+ t.toolbar.heading6,
3015
+ t.toolbar.paragraph
3016
+ ]
2484
3017
  );
2485
3018
  const handleToggleHistory = React.useCallback(() => {
2486
3019
  setHistorySidebarOpen(!historySidebarOpen);
@@ -2514,6 +3047,26 @@ const Toolbar = () => {
2514
3047
  )
2515
3048
  ] }),
2516
3049
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
3050
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-toolbar-group-gap lex4-toolbar-group-block", children: /* @__PURE__ */ jsxRuntime.jsxs(
3051
+ "select",
3052
+ {
3053
+ className: "lex4-toolbar-select lex4-toolbar-select-block",
3054
+ "data-testid": "block-type-selector",
3055
+ "aria-label": t.toolbar.blockType,
3056
+ value: activeBlockType,
3057
+ onChange: handleBlockTypeChange,
3058
+ children: [
3059
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "paragraph", children: t.toolbar.paragraph }),
3060
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "h1", children: t.toolbar.heading1 }),
3061
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "h2", children: t.toolbar.heading2 }),
3062
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "h3", children: t.toolbar.heading3 }),
3063
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "h4", children: t.toolbar.heading4 }),
3064
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "h5", children: t.toolbar.heading5 }),
3065
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "h6", children: t.toolbar.heading6 })
3066
+ ]
3067
+ }
3068
+ ) }),
3069
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2517
3070
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group-gap", children: [
2518
3071
  /* @__PURE__ */ jsxRuntime.jsx(Type, { size: 14, className: "lex4-toolbar-icon" }),
2519
3072
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2521,7 +3074,7 @@ const Toolbar = () => {
2521
3074
  {
2522
3075
  className: "lex4-toolbar-select",
2523
3076
  "data-testid": "font-selector",
2524
- defaultValue: "Calibri",
3077
+ value: activeFontFamily,
2525
3078
  onChange: handleFontChange,
2526
3079
  children: SUPPORTED_FONTS.map((font) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: font, style: { fontFamily: font }, children: font }, font))
2527
3080
  }
@@ -2534,7 +3087,7 @@ const Toolbar = () => {
2534
3087
  {
2535
3088
  className: "lex4-toolbar-select lex4-toolbar-select-narrow",
2536
3089
  "data-testid": "font-size-selector",
2537
- defaultValue: "12",
3090
+ value: String(activeFontSize),
2538
3091
  onChange: handleFontSizeChange,
2539
3092
  children: SUPPORTED_FONT_SIZES.map((size) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: size, children: size }, size))
2540
3093
  }
@@ -2569,7 +3122,7 @@ const Toolbar = () => {
2569
3122
  /* @__PURE__ */ jsxRuntime.jsx(CanvasControls, {}),
2570
3123
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-end", children: [
2571
3124
  toolbarEndItems.map((EndItem, idx) => /* @__PURE__ */ jsxRuntime.jsx(EndItem, {}, idx)),
2572
- /* @__PURE__ */ jsxRuntime.jsxs(
3125
+ toolbarConfig.history.visible && /* @__PURE__ */ jsxRuntime.jsxs(
2573
3126
  "button",
2574
3127
  {
2575
3128
  type: "button",
@@ -2581,7 +3134,7 @@ const Toolbar = () => {
2581
3134
  "aria-label": historySidebarOpen ? t.toolbar.closeHistory : t.toolbar.openHistory,
2582
3135
  children: [
2583
3136
  /* @__PURE__ */ jsxRuntime.jsx(History, { size: 14 }),
2584
- "History"
3137
+ toolbarConfig.history.showLabel && t.toolbar.history
2585
3138
  ]
2586
3139
  }
2587
3140
  )
@@ -2925,7 +3478,8 @@ const lexicalTheme = {
2925
3478
  h2: "lex4-heading lex4-heading-h2",
2926
3479
  h3: "lex4-heading lex4-heading-h3",
2927
3480
  h4: "lex4-heading lex4-heading-h4",
2928
- h5: "lex4-heading lex4-heading-h5"
3481
+ h5: "lex4-heading lex4-heading-h5",
3482
+ h6: "lex4-heading lex4-heading-h6"
2929
3483
  },
2930
3484
  text: {
2931
3485
  bold: "lex4-text-bold",
@@ -4462,12 +5016,10 @@ function decodeFormatBitmask(format) {
4462
5016
  return marks;
4463
5017
  }
4464
5018
  function extractFontFamily(style) {
4465
- const match = style.match(/font-family:\s*([^;]+)/);
4466
- return match ? match[1].trim().replace(/['"]/g, "") : void 0;
5019
+ return extractFontFamilyFromStyle(style);
4467
5020
  }
4468
5021
  function extractFontSizePt(style) {
4469
- const match = style.match(/font-size:\s*(\d+(?:\.\d+)?)\s*pt/);
4470
- return match ? parseFloat(match[1]) : void 0;
5022
+ return extractFontSizePtFromStyle(style);
4471
5023
  }
4472
5024
  function buildTextMarks(format, style) {
4473
5025
  const formatMarks = decodeFormatBitmask(format);
@@ -4501,9 +5053,11 @@ function mapTextNode(node) {
4501
5053
  };
4502
5054
  }
4503
5055
  function mapVariableNode(node) {
5056
+ const marks = buildTextMarks(node.format ?? 0, node.style);
4504
5057
  return {
4505
5058
  type: "variable",
4506
- key: node.variableKey
5059
+ key: node.variableKey,
5060
+ ...marks ? { marks } : {}
4507
5061
  };
4508
5062
  }
4509
5063
  function mapLineBreak() {
@@ -4567,7 +5121,7 @@ function mapParagraph(node) {
4567
5121
  function mapHeading(node) {
4568
5122
  var _a;
4569
5123
  const alignment = decodeAlignment(node.format);
4570
- const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h(\d)$/);
5124
+ const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h([1-6])$/);
4571
5125
  const level = tagMatch ? parseInt(tagMatch[1], 10) : 1;
4572
5126
  return {
4573
5127
  type: "heading",
@@ -4692,6 +5246,105 @@ function buildSavePayload(ast, options) {
4692
5246
  function serializeDocumentJson(ast) {
4693
5247
  return JSON.stringify(ast, null, 2);
4694
5248
  }
5249
+ function appendChildren(parent, children = []) {
5250
+ const nodes = children.map(buildLexicalNode).filter((node) => node !== null);
5251
+ if (nodes.length > 0) {
5252
+ parent.append(...nodes);
5253
+ }
5254
+ }
5255
+ function applyElementFormatting(node, serializedNode) {
5256
+ if (typeof serializedNode.format === "string" && ["left", "center", "right", "justify"].includes(serializedNode.format) && node.setFormat) {
5257
+ node.setFormat(serializedNode.format);
5258
+ }
5259
+ if (typeof serializedNode.indent === "number" && serializedNode.indent > 0 && node.setIndent) {
5260
+ node.setIndent(serializedNode.indent);
5261
+ }
5262
+ }
5263
+ function buildLexicalNode(serializedNode) {
5264
+ switch (serializedNode.type) {
5265
+ case "paragraph": {
5266
+ const node = lexical.$createParagraphNode();
5267
+ applyElementFormatting(node, serializedNode);
5268
+ appendChildren(node, serializedNode.children);
5269
+ return node;
5270
+ }
5271
+ case "heading": {
5272
+ const tag = typeof serializedNode.tag === "string" && /^h[1-6]$/.test(serializedNode.tag) ? serializedNode.tag : "h1";
5273
+ const node = richText.$createHeadingNode(tag);
5274
+ applyElementFormatting(node, serializedNode);
5275
+ appendChildren(node, serializedNode.children);
5276
+ return node;
5277
+ }
5278
+ case "quote": {
5279
+ const node = richText.$createQuoteNode();
5280
+ applyElementFormatting(node, serializedNode);
5281
+ appendChildren(node, serializedNode.children);
5282
+ return node;
5283
+ }
5284
+ case "list": {
5285
+ const listType = serializedNode.listType === "number" ? "number" : "bullet";
5286
+ const node = list.$createListNode(listType, typeof serializedNode.start === "number" ? serializedNode.start : 1);
5287
+ appendChildren(node, serializedNode.children);
5288
+ return node;
5289
+ }
5290
+ case "listitem": {
5291
+ const node = list.$createListItemNode();
5292
+ appendChildren(node, serializedNode.children);
5293
+ return node;
5294
+ }
5295
+ case "text": {
5296
+ const node = lexical.$createTextNode(serializedNode.text ?? "");
5297
+ if (typeof serializedNode.format === "number" && serializedNode.format > 0) {
5298
+ node.setFormat(serializedNode.format);
5299
+ }
5300
+ if (typeof serializedNode.style === "string" && serializedNode.style.trim() !== "") {
5301
+ node.setStyle(serializedNode.style);
5302
+ }
5303
+ return node;
5304
+ }
5305
+ case "linebreak":
5306
+ return lexical.$createLineBreakNode();
5307
+ case "variable-node":
5308
+ return $createVariableNode(
5309
+ serializedNode.variableKey ?? "",
5310
+ typeof serializedNode.format === "number" ? serializedNode.format : 0,
5311
+ typeof serializedNode.style === "string" ? serializedNode.style : ""
5312
+ );
5313
+ default:
5314
+ return null;
5315
+ }
5316
+ }
5317
+ function getBodyChildren(pageState) {
5318
+ const root = pageState == null ? void 0 : pageState.root;
5319
+ return (root == null ? void 0 : root.children) ?? [];
5320
+ }
5321
+ function insertDocumentContent(editor, document2) {
5322
+ let inserted = false;
5323
+ editor.update(() => {
5324
+ const nodes = document2.pages.flatMap((page) => getBodyChildren(page.bodyState)).map(buildLexicalNode).filter((node) => node !== null);
5325
+ if (nodes.length === 0) {
5326
+ return;
5327
+ }
5328
+ const selection2 = lexical.$getSelection();
5329
+ if (lexical.$isRangeSelection(selection2)) {
5330
+ if (selection2.isCollapsed()) {
5331
+ selection2.insertParagraph();
5332
+ }
5333
+ const nextSelection = lexical.$getSelection();
5334
+ if (lexical.$isRangeSelection(nextSelection) || lexical.$isNodeSelection(nextSelection)) {
5335
+ nextSelection.insertNodes(nodes);
5336
+ } else {
5337
+ lexical.$insertNodes(nodes);
5338
+ }
5339
+ } else if (lexical.$isNodeSelection(selection2)) {
5340
+ selection2.insertNodes(nodes);
5341
+ } else {
5342
+ lexical.$insertNodes(nodes);
5343
+ }
5344
+ inserted = true;
5345
+ });
5346
+ return inserted;
5347
+ }
4695
5348
  function selectEntireDocument(rootElement, selectionBuffer) {
4696
5349
  if (!rootElement || !selectionBuffer) {
4697
5350
  return;
@@ -4898,10 +5551,13 @@ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, on
4898
5551
  const {
4899
5552
  document: doc,
4900
5553
  activeEditor,
5554
+ activeCaretPosition,
4901
5555
  historySidebarOpen,
5556
+ runHistoryAction,
4902
5557
  setHistorySidebarOpen
4903
5558
  } = useDocument();
4904
5559
  const { handleFactories } = useExtensions();
5560
+ const t = useTranslations();
4905
5561
  const getDocument = React.useCallback(() => doc, [doc]);
4906
5562
  const getActiveEditor = React.useCallback(() => activeEditor, [activeEditor]);
4907
5563
  const extensionCtx = useExtensionContext(getDocument, getActiveEditor);
@@ -4912,6 +5568,23 @@ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, on
4912
5568
  },
4913
5569
  toggleHistorySidebar: () => {
4914
5570
  setHistorySidebarOpen(!historySidebarOpen);
5571
+ },
5572
+ insertDocumentContent: (documentToInsert) => {
5573
+ if (!activeEditor || (activeCaretPosition == null ? void 0 : activeCaretPosition.region) !== "body") {
5574
+ return false;
5575
+ }
5576
+ let inserted = false;
5577
+ runHistoryAction(
5578
+ {
5579
+ label: t.history.actions.insertedDocumentContent,
5580
+ source: "toolbar",
5581
+ region: "document"
5582
+ },
5583
+ () => {
5584
+ inserted = insertDocumentContent(activeEditor, documentToInsert);
5585
+ }
5586
+ );
5587
+ return inserted;
4915
5588
  }
4916
5589
  };
4917
5590
  for (const factory of handleFactories) {
@@ -4919,7 +5592,16 @@ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, on
4919
5592
  Object.assign(handle, methods);
4920
5593
  }
4921
5594
  return handle;
4922
- }, [extensionCtx, handleFactories, historySidebarOpen, setHistorySidebarOpen]);
5595
+ }, [
5596
+ activeCaretPosition == null ? void 0 : activeCaretPosition.region,
5597
+ activeEditor,
5598
+ extensionCtx,
5599
+ handleFactories,
5600
+ historySidebarOpen,
5601
+ runHistoryAction,
5602
+ setHistorySidebarOpen,
5603
+ t.history.actions.insertedDocumentContent
5604
+ ]);
4923
5605
  return /* @__PURE__ */ jsxRuntime.jsx(
4924
5606
  EditorChrome,
4925
5607
  {
@@ -4937,9 +5619,10 @@ const Lex4Editor = React.forwardRef(({
4937
5619
  onSave,
4938
5620
  extensions,
4939
5621
  translations,
5622
+ toolbar,
4940
5623
  className
4941
5624
  }, ref) => {
4942
- return /* @__PURE__ */ jsxRuntime.jsx(TranslationsProvider, { translations, children: /* @__PURE__ */ jsxRuntime.jsx(ExtensionStateProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(ExtensionProvider, { extensions, children: /* @__PURE__ */ jsxRuntime.jsx(
5625
+ return /* @__PURE__ */ jsxRuntime.jsx(TranslationsProvider, { translations, children: /* @__PURE__ */ jsxRuntime.jsx(ToolbarConfigProvider, { toolbar, children: /* @__PURE__ */ jsxRuntime.jsx(ExtensionStateProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(ExtensionProvider, { extensions, children: /* @__PURE__ */ jsxRuntime.jsx(
4943
5626
  DocumentProvider,
4944
5627
  {
4945
5628
  initialDocument,
@@ -4954,7 +5637,7 @@ const Lex4Editor = React.forwardRef(({
4954
5637
  }
4955
5638
  )
4956
5639
  }
4957
- ) }) }) });
5640
+ ) }) }) }) });
4958
5641
  });
4959
5642
  Lex4Editor.displayName = "Lex4Editor";
4960
5643
  function useOverflowDetection(bodyHeight, onOverflow, debounceMs = 100) {
@@ -5048,120 +5731,6 @@ function astExtension() {
5048
5731
  }
5049
5732
  };
5050
5733
  }
5051
- const EMPTY_CONTEXT = {
5052
- definitions: [],
5053
- refreshDefinitions: () => {
5054
- },
5055
- getDefinition: () => void 0
5056
- };
5057
- const VariableContext = React.createContext(EMPTY_CONTEXT);
5058
- const VariableProvider = ({
5059
- initialDefinitions = [],
5060
- children
5061
- }) => {
5062
- const [definitions, setDefinitions] = React.useState(initialDefinitions);
5063
- const refresh = React.useCallback((newDefinitions) => {
5064
- setDefinitions(newDefinitions);
5065
- }, []);
5066
- const getDefinition = React.useCallback(
5067
- (key) => {
5068
- return definitions.find((d) => d.key === key);
5069
- },
5070
- [definitions]
5071
- );
5072
- const value = React.useMemo(
5073
- () => ({ definitions, refreshDefinitions: refresh, getDefinition }),
5074
- [definitions, refresh, getDefinition]
5075
- );
5076
- return /* @__PURE__ */ jsxRuntime.jsx(VariableContext.Provider, { value, children });
5077
- };
5078
- function useVariables() {
5079
- return React.useContext(VariableContext);
5080
- }
5081
- class VariableNode extends lexical.DecoratorNode {
5082
- constructor(variableKey, key) {
5083
- super(key);
5084
- __publicField(this, "__variableKey");
5085
- this.__variableKey = variableKey;
5086
- }
5087
- static getType() {
5088
- return "variable-node";
5089
- }
5090
- static clone(node) {
5091
- return new VariableNode(node.__variableKey, node.__key);
5092
- }
5093
- getVariableKey() {
5094
- return this.__variableKey;
5095
- }
5096
- // -- Serialization --
5097
- static importJSON(serializedNode) {
5098
- return $createVariableNode(serializedNode.variableKey);
5099
- }
5100
- exportJSON() {
5101
- return {
5102
- type: "variable-node",
5103
- version: 1,
5104
- variableKey: this.__variableKey
5105
- };
5106
- }
5107
- // -- DOM --
5108
- createDOM() {
5109
- const span = document.createElement("span");
5110
- span.className = "lex4-variable";
5111
- span.setAttribute("data-variable-key", this.__variableKey);
5112
- span.setAttribute("data-testid", `variable-${this.__variableKey}`);
5113
- span.contentEditable = "false";
5114
- return span;
5115
- }
5116
- updateDOM() {
5117
- return false;
5118
- }
5119
- exportDOM() {
5120
- const span = document.createElement("span");
5121
- span.setAttribute("data-variable-key", this.__variableKey);
5122
- span.textContent = `{{${this.__variableKey}}}`;
5123
- return { element: span };
5124
- }
5125
- static importDOM() {
5126
- return null;
5127
- }
5128
- // -- Behavior --
5129
- isInline() {
5130
- return true;
5131
- }
5132
- isKeyboardSelectable() {
5133
- return true;
5134
- }
5135
- getTextContent() {
5136
- return `{{${this.__variableKey}}}`;
5137
- }
5138
- // -- Rendering --
5139
- decorate() {
5140
- return /* @__PURE__ */ jsxRuntime.jsx(VariableChip, { variableKey: this.__variableKey });
5141
- }
5142
- }
5143
- function VariableChip({ variableKey }) {
5144
- const { getDefinition } = useVariables();
5145
- const def = getDefinition(variableKey);
5146
- const label = (def == null ? void 0 : def.label) ?? variableKey;
5147
- const group = def == null ? void 0 : def.group;
5148
- return /* @__PURE__ */ jsxRuntime.jsx(
5149
- "span",
5150
- {
5151
- className: "lex4-variable-chip",
5152
- "data-testid": `variable-chip-${variableKey}`,
5153
- "data-variable-group": group,
5154
- title: variableKey,
5155
- children: label
5156
- }
5157
- );
5158
- }
5159
- function $createVariableNode(variableKey) {
5160
- return lexical.$applyNodeReplacement(new VariableNode(variableKey));
5161
- }
5162
- function $isVariableNode(node) {
5163
- return node instanceof VariableNode;
5164
- }
5165
5734
  const INSERT_VARIABLE_COMMAND = lexical.createCommand("INSERT_VARIABLE");
5166
5735
  const VariablePlugin = () => {
5167
5736
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
@@ -5170,8 +5739,8 @@ const VariablePlugin = () => {
5170
5739
  INSERT_VARIABLE_COMMAND,
5171
5740
  (variableKey) => {
5172
5741
  editor.update(() => {
5173
- const selection = lexical.$getSelection();
5174
- if (!lexical.$isRangeSelection(selection)) return;
5742
+ const selection2 = lexical.$getSelection();
5743
+ if (!lexical.$isRangeSelection(selection2)) return;
5175
5744
  const variableNode = $createVariableNode(variableKey);
5176
5745
  lexical.$insertNodes([variableNode]);
5177
5746
  });
@@ -5422,7 +5991,11 @@ const VariablePanelStateProvider = ({ children }) => {
5422
5991
  };
5423
5992
  const VariableToolbarToggle = () => {
5424
5993
  const { panelOpen, setPanelOpen } = useVariablePanelState();
5994
+ const toolbarConfig = useToolbarConfig();
5425
5995
  const t = useTranslations();
5996
+ if (!toolbarConfig.variables.visible) {
5997
+ return null;
5998
+ }
5426
5999
  return /* @__PURE__ */ jsxRuntime.jsxs(
5427
6000
  "button",
5428
6001
  {
@@ -5435,7 +6008,7 @@ const VariableToolbarToggle = () => {
5435
6008
  "aria-label": panelOpen ? t.variables.closePanel : t.variables.openPanel,
5436
6009
  children: [
5437
6010
  /* @__PURE__ */ jsxRuntime.jsx(Braces, { size: 14 }),
5438
- t.variables.title
6011
+ toolbarConfig.variables.showLabel && t.variables.title
5439
6012
  ]
5440
6013
  }
5441
6014
  );