@yurikilian/lex4 0.2.1 → 0.3.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 (35) hide show
  1. package/README.md +2 -3
  2. package/dist/components/DocumentView.d.ts.map +1 -1
  3. package/dist/components/HeaderFooterActions.d.ts.map +1 -1
  4. package/dist/components/HeaderFooterToggle.d.ts.map +1 -1
  5. package/dist/components/PageBody.d.ts.map +1 -1
  6. package/dist/components/PageFooter.d.ts.map +1 -1
  7. package/dist/components/PageHeader.d.ts.map +1 -1
  8. package/dist/components/PageView.d.ts.map +1 -1
  9. package/dist/components/Toolbar.d.ts.map +1 -1
  10. package/dist/context/document-provider.d.ts.map +1 -1
  11. package/dist/engine/overflow.d.ts +2 -0
  12. package/dist/engine/overflow.d.ts.map +1 -1
  13. package/dist/hooks/use-pagination.d.ts.map +1 -1
  14. package/dist/i18n/defaults.d.ts.map +1 -1
  15. package/dist/i18n/index.d.ts +1 -0
  16. package/dist/i18n/index.d.ts.map +1 -1
  17. package/dist/i18n/pt-BR.d.ts +3 -0
  18. package/dist/i18n/pt-BR.d.ts.map +1 -0
  19. package/dist/i18n/types.d.ts +42 -0
  20. package/dist/i18n/types.d.ts.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/lex4-editor.cjs +1025 -444
  24. package/dist/lex4-editor.cjs.map +1 -1
  25. package/dist/lex4-editor.js +1028 -447
  26. package/dist/lex4-editor.js.map +1 -1
  27. package/dist/lexical/plugins/font-plugin.d.ts +1 -1
  28. package/dist/lexical/plugins/history-capture-plugin.d.ts.map +1 -1
  29. package/dist/lexical/plugins/overflow-plugin.d.ts.map +1 -1
  30. package/dist/lexical/utils/mid-block-split.d.ts +34 -0
  31. package/dist/lexical/utils/mid-block-split.d.ts.map +1 -0
  32. package/dist/style.css +49 -28
  33. package/dist/utils/editor-state-utils.d.ts +10 -0
  34. package/dist/utils/editor-state-utils.d.ts.map +1 -1
  35. package/package.json +1 -1
@@ -454,6 +454,258 @@ function jumpToHistoryEntry(history, entryIndex) {
454
454
  caretSelection: ((_b = nextHistory.entries[nextHistory.cursor - 1]) == null ? void 0 : _b.caretSelection) ?? null
455
455
  };
456
456
  }
457
+ const DEFAULT_TRANSLATIONS = {
458
+ toolbar: {
459
+ undo: "Undo",
460
+ redo: "Redo",
461
+ bold: "Bold (Ctrl+B)",
462
+ italic: "Italic (Ctrl+I)",
463
+ underline: "Underline (Ctrl+U)",
464
+ strikethrough: "Strikethrough",
465
+ alignLeft: "Align Left",
466
+ alignCenter: "Align Center",
467
+ alignRight: "Align Right",
468
+ justify: "Justify",
469
+ numberedList: "Numbered List",
470
+ bulletList: "Bullet List",
471
+ indent: "Indent",
472
+ outdent: "Outdent",
473
+ openHistory: "Open History",
474
+ closeHistory: "Close History"
475
+ },
476
+ history: {
477
+ title: "History",
478
+ subtitle: "Word-style session history (last 100 actions)",
479
+ empty: "No history yet.",
480
+ clearHistory: "Clear History",
481
+ actions: {
482
+ enabledHeadersFooters: "Enabled headers and footers",
483
+ disabledHeadersFooters: "Disabled headers and footers",
484
+ copiedHeaderToAll: "Copied header to all pages",
485
+ copiedFooterToAll: "Copied footer to all pages",
486
+ clearedHeader: "Cleared header",
487
+ clearedFooter: "Cleared footer",
488
+ clearedAllHeaders: "Cleared all headers",
489
+ clearedAllFooters: "Cleared all footers",
490
+ pageCounterSet: "Page counter set to {{value}}",
491
+ boldApplied: "Bold applied",
492
+ italicApplied: "Italic applied",
493
+ underlineApplied: "Underline applied",
494
+ strikethroughApplied: "Strikethrough applied",
495
+ alignedLeft: "Aligned left",
496
+ alignedCenter: "Aligned center",
497
+ alignedRight: "Aligned right",
498
+ justifiedText: "Justified text",
499
+ insertedNumberedList: "Inserted numbered list",
500
+ insertedBulletList: "Inserted bullet list",
501
+ indentedContent: "Indented content",
502
+ outdentedContent: "Outdented content",
503
+ fontChanged: "Font changed to {{value}}",
504
+ fontSizeChanged: "Font size changed to {{value}}pt"
505
+ }
506
+ },
507
+ variables: {
508
+ title: "Variables",
509
+ available: "{{count}} available",
510
+ refreshVariables: "Refresh variables",
511
+ searchPlaceholder: "Search variables...",
512
+ noVariablesFound: "No variables found",
513
+ insertVariable: "Insert variable {{key}}",
514
+ openPanel: "Open Variables",
515
+ closePanel: "Close Variables"
516
+ },
517
+ header: {
518
+ placeholder: "Header"
519
+ },
520
+ footer: {
521
+ placeholder: "Footer"
522
+ },
523
+ body: {
524
+ placeholder: "Start typing..."
525
+ },
526
+ pageCounter: {
527
+ format: "Page {{current}} of {{total}}"
528
+ },
529
+ regions: {
530
+ body: "body",
531
+ header: "header",
532
+ footer: "footer",
533
+ document: "document",
534
+ toolbar: "toolbar",
535
+ overflow: "overflow",
536
+ history: "history",
537
+ page: "Page {{page}}"
538
+ },
539
+ headerFooter: {
540
+ label: "Headers & Footers"
541
+ },
542
+ sidebar: {
543
+ close: "Close sidebar"
544
+ },
545
+ historyLabels: {
546
+ typedText: "Typed text",
547
+ pastedContent: "Pasted content",
548
+ insertedLineBreak: "Inserted line break",
549
+ deletedBackward: "Deleted backward",
550
+ deletedForward: "Deleted forward",
551
+ formattedText: "Formatted text",
552
+ formattedParagraph: "Formatted paragraph",
553
+ editedBody: "Edited body",
554
+ editedHeader: "Edited header",
555
+ editedFooter: "Edited footer",
556
+ clearedDocumentBody: "Cleared document body",
557
+ resizedHeader: "Resized header",
558
+ resizedFooter: "Resized footer",
559
+ addedPage: "Added page",
560
+ removedPage: "Removed page",
561
+ documentReflow: "Document reflow",
562
+ updatedDocument: "Updated document"
563
+ }
564
+ };
565
+ const PT_BR_TRANSLATIONS = {
566
+ toolbar: {
567
+ undo: "Desfazer",
568
+ redo: "Refazer",
569
+ bold: "Negrito (Ctrl+B)",
570
+ italic: "Itálico (Ctrl+I)",
571
+ underline: "Sublinhado (Ctrl+U)",
572
+ strikethrough: "Tachado",
573
+ alignLeft: "Alinhar à Esquerda",
574
+ alignCenter: "Centralizar",
575
+ alignRight: "Alinhar à Direita",
576
+ justify: "Justificar",
577
+ numberedList: "Lista Numerada",
578
+ bulletList: "Lista com Marcadores",
579
+ indent: "Aumentar Recuo",
580
+ outdent: "Diminuir Recuo",
581
+ openHistory: "Abrir Histórico",
582
+ closeHistory: "Fechar Histórico"
583
+ },
584
+ history: {
585
+ title: "Histórico",
586
+ subtitle: "Histórico de sessão (últimas 100 ações)",
587
+ empty: "Nenhum histórico ainda.",
588
+ clearHistory: "Limpar Histórico",
589
+ actions: {
590
+ enabledHeadersFooters: "Cabeçalhos e rodapés ativados",
591
+ disabledHeadersFooters: "Cabeçalhos e rodapés desativados",
592
+ copiedHeaderToAll: "Cabeçalho copiado para todas as páginas",
593
+ copiedFooterToAll: "Rodapé copiado para todas as páginas",
594
+ clearedHeader: "Cabeçalho limpo",
595
+ clearedFooter: "Rodapé limpo",
596
+ clearedAllHeaders: "Todos os cabeçalhos limpos",
597
+ clearedAllFooters: "Todos os rodapés limpos",
598
+ pageCounterSet: "Contador de páginas definido como {{value}}",
599
+ boldApplied: "Negrito aplicado",
600
+ italicApplied: "Itálico aplicado",
601
+ underlineApplied: "Sublinhado aplicado",
602
+ strikethroughApplied: "Tachado aplicado",
603
+ alignedLeft: "Alinhado à esquerda",
604
+ alignedCenter: "Centralizado",
605
+ alignedRight: "Alinhado à direita",
606
+ justifiedText: "Texto justificado",
607
+ insertedNumberedList: "Lista numerada inserida",
608
+ insertedBulletList: "Lista com marcadores inserida",
609
+ indentedContent: "Conteúdo recuado",
610
+ outdentedContent: "Recuo reduzido",
611
+ fontChanged: "Fonte alterada para {{value}}",
612
+ fontSizeChanged: "Tamanho da fonte alterado para {{value}}pt"
613
+ }
614
+ },
615
+ variables: {
616
+ title: "Variáveis",
617
+ available: "{{count}} disponíveis",
618
+ refreshVariables: "Atualizar variáveis",
619
+ searchPlaceholder: "Buscar variáveis...",
620
+ noVariablesFound: "Nenhuma variável encontrada",
621
+ insertVariable: "Inserir variável {{key}}",
622
+ openPanel: "Abrir Variáveis",
623
+ closePanel: "Fechar Variáveis"
624
+ },
625
+ header: {
626
+ placeholder: "Cabeçalho"
627
+ },
628
+ footer: {
629
+ placeholder: "Rodapé"
630
+ },
631
+ body: {
632
+ placeholder: "Comece a digitar..."
633
+ },
634
+ pageCounter: {
635
+ format: "Página {{current}} de {{total}}"
636
+ },
637
+ regions: {
638
+ body: "corpo",
639
+ header: "cabeçalho",
640
+ footer: "rodapé",
641
+ document: "documento",
642
+ toolbar: "barra de ferramentas",
643
+ overflow: "estouro",
644
+ history: "histórico",
645
+ page: "Página {{page}}"
646
+ },
647
+ headerFooter: {
648
+ label: "Cabeçalhos e Rodapés"
649
+ },
650
+ sidebar: {
651
+ close: "Fechar barra lateral"
652
+ },
653
+ historyLabels: {
654
+ typedText: "Texto digitado",
655
+ pastedContent: "Conteúdo colado",
656
+ insertedLineBreak: "Quebra de linha inserida",
657
+ deletedBackward: "Exclusão para trás",
658
+ deletedForward: "Exclusão para frente",
659
+ formattedText: "Texto formatado",
660
+ formattedParagraph: "Parágrafo formatado",
661
+ editedBody: "Corpo editado",
662
+ editedHeader: "Cabeçalho editado",
663
+ editedFooter: "Rodapé editado",
664
+ clearedDocumentBody: "Corpo do documento limpo",
665
+ resizedHeader: "Cabeçalho redimensionado",
666
+ resizedFooter: "Rodapé redimensionado",
667
+ addedPage: "Página adicionada",
668
+ removedPage: "Página removida",
669
+ documentReflow: "Redistribuição do documento",
670
+ updatedDocument: "Documento atualizado"
671
+ }
672
+ };
673
+ function deepMerge(target, source) {
674
+ if (typeof target !== "object" || target === null) {
675
+ return source ?? target;
676
+ }
677
+ const result = { ...target };
678
+ for (const key of Object.keys(source)) {
679
+ const sourceVal = source[key];
680
+ const targetVal = target[key];
681
+ if (sourceVal !== void 0 && typeof sourceVal === "object" && sourceVal !== null && !Array.isArray(sourceVal) && typeof targetVal === "object" && targetVal !== null) {
682
+ result[key] = deepMerge(targetVal, sourceVal);
683
+ } else if (sourceVal !== void 0) {
684
+ result[key] = sourceVal;
685
+ }
686
+ }
687
+ return result;
688
+ }
689
+ const TranslationsContext = React.createContext(DEFAULT_TRANSLATIONS);
690
+ function useTranslations() {
691
+ return React.useContext(TranslationsContext);
692
+ }
693
+ function interpolate(template, params) {
694
+ return template.replace(
695
+ /\{\{(\w+)\}\}/g,
696
+ (_, key) => String(params[key] ?? `{{${key}}}`)
697
+ );
698
+ }
699
+ const TranslationsProvider = ({
700
+ translations,
701
+ children
702
+ }) => {
703
+ const merged = React.useMemo(
704
+ () => translations ? deepMerge(DEFAULT_TRANSLATIONS, translations) : DEFAULT_TRANSLATIONS,
705
+ [translations]
706
+ );
707
+ return /* @__PURE__ */ jsxRuntime.jsx(TranslationsContext.Provider, { value: merged, children });
708
+ };
457
709
  const HISTORY_RESTORE_SUPPRESSION_MS = 100;
458
710
  const HISTORY_BATCH_FLUSH_MS = 16;
