@yurikilian/lex4 0.3.2 → 1.0.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.
@@ -1742,21 +1742,21 @@ const EditorSidebar = ({
1742
1742
  return /* @__PURE__ */ jsxRuntime.jsxs(
1743
1743
  "aside",
1744
1744
  {
1745
- className: "flex h-full w-[320px] shrink-0 flex-col border-l border-gray-200 bg-white",
1745
+ className: "lex4-sidebar",
1746
1746
  "data-testid": testId,
1747
1747
  children: [
1748
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between border-b border-gray-200 px-3 py-2.5", children: [
1748
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-sidebar-header", children: [
1749
1749
  /* @__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 })
1750
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "lex4-sidebar-title", children: title }),
1751
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lex4-sidebar-subtitle", children: subtitle })
1752
1752
  ] }),
1753
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
1753
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-sidebar-actions", children: [
1754
1754
  headerActions,
1755
1755
  onClose && /* @__PURE__ */ jsxRuntime.jsx(
1756
1756
  "button",
1757
1757
  {
1758
1758
  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",
1759
+ className: "lex4-sidebar-action-btn",
1760
1760
  "data-testid": "close-editor-sidebar",
1761
1761
  onClick: onClose,
1762
1762
  "aria-label": t.sidebar.close,
@@ -1765,7 +1765,7 @@ const EditorSidebar = ({
1765
1765
  )
1766
1766
  ] })
1767
1767
  ] }),
1768
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-auto", children })
1768
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-sidebar-body", children })
1769
1769
  ]
1770
1770
  }
1771
1771
  );
@@ -1797,7 +1797,7 @@ const HistorySidebar = () => {
1797
1797
  "aria-label": t.history.clearHistory,
1798
1798
  onMouseDown: (e) => e.preventDefault(),
1799
1799
  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",
1800
+ className: "lex4-sidebar-action-btn",
1801
1801
  "data-testid": "clear-history",
1802
1802
  children: /* @__PURE__ */ jsxRuntime.jsx(Trash2, { size: 13 })
1803
1803
  }
@@ -1814,27 +1814,27 @@ const HistorySidebar = () => {
1814
1814
  children: visibleEntries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1815
1815
  "div",
1816
1816
  {
1817
- className: "px-3 py-4 text-xs text-gray-500",
1817
+ className: "lex4-history-empty",
1818
1818
  "data-testid": "history-empty",
1819
1819
  children: t.history.empty
1820
1820
  }
1821
- ) : /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "divide-y divide-gray-100", "data-testid": "history-entry-list", children: visibleEntries.map((entry, reversedIndex) => {
1821
+ ) : /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "lex4-history-list", "data-testid": "history-entry-list", children: visibleEntries.map((entry, reversedIndex) => {
1822
1822
  const actualIndex = historyEntries.length - reversedIndex - 1;
1823
1823
  const isCurrent = actualIndex === historyCursor - 1;
1824
1824
  return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
1825
1825
  "button",
1826
1826
  {
1827
1827
  type: "button",
1828
- className: `w-full px-3 py-2 text-left transition-colors ${isCurrent ? "bg-blue-50" : "bg-white hover:bg-gray-50"}`,
1828
+ className: `lex4-history-entry ${isCurrent ? "active" : ""}`,
1829
1829
  "data-testid": `history-entry-${actualIndex}`,
1830
1830
  "data-history-current": isCurrent ? "true" : "false",
1831
1831
  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 })
1832
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-history-entry-row", children: [
1833
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-history-entry-content", children: [
1834
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `lex4-history-entry-label${isCurrent ? " current" : ""}`, children: entry.label }),
1835
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-history-entry-source", children: t.regions[entry.source] ?? entry.source })
1836
1836
  ] }),
1837
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 text-xs text-gray-400", children: formatTimestamp(entry.timestamp) })
1837
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-history-entry-time", children: formatTimestamp(entry.timestamp) })
1838
1838
  ] })
1839
1839
  }
1840
1840
  ) }, entry.id);
