@yurikilian/lex4 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +236 -20
  2. package/dist/ast/block-mapper.d.ts +24 -0
  3. package/dist/ast/block-mapper.d.ts.map +1 -0
  4. package/dist/ast/content-mapper.d.ts +16 -0
  5. package/dist/ast/content-mapper.d.ts.map +1 -0
  6. package/dist/ast/document-serializer.d.ts +11 -0
  7. package/dist/ast/document-serializer.d.ts.map +1 -0
  8. package/dist/ast/index.d.ts +9 -0
  9. package/dist/ast/index.d.ts.map +1 -0
  10. package/dist/ast/inline-mapper.d.ts +41 -0
  11. package/dist/ast/inline-mapper.d.ts.map +1 -0
  12. package/dist/ast/payload-builder.d.ts +18 -0
  13. package/dist/ast/payload-builder.d.ts.map +1 -0
  14. package/dist/ast/types.d.ts +112 -0
  15. package/dist/ast/types.d.ts.map +1 -0
  16. package/dist/components/EditorSidebar.d.ts +1 -0
  17. package/dist/components/EditorSidebar.d.ts.map +1 -1
  18. package/dist/components/HistorySidebar.d.ts.map +1 -1
  19. package/dist/components/Lex4Editor.d.ts +9 -1
  20. package/dist/components/Lex4Editor.d.ts.map +1 -1
  21. package/dist/components/PageBody.d.ts +1 -2
  22. package/dist/components/PageBody.d.ts.map +1 -1
  23. package/dist/components/PageFooter.d.ts.map +1 -1
  24. package/dist/components/PageHeader.d.ts.map +1 -1
  25. package/dist/components/Toolbar.d.ts.map +1 -1
  26. package/dist/components/VariablePanel.d.ts +15 -0
  27. package/dist/components/VariablePanel.d.ts.map +1 -0
  28. package/dist/components/VariablePicker.d.ts +14 -0
  29. package/dist/components/VariablePicker.d.ts.map +1 -0
  30. package/dist/extensions/ast-extension.d.ts +16 -0
  31. package/dist/extensions/ast-extension.d.ts.map +1 -0
  32. package/dist/extensions/extension-context.d.ts +29 -0
  33. package/dist/extensions/extension-context.d.ts.map +1 -0
  34. package/dist/extensions/index.d.ts +6 -0
  35. package/dist/extensions/index.d.ts.map +1 -0
  36. package/dist/extensions/types.d.ts +74 -0
  37. package/dist/extensions/types.d.ts.map +1 -0
  38. package/dist/extensions/variables-extension.d.ts +31 -0
  39. package/dist/extensions/variables-extension.d.ts.map +1 -0
  40. package/dist/i18n/context.d.ts +28 -0
  41. package/dist/i18n/context.d.ts.map +1 -0
  42. package/dist/i18n/defaults.d.ts +3 -0
  43. package/dist/i18n/defaults.d.ts.map +1 -0
  44. package/dist/i18n/index.d.ts +4 -0
  45. package/dist/i18n/index.d.ts.map +1 -0
  46. package/dist/i18n/types.d.ts +82 -0
  47. package/dist/i18n/types.d.ts.map +1 -0
  48. package/dist/index.d.ts +10 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/lex4-editor.cjs +1113 -101
  51. package/dist/lex4-editor.cjs.map +1 -1
  52. package/dist/lex4-editor.js +1116 -104
  53. package/dist/lex4-editor.js.map +1 -1
  54. package/dist/lexical/editor-setup.d.ts +5 -1
  55. package/dist/lexical/editor-setup.d.ts.map +1 -1
  56. package/dist/lexical/plugins/font-plugin.d.ts +1 -1
  57. package/dist/lexical/plugins/font-plugin.d.ts.map +1 -1
  58. package/dist/lexical/plugins/font-size-plugin.d.ts +19 -0
  59. package/dist/lexical/plugins/font-size-plugin.d.ts.map +1 -0
  60. package/dist/lexical/plugins/index.d.ts +2 -0
  61. package/dist/lexical/plugins/index.d.ts.map +1 -1
  62. package/dist/style.css +532 -160
  63. package/dist/types/editor-handle.d.ts +14 -0
  64. package/dist/types/editor-handle.d.ts.map +1 -0
  65. package/dist/types/editor-props.d.ts +23 -0
  66. package/dist/types/editor-props.d.ts.map +1 -1
  67. package/dist/variables/index.d.ts +5 -0
  68. package/dist/variables/index.d.ts.map +1 -0
  69. package/dist/variables/types.d.ts +26 -0
  70. package/dist/variables/types.d.ts.map +1 -0
  71. package/dist/variables/variable-commands.d.ts +11 -0
  72. package/dist/variables/variable-commands.d.ts.map +1 -0
  73. package/dist/variables/variable-context.d.ts +21 -0
  74. package/dist/variables/variable-context.d.ts.map +1 -0
  75. package/dist/variables/variable-node.d.ts +34 -0
  76. package/dist/variables/variable-node.d.ts.map +1 -0
  77. package/dist/variables/variable-plugin.d.ts +8 -0
  78. package/dist/variables/variable-plugin.d.ts.map +1 -0
  79. package/package.json +1 -1
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
2
5
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
6
  const jsxRuntime = require("react/jsx-runtime");
4
7
  const React = require("react");
