@yurikilian/lex4 1.5.9 → 1.7.0

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.
@@ -779,6 +779,115 @@ const TranslationsProvider = ({
779
779
  );
780
780
  return /* @__PURE__ */ jsxRuntime.jsx(TranslationsContext.Provider, { value: merged, children });
781
781
  };
782
+ const createStoreImpl = (createState) => {
783
+ let state;
784
+ const listeners = /* @__PURE__ */ new Set();
785
+ const setState = (partial, replace) => {
786
+ const nextState = typeof partial === "function" ? partial(state) : partial;
787
+ if (!Object.is(nextState, state)) {
788
+ const previousState = state;
789
+ state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
790
+ listeners.forEach((listener) => listener(state, previousState));
791
+ }
792
+ };
793
+ const getState = () => state;
794
+ const getInitialState = () => initialState;
795
+ const subscribe = (listener) => {
796
+ listeners.add(listener);
797
+ return () => listeners.delete(listener);
798
+ };
799
+ const api = { setState, getState, getInitialState, subscribe };
800
+ const initialState = state = createState(setState, getState, api);
801
+ return api;
802
+ };
803
+ const createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
804
+ const identity = (arg) => arg;
805
+ function useStore(api, selector = identity) {
806
+ const slice = React.useSyncExternalStore(
807
+ api.subscribe,
808
+ React.useCallback(() => selector(api.getState()), [api, selector]),
809
+ React.useCallback(() => selector(api.getInitialState()), [api, selector])
810
+ );
811
+ React.useDebugValue(slice);
812
+ return slice;
813
+ }
814
+ const SUPPORTED_FONT_SIZES = [
815
+ 8,
816
+ 9,
817
+ 10,
818
+ 11,
819
+ 12,
820
+ 14,
821
+ 16,
822
+ 18,
823
+ 20,
824
+ 24,
825
+ 28,
826
+ 32,
827
+ 36,
828
+ 48,
829
+ 72
830
+ ];
831
+ const DEFAULT_FONT_SIZE = 12;
832
+ function applyFontSize(editor, size) {
833
+ editor.update(() => {
834
+ const selection2 = lexical.$getSelection();
835
+ if (!lexical.$isRangeSelection(selection2)) return;
836
+ const nodes = selection2.getNodes();
837
+ for (const node of nodes) {
838
+ if (lexical.$isTextNode(node)) {
839
+ const existing = node.getStyle();
840
+ const updated = mergeFontSize(existing, size);
841
+ node.setStyle(updated);
842
+ }
843
+ }
844
+ });
845
+ }
846
+ function mergeFontSize(existingStyle, size) {
847
+ const stripped = existingStyle.replace(/font-size:\s*[^;]+;?\s*/g, "").trim();
848
+ const sizeDecl = `font-size: ${size}pt`;
849
+ return stripped ? `${stripped}; ${sizeDecl}` : sizeDecl;
850
+ }
851
+ const DEFAULT_TOOLBAR_STYLE_SNAPSHOT = {
852
+ blockType: "paragraph",
853
+ fontFamily: "Calibri",
854
+ fontSize: DEFAULT_FONT_SIZE,
855
+ alignment: "left",
856
+ isBold: false,
857
+ isItalic: false,
858
+ isUnderline: false,
859
+ isStrikethrough: false,
860
+ hasSelectedVariable: false
861
+ };
862
+ function createToolbarStyleStore(initialSnapshot = DEFAULT_TOOLBAR_STYLE_SNAPSHOT) {
863
+ return createStore((set) => ({
864
+ ...initialSnapshot,
865
+ setSnapshot: (snapshot) => set(snapshot),
866
+ reset: () => set(DEFAULT_TOOLBAR_STYLE_SNAPSHOT)
867
+ }));
868
+ }
869
+ const ToolbarStyleStoreContext = React.createContext(null);
870
+ const ToolbarStyleStoreProvider = ({ children }) => {
871
+ const storeRef = React.useRef(null);
872
+ if (!storeRef.current) {
873
+ storeRef.current = createToolbarStyleStore();
874
+ }
875
+ return /* @__PURE__ */ jsxRuntime.jsx(ToolbarStyleStoreContext.Provider, { value: storeRef.current, children });
876
+ };
877
+ function useToolbarStyleStore(selector) {
878
+ const store = React.useContext(ToolbarStyleStoreContext);
879
+ if (!store) {
880
+ throw new Error("useToolbarStyleStore must be used within a ToolbarStyleStoreProvider");
881
+ }
882
+ return useStore(store, selector);
883
+ }
884
+ function useToolbarStyleStoreApi() {
885
+ const store = React.useContext(ToolbarStyleStoreContext);
886
+ if (!store) {
887
+ throw new Error("useToolbarStyleStoreApi must be used within a ToolbarStyleStoreProvider");
888
+ }
889
+ return store;
890
+ }
782
891
  const HISTORY_RESTORE_SUPPRESSION_MS = 100;
783
892
  const HISTORY_BATCH_FLUSH_MS = 16;
784
893
  function cloneDocumentSnapshot(document2) {
@@ -1026,7 +1135,11 @@ const DocumentProvider = ({
1026
1135
  register: (pageId, editor) => {
1027
1136
  editorMapRef.current.set(pageId, editor);
1028
1137
  },
1029
- unregister: (pageId) => {
1138
+ unregister: (pageId, editor) => {
1139
+ const currentEditor = editorMapRef.current.get(pageId);
1140
+ if (editor && currentEditor && currentEditor !== editor) {
1141
+ return;
1142
+ }
1030
1143
  editorMapRef.current.delete(pageId);
1031
1144
  },
1032
1145
  get: (pageId) => editorMapRef.current.get(pageId),
@@ -1331,7 +1444,7 @@ const DocumentProvider = ({
1331
1444
  undo,
1332
1445
  redo,
1333
1446
  editorRegistry
1334
- }, children });
1447
+ }, children: /* @__PURE__ */ jsxRuntime.jsx(ToolbarStyleStoreProvider, { children }) });
1335
1448
  };
