@yurikilian/lex4 0.3.2 → 1.1.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 (38) hide show
  1. package/README.md +71 -4
  2. package/dist/ast/document-serializer.d.ts.map +1 -1
  3. package/dist/components/EditorSidebar.d.ts.map +1 -1
  4. package/dist/components/HeaderFooterActions.d.ts.map +1 -1
  5. package/dist/components/HeaderFooterToggle.d.ts.map +1 -1
  6. package/dist/components/HistorySidebar.d.ts.map +1 -1
  7. package/dist/components/Lex4Editor.d.ts.map +1 -1
  8. package/dist/components/PageBody.d.ts.map +1 -1
  9. package/dist/components/PageView.d.ts.map +1 -1
  10. package/dist/components/Toolbar.d.ts.map +1 -1
  11. package/dist/components/VariablePanel.d.ts.map +1 -1
  12. package/dist/components/VariablePicker.d.ts.map +1 -1
  13. package/dist/constants/dimensions.d.ts +6 -2
  14. package/dist/constants/dimensions.d.ts.map +1 -1
  15. package/dist/constants/index.d.ts +1 -1
  16. package/dist/constants/index.d.ts.map +1 -1
  17. package/dist/constants/page-layout.d.ts +0 -4
  18. package/dist/constants/page-layout.d.ts.map +1 -1
  19. package/dist/extensions/ast-extension.d.ts +9 -0
  20. package/dist/extensions/ast-extension.d.ts.map +1 -1
  21. package/dist/extensions/extension-context.d.ts +3 -1
  22. package/dist/extensions/extension-context.d.ts.map +1 -1
  23. package/dist/extensions/types.d.ts +12 -0
  24. package/dist/extensions/types.d.ts.map +1 -1
  25. package/dist/extensions/variables-extension.d.ts +6 -0
  26. package/dist/extensions/variables-extension.d.ts.map +1 -1
  27. package/dist/lex4-editor.cjs +402 -386
  28. package/dist/lex4-editor.cjs.map +1 -1
  29. package/dist/lex4-editor.js +402 -386
  30. package/dist/lex4-editor.js.map +1 -1
  31. package/dist/lexical/theme.d.ts +1 -1
  32. package/dist/style.css +789 -695
  33. package/dist/types/editor-handle.d.ts +12 -7
  34. package/dist/types/editor-handle.d.ts.map +1 -1
  35. package/dist/types/editor-props.d.ts +2 -1
  36. package/dist/types/editor-props.d.ts.map +1 -1
  37. package/dist/variables/variable-node.d.ts.map +1 -1
  38. package/package.json +1 -1
@@ -61,7 +61,10 @@ const A4_HEIGHT_MM = 297;
61
61
  const PX_PER_MM = 96 / 25.4;
62
62
  const A4_WIDTH_PX = Math.round(A4_WIDTH_MM * PX_PER_MM);
63
63
  const A4_HEIGHT_PX = Math.round(A4_HEIGHT_MM * PX_PER_MM);
64
- const PAGE_MARGIN_PX = 40;
64
+ const PAGE_MARGIN_TOP_PX = Math.round(25.4 * PX_PER_MM);
65
+ const PAGE_MARGIN_BOTTOM_PX = Math.round(25.4 * PX_PER_MM);
66
+ const PAGE_MARGIN_LEFT_PX = Math.round(31.75 * PX_PER_MM);
67
+ const PAGE_MARGIN_RIGHT_PX = Math.round(31.75 * PX_PER_MM);
65
68
  const MAX_HEADER_RATIO = 0.2;
66
69
  const MAX_FOOTER_RATIO = 0.2;
67
70
  const MAX_HEADER_HEIGHT_PX = Math.round(A4_HEIGHT_PX * MAX_HEADER_RATIO);
@@ -1742,21 +1745,21 @@ const EditorSidebar = ({
1742
1745
  return /* @__PURE__ */ jsxRuntime.jsxs(
1743
1746
  "aside",
1744
1747
  {
1745
- className: "flex h-full w-[320px] shrink-0 flex-col border-l border-gray-200 bg-white",
1748
+ className: "lex4-sidebar",
1746
1749
  "data-testid": testId,
1747
1750
  children: [
1748
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between border-b border-gray-200 px-3 py-2.5", children: [
1751
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-sidebar-header", children: [
1749
1752
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1750
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xs font-semibold text-gray-900", children: title }),
1751
- subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-xs text-gray-500", children: subtitle })
1753
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "lex4-sidebar-title", children: title }),
1754
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lex4-sidebar-subtitle", children: subtitle })
1752
1755
  ] }),
1753
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
1756
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-sidebar-actions", children: [
1754
1757
  headerActions,
1755
1758
  onClose && /* @__PURE__ */ jsxRuntime.jsx(
1756
1759
  "button",
1757
1760
  {
1758
1761
  type: "button",
1759
- 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",
1762
+ className: "lex4-sidebar-action-btn",
1760
1763
  "data-testid": "close-editor-sidebar",
1761
1764
  onClick: onClose,
1762
1765
  "aria-label": t.sidebar.close,
@@ -1765,7 +1768,7 @@ const EditorSidebar = ({
1765
1768
  )
1766
1769
  ] })
1767
1770
  ] }),
1768
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-auto", children })
1771
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-sidebar-body", children })
1769
1772
  ]
1770
1773
  }
1771
1774
  );
@@ -1797,7 +1800,7 @@ const HistorySidebar = () => {
1797
1800
  "aria-label": t.history.clearHistory,
1798
1801
  onMouseDown: (e) => e.preventDefault(),
1799
1802
  onClick: () => clearHistory("manual"),
1800
- 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",
1803
+ className: "lex4-sidebar-action-btn",
1801
1804
  "data-testid": "clear-history",
1802
1805
  children: /* @__PURE__ */ jsxRuntime.jsx(Trash2, { size: 13 })
1803
1806
  }
@@ -1814,27 +1817,27 @@ const HistorySidebar = () => {
1814
1817
  children: visibleEntries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1815
1818
  "div",
1816
1819
  {
1817
- className: "px-3 py-4 text-xs text-gray-500",
1820
+ className: "lex4-history-empty",
1818
1821
  "data-testid": "history-empty",
1819
1822
  children: t.history.empty
1820
1823
  }
1821
- ) : /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "divide-y divide-gray-100", "data-testid": "history-entry-list", children: visibleEntries.map((entry, reversedIndex) => {
1824
+ ) : /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "lex4-history-list", "data-testid": "history-entry-list", children: visibleEntries.map((entry, reversedIndex) => {
1822
1825
  const actualIndex = historyEntries.length - reversedIndex - 1;
1823
1826
  const isCurrent = actualIndex === historyCursor - 1;
1824
1827
  return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
1825
1828
  "button",
1826
1829
  {
1827
1830
  type: "button",
1828
- className: `w-full px-3 py-2 text-left transition-colors ${isCurrent ? "bg-blue-50" : "bg-white hover:bg-gray-50"}`,
1831
+ className: `lex4-history-entry ${isCurrent ? "active" : ""}`,
1829
1832
  "data-testid": `history-entry-${actualIndex}`,
1830
1833
  "data-history-current": isCurrent ? "true" : "false",
1831
1834
  onClick: () => jumpToHistoryEntry2(actualIndex),
1832
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
1833
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
1834
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: `text-xs ${isCurrent ? "font-semibold text-blue-700" : "text-gray-900"}`, children: entry.label }),
1835
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-0.5 text-xs text-gray-400", children: t.regions[entry.source] ?? entry.source })
1835
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-history-entry-row", children: [
1836
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-history-entry-content", children: [
1837
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `lex4-history-entry-label${isCurrent ? " current" : ""}`, children: entry.label }),
1838
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-history-entry-source", children: t.regions[entry.source] ?? entry.source })
1836
1839
  ] }),
1837
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 text-xs text-gray-400", children: formatTimestamp(entry.timestamp) })
1840
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-history-entry-time", children: formatTimestamp(entry.timestamp) })
1838
1841
  ] })
1839
1842
  }
1840
1843
  ) }, entry.id);