459
711
  function cloneDocumentSnapshot(document2) {
@@ -526,12 +778,15 @@ function getPageNumber(document2, pageId) {
526
778
  const index = document2.pages.findIndex((page) => page.id === pageId);
527
779
  return index >= 0 ? index + 1 : null;
528
780
  }
529
- function describeAction(action, document2) {
781
+ function describeAction(action, document2, t) {
782
+ const pageSuffix = (pageId) => {
783
+ const num = getPageNumber(document2, pageId);
784
+ return num ? ` - ${interpolate(t.regions.page, { page: num })}` : "";
785
+ };
530
786
  switch (action.type) {
531
787
  case "UPDATE_PAGE_BODY": {
532
- const pageNumber = getPageNumber(document2, action.pageId);
533
788
  return {
534
- label: pageNumber ? `Edited body - Page ${pageNumber}` : "Edited body",
789
+ label: t.historyLabels.editedBody + pageSuffix(action.pageId),
535
790
  source: "body",
536
791
  pageId: action.pageId,
537
792
  region: "body"
@@ -539,9 +794,8 @@ function describeAction(action, document2) {
539
794
  }
540
795
  case "UPDATE_PAGE_HEADER":
541
796
  case "UPDATE_PAGE_HEADER_CONTENT": {
542
- const pageNumber = getPageNumber(document2, action.pageId);
543
797
  return {
544
- label: pageNumber ? `Edited header - Page ${pageNumber}` : "Edited header",
798
+ label: t.historyLabels.editedHeader + pageSuffix(action.pageId),
545
799
  source: "header",
546
800
  pageId: action.pageId,
547
801
  region: "header"
@@ -549,9 +803,8 @@ function describeAction(action, document2) {
549
803
  }
550
804
  case "UPDATE_PAGE_FOOTER":
551
805
  case "UPDATE_PAGE_FOOTER_CONTENT": {
552
- const pageNumber = getPageNumber(document2, action.pageId);
553
806
  return {
554
- label: pageNumber ? `Edited footer - Page ${pageNumber}` : "Edited footer",
807
+ label: t.historyLabels.editedFooter + pageSuffix(action.pageId),
555
808
  source: "footer",
556
809
  pageId: action.pageId,
557
810
  region: "footer"
@@ -559,43 +812,41 @@ function describeAction(action, document2) {
559
812
  }
560
813
  case "SET_HEADER_FOOTER_ENABLED":
561
814
  return {
562
- label: action.enabled ? "Enabled headers and footers" : "Disabled headers and footers",
815
+ label: action.enabled ? t.history.actions.enabledHeadersFooters : t.history.actions.disabledHeadersFooters,
563
816
  source: "document",
564
817
  region: "document"
565
818
  };
566
819
  case "SET_PAGE_COUNTER_MODE":
567
820
  return {
568
- label: `Page counter set to ${action.mode}`,
821
+ label: interpolate(t.history.actions.pageCounterSet, { value: action.mode }),
569
822
  source: "document",
570
823
  region: "document"
571
824
  };
572
825
  case "COPY_HEADER_TO_ALL":
573
826
  return {
574
- label: "Copied header to all pages",
827
+ label: t.history.actions.copiedHeaderToAll,
575
828
  source: "toolbar",
576
829
  pageId: action.sourcePageId,
577
830
  region: "header"
578
831
  };
579
832
  case "COPY_FOOTER_TO_ALL":
580
833
  return {
581
- label: "Copied footer to all pages",
834
+ label: t.history.actions.copiedFooterToAll,
582
835
  source: "toolbar",
583
836
  pageId: action.sourcePageId,
584
837
  region: "footer"
585
838
  };
586
839
  case "CLEAR_HEADER": {
587
- const pageNumber = getPageNumber(document2, action.pageId);
588
840
  return {
589
- label: pageNumber ? `Cleared header - Page ${pageNumber}` : "Cleared header",
841
+ label: t.history.actions.clearedHeader + pageSuffix(action.pageId),
590
842
  source: "toolbar",
591
843
  pageId: action.pageId,
592
844
  region: "header"
593
845
  };
594
846
  }
595
847
  case "CLEAR_FOOTER": {
596
- const pageNumber = getPageNumber(document2, action.pageId);
597
848
  return {
598
- label: pageNumber ? `Cleared footer - Page ${pageNumber}` : "Cleared footer",
849
+ label: t.history.actions.clearedFooter + pageSuffix(action.pageId),
599
850
  source: "toolbar",
600
851
  pageId: action.pageId,
601
852
  region: "footer"
@@ -603,35 +854,33 @@ function describeAction(action, document2) {
603
854
  }
604
855
  case "CLEAR_ALL_HEADERS":
605
856
  return {
606
- label: "Cleared all headers",
857
+ label: t.history.actions.clearedAllHeaders,
607
858
  source: "toolbar",
608
859
  region: "header"
609
860
  };
610
861
  case "CLEAR_ALL_FOOTERS":
611
862
  return {
612
- label: "Cleared all footers",
863
+ label: t.history.actions.clearedAllFooters,
613
864
  source: "toolbar",
614
865
  region: "footer"
615
866
  };
616
867
  case "CLEAR_DOCUMENT_CONTENT":
617
868
  return {
618
- label: "Cleared document body",
869
+ label: t.historyLabels.clearedDocumentBody,
619
870
  source: "document",
620
871
  region: "document"
621
872
  };
622
873
  case "SET_HEADER_HEIGHT": {
623
- const pageNumber = getPageNumber(document2, action.pageId);
624
874
  return {
625
- label: pageNumber ? `Resized header - Page ${pageNumber}` : "Resized header",
875
+ label: t.historyLabels.resizedHeader + pageSuffix(action.pageId),
626
876
  source: "header",
627
877
  pageId: action.pageId,
628
878
  region: "header"
629
879
  };
630
880
  }
631
881
  case "SET_FOOTER_HEIGHT": {
632
- const pageNumber = getPageNumber(document2, action.pageId);
633
882
  return {
634
- label: pageNumber ? `Resized footer - Page ${pageNumber}` : "Resized footer",
883
+ label: t.historyLabels.resizedFooter + pageSuffix(action.pageId),
635
884
  source: "footer",
636
885
  pageId: action.pageId,
637
886
  region: "footer"
@@ -639,25 +888,25 @@ function describeAction(action, document2) {
639
888
  }
640
889
  case "ADD_PAGE":
641
890
  return {
642
- label: "Added page",
891
+ label: t.historyLabels.addedPage,
643
892
  source: "overflow",
644
893
  region: "document"
645
894
  };
646
895
  case "REMOVE_PAGE":
647
896
  return {
648
- label: "Removed page",
897
+ label: t.historyLabels.removedPage,
649
898
  source: "overflow",
650
899
  region: "document"
651
900
  };
652
901
  case "SET_DOCUMENT":
653
902
  return {
654
- label: "Document reflow",
903
+ label: t.historyLabels.documentReflow,
655
904
  source: "overflow",
656
905
  region: "document"
657
906
  };
658
907
  default:
659
908
  return {
660
- label: "Updated document",
909
+ label: t.historyLabels.updatedDocument,
661
910
  source: "document",
662
911
  region: "document"
663
912
  };
@@ -684,6 +933,7 @@ const DocumentProvider = ({
684
933
  const [globalSelectionActive, setGlobalSelectionActive] = React.useState(false);
685
934
  const [historySidebarOpen, setHistorySidebarOpen] = React.useState(true);
686
935
  const [focusAtEndVersion, setFocusAtEndVersion] = React.useState(0);
936
+ const t = useTranslations();
687
937
  const activeEditorRef = React.useRef(null);
688
938
  const activeCaretPositionRef = React.useRef(null);
689
939
  const pendingCaretPositionRef = React.useRef(null);
@@ -877,7 +1127,7 @@ const DocumentProvider = ({
877
1127
  };
878
1128
  scheduleHistoryBatchFlush();
879
1129
  } else {
880
- const descriptor = describeAction(action, currentDocument);
1130
+ const descriptor = describeAction(action, currentDocument, t);
881
1131
  setHistoryState((previousHistory) => {
882
1132
  const nextHistory = recordHistoryEntry(
883
1133
  previousHistory,
@@ -1136,20 +1386,33 @@ const createLucideIcon = (iconName, iconNode) => {
1136
1386
  * This source code is licensed under the ISC license.
1137
1387
  * See the LICENSE file in the root directory of this source tree.
1138
1388
  */
1139
- const __iconNode$m = [
1389
+ const __iconNode$q = [
1390
+ ["path", { d: "m15 16 2.536-7.328a1.02 1.02 1 0 1 1.928 0L22 16", key: "xik6mr" }],
1391
+ ["path", { d: "M15.697 14h5.606", key: "1stdlc" }],
1392
+ ["path", { d: "m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16", key: "d5nyq2" }],
1393
+ ["path", { d: "M3.304 13h6.392", key: "1q3zxz" }]
1394
+ ];
1395
+ const ALargeSmall = createLucideIcon("a-large-small", __iconNode$q);
1396
+ /**
1397
+ * @license lucide-react v1.8.0 - ISC
1398
+ *
1399
+ * This source code is licensed under the ISC license.
1400
+ * See the LICENSE file in the root directory of this source tree.
1401
+ */
1402
+ const __iconNode$p = [
1140
1403
  [
1141
1404
  "path",
1142
1405
  { 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" }
1143
1406
  ]
1144
1407
  ];
1145
- const Bold = createLucideIcon("bold", __iconNode$m);
1408
+ const Bold = createLucideIcon("bold", __iconNode$p);
1146
1409
  /**
1147
1410
  * @license lucide-react v1.8.0 - ISC
1148
1411
  *
1149
1412
  * This source code is licensed under the ISC license.
1150
1413
  * See the LICENSE file in the root directory of this source tree.
1151
1414
  */
1152
- const __iconNode$l = [
1415
+ const __iconNode$o = [
1153
1416
  [
1154
1417
  "path",
1155
1418
  { 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" }
@@ -1162,27 +1425,35 @@ const __iconNode$l = [
1162
1425
  }
1163
1426
  ]
1164
1427
  ];
1165
- const Braces = createLucideIcon("braces", __iconNode$l);
1428
+ const Braces = createLucideIcon("braces", __iconNode$o);
1166
1429
  /**
1167
1430
  * @license lucide-react v1.8.0 - ISC
1168
1431
  *
1169
1432
  * This source code is licensed under the ISC license.
1170
1433
  * See the LICENSE file in the root directory of this source tree.
1171
1434
  */
1172
- const __iconNode$k = [
1435
+ const __iconNode$n = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
1436
+ const ChevronDown = createLucideIcon("chevron-down", __iconNode$n);
1437
+ /**
1438
+ * @license lucide-react v1.8.0 - ISC
1439
+ *
1440
+ * This source code is licensed under the ISC license.
1441
+ * See the LICENSE file in the root directory of this source tree.
1442
+ */
1443
+ const __iconNode$m = [
1173
1444
  ["line", { x1: "15", x2: "15", y1: "12", y2: "18", key: "1p7wdc" }],
1174
1445
  ["line", { x1: "12", x2: "18", y1: "15", y2: "15", key: "1nscbv" }],
1175
1446
  ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
1176
1447
  ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
1177
1448
  ];
1178
- const CopyPlus = createLucideIcon("copy-plus", __iconNode$k);
1449
+ const CopyPlus = createLucideIcon("copy-plus", __iconNode$m);
1179
1450
  /**
1180
1451
  * @license lucide-react v1.8.0 - ISC
1181
1452
  *
1182
1453
  * This source code is licensed under the ISC license.
1183
1454
  * See the LICENSE file in the root directory of this source tree.
1184
1455
  */
1185
- const __iconNode$j = [
1456
+ const __iconNode$l = [
1186
1457
  [
1187
1458
  "path",
1188
1459
  {
@@ -1192,65 +1463,52 @@ const __iconNode$j = [
1192
1463
  ],
1193
1464
  ["path", { d: "m5.082 11.09 8.828 8.828", key: "1wx5vj" }]
1194
1465
  ];
1195
- const Eraser = createLucideIcon("eraser", __iconNode$j);
1466
+ const Eraser = createLucideIcon("eraser", __iconNode$l);
1196
1467
  /**
1197
1468
  * @license lucide-react v1.8.0 - ISC
1198
1469
  *
1199
1470
  * This source code is licensed under the ISC license.
1200
1471
  * See the LICENSE file in the root directory of this source tree.
1201
1472
  */
1202
- const __iconNode$i = [
1203
- ["line", { x1: "4", x2: "20", y1: "9", y2: "9", key: "4lhtct" }],
1204
- ["line", { x1: "4", x2: "20", y1: "15", y2: "15", key: "vyu0kd" }],
1205
- ["line", { x1: "10", x2: "8", y1: "3", y2: "21", key: "1ggp8o" }],
1206
- ["line", { x1: "16", x2: "14", y1: "3", y2: "21", key: "weycgp" }]
1207
- ];
1208
- const Hash = createLucideIcon("hash", __iconNode$i);
1209
- /**
1210
- * @license lucide-react v1.8.0 - ISC
1211
- *
1212
- * This source code is licensed under the ISC license.
1213
- * See the LICENSE file in the root directory of this source tree.
1214
- */
1215
- const __iconNode$h = [
1473
+ const __iconNode$k = [
1216
1474
  ["line", { x1: "19", x2: "10", y1: "4", y2: "4", key: "15jd3p" }],
1217
1475
  ["line", { x1: "14", x2: "5", y1: "20", y2: "20", key: "bu0au3" }],
1218
1476
  ["line", { x1: "15", x2: "9", y1: "4", y2: "20", key: "uljnxc" }]
1219
1477
  ];
1220
- const Italic = createLucideIcon("italic", __iconNode$h);
1478
+ const Italic = createLucideIcon("italic", __iconNode$k);
1221
1479
  /**
1222
1480
  * @license lucide-react v1.8.0 - ISC
1223
1481
  *
1224
1482
  * This source code is licensed under the ISC license.
1225
1483
  * See the LICENSE file in the root directory of this source tree.
1226
1484
  */
1227
- const __iconNode$g = [
1485
+ const __iconNode$j = [
1228
1486
  ["path", { d: "M21 5H11", key: "us1j55" }],
1229
1487
  ["path", { d: "M21 12H11", key: "wd7e0v" }],
1230
1488
  ["path", { d: "M21 19H11", key: "saa85w" }],
1231
1489
  ["path", { d: "m7 8-4 4 4 4", key: "o5hrat" }]
1232
1490
  ];
1233
- const ListIndentDecrease = createLucideIcon("list-indent-decrease", __iconNode$g);
1491
+ const ListIndentDecrease = createLucideIcon("list-indent-decrease", __iconNode$j);
1234
1492
  /**
1235
1493
  * @license lucide-react v1.8.0 - ISC
1236
1494
  *
1237
1495
  * This source code is licensed under the ISC license.
1238
1496
  * See the LICENSE file in the root directory of this source tree.
1239
1497
  */
1240
- const __iconNode$f = [
1498
+ const __iconNode$i = [
1241
1499
  ["path", { d: "M21 5H11", key: "us1j55" }],
1242
1500
  ["path", { d: "M21 12H11", key: "wd7e0v" }],
1243
1501
  ["path", { d: "M21 19H11", key: "saa85w" }],
1244
1502
  ["path", { d: "m3 8 4 4-4 4", key: "1a3j6y" }]
1245
1503
  ];
1246
- const ListIndentIncrease = createLucideIcon("list-indent-increase", __iconNode$f);
1504
+ const ListIndentIncrease = createLucideIcon("list-indent-increase", __iconNode$i);
1247
1505
  /**
1248
1506
  * @license lucide-react v1.8.0 - ISC
1249
1507
  *
1250
1508
  * This source code is licensed under the ISC license.
1251
1509
  * See the LICENSE file in the root directory of this source tree.
1252
1510
  */
1253
- const __iconNode$e = [
1511
+ const __iconNode$h = [
1254
1512
  ["path", { d: "M11 5h10", key: "1cz7ny" }],
1255
1513
  ["path", { d: "M11 12h10", key: "1438ji" }],
1256
1514
  ["path", { d: "M11 19h10", key: "11t30w" }],
@@ -1258,14 +1516,14 @@ const __iconNode$e = [
1258
1516
  ["path", { d: "M4 9h2", key: "r1h2o0" }],
1259
1517
  ["path", { d: "M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02", key: "xtkcd5" }]
1260
1518
  ];
1261
- const ListOrdered = createLucideIcon("list-ordered", __iconNode$e);
1519
+ const ListOrdered = createLucideIcon("list-ordered", __iconNode$h);
1262
1520
  /**
1263
1521
  * @license lucide-react v1.8.0 - ISC
1264
1522
  *
1265
1523
  * This source code is licensed under the ISC license.
1266
1524
  * See the LICENSE file in the root directory of this source tree.
1267
1525
  */
1268
- const __iconNode$d = [
1526
+ const __iconNode$g = [
1269
1527
  ["path", { d: "M3 5h.01", key: "18ugdj" }],
1270
1528
  ["path", { d: "M3 12h.01", key: "nlz23k" }],
1271
1529
  ["path", { d: "M3 19h.01", key: "noohij" }],
@@ -1273,127 +1531,168 @@ const __iconNode$d = [
1273
1531
  ["path", { d: "M8 12h13", key: "1za7za" }],
1274
1532
  ["path", { d: "M8 19h13", key: "m83p4d" }]
1275
1533
  ];
1276
- const List = createLucideIcon("list", __iconNode$d);
1534
+ const List = createLucideIcon("list", __iconNode$g);
1277
1535
  /**
1278
1536
  * @license lucide-react v1.8.0 - ISC
1279
1537
  *
1280
1538
  * This source code is licensed under the ISC license.
1281
1539
  * See the LICENSE file in the root directory of this source tree.
1282
1540
  */
1283
- const __iconNode$c = [
1541
+ const __iconNode$f = [
1284
1542
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }],
1285
1543
  ["path", { d: "M15 3v18", key: "14nvp0" }]
1286
1544
  ];
1287
- const PanelRight = createLucideIcon("panel-right", __iconNode$c);
1545
+ const PanelRight = createLucideIcon("panel-right", __iconNode$f);
1288
1546
  /**
1289
1547
  * @license lucide-react v1.8.0 - ISC
1290
1548
  *
1291
1549
  * This source code is licensed under the ISC license.
1292
1550
  * See the LICENSE file in the root directory of this source tree.
1293
1551
  */
1294
- const __iconNode$b = [
1552
+ const __iconNode$e = [
1295
1553
  ["path", { d: "m15 14 5-5-5-5", key: "12vg1m" }],
1296
1554
  ["path", { d: "M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13", key: "6uklza" }]
1297
1555
  ];
1298
- const Redo2 = createLucideIcon("redo-2", __iconNode$b);
1556
+ const Redo2 = createLucideIcon("redo-2", __iconNode$e);
1299
1557
  /**
1300
1558
  * @license lucide-react v1.8.0 - ISC
1301
1559
  *
1302
1560
  * This source code is licensed under the ISC license.
1303
1561
  * See the LICENSE file in the root directory of this source tree.
1304
1562
  */
1305
- const __iconNode$a = [
1563
+ const __iconNode$d = [
1306
1564
  ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }],
1307
1565
  ["path", { d: "M21 3v5h-5", key: "1q7to0" }],
1308
1566
  ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }],
1309
1567
  ["path", { d: "M8 16H3v5", key: "1cv678" }]
1310
1568
  ];
1311
- const RefreshCw = createLucideIcon("refresh-cw", __iconNode$a);
1569
+ const RefreshCw = createLucideIcon("refresh-cw", __iconNode$d);
1312
1570
  /**
1313
1571
  * @license lucide-react v1.8.0 - ISC
1314
1572
  *
1315
1573
  * This source code is licensed under the ISC license.
1316
1574
  * See the LICENSE file in the root directory of this source tree.
1317
1575
  */
1318
- const __iconNode$9 = [
1576
+ const __iconNode$c = [
1577
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }],
1578
+ ["path", { d: "M21 9H3", key: "1338ky" }],
1579
+ ["path", { d: "M21 15H3", key: "9uk58r" }]
1580
+ ];
1581
+ const Rows3 = createLucideIcon("rows-3", __iconNode$c);
1582
+ /**
1583
+ * @license lucide-react v1.8.0 - ISC
1584
+ *
1585
+ * This source code is licensed under the ISC license.
1586
+ * See the LICENSE file in the root directory of this source tree.
1587
+ */
1588
+ const __iconNode$b = [
1319
1589
  ["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
1320
1590
  ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
1321
1591
  ];
1322
- const Search = createLucideIcon("search", __iconNode$9);
1592
+ const Search = createLucideIcon("search", __iconNode$b);
1323
1593
  /**
1324
1594
  * @license lucide-react v1.8.0 - ISC
1325
1595
  *
1326
1596
  * This source code is licensed under the ISC license.
1327
1597
  * See the LICENSE file in the root directory of this source tree.
1328
1598
  */
1329
- const __iconNode$8 = [
1599
+ const __iconNode$a = [
1600
+ [
1601
+ "path",
1602
+ {
1603
+ d: "M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915",
1604
+ key: "1i5ecw"
1605
+ }
1606
+ ],
1607
+ ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
1608
+ ];
1609
+ const Settings = createLucideIcon("settings", __iconNode$a);
1610
+ /**
1611
+ * @license lucide-react v1.8.0 - ISC
1612
+ *
1613
+ * This source code is licensed under the ISC license.
1614
+ * See the LICENSE file in the root directory of this source tree.
1615
+ */
1616
+ const __iconNode$9 = [
1330
1617
  ["path", { d: "M16 4H9a3 3 0 0 0-2.83 4", key: "43sutm" }],
1331
1618
  ["path", { d: "M14 12a4 4 0 0 1 0 8H6", key: "nlfj13" }],
1332
1619
  ["line", { x1: "4", x2: "20", y1: "12", y2: "12", key: "1e0a9i" }]
1333
1620
  ];
1334
- const Strikethrough = createLucideIcon("strikethrough", __iconNode$8);
1621
+ const Strikethrough = createLucideIcon("strikethrough", __iconNode$9);
1335
1622
  /**
1336
1623
  * @license lucide-react v1.8.0 - ISC
1337
1624
  *
1338
1625
  * This source code is licensed under the ISC license.
1339
1626
  * See the LICENSE file in the root directory of this source tree.
1340
1627
  */
1341
- const __iconNode$7 = [
1628
+ const __iconNode$8 = [
1342
1629
  ["path", { d: "M21 5H3", key: "1fi0y6" }],
1343
1630
  ["path", { d: "M17 12H7", key: "16if0g" }],
1344
1631
  ["path", { d: "M19 19H5", key: "vjpgq2" }]
1345
1632
  ];
1346
- const TextAlignCenter = createLucideIcon("text-align-center", __iconNode$7);
1633
+ const TextAlignCenter = createLucideIcon("text-align-center", __iconNode$8);
1347
1634
  /**
1348
1635
  * @license lucide-react v1.8.0 - ISC
1349
1636
  *
1350
1637
  * This source code is licensed under the ISC license.
1351
1638
  * See the LICENSE file in the root directory of this source tree.
1352
1639
  */
1353
- const __iconNode$6 = [
1640
+ const __iconNode$7 = [
1354
1641
  ["path", { d: "M21 5H3", key: "1fi0y6" }],
1355
1642
  ["path", { d: "M21 12H9", key: "dn1m92" }],
1356
1643
  ["path", { d: "M21 19H7", key: "4cu937" }]
1357
1644
  ];
1358
- const TextAlignEnd = createLucideIcon("text-align-end", __iconNode$6);
1645
+ const TextAlignEnd = createLucideIcon("text-align-end", __iconNode$7);
1359
1646
  /**
1360
1647
  * @license lucide-react v1.8.0 - ISC
1361
1648
  *
1362
1649
  * This source code is licensed under the ISC license.
1363
1650
  * See the LICENSE file in the root directory of this source tree.
1364
1651
  */
1365
- const __iconNode$5 = [
1652
+ const __iconNode$6 = [
1366
1653
  ["path", { d: "M3 5h18", key: "1u36vt" }],
1367
1654
  ["path", { d: "M3 12h18", key: "1i2n21" }],
1368
1655
  ["path", { d: "M3 19h18", key: "awlh7x" }]
1369
1656
  ];
1370
- const TextAlignJustify = createLucideIcon("text-align-justify", __iconNode$5);
1657
+ const TextAlignJustify = createLucideIcon("text-align-justify", __iconNode$6);
1371
1658
  /**
1372
1659
  * @license lucide-react v1.8.0 - ISC
1373
1660
  *
1374
1661
  * This source code is licensed under the ISC license.
1375
1662
  * See the LICENSE file in the root directory of this source tree.
1376
1663
  */
1377
- const __iconNode$4 = [
1664
+ const __iconNode$5 = [
1378
1665
  ["path", { d: "M21 5H3", key: "1fi0y6" }],
1379
1666
  ["path", { d: "M15 12H3", key: "6jk70r" }],
1380
1667
  ["path", { d: "M17 19H3", key: "z6ezky" }]
1381
1668
  ];
1382
- const TextAlignStart = createLucideIcon("text-align-start", __iconNode$4);
1669
+ const TextAlignStart = createLucideIcon("text-align-start", __iconNode$5);
1383
1670
  /**
1384
1671
  * @license lucide-react v1.8.0 - ISC
1385
1672
  *
1386
1673
  * This source code is licensed under the ISC license.
1387
1674
  * See the LICENSE file in the root directory of this source tree.
1388
1675
  */
1389
- const __iconNode$3 = [
1676
+ const __iconNode$4 = [
1390
1677
  ["path", { d: "M10 11v6", key: "nco0om" }],
1391
1678
  ["path", { d: "M14 11v6", key: "outv1u" }],
1392
1679
  ["path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6", key: "miytrc" }],
1393
1680
  ["path", { d: "M3 6h18", key: "d0wm0j" }],
1394
1681
  ["path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2", key: "e791ji" }]
1395
1682
  ];
1396
- const Trash2 = createLucideIcon("trash-2", __iconNode$3);
1683
+ const Trash2 = createLucideIcon("trash-2", __iconNode$4);
1684
+ /**
1685
+ * @license lucide-react v1.8.0 - ISC
1686
+ *
1687
+ * This source code is licensed under the ISC license.
1688
+ * See the LICENSE file in the root directory of this source tree.
1689
+ */
1690
+ const __iconNode$3 = [
1691
+ ["path", { d: "M12 4v16", key: "1654pz" }],
1692
+ ["path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2", key: "e0r10z" }],
1693
+ ["path", { d: "M9 20h6", key: "s66wpe" }]
1694
+ ];
1695
+ const Type = createLucideIcon("type", __iconNode$3);
1397
1696
  /**
1398
1697
  * @license lucide-react v1.8.0 - ISC
1399
1698
  *
@@ -1416,123 +1715,17 @@ const __iconNode$1 = [
1416
1715
  ["path", { d: "M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11", key: "f3b9sd" }]
1417
1716
  ];
1418
1717
  const Undo2 = createLucideIcon("undo-2", __iconNode$1);
1419
- /**
1420
- * @license lucide-react v1.8.0 - ISC
1421
- *
1422
- * This source code is licensed under the ISC license.
1423
- * See the LICENSE file in the root directory of this source tree.
1424
- */
1425
- const __iconNode = [
1426
- ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
1427
- ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
1428
- ];
1429
- const X = createLucideIcon("x", __iconNode);
1430
- const DEFAULT_TRANSLATIONS = {
1431
- toolbar: {
1432
- undo: "Undo",
1433
- redo: "Redo",
1434
- bold: "Bold (Ctrl+B)",
1435
- italic: "Italic (Ctrl+I)",
1436
- underline: "Underline (Ctrl+U)",
1437
- strikethrough: "Strikethrough",
1438
- alignLeft: "Align Left",
1439
- alignCenter: "Align Center",
1440
- alignRight: "Align Right",
1441
- justify: "Justify",
1442
- numberedList: "Numbered List",
1443
- bulletList: "Bullet List",
1444
- indent: "Indent",
1445
- outdent: "Outdent",
1446
- openHistory: "Open History",
1447
- closeHistory: "Close History"
1448
- },
1449
- history: {
1450
- title: "History",
1451
- subtitle: "Word-style session history (last 100 actions)",
1452
- empty: "No history yet.",
1453
- clearHistory: "Clear History",
1454
- actions: {
1455
- enabledHeadersFooters: "Enabled headers and footers",
1456
- disabledHeadersFooters: "Disabled headers and footers",
1457
- copiedHeaderToAll: "Copied header to all pages",
1458
- copiedFooterToAll: "Copied footer to all pages",
1459
- clearedHeader: "Cleared header",
1460
- clearedFooter: "Cleared footer",
1461
- clearedAllHeaders: "Cleared all headers",
1462
- clearedAllFooters: "Cleared all footers",
1463
- pageCounterSet: "Page counter set to {{value}}",
1464
- boldApplied: "Bold applied",
1465
- italicApplied: "Italic applied",
1466
- underlineApplied: "Underline applied",
1467
- strikethroughApplied: "Strikethrough applied",
1468
- alignedLeft: "Aligned left",
1469
- alignedCenter: "Aligned center",
1470
- alignedRight: "Aligned right",
1471
- justifiedText: "Justified text",
1472
- insertedNumberedList: "Inserted numbered list",
1473
- insertedBulletList: "Inserted bullet list",
1474
- indentedContent: "Indented content",
1475
- outdentedContent: "Outdented content",
1476
- fontChanged: "Font changed to {{value}}",
1477
- fontSizeChanged: "Font size changed to {{value}}pt"
1478
- }
1479
- },
1480
- variables: {
1481
- title: "Variables",
1482
- available: "{{count}} available",
1483
- refreshVariables: "Refresh variables",
1484
- searchPlaceholder: "Search variables...",
1485
- noVariablesFound: "No variables found",
1486
- insertVariable: "Insert variable {{key}}",
1487
- openPanel: "Open Variables",
1488
- closePanel: "Close Variables"
1489
- },
1490
- header: {
1491
- placeholder: "Header"
1492
- },
1493
- footer: {
1494
- placeholder: "Footer"
1495
- },
1496
- sidebar: {
1497
- close: "Close sidebar"
1498
- }
1499
- };
1500
- function deepMerge(target, source) {
1501
- if (typeof target !== "object" || target === null) {
1502
- return source ?? target;
1503
- }
1504
- const result = { ...target };
1505
- for (const key of Object.keys(source)) {
1506
- const sourceVal = source[key];
1507
- const targetVal = target[key];
1508
- if (sourceVal !== void 0 && typeof sourceVal === "object" && sourceVal !== null && !Array.isArray(sourceVal) && typeof targetVal === "object" && targetVal !== null) {
1509
- result[key] = deepMerge(targetVal, sourceVal);
1510
- } else if (sourceVal !== void 0) {
1511
- result[key] = sourceVal;
1512
- }
1513
- }
1514
- return result;
1515
- }
1516
- const TranslationsContext = React.createContext(DEFAULT_TRANSLATIONS);
1517
- function useTranslations() {
1518
- return React.useContext(TranslationsContext);
1519
- }
1520
- function interpolate(template, params) {
1521
- return template.replace(
1522
- /\{\{(\w+)\}\}/g,
1523
- (_, key) => String(params[key] ?? `{{${key}}}`)
1524
- );
1525
- }
1526
- const TranslationsProvider = ({
1527
- translations,
1528
- children
1529
- }) => {
1530
- const merged = React.useMemo(
1531
- () => translations ? deepMerge(DEFAULT_TRANSLATIONS, translations) : DEFAULT_TRANSLATIONS,
1532
- [translations]
1533
- );
1534
- return /* @__PURE__ */ jsxRuntime.jsx(TranslationsContext.Provider, { value: merged, children });
1535
- };
1718
+ /**
1719
+ * @license lucide-react v1.8.0 - ISC
1720
+ *
1721
+ * This source code is licensed under the ISC license.
1722
+ * See the LICENSE file in the root directory of this source tree.
1723
+ */
1724
+ const __iconNode = [
1725
+ ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
1726
+ ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
1727
+ ];
1728
+ const X = createLucideIcon("x", __iconNode);
1536
1729
  const EditorSidebar = ({
1537
1730
  title,
1538
1731
  subtitle,
@@ -1639,7 +1832,7 @@ const HistorySidebar = () => {
1639
1832
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
1640
1833
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
1641
1834
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: `text-xs ${isCurrent ? "font-semibold text-blue-700" : "text-gray-900"}`, children: entry.label }),
1642
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-0.5 text-xs text-gray-400", children: entry.source })
1835
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-0.5 text-xs text-gray-400", children: t.regions[entry.source] ?? entry.source })
1643
1836
  ] }),
1644
1837
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 text-xs text-gray-400", children: formatTimestamp(entry.timestamp) })
1645
1838
  ] })
@@ -1653,13 +1846,15 @@ const HeaderFooterToggle = ({
1653
1846
  enabled,
1654
1847
  onToggle
1655
1848
  }) => {
1849
+ const t = useTranslations();
1656
1850
  return /* @__PURE__ */ jsxRuntime.jsxs(
1657
1851
  "label",
1658
1852
  {
1659
1853
  className: "flex items-center gap-1.5 cursor-pointer select-none",
1660
1854
  "data-testid": "header-footer-toggle",
1661
1855
  children: [
1662
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-gray-600", children: "Headers & Footers" }),
1856
+ /* @__PURE__ */ jsxRuntime.jsx(Rows3, { size: 14, className: "text-gray-500" }),
1857
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-gray-600", children: t.headerFooter.label }),
1663
1858
  /* @__PURE__ */ jsxRuntime.jsx(
1664
1859
  "button",
1665
1860
  {
@@ -1690,6 +1885,12 @@ const HeaderFooterToggle = ({
1690
1885
  }
1691
1886
  );
1692
1887
  };
1888
+ const PAGE_COUNTER_OPTIONS = [
1889
+ { value: "none", label: "None" },
1890
+ { value: "header", label: "Header" },
1891
+ { value: "footer", label: "Footer" },
1892
+ { value: "both", label: "Both" }
1893
+ ];
1693
1894
  const HeaderFooterActions = ({
1694
1895
  activePageId,
1695
1896
  pageCounterMode,
@@ -1701,119 +1902,154 @@ const HeaderFooterActions = ({
1701
1902
  onClearAllHeaders,
1702
1903
  onClearAllFooters
1703
1904
  }) => {
1905
+ const [open, setOpen] = React.useState(false);
1906
+ const containerRef = React.useRef(null);
1704
1907
  const hasActivePage = activePageId !== null;
1705
- const handlePageCounterModeChange = (event) => {
1706
- const nextMode = event.target.value;
1707
- if (nextMode === "none" || nextMode === "header" || nextMode === "footer" || nextMode === "both") {
1708
- onPageCounterModeChange(nextMode);
1709
- }
1908
+ const close = React.useCallback(() => setOpen(false), []);
1909
+ React.useEffect(() => {
1910
+ if (!open) return;
1911
+ const handleClickOutside = (e) => {
1912
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
1913
+ close();
1914
+ }
1915
+ };
1916
+ document.addEventListener("mousedown", handleClickOutside);
1917
+ return () => document.removeEventListener("mousedown", handleClickOutside);
1918
+ }, [open, close]);
1919
+ const handleAction = (action) => {
1920
+ action();
1921
+ close();
1710
1922
  };
1711
- return /* @__PURE__ */ jsxRuntime.jsxs(
1712
- "div",
1713
- {
1714
- className: "flex flex-wrap items-center gap-2 text-xs",
1715
- "data-testid": "header-footer-actions",
1716
- children: [
1717
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-gray-500", htmlFor: "page-counter-mode", children: [
1718
- /* @__PURE__ */ jsxRuntime.jsx(Hash, { size: 13, className: "text-gray-400" }),
1719
- /* @__PURE__ */ jsxRuntime.jsxs(
1720
- "select",
1923
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", "data-testid": "header-footer-actions", children: [
1924
+ /* @__PURE__ */ jsxRuntime.jsxs(
1925
+ "button",
1926
+ {
1927
+ type: "button",
1928
+ onMouseDown: (e) => e.preventDefault(),
1929
+ onClick: () => setOpen(!open),
1930
+ className: `
1931
+ flex h-7 items-center gap-1 rounded px-1.5 text-xs transition-colors
1932
+ ${open ? "bg-blue-50 text-blue-600" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"}
1933
+ `,
1934
+ "data-testid": "header-footer-menu-trigger",
1935
+ "aria-expanded": open,
1936
+ "aria-haspopup": "true",
1937
+ children: [
1938
+ /* @__PURE__ */ jsxRuntime.jsx(Settings, { size: 14 }),
1939
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, { size: 12 })
1940
+ ]
1941
+ }
1942
+ ),
1943
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
1944
+ "div",
1945
+ {
1946
+ className: "absolute left-0 top-full mt-1 z-50 w-56 rounded-lg border border-gray-200 bg-white py-1 shadow-lg",
1947
+ role: "menu",
1948
+ "data-testid": "header-footer-menu",
1949
+ children: [
1950
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "Page counter" }),
1951
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pb-2 grid grid-cols-2 gap-1", "data-testid": "page-counter-mode", children: PAGE_COUNTER_OPTIONS.map(({ value, label }) => /* @__PURE__ */ jsxRuntime.jsx(
1952
+ "button",
1721
1953
  {
1722
- id: "page-counter-mode",
1723
- className: "h-6 rounded border border-gray-200 bg-white px-1.5 text-xs text-gray-600\n focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-400",
1724
- "data-testid": "page-counter-mode",
1725
- value: pageCounterMode,
1726
- onChange: handlePageCounterModeChange,
1727
- children: [
1728
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "none", children: "None" }),
1729
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "header", children: "Header" }),
1730
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "footer", children: "Footer" }),
1731
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "both", children: "Both" })
1732
- ]
1954
+ type: "button",
1955
+ role: "menuitemradio",
1956
+ "aria-checked": pageCounterMode === value,
1957
+ onMouseDown: (e) => e.preventDefault(),
1958
+ onClick: () => onPageCounterModeChange(value),
1959
+ className: `
1960
+ rounded px-2 py-1 text-xs text-left transition-colors
1961
+ ${pageCounterMode === value ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-600 hover:bg-gray-100"}
1962
+ `,
1963
+ "data-testid": `page-counter-${value}`,
1964
+ children: label
1965
+ },
1966
+ value
1967
+ )) }),
1968
+ /* @__PURE__ */ jsxRuntime.jsx(MenuDivider, {}),
1969
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "Header" }),
1970
+ /* @__PURE__ */ jsxRuntime.jsx(
1971
+ MenuItem,
1972
+ {
1973
+ icon: /* @__PURE__ */ jsxRuntime.jsx(CopyPlus, { size: 14 }),
1974
+ label: "Copy to all pages",
1975
+ onClick: () => handleAction(onCopyHeaderToAll),
1976
+ disabled: !hasActivePage,
1977
+ testId: "copy-header-all"
1978
+ }
1979
+ ),
1980
+ /* @__PURE__ */ jsxRuntime.jsx(
1981
+ MenuItem,
1982
+ {
1983
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Eraser, { size: 14 }),
1984
+ label: "Clear this page",
1985
+ onClick: () => handleAction(onClearHeader),
1986
+ disabled: !hasActivePage,
1987
+ testId: "clear-header"
1988
+ }
1989
+ ),
1990
+ /* @__PURE__ */ jsxRuntime.jsx(
1991
+ MenuItem,
1992
+ {
1993
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Trash2, { size: 14 }),
1994
+ label: "Clear all",
1995
+ onClick: () => handleAction(onClearAllHeaders),
1996
+ testId: "clear-all-headers"
1997
+ }
1998
+ ),
1999
+ /* @__PURE__ */ jsxRuntime.jsx(MenuDivider, {}),
2000
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "Footer" }),
2001
+ /* @__PURE__ */ jsxRuntime.jsx(
2002
+ MenuItem,
2003
+ {
2004
+ icon: /* @__PURE__ */ jsxRuntime.jsx(CopyPlus, { size: 14 }),
2005
+ label: "Copy to all pages",
2006
+ onClick: () => handleAction(onCopyFooterToAll),
2007
+ disabled: !hasActivePage,
2008
+ testId: "copy-footer-all"
2009
+ }
2010
+ ),
2011
+ /* @__PURE__ */ jsxRuntime.jsx(
2012
+ MenuItem,
2013
+ {
2014
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Eraser, { size: 14 }),
2015
+ label: "Clear this page",
2016
+ onClick: () => handleAction(onClearFooter),
2017
+ disabled: !hasActivePage,
2018
+ testId: "clear-footer"
2019
+ }
2020
+ ),
2021
+ /* @__PURE__ */ jsxRuntime.jsx(
2022
+ MenuItem,
2023
+ {
2024
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Trash2, { size: 14 }),
2025
+ label: "Clear all",
2026
+ onClick: () => handleAction(onClearAllFooters),
2027
+ testId: "clear-all-footers"
1733
2028
  }
1734
2029
  )
1735
- ] }),
1736
- /* @__PURE__ */ jsxRuntime.jsx(ActionDivider, {}),
1737
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 font-medium", children: "Header" }),
1738
- /* @__PURE__ */ jsxRuntime.jsx(
1739
- ActionIconButton,
1740
- {
1741
- title: "Copy header to all pages",
1742
- icon: /* @__PURE__ */ jsxRuntime.jsx(CopyPlus, { size: 13 }),
1743
- onClick: onCopyHeaderToAll,
1744
- disabled: !hasActivePage,
1745
- testId: "copy-header-all"
1746
- }
1747
- ),
1748
- /* @__PURE__ */ jsxRuntime.jsx(
1749
- ActionIconButton,
1750
- {
1751
- title: "Clear header on this page",
1752
- icon: /* @__PURE__ */ jsxRuntime.jsx(Eraser, { size: 13 }),
1753
- onClick: onClearHeader,
1754
- disabled: !hasActivePage,
1755
- testId: "clear-header"
1756
- }
1757
- ),
1758
- /* @__PURE__ */ jsxRuntime.jsx(
1759
- ActionIconButton,
1760
- {
1761
- title: "Clear all headers",
1762
- icon: /* @__PURE__ */ jsxRuntime.jsx(Trash2, { size: 13 }),
1763
- onClick: onClearAllHeaders,
1764
- testId: "clear-all-headers"
1765
- }
1766
- ),
1767
- /* @__PURE__ */ jsxRuntime.jsx(ActionDivider, {}),
1768
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 font-medium", children: "Footer" }),
1769
- /* @__PURE__ */ jsxRuntime.jsx(
1770
- ActionIconButton,
1771
- {
1772
- title: "Copy footer to all pages",
1773
- icon: /* @__PURE__ */ jsxRuntime.jsx(CopyPlus, { size: 13 }),
1774
- onClick: onCopyFooterToAll,
1775
- disabled: !hasActivePage,
1776
- testId: "copy-footer-all"
1777
- }
1778
- ),
1779
- /* @__PURE__ */ jsxRuntime.jsx(
1780
- ActionIconButton,
1781
- {
1782
- title: "Clear footer on this page",
1783
- icon: /* @__PURE__ */ jsxRuntime.jsx(Eraser, { size: 13 }),
1784
- onClick: onClearFooter,
1785
- disabled: !hasActivePage,
1786
- testId: "clear-footer"
1787
- }
1788
- ),
1789
- /* @__PURE__ */ jsxRuntime.jsx(
1790
- ActionIconButton,
1791
- {
1792
- title: "Clear all footers",
1793
- icon: /* @__PURE__ */ jsxRuntime.jsx(Trash2, { size: 13 }),
1794
- onClick: onClearAllFooters,
1795
- testId: "clear-all-footers"
1796
- }
1797
- )
1798
- ]
1799
- }
1800
- );
2030
+ ]
2031
+ }
2032
+ )
2033
+ ] });
1801
2034
  };
1802
- const ActionIconButton = ({ title, icon, onClick, disabled, testId }) => /* @__PURE__ */ jsxRuntime.jsx(
2035
+ const SectionLabel = ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400", children });
2036
+ const MenuItem = ({ icon, label, onClick, disabled, testId }) => /* @__PURE__ */ jsxRuntime.jsxs(
1803
2037
  "button",
1804
2038
  {
1805
2039
  type: "button",
1806
- title,
1807
- "aria-label": title,
2040
+ role: "menuitem",
1808
2041
  onMouseDown: (e) => e.preventDefault(),
1809
2042
  onClick,
1810
2043
  disabled,
1811
- className: "flex h-6 w-6 items-center justify-center rounded text-gray-500 transition-colors\n hover:bg-gray-200/60 hover:text-gray-700\n disabled:cursor-not-allowed disabled:text-gray-300",
2044
+ className: "flex w-full items-center gap-2 px-3 py-1.5 text-xs text-gray-700 transition-colors\n hover:bg-gray-50 disabled:cursor-not-allowed disabled:text-gray-300",
1812
2045
  "data-testid": testId,
1813
- children: icon
2046
+ children: [
2047
+ icon,
2048
+ label
2049
+ ]
1814
2050
  }
1815
2051
  );
1816
- const ActionDivider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-0.5 h-4 w-px bg-gray-300/60" });
2052
+ const MenuDivider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-1 h-px bg-gray-100" });
1817
2053
  function resolveExtensions(extensions) {
1818
2054
  const resolved = {
1819
2055
  nodes: [],
@@ -1888,10 +2124,10 @@ function useExtensionContext(getDocument, getActiveEditor) {
1888
2124
  }), [getDocument, getActiveEditor, stateRef]);
1889
2125
  }
1890
2126
  const SUPPORTED_FONTS = [
2127
+ "Calibri",
1891
2128
  "Inter",
1892
2129
  "Times New Roman",
1893
2130
  "Arial",
1894
- "Calibri",
1895
2131
  "Georgia",
1896
2132
  "Courier New"
1897
2133
  ];
@@ -2155,87 +2391,107 @@ const Toolbar = () => {
2155
2391
  },
2156
2392
  [applyToBodyEditors, runToolbarAction, t.history.actions.fontSizeChanged]
2157
2393
  );
2158
- return /* @__PURE__ */ jsxRuntime.jsx(
2394
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2159
2395
  "div",
2160
2396
  {
2161
2397
  className: "lex4-toolbar sticky top-0 z-10 bg-white border-b border-gray-200",
2162
2398
  "data-testid": "toolbar",
2163
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 px-2 py-1.5", children: [
2164
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "history-controls", children: [
2165
- /* @__PURE__ */ jsxRuntime.jsx(
2399
+ children: [
2400
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 px-2 py-1.5", children: [
2401
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "history-controls", children: [
2402
+ /* @__PURE__ */ jsxRuntime.jsx(
2403
+ ToolbarIconButton,
2404
+ {
2405
+ title: t.toolbar.undo,
2406
+ testId: "btn-undo",
2407
+ disabled: !canUndo,
2408
+ onClick: undo,
2409
+ children: /* @__PURE__ */ jsxRuntime.jsx(Undo2, { size: 15 })
2410
+ }
2411
+ ),
2412
+ /* @__PURE__ */ jsxRuntime.jsx(
2413
+ ToolbarIconButton,
2414
+ {
2415
+ title: t.toolbar.redo,
2416
+ testId: "btn-redo",
2417
+ disabled: !canRedo,
2418
+ onClick: redo,
2419
+ children: /* @__PURE__ */ jsxRuntime.jsx(Redo2, { size: 15 })
2420
+ }
2421
+ )
2422
+ ] }),
2423
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2424
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
2425
+ /* @__PURE__ */ jsxRuntime.jsx(Type, { size: 14, className: "text-gray-500" }),
2426
+ /* @__PURE__ */ jsxRuntime.jsx(
2427
+ "select",
2428
+ {
2429
+ className: "h-7 rounded border border-gray-200 bg-white px-2 text-xs text-gray-700\n focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-400",
2430
+ "data-testid": "font-selector",
2431
+ defaultValue: "Calibri",
2432
+ onChange: handleFontChange,
2433
+ children: SUPPORTED_FONTS.map((font) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: font, style: { fontFamily: font }, children: font }, font))
2434
+ }
2435
+ )
2436
+ ] }),
2437
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
2438
+ /* @__PURE__ */ jsxRuntime.jsx(ALargeSmall, { size: 14, className: "text-gray-500" }),
2439
+ /* @__PURE__ */ jsxRuntime.jsx(
2440
+ "select",
2441
+ {
2442
+ className: "h-7 w-16 rounded border border-gray-200 bg-white px-1 text-xs text-gray-700\n focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-400",
2443
+ "data-testid": "font-size-selector",
2444
+ defaultValue: "12",
2445
+ onChange: handleFontSizeChange,
2446
+ children: SUPPORTED_FONT_SIZES.map((size) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: size, children: size }, size))
2447
+ }
2448
+ )
2449
+ ] }),
2450
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2451
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "format-group", children: [
2452
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bold, testId: "btn-bold", onClick: handleBold, children: /* @__PURE__ */ jsxRuntime.jsx(Bold, { size: 15 }) }),
2453
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.italic, testId: "btn-italic", onClick: handleItalic, children: /* @__PURE__ */ jsxRuntime.jsx(Italic, { size: 15 }) }),
2454
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.underline, testId: "btn-underline", onClick: handleUnderline, children: /* @__PURE__ */ jsxRuntime.jsx(Underline, { size: 15 }) }),
2455
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.strikethrough, testId: "btn-strike", onClick: handleStrikethrough, children: /* @__PURE__ */ jsxRuntime.jsx(Strikethrough, { size: 15 }) })
2456
+ ] }),
2457
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2458
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "align-group", children: [
2459
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignLeft, testId: "btn-align-left", onClick: handleAlignLeft, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignStart, { size: 15 }) }),
2460
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignCenter, testId: "btn-align-center", onClick: handleAlignCenter, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignCenter, { size: 15 }) }),
2461
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignRight, testId: "btn-align-right", onClick: handleAlignRight, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignEnd, { size: 15 }) }),
2462
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.justify, testId: "btn-align-justify", onClick: handleAlignJustify, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignJustify, { size: 15 }) })
2463
+ ] }),
2464
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2465
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "list-group", children: [
2466
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.numberedList, testId: "btn-list-number", onClick: handleListNumber, children: /* @__PURE__ */ jsxRuntime.jsx(ListOrdered, { size: 15 }) }),
2467
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bulletList, testId: "btn-list-bullet", onClick: handleListBullet, children: /* @__PURE__ */ jsxRuntime.jsx(List, { size: 15 }) }),
2468
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.indent, testId: "btn-indent", onClick: handleIndent, children: /* @__PURE__ */ jsxRuntime.jsx(ListIndentIncrease, { size: 15 }) }),
2469
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.outdent, testId: "btn-outdent", onClick: handleOutdent, children: /* @__PURE__ */ jsxRuntime.jsx(ListIndentDecrease, { size: 15 }) })
2470
+ ] }),
2471
+ toolbarItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2472
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2473
+ toolbarItems.map((ToolbarItem, idx) => /* @__PURE__ */ jsxRuntime.jsx(ToolbarItem, {}, idx))
2474
+ ] }),
2475
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
2166
2476
  ToolbarIconButton,