1336
1449
  /**
1337
1450
  * @license lucide-react v1.8.0 - ISC
@@ -2079,43 +2192,6 @@ function applyFontFamily(editor, fontFamily) {
2079
2192
  }
2080
2193
  });
2081
2194
  }
2082
- const SUPPORTED_FONT_SIZES = [
2083
- 8,
2084
- 9,
2085
- 10,
2086
- 11,
2087
- 12,
2088
- 14,
2089
- 16,
2090
- 18,
2091
- 20,
2092
- 24,
2093
- 28,
2094
- 32,
2095
- 36,
2096
- 48,
2097
- 72
2098
- ];
2099
- const DEFAULT_FONT_SIZE = 12;
2100
- function applyFontSize(editor, size) {
2101
- editor.update(() => {
2102
- const selection2 = lexical.$getSelection();
2103
- if (!lexical.$isRangeSelection(selection2)) return;
2104
- const nodes = selection2.getNodes();
2105
- for (const node of nodes) {
2106
- if (lexical.$isTextNode(node)) {
2107
- const existing = node.getStyle();
2108
- const updated = mergeFontSize(existing, size);
2109
- node.setStyle(updated);
2110
- }
2111
- }
2112
- });
2113
- }
2114
- function mergeFontSize(existingStyle, size) {
2115
- const stripped = existingStyle.replace(/font-size:\s*[^;]+;?\s*/g, "").trim();
2116
- const sizeDecl = `font-size: ${size}pt`;
2117
- return stripped ? `${stripped}; ${sizeDecl}` : sizeDecl;
2118
- }
2119
2195
  function toggleFormat(editor, format) {
2120
2196
  editor.dispatchCommand(lexical.FORMAT_TEXT_COMMAND, format);
2121
2197
  }