@@ -1850,11 +1853,11 @@ const HeaderFooterToggle = ({
1850
1853
  return /* @__PURE__ */ jsxRuntime.jsxs(
1851
1854
  "label",
1852
1855
  {
1853
- className: "flex items-center gap-1.5 cursor-pointer select-none",
1856
+ className: "lex4-hf-toggle",
1854
1857
  "data-testid": "header-footer-toggle",
1855
1858
  children: [
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 }),
1859
+ /* @__PURE__ */ jsxRuntime.jsx(Rows3, { size: 14, className: "lex4-hf-toggle-icon" }),
1860
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-hf-toggle-label", children: t.headerFooter.label }),
1858
1861
  /* @__PURE__ */ jsxRuntime.jsx(
1859
1862
  "button",
1860
1863
  {
@@ -1863,22 +1866,10 @@ const HeaderFooterToggle = ({
1863
1866
  "aria-checked": enabled,
1864
1867
  onMouseDown: (e) => e.preventDefault(),
1865
1868
  onClick: () => onToggle(!enabled),
1866
- className: `
1867
- relative inline-flex h-4 w-7 items-center rounded-full
1868
- transition-colors duration-200
1869
- ${enabled ? "bg-blue-500" : "bg-gray-300"}
1870
- `,
1869
+ className: "lex4-hf-switch",
1870
+ style: { backgroundColor: enabled ? "var(--lex4-color-primary)" : "var(--lex4-color-text-disabled)" },
1871
1871
  "data-testid": "header-footer-switch",
1872
- children: /* @__PURE__ */ jsxRuntime.jsx(
1873
- "span",
1874
- {
1875
- className: `
1876
- inline-block h-3 w-3 rounded-full bg-white shadow-sm
1877
- transition-transform duration-200
1878
- ${enabled ? "translate-x-3.5" : "translate-x-0.5"}
1879
- `
1880
- }
1881
- )
1872
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-hf-switch-knob" })
1882
1873
  }
1883
1874
  )
1884
1875
  ]
@@ -1920,17 +1911,14 @@ const HeaderFooterActions = ({
1920
1911
  action();
1921
1912
  close();
1922
1913
  };
1923
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", "data-testid": "header-footer-actions", children: [
1914
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, style: { position: "relative" }, "data-testid": "header-footer-actions", children: [
1924
1915
  /* @__PURE__ */ jsxRuntime.jsxs(
1925
1916
  "button",
1926
1917
  {
1927
1918
  type: "button",
1928
1919
  onMouseDown: (e) => e.preventDefault(),
1929
1920
  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
- `,
1921
+ className: "lex4-settings-trigger",
1934
1922
  "data-testid": "header-footer-menu-trigger",
1935
1923
  "aria-expanded": open,
1936
1924
  "aria-haspopup": "true",
@@ -1943,12 +1931,12 @@ const HeaderFooterActions = ({
1943
1931
  open && /* @__PURE__ */ jsxRuntime.jsxs(
1944
1932
  "div",
1945
1933
  {
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",
1934
+ className: "lex4-settings-menu",
1947
1935
  role: "menu",
1948
1936
  "data-testid": "header-footer-menu",
1949
1937
  children: [
1950
1938
  /* @__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(
1939
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-settings-counter-grid", "data-testid": "page-counter-mode", children: PAGE_COUNTER_OPTIONS.map(({ value, label }) => /* @__PURE__ */ jsxRuntime.jsx(
1952
1940
  "button",
1953
1941
  {
1954
1942
  type: "button",
@@ -1956,10 +1944,7 @@ const HeaderFooterActions = ({
1956
1944
  "aria-checked": pageCounterMode === value,
1957
1945
  onMouseDown: (e) => e.preventDefault(),
1958
1946
  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
- `,
1947
+ className: "lex4-settings-counter-btn",
1963
1948
  "data-testid": `page-counter-${value}`,
1964
1949
  children: label
1965
1950
  },
@@ -2032,7 +2017,7 @@ const HeaderFooterActions = ({
2032
2017
  )
2033
2018
  ] });
2034
2019
  };
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 });
2020
+ const SectionLabel = ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-settings-label", children });
2036
2021
  const MenuItem = ({ icon, label, onClick, disabled, testId }) => /* @__PURE__ */ jsxRuntime.jsxs(
2037
2022
  "button",
2038
2023
  {
@@ -2041,7 +2026,7 @@ const MenuItem = ({ icon, label, onClick, disabled, testId }) => /* @__PURE__ */
2041
2026
  onMouseDown: (e) => e.preventDefault(),
2042
2027
  onClick,
2043
2028
  disabled,
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",
2029
+ className: "lex4-settings-item",
2045
2030
  "data-testid": testId,
2046
2031
  children: [
2047
2032
  icon,
@@ -2049,7 +2034,7 @@ const MenuItem = ({ icon, label, onClick, disabled, testId }) => /* @__PURE__ */
2049
2034
  ]
2050
2035
  }
2051
2036
  );
2052
- const MenuDivider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-1 h-px bg-gray-100" });
2037
+ const MenuDivider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-settings-divider" });
2053
2038
  function resolveExtensions(extensions) {
2054
2039
  const resolved = {
2055
2040
  nodes: [],
@@ -2058,6 +2043,8 @@ function resolveExtensions(extensions) {
2058
2043
  sidePanels: [],
2059
2044
  providers: [],
2060
2045
  themeOverrides: {},
2046
+ cssVariables: {},
2047
+ rootClassNames: [],
2061
2048
  handleFactories: []
2062
2049
  };
2063
2050
  for (const ext of extensions) {
@@ -2069,6 +2056,10 @@ function resolveExtensions(extensions) {
2069
2056
  if (ext.themeOverrides) {
2070
2057
  resolved.themeOverrides = { ...resolved.themeOverrides, ...ext.themeOverrides };
2071
2058
  }
2059
+ if (ext.cssVariables) {
2060
+ Object.assign(resolved.cssVariables, ext.cssVariables);
2061
+ }
2062
+ if (ext.rootClassName) resolved.rootClassNames.push(ext.rootClassName);
2072
2063
  if (ext.handleMethods) resolved.handleFactories.push(ext.handleMethods);
2073
2064
  }
2074
2065
  return resolved;
@@ -2080,14 +2071,27 @@ const EMPTY_RESOLVED = {
2080
2071
  sidePanels: [],
2081
2072
  providers: [],
2082
2073
  themeOverrides: {},
2074
+ cssVariables: {},
2075
+ rootClassNames: [],
2083
2076
  handleFactories: []
2084
2077
  };
2085
2078
  const ExtensionResolvedContext = React.createContext(EMPTY_RESOLVED);
2079
+ function arraysEqual(a, b) {
2080
+ if (a.length !== b.length) return false;
2081
+ for (let i = 0; i < a.length; i++) {
2082
+ if (a[i] !== b[i]) return false;
2083
+ }
2084
+ return true;
2085
+ }
2086
2086
  const ExtensionProvider = ({ extensions, children }) => {
2087
- const resolved = React.useMemo(
2088
- () => extensions && extensions.length > 0 ? resolveExtensions(extensions) : EMPTY_RESOLVED,
2089
- [extensions]
2090
- );
2087
+ const prevNamesRef = React.useRef([]);
2088
+ const resolvedRef = React.useRef(EMPTY_RESOLVED);
2089
+ const currentNames = (extensions ?? []).map((e) => e.name);
2090
+ if (!arraysEqual(currentNames, prevNamesRef.current)) {
2091
+ resolvedRef.current = extensions && extensions.length > 0 ? resolveExtensions(extensions) : EMPTY_RESOLVED;
2092
+ prevNamesRef.current = currentNames;
2093
+ }
2094
+ const resolved = resolvedRef.current;
2091
2095
  let wrapped = /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
2092
2096
  for (let i = resolved.providers.length - 1; i >= 0; i--) {
2093
2097
  const Provider = resolved.providers[i];
@@ -2394,11 +2398,11 @@ const Toolbar = () => {
2394
2398
  return /* @__PURE__ */ jsxRuntime.jsxs(
2395
2399
  "div",
2396
2400
  {
2397
- className: "lex4-toolbar sticky top-0 z-10 bg-white border-b border-gray-200",
2401
+ className: "lex4-toolbar",
2398
2402
  "data-testid": "toolbar",
2399
2403
  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: [
2404
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-row", children: [
2405
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group", "data-testid": "history-controls", children: [
2402
2406
  /* @__PURE__ */ jsxRuntime.jsx(
2403
2407
  ToolbarIconButton,
2404
2408
  {
@@ -2421,12 +2425,12 @@ const Toolbar = () => {
2421
2425
  )
2422
2426
  ] }),
2423
2427
  /* @__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" }),
2428
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group-gap", children: [
2429
+ /* @__PURE__ */ jsxRuntime.jsx(Type, { size: 14, className: "lex4-toolbar-icon" }),
2426
2430
  /* @__PURE__ */ jsxRuntime.jsx(
2427
2431
  "select",
2428
2432
  {
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",
2433
+ className: "lex4-toolbar-select",
2430
2434
  "data-testid": "font-selector",
2431
2435
  defaultValue: "Calibri",
2432
2436
  onChange: handleFontChange,
@@ -2434,12 +2438,12 @@ const Toolbar = () => {
2434
2438
  }
2435
2439
  )
2436
2440
  ] }),
2437
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
2438
- /* @__PURE__ */ jsxRuntime.jsx(ALargeSmall, { size: 14, className: "text-gray-500" }),
2441
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group-gap", children: [
2442
+ /* @__PURE__ */ jsxRuntime.jsx(ALargeSmall, { size: 14, className: "lex4-toolbar-icon" }),
2439
2443
  /* @__PURE__ */ jsxRuntime.jsx(
2440
2444
  "select",
2441
2445
  {
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",
2446
+ className: "lex4-toolbar-select lex4-toolbar-select-narrow",
2443
2447
  "data-testid": "font-size-selector",
2444
2448
  defaultValue: "12",
2445
2449
  onChange: handleFontSizeChange,
@@ -2448,21 +2452,21 @@ const Toolbar = () => {
2448
2452
  )
2449
2453
  ] }),
2450
2454
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2451
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "format-group", children: [
2455
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group", "data-testid": "format-group", children: [
2452
2456
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bold, testId: "btn-bold", onClick: handleBold, children: /* @__PURE__ */ jsxRuntime.jsx(Bold, { size: 15 }) }),
2453
2457
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.italic, testId: "btn-italic", onClick: handleItalic, children: /* @__PURE__ */ jsxRuntime.jsx(Italic, { size: 15 }) }),
2454
2458
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.underline, testId: "btn-underline", onClick: handleUnderline, children: /* @__PURE__ */ jsxRuntime.jsx(Underline, { size: 15 }) }),
2455
2459
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.strikethrough, testId: "btn-strike", onClick: handleStrikethrough, children: /* @__PURE__ */ jsxRuntime.jsx(Strikethrough, { size: 15 }) })
2456
2460
  ] }),