@@ -1850,11 +1850,11 @@ const HeaderFooterToggle = ({
1850
1850
  return /* @__PURE__ */ jsxRuntime.jsxs(
1851
1851
  "label",
1852
1852
  {
1853
- className: "flex items-center gap-1.5 cursor-pointer select-none",
1853
+ className: "lex4-hf-toggle",
1854
1854
  "data-testid": "header-footer-toggle",
1855
1855
  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 }),
1856
+ /* @__PURE__ */ jsxRuntime.jsx(Rows3, { size: 14, className: "lex4-hf-toggle-icon" }),
1857
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-hf-toggle-label", children: t.headerFooter.label }),
1858
1858
  /* @__PURE__ */ jsxRuntime.jsx(
1859
1859
  "button",
1860
1860
  {
@@ -1863,22 +1863,10 @@ const HeaderFooterToggle = ({
1863
1863
  "aria-checked": enabled,
1864
1864
  onMouseDown: (e) => e.preventDefault(),
1865
1865
  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
- `,
1866
+ className: "lex4-hf-switch",
1867
+ style: { backgroundColor: enabled ? "var(--lex4-color-primary)" : "var(--lex4-color-text-disabled)" },
1871
1868
  "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
- )
1869
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-hf-switch-knob" })
1882
1870
  }
1883
1871
  )
1884
1872
  ]
@@ -1920,17 +1908,14 @@ const HeaderFooterActions = ({
1920
1908
  action();
1921
1909
  close();
1922
1910
  };
1923
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", "data-testid": "header-footer-actions", children: [
1911
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, style: { position: "relative" }, "data-testid": "header-footer-actions", children: [
1924
1912
  /* @__PURE__ */ jsxRuntime.jsxs(
1925
1913
  "button",
1926
1914
  {
1927
1915
  type: "button",
1928
1916
  onMouseDown: (e) => e.preventDefault(),
1929
1917
  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
- `,
1918
+ className: "lex4-settings-trigger",
1934
1919
  "data-testid": "header-footer-menu-trigger",
1935
1920
  "aria-expanded": open,
1936
1921
  "aria-haspopup": "true",
@@ -1943,12 +1928,12 @@ const HeaderFooterActions = ({
1943
1928
  open && /* @__PURE__ */ jsxRuntime.jsxs(
1944
1929
  "div",
1945
1930
  {
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",
1931
+ className: "lex4-settings-menu",
1947
1932
  role: "menu",
1948
1933
  "data-testid": "header-footer-menu",
1949
1934
  children: [
1950
1935
  /* @__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(
1936
+ /* @__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
1937
  "button",
1953
1938
  {
1954
1939
  type: "button",
@@ -1956,10 +1941,7 @@ const HeaderFooterActions = ({
1956
1941
  "aria-checked": pageCounterMode === value,
1957
1942
  onMouseDown: (e) => e.preventDefault(),
1958
1943
  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
- `,
1944
+ className: "lex4-settings-counter-btn",
1963
1945
  "data-testid": `page-counter-${value}`,
1964
1946
  children: label
1965
1947
  },
@@ -2032,7 +2014,7 @@ const HeaderFooterActions = ({
2032
2014
  )
2033
2015
  ] });
2034
2016
  };
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 });
2017
+ const SectionLabel = ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-settings-label", children });
2036
2018
  const MenuItem = ({ icon, label, onClick, disabled, testId }) => /* @__PURE__ */ jsxRuntime.jsxs(
2037
2019
  "button",
2038
2020
  {
@@ -2041,7 +2023,7 @@ const MenuItem = ({ icon, label, onClick, disabled, testId }) => /* @__PURE__ */
2041
2023
  onMouseDown: (e) => e.preventDefault(),
2042
2024
  onClick,
2043
2025
  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",
2026
+ className: "lex4-settings-item",
2045
2027
  "data-testid": testId,
2046
2028
  children: [
2047
2029
  icon,
@@ -2049,7 +2031,7 @@ const MenuItem = ({ icon, label, onClick, disabled, testId }) => /* @__PURE__ */
2049
2031
  ]
2050
2032
  }
2051
2033
  );
2052
- const MenuDivider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-1 h-px bg-gray-100" });
2034
+ const MenuDivider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-settings-divider" });
2053
2035
  function resolveExtensions(extensions) {
2054
2036
  const resolved = {
2055
2037
  nodes: [],
@@ -2058,6 +2040,8 @@ function resolveExtensions(extensions) {
2058
2040
  sidePanels: [],
2059
2041
  providers: [],
2060
2042
  themeOverrides: {},
2043
+ cssVariables: {},
2044
+ rootClassNames: [],
2061
2045
  handleFactories: []
2062
2046
  };
2063
2047
  for (const ext of extensions) {
@@ -2069,6 +2053,10 @@ function resolveExtensions(extensions) {
2069
2053
  if (ext.themeOverrides) {
2070
2054
  resolved.themeOverrides = { ...resolved.themeOverrides, ...ext.themeOverrides };
2071
2055
  }
2056
+ if (ext.cssVariables) {
2057
+ Object.assign(resolved.cssVariables, ext.cssVariables);
2058
+ }
2059
+ if (ext.rootClassName) resolved.rootClassNames.push(ext.rootClassName);
2072
2060
  if (ext.handleMethods) resolved.handleFactories.push(ext.handleMethods);
2073
2061
  }
2074
2062
  return resolved;
@@ -2080,14 +2068,27 @@ const EMPTY_RESOLVED = {
2080
2068
  sidePanels: [],
2081
2069
  providers: [],
2082
2070
  themeOverrides: {},
2071
+ cssVariables: {},
2072
+ rootClassNames: [],
2083
2073
  handleFactories: []
2084
2074
  };
2085
2075
  const ExtensionResolvedContext = React.createContext(EMPTY_RESOLVED);
2076
+ function arraysEqual(a, b) {
2077
+ if (a.length !== b.length) return false;
2078
+ for (let i = 0; i < a.length; i++) {
2079
+ if (a[i] !== b[i]) return false;
2080
+ }
2081
+ return true;
2082
+ }
2086
2083
  const ExtensionProvider = ({ extensions, children }) => {
2087
- const resolved = React.useMemo(
2088
- () => extensions && extensions.length > 0 ? resolveExtensions(extensions) : EMPTY_RESOLVED,
2089
- [extensions]
2090
- );
2084
+ const prevNamesRef = React.useRef([]);
2085
+ const resolvedRef = React.useRef(EMPTY_RESOLVED);
2086
+ const currentNames = (extensions ?? []).map((e) => e.name);
2087
+ if (!arraysEqual(currentNames, prevNamesRef.current)) {
2088
+ resolvedRef.current = extensions && extensions.length > 0 ? resolveExtensions(extensions) : EMPTY_RESOLVED;
2089
+ prevNamesRef.current = currentNames;
2090
+ }
2091
+ const resolved = resolvedRef.current;
2091
2092
  let wrapped = /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
2092
2093
  for (let i = resolved.providers.length - 1; i >= 0; i--) {
2093
2094
  const Provider = resolved.providers[i];
@@ -2394,11 +2395,11 @@ const Toolbar = () => {
2394
2395
  return /* @__PURE__ */ jsxRuntime.jsxs(
2395
2396
  "div",
2396
2397
  {
2397
- className: "lex4-toolbar sticky top-0 z-10 bg-white border-b border-gray-200",
2398
+ className: "lex4-toolbar",
2398
2399
  "data-testid": "toolbar",
2399
2400
  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: [
2401
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-row", children: [
2402
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group", "data-testid": "history-controls", children: [
2402
2403
  /* @__PURE__ */ jsxRuntime.jsx(
2403
2404
  ToolbarIconButton,
2404
2405
  {
@@ -2421,12 +2422,12 @@ const Toolbar = () => {
2421
2422
  )
2422
2423
  ] }),
2423
2424
  /* @__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" }),
2425
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group-gap", children: [
2426
+ /* @__PURE__ */ jsxRuntime.jsx(Type, { size: 14, className: "lex4-toolbar-icon" }),
2426
2427
  /* @__PURE__ */ jsxRuntime.jsx(
2427
2428
  "select",
2428
2429
  {
2429
- className: "h-7 rounded border border-gray-200 bg-white px-2 text-xs text-gray-700\n focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-400",
2430
+ className: "lex4-toolbar-select",
2430
2431
  "data-testid": "font-selector",
2431
2432
  defaultValue: "Calibri",
2432
2433
  onChange: handleFontChange,
@@ -2434,12 +2435,12 @@ const Toolbar = () => {
2434
2435
  }
2435
2436
  )
2436
2437
  ] }),
2437
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
2438
- /* @__PURE__ */ jsxRuntime.jsx(ALargeSmall, { size: 14, className: "text-gray-500" }),
2438
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group-gap", children: [
2439
+ /* @__PURE__ */ jsxRuntime.jsx(ALargeSmall, { size: 14, className: "lex4-toolbar-icon" }),
2439
2440
  /* @__PURE__ */ jsxRuntime.jsx(
2440
2441
  "select",
2441
2442
  {
2442
- className: "h-7 w-16 rounded border border-gray-200 bg-white px-1 text-xs text-gray-700\n focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-400",
2443
+ className: "lex4-toolbar-select lex4-toolbar-select-narrow",
2443
2444
  "data-testid": "font-size-selector",
2444
2445
  defaultValue: "12",
2445
2446
  onChange: handleFontSizeChange,
@@ -2448,21 +2449,21 @@ const Toolbar = () => {
2448
2449
  )
2449
2450
  ] }),
2450
2451
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2451
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "format-group", children: [
2452
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group", "data-testid": "format-group", children: [
2452
2453
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bold, testId: "btn-bold", onClick: handleBold, children: /* @__PURE__ */ jsxRuntime.jsx(Bold, { size: 15 }) }),
2453
2454
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.italic, testId: "btn-italic", onClick: handleItalic, children: /* @__PURE__ */ jsxRuntime.jsx(Italic, { size: 15 }) }),
2454
2455
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.underline, testId: "btn-underline", onClick: handleUnderline, children: /* @__PURE__ */ jsxRuntime.jsx(Underline, { size: 15 }) }),
2455
2456
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.strikethrough, testId: "btn-strike", onClick: handleStrikethrough, children: /* @__PURE__ */ jsxRuntime.jsx(Strikethrough, { size: 15 }) })
2456
2457
  ] }),
2457
2458
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2458
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "align-group", children: [
2459
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group", "data-testid": "align-group", children: [
2459
2460
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignLeft, testId: "btn-align-left", onClick: handleAlignLeft, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignStart, { size: 15 }) }),
2460
2461
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignCenter, testId: "btn-align-center", onClick: handleAlignCenter, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignCenter, { size: 15 }) }),
2461
2462
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.alignRight, testId: "btn-align-right", onClick: handleAlignRight, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignEnd, { size: 15 }) }),
2462
2463
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.justify, testId: "btn-align-justify", onClick: handleAlignJustify, children: /* @__PURE__ */ jsxRuntime.jsx(TextAlignJustify, { size: 15 }) })
2463
2464
  ] }),