@@ -2147,48 +2223,77 @@ function indentContent(editor) {
2147
2223
  function outdentContent(editor) {
2148
2224
  editor.dispatchCommand(lexical.OUTDENT_CONTENT_COMMAND, void 0);
2149
2225
  }
2150
- function setBlockType(editor, blockType) {
2151
- editor.update(() => {
2152
- const selection$1 = lexical.$getSelection();
2153
- if (!lexical.$isRangeSelection(selection$1)) {
2154
- return;
2155
- }
2156
- if (blockType === "paragraph") {
2157
- selection.$setBlocksType(selection$1, () => lexical.$createParagraphNode());
2158
- return;
2159
- }
2160
- selection.$setBlocksType(selection$1, () => richText.$createHeadingNode(blockType));
2161
- });
2226
+ const INLINE_BLOCK_STYLE_PROPERTY = "--lex4-block-type";
2227
+ const INLINE_BLOCK_STYLE_PRESETS = {
2228
+ paragraph: {
2229
+ [INLINE_BLOCK_STYLE_PROPERTY]: "paragraph",
2230
+ "font-size": "12pt",
2231
+ "font-weight": "400"
2232
+ },
2233
+ h1: {
2234
+ [INLINE_BLOCK_STYLE_PROPERTY]: "h1",
2235
+ "font-size": "22.5pt",
2236
+ "font-weight": "700"
2237
+ },
2238
+ h2: {
2239
+ [INLINE_BLOCK_STYLE_PROPERTY]: "h2",
2240
+ "font-size": "18pt",
2241
+ "font-weight": "700"
2242
+ },
2243
+ h3: {
2244
+ [INLINE_BLOCK_STYLE_PROPERTY]: "h3",
2245
+ "font-size": "15pt",
2246
+ "font-weight": "600"
2247
+ },
2248
+ h4: {
2249
+ [INLINE_BLOCK_STYLE_PROPERTY]: "h4",
2250
+ "font-size": "13.5pt",
2251
+ "font-weight": "600"
2252
+ },
2253
+ h5: {
2254
+ [INLINE_BLOCK_STYLE_PROPERTY]: "h5",
2255
+ "font-size": "12pt",
2256
+ "font-weight": "500"
2257
+ },
2258
+ h6: {
2259
+ [INLINE_BLOCK_STYLE_PROPERTY]: "h6",
2260
+ "font-size": "11.25pt",
2261
+ "font-weight": "500"
2262
+ }
2263
+ };
2264
+ function escapeStyleProperty(property) {
2265
+ const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2266
+ return escapedProperty;
2162
2267
  }
2163
- function getActiveBlockType(editor) {
2164
- let blockType = "paragraph";
2165
- editor.getEditorState().read(() => {
2166
- const selection2 = lexical.$getSelection();
2167
- if (!lexical.$isRangeSelection(selection2)) {
2168
- return;
2169
- }
2170
- const anchorNode = selection2.anchor.getNode();
2171
- const topLevelElement = anchorNode.getTopLevelElementOrThrow();
2172
- if (richText.$isHeadingNode(topLevelElement)) {
2173
- blockType = topLevelElement.getTag();
2174
- }
2175
- });
2176
- return blockType;
2268
+ function stripStyleDeclaration(existingStyle, property) {
2269
+ const escapedProperty = escapeStyleProperty(property);
2270
+ return existingStyle.replace(
2271
+ new RegExp(`${escapedProperty}:\\s*[^;]+;?\\s*`, "g"),
2272
+ ""
2273
+ ).trim();
2274
+ }
2275
+ function isSupportedInlineBlockType(value) {
2276
+ return value === "paragraph" || value === "h1" || value === "h2" || value === "h3" || value === "h4" || value === "h5" || value === "h6";
2177
2277
  }
2178
2278
  function extractStyleValue(style, property) {
2179
- const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2279
+ const escapedProperty = escapeStyleProperty(property);
2180
2280
  const match = style.match(new RegExp(`${escapedProperty}:\\s*([^;]+)`));
2181
2281
  return match ? match[1].trim().replace(/['"]/g, "") : void 0;
2182
2282
  }
2283
+ function removeStyleDeclaration(existingStyle, property) {
2284
+ return stripStyleDeclaration(existingStyle, property);
2285
+ }
2183
2286
  function mergeStyleDeclaration(existingStyle, property, value) {
2184
- const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2185
- const stripped = existingStyle.replace(
2186
- new RegExp(`${escapedProperty}:\\s*[^;]+;?\\s*`, "g"),
2187
- ""
2188
- ).trim();
2287
+ const stripped = stripStyleDeclaration(existingStyle, property);
2189
2288
  const declaration = `${property}: ${value}`;
2190
2289
  return stripped ? `${stripped}; ${declaration}` : declaration;
2191
2290
  }
2291
+ function mergeStyleDeclarations(existingStyle, declarations) {
2292
+ return Object.entries(declarations).reduce(
2293
+ (style, [property, value]) => mergeStyleDeclaration(style, property, value),
2294
+ existingStyle
2295
+ );
2296
+ }
2192
2297
  function extractFontFamilyFromStyle(style) {
2193
2298
  return extractStyleValue(style, "font-family");
2194
2299
  }
@@ -2206,6 +2311,21 @@ function mergeFontFamilyStyle(existingStyle, fontFamily) {
2206
2311
  function mergeFontSizeStyle(existingStyle, size) {
2207
2312
  return mergeStyleDeclaration(existingStyle, "font-size", `${size}pt`);
2208
2313
  }
2314
+ function extractInlineBlockTypeFromStyle(style) {
2315
+ const value = extractStyleValue(style, INLINE_BLOCK_STYLE_PROPERTY);
2316
+ return isSupportedInlineBlockType(value) ? value : void 0;
2317
+ }
2318
+ function createInlineBlockTypeStylePatch(blockType) {
2319
+ return INLINE_BLOCK_STYLE_PRESETS[blockType];
2320
+ }
2321
+ function mergeInlineBlockTypeStyle(existingStyle, blockType) {
2322
+ const baseStyle = [
2323
+ INLINE_BLOCK_STYLE_PROPERTY,
2324
+ "font-size",
2325
+ "font-weight"
2326
+ ].reduce((style, property) => removeStyleDeclaration(style, property), existingStyle);
2327
+ return mergeStyleDeclarations(baseStyle, createInlineBlockTypeStylePatch(blockType));
2328
+ }
2209
2329
  const EMPTY_CONTEXT = {
2210
2330
  definitions: [],
2211
2331
  refreshDefinitions: () => {
@@ -2347,9 +2467,11 @@ function VariableChip({
2347
2467
  const style = React.useMemo(() => {
2348
2468
  const fontFamily = extractFontFamilyFromStyle(styleValue);
2349
2469
  const fontSize = extractFontSizePtFromStyle(styleValue);
2470
+ const fontWeight = extractStyleValue(styleValue, "font-weight");
2350
2471
  return {
2351
2472
  ...fontFamily ? { fontFamily } : {},
2352
- ...fontSize ? { fontSize: `${fontSize}pt` } : {}
2473
+ ...fontSize ? { fontSize: `${fontSize}pt` } : {},
2474
+ ...fontWeight ? { fontWeight } : {}
2353
2475
  };
2354
2476
  }, [styleValue]);
2355
2477
  const className = [
@@ -2407,9 +2529,43 @@ function VariableChip({
2407
2529
  },
2408
2530
  lexical.COMMAND_PRIORITY_LOW
2409
2531
  );
2532
+ const moveCaretFromSelectedNode = (direction) => {
2533
+ editor.update(() => {
2534
+ const node = lexical.$getNodeByKey(nodeKey);
2535
+ if (!$isVariableNode(node)) {
2536
+ return;
2537
+ }
2538
+ if (direction === "backward") {
2539
+ node.selectPrevious();
2540
+ } else {
2541
+ node.selectNext();
2542
+ }
2543
+ });
2544
+ };
2545
+ const unregisterArrowNavigation = editor.registerCommand(
2546
+ lexical.KEY_DOWN_COMMAND,
2547
+ (event) => {
2548
+ if (!isSelected || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) {
2549
+ return false;
2550
+ }
2551
+ if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
2552
+ event.preventDefault();
2553
+ moveCaretFromSelectedNode("backward");
2554
+ return true;
2555
+ }
2556
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") {
2557
+ event.preventDefault();
2558
+ moveCaretFromSelectedNode("forward");
2559
+ return true;
2560
+ }
2561
+ return false;
2562
+ },
2563
+ lexical.COMMAND_PRIORITY_LOW
2564
+ );
2410
2565
  return () => {
2411
2566
  unregisterBackspace();
2412
2567
  unregisterDelete();
2568
+ unregisterArrowNavigation();
2413
2569
  };
2414
2570
  }, [editor, isSelected, nodeKey]);
2415
2571
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -2432,7 +2588,126 @@ function $createVariableNode(variableKey, format = 0, style = "") {
2432
2588
  function $isVariableNode(node) {
2433
2589
  return node instanceof VariableNode;
2434
2590
  }
2435
- const FORMAT_MASKS = {
2591
+ function getElementBlockType$1(element) {
2592
+ if (richText.$isHeadingNode(element)) {
2593
+ return element.getTag();
2594
+ }
2595
+ return "paragraph";
2596
+ }
2597
+ function getVariableTopLevelElement(variable) {
2598
+ const topLevelElement = variable.getTopLevelElementOrThrow();
2599
+ return lexical.$isElementNode(topLevelElement) ? topLevelElement : null;
2600
+ }
2601
+ function replaceTopLevelBlockType(element, blockType) {
2602
+ const currentType = getElementBlockType$1(element);
2603
+ if (currentType === blockType) {
2604
+ return;
2605
+ }
2606
+ const nextElement = blockType === "paragraph" ? lexical.$createParagraphNode() : richText.$createHeadingNode(blockType);
2607
+ nextElement.setFormat(element.getFormatType());
2608
+ nextElement.setIndent(element.getIndent());
2609
+ const children = element.getChildren();
2610
+ for (const child of children) {
2611
+ nextElement.append(child);
2612
+ }
2613
+ element.replace(nextElement);
2614
+ }
2615
+ function isPartialSingleBlockSelection(selection2) {
2616
+ if (selection2.isCollapsed()) {
2617
+ return false;
2618
+ }
2619
+ const anchorTopLevel = selection2.anchor.getNode().getTopLevelElementOrThrow();
2620
+ const focusTopLevel = selection2.focus.getNode().getTopLevelElementOrThrow();
2621
+ if (!anchorTopLevel.is(focusTopLevel)) {
2622
+ return false;
2623
+ }
2624
+ const selectedText = selection2.getTextContent().trim();
2625
+ const blockText = anchorTopLevel.getTextContent().trim();
2626
+ return selectedText.length > 0 && selectedText.length < blockText.length;
2627
+ }
2628
+ function applySemanticBlockType(selection$1, blockType) {
2629
+ if (blockType === "paragraph") {
2630
+ selection.$setBlocksType(selection$1, () => lexical.$createParagraphNode());
2631
+ return;
2632
+ }
2633
+ selection.$setBlocksType(selection$1, () => richText.$createHeadingNode(blockType));
2634
+ }
2635
+ function selectedVariablesOccupyEntireBlock(variables, topLevelElement) {
2636
+ const selectedKeys = new Set(variables.map((variable) => variable.getKey()));
2637
+ const meaningfulChildren = topLevelElement.getChildren().filter(
2638
+ (child) => !(lexical.$isTextNode(child) && child.getTextContent().trim() === "")
2639
+ );
2640
+ if (meaningfulChildren.length === 0) {
2641
+ return false;
2642
+ }
2643
+ return meaningfulChildren.every(
2644
+ (child) => $isVariableNode(child) && selectedKeys.has(child.getKey())
2645
+ );
2646
+ }
2647
+ function getStandaloneVariableChildren(topLevelElement) {
2648
+ const meaningfulChildren = topLevelElement.getChildren().filter(
2649
+ (child) => !(lexical.$isTextNode(child) && child.getTextContent().trim() === "")
2650
+ );
2651
+ if (meaningfulChildren.length === 0 || !meaningfulChildren.every($isVariableNode)) {
2652
+ return null;
2653
+ }
2654
+ return meaningfulChildren;
2655
+ }
2656
+ function setBlockType(editor, blockType) {
2657
+ editor.update(() => {
2658
+ const currentSelection = lexical.$getSelection();
2659
+ const selection$1 = lexical.$isNodeSelection(currentSelection) ? currentSelection : lexical.$createRangeSelectionFromDom(window.getSelection(), editor) ?? currentSelection;
2660
+ if (lexical.$isRangeSelection(selection$1)) {
2661
+ lexical.$setSelection(selection$1);
2662
+ }
2663
+ if (lexical.$isNodeSelection(selection$1)) {
2664
+ const variables = selection$1.getNodes().filter($isVariableNode);
2665
+ if (variables.length === 0) {
2666
+ return;
2667
+ }
2668
+ const firstTopLevelElement = getVariableTopLevelElement(variables[0]);
2669
+ if (!firstTopLevelElement) {
2670
+ return;
2671
+ }
2672
+ const sameTopLevelElement = variables.every(
2673
+ (variable) => {
2674
+ var _a;
2675
+ return ((_a = getVariableTopLevelElement(variable)) == null ? void 0 : _a.is(firstTopLevelElement)) ?? false;
2676
+ }
2677
+ );
2678
+ if (sameTopLevelElement && selectedVariablesOccupyEntireBlock(variables, firstTopLevelElement)) {
2679
+ for (const variable of variables) {
2680
+ variable.setStyle(mergeInlineBlockTypeStyle(variable.getStyle(), blockType));
2681
+ }
2682
+ replaceTopLevelBlockType(firstTopLevelElement, blockType);
2683
+ const nextSelection = lexical.$createNodeSelection();
2684
+ for (const variable of variables) {
2685
+ nextSelection.add(variable.getKey());
2686
+ }
2687
+ lexical.$setSelection(nextSelection);
2688
+ return;
2689
+ }
2690
+ for (const variable of variables) {
2691
+ variable.setStyle(mergeInlineBlockTypeStyle(variable.getStyle(), blockType));
2692
+ }
2693
+ return;
2694
+ }
2695
+ if (!lexical.$isRangeSelection(selection$1)) {
2696
+ return;
2697
+ }
2698
+ const anchorTopLevel = selection$1.anchor.getNode().getTopLevelElementOrThrow();
2699
+ const standaloneVariables = lexical.$isElementNode(anchorTopLevel) ? getStandaloneVariableChildren(anchorTopLevel) : null;
2700
+ if (isPartialSingleBlockSelection(selection$1)) {
2701
+ selection.$patchStyleText(selection$1, createInlineBlockTypeStylePatch(blockType));
2702
+ return;
2703
+ }
2704
+ for (const variable of standaloneVariables ?? []) {
2705
+ variable.setStyle(mergeInlineBlockTypeStyle(variable.getStyle(), blockType));
2706
+ }
2707
+ applySemanticBlockType(selection$1, blockType);
2708
+ });
2709
+ }
2710
+ const FORMAT_MASKS$1 = {
2436
2711
  bold: 1,
2437
2712
  italic: 2,
2438
2713
  strikethrough: 4,
@@ -2454,19 +2729,8 @@ function withSelectedVariableNodes(editor, updater) {
2454
2729
  });
2455
2730
  return updated;
2456
2731
  }
2457
- function getSelectedVariableNodes(editor) {
2458
- let nodes = [];
2459
- editor.getEditorState().read(() => {
2460
- const selection2 = lexical.$getSelection();
2461
- if (!lexical.$isNodeSelection(selection2)) {
2462
- return;
2463
- }
2464
- nodes = selection2.getNodes().filter($isVariableNode);
2465
- });
2466
- return nodes;
2467
- }
2468
2732
  function toggleSelectedVariableFormat(editor, format) {
2469
- const mask = FORMAT_MASKS[format];
2733
+ const mask = FORMAT_MASKS$1[format];
2470
2734
  if (!mask) {
2471
2735
  return false;
2472
2736
  }
@@ -2492,17 +2756,6 @@ function applyFontSizeToSelectedVariables(editor, size) {
2492
2756
  }
2493
2757
  });
2494
2758
  }
2495
- function readSelectedVariableFormatting(editor) {
2496
- const firstNode = getSelectedVariableNodes(editor)[0];
2497
- if (!firstNode) {
2498
- return {};
2499
- }
2500
- const style = firstNode.getStyle();
2501
- return {
2502
- fontFamily: extractFontFamilyFromStyle(style),
2503
- fontSize: extractFontSizePtFromStyle(style)
2504
- };
2505
- }
2506
2759
  const BLOCK_TYPE_OPTIONS = [
2507
2760
  { value: "paragraph", shortLabel: "P" },
2508
2761
  { value: "h1", shortLabel: "H1" },
@@ -2918,6 +3171,108 @@ const CanvasControls = () => {
2918
3171
  )
2919
3172
  ] });
2920
3173
  };
3174
+ const FORMAT_MASKS = {
3175
+ bold: 1,
3176
+ italic: 2,
3177
+ strikethrough: 4,
3178
+ underline: 8
3179
+ };
3180
+ function normalizeFontFamily(fontFamily) {
3181
+ if (fontFamily && SUPPORTED_FONTS.includes(fontFamily)) {
3182
+ return fontFamily;
3183
+ }
3184
+ return "Calibri";
3185
+ }
3186
+ function normalizeAlignment(alignment) {
3187
+ if (alignment === "center" || alignment === "right" || alignment === "justify") {
3188
+ return alignment;
3189
+ }
3190
+ return "left";
3191
+ }
3192
+ function getElementBlockType(node) {
3193
+ const topLevelElement = node.getTopLevelElementOrThrow();
3194
+ if (richText.$isHeadingNode(topLevelElement)) {
3195
+ return topLevelElement.getTag();
3196
+ }
3197
+ return "paragraph";
3198
+ }
3199
+ function getElementAlignment(node) {
3200
+ const topLevelElement = node.getTopLevelElementOrThrow();
3201
+ if (lexical.$isElementNode(topLevelElement)) {
3202
+ return normalizeAlignment(topLevelElement.getFormatType());
3203
+ }
3204
+ return "left";
3205
+ }
3206
+ function getInlineStyleTarget(nodes, anchorNode) {
3207
+ if (lexical.$isTextNode(anchorNode) || $isVariableNode(anchorNode)) {
3208
+ return anchorNode;
3209
+ }
3210
+ return nodes.find((node) => lexical.$isTextNode(node) || $isVariableNode(node)) ?? null;
3211
+ }
3212
+ function getInlineStyleFromNode(node) {
3213
+ if (lexical.$isTextNode(node) || $isVariableNode(node)) {
3214
+ return node.getStyle();
3215
+ }
3216
+ return "";
3217
+ }
3218
+ function hasInlineFormat(node, format) {
3219
+ if ($isVariableNode(node)) {
3220
+ return (node.getFormat() & FORMAT_MASKS[format]) !== 0;
3221
+ }
3222
+ if (lexical.$isTextNode(node) && "hasFormat" in node && typeof node.hasFormat === "function") {
3223
+ return node.hasFormat(format);
3224
+ }
3225
+ if (lexical.$isTextNode(node) && "getFormat" in node && typeof node.getFormat === "function") {
3226
+ return (node.getFormat() & FORMAT_MASKS[format]) !== 0;
3227
+ }
3228
+ return false;
3229
+ }
3230
+ function readToolbarStyleSnapshot(editor, editorState = editor.getEditorState()) {
3231
+ let snapshot = DEFAULT_TOOLBAR_STYLE_SNAPSHOT;
3232
+ editorState.read(() => {
3233
+ const currentSelection = lexical.$getSelection();
3234
+ const selection2 = lexical.$isNodeSelection(currentSelection) ? currentSelection : lexical.$createRangeSelectionFromDom(window.getSelection(), editor) ?? currentSelection;
3235
+ if (lexical.$isNodeSelection(selection2)) {
3236
+ const variableNodes = selection2.getNodes().filter($isVariableNode);
3237
+ if (variableNodes.length === 0) {
3238
+ return;
3239
+ }
3240
+ const firstVariableNode = variableNodes[0];
3241
+ const style2 = firstVariableNode.getStyle();
3242
+ snapshot = {
3243
+ blockType: extractInlineBlockTypeFromStyle(style2) ?? getElementBlockType(firstVariableNode),
3244
+ fontFamily: normalizeFontFamily(extractFontFamilyFromStyle(style2)),
3245
+ fontSize: extractFontSizePtFromStyle(style2) ?? DEFAULT_FONT_SIZE,
3246
+ alignment: getElementAlignment(firstVariableNode),
3247
+ isBold: variableNodes.every((node) => (node.getFormat() & FORMAT_MASKS.bold) !== 0),
3248
+ isItalic: variableNodes.every((node) => (node.getFormat() & FORMAT_MASKS.italic) !== 0),
3249
+ isUnderline: variableNodes.every((node) => (node.getFormat() & FORMAT_MASKS.underline) !== 0),
3250
+ isStrikethrough: variableNodes.every((node) => (node.getFormat() & FORMAT_MASKS.strikethrough) !== 0),
3251
+ hasSelectedVariable: true
3252
+ };
3253
+ return;
3254
+ }
3255
+ if (!lexical.$isRangeSelection(selection2)) {
3256
+ return;
3257
+ }
3258
+ const anchorNode = selection2.anchor.getNode();
3259
+ const inlineStyleTarget = getInlineStyleTarget(selection2.getNodes(), anchorNode);
3260
+ const style = selection2.style || getInlineStyleFromNode(inlineStyleTarget);
3261
+ const isCollapsed = selection2.isCollapsed();
3262
+ snapshot = {
3263
+ blockType: extractInlineBlockTypeFromStyle(style) ?? getElementBlockType(anchorNode),
3264
+ fontFamily: normalizeFontFamily(extractFontFamilyFromStyle(style)),
3265
+ fontSize: extractFontSizePtFromStyle(style) ?? DEFAULT_FONT_SIZE,
3266
+ alignment: getElementAlignment(anchorNode),
3267
+ isBold: selection2.hasFormat("bold") || isCollapsed && hasInlineFormat(inlineStyleTarget, "bold"),
3268
+ isItalic: selection2.hasFormat("italic") || isCollapsed && hasInlineFormat(inlineStyleTarget, "italic"),
3269
+ isUnderline: selection2.hasFormat("underline") || isCollapsed && hasInlineFormat(inlineStyleTarget, "underline"),
3270
+ isStrikethrough: selection2.hasFormat("strikethrough") || isCollapsed && hasInlineFormat(inlineStyleTarget, "strikethrough"),
3271
+ hasSelectedVariable: false
3272
+ };
3273
+ });
3274
+ return snapshot;
3275
+ }
2921
3276
  const Toolbar = () => {
2922
3277
  const {
2923
3278
  activeEditor,
@@ -2934,15 +3289,15 @@ const Toolbar = () => {
2934
3289
  const { toolbarItems, toolbarEndItems } = useExtensions();
2935
3290
  const toolbarConfig = useToolbarConfig();
2936
3291
  const t = useTranslations();
2937
- const [activeBlockType, setActiveBlockType] = React.useState("paragraph");
2938
- const [activeFontFamily, setActiveFontFamily] = React.useState("Calibri");
2939
- const [activeFontSize, setActiveFontSize] = React.useState(DEFAULT_FONT_SIZE);
2940
- const normalizeFontFamily = React.useCallback((fontFamily) => {
2941
- if (fontFamily && SUPPORTED_FONTS.includes(fontFamily)) {
2942
- return fontFamily;
2943
- }
2944
- return "Calibri";
2945
- }, []);
3292
+ const toolbarStyleStore = useToolbarStyleStoreApi();
3293
+ const activeBlockType = useToolbarStyleStore((state) => state.blockType);
3294
+ const activeFontFamily = useToolbarStyleStore((state) => state.fontFamily);
3295
+ const activeFontSize = useToolbarStyleStore((state) => state.fontSize);
3296
+ const activeAlignment = useToolbarStyleStore((state) => state.alignment);
3297
+ const isBoldActive = useToolbarStyleStore((state) => state.isBold);
3298
+ const isItalicActive = useToolbarStyleStore((state) => state.isItalic);
3299
+ const isUnderlineActive = useToolbarStyleStore((state) => state.isUnderline);
3300
+ const isStrikethroughActive = useToolbarStyleStore((state) => state.isStrikethrough);
2946
3301
  const withBodySelection = React.useCallback(
2947
3302
  (editor, action) => {
2948
3303
  editor.update(() => {
@@ -2980,38 +3335,11 @@ const Toolbar = () => {
2980
3335
  );
2981
3336
  React.useEffect(() => {
2982
3337
  if (!activeEditor) {
2983
- setActiveBlockType("paragraph");
2984
- setActiveFontFamily("Calibri");
2985
- setActiveFontSize(DEFAULT_FONT_SIZE);
3338
+ toolbarStyleStore.getState().reset();
2986
3339
  return;
2987
3340
  }
2988
- const updateSelectionState = () => {
2989
- const selectedVariables = getSelectedVariableNodes(activeEditor);
2990
- if (selectedVariables.length > 0) {
2991
- const formatting = readSelectedVariableFormatting(activeEditor);
2992
- setActiveBlockType("paragraph");
2993
- setActiveFontFamily(normalizeFontFamily(formatting.fontFamily));
2994
- setActiveFontSize(formatting.fontSize ?? DEFAULT_FONT_SIZE);
2995
- return;
2996
- }
2997
- setActiveBlockType(getActiveBlockType(activeEditor));
2998
- let nextFontFamily = "Calibri";
2999
- let nextFontSize = DEFAULT_FONT_SIZE;
3000
- activeEditor.getEditorState().read(() => {
3001
- const selection2 = lexical.$getSelection();
3002
- if (!lexical.$isRangeSelection(selection2)) {
3003
- return;
3004
- }
3005
- const textNode = selection2.getNodes().find(lexical.$isTextNode);
3006
- if (!textNode) {
3007
- return;
3008
- }
3009
- const style = textNode.getStyle();
3010
- nextFontFamily = normalizeFontFamily(extractFontFamilyFromStyle(style));
3011
- nextFontSize = extractFontSizePtFromStyle(style) ?? DEFAULT_FONT_SIZE;
3012
- });
3013
- setActiveFontFamily(nextFontFamily);
3014
- setActiveFontSize(nextFontSize);
3341
+ const updateSelectionState = (editorState = activeEditor.getEditorState()) => {
3342
+ toolbarStyleStore.getState().setSnapshot(readToolbarStyleSnapshot(activeEditor, editorState));
3015
3343
  };
3016
3344
  updateSelectionState();
3017
3345
  const unregisterSelectionChange = activeEditor.registerCommand(
@@ -3022,14 +3350,14 @@ const Toolbar = () => {
3022
3350
  },
3023
3351
  lexical.COMMAND_PRIORITY_LOW
3024
3352
  );
3025
- const unregisterUpdateListener = activeEditor.registerUpdateListener(() => {
3026
- updateSelectionState();
3353
+ const unregisterUpdateListener = activeEditor.registerUpdateListener(({ editorState }) => {
3354
+ updateSelectionState(editorState);
3027
3355
  });
3028
3356
  return () => {
3029
3357
  unregisterSelectionChange();
3030
3358
  unregisterUpdateListener();
3031
3359
  };
3032
- }, [activeEditor, normalizeFontFamily]);
3360
+ }, [activeEditor, toolbarStyleStore]);
3033
3361
  const handleBold = React.useCallback(() => {
3034
3362
  debug("toolbar", `bold (globalSelection=${globalSelectionActive}, editors=${editorRegistry.all().length}, hasEditor=${!!activeEditor})`);
3035
3363
  runToolbarAction(t.history.actions.boldApplied, () => {
@@ -3214,17 +3542,17 @@ const Toolbar = () => {
3214
3542
  ] }),
3215
3543
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
3216
3544
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group", "data-testid": "format-group", children: [
3217
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bold, testId: "btn-bold", onClick: handleBold, children: /* @__PURE__ */ jsxRuntime.jsx(Bold, { size: 15 }) }),
3218
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.italic, testId: "btn-italic", onClick: handleItalic, children: /* @__PURE__ */ jsxRuntime.jsx(Italic, { size: 15 }) }),
3219
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.underline, testId: "btn-underline", onClick: handleUnderline, children: /* @__PURE__ */ jsxRuntime.jsx(Underline, { size: 15 }) }),
3220
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.strikethrough, testId: "btn-strike", onClick: handleStrikethrough, children: /* @__PURE__ */ jsxRuntime.jsx(Strikethrough, { size: 15 }) })
3545
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bold, testId: "btn-bold", active: isBoldActive, onClick: handleBold, children: /* @__PURE__ */ jsxRuntime.jsx(Bold, { size: 15 }) }),
3546
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.italic, testId: "btn-italic", active: isItalicActive, onClick: handleItalic, children: /* @__PURE__ */ jsxRuntime.jsx(Italic, { size: 15 }) }),
3547
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.underline, testId: "btn-underline", active: isUnderlineActive, onClick: handleUnderline, children: /* @__PURE__ */ jsxRuntime.jsx(Underline, { size: 15 }) }),
3548
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.strikethrough, testId: "btn-strike", active: isStrikethroughActive, onClick: handleStrikethrough, children: /* @__PURE__ */ jsxRuntime.jsx(Strikethrough, { size: 15 }) })
3221
3549
  ] }),