2457
2461
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2458
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "align-group", children: [
2462
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group", "data-testid": "align-group", children: [
2459
2463
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignLeft, testId: "btn-align-left", onClick: handleAlignLeft, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignStart, { size: 15 }) }),
2460
2464
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignCenter, testId: "btn-align-center", onClick: handleAlignCenter, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignCenter, { size: 15 }) }),
2461
2465
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignRight, testId: "btn-align-right", onClick: handleAlignRight, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignEnd, { size: 15 }) }),
2462
2466
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.justify, testId: "btn-align-justify", onClick: handleAlignJustify, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignJustify, { size: 15 }) })
2463
2467
  ] }),
2464
2468
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2465
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "list-group", children: [
2469
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group", "data-testid": "list-group", children: [
2466
2470
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.numberedList, testId: "btn-list-number", onClick: handleListNumber, children: /* @__PURE__ */ jsxRuntime.jsx(ListOrdered, { size: 15 }) }),
2467
2471
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bulletList, testId: "btn-list-bullet", onClick: handleListBullet, children: /* @__PURE__ */ jsxRuntime.jsx(List, { size: 15 }) }),
2468
2472
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.indent, testId: "btn-indent", onClick: handleIndent, children: /* @__PURE__ */ jsxRuntime.jsx(ListIndentIncrease, { size: 15 }) }),
@@ -2472,7 +2476,7 @@ const Toolbar = () => {
2472
2476
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2473
2477
  toolbarItems.map((ToolbarItem, idx) => /* @__PURE__ */ jsxRuntime.jsx(ToolbarItem, {}, idx))
2474
2478
  ] }),
2475
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
2479
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-toolbar-end", children: /* @__PURE__ */ jsxRuntime.jsx(
2476
2480
  ToolbarIconButton,
2477
2481
  {
2478
2482
  title: historySidebarOpen ? t.toolbar.closeHistory : t.toolbar.openHistory,
@@ -2483,7 +2487,7 @@ const Toolbar = () => {
2483
2487
  }
2484
2488
  ) })
2485
2489
  ] }),