2464
2465
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2465
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", "data-testid": "list-group", children: [
2466
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-toolbar-group", "data-testid": "list-group", children: [
2466
2467
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.numberedList, testId: "btn-list-number", onClick: handleListNumber, children: /* @__PURE__ */ jsxRuntime.jsx(ListOrdered, { size: 15 }) }),
2467
2468
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.bulletList, testId: "btn-list-bullet", onClick: handleListBullet, children: /* @__PURE__ */ jsxRuntime.jsx(List, { size: 15 }) }),
2468
2469
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarIconButton, { title: t.toolbar.indent, testId: "btn-indent", onClick: handleIndent, children: /* @__PURE__ */ jsxRuntime.jsx(ListIndentIncrease, { size: 15 }) }),
@@ -2472,7 +2473,7 @@ const Toolbar = () => {
2472
2473
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
2473
2474
  toolbarItems.map((ToolbarItem, idx) => /* @__PURE__ */ jsxRuntime.jsx(ToolbarItem, {}, idx))
2474
2475
  ] }),
2475
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
2476
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-toolbar-end", children: /* @__PURE__ */ jsxRuntime.jsx(
2476
2477
  ToolbarIconButton,
2477
2478
  {
2478
2479
  title: historySidebarOpen ? t.toolbar.closeHistory : t.toolbar.openHistory,
@@ -2483,7 +2484,7 @@ const Toolbar = () => {
2483
2484
  }
2484
2485
  ) })
2485
2486
  ] }),
