@yurikilian/lex4 1.4.1 → 1.5.2

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 (44) 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/BlockTypePicker.d.ts +11 -0
  6. package/dist/components/BlockTypePicker.d.ts.map +1 -0
  7. package/dist/components/CanvasControls.d.ts.map +1 -1
  8. package/dist/components/HeaderFooterToggle.d.ts +1 -0
  9. package/dist/components/HeaderFooterToggle.d.ts.map +1 -1
  10. package/dist/components/Lex4Editor.d.ts.map +1 -1
  11. package/dist/components/Toolbar.d.ts.map +1 -1
  12. package/dist/context/document-context.d.ts +1 -0
  13. package/dist/context/document-context.d.ts.map +1 -1
  14. package/dist/context/document-provider.d.ts.map +1 -1
  15. package/dist/context/toolbar-config.d.ts +18 -0
  16. package/dist/context/toolbar-config.d.ts.map +1 -0
  17. package/dist/extensions/variables-extension.d.ts.map +1 -1
  18. package/dist/i18n/defaults.d.ts.map +1 -1
  19. package/dist/i18n/pt-BR.d.ts.map +1 -1
  20. package/dist/i18n/types.d.ts +12 -0
  21. package/dist/i18n/types.d.ts.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/lex4-editor.cjs +845 -163
  25. package/dist/lex4-editor.cjs.map +1 -1
  26. package/dist/lex4-editor.js +829 -147
  27. package/dist/lex4-editor.js.map +1 -1
  28. package/dist/lexical/commands/block-commands.d.ts +5 -0
  29. package/dist/lexical/commands/block-commands.d.ts.map +1 -0
  30. package/dist/lexical/theme.d.ts.map +1 -1
  31. package/dist/lexical/utils/import-document-content.d.ts +4 -0
  32. package/dist/lexical/utils/import-document-content.d.ts.map +1 -0
  33. package/dist/style.css +156 -25
  34. package/dist/types/editor-handle.d.ts +2 -0
  35. package/dist/types/editor-handle.d.ts.map +1 -1
  36. package/dist/types/editor-props.d.ts +16 -0
  37. package/dist/types/editor-props.d.ts.map +1 -1
  38. package/dist/utils/text-style.d.ts +7 -0
  39. package/dist/utils/text-style.d.ts.map +1 -0
  40. package/dist/variables/variable-formatting.d.ts +11 -0
  41. package/dist/variables/variable-formatting.d.ts.map +1 -0
  42. package/dist/variables/variable-node.d.ts +10 -2
  43. package/dist/variables/variable-node.d.ts.map +1 -1
  44. package/package.json +1 -1
@@ -3,16 +3,18 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
5
5
  import React, { createContext, useContext, useMemo, useReducer, useState, useRef, useCallback, useEffect, forwardRef, createElement, useImperativeHandle } from "react";
6
- import { $getRoot, $createRangeSelectionFromDom, $getSelection, $isRangeSelection, $isTextNode, FORMAT_ELEMENT_COMMAND, FORMAT_TEXT_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $selectAll, KEY_TAB_COMMAND, COMMAND_PRIORITY_LOW, $isElementNode, $isParagraphNode, $setSelection, FOCUS_COMMAND, $splitNode, $getNearestNodeFromDOMNode, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_DOWN_COMMAND, PASTE_COMMAND, KEY_ENTER_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_CRITICAL, $applyNodeReplacement, DecoratorNode, createCommand, $insertNodes, COMMAND_PRIORITY_EDITOR } from "lexical";
6
+ import { $getRoot, $createRangeSelectionFromDom, $getSelection, $isRangeSelection, $isTextNode, FORMAT_ELEMENT_COMMAND, FORMAT_TEXT_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $createParagraphNode, $applyNodeReplacement, DecoratorNode, KEY_BACKSPACE_COMMAND, COMMAND_PRIORITY_LOW, KEY_DELETE_COMMAND, $isNodeSelection, $getNodeByKey, $selectAll, SELECTION_CHANGE_COMMAND, KEY_TAB_COMMAND, $isElementNode, $isParagraphNode, $setSelection, FOCUS_COMMAND, $splitNode, $getNearestNodeFromDOMNode, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_DOWN_COMMAND, PASTE_COMMAND, KEY_ENTER_COMMAND, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_CRITICAL, $insertNodes, $createLineBreakNode, $createTextNode, createCommand, COMMAND_PRIORITY_EDITOR } from "lexical";
7
7
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
8
- import { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListNode, ListItemNode, $isListNode } from "@lexical/list";
8
+ import { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListNode, ListItemNode, $isListNode, $createListItemNode, $createListNode } from "@lexical/list";
9
+ import { $setBlocksType } from "@lexical/selection";
10
+ import { $createHeadingNode, $isHeadingNode, HeadingNode, QuoteNode, $createQuoteNode } from "@lexical/rich-text";
11
+ import { useLexicalNodeSelection } from "@lexical/react/useLexicalNodeSelection";
9
12
  import { LexicalComposer } from "@lexical/react/LexicalComposer";
10
13
  import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
11
14
  import { ContentEditable } from "@lexical/react/LexicalContentEditable";
12
15
  import { ListPlugin } from "@lexical/react/LexicalListPlugin";
13
16
  import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
14
17
  import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
