@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
@@ -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,
@@ -1890,6 +1915,36 @@ const HistorySidebar = () => {
1890
1915
  }
1891
1916
  );
1892
1917
  };
1918
+ const DEFAULT_TOOLBAR_CONTROL_CONFIG = {
1919
+ visible: true,
1920
+ showLabel: true
1921
+ };
1922
+ const DEFAULT_TOOLBAR_CONFIG = {
1923
+ history: DEFAULT_TOOLBAR_CONTROL_CONFIG,
1924
+ variables: DEFAULT_TOOLBAR_CONTROL_CONFIG,
1925
+ headerFooter: DEFAULT_TOOLBAR_CONTROL_CONFIG
1926
+ };
1927
+ const ToolbarConfigContext = createContext(DEFAULT_TOOLBAR_CONFIG);
1928
+ function resolveControlConfig(config) {
1929
+ return {
1930
+ visible: (config == null ? void 0 : config.visible) ?? true,
1931
+ showLabel: (config == null ? void 0 : config.showLabel) ?? true
1932
+ };
1933
+ }
1934
+ function normalizeToolbarConfig(config) {
1935
+ return {
1936
+ history: resolveControlConfig(config == null ? void 0 : config.history),
1937
+ variables: resolveControlConfig(config == null ? void 0 : config.variables),
1938
+ headerFooter: resolveControlConfig(config == null ? void 0 : config.headerFooter)
1939
+ };
1940
+ }
1941
+ const ToolbarConfigProvider = ({ toolbar, children }) => {
1942
+ const resolvedConfig = useMemo(() => normalizeToolbarConfig(toolbar), [toolbar]);
1943
+ return /* @__PURE__ */ jsx(ToolbarConfigContext.Provider, { value: resolvedConfig, children });
1944
+ };
1945
+ function useToolbarConfig() {
1946
+ return useContext(ToolbarConfigContext);
1947
+ }
1893
1948
  function resolveExtensions(extensions) {
1894
1949
  const resolved = {
1895
1950
  nodes: [],
@@ -2022,6 +2077,7 @@ const SUPPORTED_FONT_SIZES = [
2022
2077
  48,
2023
2078
  72
2024
2079
  ];
2080
+ const DEFAULT_FONT_SIZE = 12;
2025
2081
  function applyFontSize(editor, size) {
2026
2082
  editor.update(() => {
2027
2083
  const selection = $getSelection();
@@ -2072,9 +2128,366 @@ function indentContent(editor) {
2072
2128
  function outdentContent(editor) {
2073
2129
  editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, void 0);
2074
2130
  }
2131
+ function setBlockType(editor, blockType) {
2132
+ editor.update(() => {
2133
+ const selection = $getSelection();
2134
+ if (!$isRangeSelection(selection)) {
2135
+ return;
2136
+ }
2137
+ if (blockType === "paragraph") {
2138
+ $setBlocksType(selection, () => $createParagraphNode());
2139
+ return;
2140
+ }
2141
+ $setBlocksType(selection, () => $createHeadingNode(blockType));
2142
+ });
2143
+ }
2144
+ function getActiveBlockType(editor) {
2145
+ let blockType = "paragraph";
2146
+ editor.getEditorState().read(() => {
2147
+ const selection = $getSelection();
2148
+ if (!$isRangeSelection(selection)) {
2149
+ return;
2150
+ }
2151
+ const anchorNode = selection.anchor.getNode();
2152
+ const topLevelElement = anchorNode.getTopLevelElementOrThrow();
2153
+ if ($isHeadingNode(topLevelElement)) {
2154
+ blockType = topLevelElement.getTag();
2155
+ }
2156
+ });
2157
+ return blockType;
2158
+ }
2159
+ function extractStyleValue(style, property) {
2160
+ const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2161
+ const match = style.match(new RegExp(`${escapedProperty}:\\s*([^;]+)`));
2162
+ return match ? match[1].trim().replace(/['"]/g, "") : void 0;
2163
+ }
2164
+ function mergeStyleDeclaration(existingStyle, property, value) {
2165
+ const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2166
+ const stripped = existingStyle.replace(
2167
+ new RegExp(`${escapedProperty}:\\s*[^;]+;?\\s*`, "g"),
2168
+ ""
2169
+ ).trim();
2170
+ const declaration = `${property}: ${value}`;
2171
+ return stripped ? `${stripped}; ${declaration}` : declaration;
2172
+ }
2173
+ function extractFontFamilyFromStyle(style) {
2174
+ return extractStyleValue(style, "font-family");
2175
+ }
2176
+ function extractFontSizePtFromStyle(style) {
2177
+ const value = extractStyleValue(style, "font-size");
2178
+ if (!value) {
2179
+ return void 0;
2180
+ }
2181
+ const match = value.match(/^(\d+(?:\.\d+)?)\s*pt$/);
2182
+ return match ? parseFloat(match[1]) : void 0;
2183
+ }
2184
+ function mergeFontFamilyStyle(existingStyle, fontFamily) {
2185
+ return mergeStyleDeclaration(existingStyle, "font-family", fontFamily);
2186
+ }
2187
+ function mergeFontSizeStyle(existingStyle, size) {
2188
+ return mergeStyleDeclaration(existingStyle, "font-size", `${size}pt`);
2189
+ }
2190
+ const EMPTY_CONTEXT = {
2191
+ definitions: [],
2192
+ refreshDefinitions: () => {
2193
+ },
2194
+ getDefinition: () => void 0
2195
+ };
2196
+ const VariableContext = createContext(EMPTY_CONTEXT);
2197
+ const VariableProvider = ({
2198
+ initialDefinitions = [],
2199
+ children
2200
+ }) => {
2201
+ const [definitions, setDefinitions] = useState(initialDefinitions);
2202
+ const refresh = useCallback((newDefinitions) => {
2203
+ setDefinitions(newDefinitions);
2204
+ }, []);
2205
+ const getDefinition = useCallback(
2206
+ (key) => {
2207
+ return definitions.find((d) => d.key === key);
2208
+ },
2209
+ [definitions]
2210
+ );
2211
+ const value = useMemo(
2212
+ () => ({ definitions, refreshDefinitions: refresh, getDefinition }),
2213
+ [definitions, refresh, getDefinition]
2214
+ );
2215
+ return /* @__PURE__ */ jsx(VariableContext.Provider, { value, children });
2216
+ };
2217
+ function useVariables() {
2218
+ return useContext(VariableContext);
2219
+ }
2220
+ class VariableNode extends DecoratorNode {
2221
+ constructor(variableKey, format = 0, style = "", key) {
2222
+ super(key);
2223
+ __publicField(this, "__variableKey");
2224
+ __publicField(this, "__format");
2225
+ __publicField(this, "__style");
2226
+ this.__variableKey = variableKey;
2227
+ this.__format = format;
2228
+ this.__style = style;
2229
+ }
2230
+ static getType() {
2231
+ return "variable-node";
2232
+ }
2233
+ static clone(node) {
2234
+ return new VariableNode(node.__variableKey, node.__format, node.__style, node.__key);
2235
+ }
2236
+ getVariableKey() {
2237
+ return this.getLatest().__variableKey;
2238
+ }
2239
+ getFormat() {
2240
+ return this.getLatest().__format;
2241
+ }
2242
+ setFormat(format) {
2243
+ const writable = this.getWritable();
2244
+ writable.__format = format;
2245
+ return writable;
2246
+ }
2247
+ getStyle() {
2248
+ return this.getLatest().__style;
2249
+ }
2250
+ setStyle(style) {
2251
+ const writable = this.getWritable();
2252
+ writable.__style = style;
2253
+ return writable;
2254
+ }
2255
+ // -- Serialization --
2256
+ static importJSON(serializedNode) {
2257
+ return $createVariableNode(
2258
+ serializedNode.variableKey,
2259
+ serializedNode.format ?? 0,
2260
+ serializedNode.style ?? ""
2261
+ );
2262
+ }
2263
+ exportJSON() {
2264
+ return {
2265
+ type: "variable-node",
2266
+ version: 1,
2267
+ variableKey: this.__variableKey,
2268
+ format: this.__format,
2269
+ style: this.__style
2270
+ };
2271
+ }
2272
+ // -- DOM --
2273
+ createDOM() {
2274
+ const span = document.createElement("span");
2275
+ span.className = "lex4-variable";
2276
+ span.setAttribute("data-variable-key", this.__variableKey);
2277
+ span.setAttribute("data-testid", `variable-${this.__variableKey}`);
2278
+ span.contentEditable = "false";
2279
+ return span;
2280
+ }
2281
+ updateDOM() {
2282
+ return false;
2283
+ }
2284
+ exportDOM() {
2285
+ const span = document.createElement("span");
2286
+ span.setAttribute("data-variable-key", this.__variableKey);
2287
+ span.textContent = `{{${this.__variableKey}}}`;
2288
+ return { element: span };
2289
+ }
2290
+ static importDOM() {
2291
+ return null;
2292
+ }
2293
+ // -- Behavior --
2294
+ isInline() {
2295
+ return true;
2296
+ }
2297
+ isKeyboardSelectable() {
2298
+ return true;
2299
+ }
2300
+ getTextContent() {
2301
+ return `{{${this.__variableKey}}}`;
2302
+ }
2303
+ // -- Rendering --
2304
+ decorate() {
2305
+ return /* @__PURE__ */ jsx(
2306
+ VariableChip,
2307
+ {
2308
+ nodeKey: this.__key,
2309
+ variableKey: this.__variableKey,
2310
+ format: this.__format,
2311
+ styleValue: this.__style
2312
+ }
2313
+ );
2314
+ }
2315
+ }
2316
+ function VariableChip({
2317
+ nodeKey,
2318
+ variableKey,
2319
+ format,
2320
+ styleValue
2321
+ }) {
2322
+ const { getDefinition } = useVariables();
2323
+ const [editor] = useLexicalComposerContext();
2324
+ const [isSelected, setSelected, clearOtherSelections] = useLexicalNodeSelection(nodeKey);
2325
+ const def = getDefinition(variableKey);
2326
+ const label = (def == null ? void 0 : def.label) ?? variableKey;
2327
+ const group = def == null ? void 0 : def.group;
2328
+ const style = useMemo(() => {
2329
+ const fontFamily = extractFontFamilyFromStyle(styleValue);
2330
+ const fontSize = extractFontSizePtFromStyle(styleValue);
2331
+ return {
2332
+ ...fontFamily ? { fontFamily } : {},
2333
+ ...fontSize ? { fontSize: `${fontSize}pt` } : {}
2334
+ };
2335
+ }, [styleValue]);
2336
+ const className = [
2337
+ "lex4-variable-chip",
2338
+ isSelected && "lex4-variable-chip-selected",
2339
+ format & 1 ? "lex4-text-bold" : "",
2340
+ format & 2 ? "lex4-text-italic" : "",
2341
+ format & 8 ? "lex4-text-underline" : "",
2342
+ format & 4 ? "lex4-text-strikethrough" : ""
2343
+ ].filter(Boolean).join(" ");
2344
+ const handleClick = useCallback((event) => {
2345
+ event.preventDefault();
2346
+ if (!event.shiftKey) {
2347
+ clearOtherSelections();
2348
+ }
2349
+ setSelected(!isSelected);
2350
+ }, [clearOtherSelections, isSelected, setSelected]);
2351
+ useEffect(() => {
2352
+ const removeSelectedNodes = () => {
2353
+ editor.update(() => {
2354
+ const selection = $getSelection();
2355
+ if (!$isNodeSelection(selection)) {
2356
+ const node = $getNodeByKey(nodeKey);
2357
+ if ($isVariableNode(node)) {
2358
+ node.remove();
2359
+ }
2360
+ return;
2361
+ }
2362
+ for (const node of selection.getNodes()) {
2363
+ if ($isVariableNode(node)) {
2364
+ node.remove();
2365
+ }
2366
+ }
2367
+ });
2368
+ };
2369
+ const unregisterBackspace = editor.registerCommand(
2370
+ KEY_BACKSPACE_COMMAND,
2371
+ () => {
2372
+ if (!isSelected) {
2373
+ return false;
2374
+ }
2375
+ removeSelectedNodes();
2376
+ return true;
2377
+ },
2378
+ COMMAND_PRIORITY_LOW
2379
+ );
2380
+ const unregisterDelete = editor.registerCommand(
2381
+ KEY_DELETE_COMMAND,
2382
+ () => {
2383
+ if (!isSelected) {
2384
+ return false;
2385
+ }
2386
+ removeSelectedNodes();
2387
+ return true;
2388
+ },
2389
+ COMMAND_PRIORITY_LOW
2390
+ );
2391
+ return () => {
2392
+ unregisterBackspace();
2393
+ unregisterDelete();
2394
+ };
2395
+ }, [editor, isSelected, nodeKey]);
2396
+ return /* @__PURE__ */ jsx(
2397
+ "span",
2398
+ {
2399
+ className,
2400
+ "data-testid": `variable-chip-${variableKey}`,
2401
+ "data-variable-group": group,
2402
+ title: variableKey,
2403
+ style,
2404
+ onMouseDown: (event) => event.preventDefault(),
2405
+ onClick: handleClick,
2406
+ children: label
2407
+ }
2408
+ );
2409
+ }
2410
+ function $createVariableNode(variableKey, format = 0, style = "") {
2411
+ return $applyNodeReplacement(new VariableNode(variableKey, format, style));
2412
+ }
2413
+ function $isVariableNode(node) {
2414
+ return node instanceof VariableNode;
2415
+ }
2416
+ const FORMAT_MASKS = {
2417
+ bold: 1,
2418
+ italic: 2,
2419
+ strikethrough: 4,
2420
+ underline: 8
2421
+ };
2422
+ function withSelectedVariableNodes(editor, updater) {
2423
+ let updated = false;
2424
+ editor.update(() => {
2425
+ const selection = $getSelection();
2426
+ if (!$isNodeSelection(selection)) {
2427
+ return;
2428
+ }
2429
+ const nodes = selection.getNodes().filter($isVariableNode);
2430
+ if (nodes.length === 0) {
2431
+ return;
2432
+ }
2433
+ updater(nodes);
2434
+ updated = true;
2435
+ });
2436
+ return updated;
2437
+ }
2438
+ function getSelectedVariableNodes(editor) {
2439
+ let nodes = [];
2440
+ editor.getEditorState().read(() => {
2441
+ const selection = $getSelection();
2442
+ if (!$isNodeSelection(selection)) {
2443
+ return;
2444
+ }
2445
+ nodes = selection.getNodes().filter($isVariableNode);
2446
+ });
2447
+ return nodes;
2448
+ }
2449
+ function toggleSelectedVariableFormat(editor, format) {
2450
+ const mask = FORMAT_MASKS[format];
2451
+ if (!mask) {
2452
+ return false;
2453
+ }
2454
+ return withSelectedVariableNodes(editor, (nodes) => {
2455
+ const shouldEnable = nodes.some((node) => (node.getFormat() & mask) === 0);
2456
+ for (const node of nodes) {
2457
+ const nextFormat = shouldEnable ? node.getFormat() | mask : node.getFormat() & ~mask;
2458
+ node.setFormat(nextFormat);
2459
+ }
2460
+ });
2461
+ }
2462
+ function applyFontFamilyToSelectedVariables(editor, fontFamily) {
2463
+ return withSelectedVariableNodes(editor, (nodes) => {
2464
+ for (const node of nodes) {
2465
+ node.setStyle(mergeFontFamilyStyle(node.getStyle(), fontFamily));
2466
+ }
2467
+ });
2468
+ }
2469
+ function applyFontSizeToSelectedVariables(editor, size) {
2470
+ return withSelectedVariableNodes(editor, (nodes) => {
2471
+ for (const node of nodes) {
2472
+ node.setStyle(mergeFontSizeStyle(node.getStyle(), size));
2473
+ }
2474
+ });
2475
+ }
2476
+ function readSelectedVariableFormatting(editor) {
2477
+ const firstNode = getSelectedVariableNodes(editor)[0];
2478
+ if (!firstNode) {
2479
+ return {};
2480
+ }
2481
+ const style = firstNode.getStyle();
2482
+ return {
2483
+ fontFamily: extractFontFamilyFromStyle(style),
2484
+ fontSize: extractFontSizePtFromStyle(style)
2485
+ };
2486
+ }
2075
2487
  const HeaderFooterToggle = ({
2076
2488
  enabled,
2077
- onToggle
2489
+ onToggle,
2490
+ showLabel = true
2078
2491
  }) => {
2079
2492
  const t = useTranslations();
2080
2493
  return /* @__PURE__ */ jsxs(
@@ -2084,18 +2497,20 @@ const HeaderFooterToggle = ({
2084
2497
  "data-testid": "header-footer-toggle",
2085
2498
  children: [
2086
2499
  /* @__PURE__ */ jsx(FileText, { size: 14, className: "lex4-hf-toggle-icon" }),
2087
- /* @__PURE__ */ jsx("span", { className: "lex4-hf-toggle-label", children: t.headerFooter.label }),
2500
+ showLabel && /* @__PURE__ */ jsx("span", { className: "lex4-hf-toggle-label", children: t.headerFooter.label }),
2088
2501
  /* @__PURE__ */ jsx(
2089
2502
  "button",
2090
2503
  {
2091
2504
  type: "button",
2092
2505
  role: "switch",
2093
2506
  "aria-checked": enabled,
2507
+ "aria-label": t.headerFooter.label,
2094
2508
  onMouseDown: (e) => e.preventDefault(),
2095
2509
  onClick: () => onToggle(!enabled),
2096
2510
  className: "lex4-hf-switch",
2097
2511
  style: { backgroundColor: enabled ? "var(--color-primary)" : "var(--color-muted)" },
2098
2512
  "data-testid": "header-footer-switch",
2513
+ title: t.headerFooter.label,
2099
2514
  children: /* @__PURE__ */ jsx("span", { className: "lex4-hf-switch-knob" })
2100
2515
  }
2101
2516
  )
@@ -2259,6 +2674,7 @@ const MenuItem = ({ icon, label, onClick, disabled, testId }) => /* @__PURE__ */
2259
2674
  const MenuDivider = () => /* @__PURE__ */ jsx("div", { className: "lex4-settings-divider" });
2260
2675
  const CanvasControls = () => {
2261
2676
  const { document: document2, dispatch, activePageId, runHistoryAction } = useDocument();
2677
+ const toolbarConfig = useToolbarConfig();
2262
2678
  const t = useTranslations();
2263
2679
  const runCanvasAction = useCallback((label, callback) => {
2264
2680
  runHistoryAction(
@@ -2325,12 +2741,16 @@ const CanvasControls = () => {
2325
2741
  dispatch({ type: "SET_PAGE_COUNTER_MODE", mode });
2326
2742
  });
2327
2743
  }, [dispatch, runCanvasAction, t.history.actions.pageCounterSet]);
2744
+ if (!toolbarConfig.headerFooter.visible) {
2745
+ return null;
2746
+ }
2328
2747
  return /* @__PURE__ */ jsxs("div", { className: "lex4-toolbar-group lex4-toolbar-group-gap", "data-testid": "header-footer-controls", children: [
2329
2748
  /* @__PURE__ */ jsx(
2330
2749
  HeaderFooterToggle,
2331
2750
  {
2332
2751
  enabled: document2.headerFooterEnabled,
2333
- onToggle: handleToggle
2752
+ onToggle: handleToggle,
2753
+ showLabel: toolbarConfig.headerFooter.showLabel
2334
2754
  }
2335
2755
  ),
2336
2756
  document2.headerFooterEnabled && /* @__PURE__ */ jsx(
@@ -2363,7 +2783,17 @@ const Toolbar = () => {
2363
2783
  undo
2364
2784
  } = useDocument();
2365
2785
  const { toolbarItems, toolbarEndItems } = useExtensions();
2786
+ const toolbarConfig = useToolbarConfig();
2366
2787
  const t = useTranslations();
2788
+ const [activeBlockType, setActiveBlockType] = useState("paragraph");
2789
+ const [activeFontFamily, setActiveFontFamily] = useState("Calibri");
2790
+ const [activeFontSize, setActiveFontSize] = useState(DEFAULT_FONT_SIZE);
2791
+ const normalizeFontFamily = useCallback((fontFamily) => {
2792
+ if (fontFamily && SUPPORTED_FONTS.includes(fontFamily)) {
2793
+ return fontFamily;
2794
+ }
2795
+ return "Calibri";
2796
+ }, []);
2367
2797
  const withBodySelection = useCallback(
2368
2798
  (editor, action) => {
2369
2799
  editor.update(() => {
@@ -2399,27 +2829,91 @@ const Toolbar = () => {
2399
2829
  },
2400
2830
  [runHistoryAction]
2401
2831
  );
2832
+ useEffect(() => {
2833
+ if (!activeEditor) {
2834
+ setActiveBlockType("paragraph");
2835
+ setActiveFontFamily("Calibri");
2836
+ setActiveFontSize(DEFAULT_FONT_SIZE);
2837
+ return;
2838
+ }
2839
+ const updateSelectionState = () => {
2840
+ const selectedVariables = getSelectedVariableNodes(activeEditor);
2841
+ if (selectedVariables.length > 0) {
2842
+ const formatting = readSelectedVariableFormatting(activeEditor);
2843
+ setActiveBlockType("paragraph");
2844
+ setActiveFontFamily(normalizeFontFamily(formatting.fontFamily));
2845
+ setActiveFontSize(formatting.fontSize ?? DEFAULT_FONT_SIZE);
2846
+ return;
2847
+ }
2848
+ setActiveBlockType(getActiveBlockType(activeEditor));
2849
+ let nextFontFamily = "Calibri";
2850
+ let nextFontSize = DEFAULT_FONT_SIZE;
2851
+ activeEditor.getEditorState().read(() => {
2852
+ const selection = $getSelection();
2853
+ if (!$isRangeSelection(selection)) {
2854
+ return;
2855
+ }
2856
+ const textNode = selection.getNodes().find($isTextNode);
2857
+ if (!textNode) {
2858
+ return;
2859
+ }
2860
+ const style = textNode.getStyle();
2861
+ nextFontFamily = normalizeFontFamily(extractFontFamilyFromStyle(style));
2862
+ nextFontSize = extractFontSizePtFromStyle(style) ?? DEFAULT_FONT_SIZE;
2863
+ });
2864
+ setActiveFontFamily(nextFontFamily);
2865
+ setActiveFontSize(nextFontSize);
2866
+ };
2867
+ updateSelectionState();
2868
+ const unregisterSelectionChange = activeEditor.registerCommand(
2869
+ SELECTION_CHANGE_COMMAND,
2870
+ () => {
2871
+ updateSelectionState();
2872
+ return false;
2873
+ },
2874
+ COMMAND_PRIORITY_LOW
2875
+ );
2876
+ const unregisterUpdateListener = activeEditor.registerUpdateListener(() => {
2877
+ updateSelectionState();
2878
+ });
2879
+ return () => {
2880
+ unregisterSelectionChange();
2881
+ unregisterUpdateListener();
2882
+ };
2883
+ }, [activeEditor, normalizeFontFamily]);
2402
2884
  const handleBold = useCallback(() => {
2403
2885
  debug("toolbar", `bold (globalSelection=${globalSelectionActive}, editors=${editorRegistry.all().length}, hasEditor=${!!activeEditor})`);
2404
2886
  runToolbarAction(t.history.actions.boldApplied, () => {
2887
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "bold")) {
2888
+ return;
2889
+ }
2405
2890
  applyToBodyEditors(toggleBold);
2406
2891
  });
2407
2892
  }, [activeEditor, applyToBodyEditors, editorRegistry, globalSelectionActive, runToolbarAction, t.history.actions.boldApplied]);
2408
2893
  const handleItalic = useCallback(() => {
2409
2894
  debug("toolbar", `italic (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
2410
2895
  runToolbarAction(t.history.actions.italicApplied, () => {
2896
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "italic")) {
2897
+ return;
2898
+ }
2411
2899
  applyToBodyEditors(toggleItalic);
2412
2900
  });
2413
2901
  }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.italicApplied]);
2414
2902
  const handleUnderline = useCallback(() => {
2415
2903
  debug("toolbar", `underline (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
2416
2904
  runToolbarAction(t.history.actions.underlineApplied, () => {
2905
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "underline")) {
2906
+ return;
2907
+ }
2417
2908
  applyToBodyEditors(toggleUnderline);
2418
2909
  });
2419
2910
  }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.underlineApplied]);
2420
2911
  const handleStrikethrough = useCallback(() => {
2421
2912
  debug("toolbar", `strikethrough (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
2422
2913
  runToolbarAction(t.history.actions.strikethroughApplied, () => {
2914
+ if (activeEditor && toggleSelectedVariableFormat(activeEditor, "strikethrough")) {
2915
+ return;
2916
+ }
2423
2917
  applyToBodyEditors(toggleStrikethrough);
2424
2918
  });
2425
2919
  }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.strikethroughApplied]);
@@ -2465,20 +2959,59 @@ const Toolbar = () => {
2465
2959
  }, [applyToBodyEditors, runToolbarAction, t.history.actions.outdentedContent]);
2466
2960
  const handleFontChange = useCallback(
2467
2961
  (e) => {
2468
- runToolbarAction(interpolate(t.history.actions.fontChanged, { value: e.target.value }), () => {
2469
- applyToBodyEditors((editor) => applyFontFamily(editor, e.target.value));
2962
+ const fontFamily = e.target.value;
2963
+ runToolbarAction(interpolate(t.history.actions.fontChanged, { value: fontFamily }), () => {
2964
+ if (activeEditor && applyFontFamilyToSelectedVariables(activeEditor, fontFamily)) {
2965
+ return;
2966
+ }
2967
+ applyToBodyEditors((editor) => applyFontFamily(editor, fontFamily));
2470
2968
  });
2471
2969
  },
2472
- [applyToBodyEditors, runToolbarAction, t.history.actions.fontChanged]
2970
+ [activeEditor, applyToBodyEditors, runToolbarAction, t.history.actions.fontChanged]
2473
2971
  );
2474
2972
  const handleFontSizeChange = useCallback(
2475
2973
  (e) => {
2476
2974
  const size = parseInt(e.target.value, 10);
2477
2975
  runToolbarAction(interpolate(t.history.actions.fontSizeChanged, { value: String(size) }), () => {
2976
+ if (activeEditor && applyFontSizeToSelectedVariables(activeEditor, size)) {
2977
+ return;
2978
+ }
2478
2979
  applyToBodyEditors((editor) => applyFontSize(editor, size));
2479
2980
  });
2480
2981
  },
2481
- [applyToBodyEditors, runToolbarAction, t.history.actions.fontSizeChanged]
2982
+ [activeEditor, applyToBodyEditors, runToolbarAction, t.history.actions.fontSizeChanged]
2983
+ );
2984
+ const handleBlockTypeChange = useCallback(
2985
+ (e) => {
2986
+ const blockType = e.target.value;
2987
+ const labelMap = {
2988
+ paragraph: t.toolbar.paragraph,
2989
+ h1: t.toolbar.heading1,
2990
+ h2: t.toolbar.heading2,
2991
+ h3: t.toolbar.heading3,
2992
+ h4: t.toolbar.heading4,
2993
+ h5: t.toolbar.heading5,
2994
+ h6: t.toolbar.heading6
2995
+ };
2996
+ runToolbarAction(
2997
+ interpolate(t.history.actions.blockTypeChanged, { value: labelMap[blockType] }),
2998
+ () => {
2999
+ applyToBodyEditors((editor) => setBlockType(editor, blockType));
3000
+ }
3001
+ );
3002
+ },
3003
+ [
3004
+ applyToBodyEditors,
3005
+ runToolbarAction,
3006
+ t.history.actions.blockTypeChanged,
3007
+ t.toolbar.heading1,
3008
+ t.toolbar.heading2,
3009
+ t.toolbar.heading3,
3010
+ t.toolbar.heading4,
3011
+ t.toolbar.heading5,
3012
+ t.toolbar.heading6,
3013
+ t.toolbar.paragraph
3014
+ ]
2482
3015
  );
2483
3016
  const handleToggleHistory = useCallback(() => {
2484
3017
  setHistorySidebarOpen(!historySidebarOpen);
@@ -2512,6 +3045,26 @@ const Toolbar = () => {
2512
3045
  )
2513
3046
  ] }),
2514
3047
  /* @__PURE__ */ jsx(Divider, {}),
3048
+ /* @__PURE__ */ jsx("div", { className: "lex4-toolbar-group-gap lex4-toolbar-group-block", children: /* @__PURE__ */ jsxs(
3049
+ "select",
3050
+ {
3051
+ className: "lex4-toolbar-select lex4-toolbar-select-block",
3052
+ "data-testid": "block-type-selector",
3053
+ "aria-label": t.toolbar.blockType,
3054
+ value: activeBlockType,
3055
+ onChange: handleBlockTypeChange,
3056
+ children: [
3057
+ /* @__PURE__ */ jsx("option", { value: "paragraph", children: t.toolbar.paragraph }),
3058
+ /* @__PURE__ */ jsx("option", { value: "h1", children: t.toolbar.heading1 }),
3059
+ /* @__PURE__ */ jsx("option", { value: "h2", children: t.toolbar.heading2 }),
3060
+ /* @__PURE__ */ jsx("option", { value: "h3", children: t.toolbar.heading3 }),
3061
+ /* @__PURE__ */ jsx("option", { value: "h4", children: t.toolbar.heading4 }),
3062
+ /* @__PURE__ */ jsx("option", { value: "h5", children: t.toolbar.heading5 }),
3063
+ /* @__PURE__ */ jsx("option", { value: "h6", children: t.toolbar.heading6 })
3064
+ ]
3065
+ }
3066
+ ) }),
3067
+ /* @__PURE__ */ jsx(Divider, {}),
2515
3068
  /* @__PURE__ */ jsxs("div", { className: "lex4-toolbar-group-gap", children: [
2516
3069
  /* @__PURE__ */ jsx(Type, { size: 14, className: "lex4-toolbar-icon" }),
2517
3070
  /* @__PURE__ */ jsx(
@@ -2519,7 +3072,7 @@ const Toolbar = () => {
2519
3072
  {
2520
3073
  className: "lex4-toolbar-select",
2521
3074
  "data-testid": "font-selector",
2522
- defaultValue: "Calibri",
3075
+ value: activeFontFamily,
2523
3076
  onChange: handleFontChange,
2524
3077
  children: SUPPORTED_FONTS.map((font) => /* @__PURE__ */ jsx("option", { value: font, style: { fontFamily: font }, children: font }, font))
2525
3078
  }
@@ -2532,7 +3085,7 @@ const Toolbar = () => {
2532
3085
  {
2533
3086
  className: "lex4-toolbar-select lex4-toolbar-select-narrow",
2534
3087
  "data-testid": "font-size-selector",
2535
- defaultValue: "12",
3088
+ value: String(activeFontSize),
2536
3089
  onChange: handleFontSizeChange,
2537
3090
  children: SUPPORTED_FONT_SIZES.map((size) => /* @__PURE__ */ jsx("option", { value: size, children: size }, size))
2538
3091
  }
@@ -2567,7 +3120,7 @@ const Toolbar = () => {
2567
3120
  /* @__PURE__ */ jsx(CanvasControls, {}),
2568
3121
  /* @__PURE__ */ jsxs("div", { className: "lex4-toolbar-end", children: [
2569
3122
  toolbarEndItems.map((EndItem, idx) => /* @__PURE__ */ jsx(EndItem, {}, idx)),
2570
- /* @__PURE__ */ jsxs(
3123
+ toolbarConfig.history.visible && /* @__PURE__ */ jsxs(
2571
3124
  "button",
2572
3125
  {
2573
3126
  type: "button",
@@ -2579,7 +3132,7 @@ const Toolbar = () => {
2579
3132
  "aria-label": historySidebarOpen ? t.toolbar.closeHistory : t.toolbar.openHistory,
2580
3133
  children: [
2581
3134
  /* @__PURE__ */ jsx(History, { size: 14 }),
2582
- "History"
3135
+ toolbarConfig.history.showLabel && t.toolbar.history
2583
3136
  ]
2584
3137
  }
2585
3138
  )
@@ -2923,7 +3476,8 @@ const lexicalTheme = {
2923
3476
  h2: "lex4-heading lex4-heading-h2",
2924
3477
  h3: "lex4-heading lex4-heading-h3",
2925
3478
  h4: "lex4-heading lex4-heading-h4",
2926
- h5: "lex4-heading lex4-heading-h5"
3479
+ h5: "lex4-heading lex4-heading-h5",
3480
+ h6: "lex4-heading lex4-heading-h6"
2927
3481
  },
2928
3482
  text: {
2929
3483
  bold: "lex4-text-bold",
@@ -4460,12 +5014,10 @@ function decodeFormatBitmask(format) {
4460
5014
  return marks;
4461
5015
  }
4462
5016
  function extractFontFamily(style) {
4463
- const match = style.match(/font-family:\s*([^;]+)/);
4464
- return match ? match[1].trim().replace(/['"]/g, "") : void 0;
5017
+ return extractFontFamilyFromStyle(style);
4465
5018
  }
4466
5019
  function extractFontSizePt(style) {
4467
- const match = style.match(/font-size:\s*(\d+(?:\.\d+)?)\s*pt/);
4468
- return match ? parseFloat(match[1]) : void 0;
5020
+ return extractFontSizePtFromStyle(style);
4469
5021
  }
4470
5022
  function buildTextMarks(format, style) {
4471
5023
  const formatMarks = decodeFormatBitmask(format);
@@ -4499,9 +5051,11 @@ function mapTextNode(node) {
4499
5051
  };
4500
5052
  }
4501
5053
  function mapVariableNode(node) {
5054
+ const marks = buildTextMarks(node.format ?? 0, node.style);
4502
5055
  return {
4503
5056
  type: "variable",
4504
- key: node.variableKey
5057
+ key: node.variableKey,
5058
+ ...marks ? { marks } : {}
4505
5059
  };
4506
5060
  }
4507
5061
  function mapLineBreak() {
@@ -4565,7 +5119,7 @@ function mapParagraph(node) {
4565
5119
  function mapHeading(node) {
4566
5120
  var _a;
4567
5121
  const alignment = decodeAlignment(node.format);
4568
- const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h(\d)$/);
5122
+ const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h([1-6])$/);
4569
5123
  const level = tagMatch ? parseInt(tagMatch[1], 10) : 1;
4570
5124
  return {
4571
5125
  type: "heading",
@@ -4690,6 +5244,105 @@ function buildSavePayload(ast, options) {
4690
5244
  function serializeDocumentJson(ast) {
4691
5245
  return JSON.stringify(ast, null, 2);
4692
5246
  }
5247
+ function appendChildren(parent, children = []) {
5248
+ const nodes = children.map(buildLexicalNode).filter((node) => node !== null);
5249
+ if (nodes.length > 0) {
5250
+ parent.append(...nodes);
5251
+ }
5252
+ }
5253
+ function applyElementFormatting(node, serializedNode) {
5254
+ if (typeof serializedNode.format === "string" && ["left", "center", "right", "justify"].includes(serializedNode.format) && node.setFormat) {
5255
+ node.setFormat(serializedNode.format);
5256
+ }
5257
+ if (typeof serializedNode.indent === "number" && serializedNode.indent > 0 && node.setIndent) {
5258
+ node.setIndent(serializedNode.indent);
5259
+ }
5260
+ }
5261
+ function buildLexicalNode(serializedNode) {
5262
+ switch (serializedNode.type) {
5263
+ case "paragraph": {
5264
+ const node = $createParagraphNode();
5265
+ applyElementFormatting(node, serializedNode);
5266
+ appendChildren(node, serializedNode.children);
5267
+ return node;
5268
+ }
5269
+ case "heading": {
5270
+ const tag = typeof serializedNode.tag === "string" && /^h[1-6]$/.test(serializedNode.tag) ? serializedNode.tag : "h1";
5271
+ const node = $createHeadingNode(tag);
5272
+ applyElementFormatting(node, serializedNode);
5273
+ appendChildren(node, serializedNode.children);
5274
+ return node;
5275
+ }
5276
+ case "quote": {
5277
+ const node = $createQuoteNode();
5278
+ applyElementFormatting(node, serializedNode);
5279
+ appendChildren(node, serializedNode.children);
5280
+ return node;
5281
+ }
5282
+ case "list": {
5283
+ const listType = serializedNode.listType === "number" ? "number" : "bullet";
5284
+ const node = $createListNode(listType, typeof serializedNode.start === "number" ? serializedNode.start : 1);
5285
+ appendChildren(node, serializedNode.children);
5286
+ return node;
5287
+ }
5288
+ case "listitem": {
5289
+ const node = $createListItemNode();
5290
+ appendChildren(node, serializedNode.children);
5291
+ return node;
5292
+ }
5293
+ case "text": {
5294
+ const node = $createTextNode(serializedNode.text ?? "");
5295
+ if (typeof serializedNode.format === "number" && serializedNode.format > 0) {
5296
+ node.setFormat(serializedNode.format);
5297
+ }
5298
+ if (typeof serializedNode.style === "string" && serializedNode.style.trim() !== "") {
5299
+ node.setStyle(serializedNode.style);
5300
+ }
5301
+ return node;
5302
+ }
5303
+ case "linebreak":
5304
+ return $createLineBreakNode();
5305
+ case "variable-node":
5306
+ return $createVariableNode(
5307
+ serializedNode.variableKey ?? "",
5308
+ typeof serializedNode.format === "number" ? serializedNode.format : 0,
5309
+ typeof serializedNode.style === "string" ? serializedNode.style : ""
5310
+ );
5311
+ default:
5312
+ return null;
5313
+ }
5314
+ }
5315
+ function getBodyChildren(pageState) {
5316
+ const root = pageState == null ? void 0 : pageState.root;
5317
+ return (root == null ? void 0 : root.children) ?? [];
5318
+ }
5319
+ function insertDocumentContent(editor, document2) {
5320
+ let inserted = false;
5321
+ editor.update(() => {
5322
+ const nodes = document2.pages.flatMap((page) => getBodyChildren(page.bodyState)).map(buildLexicalNode).filter((node) => node !== null);
5323
+ if (nodes.length === 0) {
5324
+ return;
5325
+ }
5326
+ const selection = $getSelection();
5327
+ if ($isRangeSelection(selection)) {
5328
+ if (selection.isCollapsed()) {
5329
+ selection.insertParagraph();
5330
+ }
5331
+ const nextSelection = $getSelection();
5332
+ if ($isRangeSelection(nextSelection) || $isNodeSelection(nextSelection)) {
5333
+ nextSelection.insertNodes(nodes);
5334
+ } else {
5335
+ $insertNodes(nodes);
5336
+ }
5337
+ } else if ($isNodeSelection(selection)) {
5338
+ selection.insertNodes(nodes);
5339
+ } else {
5340
+ $insertNodes(nodes);
5341
+ }
5342
+ inserted = true;
5343
+ });
5344
+ return inserted;
5345
+ }
4693
5346
  function selectEntireDocument(rootElement, selectionBuffer) {
4694
5347
  if (!rootElement || !selectionBuffer) {
4695
5348
  return;
@@ -4896,10 +5549,13 @@ const EditorWithHandle = forwardRef(({ captureHistoryShortcutsOnWindow, onSave,
4896
5549
  const {
4897
5550
  document: doc,
4898
5551
  activeEditor,
5552
+ activeCaretPosition,
4899
5553
  historySidebarOpen,
5554
+ runHistoryAction,
4900
5555
  setHistorySidebarOpen
4901
5556
  } = useDocument();
4902
5557
  const { handleFactories } = useExtensions();
5558
+ const t = useTranslations();
4903
5559
  const getDocument = useCallback(() => doc, [doc]);
4904
5560
  const getActiveEditor = useCallback(() => activeEditor, [activeEditor]);
4905
5561
  const extensionCtx = useExtensionContext(getDocument, getActiveEditor);
@@ -4910,6 +5566,23 @@ const EditorWithHandle = forwardRef(({ captureHistoryShortcutsOnWindow, onSave,
4910
5566
  },
4911
5567
  toggleHistorySidebar: () => {
4912
5568
  setHistorySidebarOpen(!historySidebarOpen);
5569
+ },
5570
+ insertDocumentContent: (documentToInsert) => {
5571
+ if (!activeEditor || (activeCaretPosition == null ? void 0 : activeCaretPosition.region) !== "body") {
5572
+ return false;
5573
+ }
5574
+ let inserted = false;
5575
+ runHistoryAction(
5576
+ {
5577
+ label: t.history.actions.insertedDocumentContent,
5578
+ source: "toolbar",
5579
+ region: "document"
5580
+ },
5581
+ () => {
5582
+ inserted = insertDocumentContent(activeEditor, documentToInsert);
5583
+ }
5584
+ );
5585
+ return inserted;
4913
5586
  }
4914
5587
  };
4915
5588
  for (const factory of handleFactories) {
@@ -4917,7 +5590,16 @@ const EditorWithHandle = forwardRef(({ captureHistoryShortcutsOnWindow, onSave,
4917
5590
  Object.assign(handle, methods);
4918
5591
  }
4919
5592
  return handle;
4920
- }, [extensionCtx, handleFactories, historySidebarOpen, setHistorySidebarOpen]);
5593
+ }, [
5594
+ activeCaretPosition == null ? void 0 : activeCaretPosition.region,
5595
+ activeEditor,
5596
+ extensionCtx,
5597
+ handleFactories,
5598
+ historySidebarOpen,
5599
+ runHistoryAction,
5600
+ setHistorySidebarOpen,
5601
+ t.history.actions.insertedDocumentContent
5602
+ ]);
4921
5603
  return /* @__PURE__ */ jsx(
4922
5604
  EditorChrome,
4923
5605
  {
@@ -4935,9 +5617,10 @@ const Lex4Editor = forwardRef(({
4935
5617
  onSave,
4936
5618
  extensions,
4937
5619
  translations,
5620
+ toolbar,
4938
5621
  className
4939
5622
  }, ref) => {
4940
- return /* @__PURE__ */ jsx(TranslationsProvider, { translations, children: /* @__PURE__ */ jsx(ExtensionStateProvider, { children: /* @__PURE__ */ jsx(ExtensionProvider, { extensions, children: /* @__PURE__ */ jsx(
5623
+ return /* @__PURE__ */ jsx(TranslationsProvider, { translations, children: /* @__PURE__ */ jsx(ToolbarConfigProvider, { toolbar, children: /* @__PURE__ */ jsx(ExtensionStateProvider, { children: /* @__PURE__ */ jsx(ExtensionProvider, { extensions, children: /* @__PURE__ */ jsx(
4941
5624
  DocumentProvider,
4942
5625
  {
4943
5626
  initialDocument,
@@ -4952,7 +5635,7 @@ const Lex4Editor = forwardRef(({
4952
5635
  }
4953
5636
  )
4954
5637
  }
4955
- ) }) }) });
5638
+ ) }) }) }) });
4956
5639
  });
4957
5640
  Lex4Editor.displayName = "Lex4Editor";
4958
5641
  function useOverflowDetection(bodyHeight, onOverflow, debounceMs = 100) {
@@ -5046,120 +5729,6 @@ function astExtension() {
5046
5729
  }
5047
5730
  };
5048
5731
  }
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
5732
  const INSERT_VARIABLE_COMMAND = createCommand("INSERT_VARIABLE");
5164
5733
  const VariablePlugin = () => {
5165
5734
  const [editor] = useLexicalComposerContext();
@@ -5420,7 +5989,11 @@ const VariablePanelStateProvider = ({ children }) => {
5420
5989
  };
5421
5990
  const VariableToolbarToggle = () => {
5422
5991
  const { panelOpen, setPanelOpen } = useVariablePanelState();
5992
+ const toolbarConfig = useToolbarConfig();
5423
5993
  const t = useTranslations();
5994
+ if (!toolbarConfig.variables.visible) {
5995
+ return null;
5996
+ }
5424
5997
  return /* @__PURE__ */ jsxs(
5425
5998
  "button",
5426
5999
  {
@@ -5433,7 +6006,7 @@ const VariableToolbarToggle = () => {
5433
6006
  "aria-label": panelOpen ? t.variables.closePanel : t.variables.openPanel,
5434
6007
  children: [
5435
6008
  /* @__PURE__ */ jsx(Braces, { size: 14 }),
5436
- t.variables.title
6009
+ toolbarConfig.variables.showLabel && t.variables.title
5437
6010
  ]
5438
6011
  }
5439
6012
  );