3222
3550
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
3223
3551
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group", "data-testid": "align-group", children: [
3224
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignLeft, testId: "btn-align-left", onClick: handleAlignLeft, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignStart, { size: 15 }) }),
3225
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignCenter, testId: "btn-align-center", onClick: handleAlignCenter, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignCenter, { size: 15 }) }),
3226
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignRight, testId: "btn-align-right", onClick: handleAlignRight, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignEnd, { size: 15 }) }),
3227
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.justify, testId: "btn-align-justify", onClick: handleAlignJustify, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignJustify, { size: 15 }) })
3552
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignLeft, testId: "btn-align-left", active: activeAlignment === "left", onClick: handleAlignLeft, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignStart, { size: 15 }) }),
3553
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignCenter, testId: "btn-align-center", active: activeAlignment === "center", onClick: handleAlignCenter, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignCenter, { size: 15 }) }),
3554
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignRight, testId: "btn-align-right", active: activeAlignment === "right", onClick: handleAlignRight, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignEnd, { size: 15 }) }),
3555
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.justify, testId: "btn-align-justify", active: activeAlignment === "justify", onClick: handleAlignJustify, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignJustify, { size: 15 }) })
3228
3556
  ] }),
3229
3557
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
3230
3558
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group", "data-testid": "list-group", children: [
@@ -3275,6 +3603,7 @@ const ToolbarIconButton = ({
3275
3603
  type: "button",
3276
3604
  title,
3277
3605
  "aria-label": title,
3606
+ "aria-pressed": active,
3278
3607
  disabled,
3279
3608
  onMouseDown: (e) => e.preventDefault(),
3280
3609
  onClick,
@@ -3785,14 +4114,18 @@ const ActiveEditorPlugin = ({
3785
4114
  ]);
3786
4115
  React.useEffect(() => {
3787
4116
  const caretPosition = { pageId, region };
3788
- const handleFocusIn = () => {
4117
+ const markEditorActive = () => {
3789
4118
  setActivePageId(pageId);
3790
4119
  setActiveEditor(editor, caretPosition);
3791
4120
  onFocus == null ? void 0 : onFocus(editor);
3792
4121
  };
3793
4122
  return editor.registerRootListener((rootElement, prevRootElement) => {
3794
- prevRootElement == null ? void 0 : prevRootElement.removeEventListener("focusin", handleFocusIn);
3795
- rootElement == null ? void 0 : rootElement.addEventListener("focusin", handleFocusIn);
4123
+ prevRootElement == null ? void 0 : prevRootElement.removeEventListener("focusin", markEditorActive);
4124
+ prevRootElement == null ? void 0 : prevRootElement.removeEventListener("mousedown", markEditorActive);
4125
+ prevRootElement == null ? void 0 : prevRootElement.removeEventListener("pointerdown", markEditorActive);
4126
+ rootElement == null ? void 0 : rootElement.addEventListener("focusin", markEditorActive);
4127
+ rootElement == null ? void 0 : rootElement.addEventListener("mousedown", markEditorActive);
4128
+ rootElement == null ? void 0 : rootElement.addEventListener("pointerdown", markEditorActive);
3796
4129
  });
3797
4130
  }, [editor, onFocus, pageId, region, setActiveEditor, setActivePageId]);
3798
4131
  React.useEffect(() => {
@@ -4462,7 +4795,7 @@ const EditorRegistryPlugin = ({ pageId }) => {
4462
4795
  editorRegistry.register(pageId, editor);
4463
4796
  debug("registry", `registered editor for page ${shortId(pageId)}`);
4464
4797
  return () => {
4465
- editorRegistry.unregister(pageId);
4798
+ editorRegistry.unregister(pageId, editor);
4466
4799
  debug("registry", `unregistered editor for page ${shortId(pageId)}`);
4467
4800
  };
4468
4801
  }, [editor, pageId, editorRegistry]);
@@ -4786,7 +5119,7 @@ const PageView = React.memo(({
4786
5119
  onMoveToPreviousPage,
4787
5120
  onMoveToNextPage
4788
5121
  }) => {
4789
- const { document: document2, dispatch, setActivePageId } = useDocument();
5122
+ const { document: document2, dispatch, editorRegistry, setActiveEditor, setActivePageId } = useDocument();
4790
5123
  const t = useTranslations();
4791
5124
  const page = document2.pages.find((p) => p.id === pageId);
4792
5125
  const showHeaderFooter = document2.headerFooterEnabled;
@@ -4816,7 +5149,11 @@ const PageView = React.memo(({
4816
5149
  );
4817
5150
  const handleFocus = React.useCallback(() => {
4818
5151
  setActivePageId(pageId);
4819
- }, [setActivePageId, pageId]);
5152
+ const editor = editorRegistry.get(pageId);
5153
+ if (editor) {
5154
+ setActiveEditor(editor, { pageId, region: "body" });
5155
+ }
5156
+ }, [editorRegistry, pageId, setActiveEditor, setActivePageId]);
4820
5157
  const handleOverflow = React.useCallback(
4821
5158
  (overflowContent, cause) => {
4822
5159
  onOverflow == null ? void 0 : onOverflow(overflowContent, cause);
@@ -5476,6 +5813,20 @@ function insertDocumentContent(editor, document2) {
5476
5813
  });
5477
5814
  return inserted;
5478
5815
  }
5816
+ function resolveDocumentInsertTarget({
5817
+ activeEditor,
5818
+ activeCaretPosition,
5819
+ activePageId,
5820
+ editorRegistry
5821
+ }) {
5822
+ if (activeEditor && (activeCaretPosition == null ? void 0 : activeCaretPosition.region) === "body") {
5823
+ return activeEditor;
5824
+ }
5825
+ if (activePageId) {
5826
+ return editorRegistry.get(activePageId) ?? null;
5827
+ }
5828
+ return editorRegistry.all()[0] ?? null;
5829
+ }
5479
5830
  function selectEntireDocument(rootElement, selectionBuffer) {
5480
5831
  if (!rootElement || !selectionBuffer) {
5481
5832
  return;
@@ -5681,8 +6032,10 @@ const EditorChrome = ({
5681
6032
  const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, onSave, className }, ref) => {
5682
6033
  const {
5683
6034
  document: doc,
6035
+ activePageId,
5684
6036
  activeEditor,
5685
6037
  activeCaretPosition,
6038
+ editorRegistry,
5686
6039
  historySidebarOpen,
5687
6040
  runHistoryAction,
5688
6041
  setHistorySidebarOpen
@@ -5691,7 +6044,6 @@ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, on
5691
6044
  const t = useTranslations();
5692
6045
  const documentRef = React.useRef(doc);
5693
6046
  const activeEditorRef = React.useRef(activeEditor);
5694
- const activeCaretRegionRef = React.useRef(activeCaretPosition == null ? void 0 : activeCaretPosition.region);
5695
6047
  const historySidebarOpenRef = React.useRef(historySidebarOpen);
5696
6048
  const runHistoryActionRef = React.useRef(runHistoryAction);
5697
6049
  const insertedDocumentContentLabelRef = React.useRef(t.history.actions.insertedDocumentContent);
@@ -5701,9 +6053,6 @@ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, on
5701
6053
  React.useEffect(() => {
5702
6054
  activeEditorRef.current = activeEditor;
5703
6055
  }, [activeEditor]);
5704
- React.useEffect(() => {
5705
- activeCaretRegionRef.current = activeCaretPosition == null ? void 0 : activeCaretPosition.region;
5706
- }, [activeCaretPosition == null ? void 0 : activeCaretPosition.region]);
5707
6056
  React.useEffect(() => {
5708
6057
  historySidebarOpenRef.current = historySidebarOpen;
5709
6058
  }, [historySidebarOpen]);
@@ -5726,8 +6075,13 @@ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, on
5726
6075
  setHistorySidebarOpen(!historySidebarOpenRef.current);
5727
6076
  };
5728
6077
  handle.insertDocumentContent = (documentToInsert) => {
5729
- const currentActiveEditor = activeEditorRef.current;
5730
- if (!currentActiveEditor || activeCaretRegionRef.current !== "body") {
6078
+ const targetEditor = resolveDocumentInsertTarget({
6079
+ activeEditor: activeEditorRef.current,
6080
+ activeCaretPosition,
6081
+ activePageId,
6082
+ editorRegistry
6083
+ });
6084
+ if (!targetEditor) {
5731
6085
  return false;
5732
6086
  }
5733
6087
  let inserted = false;
@@ -5738,7 +6092,7 @@ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, on
5738
6092
  region: "document"
5739
6093
  },
5740
6094
  () => {
5741
- inserted = insertDocumentContent(currentActiveEditor, documentToInsert);
6095
+ inserted = insertDocumentContent(targetEditor, documentToInsert);
5742
6096
  }
5743
6097
  );
5744
6098
  return inserted;