@@ -1133,33 +1136,53 @@ const createLucideIcon = (iconName, iconNode) => {
1133
1136
  * This source code is licensed under the ISC license.
1134
1137
  * See the LICENSE file in the root directory of this source tree.
1135
1138
  */
1136
- const __iconNode$j = [
1139
+ const __iconNode$m = [
1137
1140
  [
1138
1141
  "path",
1139
1142
  { 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" }
1140
1143
  ]
1141
1144
  ];
1142
- const Bold = createLucideIcon("bold", __iconNode$j);
1145
+ const Bold = createLucideIcon("bold", __iconNode$m);
1143
1146
  /**
1144
1147
  * @license lucide-react v1.8.0 - ISC
1145
1148
  *
1146
1149
  * This source code is licensed under the ISC license.
1147
1150
  * See the LICENSE file in the root directory of this source tree.
1148
1151
  */
1149
- const __iconNode$i = [
1152
+ const __iconNode$l = [
1153
+ [
1154
+ "path",
1155
+ { 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" }
1156
+ ],
1157
+ [
1158
+ "path",
1159
+ {
1160
+ d: "M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1",
1161
+ key: "e1hn23"
1162
+ }
1163
+ ]
1164
+ ];
1165
+ const Braces = createLucideIcon("braces", __iconNode$l);
1166
+ /**
1167
+ * @license lucide-react v1.8.0 - ISC
1168
+ *
1169
+ * This source code is licensed under the ISC license.
1170
+ * See the LICENSE file in the root directory of this source tree.
1171
+ */
1172
+ const __iconNode$k = [
1150
1173
  ["line", { x1: "15", x2: "15", y1: "12", y2: "18", key: "1p7wdc" }],
1151
1174
  ["line", { x1: "12", x2: "18", y1: "15", y2: "15", key: "1nscbv" }],
1152
1175
  ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
1153
1176
  ["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" }]
1154
1177
  ];
1155
- const CopyPlus = createLucideIcon("copy-plus", __iconNode$i);
1178
+ const CopyPlus = createLucideIcon("copy-plus", __iconNode$k);
1156
1179
  /**
1157
1180
  * @license lucide-react v1.8.0 - ISC
1158
1181
  *
1159
1182
  * This source code is licensed under the ISC license.
1160
1183
  * See the LICENSE file in the root directory of this source tree.
1161
1184
  */
1162
- const __iconNode$h = [
1185
+ const __iconNode$j = [
1163
1186
  [
1164
1187
  "path",
1165
1188
  {
@@ -1169,65 +1192,65 @@ const __iconNode$h = [
1169
1192
  ],
1170
1193
  ["path", { d: "m5.082 11.09 8.828 8.828", key: "1wx5vj" }]
1171
1194
  ];
1172
- const Eraser = createLucideIcon("eraser", __iconNode$h);
1195
+ const Eraser = createLucideIcon("eraser", __iconNode$j);
1173
1196
  /**
1174
1197
  * @license lucide-react v1.8.0 - ISC
1175
1198
  *
1176
1199
  * This source code is licensed under the ISC license.
1177
1200
  * See the LICENSE file in the root directory of this source tree.
1178
1201
  */
1179
- const __iconNode$g = [
1202
+ const __iconNode$i = [
1180
1203
  ["line", { x1: "4", x2: "20", y1: "9", y2: "9", key: "4lhtct" }],
1181
1204
  ["line", { x1: "4", x2: "20", y1: "15", y2: "15", key: "vyu0kd" }],
1182
1205
  ["line", { x1: "10", x2: "8", y1: "3", y2: "21", key: "1ggp8o" }],
1183
1206
  ["line", { x1: "16", x2: "14", y1: "3", y2: "21", key: "weycgp" }]
1184
1207
  ];
1185
- const Hash = createLucideIcon("hash", __iconNode$g);
1208
+ const Hash = createLucideIcon("hash", __iconNode$i);
1186
1209
  /**
1187
1210
  * @license lucide-react v1.8.0 - ISC
1188
1211
  *
1189
1212
  * This source code is licensed under the ISC license.
1190
1213
  * See the LICENSE file in the root directory of this source tree.
1191
1214
  */
1192
- const __iconNode$f = [
1215
+ const __iconNode$h = [
1193
1216
  ["line", { x1: "19", x2: "10", y1: "4", y2: "4", key: "15jd3p" }],
1194
1217
  ["line", { x1: "14", x2: "5", y1: "20", y2: "20", key: "bu0au3" }],
1195
1218
  ["line", { x1: "15", x2: "9", y1: "4", y2: "20", key: "uljnxc" }]
1196
1219
  ];
1197
- const Italic = createLucideIcon("italic", __iconNode$f);
1220
+ const Italic = createLucideIcon("italic", __iconNode$h);
1198
1221
  /**
1199
1222
  * @license lucide-react v1.8.0 - ISC
1200
1223
  *
1201
1224
  * This source code is licensed under the ISC license.
1202
1225
  * See the LICENSE file in the root directory of this source tree.
1203
1226
  */
1204
- const __iconNode$e = [
1227
+ const __iconNode$g = [
1205
1228
  ["path", { d: "M21 5H11", key: "us1j55" }],
1206
1229
  ["path", { d: "M21 12H11", key: "wd7e0v" }],
1207
1230
  ["path", { d: "M21 19H11", key: "saa85w" }],
1208
1231
  ["path", { d: "m7 8-4 4 4 4", key: "o5hrat" }]
1209
1232
  ];
1210
- const ListIndentDecrease = createLucideIcon("list-indent-decrease", __iconNode$e);
1233
+ const ListIndentDecrease = createLucideIcon("list-indent-decrease", __iconNode$g);
1211
1234
  /**
1212
1235
  * @license lucide-react v1.8.0 - ISC
1213
1236
  *
1214
1237
  * This source code is licensed under the ISC license.
1215
1238
  * See the LICENSE file in the root directory of this source tree.
1216
1239
  */
1217
- const __iconNode$d = [
1240
+ const __iconNode$f = [
1218
1241
  ["path", { d: "M21 5H11", key: "us1j55" }],
1219
1242
  ["path", { d: "M21 12H11", key: "wd7e0v" }],
1220
1243
  ["path", { d: "M21 19H11", key: "saa85w" }],
1221
1244
  ["path", { d: "m3 8 4 4-4 4", key: "1a3j6y" }]
1222
1245
  ];
1223
- const ListIndentIncrease = createLucideIcon("list-indent-increase", __iconNode$d);
1246
+ const ListIndentIncrease = createLucideIcon("list-indent-increase", __iconNode$f);
1224
1247
  /**
1225
1248
  * @license lucide-react v1.8.0 - ISC
1226
1249
  *
1227
1250
  * This source code is licensed under the ISC license.
1228
1251
  * See the LICENSE file in the root directory of this source tree.
1229
1252
  */
1230
- const __iconNode$c = [
1253
+ const __iconNode$e = [
1231
1254
  ["path", { d: "M11 5h10", key: "1cz7ny" }],
1232
1255
  ["path", { d: "M11 12h10", key: "1438ji" }],
1233
1256
  ["path", { d: "M11 19h10", key: "11t30w" }],
@@ -1235,14 +1258,14 @@ const __iconNode$c = [
1235
1258
  ["path", { d: "M4 9h2", key: "r1h2o0" }],
1236
1259
  ["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" }]
1237
1260
  ];
1238
- const ListOrdered = createLucideIcon("list-ordered", __iconNode$c);
1261
+ const ListOrdered = createLucideIcon("list-ordered", __iconNode$e);
1239
1262
  /**
1240
1263
  * @license lucide-react v1.8.0 - ISC
1241
1264
  *
1242
1265
  * This source code is licensed under the ISC license.
1243
1266
  * See the LICENSE file in the root directory of this source tree.
1244
1267
  */
1245
- const __iconNode$b = [
1268
+ const __iconNode$d = [
1246
1269
  ["path", { d: "M3 5h.01", key: "18ugdj" }],
1247
1270
  ["path", { d: "M3 12h.01", key: "nlz23k" }],
1248
1271
  ["path", { d: "M3 19h.01", key: "noohij" }],
@@ -1250,29 +1273,53 @@ const __iconNode$b = [
1250
1273
  ["path", { d: "M8 12h13", key: "1za7za" }],
1251
1274
  ["path", { d: "M8 19h13", key: "m83p4d" }]
1252
1275
  ];
1253
- const List = createLucideIcon("list", __iconNode$b);
1276
+ const List = createLucideIcon("list", __iconNode$d);
1254
1277
  /**
1255
1278
  * @license lucide-react v1.8.0 - ISC
1256
1279
  *
1257
1280
  * This source code is licensed under the ISC license.
1258
1281
  * See the LICENSE file in the root directory of this source tree.
1259
1282
  */
1260
- const __iconNode$a = [
1283
+ const __iconNode$c = [
1261
1284
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }],
1262
1285
  ["path", { d: "M15 3v18", key: "14nvp0" }]
1263
1286
  ];
1264
- const PanelRight = createLucideIcon("panel-right", __iconNode$a);
1287
+ const PanelRight = createLucideIcon("panel-right", __iconNode$c);
1265
1288
  /**
1266
1289
  * @license lucide-react v1.8.0 - ISC
1267
1290
  *
1268
1291
  * This source code is licensed under the ISC license.
1269
1292
  * See the LICENSE file in the root directory of this source tree.
1270
1293
  */
1271
- const __iconNode$9 = [
1294
+ const __iconNode$b = [
1272
1295
  ["path", { d: "m15 14 5-5-5-5", key: "12vg1m" }],
1273
1296
  ["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" }]
1274
1297
  ];
1275
- const Redo2 = createLucideIcon("redo-2", __iconNode$9);
1298
+ const Redo2 = createLucideIcon("redo-2", __iconNode$b);
1299
+ /**
1300
+ * @license lucide-react v1.8.0 - ISC
1301
+ *
1302
+ * This source code is licensed under the ISC license.
1303
+ * See the LICENSE file in the root directory of this source tree.
1304
+ */
1305
+ const __iconNode$a = [
1306
+ ["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
+ ["path", { d: "M21 3v5h-5", key: "1q7to0" }],
1308
+ ["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
+ ["path", { d: "M8 16H3v5", key: "1cv678" }]
1310
+ ];
1311
+ const RefreshCw = createLucideIcon("refresh-cw", __iconNode$a);
1312
+ /**
1313
+ * @license lucide-react v1.8.0 - ISC
1314
+ *
1315
+ * This source code is licensed under the ISC license.
1316
+ * See the LICENSE file in the root directory of this source tree.
1317
+ */
1318
+ const __iconNode$9 = [
1319
+ ["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
1320
+ ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
1321
+ ];
1322
+ const Search = createLucideIcon("search", __iconNode$9);
1276
1323
  /**
1277
1324
  * @license lucide-react v1.8.0 - ISC
1278
1325
  *
@@ -1380,14 +1427,122 @@ const __iconNode = [
1380
1427
  ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
1381
1428
  ];
1382
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
+ };
1383
1536
  const EditorSidebar = ({
1384
1537
  title,
1385
1538
  subtitle,
1386
1539
  open,
1387
1540
  onClose,
1388
1541
  headerActions,
1542
+ testId = "editor-sidebar",
1389
1543
  children
1390
1544
  }) => {
1545
+ const t = useTranslations();
1391
1546
  if (!open) {
1392
1547
  return null;
1393
1548
  }
@@ -1395,7 +1550,7 @@ const EditorSidebar = ({
1395
1550
  "aside",
1396
1551
  {
1397
1552
  className: "flex h-full w-[320px] shrink-0 flex-col border-l border-gray-200 bg-white",
1398
- "data-testid": "editor-sidebar",
1553
+ "data-testid": testId,
1399
1554
  children: [
1400
1555
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between border-b border-gray-200 px-3 py-2.5", children: [
1401
1556
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
@@ -1411,7 +1566,7 @@ const EditorSidebar = ({
1411
1566
  className: "flex h-6 w-6 items-center justify-center rounded text-gray-400\n transition-colors hover:bg-gray-100 hover:text-gray-600",
1412
1567
  "data-testid": "close-editor-sidebar",
1413
1568
  onClick: onClose,
1414
- "aria-label": "Close sidebar",
1569
+ "aria-label": t.sidebar.close,
1415
1570
  children: /* @__PURE__ */ jsxRuntime.jsx(X, { size: 14 })
1416
1571
  }
1417
1572
  )
@@ -1439,13 +1594,14 @@ const HistorySidebar = () => {
1439
1594
  jumpToHistoryEntry: jumpToHistoryEntry2,
1440
1595
  setHistorySidebarOpen
1441
1596
  } = useDocument();
1597
+ const t = useTranslations();
1442
1598
  const visibleEntries = historyEntries.slice().reverse();
1443
1599
  const headerActions = /* @__PURE__ */ jsxRuntime.jsx(
1444
1600
  "button",
1445
1601
  {
1446
1602
  type: "button",
1447
- title: "Clear History",
1448
- "aria-label": "Clear History",
1603
+ title: t.history.clearHistory,
1604
+ "aria-label": t.history.clearHistory,
1449
1605
  onMouseDown: (e) => e.preventDefault(),
1450
1606
  onClick: () => clearHistory("manual"),
1451
1607
  className: "flex h-6 w-6 items-center justify-center rounded text-gray-400\n transition-colors hover:bg-gray-100 hover:text-gray-600",
@@ -1456,17 +1612,18 @@ const HistorySidebar = () => {
1456
1612
  return /* @__PURE__ */ jsxRuntime.jsx(
1457
1613
  EditorSidebar,
1458
1614
  {
1459
- title: "History",
1460
- subtitle: "Word-style session history (last 100 actions)",
1615
+ title: t.history.title,
1616
+ subtitle: t.history.subtitle,
1461
1617
  open: historySidebarOpen,
1462
1618
  onClose: () => setHistorySidebarOpen(false),
1463
1619
  headerActions,
1620
+ testId: "history-sidebar",
1464
1621
  children: visibleEntries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1465
1622
  "div",
1466
1623
  {
1467
1624
  className: "px-3 py-4 text-xs text-gray-500",
1468
1625
  "data-testid": "history-empty",
1469
- children: "No history yet."
1626
+ children: t.history.empty
1470
1627
  }
1471
1628
  ) : /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "divide-y divide-gray-100", "data-testid": "history-entry-list", children: visibleEntries.map((entry, reversedIndex) => {
1472
1629
  const actualIndex = historyEntries.length - reversedIndex - 1;
@@ -1657,7 +1814,81 @@ const ActionIconButton = ({ title, icon, onClick, disabled, testId }) => /* @__P
1657
1814
  }
1658
1815
  );
1659
1816
  const ActionDivider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-0.5 h-4 w-px bg-gray-300/60" });
1817
+ function resolveExtensions(extensions) {
1818
+ const resolved = {
1819
+ nodes: [],
1820
+ bodyPlugins: [],
1821
+ toolbarItems: [],
1822
+ sidePanels: [],
1823
+ providers: [],
1824
+ themeOverrides: {},
1825
+ handleFactories: []
1826
+ };
1827
+ for (const ext of extensions) {
1828
+ if (ext.nodes) resolved.nodes.push(...ext.nodes);
1829
+ if (ext.bodyPlugins) resolved.bodyPlugins.push(...ext.bodyPlugins);
1830
+ if (ext.toolbarItems) resolved.toolbarItems.push(...ext.toolbarItems);
1831
+ if (ext.sidePanel) resolved.sidePanels.push(ext.sidePanel);
1832
+ if (ext.provider) resolved.providers.push(ext.provider);
1833
+ if (ext.themeOverrides) {
1834
+ resolved.themeOverrides = { ...resolved.themeOverrides, ...ext.themeOverrides };
1835
+ }
1836
+ if (ext.handleMethods) resolved.handleFactories.push(ext.handleMethods);
1837
+ }
1838
+ return resolved;
1839
+ }
1840
+ const EMPTY_RESOLVED = {
1841
+ nodes: [],
1842
+ bodyPlugins: [],
1843
+ toolbarItems: [],
1844
+ sidePanels: [],
1845
+ providers: [],
1846
+ themeOverrides: {},
1847
+ handleFactories: []
1848
+ };
1849
+ const ExtensionResolvedContext = React.createContext(EMPTY_RESOLVED);
1850
+ const ExtensionProvider = ({ extensions, children }) => {
1851
+ const resolved = React.useMemo(
1852
+ () => extensions && extensions.length > 0 ? resolveExtensions(extensions) : EMPTY_RESOLVED,
1853
+ [extensions]
1854
+ );
1855
+ let wrapped = /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
1856
+ for (let i = resolved.providers.length - 1; i >= 0; i--) {
1857
+ const Provider = resolved.providers[i];
1858
+ wrapped = /* @__PURE__ */ jsxRuntime.jsx(Provider, { children: wrapped });
1859
+ }
1860
+ return /* @__PURE__ */ jsxRuntime.jsx(ExtensionResolvedContext.Provider, { value: resolved, children: wrapped });
1861
+ };
1862
+ function useExtensions() {
1863
+ return React.useContext(ExtensionResolvedContext);
1864
+ }
1865
+ const ExtensionStateContext = React.createContext(null);
1866
+ const ExtensionStateProvider = ({ children }) => {
1867
+ const stateRef = React.useRef({});
1868
+ return /* @__PURE__ */ jsxRuntime.jsx(ExtensionStateContext.Provider, { value: stateRef, children });
1869
+ };
1870
+ function useExtensionState() {
1871
+ const stateRef = React.useContext(ExtensionStateContext);
1872
+ return {
1873
+ get: (key) => stateRef == null ? void 0 : stateRef.current[key],
1874
+ set: (key, value) => {
1875
+ if (stateRef) stateRef.current[key] = value;
1876
+ }
1877
+ };
1878
+ }
1879
+ function useExtensionContext(getDocument, getActiveEditor) {
1880
+ const stateRef = React.useContext(ExtensionStateContext);
1881
+ return React.useMemo(() => ({
1882
+ getDocument,
1883
+ getActiveEditor,
1884
+ getExtensionState: (key) => stateRef == null ? void 0 : stateRef.current[key],
1885
+ setExtensionState: (key, value) => {
1886
+ if (stateRef) stateRef.current[key] = value;
1887
+ }
1888
+ }), [getDocument, getActiveEditor, stateRef]);
1889
+ }
1660
1890
  const SUPPORTED_FONTS = [
1891
+ "Inter",
1661
1892
  "Times New Roman",
1662
1893
  "Arial",
1663
1894
  "Calibri",
@@ -1676,6 +1907,42 @@ function applyFontFamily(editor, fontFamily) {
1676
1907
  }
1677
1908
  });
1678
1909
  }
1910
+ const SUPPORTED_FONT_SIZES = [
1911
+ 8,
1912
+ 9,
1913
+ 10,
1914
+ 11,
1915
+ 12,
1916
+ 14,
1917
+ 16,
1918
+ 18,
1919
+ 20,
1920
+ 24,
1921
+ 28,
1922
+ 32,
1923
+ 36,
1924
+ 48,
1925
+ 72
1926
+ ];
1927
+ function applyFontSize(editor, size) {
1928
+ editor.update(() => {
1929
+ const selection = lexical.$getSelection();
1930
+ if (!lexical.$isRangeSelection(selection)) return;
1931
+ const nodes = selection.getNodes();
1932
+ for (const node of nodes) {
1933
+ if (lexical.$isTextNode(node)) {
1934
+ const existing = node.getStyle();
1935
+ const updated = mergeFontSize(existing, size);
1936
+ node.setStyle(updated);
1937
+ }
1938
+ }
1939
+ });
1940
+ }
1941
+ function mergeFontSize(existingStyle, size) {
1942
+ const stripped = existingStyle.replace(/font-size:\s*[^;]+;?\s*/g, "").trim();
1943
+ const sizeDecl = `font-size: ${size}pt`;
1944
+ return stripped ? `${stripped}; ${sizeDecl}` : sizeDecl;
1945
+ }
1679
1946
  function toggleFormat(editor, format) {
1680
1947
  editor.dispatchCommand(lexical.FORMAT_TEXT_COMMAND, format);
1681
1948
  }
@@ -1723,6 +1990,8 @@ const Toolbar = () => {
1723
1990
  setHistorySidebarOpen,
1724
1991
  undo
1725
1992
  } = useDocument();
1993
+ const { toolbarItems } = useExtensions();
1994
+ const t = useTranslations();
1726
1995
  const withBodySelection = React.useCallback(
1727
1996
  (editor, action) => {
1728
1997
  editor.update(() => {
@@ -1760,7 +2029,7 @@ const Toolbar = () => {
1760
2029
  );
1761
2030
  const handleToggle = (enabled) => {
1762
2031
  runToolbarAction(
1763
- enabled ? "Enabled headers and footers" : "Disabled headers and footers",
2032
+ enabled ? t.history.actions.enabledHeadersFooters : t.history.actions.disabledHeadersFooters,
1764
2033
  () => {
1765
2034
  dispatch({ type: "SET_HEADER_FOOTER_ENABLED", enabled });
1766
2035
  }
@@ -1768,114 +2037,123 @@ const Toolbar = () => {
1768
2037
  };
1769
2038
  const handleCopyHeaderToAll = () => {
1770
2039
  if (activePageId) {
1771
- runToolbarAction("Copied header to all pages", () => {
2040
+ runToolbarAction(t.history.actions.copiedHeaderToAll, () => {
1772
2041
  dispatch({ type: "COPY_HEADER_TO_ALL", sourcePageId: activePageId });
1773
2042
  });
1774
2043
  }
1775
2044
  };
1776
2045
  const handleCopyFooterToAll = () => {
1777
2046
  if (activePageId) {
1778
- runToolbarAction("Copied footer to all pages", () => {
2047
+ runToolbarAction(t.history.actions.copiedFooterToAll, () => {
1779
2048
  dispatch({ type: "COPY_FOOTER_TO_ALL", sourcePageId: activePageId });
1780
2049
  });
1781
2050
  }
1782
2051
  };
1783
2052
  const handleClearHeader = () => {
1784
2053
  if (activePageId) {
1785
- runToolbarAction("Cleared header", () => {
2054
+ runToolbarAction(t.history.actions.clearedHeader, () => {
1786
2055
  dispatch({ type: "CLEAR_HEADER", pageId: activePageId });
1787
2056
  });
1788
2057
  }
1789
2058
  };
1790
2059
  const handleClearFooter = () => {
1791
2060
  if (activePageId) {
1792
- runToolbarAction("Cleared footer", () => {
2061
+ runToolbarAction(t.history.actions.clearedFooter, () => {
1793
2062
  dispatch({ type: "CLEAR_FOOTER", pageId: activePageId });
1794
2063
  });
1795
2064
  }
1796
2065
  };
1797
- const handleClearAllHeaders = () => runToolbarAction("Cleared all headers", () => {
2066
+ const handleClearAllHeaders = () => runToolbarAction(t.history.actions.clearedAllHeaders, () => {
1798
2067
  dispatch({ type: "CLEAR_ALL_HEADERS" });
1799
2068
  });
1800
- const handleClearAllFooters = () => runToolbarAction("Cleared all footers", () => {
2069
+ const handleClearAllFooters = () => runToolbarAction(t.history.actions.clearedAllFooters, () => {
1801
2070
  dispatch({ type: "CLEAR_ALL_FOOTERS" });
1802
2071
  });
1803
2072
  const handlePageCounterModeChange = React.useCallback((mode) => {
1804
- runToolbarAction(`Page counter set to ${mode}`, () => {
2073
+ runToolbarAction(interpolate(t.history.actions.pageCounterSet, { value: mode }), () => {
1805
2074
  dispatch({ type: "SET_PAGE_COUNTER_MODE", mode });
1806
2075
  });
1807
- }, [dispatch, runToolbarAction]);
2076
+ }, [dispatch, runToolbarAction, t.history.actions.pageCounterSet]);
1808
2077
  const handleBold = React.useCallback(() => {
1809
2078
  debug("toolbar", `bold (globalSelection=${globalSelectionActive}, editors=${editorRegistry.all().length}, hasEditor=${!!activeEditor})`);
1810
- runToolbarAction("Bold applied", () => {
2079
+ runToolbarAction(t.history.actions.boldApplied, () => {
1811
2080
  applyToBodyEditors(toggleBold);
1812
2081
  });
1813
- }, [activeEditor, applyToBodyEditors, editorRegistry, globalSelectionActive, runToolbarAction]);
2082
+ }, [activeEditor, applyToBodyEditors, editorRegistry, globalSelectionActive, runToolbarAction, t.history.actions.boldApplied]);
1814
2083
  const handleItalic = React.useCallback(() => {
1815
2084
  debug("toolbar", `italic (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
1816
- runToolbarAction("Italic applied", () => {
2085
+ runToolbarAction(t.history.actions.italicApplied, () => {
1817
2086
  applyToBodyEditors(toggleItalic);
1818
2087
  });
1819
- }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction]);
2088
+ }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.italicApplied]);
1820
2089
  const handleUnderline = React.useCallback(() => {
1821
2090
  debug("toolbar", `underline (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
1822
- runToolbarAction("Underline applied", () => {
2091
+ runToolbarAction(t.history.actions.underlineApplied, () => {
1823
2092
  applyToBodyEditors(toggleUnderline);
1824
2093
  });
1825
- }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction]);
2094
+ }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.underlineApplied]);
1826
2095
  const handleStrikethrough = React.useCallback(() => {
1827
2096
  debug("toolbar", `strikethrough (globalSelection=${globalSelectionActive}, hasEditor=${!!activeEditor})`);
1828
- runToolbarAction("Strikethrough applied", () => {
2097
+ runToolbarAction(t.history.actions.strikethroughApplied, () => {
1829
2098
  applyToBodyEditors(toggleStrikethrough);
1830
2099
  });
1831
- }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction]);
2100
+ }, [activeEditor, applyToBodyEditors, globalSelectionActive, runToolbarAction, t.history.actions.strikethroughApplied]);
1832
2101
  const handleAlignLeft = React.useCallback(() => {
1833
- runToolbarAction("Aligned left", () => {
2102
+ runToolbarAction(t.history.actions.alignedLeft, () => {
1834
2103
  applyToBodyEditors((editor) => setAlignment(editor, "left"));
1835
2104
  });
1836
- }, [applyToBodyEditors, runToolbarAction]);
2105
+ }, [applyToBodyEditors, runToolbarAction, t.history.actions.alignedLeft]);
1837
2106
  const handleAlignCenter = React.useCallback(() => {
1838
- runToolbarAction("Aligned center", () => {
2107
+ runToolbarAction(t.history.actions.alignedCenter, () => {
1839
2108
  applyToBodyEditors((editor) => setAlignment(editor, "center"));
1840
2109
  });
1841
- }, [applyToBodyEditors, runToolbarAction]);
2110
+ }, [applyToBodyEditors, runToolbarAction, t.history.actions.alignedCenter]);
1842
2111
  const handleAlignRight = React.useCallback(() => {
1843
- runToolbarAction("Aligned right", () => {
2112
+ runToolbarAction(t.history.actions.alignedRight, () => {
1844
2113
  applyToBodyEditors((editor) => setAlignment(editor, "right"));
1845
2114
  });
1846
- }, [applyToBodyEditors, runToolbarAction]);
2115
+ }, [applyToBodyEditors, runToolbarAction, t.history.actions.alignedRight]);
1847
2116
  const handleAlignJustify = React.useCallback(() => {
1848
- runToolbarAction("Justified text", () => {
2117
+ runToolbarAction(t.history.actions.justifiedText, () => {
1849
2118
  applyToBodyEditors((editor) => setAlignment(editor, "justify"));
1850
2119
  });
1851
- }, [applyToBodyEditors, runToolbarAction]);
2120
+ }, [applyToBodyEditors, runToolbarAction, t.history.actions.justifiedText]);
1852
2121
  const handleListNumber = React.useCallback(() => {
1853
- runToolbarAction("Inserted numbered list", () => {
2122
+ runToolbarAction(t.history.actions.insertedNumberedList, () => {
1854
2123
  applyToBodyEditors((editor) => insertList(editor, "number"));
1855
2124
  });
1856
- }, [applyToBodyEditors, runToolbarAction]);
2125
+ }, [applyToBodyEditors, runToolbarAction, t.history.actions.insertedNumberedList]);
1857
2126
  const handleListBullet = React.useCallback(() => {
1858
- runToolbarAction("Inserted bullet list", () => {
2127
+ runToolbarAction(t.history.actions.insertedBulletList, () => {
1859
2128
  applyToBodyEditors((editor) => insertList(editor, "bullet"));
1860
2129
  });
1861
- }, [applyToBodyEditors, runToolbarAction]);
2130
+ }, [applyToBodyEditors, runToolbarAction, t.history.actions.insertedBulletList]);
1862
2131
  const handleIndent = React.useCallback(() => {
1863
- runToolbarAction("Indented content", () => {
2132
+ runToolbarAction(t.history.actions.indentedContent, () => {
1864
2133
  applyToBodyEditors(indentContent);
1865
2134
  });
1866
- }, [applyToBodyEditors, runToolbarAction]);
2135
+ }, [applyToBodyEditors, runToolbarAction, t.history.actions.indentedContent]);
1867
2136
  const handleOutdent = React.useCallback(() => {
1868
- runToolbarAction("Outdented content", () => {
2137
+ runToolbarAction(t.history.actions.outdentedContent, () => {
1869
2138
  applyToBodyEditors(outdentContent);
1870
2139
  });
1871
- }, [applyToBodyEditors, runToolbarAction]);
2140
+ }, [applyToBodyEditors, runToolbarAction, t.history.actions.outdentedContent]);
1872
2141
  const handleFontChange = React.useCallback(
1873
2142
  (e) => {
1874
- runToolbarAction(`Font changed to ${e.target.value}`, () => {
2143
+ runToolbarAction(interpolate(t.history.actions.fontChanged, { value: e.target.value }), () => {
1875
2144
  applyToBodyEditors((editor) => applyFontFamily(editor, e.target.value));
1876
2145
  });
1877
2146
  },
1878
- [applyToBodyEditors, runToolbarAction]
2147
+ [applyToBodyEditors, runToolbarAction, t.history.actions.fontChanged]
2148
+ );
2149
+ const handleFontSizeChange = React.useCallback(
2150
+ (e) => {
2151
+ const size = parseInt(e.target.value, 10);
2152
+ runToolbarAction(interpolate(t.history.actions.fontSizeChanged, { value: String(size) }), () => {
2153
+ applyToBodyEditors((editor) => applyFontSize(editor, size));
2154
+ });
2155
+ },
2156
+ [applyToBodyEditors, runToolbarAction, t.history.actions.fontSizeChanged]
1879
2157
  );
1880
2158
  return /* @__PURE__ */ jsxRuntime.jsx(
1881
2159
  "div",
@@ -1887,7 +2165,7 @@ const Toolbar = () => {
1887
2165
  /* @__PURE__ */ jsxRuntime.jsx(
1888
2166
  ToolbarIconButton,
1889
2167
  {
1890
- title: "Undo",
2168
+ title: t.toolbar.undo,
1891
2169
  testId: "btn-undo",
1892
2170
  disabled: !canUndo,
1893
2171
  onClick: undo,
@@ -1897,7 +2175,7 @@ const Toolbar = () => {
1897
2175
  /* @__PURE__ */ jsxRuntime.jsx(
1898
2176
  ToolbarIconButton,
1899
2177
  {
1900
- title: "Redo",
2178
+ title: t.toolbar.redo,
1901
2179
  testId: "btn-redo",
1902
2180
  disabled: !canRedo,
1903
2181
  onClick: redo,
@@ -1911,31 +2189,41 @@ const Toolbar = () => {
1911
2189
  {
1912
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",
1913
2191
  "data-testid": "font-selector",
1914
- defaultValue: "Arial",
2192
+ defaultValue: "Inter",
1915
2193
  onChange: handleFontChange,
1916
2194
  children: SUPPORTED_FONTS.map((font) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: font, style: { fontFamily: font }, children: font }, font))
1917
2195
  }
1918
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
+ ),
1919
2207
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
1920
2208
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "format-group", children: [
1921
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Bold (Ctrl+B)", testId: "btn-bold", onClick: handleBold, children: /* @__PURE__ */ jsxRuntime.jsx(Bold, { size: 15 }) }),
1922
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Italic (Ctrl+I)", testId: "btn-italic", onClick: handleItalic, children: /* @__PURE__ */ jsxRuntime.jsx(Italic, { size: 15 }) }),
1923
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Underline (Ctrl+U)", testId: "btn-underline", onClick: handleUnderline, children: /* @__PURE__ */ jsxRuntime.jsx(Underline, { size: 15 }) }),
1924
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Strikethrough", testId: "btn-strike", onClick: handleStrikethrough, children: /* @__PURE__ */ jsxRuntime.jsx(Strikethrough, { size: 15 }) })
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 }) })
1925
2213
  ] }),
1926
2214
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
1927
2215
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "align-group", children: [
1928
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Align Left", testId: "btn-align-left", onClick: handleAlignLeft, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignStart, { size: 15 }) }),
1929
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Align Center", testId: "btn-align-center", onClick: handleAlignCenter, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignCenter, { size: 15 }) }),
1930
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Align Right", testId: "btn-align-right", onClick: handleAlignRight, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignEnd, { size: 15 }) }),
1931
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Justify", testId: "btn-align-justify", onClick: handleAlignJustify, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignJustify, { size: 15 }) })
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 }) })
1932
2220
  ] }),
1933
2221
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
1934
2222
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "list-group", children: [
1935
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Numbered List", testId: "btn-list-number", onClick: handleListNumber, children: /* @__PURE__ */ jsxRuntime.jsx(ListOrdered, { size: 15 }) }),
1936
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Bullet List", testId: "btn-list-bullet", onClick: handleListBullet, children: /* @__PURE__ */ jsxRuntime.jsx(List, { size: 15 }) }),
1937
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Indent", testId: "btn-indent", onClick: handleIndent, children: /* @__PURE__ */ jsxRuntime.jsx(ListIndentIncrease, { size: 15 }) }),
1938
- /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: "Outdent", testId: "btn-outdent", onClick: handleOutdent, children: /* @__PURE__ */ jsxRuntime.jsx(ListIndentDecrease, { size: 15 }) })
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 }) })
1939
2227
  ] }),
1940
2228
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
1941
2229
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1962,10 +2250,14 @@ const Toolbar = () => {
1962
2250
  }
1963
2251
  )
1964
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
+ ] }),
1965
2257
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
1966
2258
  ToolbarIconButton,
1967
2259
  {
1968
- title: historySidebarOpen ? "Close History" : "Open History",
2260
+ title: historySidebarOpen ? t.toolbar.closeHistory : t.toolbar.openHistory,
1969
2261
  testId: "toggle-history-sidebar",
1970
2262
  active: historySidebarOpen,
1971
2263
  onClick: () => setHistorySidebarOpen(!historySidebarOpen),
@@ -2268,12 +2560,13 @@ const lexicalTheme = {
2268
2560
  },
2269
2561
  quote: "border-l-4 border-gray-300 pl-4 italic text-gray-600"
2270
2562
  };
2271
- function createEditorConfig(mode, pageId) {
2563
+ const DEFAULT_NODES = [richText.HeadingNode, richText.QuoteNode, list.ListNode, list.ListItemNode];
2564
+ function createEditorConfig(mode, pageId, extraNodes = [], themeOverrides = {}) {
2272
2565
  const namespace = pageId ? `lex4-${mode}-${pageId}` : `lex4-${mode}`;
2273
2566
  return {
2274
2567
  namespace,
2275
- theme: lexicalTheme,
2276
- nodes: [richText.HeadingNode, richText.QuoteNode, list.ListNode, list.ListItemNode],
2568
+ theme: { ...lexicalTheme, ...themeOverrides },
2569
+ nodes: [...DEFAULT_NODES, ...extraNodes],
2277
2570
  onError: (error) => {
2278
2571
  console.error(`[Lex4 ${mode} editor error]`, error);
2279
2572
  }
@@ -2851,11 +3144,12 @@ const PageBody = ({
2851
3144
  onMoveToNextPage,
2852
3145
  readOnly = false
2853
3146
  }) => {
3147
+ const { nodes, bodyPlugins, themeOverrides } = useExtensions();
2854
3148
  const config = React.useMemo(
2855
3149
  () => {
2856
3150
  var _a, _b;
2857
3151
  const baseConfig = {
2858
- ...createEditorConfig("body", pageId),
3152
+ ...createEditorConfig("body", pageId, nodes, themeOverrides),
2859
3153
  editable: !readOnly
2860
3154
  };
2861
3155
  if (initialBodyState) {
@@ -2869,7 +3163,7 @@ const PageBody = ({
2869
3163
  return baseConfig;
2870
3164
  },
2871
3165
  // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally only use initialBodyState at mount time
2872
- [pageId, readOnly]
3166
+ [pageId, readOnly, nodes, themeOverrides]
2873
3167
  );
2874
3168
  const handleChange = React.useCallback(
2875
3169
  (editorState) => {
@@ -2925,6 +3219,7 @@ const PageBody = ({
2925
3219
  }
2926
3220
  ),
2927
3221
  /* @__PURE__ */ jsxRuntime.jsx(OverflowPlugin, { onOverflow: handleOverflow }),
3222
+ bodyPlugins.map((Plugin, idx) => /* @__PURE__ */ jsxRuntime.jsx(Plugin, {}, idx)),
2928
3223
  /* @__PURE__ */ jsxRuntime.jsx(LexicalOnChangePlugin.OnChangePlugin, { onChange: handleChange, ignoreSelectionChange: true })
2929
3224
  ] })
2930
3225
  }
@@ -2994,16 +3289,18 @@ const PageHeader = ({
2994
3289
  onHeaderChange
2995
3290
  }) => {
2996
3291
  const hasPageCounter = !!pageCounterLabel;
3292
+ const { nodes, themeOverrides } = useExtensions();
3293
+ const t = useTranslations();
2997
3294
  const config = React.useMemo(
2998
3295
  () => {
2999
- const baseConfig = createEditorConfig("header", pageId);
3296
+ const baseConfig = createEditorConfig("header", pageId, nodes, themeOverrides);
3000
3297
  if (initialHeaderState) {
3001
3298
  return { ...baseConfig, editorState: JSON.stringify(initialHeaderState) };
3002
3299
  }
3003
3300
  return baseConfig;
3004
3301
  },
3005
3302
  // eslint-disable-next-line react-hooks/exhaustive-deps -- syncVersion forces remount via key, only used at init
3006
- [pageId]
3303
+ [pageId, nodes, themeOverrides]
3007
3304
  );
3008
3305
  const contentRef = React.useRef(null);
3009
3306
  const handleChange = React.useCallback(
@@ -3029,7 +3326,7 @@ const PageHeader = ({
3029
3326
  return /* @__PURE__ */ jsxRuntime.jsxs(
3030
3327
  "div",
3031
3328
  {
3032
- className: "lex4-page-header border-b border-dashed border-gray-200 relative flex-shrink-0",
3329
+ className: "lex4-page-header bg-blue-50/60 border-t-2 border-t-blue-200 border-b border-dashed border-blue-100 relative flex-shrink-0",
3033
3330
  style: { maxHeight: MAX_HEADER_HEIGHT_PX, overflow: "clip" },
3034
3331
  "data-testid": `page-header-${pageId}`,
3035
3332
  children: [
@@ -3044,7 +3341,7 @@ const PageHeader = ({
3044
3341
  className: `outline-none p-2 text-sm text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
3045
3342
  }
3046
3343
  ),
3047
- 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: "Header" }),
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 }),
3048
3345
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
3049
3346
  }
3050
3347
  ),
@@ -3072,16 +3369,18 @@ const PageFooter = ({
3072
3369
  onFooterChange
3073
3370
  }) => {
3074
3371
  const hasPageCounter = !!pageCounterLabel;
3372
+ const { nodes, themeOverrides } = useExtensions();
3373
+ const t = useTranslations();
3075
3374
  const config = React.useMemo(
3076
3375
  () => {
3077
- const baseConfig = createEditorConfig("footer", pageId);
3376
+ const baseConfig = createEditorConfig("footer", pageId, nodes, themeOverrides);
3078
3377
  if (initialFooterState) {
3079
3378
  return { ...baseConfig, editorState: JSON.stringify(initialFooterState) };
3080
3379
  }
3081
3380
  return baseConfig;
3082
3381
  },
3083
3382
  // eslint-disable-next-line react-hooks/exhaustive-deps -- syncVersion forces remount via key, only used at init
3084
- [pageId]
3383
+ [pageId, nodes, themeOverrides]
3085
3384
  );
3086
3385
  const contentRef = React.useRef(null);
3087
3386
  const handleChange = React.useCallback(
@@ -3107,7 +3406,7 @@ const PageFooter = ({
3107
3406
  return /* @__PURE__ */ jsxRuntime.jsxs(
3108
3407
  "div",
3109
3408
  {
3110
- className: "lex4-page-footer border-t border-dashed border-gray-200 relative flex-shrink-0",
3409
+ className: "lex4-page-footer bg-blue-50/60 border-b-2 border-b-blue-200 border-t border-dashed border-blue-100 relative flex-shrink-0",
3111
3410
  style: { maxHeight: MAX_FOOTER_HEIGHT_PX, overflow: "clip" },
3112
3411
  "data-testid": `page-footer-${pageId}`,
3113
3412
  children: [
@@ -3122,7 +3421,7 @@ const PageFooter = ({
3122
3421
  className: `outline-none p-2 text-sm text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
3123
3422
  }
3124
3423
  ),
3125
- 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: "Footer" }),
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 }),
3126
3425
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
3127
3426
  }
3128
3427
  ),
@@ -3188,7 +3487,7 @@ const PageView = React.memo(({
3188
3487
  return /* @__PURE__ */ jsxRuntime.jsxs(
3189
3488
  "div",
3190
3489
  {
3191
- className: "lex4-page bg-white shadow-sm flex flex-col",
3490
+ className: "lex4-page bg-white shadow-xl flex flex-col",
3192
3491
  style: {
3193
3492
  width: A4_WIDTH_PX,
3194
3493
  height: A4_HEIGHT_PX,
@@ -3524,6 +3823,7 @@ const EditorChrome = ({
3524
3823
  undo,
3525
3824
  redo
3526
3825
  } = useDocument();
3826
+ const { sidePanels } = useExtensions();
3527
3827
  const rootRef = React.useRef(null);
3528
3828
  const selectionBufferRef = React.useRef(null);
3529
3829
  const clearGlobalSelection = React.useCallback(() => {
@@ -3665,35 +3965,63 @@ const EditorChrome = ({
3665
3965
  }
3666
3966
  ),
3667
3967
  /* @__PURE__ */ jsxRuntime.jsx(Toolbar, {}),
3668
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden bg-gray-200", children: [
3968
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden bg-gray-700", children: [
3669
3969
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(DocumentView, {}) }),
3970
+ sidePanels.map((Panel, idx) => /* @__PURE__ */ jsxRuntime.jsx(Panel, {}, idx)),
3670
3971
  /* @__PURE__ */ jsxRuntime.jsx(HistorySidebar, {})
3671
3972
  ] })
3672
3973
  ]
3673
3974
  }
3674
3975
  );
3675
3976
  };
3676
- const Lex4Editor = ({
3977
+ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, className }, ref) => {
3978
+ const { document: doc, activeEditor } = useDocument();
3979
+ const { handleFactories } = useExtensions();
3980
+ const getDocument = React.useCallback(() => doc, [doc]);
3981
+ const getActiveEditor = React.useCallback(() => activeEditor, [activeEditor]);
3982
+ const extensionCtx = useExtensionContext(getDocument, getActiveEditor);
3983
+ React.useImperativeHandle(ref, () => {
3984
+ const handle = {};
3985
+ for (const factory of handleFactories) {
3986
+ const methods = factory(extensionCtx);
3987
+ Object.assign(handle, methods);
3988
+ }
3989
+ return handle;
3990
+ }, [extensionCtx, handleFactories]);
3991
+ return /* @__PURE__ */ jsxRuntime.jsx(
3992
+ EditorChrome,
3993
+ {
3994
+ captureHistoryShortcutsOnWindow,
3995
+ className
3996
+ }
3997
+ );
3998
+ });
3999
+ EditorWithHandle.displayName = "EditorWithHandle";
4000
+ const Lex4Editor = React.forwardRef(({
3677
4001
  captureHistoryShortcutsOnWindow = true,
3678
4002
  initialDocument,
3679
4003
  onDocumentChange,
4004
+ extensions,
4005
+ translations,
3680
4006
  className
3681
- }) => {
3682
- return /* @__PURE__ */ jsxRuntime.jsx(
4007
+ }, ref) => {
4008
+ return /* @__PURE__ */ jsxRuntime.jsx(TranslationsProvider, { translations, children: /* @__PURE__ */ jsxRuntime.jsx(ExtensionStateProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(ExtensionProvider, { extensions, children: /* @__PURE__ */ jsxRuntime.jsx(
3683
4009
  DocumentProvider,
3684
4010
  {
3685
4011
  initialDocument,
3686
4012
  onDocumentChange,
3687
4013
  children: /* @__PURE__ */ jsxRuntime.jsx(
3688
- EditorChrome,
4014
+ EditorWithHandle,
3689
4015
  {
4016
+ ref,
3690
4017
  captureHistoryShortcutsOnWindow,
3691
4018
  className
3692
4019
  }
3693
4020
  )
3694
4021
  }
3695
- );
3696
- };
4022
+ ) }) }) });
4023
+ });
4024
+ Lex4Editor.displayName = "Lex4Editor";
3697
4025
  function useOverflowDetection(bodyHeight, onOverflow, debounceMs = 100) {
3698
4026
  const timerRef = React.useRef(null);
3699
4027
  const observerRef = React.useRef(null);
@@ -3759,16 +4087,700 @@ function useHeaderFooter(maxHeight, onHeightChange) {
3759
4087
  }, []);
3760
4088
  return { attachRef };
3761
4089
  }
4090
+ const AST_VERSION = "1.0.0";
4091
+ const IS_BOLD = 1;
4092
+ const IS_ITALIC = 2;
4093
+ const IS_STRIKETHROUGH = 4;
4094
+ const IS_UNDERLINE = 8;
4095
+ function decodeFormatBitmask(format) {
4096
+ const marks = {};
4097
+ if (format & IS_BOLD) marks.bold = true;
4098
+ if (format & IS_ITALIC) marks.italic = true;
4099
+ if (format & IS_UNDERLINE) marks.underline = true;
4100
+ if (format & IS_STRIKETHROUGH) marks.strikethrough = true;
4101
+ return marks;
4102
+ }
4103
+ function extractFontFamily(style) {
4104
+ const match = style.match(/font-family:\s*([^;]+)/);
4105
+ return match ? match[1].trim().replace(/['"]/g, "") : void 0;
4106
+ }
4107
+ function extractFontSizePt(style) {
4108
+ const match = style.match(/font-size:\s*(\d+(?:\.\d+)?)\s*pt/);
4109
+ return match ? parseFloat(match[1]) : void 0;
4110
+ }
4111
+ function buildTextMarks(format, style) {
4112
+ const formatMarks = decodeFormatBitmask(format);
4113
+ const fontFamily = style ? extractFontFamily(style) : void 0;
4114
+ const fontSize = style ? extractFontSizePt(style) : void 0;
4115
+ const marks = {
4116
+ ...formatMarks,
4117
+ ...fontFamily ? { fontFamily } : {},
4118
+ ...fontSize ? { fontSize } : {}
4119
+ };
4120
+ return Object.keys(marks).length > 0 ? marks : void 0;
4121
+ }
4122
+ function mapInlineNode(node) {
4123
+ switch (node.type) {
4124
+ case "text":
4125
+ return mapTextNode(node);
4126
+ case "variable-node":
4127
+ return mapVariableNode(node);
4128
+ case "linebreak":
4129
+ return mapLineBreak();
4130
+ default:
4131
+ return { type: "text", text: "" };
4132
+ }
4133
+ }
4134
+ function mapTextNode(node) {
4135
+ const marks = buildTextMarks(node.format, node.style);
4136
+ return {
4137
+ type: "text",
4138
+ text: node.text,
4139
+ ...marks ? { marks } : {}
4140
+ };
4141
+ }
4142
+ function mapVariableNode(node) {
4143
+ return {
4144
+ type: "variable",
4145
+ key: node.variableKey
4146
+ };
4147
+ }
4148
+ function mapLineBreak() {
4149
+ return { type: "linebreak" };
4150
+ }
4151
+ function mapInlineNodes(nodes) {
4152
+ return nodes.map(mapInlineNode);
4153
+ }
4154
+ const ALIGN_LEFT = 1;
4155
+ const ALIGN_CENTER = 2;
4156
+ const ALIGN_RIGHT = 3;
4157
+ const ALIGN_JUSTIFY = 4;
4158
+ function decodeAlignment(format) {
4159
+ if (typeof format === "string") {
4160
+ if (["left", "center", "right", "justify"].includes(format)) {
4161
+ return format;
4162
+ }
4163
+ return void 0;
4164
+ }
4165
+ if (typeof format !== "number" || format === 0) return void 0;
4166
+ switch (format) {
4167
+ case ALIGN_LEFT:
4168
+ return "left";
4169
+ case ALIGN_CENTER:
4170
+ return "center";
4171
+ case ALIGN_RIGHT:
4172
+ return "right";
4173
+ case ALIGN_JUSTIFY:
4174
+ return "justify";
4175
+ default:
4176
+ return void 0;
4177
+ }
4178
+ }
4179
+ function mapBlockNode(node) {
4180
+ switch (node.type) {
4181
+ case "paragraph":
4182
+ return mapParagraph(node);
4183
+ case "heading":
4184
+ return mapHeading(node);
4185
+ case "list":
4186
+ return mapList(node);
4187
+ case "quote":
4188
+ return mapBlockQuote(node);
4189
+ default:
4190
+ return {
4191
+ type: "paragraph",
4192
+ children: mapInlineChildren(node)
4193
+ };
4194
+ }
4195
+ }
4196
+ function mapParagraph(node) {
4197
+ const alignment = decodeAlignment(node.format);
4198
+ const indent = node.indent && node.indent > 0 ? node.indent : void 0;
4199
+ return {
4200
+ type: "paragraph",
4201
+ ...alignment ? { alignment } : {},
4202
+ ...indent ? { indent } : {},
4203
+ children: mapInlineChildren(node)
4204
+ };
4205
+ }
4206
+ function mapHeading(node) {
4207
+ var _a;
4208
+ const alignment = decodeAlignment(node.format);
4209
+ const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h(\d)$/);
4210
+ const level = tagMatch ? parseInt(tagMatch[1], 10) : 1;
4211
+ return {
4212
+ type: "heading",
4213
+ level,
4214
+ ...alignment ? { alignment } : {},
4215
+ children: mapInlineChildren(node)
4216
+ };
4217
+ }
4218
+ function mapList(node) {
4219
+ const listType = node.listType === "number" ? "ordered" : "unordered";
4220
+ const items = (node.children ?? []).filter((c) => c.type === "listitem").map(mapListItem);
4221
+ return {
4222
+ type: "list",
4223
+ listType,
4224
+ items
4225
+ };
4226
+ }
4227
+ function mapListItem(node) {
4228
+ const inlineChildren = [];
4229
+ let nestedList;
4230
+ for (const child of node.children ?? []) {
4231
+ if (child.type === "list") {
4232
+ nestedList = mapList(child);
4233
+ } else {
4234
+ const mapped = mapInlineNodes([child]);
4235
+ inlineChildren.push(...mapped);
4236
+ }
4237
+ }
4238
+ return {
4239
+ type: "list-item",
4240
+ children: inlineChildren,
4241
+ ...nestedList ? { nestedList } : {}
4242
+ };
4243
+ }
4244
+ function mapBlockQuote(node) {
4245
+ return {
4246
+ type: "blockquote",
4247
+ children: mapInlineChildren(node)
4248
+ };
4249
+ }
4250
+ function mapInlineChildren(node) {
4251
+ if (!node.children || node.children.length === 0) return [];
4252
+ return mapInlineNodes(node.children);
4253
+ }
4254
+ function mapBlockNodes(nodes) {
4255
+ return nodes.map(mapBlockNode);
4256
+ }
4257
+ function mapEditorStateToContent(state) {
4258
+ if (!state || !state.root || !state.root.children) {
4259
+ return null;
4260
+ }
4261
+ const blocks = mapBlockNodes(state.root.children);
4262
+ return { blocks };
4263
+ }
4264
+ function mapEditorStateToBlocks(state) {
4265
+ if (!state || !state.root || !state.root.children) {
4266
+ return [];
4267
+ }
4268
+ return mapBlockNodes(state.root.children);
4269
+ }
4270
+ const MARGIN_MM = Math.round(PAGE_MARGIN_PX / PX_PER_MM * 10) / 10;
4271
+ function serializeDocument(document2, variableDefinitions = []) {
4272
+ const pages = document2.pages.map(
4273
+ (page, index) => serializePage(page, index)
4274
+ );
4275
+ const metadata = buildMetadata(variableDefinitions);
4276
+ return {
4277
+ version: AST_VERSION,
4278
+ page: {
4279
+ format: "A4",
4280
+ widthMm: 210,
4281
+ heightMm: 297,
4282
+ margins: {
4283
+ topMm: MARGIN_MM,
4284
+ rightMm: MARGIN_MM,
4285
+ bottomMm: MARGIN_MM,
4286
+ leftMm: MARGIN_MM
4287
+ }
4288
+ },
4289
+ headerFooter: {
4290
+ enabled: document2.headerFooterEnabled,
4291
+ pageCounterMode: document2.pageCounterMode,
4292
+ defaultHeader: document2.pages.length > 0 ? mapEditorStateToContent(document2.pages[0].headerState) : null,
4293
+ defaultFooter: document2.pages.length > 0 ? mapEditorStateToContent(document2.pages[0].footerState) : null
4294
+ },
4295
+ pages,
4296
+ metadata
4297
+ };
4298
+ }
4299
+ function serializePage(page, pageIndex) {
4300
+ return {
4301
+ pageIndex,
4302
+ body: mapEditorStateToBlocks(page.bodyState),
4303
+ header: mapEditorStateToContent(page.headerState),
4304
+ footer: mapEditorStateToContent(page.footerState)
4305
+ };
4306
+ }
4307
+ function buildMetadata(variableDefinitions) {
4308
+ const variables = {};
4309
+ for (const def of variableDefinitions) {
4310
+ variables[def.key] = {
4311
+ key: def.key,
4312
+ label: def.label,
4313
+ ...def.description ? { description: def.description } : {},
4314
+ ...def.valueType ? { valueType: def.valueType } : {},
4315
+ ...def.group ? { group: def.group } : {}
4316
+ };
4317
+ }
4318
+ return { variables };
4319
+ }
4320
+ function buildSavePayload(ast, options) {
4321
+ return {
4322
+ document: ast,
4323
+ ...(options == null ? void 0 : options.exportTarget) ? { exportTarget: options.exportTarget } : {},
4324
+ ...(options == null ? void 0 : options.documentId) ? { documentId: options.documentId } : {},
4325
+ ...(options == null ? void 0 : options.metadata) ? { metadata: options.metadata } : {}
4326
+ };
4327
+ }
4328
+ function serializeDocumentJson(ast) {
4329
+ return JSON.stringify(ast, null, 2);
4330
+ }
4331
+ function astExtension() {
4332
+ return {
4333
+ name: "ast",
4334
+ handleMethods: (ctx) => {
4335
+ const getDefinitions = () => {
4336
+ return ctx.getExtensionState("variableDefinitions") ?? [];
4337
+ };
4338
+ return {
4339
+ getDocumentAst: () => {
4340
+ const doc = ctx.getDocument();
4341
+ return serializeDocument(doc, getDefinitions());
4342
+ },
4343
+ getDocumentJson: () => {
4344
+ const doc = ctx.getDocument();
4345
+ const ast = serializeDocument(doc, getDefinitions());
4346
+ return serializeDocumentJson(ast);
4347
+ },
4348
+ buildSavePayload: (options) => {
4349
+ const doc = ctx.getDocument();
4350
+ const ast = serializeDocument(doc, getDefinitions());
4351
+ return buildSavePayload(ast, options);
4352
+ }
4353
+ };
4354
+ }
4355
+ };
4356
+ }
4357
+ class VariableNode extends lexical.DecoratorNode {
4358
+ constructor(variableKey, key) {
4359
+ super(key);
4360
+ __publicField(this, "__variableKey");
4361
+ this.__variableKey = variableKey;
4362
+ }
4363
+ static getType() {
4364
+ return "variable-node";
4365
+ }
4366
+ static clone(node) {
4367
+ return new VariableNode(node.__variableKey, node.__key);
4368
+ }
4369
+ getVariableKey() {
4370
+ return this.__variableKey;
4371
+ }
4372
+ // -- Serialization --
4373
+ static importJSON(serializedNode) {
4374
+ return $createVariableNode(serializedNode.variableKey);
4375
+ }
4376
+ exportJSON() {
4377
+ return {
4378
+ type: "variable-node",
4379
+ version: 1,
4380
+ variableKey: this.__variableKey
4381
+ };
4382
+ }
4383
+ // -- DOM --
4384
+ createDOM() {
4385
+ const span = document.createElement("span");
4386
+ span.className = "lex4-variable";
4387
+ span.setAttribute("data-variable-key", this.__variableKey);
4388
+ span.setAttribute("data-testid", `variable-${this.__variableKey}`);
4389
+ span.contentEditable = "false";
4390
+ return span;
4391
+ }
4392
+ updateDOM() {
4393
+ return false;
4394
+ }
4395
+ exportDOM() {
4396
+ const span = document.createElement("span");
4397
+ span.setAttribute("data-variable-key", this.__variableKey);
4398
+ span.textContent = `{{${this.__variableKey}}}`;
4399
+ return { element: span };
4400
+ }
4401
+ static importDOM() {
4402
+ return null;
4403
+ }
4404
+ // -- Behavior --
4405
+ isInline() {
4406
+ return true;
4407
+ }
4408
+ isKeyboardSelectable() {
4409
+ return true;
4410
+ }
4411
+ getTextContent() {
4412
+ return `{{${this.__variableKey}}}`;
4413
+ }
4414
+ // -- Rendering --
4415
+ decorate() {
4416
+ return /* @__PURE__ */ jsxRuntime.jsx(
4417
+ "span",
4418
+ {
4419
+ className: "lex4-variable-chip inline-flex items-center rounded bg-white px-1.5 py-0.5\n text-xs font-medium text-blue-700 border border-blue-300 select-none\n cursor-default whitespace-nowrap mx-0.5",
4420
+ "data-testid": `variable-chip-${this.__variableKey}`,
4421
+ title: this.__variableKey,
4422
+ children: `{{${this.__variableKey}}}`
4423
+ }
4424
+ );
4425
+ }
4426
+ }
4427
+ function $createVariableNode(variableKey) {
4428
+ return lexical.$applyNodeReplacement(new VariableNode(variableKey));
4429
+ }
4430
+ function $isVariableNode(node) {
4431
+ return node instanceof VariableNode;
4432
+ }
4433
+ const INSERT_VARIABLE_COMMAND = lexical.createCommand("INSERT_VARIABLE");
4434
+ const VariablePlugin = () => {
4435
+ const [editor] = LexicalComposerContext.useLexicalComposerContext();
4436
+ React.useEffect(() => {
4437
+ return editor.registerCommand(
4438
+ INSERT_VARIABLE_COMMAND,
4439
+ (variableKey) => {
4440
+ editor.update(() => {
4441
+ const selection = lexical.$getSelection();
4442
+ if (!lexical.$isRangeSelection(selection)) return;
4443
+ const variableNode = $createVariableNode(variableKey);
4444
+ lexical.$insertNodes([variableNode]);
4445
+ });
4446
+ return true;
4447
+ },
4448
+ lexical.COMMAND_PRIORITY_EDITOR
4449
+ );
4450
+ }, [editor]);
4451
+ return null;
4452
+ };
4453
+ const EMPTY_CONTEXT = {
4454
+ definitions: [],
4455
+ refreshDefinitions: () => {
4456
+ },
4457
+ getDefinition: () => void 0
4458
+ };
4459
+ const VariableContext = React.createContext(EMPTY_CONTEXT);
4460
+ const VariableProvider = ({
4461
+ initialDefinitions = [],
4462
+ children
4463
+ }) => {
4464
+ const [definitions, setDefinitions] = React.useState(initialDefinitions);
4465
+ const refresh = React.useCallback((newDefinitions) => {
4466
+ setDefinitions(newDefinitions);
4467
+ }, []);
4468
+ const getDefinition = React.useCallback(
4469
+ (key) => {
4470
+ return definitions.find((d) => d.key === key);
4471
+ },
4472
+ [definitions]
4473
+ );
4474
+ const value = React.useMemo(
4475
+ () => ({ definitions, refreshDefinitions: refresh, getDefinition }),
4476
+ [definitions, refresh, getDefinition]
4477
+ );
4478
+ return /* @__PURE__ */ jsxRuntime.jsx(VariableContext.Provider, { value, children });
4479
+ };
4480
+ function useVariables() {
4481
+ return React.useContext(VariableContext);
4482
+ }
4483
+ const VariablePicker = ({ onInsert, disabled = false }) => {
4484
+ const { definitions } = useVariables();
4485
+ const [open, setOpen] = React.useState(false);
4486
+ const [filter, setFilter] = React.useState("");
4487
+ const containerRef = React.useRef(null);
4488
+ const filtered = React.useMemo(() => {
4489
+ if (!filter) return definitions;
4490
+ const lower = filter.toLowerCase();
4491
+ return definitions.filter(
4492
+ (d) => d.key.toLowerCase().includes(lower) || d.label.toLowerCase().includes(lower)
4493
+ );
4494
+ }, [definitions, filter]);
4495
+ const grouped = React.useMemo(() => {
4496
+ const groups = {};
4497
+ for (const def of filtered) {
4498
+ const g = def.group ?? "Other";
4499
+ if (!groups[g]) groups[g] = [];
4500
+ groups[g].push(def);
4501
+ }
4502
+ return groups;
4503
+ }, [filtered]);
4504
+ const handleInsert = React.useCallback(
4505
+ (key) => {
4506
+ onInsert(key);
4507
+ setOpen(false);
4508
+ setFilter("");
4509
+ },
4510
+ [onInsert]
4511
+ );
4512
+ React.useEffect(() => {
4513
+ if (!open) return;
4514
+ const handler = (e) => {
4515
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
4516
+ setOpen(false);
4517
+ setFilter("");
4518
+ }
4519
+ };
4520
+ document.addEventListener("mousedown", handler);
4521
+ return () => document.removeEventListener("mousedown", handler);
4522
+ }, [open]);
4523
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative inline-block", children: [
4524
+ /* @__PURE__ */ jsxRuntime.jsx(
4525
+ "button",
4526
+ {
4527
+ type: "button",
4528
+ className: "h-7 rounded border border-gray-200 bg-white px-2 text-xs font-medium text-gray-700\n hover:bg-gray-50 focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-400\n disabled:opacity-50 disabled:cursor-not-allowed",
4529
+ "data-testid": "variable-picker-button",
4530
+ disabled: disabled || definitions.length === 0,
4531
+ onClick: () => setOpen(!open),
4532
+ title: "Insert variable",
4533
+ children: "{x}"
4534
+ }
4535
+ ),
4536
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
4537
+ "div",
4538
+ {
4539
+ className: "absolute left-0 top-full z-50 mt-1 w-64 rounded-md border border-gray-200\n bg-white shadow-lg",
4540
+ "data-testid": "variable-picker-dropdown",
4541
+ children: [
4542
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-gray-100 p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
4543
+ "input",
4544
+ {
4545
+ type: "text",
4546
+ className: "w-full rounded border border-gray-200 px-2 py-1 text-xs\n focus:border-blue-400 focus:outline-none",
4547
+ placeholder: "Search variables...",
4548
+ "data-testid": "variable-picker-search",
4549
+ value: filter,
4550
+ onChange: (e) => setFilter(e.target.value),
4551
+ autoFocus: true
4552
+ }
4553
+ ) }),
4554
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-h-48 overflow-y-auto p-1", children: [
4555
+ Object.keys(grouped).length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1 text-xs text-gray-400", children: "No variables found" }),
4556
+ Object.entries(grouped).map(([group, defs]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
4557
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1 text-[10px] font-semibold uppercase text-gray-400", children: group }),
4558
+ defs.map((def) => /* @__PURE__ */ jsxRuntime.jsxs(
4559
+ "button",
4560
+ {
4561
+ type: "button",
4562
+ className: "flex w-full items-center gap-2 rounded px-2 py-1 text-left text-xs\n hover:bg-blue-50",
4563
+ "data-testid": `variable-option-${def.key}`,
4564
+ onClick: () => handleInsert(def.key),
4565
+ children: [
4566
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-blue-700", children: `{{${def.key}}}` }),
4567
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500", children: def.label })
4568
+ ]
4569
+ },
4570
+ def.key
4571
+ ))
4572
+ ] }, group))
4573
+ ] })
4574
+ ]
4575
+ }
4576
+ )
4577
+ ] });
4578
+ };
4579
+ const VariablePanel = ({ open, onClose }) => {
4580
+ const { definitions } = useVariables();
4581
+ const { activeEditor, runHistoryAction } = useDocument();
4582
+ const t = useTranslations();
4583
+ const [filter, setFilter] = React.useState("");
4584
+ const filtered = React.useMemo(() => {
4585
+ if (!filter) return definitions;
4586
+ const lower = filter.toLowerCase();
4587
+ return definitions.filter(
4588
+ (d) => d.key.toLowerCase().includes(lower) || d.label.toLowerCase().includes(lower)
4589
+ );
4590
+ }, [definitions, filter]);
4591
+ const grouped = React.useMemo(() => {
4592
+ const groups = {};
4593
+ for (const def of filtered) {
4594
+ const g = def.group ?? "Other";
4595
+ if (!groups[g]) groups[g] = [];
4596
+ groups[g].push(def);
4597
+ }
4598
+ return groups;
4599
+ }, [filtered]);
4600
+ const handleInsert = React.useCallback(
4601
+ (key) => {
4602
+ if (!activeEditor) return;
4603
+ runHistoryAction(
4604
+ { label: interpolate(t.variables.insertVariable, { key }), source: "toolbar", region: "document" },
4605
+ () => {
4606
+ activeEditor.dispatchCommand(INSERT_VARIABLE_COMMAND, key);
4607
+ }
4608
+ );
4609
+ },
4610
+ [activeEditor, runHistoryAction]
4611
+ );
4612
+ if (definitions.length === 0) return null;
4613
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4614
+ EditorSidebar,
4615
+ {
4616
+ title: t.variables.title,
4617
+ subtitle: interpolate(t.variables.available, { count: String(definitions.length) }),
4618
+ open,
4619
+ onClose,
4620
+ testId: "variable-panel",
4621
+ headerActions: /* @__PURE__ */ jsxRuntime.jsx(
4622
+ "button",
4623
+ {
4624
+ type: "button",
4625
+ className: "flex h-6 w-6 items-center justify-center rounded text-gray-400\n transition-colors hover:bg-gray-100 hover:text-gray-600",
4626
+ title: t.variables.refreshVariables,
4627
+ "data-testid": "btn-refresh-variables",
4628
+ children: /* @__PURE__ */ jsxRuntime.jsx(RefreshCw, { size: 12 })
4629
+ }
4630
+ ),
4631
+ children: [
4632
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
4633
+ /* @__PURE__ */ jsxRuntime.jsx(Search, { size: 14, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-400" }),
4634
+ /* @__PURE__ */ jsxRuntime.jsx(
4635
+ "input",
4636
+ {
4637
+ type: "text",
4638
+ className: "w-full rounded-lg border border-gray-200 bg-gray-50 py-1.5 pl-8 pr-3 text-xs\n placeholder-gray-400 focus:border-blue-400 focus:bg-white focus:outline-none\n focus:ring-1 focus:ring-blue-400",
4639
+ placeholder: t.variables.searchPlaceholder,
4640
+ "data-testid": "variable-panel-search",
4641
+ value: filter,
4642
+ onChange: (e) => setFilter(e.target.value)
4643
+ }
4644
+ )
4645
+ ] }) }),
4646
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 pb-3", children: [
4647
+ Object.keys(grouped).length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-4 text-center text-xs text-gray-400", children: t.variables.noVariablesFound }),
4648
+ Object.entries(grouped).map(([group, defs]) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1", children: defs.map((def) => /* @__PURE__ */ jsxRuntime.jsxs(
4649
+ "button",
4650
+ {
4651
+ type: "button",
4652
+ className: "flex items-center justify-between gap-2 rounded-lg px-2.5 py-1.5 text-left text-xs\n transition-colors hover:bg-blue-50 group",
4653
+ "data-testid": `variable-panel-${def.key}`,
4654
+ onClick: () => handleInsert(def.key),
4655
+ disabled: !activeEditor,
4656
+ children: [
4657
+ /* @__PURE__ */ jsxRuntime.jsx(
4658
+ "span",
4659
+ {
4660
+ className: "inline-flex items-center rounded-full border border-blue-300 bg-white\n px-2 py-0.5 text-[11px] font-medium text-blue-700\n group-hover:border-blue-400 group-hover:bg-blue-50",
4661
+ children: def.label
4662
+ }
4663
+ ),
4664
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-gray-400", children: group })
4665
+ ]
4666
+ },
4667
+ def.key
4668
+ )) }) }, group))
4669
+ ] })
4670
+ ]
4671
+ }
4672
+ );
4673
+ };
4674
+ const VariablePanelContext = React.createContext({
4675
+ panelOpen: false,
4676
+ setPanelOpen: () => {
4677
+ }
4678
+ });
4679
+ function useVariablePanelState() {
4680
+ return React.useContext(VariablePanelContext);
4681
+ }
4682
+ const VariablePanelStateProvider = ({ children }) => {
4683
+ const [panelOpen, setPanelOpen] = React.useState(false);
4684
+ return /* @__PURE__ */ jsxRuntime.jsx(VariablePanelContext.Provider, { value: { panelOpen, setPanelOpen }, children });
4685
+ };
4686
+ const VariableToolbarItem = () => {
4687
+ const { activeEditor, runHistoryAction } = useDocument();
4688
+ const t = useTranslations();
4689
+ const handleInsert = React.useCallback((variableKey) => {
4690
+ runHistoryAction(
4691
+ { label: interpolate(t.variables.insertVariable, { key: variableKey }), source: "toolbar", region: "document" },
4692
+ () => {
4693
+ if (activeEditor) {
4694
+ activeEditor.dispatchCommand(INSERT_VARIABLE_COMMAND, variableKey);
4695
+ }
4696
+ }
4697
+ );
4698
+ }, [activeEditor, runHistoryAction]);
4699
+ return /* @__PURE__ */ jsxRuntime.jsx(
4700
+ VariablePicker,
4701
+ {
4702
+ onInsert: handleInsert,
4703
+ disabled: !activeEditor
4704
+ }
4705
+ );
4706
+ };
4707
+ const VariablePanelToggle = () => {
4708
+ const { panelOpen, setPanelOpen } = useVariablePanelState();
4709
+ const t = useTranslations();
4710
+ return /* @__PURE__ */ jsxRuntime.jsx(
4711
+ "button",
4712
+ {
4713
+ type: "button",
4714
+ title: panelOpen ? t.variables.closePanel : t.variables.openPanel,
4715
+ "aria-label": panelOpen ? t.variables.closePanel : t.variables.openPanel,
4716
+ onMouseDown: (e) => e.preventDefault(),
4717
+ onClick: () => setPanelOpen(!panelOpen),
4718
+ className: `
4719
+ flex h-7 w-7 items-center justify-center rounded transition-colors
4720
+ ${panelOpen ? "bg-blue-50 text-blue-600" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"}
4721
+ `,
4722
+ "data-testid": "toggle-variable-panel",
4723
+ children: /* @__PURE__ */ jsxRuntime.jsx(Braces, { size: 15 })
4724
+ }
4725
+ );
4726
+ };
4727
+ const VariablePanelWithState = () => {
4728
+ const { panelOpen, setPanelOpen } = useVariablePanelState();
4729
+ return /* @__PURE__ */ jsxRuntime.jsx(VariablePanel, { open: panelOpen, onClose: () => setPanelOpen(false) });
4730
+ };
4731
+ const VariableDefinitionsSync = ({ children }) => {
4732
+ const { definitions } = useVariables();
4733
+ const extState = useExtensionState();
4734
+ React.useEffect(() => {
4735
+ extState.set("variableDefinitions", definitions);
4736
+ }, [definitions, extState]);
4737
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
4738
+ };
4739
+ function variablesExtension(definitions = []) {
4740
+ const ProviderWrapper = ({ children }) => {
4741
+ return /* @__PURE__ */ jsxRuntime.jsx(VariablePanelStateProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(VariableProvider, { initialDefinitions: definitions, children: /* @__PURE__ */ jsxRuntime.jsx(VariableDefinitionsSync, { children }) }) });
4742
+ };
4743
+ return {
4744
+ name: "variables",
4745
+ nodes: [VariableNode],
4746
+ bodyPlugins: [VariablePlugin],
4747
+ toolbarItems: [VariableToolbarItem, VariablePanelToggle],
4748
+ sidePanel: VariablePanelWithState,
4749
+ provider: ProviderWrapper,
4750
+ handleMethods: (ctx) => ({
4751
+ insertVariable: (key) => {
4752
+ const editor = ctx.getActiveEditor();
4753
+ if (editor) {
4754
+ editor.dispatchCommand(INSERT_VARIABLE_COMMAND, key);
4755
+ }
4756
+ },
4757
+ refreshVariables: (newDefs) => {
4758
+ ctx.setExtensionState("variableDefinitions", newDefs);
4759
+ }
4760
+ })
4761
+ };
4762
+ }
4763
+ exports.$createVariableNode = $createVariableNode;
4764
+ exports.$isVariableNode = $isVariableNode;
3762
4765
  exports.A4_HEIGHT_MM = A4_HEIGHT_MM;