2486
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 px-2 py-1 border-t border-gray-100", children: [
2490
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-hf-row", children: [
2487
2491
  /* @__PURE__ */ jsxRuntime.jsx(
2488
2492
  HeaderFooterToggle,
2489
2493
  {
@@ -2526,17 +2530,14 @@ const ToolbarIconButton = ({
2526
2530
  disabled,
2527
2531
  onMouseDown: (e) => e.preventDefault(),
2528
2532
  onClick,
2529
- className: `
2530
- flex h-7 w-7 items-center justify-center rounded transition-colors
2531
- ${disabled ? "cursor-not-allowed text-gray-300" : active ? "bg-blue-50 text-blue-600" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"}
2532
- `,
2533
+ className: `lex4-toolbar-btn${active ? " active" : ""}`,
2533
2534
  "data-testid": testId,
2534
2535
  children
2535
2536
  }
2536
2537
  );
2537
- const Divider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-0.5 h-5 w-px bg-gray-200" });
2538
+ const Divider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-toolbar-separator" });
2538
2539
  function computeBodyHeight(headerHeight, footerHeight) {
2539
- const verticalMargins = PAGE_MARGIN_PX * 2;
2540
+ const verticalMargins = PAGE_MARGIN_TOP_PX + PAGE_MARGIN_BOTTOM_PX;
2540
2541
  return A4_HEIGHT_PX - headerHeight - footerHeight - verticalMargins;
2541
2542
  }
2542
2543
  function getTopLevelNodes(state) {
@@ -2841,33 +2842,33 @@ function usePagination(document2, dispatch) {
2841
2842
  };
2842
2843
  }
2843
2844
  const lexicalTheme = {
2844
- root: "lex4-root outline-none",
2845
- paragraph: "lex4-paragraph text-justify",
2845
+ root: "lex4-root",
2846
+ paragraph: "lex4-paragraph",
2846
2847
  heading: {
2847
- h1: "text-3xl font-bold mb-2",
2848
- h2: "text-2xl font-bold mb-2",
2849
- h3: "text-xl font-semibold mb-1",
2850
- h4: "text-lg font-semibold mb-1",
2851
- h5: "text-base font-medium mb-1"
2848
+ h1: "lex4-heading lex4-heading-h1",
2849
+ h2: "lex4-heading lex4-heading-h2",
2850
+ h3: "lex4-heading lex4-heading-h3",
2851
+ h4: "lex4-heading lex4-heading-h4",
2852
+ h5: "lex4-heading lex4-heading-h5"
2852
2853
  },
2853
2854
  text: {
2854
- bold: "font-bold",
2855
- italic: "italic",
2856
- underline: "underline",
2857
- strikethrough: "line-through",
2858
- underlineStrikethrough: "underline line-through"
2855
+ bold: "lex4-text-bold",
2856
+ italic: "lex4-text-italic",
2857
+ underline: "lex4-text-underline",
2858
+ strikethrough: "lex4-text-strikethrough",
2859
+ underlineStrikethrough: "lex4-text-underline lex4-text-strikethrough"
2859
2860
  },
2860
2861
  list: {
2861
2862
  nested: {
2862
- listitem: "list-none"
2863
+ listitem: "lex4-listitem-nested"
2863
2864
  },
2864
- ol: "list-decimal ml-6",
2865
- ul: "list-disc ml-6",
2865
+ ol: "lex4-list lex4-list-ordered",
2866
+ ul: "lex4-list lex4-list-unordered",
2866
2867
  listitem: "lex4-listitem",
2867
2868
  listitemChecked: "lex4-listitem-checked",
2868
2869
  listitemUnchecked: "lex4-listitem-unchecked"
2869
2870
  },
2870
- quote: "border-l-4 border-gray-300 pl-4 italic text-gray-600"
2871
+ quote: "lex4-quote"
2871
2872
  };
2872
2873
  const DEFAULT_NODES = [richText.HeadingNode, richText.QuoteNode, list.ListNode, list.ListItemNode];
2873
2874
  function createEditorConfig(mode, pageId, extraNodes = [], themeOverrides = {}) {
@@ -3758,8 +3759,7 @@ const PageBody = ({
3758
3759
  return /* @__PURE__ */ jsxRuntime.jsx(
3759
3760
  "div",
3760
3761
  {
3761
- className: "lex4-page-body flex-1 min-h-0 relative",
3762
- style: { overflow: "hidden" },
3762
+ className: "lex4-page-body",
3763
3763
  "data-testid": `page-body-${pageId}`,
3764
3764
  onFocus,
3765
3765
  children: /* @__PURE__ */ jsxRuntime.jsxs(LexicalComposer.LexicalComposer, { initialConfig: config, children: [
@@ -3769,11 +3769,10 @@ const PageBody = ({
3769
3769
  contentEditable: /* @__PURE__ */ jsxRuntime.jsx(
3770
3770
  LexicalContentEditable.ContentEditable,
3771
3771
  {
3772
- className: "outline-none h-full p-0",
3773
- style: { overflow: "visible" }
3772
+ className: "lex4-page-body-editable"
3774
3773
  }
3775
3774
  ),
3776
- placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-0 left-0 text-gray-400 pointer-events-none select-none", children: t.body.placeholder }),
3775
+ placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-page-placeholder", children: t.body.placeholder }),
3777
3776
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
3778
3777
  }
3779
3778
  ),
@@ -3901,7 +3900,7 @@ const PageHeader = ({
3901
3900
  return /* @__PURE__ */ jsxRuntime.jsxs(
3902
3901
  "div",
3903
3902
  {
3904
- 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",
3903
+ className: "lex4-page-header",
3905
3904
  style: { maxHeight: MAX_HEADER_HEIGHT_PX, overflow: "clip" },
3906
3905
  "data-testid": `page-header-${pageId}`,
3907
3906
  children: [
@@ -3913,10 +3912,10 @@ const PageHeader = ({
3913
3912
  LexicalContentEditable.ContentEditable,
3914
3913
  {
3915
3914
  ref: contentRef,
3916
- className: `outline-none p-2 text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
3915
+ className: `lex4-page-hf-editable${hasPageCounter ? " lex4-page-hf-narrow" : ""}`
3917
3916
  }
3918
3917
  ),
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 }),
3918
+ placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `lex4-page-hf-placeholder${hasPageCounter ? " lex4-page-hf-narrow" : ""}`, children: t.header.placeholder }),
3920
3919
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
3921
3920
  }
3922
3921
  ),
@@ -3928,7 +3927,7 @@ const PageHeader = ({
3928
3927
  pageCounterLabel && /* @__PURE__ */ jsxRuntime.jsx(
3929
3928
  "div",
3930
3929
  {
3931
- className: "pointer-events-none absolute right-2 top-2 select-none text-xs text-gray-500",
3930
+ className: "lex4-page-counter",
3932
3931
  "data-testid": `page-counter-header-${pageId}`,
3933
3932
  children: pageCounterLabel
3934
3933
  }
@@ -3981,7 +3980,7 @@ const PageFooter = ({
3981
3980
  return /* @__PURE__ */ jsxRuntime.jsxs(
3982
3981
  "div",
3983
3982
  {
3984
- 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",
3983
+ className: "lex4-page-footer",
3985
3984
  style: { maxHeight: MAX_FOOTER_HEIGHT_PX, overflow: "clip" },
3986
3985
  "data-testid": `page-footer-${pageId}`,
3987
3986
  children: [
@@ -3993,10 +3992,10 @@ const PageFooter = ({
3993
3992
  LexicalContentEditable.ContentEditable,
3994
3993
  {
3995
3994
  ref: contentRef,
3996
- className: `outline-none p-2 text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
3995
+ className: `lex4-page-hf-editable${hasPageCounter ? " lex4-page-hf-narrow" : ""}`
3997
3996
  }
3998
3997
  ),
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 }),
3998
+ placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `lex4-page-hf-placeholder${hasPageCounter ? " lex4-page-hf-narrow" : ""}`, children: t.footer.placeholder }),
4000
3999
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
4001
4000
  }
4002
4001
  ),
@@ -4008,7 +4007,7 @@ const PageFooter = ({
4008
4007
  pageCounterLabel && /* @__PURE__ */ jsxRuntime.jsx(
4009
4008
  "div",
4010
4009
  {
4011
- className: "pointer-events-none absolute right-2 top-2 select-none text-xs text-gray-500",
4010
+ className: "lex4-page-counter",
4012
4011
  "data-testid": `page-counter-footer-${pageId}`,
4013
4012
  children: pageCounterLabel
4014
4013
  }
@@ -4066,11 +4065,14 @@ const PageView = React.memo(({
4066
4065
  return /* @__PURE__ */ jsxRuntime.jsxs(
4067
4066
  "div",
4068
4067
  {
4069
- className: "lex4-page bg-white shadow-xl flex flex-col",
4068
+ className: "lex4-page",
4070
4069
  style: {
4071
4070
  width: A4_WIDTH_PX,
4072
4071
  height: A4_HEIGHT_PX,
4073
- padding: PAGE_MARGIN_PX
4072
+ paddingTop: PAGE_MARGIN_TOP_PX,
4073
+ paddingBottom: PAGE_MARGIN_BOTTOM_PX,
4074
+ paddingLeft: PAGE_MARGIN_LEFT_PX,
4075
+ paddingRight: PAGE_MARGIN_RIGHT_PX
4074
4076
  },
4075
4077
  "data-testid": `page-${pageIndex}`,
4076
4078
  "data-page-id": pageId,
@@ -4351,7 +4353,7 @@ const DocumentView = () => {
4351
4353
  return /* @__PURE__ */ jsxRuntime.jsx(
4352
4354
  "div",
4353
4355
  {
4354
- className: "lex4-document-view flex flex-col items-center gap-8 py-8 min-h-full",
4356
+ className: "lex4-document-view",
4355
4357
  "data-testid": "document-view",
4356
4358
  tabIndex: -1,
4357
4359
  children: document2.pages.map((page, index) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -4370,6 +4372,250 @@ const DocumentView = () => {
4370
4372
  }
4371
4373
  );
4372
4374
  };
4375
+ const AST_VERSION = "1.0.0";
4376
+ const IS_BOLD = 1;
4377
+ const IS_ITALIC = 2;
4378
+ const IS_STRIKETHROUGH = 4;
4379
+ const IS_UNDERLINE = 8;
4380
+ function decodeFormatBitmask(format) {
4381
+ const marks = {};
4382
+ if (format & IS_BOLD) marks.bold = true;
4383
+ if (format & IS_ITALIC) marks.italic = true;
4384
+ if (format & IS_UNDERLINE) marks.underline = true;
4385
+ if (format & IS_STRIKETHROUGH) marks.strikethrough = true;
4386
+ return marks;
4387
+ }
4388
+ function extractFontFamily(style) {
4389
+ const match = style.match(/font-family:\s*([^;]+)/);
4390
+ return match ? match[1].trim().replace(/['"]/g, "") : void 0;
4391
+ }
4392
+ function extractFontSizePt(style) {
4393
+ const match = style.match(/font-size:\s*(\d+(?:\.\d+)?)\s*pt/);
4394
+ return match ? parseFloat(match[1]) : void 0;
4395
+ }
4396
+ function buildTextMarks(format, style) {
4397
+ const formatMarks = decodeFormatBitmask(format);
4398
+ const fontFamily = style ? extractFontFamily(style) : void 0;
4399
+ const fontSize = style ? extractFontSizePt(style) : void 0;
4400
+ const marks = {
4401
+ ...formatMarks,
4402
+ ...fontFamily ? { fontFamily } : {},
4403
+ ...fontSize ? { fontSize } : {}
4404
+ };
4405
+ return Object.keys(marks).length > 0 ? marks : void 0;
4406
+ }
4407
+ function mapInlineNode(node) {
4408
+ switch (node.type) {
4409
+ case "text":
4410
+ return mapTextNode(node);
4411
+ case "variable-node":
4412
+ return mapVariableNode(node);
4413
+ case "linebreak":
4414
+ return mapLineBreak();
4415
+ default:
4416
+ return { type: "text", text: "" };
4417
+ }
4418
+ }
4419
+ function mapTextNode(node) {
4420
+ const marks = buildTextMarks(node.format, node.style);
4421
+ return {
4422
+ type: "text",
4423
+ text: node.text,
4424
+ ...marks ? { marks } : {}
4425
+ };
4426
+ }
4427
+ function mapVariableNode(node) {
4428
+ return {
4429
+ type: "variable",
4430
+ key: node.variableKey
4431
+ };
4432
+ }
4433
+ function mapLineBreak() {
4434
+ return { type: "linebreak" };
4435
+ }
4436
+ function mapInlineNodes(nodes) {
4437
+ return nodes.map(mapInlineNode);
4438
+ }
4439
+ const ALIGN_LEFT = 1;
4440
+ const ALIGN_CENTER = 2;
4441
+ const ALIGN_RIGHT = 3;
4442
+ const ALIGN_JUSTIFY = 4;
4443
+ function decodeAlignment(format) {
4444
+ if (typeof format === "string") {
4445
+ if (["left", "center", "right", "justify"].includes(format)) {
4446
+ return format;
4447
+ }
4448
+ return void 0;
4449
+ }
4450
+ if (typeof format !== "number" || format === 0) return void 0;
4451
+ switch (format) {
4452
+ case ALIGN_LEFT:
4453
+ return "left";
4454
+ case ALIGN_CENTER:
4455
+ return "center";
4456
+ case ALIGN_RIGHT:
4457
+ return "right";
4458
+ case ALIGN_JUSTIFY:
4459
+ return "justify";
4460
+ default:
4461
+ return void 0;
4462
+ }
4463
+ }
4464
+ function mapBlockNode(node) {
4465
+ switch (node.type) {
4466
+ case "paragraph":
4467
+ return mapParagraph(node);
4468
+ case "heading":
4469
+ return mapHeading(node);
4470
+ case "list":
4471
+ return mapList(node);
4472
+ case "quote":
4473
+ return mapBlockQuote(node);
4474
+ default:
4475
+ return {
4476
+ type: "paragraph",
4477
+ children: mapInlineChildren(node)
4478
+ };
4479
+ }
4480
+ }
4481
+ function mapParagraph(node) {
4482
+ const alignment = decodeAlignment(node.format);
4483
+ const indent = node.indent && node.indent > 0 ? node.indent : void 0;
4484
+ return {
4485
+ type: "paragraph",
4486
+ ...alignment ? { alignment } : {},
4487
+ ...indent ? { indent } : {},
4488
+ children: mapInlineChildren(node)
4489
+ };
4490
+ }
4491
+ function mapHeading(node) {
4492
+ var _a;
4493
+ const alignment = decodeAlignment(node.format);
4494
+ const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h(\d)$/);
4495
+ const level = tagMatch ? parseInt(tagMatch[1], 10) : 1;
4496
+ return {
4497
+ type: "heading",
4498
+ level,
4499
+ ...alignment ? { alignment } : {},
4500
+ children: mapInlineChildren(node)
4501
+ };
4502
+ }
4503
+ function mapList(node) {
4504
+ const listType = node.listType === "number" ? "ordered" : "unordered";
4505
+ const items = (node.children ?? []).filter((c) => c.type === "listitem").map(mapListItem);
4506
+ return {
4507
+ type: "list",
4508
+ listType,
4509
+ items
4510
+ };
4511
+ }
4512
+ function mapListItem(node) {
4513
+ const inlineChildren = [];
4514
+ let nestedList;
4515
+ for (const child of node.children ?? []) {
4516
+ if (child.type === "list") {
4517
+ nestedList = mapList(child);
4518
+ } else {
4519
+ const mapped = mapInlineNodes([child]);
4520
+ inlineChildren.push(...mapped);
4521
+ }
4522
+ }
4523
+ return {
4524
+ type: "list-item",
4525
+ children: inlineChildren,
4526
+ ...nestedList ? { nestedList } : {}
4527
+ };
4528
+ }
4529
+ function mapBlockQuote(node) {
4530
+ return {
4531
+ type: "blockquote",
4532
+ children: mapInlineChildren(node)
4533
+ };
4534
+ }
4535
+ function mapInlineChildren(node) {
4536
+ if (!node.children || node.children.length === 0) return [];
4537
+ return mapInlineNodes(node.children);
4538
+ }
4539
+ function mapBlockNodes(nodes) {
4540
+ return nodes.map(mapBlockNode);
4541
+ }
4542
+ function mapEditorStateToContent(state) {
4543
+ if (!state || !state.root || !state.root.children) {
4544
+ return null;
4545
+ }
4546
+ const blocks = mapBlockNodes(state.root.children);
4547
+ return { blocks };
4548
+ }
4549
+ function mapEditorStateToBlocks(state) {
4550
+ if (!state || !state.root || !state.root.children) {
4551
+ return [];
4552
+ }
4553
+ return mapBlockNodes(state.root.children);
4554
+ }
4555
+ const MARGIN_TOP_MM = Math.round(PAGE_MARGIN_TOP_PX / PX_PER_MM * 10) / 10;
4556
+ const MARGIN_BOTTOM_MM = Math.round(PAGE_MARGIN_BOTTOM_PX / PX_PER_MM * 10) / 10;
4557
+ const MARGIN_LEFT_MM = Math.round(PAGE_MARGIN_LEFT_PX / PX_PER_MM * 10) / 10;
4558
+ const MARGIN_RIGHT_MM = Math.round(PAGE_MARGIN_RIGHT_PX / PX_PER_MM * 10) / 10;
4559
+ function serializeDocument(document2, variableDefinitions = []) {
4560
+ const pages = document2.pages.map(
4561
+ (page, index) => serializePage(page, index)
4562
+ );
4563
+ const metadata = buildMetadata(variableDefinitions);
4564
+ return {
4565
+ version: AST_VERSION,
4566
+ page: {
4567
+ format: "A4",
4568
+ widthMm: 210,
4569
+ heightMm: 297,
4570
+ margins: {
4571
+ topMm: MARGIN_TOP_MM,
4572
+ rightMm: MARGIN_RIGHT_MM,
4573
+ bottomMm: MARGIN_BOTTOM_MM,
4574
+ leftMm: MARGIN_LEFT_MM
4575
+ }
4576
+ },
4577
+ headerFooter: {
4578
+ enabled: document2.headerFooterEnabled,
4579
+ pageCounterMode: document2.pageCounterMode,
4580
+ defaultHeader: document2.pages.length > 0 ? mapEditorStateToContent(document2.pages[0].headerState) : null,
4581
+ defaultFooter: document2.pages.length > 0 ? mapEditorStateToContent(document2.pages[0].footerState) : null
4582
+ },
4583
+ pages,
4584
+ metadata
4585
+ };
4586
+ }
4587
+ function serializePage(page, pageIndex) {
4588
+ return {
4589
+ pageIndex,
4590
+ body: mapEditorStateToBlocks(page.bodyState),
4591
+ header: mapEditorStateToContent(page.headerState),
4592
+ footer: mapEditorStateToContent(page.footerState)
4593
+ };
4594
+ }
4595
+ function buildMetadata(variableDefinitions) {
4596
+ const variables = {};
4597
+ for (const def of variableDefinitions) {
4598
+ variables[def.key] = {
4599
+ key: def.key,
4600
+ label: def.label,
4601
+ ...def.description ? { description: def.description } : {},
4602
+ ...def.valueType ? { valueType: def.valueType } : {},
4603
+ ...def.group ? { group: def.group } : {}
4604
+ };
4605
+ }
4606
+ return { variables };
4607
+ }
4608
+ function buildSavePayload(ast, options) {
4609
+ return {
4610
+ document: ast,
4611
+ ...(options == null ? void 0 : options.exportTarget) ? { exportTarget: options.exportTarget } : {},
4612
+ ...(options == null ? void 0 : options.documentId) ? { documentId: options.documentId } : {},
4613
+ ...(options == null ? void 0 : options.metadata) ? { metadata: options.metadata } : {}
4614
+ };
4615
+ }
4616
+ function serializeDocumentJson(ast) {
4617
+ return JSON.stringify(ast, null, 2);
4618
+ }
4373
4619
  function selectEntireDocument(rootElement, selectionBuffer) {
4374
4620
  if (!rootElement || !selectionBuffer) {
4375
4621
  return;
@@ -4393,6 +4639,7 @@ function isFormFieldTarget(target) {
4393
4639
  }
4394
4640
  const EditorChrome = ({
4395
4641
  captureHistoryShortcutsOnWindow,
4642
+ onSave,
4396
4643
  className
4397
4644
  }) => {
4398
4645
  const {
@@ -4403,7 +4650,7 @@ const EditorChrome = ({
4403
4650
  undo,
4404
4651
  redo
4405
4652
  } = useDocument();
4406
- const { sidePanels } = useExtensions();
4653
+ const { sidePanels, cssVariables, rootClassNames } = useExtensions();
4407
4654
  const rootRef = React.useRef(null);
4408
4655
  const selectionBufferRef = React.useRef(null);
4409
4656
  const clearGlobalSelection = React.useCallback(() => {
@@ -4465,12 +4712,22 @@ const EditorChrome = ({
4465
4712
  });
4466
4713
  return;
4467
4714
  }
4715
+ if (key === "s" && onSave) {
4716
+ event.preventDefault();
4717
+ event.stopPropagation();
4718
+ const ast = serializeDocument(document2);
4719
+ const json = serializeDocumentJson(ast);
4720
+ onSave({ document: document2, ast, json });
4721
+ return;
4722
+ }
4468
4723
  if (handleHistoryShortcut(event)) {
4469
4724
  return;
4470
4725
  }
4471
4726
  }, [
4727
+ document2,
4472
4728
  dispatch,
4473
4729
  handleHistoryShortcut,
4730
+ onSave,
4474
4731
  setGlobalSelectionActive
4475
4732
  ]);
4476
4733
  const handleMouseDownCapture = React.useCallback((event) => {
@@ -4488,9 +4745,13 @@ const EditorChrome = ({
4488
4745
  const editableRoots = ((_a = rootRef.current) == null ? void 0 : _a.querySelectorAll(
4489
4746
  '[data-testid^="page-body-"] [data-lexical-editor="true"]'
4490
4747
  )) ?? [];
4748
+ const root = rootRef.current;
4749
+ const styles = root ? getComputedStyle(root) : null;
4750
+ const selBg = (styles == null ? void 0 : styles.getPropertyValue("--lex4-color-selection-bg").trim()) || GLOBAL_SELECTION_BACKGROUND;
4751
+ const selFg = (styles == null ? void 0 : styles.getPropertyValue("--lex4-color-selection-text").trim()) || GLOBAL_SELECTION_FOREGROUND;
4491
4752
  editableRoots.forEach((editableRoot) => {
4492
- editableRoot.style.backgroundColor = globalSelectionActive ? GLOBAL_SELECTION_BACKGROUND : "";
4493
- editableRoot.style.color = globalSelectionActive ? GLOBAL_SELECTION_FOREGROUND : "";
4753
+ editableRoot.style.backgroundColor = globalSelectionActive ? selBg : "";
4754
+ editableRoot.style.color = globalSelectionActive ? selFg : "";
4494
4755
  editableRoot.style.caretColor = globalSelectionActive ? "transparent" : "";
4495
4756
  });
4496
4757
  }, [globalSelectionActive, document2.pages.length]);
@@ -4523,11 +4784,14 @@ const EditorChrome = ({
4523
4784
  window.removeEventListener("beforeinput", handleWindowBeforeInput, { capture: true });
4524
4785
  };
4525
4786
  }, [captureHistoryShortcutsOnWindow, clearGlobalSelection, handleHistoryShortcut, redo, undo]);
4787
+ const rootClassName = ["lex4-editor", ...rootClassNames, className].filter(Boolean).join(" ");
4788
+ const extensionStyle = Object.keys(cssVariables).length > 0 ? cssVariables : void 0;
4526
4789
  return /* @__PURE__ */ jsxRuntime.jsxs(
4527
4790
  "div",
4528
4791
  {
4529
4792
  ref: rootRef,
4530
- className: `lex4-editor flex flex-col h-full ${className ?? ""}`,
4793
+ className: rootClassName,
4794
+ style: extensionStyle,
4531
4795
  "data-testid": "lex4-editor",
4532
4796
  "data-global-selection-active": globalSelectionActive ? "true" : "false",
4533
4797
  onKeyDownCapture: handleKeyDownCapture,
@@ -4541,12 +4805,12 @@ const EditorChrome = ({
4541
4805
  "data-testid": "global-selection-buffer",
4542
4806
  readOnly: true,
4543
4807
  tabIndex: -1,
4544
- className: "pointer-events-none fixed -left-[9999px] top-0 h-0 w-0 opacity-0"
4808
+ className: "lex4-selection-buffer"
4545
4809
  }
4546
4810
  ),
4547
4811
  /* @__PURE__ */ jsxRuntime.jsx(Toolbar, {}),
4548
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden bg-gray-200", children: [
4549
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(DocumentView, {}) }),
4812
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-canvas", children: [
4813
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-canvas-scroll", children: /* @__PURE__ */ jsxRuntime.jsx(DocumentView, {}) }),
4550
4814
  sidePanels.map((Panel, idx) => /* @__PURE__ */ jsxRuntime.jsx(Panel, {}, idx)),
4551
4815
  /* @__PURE__ */ jsxRuntime.jsx(HistorySidebar, {})
4552
4816
  ] })
@@ -4554,7 +4818,7 @@ const EditorChrome = ({
4554
4818
  }
4555
4819
  );
4556
4820
  };
4557
- const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, className }, ref) => {
4821
+ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, onSave, className }, ref) => {
4558
4822
  const { document: doc, activeEditor } = useDocument();
4559
4823
  const { handleFactories } = useExtensions();
4560
4824
  const getDocument = React.useCallback(() => doc, [doc]);
@@ -4572,6 +4836,7 @@ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, cl
4572
4836
  EditorChrome,
4573
4837
  {
4574
4838
  captureHistoryShortcutsOnWindow,
4839
+ onSave,
4575
4840
  className
4576
4841
  }
4577
4842
  );
@@ -4581,6 +4846,7 @@ const Lex4Editor = React.forwardRef(({
4581
4846
  captureHistoryShortcutsOnWindow = true,
4582
4847
  initialDocument,
4583
4848
  onDocumentChange,
4849
+ onSave,
4584
4850
  extensions,
4585
4851
  translations,
4586
4852
  className
@@ -4595,6 +4861,7 @@ const Lex4Editor = React.forwardRef(({
4595
4861
  {
4596
4862
  ref,
4597
4863
  captureHistoryShortcutsOnWindow,
4864
+ onSave,
4598
4865
  className
4599
4866
  }
4600
4867
  )
@@ -4667,247 +4934,6 @@ function useHeaderFooter(maxHeight, onHeightChange) {
4667
4934
  }, []);
4668
4935
  return { attachRef };
4669
4936
  }
4670
- const AST_VERSION = "1.0.0";
4671
- const IS_BOLD = 1;
4672
- const IS_ITALIC = 2;
4673
- const IS_STRIKETHROUGH = 4;
4674
- const IS_UNDERLINE = 8;
4675
- function decodeFormatBitmask(format) {
4676
- const marks = {};
4677
- if (format & IS_BOLD) marks.bold = true;
4678
- if (format & IS_ITALIC) marks.italic = true;
4679
- if (format & IS_UNDERLINE) marks.underline = true;
4680
- if (format & IS_STRIKETHROUGH) marks.strikethrough = true;
4681
- return marks;
4682
- }
4683
- function extractFontFamily(style) {
4684
- const match = style.match(/font-family:\s*([^;]+)/);
4685
- return match ? match[1].trim().replace(/['"]/g, "") : void 0;
4686
- }
4687
- function extractFontSizePt(style) {
4688
- const match = style.match(/font-size:\s*(\d+(?:\.\d+)?)\s*pt/);
4689
- return match ? parseFloat(match[1]) : void 0;
4690
- }
4691
- function buildTextMarks(format, style) {
4692
- const formatMarks = decodeFormatBitmask(format);
4693
- const fontFamily = style ? extractFontFamily(style) : void 0;
4694
- const fontSize = style ? extractFontSizePt(style) : void 0;
4695
- const marks = {
4696
- ...formatMarks,
4697
- ...fontFamily ? { fontFamily } : {},
4698
- ...fontSize ? { fontSize } : {}
4699
- };
4700
- return Object.keys(marks).length > 0 ? marks : void 0;
4701
- }
4702
- function mapInlineNode(node) {
4703
- switch (node.type) {
4704
- case "text":
4705
- return mapTextNode(node);
4706
- case "variable-node":
4707
- return mapVariableNode(node);
4708
- case "linebreak":
4709
- return mapLineBreak();
4710
- default:
4711
- return { type: "text", text: "" };
4712
- }
4713
- }
4714
- function mapTextNode(node) {
4715
- const marks = buildTextMarks(node.format, node.style);
4716
- return {
4717
- type: "text",
4718
- text: node.text,
4719
- ...marks ? { marks } : {}
4720
- };
4721
- }
4722
- function mapVariableNode(node) {
4723
- return {
4724
- type: "variable",
4725
- key: node.variableKey
4726
- };
4727
- }
4728
- function mapLineBreak() {
4729
- return { type: "linebreak" };
4730
- }
4731
- function mapInlineNodes(nodes) {
4732
- return nodes.map(mapInlineNode);
4733
- }
4734
- const ALIGN_LEFT = 1;
4735
- const ALIGN_CENTER = 2;
4736
- const ALIGN_RIGHT = 3;
4737
- const ALIGN_JUSTIFY = 4;
4738
- function decodeAlignment(format) {
4739
- if (typeof format === "string") {
4740
- if (["left", "center", "right", "justify"].includes(format)) {
4741
- return format;
4742
- }
4743
- return void 0;
4744
- }
4745
- if (typeof format !== "number" || format === 0) return void 0;
4746
- switch (format) {
4747
- case ALIGN_LEFT:
4748
- return "left";
4749
- case ALIGN_CENTER:
4750
- return "center";
4751
- case ALIGN_RIGHT:
4752
- return "right";
4753
- case ALIGN_JUSTIFY:
4754
- return "justify";
4755
- default:
4756
- return void 0;
4757
- }
4758
- }
4759
- function mapBlockNode(node) {
4760
- switch (node.type) {
4761
- case "paragraph":
4762
- return mapParagraph(node);
4763
- case "heading":
4764
- return mapHeading(node);
4765
- case "list":
4766
- return mapList(node);
4767
- case "quote":
4768
- return mapBlockQuote(node);
4769
- default:
4770
- return {
4771
- type: "paragraph",
4772
- children: mapInlineChildren(node)
4773
- };
4774
- }
4775
- }
4776
- function mapParagraph(node) {
4777
- const alignment = decodeAlignment(node.format);
4778
- const indent = node.indent && node.indent > 0 ? node.indent : void 0;
4779
- return {
4780
- type: "paragraph",
4781
- ...alignment ? { alignment } : {},
4782
- ...indent ? { indent } : {},
4783
- children: mapInlineChildren(node)
4784
- };
4785
- }
4786
- function mapHeading(node) {
4787
- var _a;
4788
- const alignment = decodeAlignment(node.format);
4789
- const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h(\d)$/);
4790
- const level = tagMatch ? parseInt(tagMatch[1], 10) : 1;
4791
- return {
4792
- type: "heading",
4793
- level,
4794
- ...alignment ? { alignment } : {},
4795
- children: mapInlineChildren(node)
4796
- };
4797
- }
4798
- function mapList(node) {
4799
- const listType = node.listType === "number" ? "ordered" : "unordered";
4800
- const items = (node.children ?? []).filter((c) => c.type === "listitem").map(mapListItem);
4801
- return {
4802
- type: "list",
4803
- listType,
4804
- items
4805
- };
4806
- }
4807
- function mapListItem(node) {
4808
- const inlineChildren = [];
4809
- let nestedList;
4810
- for (const child of node.children ?? []) {
4811
- if (child.type === "list") {
4812
- nestedList = mapList(child);
4813
- } else {
4814
- const mapped = mapInlineNodes([child]);
4815
- inlineChildren.push(...mapped);
4816
- }
4817
- }
4818
- return {
4819
- type: "list-item",
4820
- children: inlineChildren,
4821
- ...nestedList ? { nestedList } : {}
4822
- };
4823
- }
4824
- function mapBlockQuote(node) {
4825
- return {
4826
- type: "blockquote",
4827
- children: mapInlineChildren(node)
4828
- };
4829
- }
4830
- function mapInlineChildren(node) {
4831
- if (!node.children || node.children.length === 0) return [];
4832
- return mapInlineNodes(node.children);
4833
- }
4834
- function mapBlockNodes(nodes) {
4835
- return nodes.map(mapBlockNode);
4836
- }
4837
- function mapEditorStateToContent(state) {
4838
- if (!state || !state.root || !state.root.children) {
4839
- return null;
4840
- }
4841
- const blocks = mapBlockNodes(state.root.children);
4842
- return { blocks };
4843
- }
4844
- function mapEditorStateToBlocks(state) {
4845
- if (!state || !state.root || !state.root.children) {
4846
- return [];
4847
- }
4848
- return mapBlockNodes(state.root.children);
4849
- }
4850
- const MARGIN_MM = Math.round(PAGE_MARGIN_PX / PX_PER_MM * 10) / 10;
4851
- function serializeDocument(document2, variableDefinitions = []) {
4852
- const pages = document2.pages.map(
4853
- (page, index) => serializePage(page, index)
4854
- );
4855
- const metadata = buildMetadata(variableDefinitions);
4856
- return {
4857
- version: AST_VERSION,
4858
- page: {
4859
- format: "A4",
4860
- widthMm: 210,
4861
- heightMm: 297,
4862
- margins: {
4863
- topMm: MARGIN_MM,
4864
- rightMm: MARGIN_MM,
4865
- bottomMm: MARGIN_MM,
4866
- leftMm: MARGIN_MM
4867
- }
4868
- },
4869
- headerFooter: {
4870
- enabled: document2.headerFooterEnabled,
4871
- pageCounterMode: document2.pageCounterMode,
4872
- defaultHeader: document2.pages.length > 0 ? mapEditorStateToContent(document2.pages[0].headerState) : null,
4873
- defaultFooter: document2.pages.length > 0 ? mapEditorStateToContent(document2.pages[0].footerState) : null
4874
- },
4875
- pages,
4876
- metadata
4877
- };
4878
- }
4879
- function serializePage(page, pageIndex) {
4880
- return {
4881
- pageIndex,
4882
- body: mapEditorStateToBlocks(page.bodyState),
4883
- header: mapEditorStateToContent(page.headerState),
4884
- footer: mapEditorStateToContent(page.footerState)
4885
- };
4886
- }
4887
- function buildMetadata(variableDefinitions) {
4888
- const variables = {};
4889
- for (const def of variableDefinitions) {
4890
- variables[def.key] = {
4891
- key: def.key,
4892
- label: def.label,
4893
- ...def.description ? { description: def.description } : {},
4894
- ...def.valueType ? { valueType: def.valueType } : {},
4895
- ...def.group ? { group: def.group } : {}
4896
- };
4897
- }
4898
- return { variables };
4899
- }
4900
- function buildSavePayload(ast, options) {
4901
- return {
4902
- document: ast,
4903
- ...(options == null ? void 0 : options.exportTarget) ? { exportTarget: options.exportTarget } : {},
4904
- ...(options == null ? void 0 : options.documentId) ? { documentId: options.documentId } : {},
4905
- ...(options == null ? void 0 : options.metadata) ? { metadata: options.metadata } : {}
4906
- };
4907
- }
4908
- function serializeDocumentJson(ast) {
4909
- return JSON.stringify(ast, null, 2);
4910
- }
4911
4937
  function astExtension() {
4912
4938
  return {
4913
4939
  name: "ast",
@@ -5033,7 +5059,7 @@ function VariableChip({ variableKey }) {
5033
5059
  return /* @__PURE__ */ jsxRuntime.jsx(
5034
5060
  "span",
5035
5061
  {
5036
- className: "lex4-variable-chip inline-flex items-center rounded-full border border-blue-300\n bg-white px-2 py-0.5 text-[11px] font-medium text-blue-700 select-none\n cursor-default whitespace-nowrap mx-0.5",
5062
+ className: "lex4-variable-chip",
5037
5063
  "data-testid": `variable-chip-${variableKey}`,
5038
5064
  title: variableKey,
5039
5065
  children: label
@@ -5106,12 +5132,12 @@ const VariablePicker = ({ onInsert, disabled = false }) => {
5106
5132
  document.addEventListener("mousedown", handler);
5107
5133
  return () => document.removeEventListener("mousedown", handler);
5108
5134
  }, [open]);
5109
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative inline-block", children: [
5135
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "lex4-variable-picker", children: [
5110
5136
  /* @__PURE__ */ jsxRuntime.jsx(
5111
5137
  "button",
5112
5138
  {
5113
5139
  type: "button",
5114
- 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",
5140
+ className: "lex4-variable-picker-btn",
5115
5141
  "data-testid": "variable-picker-button",
5116
5142
  disabled: disabled || definitions.length === 0,
5117
5143
  onClick: () => setOpen(!open),
@@ -5122,14 +5148,13 @@ const VariablePicker = ({ onInsert, disabled = false }) => {
5122
5148
  open && /* @__PURE__ */ jsxRuntime.jsxs(
5123
5149
  "div",
5124
5150
  {
5125
- className: "absolute left-0 top-full z-50 mt-1 w-64 rounded-md border border-gray-200\n bg-white shadow-lg",
5151
+ className: "lex4-variable-picker-dropdown",
5126
5152
  "data-testid": "variable-picker-dropdown",
5127
5153
  children: [
5128
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-gray-100 p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
5154
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-variable-picker-search", children: /* @__PURE__ */ jsxRuntime.jsx(
5129
5155
  "input",
5130
5156
  {
5131
5157
  type: "text",
5132
- className: "w-full rounded border border-gray-200 px-2 py-1 text-xs\n focus:border-blue-400 focus:outline-none",
5133
5158
  placeholder: "Search variables...",
5134
5159
  "data-testid": "variable-picker-search",
5135
5160
  value: filter,
@@ -5137,20 +5162,20 @@ const VariablePicker = ({ onInsert, disabled = false }) => {
5137
5162
  autoFocus: true
5138
5163
  }
5139
5164
  ) }),
5140
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-h-48 overflow-y-auto p-1", children: [
5141
- Object.keys(grouped).length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1 text-xs text-gray-400", children: "No variables found" }),
5165
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-variable-picker-list", children: [
5166
+ Object.keys(grouped).length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-variable-picker-empty", children: "No variables found" }),
5142
5167
  Object.entries(grouped).map(([group, defs]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5143
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1 text-[10px] font-semibold uppercase text-gray-400", children: group }),
5168
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-variable-picker-group-label", children: group }),
5144
5169
  defs.map((def) => /* @__PURE__ */ jsxRuntime.jsxs(
5145
5170
  "button",
5146
5171
  {
5147
5172
  type: "button",
5148
- className: "flex w-full items-center gap-2 rounded px-2 py-1 text-left text-xs\n hover:bg-blue-50",
5173
+ className: "lex4-variable-picker-option",
5149
5174
  "data-testid": `variable-option-${def.key}`,
5150
5175
  onClick: () => handleInsert(def.key),
5151
5176
  children: [
5152
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-blue-700", children: `{{${def.key}}}` }),
5153
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500", children: def.label })
5177
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-variable-picker-key", children: `{{${def.key}}}` }),
5178
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-variable-picker-label", children: def.label })
5154
5179
  ]
5155
5180
  },
5156
5181
  def.key
@@ -5208,20 +5233,20 @@ const VariablePanel = ({ open, onClose }) => {
5208
5233
  "button",
5209
5234
  {
5210
5235
  type: "button",
5211
- 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",
5236
+ className: "lex4-sidebar-action-btn",
5212
5237
  title: t.variables.refreshVariables,
5213
5238
  "data-testid": "btn-refresh-variables",
5214
5239
  children: /* @__PURE__ */ jsxRuntime.jsx(RefreshCw, { size: 12 })
5215
5240
  }
5216
5241
  ),
5217
5242
  children: [
5218
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
5219
- /* @__PURE__ */ jsxRuntime.jsx(Search, { size: 14, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-400" }),
5243
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-variable-search-container", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-variable-search-wrapper", children: [
5244
+ /* @__PURE__ */ jsxRuntime.jsx(Search, { size: 14, className: "lex4-variable-search-icon" }),
5220
5245
  /* @__PURE__ */ jsxRuntime.jsx(
5221
5246
  "input",
5222
5247
  {
5223
5248
  type: "text",
5224
- 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",
5249
+ className: "lex4-variable-search-input",
5225
5250
  placeholder: t.variables.searchPlaceholder,
5226
5251
  "data-testid": "variable-panel-search",
5227
5252
  value: filter,
@@ -5229,25 +5254,19 @@ const VariablePanel = ({ open, onClose }) => {
5229
5254
  }
5230
5255
  )
5231
5256
  ] }) }),
5232
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 pb-3", children: [
5233
- Object.keys(grouped).length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-4 text-center text-xs text-gray-400", children: t.variables.noVariablesFound }),
5234
- 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(
5257
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-variable-list", children: [
5258
+ Object.keys(grouped).length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-variable-list-empty", children: t.variables.noVariablesFound }),
5259
+ Object.entries(grouped).map(([group, defs]) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-variable-group", children: /* @__PURE__ */ jsxRuntime.jsx("div", { children: defs.map((def) => /* @__PURE__ */ jsxRuntime.jsxs(
5235
5260
  "button",
5236
5261
  {
5237
5262
  type: "button",
5238
- 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",
5263
+ className: "lex4-variable-list-item",
5239
5264
  "data-testid": `variable-panel-${def.key}`,
5240
5265
  onClick: () => handleInsert(def.key),
5241
5266
  disabled: !activeEditor,
5242
5267
  children: [
5243
- /* @__PURE__ */ jsxRuntime.jsx(
5244
- "span",
5245
- {
5246
- 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",
5247
- children: def.label
5248
- }
5249
- ),
5250
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wider text-gray-400", children: group })
5268
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-variable-badge", children: def.label }),
5269
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-variable-group-label", children: group })
5251
5270
  ]
5252
5271
  },
5253
5272
  def.key
@@ -5301,10 +5320,7 @@ const VariablePanelToggle = () => {
5301
5320
  "aria-label": panelOpen ? t.variables.closePanel : t.variables.openPanel,
5302
5321
  onMouseDown: (e) => e.preventDefault(),
5303
5322
  onClick: () => setPanelOpen(!panelOpen),
5304
- className: `
5305
- flex h-7 w-7 items-center justify-center rounded transition-colors
5306
- ${panelOpen ? "bg-blue-50 text-blue-600" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"}
5307
- `,
5323
+ className: `lex4-toolbar-btn${panelOpen ? " active" : ""}`,
5308
5324
  "data-testid": "toggle-variable-panel",
5309
5325
  children: /* @__PURE__ */ jsxRuntime.jsx(Braces, { size: 15 })
5310
5326
  }