2486
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 px-2 py-1 border-t border-gray-100", children: [
2487
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-hf-row", children: [
2487
2488
  /* @__PURE__ */ jsxRuntime.jsx(
2488
2489
  HeaderFooterToggle,
2489
2490
  {
@@ -2526,15 +2527,12 @@ const ToolbarIconButton = ({
2526
2527
  disabled,
2527
2528
  onMouseDown: (e) => e.preventDefault(),
2528
2529
  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
- `,
2530
+ className: `lex4-toolbar-btn${active ? " active" : ""}`,
2533
2531
  "data-testid": testId,
2534
2532
  children
2535
2533
  }
2536
2534
  );
2537
- const Divider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-0.5 h-5 w-px bg-gray-200" });
2535
+ const Divider = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-toolbar-separator" });
2538
2536
  function computeBodyHeight(headerHeight, footerHeight) {
2539
2537
  const verticalMargins = PAGE_MARGIN_PX * 2;
2540
2538
  return A4_HEIGHT_PX - headerHeight - footerHeight - verticalMargins;
@@ -2841,33 +2839,33 @@ function usePagination(document2, dispatch) {
2841
2839
  };
2842
2840
  }
2843
2841
  const lexicalTheme = {
2844
- root: "lex4-root outline-none",
2845
- paragraph: "lex4-paragraph text-justify",
2842
+ root: "lex4-root",
2843
+ paragraph: "lex4-paragraph",
2846
2844
  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"
2845
+ h1: "lex4-heading lex4-heading-h1",
2846
+ h2: "lex4-heading lex4-heading-h2",
2847
+ h3: "lex4-heading lex4-heading-h3",
2848
+ h4: "lex4-heading lex4-heading-h4",
2849
+ h5: "lex4-heading lex4-heading-h5"
2852
2850
  },
2853
2851
  text: {
2854
- bold: "font-bold",
2855
- italic: "italic",
2856
- underline: "underline",
2857
- strikethrough: "line-through",
2858
- underlineStrikethrough: "underline line-through"
2852
+ bold: "lex4-text-bold",
2853
+ italic: "lex4-text-italic",
2854
+ underline: "lex4-text-underline",
2855
+ strikethrough: "lex4-text-strikethrough",
2856
+ underlineStrikethrough: "lex4-text-underline lex4-text-strikethrough"
2859
2857
  },
2860
2858
  list: {
2861
2859
  nested: {
2862
- listitem: "list-none"
2860
+ listitem: "lex4-listitem-nested"
2863
2861
  },
2864
- ol: "list-decimal ml-6",
2865
- ul: "list-disc ml-6",
2862
+ ol: "lex4-list lex4-list-ordered",
2863
+ ul: "lex4-list lex4-list-unordered",
2866
2864
  listitem: "lex4-listitem",
2867
2865
  listitemChecked: "lex4-listitem-checked",
2868
2866
  listitemUnchecked: "lex4-listitem-unchecked"
2869
2867
  },
2870
- quote: "border-l-4 border-gray-300 pl-4 italic text-gray-600"
2868
+ quote: "lex4-quote"
2871
2869
  };
2872
2870
  const DEFAULT_NODES = [richText.HeadingNode, richText.QuoteNode, list.ListNode, list.ListItemNode];
2873
2871
  function createEditorConfig(mode, pageId, extraNodes = [], themeOverrides = {}) {
@@ -3758,8 +3756,7 @@ const PageBody = ({
3758
3756
  return /* @__PURE__ */ jsxRuntime.jsx(
3759
3757
  "div",
3760
3758
  {
3761
- className: "lex4-page-body flex-1 min-h-0 relative",
3762
- style: { overflow: "hidden" },
3759
+ className: "lex4-page-body",
3763
3760
  "data-testid": `page-body-${pageId}`,
3764
3761
  onFocus,
3765
3762
  children: /* @__PURE__ */ jsxRuntime.jsxs(LexicalComposer.LexicalComposer, { initialConfig: config, children: [
@@ -3769,11 +3766,10 @@ const PageBody = ({
3769
3766
  contentEditable: /* @__PURE__ */ jsxRuntime.jsx(
3770
3767
  LexicalContentEditable.ContentEditable,
3771
3768
  {
3772
- className: "outline-none h-full p-0",
3773
- style: { overflow: "visible" }
3769
+ className: "lex4-page-body-editable"
3774
3770
  }
3775
3771
  ),
3776
- placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-0 left-0 text-gray-400 pointer-events-none select-none", children: t.body.placeholder }),
3772
+ placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-page-placeholder", children: t.body.placeholder }),
3777
3773
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
3778
3774
  }
3779
3775
  ),
@@ -3901,7 +3897,7 @@ const PageHeader = ({
3901
3897
  return /* @__PURE__ */ jsxRuntime.jsxs(
3902
3898
  "div",
3903
3899
  {
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",
3900
+ className: "lex4-page-header",
3905
3901
  style: { maxHeight: MAX_HEADER_HEIGHT_PX, overflow: "clip" },
3906
3902
  "data-testid": `page-header-${pageId}`,
3907
3903
  children: [
@@ -3913,10 +3909,10 @@ const PageHeader = ({
3913
3909
  LexicalContentEditable.ContentEditable,
3914
3910
  {
3915
3911
  ref: contentRef,
3916
- className: `outline-none p-2 text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
3912
+ className: `lex4-page-hf-editable${hasPageCounter ? " lex4-page-hf-narrow" : ""}`
3917
3913
  }
3918
3914
  ),
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 }),
3915
+ placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `lex4-page-hf-placeholder${hasPageCounter ? " lex4-page-hf-narrow" : ""}`, children: t.header.placeholder }),
3920
3916
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
3921
3917
  }
3922
3918
  ),