15
- import { HeadingNode, QuoteNode } from "@lexical/rich-text";
16
18
  function createEmptyPage(id) {
17
19
  return {
18
20
  id: id ?? crypto.randomUUID(),
@@ -471,6 +473,15 @@ const DEFAULT_TRANSLATIONS = {
471
473
  bulletList: "Bullet List",
472
474
  indent: "Indent",
473
475
  outdent: "Outdent",
476
+ history: "History",
477
+ blockType: "Block type",
478
+ paragraph: "Paragraph",
479
+ heading1: "Heading 1",
480
+ heading2: "Heading 2",
481
+ heading3: "Heading 3",
482
+ heading4: "Heading 4",
483
+ heading5: "Heading 5",
484
+ heading6: "Heading 6",
474
485
  openHistory: "Open History",
475
486
  closeHistory: "Close History"
476
487
  },
@@ -502,7 +513,9 @@ const DEFAULT_TRANSLATIONS = {
502
513
  indentedContent: "Indented content",
503
514
  outdentedContent: "Outdented content",
504
515
  fontChanged: "Font changed to {{value}}",
505
- fontSizeChanged: "Font size changed to {{value}}pt"
516
+ fontSizeChanged: "Font size changed to {{value}}pt",
517
+ blockTypeChanged: "Block type changed to {{value}}",
518
+ insertedDocumentContent: "Inserted document content"
506
519
  }
507
520
  },
508
521
  variables: {
@@ -602,6 +615,15 @@ const PT_BR_TRANSLATIONS = {
602
615
  bulletList: "Lista com Marcadores",
603
616
  indent: "Aumentar Recuo",
604
617
  outdent: "Diminuir Recuo",
618
+ history: "Histórico",
619
+ blockType: "Tipo de bloco",
620
+ paragraph: "Parágrafo",
621
+ heading1: "Título 1",
622
+ heading2: "Título 2",
623
+ heading3: "Título 3",
624
+ heading4: "Título 4",
625
+ heading5: "Título 5",
626
+ heading6: "Título 6",
605
627
  openHistory: "Abrir Histórico",
606
628
  closeHistory: "Fechar Histórico"
607
629
  },
@@ -633,7 +655,9 @@ const PT_BR_TRANSLATIONS = {
633
655
  indentedContent: "Conteúdo recuado",
634
656
  outdentedContent: "Recuo reduzido",
635
657
  fontChanged: "Fonte alterada para {{value}}",
636
- fontSizeChanged: "Tamanho da fonte alterado para {{value}}pt"
658
+ fontSizeChanged: "Tamanho da fonte alterado para {{value}}pt",
659
+ blockTypeChanged: "Tipo de bloco alterado para {{value}}",
660
+ insertedDocumentContent: "Conteúdo do documento inserido"
637
661
  }
638
662
  },
639
663
  variables: {
@@ -1284,6 +1308,7 @@ const DocumentProvider = ({
1284
1308
  activePageId,
1285
1309
  setActivePageId,
1286
1310
  activeEditor: activeEditorRef.current,
1311
+ activeCaretPosition: activeCaretPositionRef.current,
1287
1312
  consumePendingCaretPosition,
1288
1313
  consumePendingFocusAtEnd,
1289
1314
  requestFocusAtEnd,
@@ -1433,33 +1458,33 @@ const createLucideIcon = (iconName, iconNode) => {
1433
1458
  * This source code is licensed under the ISC license.
1434
1459
  * See the LICENSE file in the root directory of this source tree.
1435
1460
  */
1436
- const __iconNode$p = [
1461
+ const __iconNode$q = [
1437
1462
  ["path", { d: "m15 16 2.536-7.328a1.02 1.02 1 0 1 1.928 0L22 16", key: "xik6mr" }],
1438
1463
  ["path", { d: "M15.697 14h5.606", key: "1stdlc" }],
1439
1464
  ["path", { d: "m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16", key: "d5nyq2" }],
1440
1465
  ["path", { d: "M3.304 13h6.392", key: "1q3zxz" }]
1441
1466
  ];
1442
- const ALargeSmall = createLucideIcon("a-large-small", __iconNode$p);
1467
+ const ALargeSmall = createLucideIcon("a-large-small", __iconNode$q);
1443
1468
  /**
1444
1469
  * @license lucide-react v1.8.0 - ISC
1445
1470
  *
1446
1471
  * This source code is licensed under the ISC license.
1447
1472
  * See the LICENSE file in the root directory of this source tree.
1448
1473
  */
1449
- const __iconNode$o = [
1474
+ const __iconNode$p = [
1450
1475
  [
1451
1476
  "path",
1452
1477
  { d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8", key: "mg9rjx" }
1453
1478
  ]
1454
1479
  ];
1455
- const Bold = createLucideIcon("bold", __iconNode$o);
1480
+ const Bold = createLucideIcon("bold", __iconNode$p);
1456
1481
  /**
1457
1482
  * @license lucide-react v1.8.0 - ISC
1458
1483
  *
1459
1484
  * This source code is licensed under the ISC license.
1460
1485
  * See the LICENSE file in the root directory of this source tree.
1461
1486
  */
1462
- const __iconNode$n = [
1487
+ const __iconNode$o = [
1463
1488
  [
1464
1489
  "path",
1465
1490
  { d: "M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1", key: "ezmyqa" }
@@ -1472,7 +1497,15 @@ const __iconNode$n = [
1472
1497
  }
1473
1498
  ]
1474
1499
  ];
1475
- const Braces = createLucideIcon("braces", __iconNode$n);
1500
+ const Braces = createLucideIcon("braces", __iconNode$o);
1501
+ /**
1502
+ * @license lucide-react v1.8.0 - ISC
1503
+ *
1504
+ * This source code is licensed under the ISC license.
1505
+ * See the LICENSE file in the root directory of this source tree.
1506
+ */
1507
+ const __iconNode$n = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
1508
+ const ChevronDown = createLucideIcon("chevron-down", __iconNode$n);
1476
1509
  /**
1477
1510
  * @license lucide-react v1.8.0 - ISC
1478
1511
  *
@@ -1890,6 +1923,36 @@ const HistorySidebar = () => {
1890
1923
  }
1891
1924
  );
1892
1925
  };
1926
+ const DEFAULT_TOOLBAR_CONTROL_CONFIG = {
1927
+ visible: true,
1928
+ showLabel: true
1929
+ };
1930
+ const DEFAULT_TOOLBAR_CONFIG = {
1931
+ history: DEFAULT_TOOLBAR_CONTROL_CONFIG,
1932
+ variables: DEFAULT_TOOLBAR_CONTROL_CONFIG,
1933
+ headerFooter: DEFAULT_TOOLBAR_CONTROL_CONFIG
1934
+ };
1935
+ const ToolbarConfigContext = createContext(DEFAULT_TOOLBAR_CONFIG);
1936
+ function resolveControlConfig(config) {
1937
+ return {
1938
+ visible: (config == null ? void 0 : config.visible) ?? true,
1939
+ showLabel: (config == null ? void 0 : config.showLabel) ?? true
1940
+ };
1941
+ }
1942
+ function normalizeToolbarConfig(config) {
1943
+ return {
1944
+ history: resolveControlConfig(config == null ? void 0 : config.history),
1945
+ variables: resolveControlConfig(config == null ? void 0 : config.variables),
1946
+ headerFooter: resolveControlConfig(config == null ? void 0 : config.headerFooter)
1947
+ };
1948
+ }
1949
+ const ToolbarConfigProvider = ({ toolbar, children }) => {
1950
+ const resolvedConfig = useMemo(() => normalizeToolbarConfig(toolbar), [toolbar]);
1951
+ return /* @__PURE__ */ jsx(ToolbarConfigContext.Provider, { value: resolvedConfig, children });
1952
+ };
1953
+ function useToolbarConfig() {
1954
+ return useContext(ToolbarConfigContext);
1955
+ }
1893
1956
  function resolveExtensions(extensions) {
1894
1957
  const resolved = {
1895
1958
  nodes: [],
@@ -2022,6 +2085,7 @@ const SUPPORTED_FONT_SIZES = [
2022
2085
  48,
2023
2086
  72
2024
2087
  ];
2088
+ const DEFAULT_FONT_SIZE = 12;
2025
2089
  function applyFontSize(editor, size) {
2026
2090
  editor.update(() => {
2027
2091
  const selection = $getSelection();
@@ -2072,9 +2136,495 @@ function indentContent(editor) {
2072
2136
  function outdentContent(editor) {
2073
2137
  editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, void 0);
2074
2138
  }
2139
+ function setBlockType(editor, blockType) {
2140
+ editor.update(() => {
2141
+ const selection = $getSelection();
2142
+ if (!$isRangeSelection(selection)) {
2143
+ return;
2144
+ }
2145
+ if (blockType === "paragraph") {
2146
+ $setBlocksType(selection, () => $createParagraphNode());
2147
+ return;
2148
+ }
2149
+ $setBlocksType(selection, () => $createHeadingNode(blockType));
2150
+ });
2151
+ }
2152
+ function getActiveBlockType(editor) {
2153
+ let blockType = "paragraph";
2154
+ editor.getEditorState().read(() => {
2155
+ const selection = $getSelection();
2156
+ if (!$isRangeSelection(selection)) {
2157
+ return;
2158
+ }
2159
+ const anchorNode = selection.anchor.getNode();
2160
+ const topLevelElement = anchorNode.getTopLevelElementOrThrow();
2161
+ if ($isHeadingNode(topLevelElement)) {
2162
+ blockType = topLevelElement.getTag();
2163
+ }
2164
+ });
2165
+ return blockType;
2166
+ }
2167
+ function extractStyleValue(style, property) {
2168
+ const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2169
+ const match = style.match(new RegExp(`${escapedProperty}:\\s*([^;]+)`));
2170
+ return match ? match[1].trim().replace(/['"]/g, "") : void 0;
2171
+ }
2172
+ function mergeStyleDeclaration(existingStyle, property, value) {
2173
+ const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2174
+ const stripped = existingStyle.replace(
2175
+ new RegExp(`${escapedProperty}:\\s*[^;]+;?\\s*`, "g"),
2176
+ ""
2177
+ ).trim();
2178
+ const declaration = `${property}: ${value}`;
2179
+ return stripped ? `${stripped}; ${declaration}` : declaration;
2180
+ }
2181
+ function extractFontFamilyFromStyle(style) {
2182
+ return extractStyleValue(style, "font-family");
2183
+ }
2184
+ function extractFontSizePtFromStyle(style) {
2185
+ const value = extractStyleValue(style, "font-size");
2186
+ if (!value) {
2187
+ return void 0;
2188
+ }
2189
+ const match = value.match(/^(\d+(?:\.\d+)?)\s*pt$/);
2190
+ return match ? parseFloat(match[1]) : void 0;
2191
+ }
2192
+ function mergeFontFamilyStyle(existingStyle, fontFamily) {
2193
+ return mergeStyleDeclaration(existingStyle, "font-family", fontFamily);
2194
+ }
2195
+ function mergeFontSizeStyle(existingStyle, size) {
2196
+ return mergeStyleDeclaration(existingStyle, "font-size", `${size}pt`);
2197
+ }
2198
+ const EMPTY_CONTEXT = {
2199
+ definitions: [],
2200
+ refreshDefinitions: () => {
2201
+ },
2202
+ getDefinition: () => void 0
2203
+ };
2204
+ const VariableContext = createContext(EMPTY_CONTEXT);
2205
+ const VariableProvider = ({
2206
+ initialDefinitions = [],
2207
+ children
2208
+ }) => {
2209
+ const [definitions, setDefinitions] = useState(initialDefinitions);
2210
+ const refresh = useCallback((newDefinitions) => {
2211
+ setDefinitions(newDefinitions);
2212
+ }, []);
2213
+ const getDefinition = useCallback(
2214
+ (key) => {
2215
+ return definitions.find((d) => d.key === key);
2216
+ },
2217
+ [definitions]
2218
+ );
2219
+ const value = useMemo(
2220
+ () => ({ definitions, refreshDefinitions: refresh, getDefinition }),
2221
+ [definitions, refresh, getDefinition]
2222
+ );
2223
+ return /* @__PURE__ */ jsx(VariableContext.Provider, { value, children });
2224
+ };
2225
+ function useVariables() {
2226
+ return useContext(VariableContext);
2227
+ }
2228
+ class VariableNode extends DecoratorNode {
2229
+ constructor(variableKey, format = 0, style = "", key) {
2230
+ super(key);
2231
+ __publicField(this, "__variableKey");
2232
+ __publicField(this, "__format");
2233
+ __publicField(this, "__style");
2234
+ this.__variableKey = variableKey;
2235
+ this.__format = format;
2236
+ this.__style = style;
2237
+ }
2238
+ static getType() {
2239
+ return "variable-node";
2240
+ }
2241
+ static clone(node) {
2242
+ return new VariableNode(node.__variableKey, node.__format, node.__style, node.__key);
2243
+ }
2244
+ getVariableKey() {
2245
+ return this.getLatest().__variableKey;
2246
+ }
2247
+ getFormat() {
2248
+ return this.getLatest().__format;
2249
+ }
2250
+ setFormat(format) {
2251
+ const writable = this.getWritable();
2252
+ writable.__format = format;
2253
+ return writable;
2254
+ }
2255
+ getStyle() {
2256
+ return this.getLatest().__style;
2257
+ }
2258
+ setStyle(style) {
2259
+ const writable = this.getWritable();
2260
+ writable.__style = style;
2261
+ return writable;
2262
+ }
2263
+ // -- Serialization --
2264
+ static importJSON(serializedNode) {
2265
+ return $createVariableNode(
2266
+ serializedNode.variableKey,
2267
+ serializedNode.format ?? 0,
2268
+ serializedNode.style ?? ""
2269
+ );
2270
+ }
2271
+ exportJSON() {
2272
+ return {
2273
+ type: "variable-node",
2274
+ version: 1,
2275
+ variableKey: this.__variableKey,
2276
+ format: this.__format,
2277
+ style: this.__style
2278
+ };
2279
+ }
2280
+ // -- DOM --
2281
+ createDOM() {
2282
+ const span = document.createElement("span");
2283
+ span.className = "lex4-variable";
2284
+ span.setAttribute("data-variable-key", this.__variableKey);
2285
+ span.setAttribute("data-testid", `variable-${this.__variableKey}`);
2286
+ span.contentEditable = "false";
2287
+ return span;
2288
+ }
2289
+ updateDOM() {
2290
+ return false;
2291
+ }
2292
+ exportDOM() {
2293
+ const span = document.createElement("span");
2294
+ span.setAttribute("data-variable-key", this.__variableKey);
2295
+ span.textContent = `{{${this.__variableKey}}}`;
2296
+ return { element: span };
2297
+ }
2298
+ static importDOM() {
2299
+ return null;
2300
+ }
2301
+ // -- Behavior --
2302
+ isInline() {
2303
+ return true;
2304
+ }
2305
+ isKeyboardSelectable() {
2306
+ return true;
2307
+ }
2308
+ getTextContent() {
2309
+ return `{{${this.__variableKey}}}`;
2310
+ }
2311
+ // -- Rendering --
2312
+ decorate() {
2313
+ return /* @__PURE__ */ jsx(
2314
+ VariableChip,
2315
+ {
2316
+ nodeKey: this.__key,
2317
+ variableKey: this.__variableKey,
2318
+ format: this.__format,
2319
+ styleValue: this.__style
2320
+ }
2321
+ );
2322
+ }
2323
+ }
2324
+ function VariableChip({
2325
+ nodeKey,
2326
+ variableKey,
2327
+ format,
2328
+ styleValue
2329
+ }) {
2330
+ const { getDefinition } = useVariables();
2331
+ const [editor] = useLexicalComposerContext();
2332
+ const [isSelected, setSelected, clearOtherSelections] = useLexicalNodeSelection(nodeKey);
2333
+ const def = getDefinition(variableKey);
2334
+ const label = (def == null ? void 0 : def.label) ?? variableKey;
2335
+ const group = def == null ? void 0 : def.group;
2336
+ const style = useMemo(() => {
2337
+ const fontFamily = extractFontFamilyFromStyle(styleValue);
2338
+ const fontSize = extractFontSizePtFromStyle(styleValue);
2339
+ return {
2340
+ ...fontFamily ? { fontFamily } : {},
2341
+ ...fontSize ? { fontSize: `${fontSize}pt` } : {}
2342
+ };
2343
+ }, [styleValue]);
2344
+ const className = [
2345
+ "lex4-variable-chip",
2346
+ isSelected && "lex4-variable-chip-selected",
2347
+ format & 1 ? "lex4-text-bold" : "",
2348
+ format & 2 ? "lex4-text-italic" : "",
2349
+ format & 8 ? "lex4-text-underline" : "",
2350
+ format & 4 ? "lex4-text-strikethrough" : ""
2351
+ ].filter(Boolean).join(" ");
2352
+ const handleClick = useCallback((event) => {
2353
+ event.preventDefault();
2354
+ if (!event.shiftKey) {
2355
+ clearOtherSelections();
2356
+ }
2357
+ setSelected(!isSelected);
2358
+ }, [clearOtherSelections, isSelected, setSelected]);
2359
+ useEffect(() => {
2360
+ const removeSelectedNodes = () => {
2361
+ editor.update(() => {
2362
+ const selection = $getSelection();
2363
+ if (!$isNodeSelection(selection)) {
2364
+ const node = $getNodeByKey(nodeKey);
2365
+ if ($isVariableNode(node)) {
2366
+ node.remove();
2367
+ }
2368
+ return;
2369
+ }
2370
+ for (const node of selection.getNodes()) {
2371
+ if ($isVariableNode(node)) {
2372
+ node.remove();
2373
+ }
2374
+ }
2375
+ });
2376
+ };
2377
+ const unregisterBackspace = editor.registerCommand(
2378
+ KEY_BACKSPACE_COMMAND,
2379
+ () => {
2380
+ if (!isSelected) {
2381
+ return false;
2382
+ }
2383
+ removeSelectedNodes();
2384
+ return true;
2385
+ },
2386
+ COMMAND_PRIORITY_LOW
2387
+ );
2388
+ const unregisterDelete = editor.registerCommand(
2389
+ KEY_DELETE_COMMAND,
2390
+ () => {
2391
+ if (!isSelected) {
2392
+ return false;
2393
+ }
2394
+ removeSelectedNodes();
2395
+ return true;
2396
+ },
2397
+ COMMAND_PRIORITY_LOW
2398
+ );
2399
+ return () => {
2400
+ unregisterBackspace();
2401
+ unregisterDelete();
2402
+ };
2403
+ }, [editor, isSelected, nodeKey]);
2404
+ return /* @__PURE__ */ jsx(
2405
+ "span",
2406
+ {
2407
+ className,
2408
+ "data-testid": `variable-chip-${variableKey}`,
2409
+ "data-variable-group": group,
2410
+ title: variableKey,
2411
+ style,
2412
+ onMouseDown: (event) => event.preventDefault(),
2413
+ onClick: handleClick,
2414
+ children: label
2415
+ }
2416
+ );
2417
+ }
2418
+ function $createVariableNode(variableKey, format = 0, style = "") {
2419
+ return $applyNodeReplacement(new VariableNode(variableKey, format, style));
2420
+ }
2421
+ function $isVariableNode(node) {
2422
+ return node instanceof VariableNode;
2423
+ }
2424
+ const FORMAT_MASKS = {
2425
+ bold: 1,
2426
+ italic: 2,
2427
+ strikethrough: 4,
2428
+ underline: 8
2429
+ };
2430
+ function withSelectedVariableNodes(editor, updater) {
2431
+ let updated = false;
2432
+ editor.update(() => {
2433
+ const selection = $getSelection();
2434
+ if (!$isNodeSelection(selection)) {
2435
+ return;
2436
+ }
2437
+ const nodes = selection.getNodes().filter($isVariableNode);
2438
+ if (nodes.length === 0) {
2439
+ return;
2440
+ }
2441
+ updater(nodes);
2442
+ updated = true;
2443
+ });
2444
+ return updated;
2445
+ }
2446
+ function getSelectedVariableNodes(editor) {
2447
+ let nodes = [];
2448
+ editor.getEditorState().read(() => {
2449
+ const selection = $getSelection();
2450
+ if (!$isNodeSelection(selection)) {
2451
+ return;
2452
+ }
2453
+ nodes = selection.getNodes().filter($isVariableNode);
2454
+ });
2455
+ return nodes;
2456
+ }
2457
+ function toggleSelectedVariableFormat(editor, format) {
2458
+ const mask = FORMAT_MASKS[format];
2459
+ if (!mask) {
2460
+ return false;
2461
+ }
2462
+ return withSelectedVariableNodes(editor, (nodes) => {
2463
+ const shouldEnable = nodes.some((node) => (node.getFormat() & mask) === 0);
2464
+ for (const node of nodes) {
2465
+ const nextFormat = shouldEnable ? node.getFormat() | mask : node.getFormat() & ~mask;
2466
+ node.setFormat(nextFormat);
2467
+ }
2468
+ });
2469
+ }
2470
+ function applyFontFamilyToSelectedVariables(editor, fontFamily) {
2471
+ return withSelectedVariableNodes(editor, (nodes) => {
2472
+ for (const node of nodes) {
2473
+ node.setStyle(mergeFontFamilyStyle(node.getStyle(), fontFamily));
2474
+ }
2475
+ });
2476
+ }
2477
+ function applyFontSizeToSelectedVariables(editor, size) {
2478
+ return withSelectedVariableNodes(editor, (nodes) => {
2479
+ for (const node of nodes) {
2480
+ node.setStyle(mergeFontSizeStyle(node.getStyle(), size));
2481
+ }
2482
+ });
2483
+ }
2484
+ function readSelectedVariableFormatting(editor) {
2485
+ const firstNode = getSelectedVariableNodes(editor)[0];
2486
+ if (!firstNode) {
2487
+ return {};
2488
+ }
2489
+ const style = firstNode.getStyle();
2490
+ return {
2491
+ fontFamily: extractFontFamilyFromStyle(style),
2492
+ fontSize: extractFontSizePtFromStyle(style)
2493
+ };
2494
+ }
2495
+ const BLOCK_TYPE_OPTIONS = [
2496
+ { value: "paragraph", shortLabel: "P" },
2497
+ { value: "h1", shortLabel: "H1" },
2498
+ { value: "h2", shortLabel: "H2" },
2499
+ { value: "h3", shortLabel: "H3" },
2500
+ { value: "h4", shortLabel: "H4" },
2501
+ { value: "h5", shortLabel: "H5" },
2502
+ { value: "h6", shortLabel: "H6" }
2503
+ ];
2504
+ function getBlockTypeLabel(t, blockType) {
2505
+ switch (blockType) {
2506
+ case "h1":
2507
+ return t.toolbar.heading1;
2508
+ case "h2":
2509
+ return t.toolbar.heading2;
2510
+ case "h3":
2511
+ return t.toolbar.heading3;
2512
+ case "h4":
2513
+ return t.toolbar.heading4;
2514
+ case "h5":
2515
+ return t.toolbar.heading5;
2516
+ case "h6":
2517
+ return t.toolbar.heading6;
2518
+ default:
2519
+ return t.toolbar.paragraph;
2520
+ }
2521
+ }
2522
+ function getBlockTypeShortLabel(blockType) {
2523
+ var _a;
2524
+ return ((_a = BLOCK_TYPE_OPTIONS.find((option) => option.value === blockType)) == null ? void 0 : _a.shortLabel) ?? "P";
2525
+ }
2526
+ const BlockTypePicker = ({ value, onChange }) => {
2527
+ const t = useTranslations();
2528
+ const [open, setOpen] = useState(false);
2529
+ const containerRef = useRef(null);
2530
+ useEffect(() => {
2531
+ if (!open) {
2532
+ return;
2533
+ }
2534
+ const handleClickOutside = (event) => {
2535
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
2536
+ setOpen(false);
2537
+ }
2538
+ };
2539
+ const handleEscape = (event) => {
2540
+ if (event.key === "Escape") {
2541
+ setOpen(false);
2542
+ }
2543
+ };
2544
+ document.addEventListener("mousedown", handleClickOutside);
2545
+ document.addEventListener("keydown", handleEscape);
2546
+ return () => {
2547
+ document.removeEventListener("mousedown", handleClickOutside);
2548
+ document.removeEventListener("keydown", handleEscape);
2549
+ };
2550
+ }, [open]);
2551
+ return /* @__PURE__ */ jsxs(
2552
+ "div",
2553
+ {
2554
+ ref: containerRef,
2555
+ className: "lex4-block-type-picker",
2556
+ "data-testid": "block-type-picker",
2557
+ children: [
2558
+ /* @__PURE__ */ jsxs(
2559
+ "button",
2560
+ {
2561
+ type: "button",
2562
+ className: `lex4-block-type-trigger${open ? " open" : ""}`,
2563
+ "data-testid": "block-type-selector",
2564
+ "aria-label": t.toolbar.blockType,
2565
+ "aria-expanded": open,
2566
+ "aria-haspopup": "menu",
2567
+ title: getBlockTypeLabel(t, value),
2568
+ onMouseDown: (event) => event.preventDefault(),
2569
+ onClick: () => setOpen((current) => !current),
2570
+ children: [
2571
+ /* @__PURE__ */ jsx(
2572
+ "span",
2573
+ {
2574
+ className: `lex4-block-type-trigger-code${value === "paragraph" ? "" : " heading"}`,
2575
+ children: getBlockTypeShortLabel(value)
2576
+ }
2577
+ ),
2578
+ /* @__PURE__ */ jsx(ChevronDown, { size: 14, className: `lex4-block-type-trigger-chevron${open ? " open" : ""}` })
2579
+ ]
2580
+ }
2581
+ ),
2582
+ open && /* @__PURE__ */ jsx(
2583
+ "div",
2584
+ {
2585
+ className: "lex4-block-type-menu",
2586
+ role: "menu",
2587
+ "data-testid": "block-type-menu",
2588
+ children: BLOCK_TYPE_OPTIONS.map((option) => {
2589
+ const label = getBlockTypeLabel(t, option.value);
2590
+ const active = option.value === value;
2591
+ return /* @__PURE__ */ jsxs(
2592
+ "button",
2593
+ {
2594
+ type: "button",
2595
+ role: "menuitemradio",
2596
+ "aria-checked": active,
2597
+ className: `lex4-block-type-item${active ? " active" : ""}`,
2598
+ "data-testid": `block-type-option-${option.value}`,
2599
+ onMouseDown: (event) => event.preventDefault(),
2600
+ onClick: () => {
2601
+ onChange(option.value);
2602
+ setOpen(false);
2603
+ },
2604
+ children: [
2605
+ /* @__PURE__ */ jsx(
2606
+ "span",
2607
+ {
2608
+ className: `lex4-block-type-item-code${option.value === "paragraph" ? "" : " heading"}${active ? " active" : ""}`,
2609
+ children: option.shortLabel
2610
+ }
2611
+ ),
2612
+ /* @__PURE__ */ jsx("span", { className: "lex4-block-type-item-label", children: label })
2613
+ ]
2614
+ },
2615
+ option.value
2616
+ );
2617
+ })
2618
+ }
2619
+ )
2620
+ ]
2621
+ }
2622
+ );
2623
+ };
2075
2624
  const HeaderFooterToggle = ({
2076
2625
  enabled,
2077
- onToggle
2626
+ onToggle,
2627
+ showLabel = true
2078
2628
  }) => {
2079
2629
  const t = useTranslations();
2080
2630
  return /* @__PURE__ */ jsxs(
@@ -2084,18 +2634,20 @@ const HeaderFooterToggle = ({
2084
2634
  "data-testid": "header-footer-toggle",
2085
2635
  children: [
2086
2636
  /* @__PURE__ */ jsx(FileText, { size: 14, className: "lex4-hf-toggle-icon" }),
2087
- /* @__PURE__ */ jsx("span", { className: "lex4-hf-toggle-label", children: t.headerFooter.label }),
2637
+ showLabel && /* @__PURE__ */ jsx("span", { className: "lex4-hf-toggle-label", children: t.headerFooter.label }),
2088
2638
  /* @__PURE__ */ jsx(
2089
2639
  "button",
2090
2640
  {
2091
2641
  type: "button",
2092
2642
  role: "switch",
2093
2643
  "aria-checked": enabled,
2644
+ "aria-label": t.headerFooter.label,
2094
2645
  onMouseDown: (e) => e.preventDefault(),
2095
2646
  onClick: () => onToggle(!enabled),
2096
2647
  className: "lex4-hf-switch",
2097
2648
  style: { backgroundColor: enabled ? "var(--color-primary)" : "var(--color-muted)" },
2098
2649
  "data-testid": "header-footer-switch",
2650
+ title: t.headerFooter.label,
2099
2651
  children: /* @__PURE__ */ jsx("span", { className: "lex4-hf-switch-knob" })
2100
2652
  }
2101
2653
  )
@@ -2259,6 +2811,7 @@ const MenuItem = ({ icon, label, onClick, disabled, testId }) => /* @__PURE__ */
2259
2811
  const MenuDivider = () => /* @__PURE__ */ jsx("div", { className: "lex4-settings-divider" });
2260
2812
  const CanvasControls = () => {
2261
2813
  const { document: document2, dispatch, activePageId, runHistoryAction } = useDocument();
2814
+ const toolbarConfig = useToolbarConfig();
2262
2815
  const t = useTranslations();
2263
2816
  const runCanvasAction = useCallback((label, callback) => {
2264
2817
  runHistoryAction(
@@ -2325,12 +2878,16 @@ const CanvasControls = () => {
2325
2878
  dispatch({ type: "SET_PAGE_COUNTER_MODE", mode });
2326
2879
  });
2327
2880
  }, [dispatch, runCanvasAction, t.history.actions.pageCounterSet]);
2881
+ if (!toolbarConfig.headerFooter.visible) {
2882
+ return null;
2883
+ }
2328
2884
  return /* @__PURE__ */ jsxs("div", { className: "lex4-toolbar-group lex4-toolbar-group-gap", "data-testid": "header-footer-controls", children: [
2329
2885
  /* @__PURE__ */ jsx(
2330
2886
  HeaderFooterToggle,
2331
2887
  {
2332
2888
  enabled: document2.headerFooterEnabled,
2333
- onToggle: handleToggle
2889
+ onToggle: handleToggle,
2890
+ showLabel: toolbarConfig.headerFooter.showLabel
2334
2891
  }
2335
2892
  ),
2336
2893
  document2.headerFooterEnabled && /* @__PURE__ */ jsx(
@@ -2363,7 +2920,17 @@ const Toolbar = () => {
2363
2920
  undo
2364
2921
  } = useDocument();
2365
2922
  const { toolbarItems, toolbarEndItems } = useExtensions();
2923
+ const toolbarConfig = useToolbarConfig();
2366
2924
  const t = useTranslations();
2925
+ const [activeBlockType, setActiveBlockType] = useState("paragraph");
2926
+ const [activeFontFamily, setActiveFontFamily] = useState("Calibri");
2927
+ const [activeFontSize, setActiveFontSize] = useState(DEFAULT_FONT_SIZE);
2928
+ const normalizeFontFamily = useCallback((fontFamily) => {
2929
+ if (fontFamily && SUPPORTED_FONTS.includes(fontFamily)) {
2930
+ return fontFamily;
2931
+ }
2932
+ return "Calibri";
2933
+ }, []);
2367
2934
  const withBodySelection = useCallback(
2368
2935
  (editor, action) => {
2369
2936
  editor.update(() => {
@@ -2399,27 +2966,91 @@ const Toolbar = () => {
2399
2966
  },
2400
2967
  [runHistoryAction]
2401
2968
  );
2969
+ useEffect(() => {
2970
+ if (!activeEditor) {
2971
+ setActiveBlockType("paragraph");
2972
+ setActiveFontFamily("Calibri");
2973
+ setActiveFontSize(DEFAULT_FONT_SIZE);
2974
+ return;
2975
+ }
2976
+ const updateSelectionState = () => {
2977
+ const selectedVariables = getSelectedVariableNodes(activeEditor);
2978
+ if (selectedVariables.length > 0) {
2979
+ const formatting = readSelectedVariableFormatting(activeEditor);
2980
+ setActiveBlockType("paragraph");
2981
+ setActiveFontFamily(normalizeFontFamily(formatting.fontFamily));
2982
+ setActiveFontSize(formatting.fontSize ?? DEFAULT_FONT_SIZE);
2983
+ return;
2984
+ }
2985
+ setActiveBlockType(getActiveBlockType(activeEditor));
2986
+ let nextFontFamily = "Calibri";
2987
+ let nextFontSize = DEFAULT_FONT_SIZE;
2988
+ activeEditor.getEditorState().read(() => {
2989
+ const selection = $getSelection();
2990
+ if (!$isRangeSelection(selection)) {
2991
+ return;
2992
+ }
2993
+ const textNode = selection.getNodes().find($isTextNode);
2994
+ if (!textNode) {
2995
+ return;
2996
+ }
2997
+ const style = textNode.getStyle();
2998
+ nextFontFamily = normalizeFontFamily(extractFontFamilyFromStyle(style));
2999
+ nextFontSize = extractFontSizePtFromStyle(style) ?? DEFAULT_FONT_SIZE;
3000
+ });
3001
+ setActiveFontFamily(nextFontFamily);
3002
+ setActiveFontSize(nextFontSize);
3003
+ };
3004
+ updateSelectionState();
3005
+ const unregisterSelectionChange = activeEditor.registerCommand(
3006
+ SELECTION_CHANGE_COMMAND,
3007
+ () => {
3008
+ updateSelectionState();
3009
+ return false;
3010
+ },
3011
+ COMMAND_PRIORITY_LOW
3012
+ );
3013
+ const unregisterUpdateListener = activeEditor.registerUpdateListener(() => {
3014
+ updateSelectionState();
3015
+ });
3016
+ return () => {
3017
+ unregisterSelectionChange();
3018
+ unregisterUpdateListener();
3019
+ };
3020
+ }, [activeEditor, normalizeFontFamily]);
2402
3021
  const handleBold = useCallback(() => {
2403
3022
  debug("toolbar", `bold (globalSelection=${globalSelectionActive}, editors=${editorRegistry.all().length}, hasEditor=${!!activeEditor})`);
2404
3023
  runToolbarAction(t.history.actions.boldApplied, () => {
3024
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "bold")) {
3025
+ return;
3026
+ }
2405
3027
  applyToBodyEditors(toggleBold);
2406
3028
  });
2407
3029
  }, [activeEditor, applyToBodyEditors, editorRegistry, globalSelectionActive, runToolbarAction, t.history.actions.boldApplied]);
2408
3030
  const handleItalic = useCallback(() => {
2409
3031
  debug("toolbar", `italic (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
2410
3032
  runToolbarAction(t.history.actions.italicApplied, () => {
3033
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "italic")) {
3034
+ return;
3035
+ }
2411
3036
  applyToBodyEditors(toggleItalic);
2412
3037
  });
2413
3038
  }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.italicApplied]);
2414
3039
  const handleUnderline = useCallback(() => {
2415
3040
  debug("toolbar", `underline (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
2416
3041
  runToolbarAction(t.history.actions.underlineApplied, () => {
3042
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "underline")) {
3043
+ return;
3044
+ }
2417
3045
  applyToBodyEditors(toggleUnderline);
2418
3046
  });
2419
3047
  }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.underlineApplied]);
2420
3048
  const handleStrikethrough = useCallback(() => {
2421
3049
  debug("toolbar", `strikethrough (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
2422
3050
  runToolbarAction(t.history.actions.strikethroughApplied, () => {
3051
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "strikethrough")) {
3052
+ return;
3053
+ }
2423
3054
  applyToBodyEditors(toggleStrikethrough);
2424
3055
  });
2425
3056
  }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.strikethroughApplied]);
@@ -2465,20 +3096,43 @@ const Toolbar = () => {
2465
3096
  }, [applyToBodyEditors, runToolbarAction, t.history.actions.outdentedContent]);
2466
3097
  const handleFontChange = useCallback(
2467
3098
  (e) => {
2468
- runToolbarAction(interpolate(t.history.actions.fontChanged, { value: e.target.value }), () => {
2469
- applyToBodyEditors((editor) => applyFontFamily(editor, e.target.value));
3099
+ const fontFamily = e.target.value;
3100
+ runToolbarAction(interpolate(t.history.actions.fontChanged, { value: fontFamily }), () => {
3101
+ if (activeEditor && applyFontFamilyToSelectedVariables(activeEditor, fontFamily)) {
3102
+ return;
3103
+ }
3104
+ applyToBodyEditors((editor) => applyFontFamily(editor, fontFamily));
2470
3105
  });
2471
3106
  },
2472
- [applyToBodyEditors, runToolbarAction, t.history.actions.fontChanged]
3107
+ [activeEditor, applyToBodyEditors, runToolbarAction, t.history.actions.fontChanged]
2473
3108
  );
2474
3109
  const handleFontSizeChange = useCallback(
2475
3110
  (e) => {
2476
3111
  const size = parseInt(e.target.value, 10);
2477
3112
  runToolbarAction(interpolate(t.history.actions.fontSizeChanged, { value: String(size) }), () => {
3113
+ if (activeEditor && applyFontSizeToSelectedVariables(activeEditor, size)) {
3114
+ return;
3115
+ }
2478
3116
  applyToBodyEditors((editor) => applyFontSize(editor, size));
2479
3117
  });
2480
3118
  },
2481
- [applyToBodyEditors, runToolbarAction, t.history.actions.fontSizeChanged]
3119
+ [activeEditor, applyToBodyEditors, runToolbarAction, t.history.actions.fontSizeChanged]
3120
+ );
3121
+ const handleBlockTypeChange = useCallback(
3122
+ (blockType) => {
3123
+ runToolbarAction(
3124
+ interpolate(t.history.actions.blockTypeChanged, { value: getBlockTypeLabel(t, blockType) }),
3125
+ () => {
3126
+ applyToBodyEditors((editor) => setBlockType(editor, blockType));
3127
+ }
3128
+ );
3129
+ },
3130
+ [
3131
+ applyToBodyEditors,
3132
+ runToolbarAction,
3133
+ t.history.actions.blockTypeChanged,
3134
+ t
3135
+ ]
2482
3136
  );
2483
3137
  const handleToggleHistory = useCallback(() => {
2484
3138
  setHistorySidebarOpen(!historySidebarOpen);
@@ -2512,6 +3166,14 @@ const Toolbar = () => {
2512
3166
  )
2513
3167
  ] }),
2514
3168
  /* @__PURE__ */ jsx(Divider, {}),
3169
+ /* @__PURE__ */ jsx("div", { className: "lex4-toolbar-group", children: /* @__PURE__ */ jsx(
3170
+ BlockTypePicker,
3171
+ {
3172
+ value: activeBlockType,
3173
+ onChange: handleBlockTypeChange
3174
+ }
3175
+ ) }),
3176
+ /* @__PURE__ */ jsx(Divider, {}),
2515
3177
  /* @__PURE__ */ jsxs("div", { className: "lex4-toolbar-group-gap", children: [
2516
3178
  /* @__PURE__ */ jsx(Type, { size: 14, className: "lex4-toolbar-icon" }),
2517
3179
  /* @__PURE__ */ jsx(
@@ -2519,7 +3181,7 @@ const Toolbar = () => {
2519
3181
  {
2520
3182
  className: "lex4-toolbar-select",
2521
3183
  "data-testid": "font-selector",
2522
- defaultValue: "Calibri",
3184
+ value: activeFontFamily,
2523
3185
  onChange: handleFontChange,
2524
3186
  children: SUPPORTED_FONTS.map((font) => /* @__PURE__ */ jsx("option", { value: font, style: { fontFamily: font }, children: font }, font))
2525
3187
  }
@@ -2532,7 +3194,7 @@ const Toolbar = () => {
2532
3194
  {
2533
3195
  className: "lex4-toolbar-select lex4-toolbar-select-narrow",
2534
3196
  "data-testid": "font-size-selector",
2535
- defaultValue: "12",
3197
+ value: String(activeFontSize),
2536
3198
  onChange: handleFontSizeChange,
2537
3199
  children: SUPPORTED_FONT_SIZES.map((size) => /* @__PURE__ */ jsx("option", { value: size, children: size }, size))
2538
3200
  }
@@ -2567,7 +3229,7 @@ const Toolbar = () => {
2567
3229
  /* @__PURE__ */ jsx(CanvasControls, {}),
2568
3230
  /* @__PURE__ */ jsxs("div", { className: "lex4-toolbar-end", children: [
2569
3231
  toolbarEndItems.map((EndItem, idx) => /* @__PURE__ */ jsx(EndItem, {}, idx)),
2570
- /* @__PURE__ */ jsxs(
3232
+ toolbarConfig.history.visible && /* @__PURE__ */ jsxs(
2571
3233
  "button",
2572
3234
  {
2573
3235
  type: "button",
@@ -2579,7 +3241,7 @@ const Toolbar = () => {
2579
3241
  "aria-label": historySidebarOpen ? t.toolbar.closeHistory : t.toolbar.openHistory,
2580
3242
  children: [
2581
3243
  /* @__PURE__ */ jsx(History, { size: 14 }),
2582
- "History"
3244
+ toolbarConfig.history.showLabel && t.toolbar.history
2583
3245
  ]
2584
3246
  }
2585
3247
  )
@@ -2923,7 +3585,8 @@ const lexicalTheme = {
2923
3585
  h2: "lex4-heading lex4-heading-h2",
2924
3586
  h3: "lex4-heading lex4-heading-h3",
2925
3587
  h4: "lex4-heading lex4-heading-h4",
2926
- h5: "lex4-heading lex4-heading-h5"
3588
+ h5: "lex4-heading lex4-heading-h5",
3589
+ h6: "lex4-heading lex4-heading-h6"
2927
3590
  },
2928
3591
  text: {
2929
3592
  bold: "lex4-text-bold",
@@ -4460,12 +5123,10 @@ function decodeFormatBitmask(format) {
4460
5123
  return marks;
4461
5124
  }
4462
5125
  function extractFontFamily(style) {
4463
- const match = style.match(/font-family:\s*([^;]+)/);
4464
- return match ? match[1].trim().replace(/['"]/g, "") : void 0;
5126
+ return extractFontFamilyFromStyle(style);
4465
5127
  }
4466
5128
  function extractFontSizePt(style) {
4467
- const match = style.match(/font-size:\s*(\d+(?:\.\d+)?)\s*pt/);
4468
- return match ? parseFloat(match[1]) : void 0;
5129
+ return extractFontSizePtFromStyle(style);
4469
5130
  }
4470
5131
  function buildTextMarks(format, style) {
4471
5132
  const formatMarks = decodeFormatBitmask(format);
@@ -4499,9 +5160,11 @@ function mapTextNode(node) {
4499
5160
  };
4500
5161
  }
4501
5162
  function mapVariableNode(node) {
5163
+ const marks = buildTextMarks(node.format ?? 0, node.style);
4502
5164
  return {
4503
5165
  type: "variable",
4504
- key: node.variableKey
5166
+ key: node.variableKey,
5167
+ ...marks ? { marks } : {}
4505
5168
  };
4506
5169
  }
4507
5170
  function mapLineBreak() {
@@ -4565,7 +5228,7 @@ function mapParagraph(node) {
4565
5228
  function mapHeading(node) {
4566
5229
  var _a;
4567
5230
  const alignment = decodeAlignment(node.format);
4568
- const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h(\d)$/);
5231
+ const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h([1-6])$/);
4569
5232
  const level = tagMatch ? parseInt(tagMatch[1], 10) : 1;
4570
5233
  return {
4571
5234
  type: "heading",
@@ -4690,6 +5353,105 @@ function buildSavePayload(ast, options) {
4690
5353
  function serializeDocumentJson(ast) {
4691
5354
  return JSON.stringify(ast, null, 2);
4692
5355
  }
5356
+ function appendChildren(parent, children = []) {
5357
+ const nodes = children.map(buildLexicalNode).filter((node) => node !== null);
5358
+ if (nodes.length > 0) {
5359
+ parent.append(...nodes);
5360
+ }
5361
+ }
5362
+ function applyElementFormatting(node, serializedNode) {
5363
+ if (typeof serializedNode.format === "string" && ["left", "center", "right", "justify"].includes(serializedNode.format) && node.setFormat) {
5364
+ node.setFormat(serializedNode.format);
5365
+ }
5366
+ if (typeof serializedNode.indent === "number" && serializedNode.indent > 0 && node.setIndent) {
5367
+ node.setIndent(serializedNode.indent);
5368
+ }
5369
+ }
5370
+ function buildLexicalNode(serializedNode) {
5371
+ switch (serializedNode.type) {
5372
+ case "paragraph": {
5373
+ const node = $createParagraphNode();
5374
+ applyElementFormatting(node, serializedNode);
5375
+ appendChildren(node, serializedNode.children);
5376
+ return node;
5377
+ }
5378
+ case "heading": {
5379
+ const tag = typeof serializedNode.tag === "string" && /^h[1-6]$/.test(serializedNode.tag) ? serializedNode.tag : "h1";
5380
+ const node = $createHeadingNode(tag);
5381
+ applyElementFormatting(node, serializedNode);
5382
+ appendChildren(node, serializedNode.children);
5383
+ return node;
5384
+ }
5385
+ case "quote": {
5386
+ const node = $createQuoteNode();
5387
+ applyElementFormatting(node, serializedNode);
5388
+ appendChildren(node, serializedNode.children);
5389
+ return node;
5390
+ }
5391
+ case "list": {
5392
+ const listType = serializedNode.listType === "number" ? "number" : "bullet";
5393
+ const node = $createListNode(listType, typeof serializedNode.start === "number" ? serializedNode.start : 1);
5394
+ appendChildren(node, serializedNode.children);
5395
+ return node;
5396
+ }
5397
+ case "listitem": {
5398
+ const node = $createListItemNode();
5399
+ appendChildren(node, serializedNode.children);
5400
+ return node;
5401
+ }
5402
+ case "text": {
5403
+ const node = $createTextNode(serializedNode.text ?? "");
5404
+ if (typeof serializedNode.format === "number" && serializedNode.format > 0) {
5405
+ node.setFormat(serializedNode.format);
5406
+ }
5407
+ if (typeof serializedNode.style === "string" && serializedNode.style.trim() !== "") {
5408
+ node.setStyle(serializedNode.style);
5409
+ }
5410
+ return node;
5411
+ }
5412
+ case "linebreak":
5413
+ return $createLineBreakNode();
5414
+ case "variable-node":
5415
+ return $createVariableNode(
5416
+ serializedNode.variableKey ?? "",
5417
+ typeof serializedNode.format === "number" ? serializedNode.format : 0,
5418
+ typeof serializedNode.style === "string" ? serializedNode.style : ""
5419
+ );
5420
+ default:
5421
+ return null;
5422
+ }
5423
+ }
5424
+ function getBodyChildren(pageState) {
5425
+ const root = pageState == null ? void 0 : pageState.root;
5426
+ return (root == null ? void 0 : root.children) ?? [];
5427
+ }
5428
+ function insertDocumentContent(editor, document2) {
5429
+ let inserted = false;
5430
+ editor.update(() => {
5431
+ const nodes = document2.pages.flatMap((page) => getBodyChildren(page.bodyState)).map(buildLexicalNode).filter((node) => node !== null);
5432
+ if (nodes.length === 0) {
5433
+ return;
5434
+ }
5435
+ const selection = $getSelection();
5436
+ if ($isRangeSelection(selection)) {
5437
+ if (selection.isCollapsed()) {
5438
+ selection.insertParagraph();
5439
+ }
5440
+ const nextSelection = $getSelection();
5441
+ if ($isRangeSelection(nextSelection) || $isNodeSelection(nextSelection)) {
5442
+ nextSelection.insertNodes(nodes);
5443
+ } else {
5444
+ $insertNodes(nodes);
5445
+ }
5446
+ } else if ($isNodeSelection(selection)) {
5447
+ selection.insertNodes(nodes);
5448
+ } else {
5449
+ $insertNodes(nodes);
5450
+ }
5451
+ inserted = true;
5452
+ });
5453
+ return inserted;
5454
+ }
4693
5455
  function selectEntireDocument(rootElement, selectionBuffer) {
4694
5456
  if (!rootElement || !selectionBuffer) {
4695
5457
  return;
@@ -4896,10 +5658,13 @@ const EditorWithHandle = forwardRef(({ captureHistoryShortcutsOnWindow, onSave,
4896
5658
  const {
4897
5659
  document: doc,
4898
5660
  activeEditor,
5661
+ activeCaretPosition,
4899
5662
  historySidebarOpen,
5663
+ runHistoryAction,
4900
5664
  setHistorySidebarOpen
4901
5665
  } = useDocument();
4902
5666
  const { handleFactories } = useExtensions();
5667
+ const t = useTranslations();
4903
5668
  const getDocument = useCallback(() => doc, [doc]);
4904
5669
  const getActiveEditor = useCallback(() => activeEditor, [activeEditor]);
4905
5670
  const extensionCtx = useExtensionContext(getDocument, getActiveEditor);
@@ -4910,6 +5675,23 @@ const EditorWithHandle = forwardRef(({ captureHistoryShortcutsOnWindow, onSave,
4910
5675
  },
4911
5676
  toggleHistorySidebar: () => {
4912
5677
  setHistorySidebarOpen(!historySidebarOpen);
5678
+ },
5679
+ insertDocumentContent: (documentToInsert) => {
5680
+ if (!activeEditor || (activeCaretPosition == null ? void 0 : activeCaretPosition.region) !== "body") {
5681
+ return false;
5682
+ }
5683
+ let inserted = false;
5684
+ runHistoryAction(
5685
+ {
5686
+ label: t.history.actions.insertedDocumentContent,
5687
+ source: "toolbar",
5688
+ region: "document"
5689
+ },
5690
+ () => {
5691
+ inserted = insertDocumentContent(activeEditor, documentToInsert);
5692
+ }
5693
+ );
5694
+ return inserted;
4913
5695
  }
4914
5696
  };
4915
5697
  for (const factory of handleFactories) {
@@ -4917,7 +5699,16 @@ const EditorWithHandle = forwardRef(({ captureHistoryShortcutsOnWindow, onSave,
4917
5699
  Object.assign(handle, methods);
4918
5700
  }
4919
5701
  return handle;
4920
- }, [extensionCtx, handleFactories, historySidebarOpen, setHistorySidebarOpen]);
5702
+ }, [
5703
+ activeCaretPosition == null ? void 0 : activeCaretPosition.region,
5704
+ activeEditor,
5705
+ extensionCtx,
5706
+ handleFactories,
5707
+ historySidebarOpen,
5708
+ runHistoryAction,
5709
+ setHistorySidebarOpen,
5710
+ t.history.actions.insertedDocumentContent
5711
+ ]);
4921
5712
  return /* @__PURE__ */ jsx(
4922
5713
  EditorChrome,
4923
5714
  {
@@ -4935,9 +5726,10 @@ const Lex4Editor = forwardRef(({
4935
5726
  onSave,
4936
5727
  extensions,
4937
5728
  translations,
5729
+ toolbar,
4938
5730
  className
4939
5731
  }, ref) => {
4940
- return /* @__PURE__ */ jsx(TranslationsProvider, { translations, children: /* @__PURE__ */ jsx(ExtensionStateProvider, { children: /* @__PURE__ */ jsx(ExtensionProvider, { extensions, children: /* @__PURE__ */ jsx(
5732
+ return /* @__PURE__ */ jsx(TranslationsProvider, { translations, children: /* @__PURE__ */ jsx(ToolbarConfigProvider, { toolbar, children: /* @__PURE__ */ jsx(ExtensionStateProvider, { children: /* @__PURE__ */ jsx(ExtensionProvider, { extensions, children: /* @__PURE__ */ jsx(
4941
5733
  DocumentProvider,
4942
5734
  {
4943
5735
  initialDocument,
@@ -4952,7 +5744,7 @@ const Lex4Editor = forwardRef(({
4952
5744
  }
4953
5745
  )
4954
5746
  }
4955
- ) }) }) });
5747
+ ) }) }) }) });
4956
5748
  });
4957
5749
  Lex4Editor.displayName = "Lex4Editor";
4958
5750
  function useOverflowDetection(bodyHeight, onOverflow, debounceMs = 100) {
@@ -5046,120 +5838,6 @@ function astExtension() {
5046
5838
  }
5047
5839
  };
5048
5840
  }
5049
- const EMPTY_CONTEXT = {
5050
- definitions: [],
5051
- refreshDefinitions: () => {
5052
- },
5053
- getDefinition: () => void 0
5054
- };
5055
- const VariableContext = createContext(EMPTY_CONTEXT);
5056
- const VariableProvider = ({
5057
- initialDefinitions = [],
5058
- children
5059
- }) => {
5060
- const [definitions, setDefinitions] = useState(initialDefinitions);
5061
- const refresh = useCallback((newDefinitions) => {
5062
- setDefinitions(newDefinitions);
5063
- }, []);
5064
- const getDefinition = useCallback(
5065
- (key) => {
5066
- return definitions.find((d) => d.key === key);
5067
- },
5068
- [definitions]
5069
- );
5070
- const value = useMemo(
5071
- () => ({ definitions, refreshDefinitions: refresh, getDefinition }),
5072
- [definitions, refresh, getDefinition]
5073
- );
5074
- return /* @__PURE__ */ jsx(VariableContext.Provider, { value, children });
5075
- };
5076
- function useVariables() {
5077
- return useContext(VariableContext);
5078
- }
5079
- class VariableNode extends DecoratorNode {
5080
- constructor(variableKey, key) {
5081
- super(key);
5082
- __publicField(this, "__variableKey");
5083
- this.__variableKey = variableKey;
5084
- }
5085
- static getType() {
5086
- return "variable-node";
5087
- }
5088
- static clone(node) {
5089
- return new VariableNode(node.__variableKey, node.__key);
5090
- }
5091
- getVariableKey() {
5092
- return this.__variableKey;
5093
- }
5094
- // -- Serialization --
5095
- static importJSON(serializedNode) {
5096
- return $createVariableNode(serializedNode.variableKey);
5097
- }
5098
- exportJSON() {
5099
- return {
5100
- type: "variable-node",
5101
- version: 1,
5102
- variableKey: this.__variableKey
5103
- };
5104
- }
5105
- // -- DOM --
5106
- createDOM() {
5107
- const span = document.createElement("span");
5108
- span.className = "lex4-variable";
5109
- span.setAttribute("data-variable-key", this.__variableKey);
5110
- span.setAttribute("data-testid", `variable-${this.__variableKey}`);
5111
- span.contentEditable = "false";
5112
- return span;
5113
- }
5114
- updateDOM() {
5115
- return false;
5116
- }
5117
- exportDOM() {
5118
- const span = document.createElement("span");
5119
- span.setAttribute("data-variable-key", this.__variableKey);
5120
- span.textContent = `{{${this.__variableKey}}}`;
5121
- return { element: span };
5122
- }
5123
- static importDOM() {
5124
- return null;
5125
- }
5126
- // -- Behavior --
5127
- isInline() {
5128
- return true;
5129
- }
5130
- isKeyboardSelectable() {
5131
- return true;
5132
- }
5133
- getTextContent() {
5134
- return `{{${this.__variableKey}}}`;
5135
- }
5136
- // -- Rendering --
5137
- decorate() {
5138
- return /* @__PURE__ */ jsx(VariableChip, { variableKey: this.__variableKey });
5139
- }
5140
- }
5141
- function VariableChip({ variableKey }) {
5142
- const { getDefinition } = useVariables();
5143
- const def = getDefinition(variableKey);
5144
- const label = (def == null ? void 0 : def.label) ?? variableKey;
5145
- const group = def == null ? void 0 : def.group;
5146
- return /* @__PURE__ */ jsx(
5147
- "span",
5148
- {
5149
- className: "lex4-variable-chip",
5150
- "data-testid": `variable-chip-${variableKey}`,
5151
- "data-variable-group": group,
5152
- title: variableKey,
5153
- children: label
5154
- }
5155
- );
5156
- }
5157
- function $createVariableNode(variableKey) {
5158
- return $applyNodeReplacement(new VariableNode(variableKey));
5159
- }
5160
- function $isVariableNode(node) {
5161
- return node instanceof VariableNode;
5162
- }
5163
5841
  const INSERT_VARIABLE_COMMAND = createCommand("INSERT_VARIABLE");
5164
5842
  const VariablePlugin = () => {
5165
5843
  const [editor] = useLexicalComposerContext();
@@ -5420,7 +6098,11 @@ const VariablePanelStateProvider = ({ children }) => {
5420
6098
  };
5421
6099
  const VariableToolbarToggle = () => {
5422
6100
  const { panelOpen, setPanelOpen } = useVariablePanelState();
6101
+ const toolbarConfig = useToolbarConfig();
5423
6102
  const t = useTranslations();
6103
+ if (!toolbarConfig.variables.visible) {
6104
+ return null;
6105
+ }
5424
6106
  return /* @__PURE__ */ jsxs(
5425
6107
  "button",
5426
6108
  {
@@ -5433,7 +6115,7 @@ const VariableToolbarToggle = () => {
5433
6115
  "aria-label": panelOpen ? t.variables.closePanel : t.variables.openPanel,
5434
6116
  children: [
5435
6117
  /* @__PURE__ */ jsx(Braces, { size: 14 }),
5436
- t.variables.title
6118
+ toolbarConfig.variables.showLabel && t.variables.title
5437
6119
  ]
5438
6120
  }
5439
6121
  );