2167
2477
  {
2168
- title: t.toolbar.undo,
2169
- testId: "btn-undo",
2170
- disabled: !canUndo,
2171
- onClick: undo,
2172
- children: /* @__PURE__ */ jsxRuntime.jsx(Undo2, { size: 15 })
2478
+ title: historySidebarOpen ? t.toolbar.closeHistory : t.toolbar.openHistory,
2479
+ testId: "toggle-history-sidebar",
2480
+ active: historySidebarOpen,
2481
+ onClick: () => setHistorySidebarOpen(!historySidebarOpen),
2482
+ children: /* @__PURE__ */ jsxRuntime.jsx(PanelRight, { size: 15 })
2173
2483
  }
2174
- ),
2484
+ ) })
2485
+ ] }),
2486
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 px-2 py-1 border-t border-gray-100", children: [
2175
2487
  /* @__PURE__ */ jsxRuntime.jsx(
2176
- ToolbarIconButton,
2488
+ HeaderFooterToggle,
2177
2489
  {
2178
- title: t.toolbar.redo,
2179
- testId: "btn-redo",
2180
- disabled: !canRedo,
2181
- onClick: redo,
2182
- children: /* @__PURE__ */ jsxRuntime.jsx(Redo2, { size: 15 })
2490
+ enabled: document2.headerFooterEnabled,
2491
+ onToggle: handleToggle
2183
2492
  }
2184
- )
2185
- ] }),
2186
- /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2187
- /* @__PURE__ */ jsxRuntime.jsx(
2188
- "select",
2189
- {
2190
- className: "h-7 rounded border border-gray-200 bg-white px-2 text-xs text-gray-700\n focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-400",
2191
- "data-testid": "font-selector",
2192
- defaultValue: "Inter",
2193
- onChange: handleFontChange,
2194
- children: SUPPORTED_FONTS.map((font) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: font, style: { fontFamily: font }, children: font }, font))
2195
- }
2196
- ),
2197
- /* @__PURE__ */ jsxRuntime.jsx(
2198
- "select",
2199
- {
2200
- className: "h-7 w-16 rounded border border-gray-200 bg-white px-1 text-xs text-gray-700\n focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-400",
2201
- "data-testid": "font-size-selector",
2202
- defaultValue: "12",
2203
- onChange: handleFontSizeChange,
2204
- children: SUPPORTED_FONT_SIZES.map((size) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: size, children: size }, size))
2205
- }
2206
- ),
2207
- /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2208
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "format-group", children: [
2209
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bold, testId: "btn-bold", onClick: handleBold, children: /* @__PURE__ */ jsxRuntime.jsx(Bold, { size: 15 }) }),
2210
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.italic, testId: "btn-italic", onClick: handleItalic, children: /* @__PURE__ */ jsxRuntime.jsx(Italic, { size: 15 }) }),
2211
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.underline, testId: "btn-underline", onClick: handleUnderline, children: /* @__PURE__ */ jsxRuntime.jsx(Underline, { size: 15 }) }),
2212
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.strikethrough, testId: "btn-strike", onClick: handleStrikethrough, children: /* @__PURE__ */ jsxRuntime.jsx(Strikethrough, { size: 15 }) })
2213
- ] }),
2214
- /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2215
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "align-group", children: [
2216
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignLeft, testId: "btn-align-left", onClick: handleAlignLeft, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignStart, { size: 15 }) }),
2217
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignCenter, testId: "btn-align-center", onClick: handleAlignCenter, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignCenter, { size: 15 }) }),
2218
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignRight, testId: "btn-align-right", onClick: handleAlignRight, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignEnd, { size: 15 }) }),
2219
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.justify, testId: "btn-align-justify", onClick: handleAlignJustify, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignJustify, { size: 15 }) })
2220
- ] }),
2221
- /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2222
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "list-group", children: [
2223
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.numberedList, testId: "btn-list-number", onClick: handleListNumber, children: /* @__PURE__ */ jsxRuntime.jsx(ListOrdered, { size: 15 }) }),
2224
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bulletList, testId: "btn-list-bullet", onClick: handleListBullet, children: /* @__PURE__ */ jsxRuntime.jsx(List, { size: 15 }) }),
2225
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.indent, testId: "btn-indent", onClick: handleIndent, children: /* @__PURE__ */ jsxRuntime.jsx(ListIndentIncrease, { size: 15 }) }),
2226
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.outdent, testId: "btn-outdent", onClick: handleOutdent, children: /* @__PURE__ */ jsxRuntime.jsx(ListIndentDecrease, { size: 15 }) })
2227
- ] }),
2228
- /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2229
- /* @__PURE__ */ jsxRuntime.jsx(
2230
- HeaderFooterToggle,
2231
- {
2232
- enabled: document2.headerFooterEnabled,
2233
- onToggle: handleToggle
2234
- }
2235
- ),
2236
- document2.headerFooterEnabled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2237
- /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2238
- /* @__PURE__ */ jsxRuntime.jsx(
2493
+ ),
2494
+ document2.headerFooterEnabled && /* @__PURE__ */ jsxRuntime.jsx(
2239
2495
  HeaderFooterActions,
2240
2496
  {
2241
2497
  activePageId,
@@ -2249,22 +2505,8 @@ const Toolbar = () => {
2249
2505
  onClearAllFooters: handleClearAllFooters
2250
2506
  }
2251
2507
  )
2252
- ] }),
2253
- toolbarItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2254
- /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2255
- toolbarItems.map((ToolbarItem, idx) => /* @__PURE__ */ jsxRuntime.jsx(ToolbarItem, {}, idx))
2256
- ] }),
2257
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
2258
- ToolbarIconButton,
2259
- {
2260
- title: historySidebarOpen ? t.toolbar.closeHistory : t.toolbar.openHistory,
2261
- testId: "toggle-history-sidebar",
2262
- active: historySidebarOpen,
2263
- onClick: () => setHistorySidebarOpen(!historySidebarOpen),
2264
- children: /* @__PURE__ */ jsxRuntime.jsx(PanelRight, { size: 15 })
2265
- }
2266
- ) })
2267
- ] })
2508
+ ] })
2509
+ ]
2268
2510
  }