3763
4766
  exports.A4_HEIGHT_PX = A4_HEIGHT_PX;
3764
4767
  exports.A4_WIDTH_MM = A4_WIDTH_MM;
3765
4768
  exports.A4_WIDTH_PX = A4_WIDTH_PX;
4769
+ exports.AST_VERSION = AST_VERSION;
4770
+ exports.DEFAULT_TRANSLATIONS = DEFAULT_TRANSLATIONS;
4771
+ exports.INSERT_VARIABLE_COMMAND = INSERT_VARIABLE_COMMAND;
3766
4772
  exports.Lex4Editor = Lex4Editor;
3767
4773
  exports.MAX_FOOTER_HEIGHT_PX = MAX_FOOTER_HEIGHT_PX;
3768
4774
  exports.MAX_HEADER_HEIGHT_PX = MAX_HEADER_HEIGHT_PX;
4775
+ exports.VariableNode = VariableNode;
4776
+ exports.astExtension = astExtension;
4777
+ exports.buildSavePayload = buildSavePayload;
3769
4778
  exports.createEmptyDocument = createEmptyDocument;
3770
4779
  exports.createEmptyPage = createEmptyPage;
4780
+ exports.serializeDocument = serializeDocument;
4781
+ exports.serializeDocumentJson = serializeDocumentJson;
3771
4782
  exports.useHeaderFooter = useHeaderFooter;
3772
4783
  exports.useOverflowDetection = useOverflowDetection;
3773
4784
  exports.usePagination = usePagination;
4785
+ exports.variablesExtension = variablesExtension;
3774
4786
  //# sourceMappingURL=lex4-editor.cjs.map