@@ -3928,7 +3924,7 @@ const PageHeader = ({
3928
3924
  pageCounterLabel && /* @__PURE__ */ jsxRuntime.jsx(
3929
3925
  "div",
3930
3926
  {
3931
- className: "pointer-events-none absolute right-2 top-2 select-none text-xs text-gray-500",
3927
+ className: "lex4-page-counter",
3932
3928
  "data-testid": `page-counter-header-${pageId}`,
3933
3929
  children: pageCounterLabel
3934
3930
  }
@@ -3981,7 +3977,7 @@ const PageFooter = ({
3981
3977
  return /* @__PURE__ */ jsxRuntime.jsxs(
3982
3978
  "div",
3983
3979
  {
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",
3980
+ className: "lex4-page-footer",
3985
3981
  style: { maxHeight: MAX_FOOTER_HEIGHT_PX, overflow: "clip" },
3986
3982
  "data-testid": `page-footer-${pageId}`,
3987
3983
  children: [
@@ -3993,10 +3989,10 @@ const PageFooter = ({
3993
3989
  LexicalContentEditable.ContentEditable,
3994
3990
  {
3995
3991
  ref: contentRef,
3996
- className: `outline-none p-2 text-gray-600 min-h-[24px] ${hasPageCounter ? "pr-24" : ""}`
3992
+ className: `lex4-page-hf-editable${hasPageCounter ? " lex4-page-hf-narrow" : ""}`
3997
3993
  }
3998
3994
  ),
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 }),
3995
+ placeholder: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `lex4-page-hf-placeholder${hasPageCounter ? " lex4-page-hf-narrow" : ""}`, children: t.footer.placeholder }),
4000
3996
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
4001
3997
  }
4002
3998
  ),
@@ -4008,7 +4004,7 @@ const PageFooter = ({
4008
4004
  pageCounterLabel && /* @__PURE__ */ jsxRuntime.jsx(
4009
4005
  "div",
4010
4006
  {
4011
- className: "pointer-events-none absolute right-2 top-2 select-none text-xs text-gray-500",
4007
+ className: "lex4-page-counter",
4012
4008
  "data-testid": `page-counter-footer-${pageId}`,
4013
4009
  children: pageCounterLabel
4014
4010
  }
@@ -4066,7 +4062,7 @@ const PageView = React.memo(({
4066
4062
  return /* @__PURE__ */ jsxRuntime.jsxs(
4067
4063
  "div",
4068
4064
  {
4069
- className: "lex4-page bg-white shadow-xl flex flex-col",
4065
+ className: "lex4-page",
4070
4066
  style: {
4071
4067
  width: A4_WIDTH_PX,
4072
4068
  height: A4_HEIGHT_PX,
@@ -4351,7 +4347,7 @@ const DocumentView = () => {
4351
4347
  return /* @__PURE__ */ jsxRuntime.jsx(
4352
4348
  "div",
4353
4349
  {
4354
- className: "lex4-document-view flex flex-col items-center gap-8 py-8 min-h-full",
4350
+ className: "lex4-document-view",
4355
4351
  "data-testid": "document-view",
4356
4352
  tabIndex: -1,
4357
4353
  children: document2.pages.map((page, index) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -4370,6 +4366,247 @@ const DocumentView = () => {
4370
4366
  }
4371
4367
  );
4372
4368
  };
4369
+ const AST_VERSION = "1.0.0";
4370
+ const IS_BOLD = 1;
4371
+ const IS_ITALIC = 2;
4372
+ const IS_STRIKETHROUGH = 4;
4373
+ const IS_UNDERLINE = 8;
4374
+ function decodeFormatBitmask(format) {
4375
+ const marks = {};
4376
+ if (format & IS_BOLD) marks.bold = true;
4377
+ if (format & IS_ITALIC) marks.italic = true;
4378
+ if (format & IS_UNDERLINE) marks.underline = true;
4379
+ if (format & IS_STRIKETHROUGH) marks.strikethrough = true;
4380
+ return marks;
4381
+ }
4382
+ function extractFontFamily(style) {
4383
+ const match = style.match(/font-family:\s*([^;]+)/);
4384
+ return match ? match[1].trim().replace(/['"]/g, "") : void 0;
4385
+ }
4386
+ function extractFontSizePt(style) {
4387
+ const match = style.match(/font-size:\s*(\d+(?:\.\d+)?)\s*pt/);
4388
+ return match ? parseFloat(match[1]) : void 0;
4389
+ }
4390
+ function buildTextMarks(format, style) {
4391
+ const formatMarks = decodeFormatBitmask(format);
4392
+ const fontFamily = style ? extractFontFamily(style) : void 0;
4393
+ const fontSize = style ? extractFontSizePt(style) : void 0;
4394
+ const marks = {
4395
+ ...formatMarks,
4396
+ ...fontFamily ? { fontFamily } : {},
4397
+ ...fontSize ? { fontSize } : {}
4398
+ };
4399
+ return Object.keys(marks).length > 0 ? marks : void 0;
4400
+ }
4401
+ function mapInlineNode(node) {
4402
+ switch (node.type) {
4403
+ case "text":
4404
+ return mapTextNode(node);
4405
+ case "variable-node":
4406
+ return mapVariableNode(node);
4407
+ case "linebreak":
4408
+ return mapLineBreak();
4409
+ default:
4410
+ return { type: "text", text: "" };
4411
+ }
4412
+ }
4413
+ function mapTextNode(node) {
4414
+ const marks = buildTextMarks(node.format, node.style);
4415
+ return {
4416
+ type: "text",
4417
+ text: node.text,
4418
+ ...marks ? { marks } : {}
4419
+ };
4420
+ }
4421
+ function mapVariableNode(node) {
4422
+ return {
4423
+ type: "variable",
4424
+ key: node.variableKey
4425
+ };
4426
+ }
4427
+ function mapLineBreak() {
4428
+ return { type: "linebreak" };
4429
+ }
4430
+ function mapInlineNodes(nodes) {
4431
+ return nodes.map(mapInlineNode);
4432
+ }
4433
+ const ALIGN_LEFT = 1;
4434
+ const ALIGN_CENTER = 2;
4435
+ const ALIGN_RIGHT = 3;
4436
+ const ALIGN_JUSTIFY = 4;
4437
+ function decodeAlignment(format) {
4438
+ if (typeof format === "string") {
4439
+ if (["left", "center", "right", "justify"].includes(format)) {
4440
+ return format;
4441
+ }
4442
+ return void 0;
4443
+ }
4444
+ if (typeof format !== "number" || format === 0) return void 0;
4445
+ switch (format) {
4446
+ case ALIGN_LEFT:
4447
+ return "left";
4448
+ case ALIGN_CENTER:
4449
+ return "center";
4450
+ case ALIGN_RIGHT:
4451
+ return "right";
4452
+ case ALIGN_JUSTIFY:
4453
+ return "justify";
4454
+ default:
4455
+ return void 0;
4456
+ }
4457
+ }
4458
+ function mapBlockNode(node) {
4459
+ switch (node.type) {
4460
+ case "paragraph":
4461
+ return mapParagraph(node);
4462
+ case "heading":
4463
+ return mapHeading(node);
4464
+ case "list":
4465
+ return mapList(node);
4466
+ case "quote":
4467
+ return mapBlockQuote(node);
4468
+ default:
4469
+ return {
4470
+ type: "paragraph",
4471
+ children: mapInlineChildren(node)
4472
+ };
4473
+ }
4474
+ }
4475
+ function mapParagraph(node) {
4476
+ const alignment = decodeAlignment(node.format);
4477
+ const indent = node.indent && node.indent > 0 ? node.indent : void 0;
4478
+ return {
4479
+ type: "paragraph",
4480
+ ...alignment ? { alignment } : {},
4481
+ ...indent ? { indent } : {},
4482
+ children: mapInlineChildren(node)
4483
+ };
4484
+ }
4485
+ function mapHeading(node) {
4486
+ var _a;
4487
+ const alignment = decodeAlignment(node.format);
4488
+ const tagMatch = (_a = node.tag) == null ? void 0 : _a.match(/^h(\d)$/);
4489
+ const level = tagMatch ? parseInt(tagMatch[1], 10) : 1;
4490
+ return {
4491
+ type: "heading",
4492
+ level,
4493
+ ...alignment ? { alignment } : {},
4494
+ children: mapInlineChildren(node)
4495
+ };
4496
+ }
4497
+ function mapList(node) {
4498
+ const listType = node.listType === "number" ? "ordered" : "unordered";
4499
+ const items = (node.children ?? []).filter((c) => c.type === "listitem").map(mapListItem);
4500
+ return {
4501
+ type: "list",
4502
+ listType,
4503
+ items
4504
+ };
4505
+ }
4506
+ function mapListItem(node) {
4507
+ const inlineChildren = [];
4508
+ let nestedList;
4509
+ for (const child of node.children ?? []) {
4510
+ if (child.type === "list") {
4511
+ nestedList = mapList(child);
4512
+ } else {
4513
+ const mapped = mapInlineNodes([child]);
4514
+ inlineChildren.push(...mapped);
4515
+ }
4516
+ }
4517
+ return {
4518
+ type: "list-item",
4519
+ children: inlineChildren,
4520
+ ...nestedList ? { nestedList } : {}
4521
+ };
4522
+ }
4523
+ function mapBlockQuote(node) {
4524
+ return {
4525
+ type: "blockquote",
4526
+ children: mapInlineChildren(node)
4527
+ };
4528
+ }
4529
+ function mapInlineChildren(node) {
4530
+ if (!node.children || node.children.length === 0) return [];
4531
+ return mapInlineNodes(node.children);
4532
+ }
4533
+ function mapBlockNodes(nodes) {
4534
+ return nodes.map(mapBlockNode);
4535
+ }
4536
+ function mapEditorStateToContent(state) {
4537
+ if (!state || !state.root || !state.root.children) {
4538
+ return null;
4539
+ }
4540
+ const blocks = mapBlockNodes(state.root.children);
4541
+ return { blocks };
4542
+ }
4543
+ function mapEditorStateToBlocks(state) {
4544
+ if (!state || !state.root || !state.root.children) {
4545
+ return [];
4546
+ }
4547
+ return mapBlockNodes(state.root.children);
4548
+ }
4549
+ const MARGIN_MM = Math.round(PAGE_MARGIN_PX / PX_PER_MM * 10) / 10;
4550
+ function serializeDocument(document2, variableDefinitions = []) {
4551
+ const pages = document2.pages.map(
4552
+ (page, index) => serializePage(page, index)
4553
+ );
4554
+ const metadata = buildMetadata(variableDefinitions);
4555
+ return {
4556
+ version: AST_VERSION,
4557
+ page: {
4558
+ format: "A4",
4559
+ widthMm: 210,
4560
+ heightMm: 297,
4561
+ margins: {
4562
+ topMm: MARGIN_MM,
4563
+ rightMm: MARGIN_MM,
4564
+ bottomMm: MARGIN_MM,
4565
+ leftMm: MARGIN_MM
4566
+ }
4567
+ },
4568
+ headerFooter: {
4569
+ enabled: document2.headerFooterEnabled,
4570
+ pageCounterMode: document2.pageCounterMode,
4571
+ defaultHeader: document2.pages.length > 0 ? mapEditorStateToContent(document2.pages[0].headerState) : null,
4572
+ defaultFooter: document2.pages.length > 0 ? mapEditorStateToContent(document2.pages[0].footerState) : null
4573
+ },
4574
+ pages,
4575
+ metadata
4576
+ };
4577
+ }
4578
+ function serializePage(page, pageIndex) {
4579
+ return {
4580
+ pageIndex,
4581
+ body: mapEditorStateToBlocks(page.bodyState),
4582
+ header: mapEditorStateToContent(page.headerState),
4583
+ footer: mapEditorStateToContent(page.footerState)
4584
+ };
4585
+ }
4586
+ function buildMetadata(variableDefinitions) {
4587
+ const variables = {};
4588
+ for (const def of variableDefinitions) {
4589
+ variables[def.key] = {
4590
+ key: def.key,
4591
+ label: def.label,
4592
+ ...def.description ? { description: def.description } : {},
4593
+ ...def.valueType ? { valueType: def.valueType } : {},
4594
+ ...def.group ? { group: def.group } : {}
4595
+ };
4596
+ }
4597
+ return { variables };
4598
+ }
4599
+ function buildSavePayload(ast, options) {
4600
+ return {
4601
+ document: ast,
4602
+ ...(options == null ? void 0 : options.exportTarget) ? { exportTarget: options.exportTarget } : {},
4603
+ ...(options == null ? void 0 : options.documentId) ? { documentId: options.documentId } : {},
4604
+ ...(options == null ? void 0 : options.metadata) ? { metadata: options.metadata } : {}
4605
+ };
4606
+ }
4607
+ function serializeDocumentJson(ast) {
4608
+ return JSON.stringify(ast, null, 2);
4609
+ }
4373
4610
  function selectEntireDocument(rootElement, selectionBuffer) {
4374
4611
  if (!rootElement || !selectionBuffer) {
4375
4612
  return;
@@ -4393,6 +4630,7 @@ function isFormFieldTarget(target) {
4393
4630
  }
4394
4631
  const EditorChrome = ({
4395
4632
  captureHistoryShortcutsOnWindow,
4633
+ onSave,
4396
4634
  className
4397
4635
  }) => {
4398
4636
  const {
@@ -4403,7 +4641,7 @@ const EditorChrome = ({
4403
4641
  undo,
4404
4642
  redo
4405
4643
  } = useDocument();
4406
- const { sidePanels } = useExtensions();
4644
+ const { sidePanels, cssVariables, rootClassNames } = useExtensions();
4407
4645
  const rootRef = React.useRef(null);
4408
4646
  const selectionBufferRef = React.useRef(null);
4409
4647
  const clearGlobalSelection = React.useCallback(() => {
@@ -4465,12 +4703,22 @@ const EditorChrome = ({
4465
4703
  });
4466
4704
  return;
4467
4705
  }
4706
+ if (key === "s" && onSave) {
4707
+ event.preventDefault();
4708
+ event.stopPropagation();
4709
+ const ast = serializeDocument(document2);
4710
+ const json = serializeDocumentJson(ast);
4711
+ onSave({ document: document2, ast, json });
4712
+ return;
4713
+ }
4468
4714
  if (handleHistoryShortcut(event)) {
4469
4715
  return;
4470
4716
  }
4471
4717
  }, [
4718
+ document2,
4472
4719
  dispatch,
4473
4720
  handleHistoryShortcut,
4721
+ onSave,
4474
4722
  setGlobalSelectionActive
4475
4723
  ]);
4476
4724
  const handleMouseDownCapture = React.useCallback((event) => {
@@ -4488,9 +4736,13 @@ const EditorChrome = ({
4488
4736
  const editableRoots = ((_a = rootRef.current) == null ? void 0 : _a.querySelectorAll(
4489
4737
  '[data-testid^="page-body-"] [data-lexical-editor="true"]'
4490
4738
  )) ?? [];
4739
+ const root = rootRef.current;
4740
+ const styles = root ? getComputedStyle(root) : null;
4741
+ const selBg = (styles == null ? void 0 : styles.getPropertyValue("--lex4-color-selection-bg").trim()) || GLOBAL_SELECTION_BACKGROUND;
4742
+ const selFg = (styles == null ? void 0 : styles.getPropertyValue("--lex4-color-selection-text").trim()) || GLOBAL_SELECTION_FOREGROUND;
4491
4743
  editableRoots.forEach((editableRoot) => {
4492
- editableRoot.style.backgroundColor = globalSelectionActive ? GLOBAL_SELECTION_BACKGROUND : "";
4493
- editableRoot.style.color = globalSelectionActive ? GLOBAL_SELECTION_FOREGROUND : "";
4744
+ editableRoot.style.backgroundColor = globalSelectionActive ? selBg : "";
4745
+ editableRoot.style.color = globalSelectionActive ? selFg : "";
4494
4746
  editableRoot.style.caretColor = globalSelectionActive ? "transparent" : "";
4495
4747
  });
4496
4748
  }, [globalSelectionActive, document2.pages.length]);
@@ -4523,11 +4775,14 @@ const EditorChrome = ({
4523
4775
  window.removeEventListener("beforeinput", handleWindowBeforeInput, { capture: true });
4524
4776
  };
4525
4777
  }, [captureHistoryShortcutsOnWindow, clearGlobalSelection, handleHistoryShortcut, redo, undo]);
4778
+ const rootClassName = ["lex4-editor", ...rootClassNames, className].filter(Boolean).join(" ");
4779
+ const extensionStyle = Object.keys(cssVariables).length > 0 ? cssVariables : void 0;
4526
4780
  return /* @__PURE__ */ jsxRuntime.jsxs(
4527
4781
  "div",
4528
4782
  {
4529
4783
  ref: rootRef,
4530
- className: `lex4-editor flex flex-col h-full ${className ?? ""}`,
4784
+ className: rootClassName,
4785
+ style: extensionStyle,
4531
4786
  "data-testid": "lex4-editor",
4532
4787
  "data-global-selection-active": globalSelectionActive ? "true" : "false",
4533
4788
  onKeyDownCapture: handleKeyDownCapture,
@@ -4541,12 +4796,12 @@ const EditorChrome = ({
4541
4796
  "data-testid": "global-selection-buffer",
4542
4797
  readOnly: true,
4543
4798
  tabIndex: -1,
4544
- className: "pointer-events-none fixed -left-[9999px] top-0 h-0 w-0 opacity-0"
4799
+ className: "lex4-selection-buffer"
4545
4800
  }
4546
4801
  ),
4547
4802
  /* @__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, {}) }),
4803
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-canvas", children: [
4804
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-canvas-scroll", children: /* @__PURE__ */ jsxRuntime.jsx(DocumentView, {}) }),
4550
4805
  sidePanels.map((Panel, idx) => /* @__PURE__ */ jsxRuntime.jsx(Panel, {}, idx)),
4551
4806
  /* @__PURE__ */ jsxRuntime.jsx(HistorySidebar, {})
4552
4807
  ] })
@@ -4554,7 +4809,7 @@ const EditorChrome = ({
4554
4809
  }
4555
4810
  );
4556
4811
  };
4557
- const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, className }, ref) => {
4812
+ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, onSave, className }, ref) => {
4558
4813
  const { document: doc, activeEditor } = useDocument();
4559
4814
  const { handleFactories } = useExtensions();
4560
4815
  const getDocument = React.useCallback(() => doc, [doc]);
@@ -4572,6 +4827,7 @@ const EditorWithHandle = React.forwardRef(({ captureHistoryShortcutsOnWindow, cl
4572
4827
  EditorChrome,
4573
4828
  {
4574
4829
  captureHistoryShortcutsOnWindow,
4830
+ onSave,
4575
4831
  className
4576
4832
  }
4577
4833
  );
@@ -4581,6 +4837,7 @@ const Lex4Editor = React.forwardRef(({
4581
4837
  captureHistoryShortcutsOnWindow = true,
4582
4838
  initialDocument,
4583
4839
  onDocumentChange,
4840
+ onSave,
4584
4841
  extensions,
4585
4842
  translations,
4586
4843
  className
@@ -4595,6 +4852,7 @@ const Lex4Editor = React.forwardRef(({
4595
4852
  {
4596
4853
  ref,
4597
4854
  captureHistoryShortcutsOnWindow,
4855
+ onSave,
4598
4856
  className
4599
4857
  }
4600
4858
  )
@@ -4667,247 +4925,6 @@ function useHeaderFooter(maxHeight, onHeightChange) {
4667
4925
  }, []);
4668
4926
  return { attachRef };
4669
4927
  }
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
4928
  function astExtension() {
4912
4929
  return {
4913
4930
  name: "ast",
@@ -5033,7 +5050,7 @@ function VariableChip({ variableKey }) {
5033
5050
  return /* @__PURE__ */ jsxRuntime.jsx(
5034
5051
  "span",
5035
5052
  {
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",
5053
+ className: "lex4-variable-chip",
5037
5054
  "data-testid": `variable-chip-${variableKey}`,
5038
5055
  title: variableKey,
5039
5056
  children: label
@@ -5106,12 +5123,12 @@ const VariablePicker = ({ onInsert, disabled = false }) => {
5106
5123
  document.addEventListener("mousedown", handler);
5107
5124
  return () => document.removeEventListener("mousedown", handler);
5108
5125
  }, [open]);
5109
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative inline-block", children: [
5126
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "lex4-variable-picker", children: [
5110
5127
  /* @__PURE__ */ jsxRuntime.jsx(
5111
5128
  "button",
5112
5129
  {
5113
5130
  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",
5131
+ className: "lex4-variable-picker-btn",
5115
5132
  "data-testid": "variable-picker-button",
5116
5133
  disabled: disabled || definitions.length === 0,
5117
5134
  onClick: () => setOpen(!open),
@@ -5122,14 +5139,13 @@ const VariablePicker = ({ onInsert, disabled = false }) => {
5122
5139
  open && /* @__PURE__ */ jsxRuntime.jsxs(
5123
5140
  "div",
5124
5141
  {
5125
- className: "absolute left-0 top-full z-50 mt-1 w-64 rounded-md border border-gray-200\n bg-white shadow-lg",
5142
+ className: "lex4-variable-picker-dropdown",
5126
5143
  "data-testid": "variable-picker-dropdown",
5127
5144
  children: [
5128
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-gray-100 p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
5145
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-variable-picker-search", children: /* @__PURE__ */ jsxRuntime.jsx(
5129
5146
  "input",
5130
5147
  {
5131
5148
  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
5149
  placeholder: "Search variables...",
5134
5150
  "data-testid": "variable-picker-search",
5135
5151
  value: filter,
@@ -5137,20 +5153,20 @@ const VariablePicker = ({ onInsert, disabled = false }) => {
5137
5153
  autoFocus: true
5138
5154
  }
5139
5155
  ) }),
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" }),
5156
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-variable-picker-list", children: [
5157
+ Object.keys(grouped).length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-variable-picker-empty", children: "No variables found" }),
5142
5158
  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 }),
5159
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-variable-picker-group-label", children: group }),
5144
5160
  defs.map((def) => /* @__PURE__ */ jsxRuntime.jsxs(
5145
5161
  "button",
5146
5162
  {
5147
5163
  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",
5164
+ className: "lex4-variable-picker-option",
5149
5165
  "data-testid": `variable-option-${def.key}`,
5150
5166
  onClick: () => handleInsert(def.key),
5151
5167
  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 })
5168
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-variable-picker-key", children: `{{${def.key}}}` }),
5169
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-variable-picker-label", children: def.label })
5154
5170
  ]
5155
5171
  },
5156
5172
  def.key
@@ -5208,20 +5224,20 @@ const VariablePanel = ({ open, onClose }) => {
5208
5224
  "button",
5209
5225
  {
5210
5226
  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",
5227
+ className: "lex4-sidebar-action-btn",
5212
5228
  title: t.variables.refreshVariables,
5213
5229
  "data-testid": "btn-refresh-variables",
5214
5230
  children: /* @__PURE__ */ jsxRuntime.jsx(RefreshCw, { size: 12 })
5215
5231
  }
5216
5232
  ),
5217
5233
  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" }),
5234
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-variable-search-container", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-variable-search-wrapper", children: [
5235
+ /* @__PURE__ */ jsxRuntime.jsx(Search, { size: 14, className: "lex4-variable-search-icon" }),
5220
5236
  /* @__PURE__ */ jsxRuntime.jsx(
5221
5237
  "input",
5222
5238
  {
5223
5239
  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",
5240
+ className: "lex4-variable-search-input",
5225
5241
  placeholder: t.variables.searchPlaceholder,
5226
5242
  "data-testid": "variable-panel-search",
5227
5243
  value: filter,
@@ -5229,25 +5245,19 @@ const VariablePanel = ({ open, onClose }) => {
5229
5245
  }
5230
5246
  )
5231
5247
  ] }) }),
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(
5248
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lex4-variable-list", children: [
5249
+ Object.keys(grouped).length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lex4-variable-list-empty", children: t.variables.noVariablesFound }),
5250
+ 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
5251
  "button",
5236
5252
  {
5237
5253
  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",
5254
+ className: "lex4-variable-list-item",
5239
5255
  "data-testid": `variable-panel-${def.key}`,
5240
5256
  onClick: () => handleInsert(def.key),
5241
5257
  disabled: !activeEditor,
5242
5258
  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 })
5259
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-variable-badge", children: def.label }),
5260
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lex4-variable-group-label", children: group })
5251
5261
  ]
5252
5262
  },
5253
5263
  def.key
@@ -5301,10 +5311,7 @@ const VariablePanelToggle = () => {
5301
5311
  "aria-label": panelOpen ? t.variables.closePanel : t.variables.openPanel,
5302
5312
  onMouseDown: (e) => e.preventDefault(),
5303
5313
  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
- `,
5314
+ className: `lex4-toolbar-btn${panelOpen ? " active" : ""}`,
5308
5315
  "data-testid": "toggle-variable-panel",
5309
5316
  children: /* @__PURE__ */ jsxRuntime.jsx(Braces, { size: 15 })
5310
5317
  }