2269
2511
  );
2270
2512
  };
@@ -2335,6 +2577,19 @@ function mergeEditorStates(stateA, stateB) {
2335
2577
  if (all.length === 0) return null;
2336
2578
  return createEditorStateFromNodes(all);
2337
2579
  }
2580
+ function splitBlockNode(block, childOffset) {
2581
+ const children = block.children ?? [];
2582
+ if (childOffset <= 0) return [null, structuredClone(block)];
2583
+ if (childOffset >= children.length) return [structuredClone(block), null];
2584
+ const before = children.slice(0, childOffset);
2585
+ const after = children.slice(childOffset);
2586
+ const makeCopy = (kids) => {
2587
+ const copy = structuredClone(block);
2588
+ copy.children = kids;
2589
+ return copy;
2590
+ };
2591
+ return [makeCopy(before), makeCopy(after)];
2592
+ }
2338
2593
  function usePagination(document2, dispatch) {
2339
2594
  const reflowingRef = React.useRef(false);
2340
2595
  const pendingReflowRef = React.useRef(false);
@@ -2369,7 +2624,61 @@ function usePagination(document2, dispatch) {
2369
2624
  const footerH = document2.headerFooterEnabled ? page.footerHeight : 0;
2370
2625
  const bodyHeight = computeBodyHeight(headerH, footerH);
2371
2626
  const nodes = getTopLevelNodes(page.bodyState);
2372
- if (nodes.length <= 1) return;
2627
+ if (nodes.length <= 1) {
2628
+ if (nodes.length === 1) {
2629
+ const singleNode = nodes[0];
2630
+ const children = singleNode.children;
2631
+ if (children && children.length > 1) {
2632
+ const estimatedLineHeight = 24;
2633
+ const maxChildren = Math.max(1, Math.floor(bodyHeight / estimatedLineHeight));
2634
+ if (maxChildren < children.length) {
2635
+ const [keepBlock, overflowBlock] = splitBlockNode(singleNode, maxChildren);
2636
+ if (keepBlock && overflowBlock) {
2637
+ const keepState2 = createEditorStateFromNodes([keepBlock]);
2638
+ const overflowState2 = createEditorStateFromNodes([overflowBlock]);
2639
+ const nextPage2 = pages[pageIndex + 1];
2640
+ if (nextPage2) {
2641
+ const mergedBody = mergeEditorStates(overflowState2, nextPage2.bodyState);
2642
+ dispatch({
2643
+ type: "SET_DOCUMENT",
2644
+ document: {
2645
+ ...document2,
2646
+ pages: pages.map((p, i) => {
2647
+ if (i === pageIndex) return { ...p, bodyState: keepState2 };
2648
+ if (i === pageIndex + 1) return { ...p, bodyState: mergedBody };
2649
+ return p;
2650
+ })
2651
+ }
2652
+ });
2653
+ } else {
2654
+ const newPage = {
2655
+ ...createPageFromTemplate({
2656
+ headerState: document2.defaultHeaderState,
2657
+ footerState: document2.defaultFooterState,
2658
+ headerHeight: document2.defaultHeaderHeight,
2659
+ footerHeight: document2.defaultFooterHeight
2660
+ }),
2661
+ bodyState: overflowState2
2662
+ };
2663
+ dispatch({
2664
+ type: "SET_DOCUMENT",
2665
+ document: {
2666
+ ...document2,
2667
+ pages: [
2668
+ ...pages.map(
2669
+ (p, i) => i === pageIndex ? { ...p, bodyState: keepState2 } : p
2670
+ ),
2671
+ newPage
2672
+ ]
2673
+ }
2674
+ });
2675
+ }
2676
+ }
2677
+ }
2678
+ }
2679
+ }
2680
+ return;
2681
+ }
2373
2682
  const fitCount = estimateNodesFitting(page.bodyState, bodyHeight);
2374
2683
  if (fitCount >= nodes.length) return;
2375
2684
  const [keepState, overflowState] = splitEditorState(page.bodyState, fitCount);
@@ -2533,7 +2842,7 @@ function usePagination(document2, dispatch) {
2533
2842
  }
2534
2843
  const lexicalTheme = {
2535
2844
  root: "lex4-root outline-none",
2536
- paragraph: "lex4-paragraph mb-1",
2845
+ paragraph: "lex4-paragraph text-justify",
2537
2846
  heading: {
2538
2847
  h1: "text-3xl font-bold mb-2",
2539
2848
  h2: "text-2xl font-bold mb-2",
@@ -2739,6 +3048,213 @@ const ActiveEditorPlugin = ({
2739
3048
  }, [editor, onFocus, pageId, region, setActiveEditor, setActivePageId]);
2740
3049
  return null;
2741
3050
  };
3051
+ function findMidBlockSplitPoint(blockElement, availableHeight) {
3052
+ const blockRect = blockElement.getBoundingClientRect();
3053
+ const maxBottom = blockRect.top + availableHeight;
3054
+ const textNodes = [];
3055
+ const walker = document.createTreeWalker(blockElement, NodeFilter.SHOW_TEXT);
3056
+ let walkNode;
3057
+ while (walkNode = walker.nextNode()) {
3058
+ if (walkNode.length > 0) {
3059
+ textNodes.push(walkNode);
3060
+ }
3061
+ }
3062
+ if (textNodes.length === 0) return null;
3063
+ const range = document.createRange();
3064
+ for (const textNode of textNodes) {
3065
+ range.selectNodeContents(textNode);
3066
+ const nodeRect = range.getBoundingClientRect();
3067
+ if (nodeRect.bottom <= maxBottom) {
3068
+ continue;
3069
+ }
3070
+ if (nodeRect.top >= maxBottom) {
3071
+ return backupToWordBoundary(textNode, 0);
3072
+ }
3073
+ let low = 0;
3074
+ let high = textNode.length;
3075
+ while (low < high) {
3076
+ const mid = Math.floor((low + high) / 2);
3077
+ range.setStart(textNode, 0);
3078
+ range.setEnd(textNode, mid + 1);
3079
+ const testRect = range.getBoundingClientRect();
3080
+ if (testRect.bottom > maxBottom) {
3081
+ high = mid;
3082
+ } else {
3083
+ low = mid + 1;
3084
+ }
3085
+ }
3086
+ return backupToWordBoundary(textNode, low);
3087
+ }
3088
+ return null;
3089
+ }
3090
+ function backupToWordBoundary(textNode, offset) {
3091
+ const text = textNode.textContent || "";
3092
+ if (offset <= 0 || offset >= text.length) {
3093
+ if (offset > 0 && offset < text.length) {
3094
+ return { textNode, offset };
3095
+ }
3096
+ if (offset > 0) return { textNode, offset };
3097
+ return null;
3098
+ }
3099
+ let adjusted = offset;
3100
+ while (adjusted > 0 && !/\s/.test(text[adjusted - 1])) {
3101
+ adjusted--;
3102
+ }
3103
+ if (adjusted <= 0) {
3104
+ adjusted = offset;
3105
+ }
3106
+ return { textNode, offset: adjusted };
3107
+ }
3108
+ function serializeNodeTree$1(node) {
3109
+ const json = node.exportJSON();
3110
+ if (lexical.$isElementNode(node)) {
3111
+ json.children = node.getChildren().map(serializeNodeTree$1);
3112
+ }
3113
+ return json;
3114
+ }
3115
+ function findListSplitIndex(listElement, availableHeight) {
3116
+ const listRect = listElement.getBoundingClientRect();
3117
+ const maxBottom = listRect.top + availableHeight;
3118
+ const children = Array.from(listElement.children);
3119
+ for (let i = 0; i < children.length; i++) {
3120
+ const child = children[i];
3121
+ const childRect = child.getBoundingClientRect();
3122
+ if (childRect.bottom > maxBottom && i > 0) {
3123
+ return i;
3124
+ }
3125
+ }
3126
+ return -1;
3127
+ }
3128
+ function performMidBlockSplit(rootElement, availableHeight, overflowBlockIndex) {
3129
+ const root = lexical.$getRoot();
3130
+ const allChildren = root.getChildren();
3131
+ const blockNode = allChildren[overflowBlockIndex];
3132
+ if (!blockNode || !lexical.$isElementNode(blockNode)) {
3133
+ debugWarn("overflow", "target node is not an ElementNode");
3134
+ return null;
3135
+ }
3136
+ const blockElements = Array.from(rootElement.children);
3137
+ const blockElement = blockElements[overflowBlockIndex];
3138
+ if (!blockElement) {
3139
+ debugWarn("overflow", "no DOM element for block index");
3140
+ return null;
3141
+ }
3142
+ const blockTop = blockElement.offsetTop;
3143
+ const heightForBlock = availableHeight - blockTop;
3144
+ if (heightForBlock <= 0) {
3145
+ debugWarn("overflow", "no space for block");
3146
+ return null;
3147
+ }
3148
+ if (list.$isListNode(blockNode)) {
3149
+ return splitListNode(blockNode, blockElement, heightForBlock);
3150
+ }
3151
+ return splitParagraphNode(blockNode, blockElement, heightForBlock);
3152
+ }
3153
+ function splitListNode(listNode, listElement, availableHeight) {
3154
+ const splitIndex = findListSplitIndex(listElement, availableHeight);
3155
+ if (splitIndex <= 0) {
3156
+ debug("overflow", "cannot split list — first item exceeds height");
3157
+ return null;
3158
+ }
3159
+ const [, overflowList] = lexical.$splitNode(listNode, splitIndex);
3160
+ const overflowNodes = [];
3161
+ const toRemove = [];
3162
+ overflowNodes.push(serializeNodeTree$1(overflowList));
3163
+ toRemove.push(overflowList);
3164
+ let nextSibling = overflowList.getNextSibling();
3165
+ while (nextSibling) {
3166
+ overflowNodes.push(serializeNodeTree$1(nextSibling));
3167
+ toRemove.push(nextSibling);
3168
+ nextSibling = nextSibling.getNextSibling();
3169
+ }
3170
+ for (const node of toRemove) {
3171
+ node.remove();
3172
+ }
3173
+ debug("overflow", `split list at item ${splitIndex}, extracted ${overflowNodes.length} overflow nodes`);
3174
+ return overflowNodes;
3175
+ }
3176
+ function splitParagraphNode(blockNode, blockElement, availableHeight) {
3177
+ const splitPoint = findMidBlockSplitPoint(blockElement, availableHeight);
3178
+ if (!splitPoint) {
3179
+ debug("overflow", "no valid split point found in paragraph");
3180
+ return null;
3181
+ }
3182
+ const lexicalNode = lexical.$getNearestNodeFromDOMNode(splitPoint.textNode);
3183
+ if (!lexicalNode || !lexical.$isTextNode(lexicalNode)) {
3184
+ debugWarn("overflow", "could not map DOM text node to Lexical TextNode");
3185
+ return null;
3186
+ }
3187
+ const [, afterText] = lexicalNode.splitText(splitPoint.offset);
3188
+ if (!afterText) {
3189
+ debugWarn("overflow", "splitText returned no second half");
3190
+ return null;
3191
+ }
3192
+ const parent = afterText.getParent();
3193
+ if (!parent || !lexical.$isElementNode(parent)) {
3194
+ debugWarn("overflow", "split text has no element parent");
3195
+ return null;
3196
+ }
3197
+ let directChild = afterText;
3198
+ let directChildParent = parent;
3199
+ while (directChildParent && directChildParent.getKey() !== blockNode.getKey()) {
3200
+ directChild = directChildParent;
3201
+ const next = directChildParent.getParent();
3202
+ if (!next || !lexical.$isElementNode(next)) {
3203
+ debugWarn("overflow", "could not find block node in parent chain");
3204
+ return null;
3205
+ }
3206
+ directChildParent = next;
3207
+ }
3208
+ const splitChildIndex = blockNode.getChildren().indexOf(directChild);
3209
+ if (splitChildIndex <= 0) {
3210
+ debug("overflow", "split index is 0 or not found — nothing to keep on this page");
3211
+ return null;
3212
+ }
3213
+ if (directChild !== afterText) {
3214
+ let current = parent;
3215
+ let childToSplitAt = afterText;
3216
+ while (current.getKey() !== blockNode.getKey()) {
3217
+ const childIdx = current.getChildren().indexOf(childToSplitAt);
3218
+ if (childIdx > 0) {
3219
+ lexical.$splitNode(current, childIdx);
3220
+ }
3221
+ childToSplitAt = current;
3222
+ const next = current.getParent();
3223
+ if (!next || !lexical.$isElementNode(next)) break;
3224
+ current = next;
3225
+ }
3226
+ }
3227
+ let finalSplitChild = afterText;
3228
+ let p = afterText.getParent();
3229
+ while (p && p.getKey() !== blockNode.getKey()) {
3230
+ finalSplitChild = p;
3231
+ p = p.getParent();
3232
+ }
3233
+ const finalSplitIndex = blockNode.getChildren().indexOf(finalSplitChild);
3234
+ if (finalSplitIndex <= 0) {
3235
+ debug("overflow", "final split index is 0 or not found — nothing to keep on this page");
3236
+ return null;
3237
+ }
3238
+ const [, overflowBlock] = lexical.$splitNode(blockNode, finalSplitIndex);
3239
+ const overflowNodes = [];
3240
+ const toRemove = [];
3241
+ overflowNodes.push(serializeNodeTree$1(overflowBlock));
3242
+ toRemove.push(overflowBlock);
3243
+ let nextSibling = overflowBlock.getNextSibling();
3244
+ while (nextSibling) {
3245
+ overflowNodes.push(serializeNodeTree$1(nextSibling));
3246
+ toRemove.push(nextSibling);
3247
+ nextSibling = nextSibling.getNextSibling();
3248
+ }
3249
+ for (const node of toRemove) {
3250
+ node.remove();
3251
+ }
3252
+ debug(
3253
+ "overflow",
3254
+ `split paragraph at offset ${splitPoint.offset}, extracted ${overflowNodes.length} overflow nodes`
3255
+ );
3256
+ return overflowNodes;
3257
+ }
2742
3258
  function serializeNodeTree(node) {
2743
3259
  const json = node.exportJSON();
2744
3260
  if (lexical.$isElementNode(node)) {
@@ -2767,18 +3283,51 @@ const OverflowPlugin = ({
2767
3283
  const children = Array.from(rootElement.children);
2768
3284
  if (children.length === 0) return;
2769
3285
  if (children.length <= 1) {
2770
- debug("overflow", `single block overflows (content=${contentHeight}px > available=${availableHeight}px) — cannot split`);
3286
+ debug("overflow", `single block overflows (content=${contentHeight}px > available=${availableHeight}px) — attempting mid-block split`);
3287
+ processingRef.current = true;
3288
+ editor.update(
3289
+ () => {
3290
+ const overflowNodes = performMidBlockSplit(rootElement, availableHeight, 0);
3291
+ if (!overflowNodes || overflowNodes.length === 0) {
3292
+ debug("overflow", "mid-block split not possible for single block");
3293
+ processingRef.current = false;
3294
+ return;
3295
+ }
3296
+ const overflowState = {
3297
+ root: {
3298
+ children: overflowNodes,
3299
+ direction: null,
3300
+ format: "",
3301
+ indent: 0,
3302
+ type: "root",
3303
+ version: 1
3304
+ }
3305
+ };
3306
+ debug("overflow", `mid-block split extracted ${overflowNodes.length} overflow nodes from single block`);
3307
+ setTimeout(() => {
3308
+ onOverflowRef.current(overflowState, cause);
3309
+ processingRef.current = false;
3310
+ }, 0);
3311
+ },
3312
+ { tag: "overflow-split" }
3313
+ );
2771
3314
  return;
2772
3315
  }
2773
3316
  processingRef.current = true;
2774
3317
  debug("overflow", `OVERFLOW detected: content=${contentHeight}px available=${availableHeight}px children=${children.length}`);
2775
3318
  let splitIndex = children.length;
3319
+ let firstBlockOverflows = false;
2776
3320
  for (let i = 0; i < children.length; i++) {
2777
3321
  const child = children[i];
2778
3322
  const childBottom = child.offsetTop + child.offsetHeight;
2779
- if (childBottom > availableHeight && i > 0) {
2780
- splitIndex = i;
2781
- debug("overflow", `split at index ${i} (childBottom=${childBottom}px > ${availableHeight}px)`);
3323
+ if (childBottom > availableHeight) {
3324
+ if (i === 0) {
3325
+ firstBlockOverflows = true;
3326
+ splitIndex = 1;
3327
+ } else {
3328
+ splitIndex = i;
3329
+ }
3330
+ debug("overflow", `split at index ${i === 0 ? "0 (mid-block)" : i} (childBottom=${childBottom}px > ${availableHeight}px)`);
2782
3331
  break;
2783
3332
  }
2784
3333
  }
@@ -2796,15 +3345,37 @@ const OverflowPlugin = ({
2796
3345
  processingRef.current = false;
2797
3346
  return;
2798
3347
  }
2799
- const overflowNodes = [];
2800
- const toRemove = allChildren.slice(splitIndex);
2801
- for (const node of toRemove) {
2802
- overflowNodes.push(serializeNodeTree(node));
3348
+ let overflowNodes = [];
3349
+ if (firstBlockOverflows) {
3350
+ const midBlockOverflow = performMidBlockSplit(rootElement, availableHeight, 0);
3351
+ if (midBlockOverflow && midBlockOverflow.length > 0) {
3352
+ overflowNodes.push(...midBlockOverflow);
3353
+ debug("overflow", `mid-block split on first block extracted ${midBlockOverflow.length} nodes`);
3354
+ } else {
3355
+ debug("overflow", "mid-block split failed on first block — keeping it whole");
3356
+ }
3357
+ const freshChildren = root.getChildren();
3358
+ const toRemove = freshChildren.slice(1);
3359
+ for (const node of toRemove) {
3360
+ overflowNodes.push(serializeNodeTree(node));
3361
+ }
3362
+ for (const node of toRemove) {
3363
+ node.remove();
3364
+ }
3365
+ } else {
3366
+ const toRemove = allChildren.slice(splitIndex);
3367
+ for (const node of toRemove) {
3368
+ overflowNodes.push(serializeNodeTree(node));
3369
+ }
3370
+ for (const node of toRemove) {
3371
+ node.remove();
3372
+ }
2803
3373
  }
2804
- for (const node of toRemove) {
2805
- node.remove();
3374
+ if (overflowNodes.length === 0) {
3375
+ processingRef.current = false;
3376
+ return;
2806
3377
  }
2807
- debug("overflow", `extracted ${overflowNodes.length} overflow nodes, kept ${splitIndex} nodes on current page`);
3378
+ debug("overflow", `extracted ${overflowNodes.length} overflow nodes total`);
2808
3379
  const overflowState = {
2809
3380
  root: {
2810
3381
  children: overflowNodes,
@@ -2885,16 +3456,10 @@ const OverflowPlugin = ({
2885
3456
  }, [editor]);
2886
3457
  return null;
2887
3458
  };
2888
- function createPageLabel(region, pageNumber) {
2889
- if (region === "body") {
2890
- return pageNumber ? `Page ${pageNumber}` : "Body";
2891
- }
2892
- const regionLabel = region.charAt(0).toUpperCase() + region.slice(1);
2893
- return pageNumber ? `${regionLabel} Page ${pageNumber}` : regionLabel;
2894
- }
2895
3459
  const HistoryCapturePlugin = ({ pageId, region }) => {
2896
3460
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
2897
3461
  const { document: document2, queueHistoryAction } = useDocument();
3462
+ const t = useTranslations();
2898
3463
  const context = React.useMemo(() => {
2899
3464
  const pageNumber = document2.pages.findIndex((page) => page.id === pageId);
2900
3465
  return {
@@ -2902,9 +3467,18 @@ const HistoryCapturePlugin = ({ pageId, region }) => {
2902
3467
  source: region
2903
3468
  };
2904
3469
  }, [document2.pages, pageId, region]);
3470
+ const createPageLabel = React.useMemo(() => {
3471
+ return (r, pageNumber) => {
3472
+ if (r === "body") {
3473
+ return pageNumber ? interpolate(t.regions.page, { page: pageNumber }) : t.regions.body;
3474
+ }
3475
+ const regionLabel = r === "header" ? t.regions.header : t.regions.footer;
3476
+ return pageNumber ? `${regionLabel} ${interpolate(t.regions.page, { page: pageNumber })}` : regionLabel;
3477
+ };
3478
+ }, [t]);
2905
3479
  React.useEffect(() => {
2906
- const buildDescriptor = (prefix) => ({
2907
- label: `${prefix} - ${createPageLabel(region, context.pageNumber)}`,
3480
+ const buildDescriptor = (label) => ({
3481
+ label: `${label} - ${createPageLabel(region, context.pageNumber)}`,
2908
3482
  source: context.source,
2909
3483
  pageId,
2910
3484
  region
@@ -2912,15 +3486,15 @@ const HistoryCapturePlugin = ({ pageId, region }) => {
2912
3486
  return editor.registerCommand(
2913
3487
  lexical.CONTROLLED_TEXT_INSERTION_COMMAND,
2914
3488
  () => {
2915
- queueHistoryAction(buildDescriptor("Typed text"));
3489
+ queueHistoryAction(buildDescriptor(t.historyLabels.typedText));
2916
3490
  return false;
2917
3491
  },
2918
3492
  lexical.COMMAND_PRIORITY_LOW
2919
3493
  );
2920
- }, [context.pageNumber, context.source, editor, pageId, queueHistoryAction, region]);
3494
+ }, [context.pageNumber, context.source, createPageLabel, editor, pageId, queueHistoryAction, region, t]);
2921
3495
  React.useEffect(() => {
2922
- const buildDescriptor = (prefix) => ({
2923
- label: `${prefix} - ${createPageLabel(region, context.pageNumber)}`,
3496
+ const buildDescriptor = (label) => ({
3497
+ label: `${label} - ${createPageLabel(region, context.pageNumber)}`,
2924
3498
  source: context.source,
2925
3499
  pageId,
2926
3500
  region
@@ -2934,15 +3508,15 @@ const HistoryCapturePlugin = ({ pageId, region }) => {
2934
3508
  if (event.key.length !== 1) {
2935
3509
  return false;
2936
3510
  }
2937
- queueHistoryAction(buildDescriptor("Typed text"));
3511
+ queueHistoryAction(buildDescriptor(t.historyLabels.typedText));
2938
3512
  return false;
2939
3513
  },
2940
3514
  lexical.COMMAND_PRIORITY_LOW
2941
3515
  );
2942
- }, [context.pageNumber, context.source, editor, pageId, queueHistoryAction, region]);
3516
+ }, [context.pageNumber, context.source, createPageLabel, editor, pageId, queueHistoryAction, region, t]);
2943
3517
  React.useEffect(() => {
2944
- const buildDescriptor = (prefix) => ({
2945
- label: `${prefix} - ${createPageLabel(region, context.pageNumber)}`,
3518
+ const buildDescriptor = (label) => ({
3519
+ label: `${label} - ${createPageLabel(region, context.pageNumber)}`,
2946
3520
  source: context.source,
2947
3521
  pageId,
2948
3522
  region
@@ -2955,15 +3529,15 @@ const HistoryCapturePlugin = ({ pageId, region }) => {
2955
3529
  if (text.trim().length === 0) {
2956
3530
  return false;
2957
3531
  }
2958
- queueHistoryAction(buildDescriptor("Pasted content"));
3532
+ queueHistoryAction(buildDescriptor(t.historyLabels.pastedContent));
2959
3533
  return false;
2960
3534
  },
2961
3535
  lexical.COMMAND_PRIORITY_LOW
2962
3536
  );
2963
- }, [context.pageNumber, context.source, editor, pageId, queueHistoryAction, region]);
3537
+ }, [context.pageNumber, context.source, createPageLabel, editor, pageId, queueHistoryAction, region, t]);
2964
3538
  React.useEffect(() => {
2965
- const buildDescriptor = (prefix) => ({
2966
- label: `${prefix} - ${createPageLabel(region, context.pageNumber)}`,
3539
+ const buildDescriptor = (label) => ({
3540
+ label: `${label} - ${createPageLabel(region, context.pageNumber)}`,
2967
3541
  source: context.source,
2968
3542
  pageId,
2969
3543
  region
@@ -2971,15 +3545,15 @@ const HistoryCapturePlugin = ({ pageId, region }) => {
2971
3545
  return editor.registerCommand(
2972
3546
  lexical.KEY_ENTER_COMMAND,
2973
3547
  () => {
2974
- queueHistoryAction(buildDescriptor("Inserted line break"));
3548
+ queueHistoryAction(buildDescriptor(t.historyLabels.insertedLineBreak));
2975
3549
  return false;
2976
3550
  },
2977
3551
  lexical.COMMAND_PRIORITY_LOW
2978
3552
  );
2979
- }, [context.pageNumber, context.source, editor, pageId, queueHistoryAction, region]);
3553
+ }, [context.pageNumber, context.source, createPageLabel, editor, pageId, queueHistoryAction, region, t]);
2980
3554
  React.useEffect(() => {
2981
- const buildDescriptor = (prefix) => ({
2982
- label: `${prefix} - ${createPageLabel(region, context.pageNumber)}`,
3555
+ const buildDescriptor = (label) => ({
3556
+ label: `${label} - ${createPageLabel(region, context.pageNumber)}`,
2983
3557
  source: context.source,
2984
3558
  pageId,
2985
3559
  region
@@ -2987,15 +3561,15 @@ const HistoryCapturePlugin = ({ pageId, region }) => {
2987
3561
  return editor.registerCommand(
2988
3562
  lexical.KEY_BACKSPACE_COMMAND,
2989
3563
  () => {
2990
- queueHistoryAction(buildDescriptor("Deleted backward"));
3564
+ queueHistoryAction(buildDescriptor(t.historyLabels.deletedBackward));
2991
3565
  return false;
2992
3566
  },
2993
3567
  lexical.COMMAND_PRIORITY_LOW
2994
3568
  );
2995
- }, [context.pageNumber, context.source, editor, pageId, queueHistoryAction, region]);
3569
+ }, [context.pageNumber, context.source, createPageLabel, editor, pageId, queueHistoryAction, region, t]);
2996
3570
  React.useEffect(() => {
2997
- const buildDescriptor = (prefix) => ({
2998
- label: `${prefix} - ${createPageLabel(region, context.pageNumber)}`,
3571
+ const buildDescriptor = (label) => ({
3572
+ label: `${label} - ${createPageLabel(region, context.pageNumber)}`,
2999
3573
  source: context.source,
3000
3574
  pageId,
3001
3575
  region
@@ -3003,15 +3577,15 @@ const HistoryCapturePlugin = ({ pageId, region }) => {
3003
3577
  return editor.registerCommand(
3004
3578
  lexical.KEY_DELETE_COMMAND,
3005
3579
  () => {
3006
- queueHistoryAction(buildDescriptor("Deleted forward"));
3580
+ queueHistoryAction(buildDescriptor(t.historyLabels.deletedForward));
3007
3581
  return false;
3008
3582
  },
3009
3583
  lexical.COMMAND_PRIORITY_LOW
3010
3584
  );
3011
- }, [context.pageNumber, context.source, editor, pageId, queueHistoryAction, region]);
3585
+ }, [context.pageNumber, context.source, createPageLabel, editor, pageId, queueHistoryAction, region, t]);
3012
3586
  React.useEffect(() => {
3013
- const buildDescriptor = (prefix) => ({
3014
- label: `${prefix} - ${createPageLabel(region, context.pageNumber)}`,
3587
+ const buildDescriptor = (label) => ({
3588
+ label: `${label} - ${createPageLabel(region, context.pageNumber)}`,
3015
3589
  source: context.source,
3016
3590
  pageId,
3017
3591
  region
@@ -3019,15 +3593,15 @@ const HistoryCapturePlugin = ({ pageId, region }) => {
3019
3593
  return editor.registerCommand(
3020
3594
  lexical.FORMAT_TEXT_COMMAND,
3021
3595
  () => {
3022
- queueHistoryAction(buildDescriptor("Formatted text"));
3596
+ queueHistoryAction(buildDescriptor(t.historyLabels.formattedText));
3023
3597
  return false;
3024
3598
  },
3025
3599
  lexical.COMMAND_PRIORITY_LOW
3026
3600
  );
3027
- }, [context.pageNumber, context.source, editor, pageId, queueHistoryAction, region]);
3601
+ }, [context.pageNumber, context.source, createPageLabel, editor, pageId, queueHistoryAction, region, t]);
3028
3602
  React.useEffect(() => {
3029
- const buildDescriptor = (prefix) => ({
3030
- label: `${prefix} - ${createPageLabel(region, context.pageNumber)}`,
3603
+ const buildDescriptor = (label) => ({
3604
+ label: `${label} - ${createPageLabel(region, context.pageNumber)}`,
3031
3605
  source: context.source,
3032
3606
  pageId,
3033
3607
  region
@@ -3035,12 +3609,12 @@ const HistoryCapturePlugin = ({ pageId, region }) => {
3035
3609
  return editor.registerCommand(
3036
3610
  lexical.FORMAT_ELEMENT_COMMAND,
3037
3611
  () => {
3038
- queueHistoryAction(buildDescriptor("Formatted paragraph"));
3612
+ queueHistoryAction(buildDescriptor(t.historyLabels.formattedParagraph));
3039
3613
  return false;
3040
3614
  },
3041
3615
  lexical.COMMAND_PRIORITY_LOW
3042
3616
  );
3043
- }, [context.pageNumber, context.source, editor, pageId, queueHistoryAction, region]);
3617
+ }, [context.pageNumber, context.source, createPageLabel, editor, pageId, queueHistoryAction, region, t]);
3044
3618
  return null;
3045
3619
  };
3046
3620
  function getCollapsedTextPosition(rootElement) {
@@ -3145,6 +3719,7 @@ const PageBody = ({
3145
3719
  readOnly = false
3146
3720
  }) => {
3147
3721
  const { nodes, bodyPlugins, themeOverrides } = useExtensions();
3722
+ const t = useTranslations();
3148
3723
  const config = React.useMemo(
3149
3724
  () => {
3150
3725
  var _a, _b;
@@ -3198,7 +3773,7 @@ const PageBody = ({
3198
3773
  style: { overflow: "visible" }
3199
3774
  }
3200
3775
  ),
3201
- placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-0 left-0 text-gray-400 pointer-events-none select-none", children: "Start typing..." }),
3776
+ placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-0 left-0 text-gray-400 pointer-events-none select-none", children: t.body.placeholder }),
3202
3777
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
3203
3778
  }
3204
3779
  ),
@@ -3338,10 +3913,10 @@ const PageHeader = ({
3338
3913
  LexicalContentEditable.ContentEditable,
3339
3914
  {
3340
3915
  ref: contentRef,
3341
- className: `outline-none p-2 text-sm text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
3916
+ className: `outline-none p-2 text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
3342
3917
  }
3343
3918
  ),
3344
- placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute top-0 left-0 text-gray-400 pointer-events-none select-none p-2 text-sm ${hasPageCounter ? "pr-24" : ""}`, children: t.header.placeholder }),
3919
+ placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute top-0 left-0 text-gray-400 pointer-events-none select-none p-2 ${hasPageCounter ? "pr-24" : ""}`, children: t.header.placeholder }),
3345
3920
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
3346
3921
  }
3347
3922
  ),
@@ -3418,10 +3993,10 @@ const PageFooter = ({
3418
3993
  LexicalContentEditable.ContentEditable,
3419
3994
  {
3420
3995
  ref: contentRef,
3421
- className: `outline-none p-2 text-sm text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
3996
+ className: `outline-none p-2 text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
3422
3997
  }
3423
3998
  ),
3424
- placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute top-0 left-0 text-gray-400 pointer-events-none select-none p-2 text-sm ${hasPageCounter ? "pr-24" : ""}`, children: t.footer.placeholder }),
3999
+ placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute top-0 left-0 text-gray-400 pointer-events-none select-none p-2 ${hasPageCounter ? "pr-24" : ""}`, children: t.footer.placeholder }),
3425
4000
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
3426
4001
  }
3427
4002
  ),
@@ -3452,10 +4027,14 @@ const PageView = React.memo(({
3452
4027
  onMoveToNextPage
3453
4028
  }) => {
3454
4029
  const { document: document2, dispatch, setActivePageId } = useDocument();
4030
+ const t = useTranslations();
3455
4031
  const page = document2.pages.find((p) => p.id === pageId);
3456
4032
  const showHeaderFooter = document2.headerFooterEnabled;
3457
4033
  const pageCounterMode = document2.pageCounterMode;
3458
- const pageCounterLabel = `Page ${pageIndex + 1} of ${document2.pages.length}`;
4034
+ const pageCounterLabel = interpolate(t.pageCounter.format, {
4035
+ current: pageIndex + 1,
4036
+ total: document2.pages.length
4037
+ });
3459
4038
  if (!page) return null;
3460
4039
  const handleBodyChange = React.useCallback(
3461
4040
  (bodyState) => {
@@ -3546,6 +4125,7 @@ const DocumentView = () => {
3546
4125
  setActiveEditor,
3547
4126
  setActivePageId
3548
4127
  } = useDocument();
4128
+ const t = useTranslations();
3549
4129
  const { handlePageUnderflow, reflowAll } = usePagination(document2, dispatch);
3550
4130
  const previousBodyHeightsRef = React.useRef(null);
3551
4131
  const pasteOverflowSequenceRef = React.useRef(false);
@@ -3710,7 +4290,7 @@ const DocumentView = () => {
3710
4290
  }
3711
4291
  runHistoryAction(
3712
4292
  {
3713
- label: `Deleted backward - Page ${pageIndex + 1}`,
4293
+ label: `${t.historyLabels.deletedBackward} - ${interpolate(t.regions.page, { page: pageIndex + 1 })}`,
3714
4294
  source: "body",
3715
4295
  pageId,
3716
4296
  region: "body"
@@ -3732,7 +4312,7 @@ const DocumentView = () => {
3732
4312
  }
3733
4313
  runHistoryAction(
3734
4314
  {
3735
- label: `Deleted forward - Page ${pageIndex + 1}`,
4315
+ label: `${t.historyLabels.deletedForward} - ${interpolate(t.regions.page, { page: pageIndex + 1 })}`,
3736
4316
  source: "body",
3737
4317
  pageId,
3738
4318
  region: "body"
@@ -3965,7 +4545,7 @@ const EditorChrome = ({
3965
4545
  }
3966
4546
  ),
3967
4547
  /* @__PURE__ */ jsxRuntime.jsx(Toolbar, {}),
3968
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden bg-gray-700", children: [
4548
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden bg-gray-200", children: [
3969
4549
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(DocumentView, {}) }),
3970
4550
  sidePanels.map((Panel, idx) => /* @__PURE__ */ jsxRuntime.jsx(Panel, {}, idx)),
3971
4551
  /* @__PURE__ */ jsxRuntime.jsx(HistorySidebar, {})
@@ -4778,6 +5358,7 @@ exports.INSERT_VARIABLE_COMMAND = INSERT_VARIABLE_COMMAND;
4778
5358
  exports.Lex4Editor = Lex4Editor;
4779
5359
  exports.MAX_FOOTER_HEIGHT_PX = MAX_FOOTER_HEIGHT_PX;
4780
5360
  exports.MAX_HEADER_HEIGHT_PX = MAX_HEADER_HEIGHT_PX;
5361
+ exports.PT_BR_TRANSLATIONS = PT_BR_TRANSLATIONS;
4781
5362
  exports.VariableNode = VariableNode;
4782
5363
  exports.astExtension = astExtension;
4783
5364
  exports.buildSavePayload = buildSavePayload;