smartrte-react 0.3.5 → 1.0.0-beta.2

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 (141) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +106 -14
  3. package/dist/adapters/domChecklistCommandBridge.js +1 -1
  4. package/dist/adapters/domCommandBridge.d.ts +2 -2
  5. package/dist/adapters/domCommandBridge.js +2 -2
  6. package/dist/adapters/domInlineAtomCommandBridge.d.ts +10 -9
  7. package/dist/adapters/domInlineAtomCommandBridge.js +154 -50
  8. package/dist/adapters/domInlineImageCommandBridge.d.ts +7 -7
  9. package/dist/adapters/domInlineImageCommandBridge.js +6 -72
  10. package/dist/adapters/domMoveCommandBridge.js +1 -1
  11. package/dist/adapters/domSelectionBridge.d.ts +3 -3
  12. package/dist/adapters/domSmartDocument.d.ts +4 -4
  13. package/dist/adapters/domSmartDocument.js +2 -2
  14. package/dist/adapters/domTableCommandBridge.d.ts +42 -16
  15. package/dist/adapters/domTableCommandBridge.js +203 -173
  16. package/dist/adapters/internalFlags.d.ts +0 -8
  17. package/dist/adapters/internalFlags.js +0 -24
  18. package/dist/adapters/legacyListShadowComparator.d.ts +1 -1
  19. package/dist/adapters/legacyListShadowComparator.js +1 -1
  20. package/dist/adapters/pdfPrint.d.ts +8 -0
  21. package/dist/adapters/pdfPrint.js +22 -0
  22. package/dist/adapters/shadowMode.d.ts +3 -3
  23. package/dist/adapters/shadowMode.js +1 -1
  24. package/dist/canonicalEditorRuntime.d.ts +53 -1
  25. package/dist/canonicalEditorRuntime.js +61 -4
  26. package/dist/capabilityPresets.d.ts +27 -0
  27. package/dist/capabilityPresets.js +21 -0
  28. package/dist/collabTransport.d.ts +66 -0
  29. package/dist/collabTransport.js +1 -0
  30. package/dist/commentProvider.d.ts +24 -0
  31. package/dist/commentProvider.js +1 -0
  32. package/dist/components/CanonicalAuthorityEditor.d.ts +35 -1
  33. package/dist/components/CanonicalAuthorityEditor.js +1086 -112
  34. package/dist/components/ClassicEditorAuthority.d.ts +93 -22
  35. package/dist/components/ClassicEditorAuthority.js +11 -43
  36. package/dist/components/ColorPickerPopover.d.ts +39 -0
  37. package/dist/components/ColorPickerPopover.js +285 -0
  38. package/dist/components/CommentMarkers.d.ts +25 -0
  39. package/dist/components/CommentMarkers.js +105 -0
  40. package/dist/components/CommentThreadPanel.d.ts +26 -0
  41. package/dist/components/CommentThreadPanel.js +47 -0
  42. package/dist/components/ContextMenu.d.ts +23 -0
  43. package/dist/components/ContextMenu.js +141 -0
  44. package/dist/components/FormulaLibraryPopover.d.ts +16 -0
  45. package/dist/components/FormulaLibraryPopover.js +107 -0
  46. package/dist/components/LinkEditorPopover.d.ts +3 -1
  47. package/dist/components/LinkEditorPopover.js +22 -6
  48. package/dist/components/MediaManager.d.ts +4 -20
  49. package/dist/components/MediaManager.js +35 -6
  50. package/dist/components/MediaOverlay.d.ts +36 -0
  51. package/dist/components/MediaOverlay.js +163 -0
  52. package/dist/components/SpecialCharacterPopover.d.ts +18 -0
  53. package/dist/components/SpecialCharacterPopover.js +99 -0
  54. package/dist/components/StructuralSuggestionMarkers.d.ts +20 -0
  55. package/dist/components/StructuralSuggestionMarkers.js +56 -0
  56. package/dist/components/SuggestionPanel.d.ts +26 -0
  57. package/dist/components/SuggestionPanel.js +33 -0
  58. package/dist/components/TableBorderPopover.d.ts +59 -0
  59. package/dist/components/TableBorderPopover.js +147 -0
  60. package/dist/components/TableResizeHandles.d.ts +25 -0
  61. package/dist/components/TableResizeHandles.js +301 -0
  62. package/dist/components/TableSizePickerPopover.d.ts +16 -0
  63. package/dist/components/TableSizePickerPopover.js +126 -0
  64. package/dist/components/ToolbarPrimitives.d.ts +129 -0
  65. package/dist/components/ToolbarPrimitives.js +146 -0
  66. package/dist/components/VersionHistoryPanel.d.ts +9 -0
  67. package/dist/components/VersionHistoryPanel.js +157 -0
  68. package/dist/formulaLibrary.d.ts +31 -0
  69. package/dist/formulaLibrary.js +90 -0
  70. package/dist/index.d.ts +22 -24
  71. package/dist/index.js +14 -11
  72. package/dist/mediaManagerAdapter.d.ts +12 -0
  73. package/dist/mediaManagerAdapter.js +34 -0
  74. package/dist/mediaProvider.d.ts +21 -1
  75. package/dist/specialCharacters.d.ts +22 -0
  76. package/dist/specialCharacters.js +86 -0
  77. package/dist/standalone/classic-editor-embed.js +19 -12
  78. package/dist/suggestionProvider.d.ts +17 -0
  79. package/dist/suggestionProvider.js +1 -0
  80. package/dist/theme.d.ts +1 -1
  81. package/dist/theme.js +150 -20
  82. package/dist/versionProvider.d.ts +29 -0
  83. package/dist/versionProvider.js +1 -0
  84. package/package.json +28 -14
  85. package/dist/adapters/canonicalInlineCommandBridge.d.ts +0 -15
  86. package/dist/adapters/canonicalInlineCommandBridge.js +0 -269
  87. package/dist/adapters/canonicalListCommandBridge.d.ts +0 -26
  88. package/dist/adapters/canonicalListCommandBridge.js +0 -430
  89. package/dist/adapters/coreBold.d.ts +0 -5
  90. package/dist/adapters/coreBold.js +0 -3
  91. package/dist/adapters/documentFormats.d.ts +0 -26
  92. package/dist/adapters/documentFormats.js +0 -49
  93. package/dist/adapters/docxFormat.d.ts +0 -5
  94. package/dist/adapters/docxFormat.js +0 -272
  95. package/dist/adapters/domBlockCommandBridge.d.ts +0 -19
  96. package/dist/adapters/domBlockCommandBridge.js +0 -79
  97. package/dist/adapters/inlineMarkCoreExecution.d.ts +0 -9
  98. package/dist/adapters/inlineMarkCoreExecution.js +0 -25
  99. package/dist/adapters/pdfFormat.d.ts +0 -23
  100. package/dist/adapters/pdfFormat.js +0 -204
  101. package/dist/adapters/portableDocxAtoms.d.ts +0 -4
  102. package/dist/adapters/portableDocxAtoms.js +0 -58
  103. package/dist/adapters/styledDocxFormat.d.ts +0 -8
  104. package/dist/adapters/styledDocxFormat.js +0 -190
  105. package/dist/builtInFormatDefinitions.d.ts +0 -5
  106. package/dist/builtInFormatDefinitions.js +0 -82
  107. package/dist/canonicalAuthorityFlag.d.ts +0 -32
  108. package/dist/canonicalAuthorityFlag.js +0 -49
  109. package/dist/canonicalClipboardRuntime.d.ts +0 -15
  110. package/dist/canonicalClipboardRuntime.js +0 -76
  111. package/dist/components/ClassicEditor.d.ts +0 -76
  112. package/dist/components/ClassicEditor.js +0 -6729
  113. package/dist/editorController.d.ts +0 -78
  114. package/dist/editorController.js +0 -298
  115. package/dist/formatFidelity.d.ts +0 -14
  116. package/dist/formatFidelity.js +0 -107
  117. package/dist/formatRuntime.d.ts +0 -50
  118. package/dist/formatRuntime.js +0 -20
  119. package/dist/pluginRuntime.d.ts +0 -68
  120. package/dist/pluginRuntime.js +0 -78
  121. package/dist/standalone/editor.js +0 -1068
  122. package/dist/test-harness/atomShadowComparator.d.ts +0 -14
  123. package/dist/test-harness/atomShadowComparator.js +0 -63
  124. package/dist/test-harness/blockShadowComparator.d.ts +0 -30
  125. package/dist/test-harness/blockShadowComparator.js +0 -140
  126. package/dist/test-harness/clipboardShadowComparator.d.ts +0 -12
  127. package/dist/test-harness/clipboardShadowComparator.js +0 -46
  128. package/dist/test-harness/inlineShadowComparator.d.ts +0 -32
  129. package/dist/test-harness/inlineShadowComparator.js +0 -152
  130. package/dist/test-harness/legacyAtomEngine.d.ts +0 -10
  131. package/dist/test-harness/legacyAtomEngine.js +0 -15
  132. package/dist/test-harness/legacyBlockEngine.d.ts +0 -31
  133. package/dist/test-harness/legacyBlockEngine.js +0 -31
  134. package/dist/test-harness/legacyClipboardEngine.d.ts +0 -14
  135. package/dist/test-harness/legacyClipboardEngine.js +0 -67
  136. package/dist/test-harness/legacyInlineEngine.d.ts +0 -19
  137. package/dist/test-harness/legacyInlineEngine.js +0 -49
  138. package/dist/test-harness/legacyTableEngine.d.ts +0 -12
  139. package/dist/test-harness/legacyTableEngine.js +0 -25
  140. package/dist/test-harness/tableShadowComparator.d.ts +0 -29
  141. package/dist/test-harness/tableShadowComparator.js +0 -101
@@ -0,0 +1,147 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
3
+ import { ColorPickerPopover } from "./ColorPickerPopover.js";
4
+ // Widths start at 2px, not 1px - table_cell's border-collapse table (see
5
+ // theme.ts's th/td rule) already renders a 1px ambient default gridline on
6
+ // every cell. A custom border tied at 1px loses CSS border-collapse's
7
+ // conflict resolution on shared edges half the time (whichever side faces
8
+ // an earlier-in-table-order neighbor keeps the neighbor's default border,
9
+ // confirmed empirically: 1px, and even a fractional 1.5px, lost this way in
10
+ // every engine tested) - only a width strictly greater than the ambient
11
+ // default reliably wins on every side regardless of table position. 2px is
12
+ // the thinnest value that's actually renderable as a real, always-visible
13
+ // custom border under this constraint - see
14
+ // docs/bugs/table-cell-border-thin-preset-loses-border-collapse-tie.md.
15
+ export const BORDER_WIDTH_PRESETS = [
16
+ { id: "thin", label: "Thin", px: 2 },
17
+ { id: "medium", label: "Medium", px: 3 },
18
+ { id: "thick", label: "Thick", px: 4 },
19
+ ];
20
+ const STYLE_OPTIONS = ["solid", "dashed", "dotted"];
21
+ const buttonStyle = {
22
+ minHeight: 32, padding: "0 10px", border: "1px solid var(--srte-input-border)", borderRadius: 8,
23
+ background: "var(--srte-input-bg)", color: "var(--srte-menu-text)", cursor: "pointer", fontWeight: 500, fontSize: 13,
24
+ };
25
+ const selectStyle = {
26
+ width: "100%", height: 32, boxSizing: "border-box", padding: "0 8px", borderRadius: 8,
27
+ border: "1px solid var(--srte-input-border)", background: "var(--srte-input-bg)", color: "var(--srte-input-text)",
28
+ font: "inherit", fontSize: 13, cursor: "pointer",
29
+ };
30
+ const fieldLabelStyle = { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.04em", opacity: 0.7, marginBottom: 4 };
31
+ const SIDE_BAR_LENGTH = 40;
32
+ const SIDE_BAR_THICKNESS = 8;
33
+ const SIDE_BOX = 64;
34
+ /**
35
+ * "Border options" - one popover combining every dimension of a cell
36
+ * border, replacing what used to be a separate toolbar-dropdown width
37
+ * <select> and a standalone "Border colour" item. table_cell.attrs only
38
+ * ever stored ONE uniform border for all four sides (`borders`) - "which
39
+ * sides get a border" (per-cell top/right/bottom/left) needed new per-side
40
+ * schema attrs (borderTop/Right/Bottom/Left), added specifically to back
41
+ * this control (see docs/bugs/table-cell-border-color-width-no-ui.md's
42
+ * addendum). One style/width/colour choice applies to whichever sides are
43
+ * toggled on here, rather than letting all four vary independently - real
44
+ * border dialogs (Word, Google Docs) default to this simpler mode too, and
45
+ * a fully independent 4-way style/width/colour matrix wasn't asked for.
46
+ *
47
+ * The colour swatch opens the existing ColorPickerPopover completely
48
+ * unmodified, as a nested popover anchored to the swatch - the dismiss/
49
+ * Escape handling below explicitly accounts for it being a DOM sibling
50
+ * (both are position:fixed overlays, so `rootRef.contains()` alone would
51
+ * never recognize a click inside it as "inside" this popover).
52
+ */
53
+ export function TableBorderPopover({ x, y, initial, recentColors, onPreview, onApply, onCancel }) {
54
+ const [draft, setDraft] = useState(initial);
55
+ const [colorPickerAnchor, setColorPickerAnchor] = useState(null);
56
+ const hexBeforeNestedPickerRef = useRef(initial.hex);
57
+ const rootRef = useRef(null);
58
+ const swatchRef = useRef(null);
59
+ const [placement, setPlacement] = useState(null);
60
+ const onCancelRef = useRef(onCancel);
61
+ onCancelRef.current = onCancel;
62
+ const onPreviewRef = useRef(onPreview);
63
+ onPreviewRef.current = onPreview;
64
+ useLayoutEffect(() => {
65
+ const el = rootRef.current;
66
+ if (!el)
67
+ return;
68
+ const margin = 8;
69
+ const { width, height } = el.getBoundingClientRect();
70
+ const viewportWidth = window.innerWidth;
71
+ const viewportHeight = window.innerHeight;
72
+ const overflowsRight = x + width > viewportWidth - margin;
73
+ const left = overflowsRight ? x - width : x;
74
+ const clampedLeft = Math.min(Math.max(margin, left), Math.max(margin, viewportWidth - width - margin));
75
+ const overflowsBottom = y + height > viewportHeight - margin;
76
+ const top = overflowsBottom ? y - height : y;
77
+ const clampedTop = Math.min(Math.max(margin, top), Math.max(margin, viewportHeight - margin));
78
+ setPlacement({ left: clampedLeft, top: clampedTop });
79
+ }, [x, y]);
80
+ useEffect(() => {
81
+ const dismissIfOutside = (event) => {
82
+ const target = event.target;
83
+ if (rootRef.current?.contains(target))
84
+ return;
85
+ // The nested colour picker is a DOM sibling (both are position:fixed
86
+ // overlays), not a descendant of rootRef - without this, clicking
87
+ // inside it would look like an "outside" click on this popover.
88
+ if (colorPickerAnchor && document.querySelector('[data-srte-color-popover="true"]')?.contains(target))
89
+ return;
90
+ onCancelRef.current();
91
+ };
92
+ window.addEventListener("pointerdown", dismissIfOutside, true);
93
+ window.addEventListener("mousedown", dismissIfOutside, true);
94
+ return () => {
95
+ window.removeEventListener("pointerdown", dismissIfOutside, true);
96
+ window.removeEventListener("mousedown", dismissIfOutside, true);
97
+ };
98
+ }, [colorPickerAnchor]);
99
+ useEffect(() => {
100
+ const onWindowKeyDown = (event) => {
101
+ if (event.key !== "Escape")
102
+ return;
103
+ // While the nested colour picker is open, let its own Escape handler
104
+ // commit/dismiss just that picker first - a second Escape closes this
105
+ // whole dialog.
106
+ if (colorPickerAnchor)
107
+ return;
108
+ onCancelRef.current();
109
+ };
110
+ window.addEventListener("keydown", onWindowKeyDown);
111
+ return () => window.removeEventListener("keydown", onWindowKeyDown);
112
+ }, [colorPickerAnchor]);
113
+ const update = (patch) => {
114
+ const next = { ...draft, ...patch };
115
+ setDraft(next);
116
+ onPreviewRef.current(next);
117
+ };
118
+ const toggleSide = (side) => update({ sides: { ...draft.sides, [side]: !draft.sides[side] } });
119
+ const edgeButton = (side, style, label) => (_jsx("button", { type: "button", "aria-label": label, "aria-pressed": draft.sides[side], onMouseDown: (event) => event.preventDefault(), onClick: () => toggleSide(side), style: {
120
+ position: "absolute", border: "none", borderRadius: 3, cursor: "pointer", padding: 0,
121
+ background: draft.sides[side] ? "var(--srte-primary)" : "var(--srte-input-border)",
122
+ ...style,
123
+ } }));
124
+ return (_jsxs("div", { ref: rootRef, "data-srte-table-border-popover": "true", role: "dialog", "aria-label": "Border options", style: {
125
+ position: "fixed",
126
+ left: placement?.left ?? x,
127
+ top: placement?.top ?? y,
128
+ visibility: placement ? "visible" : "hidden",
129
+ zIndex: 70,
130
+ width: 240,
131
+ boxSizing: "border-box",
132
+ background: "var(--srte-menu-bg)",
133
+ color: "var(--srte-menu-text)",
134
+ border: "1px solid var(--srte-border)",
135
+ borderRadius: 12,
136
+ boxShadow: "var(--srte-menu-shadow)",
137
+ padding: 14,
138
+ display: "flex",
139
+ flexDirection: "column",
140
+ gap: 12,
141
+ }, children: [_jsx("div", { style: { fontWeight: 650 }, children: "Border options" }), _jsxs("div", { style: { display: "flex", gap: 14, alignItems: "center" }, children: [_jsxs("div", { children: [_jsx("div", { style: fieldLabelStyle, children: "Sides" }), _jsxs("div", { style: { position: "relative", width: SIDE_BOX, height: SIDE_BOX, border: "1px dashed var(--srte-border)", borderRadius: 6 }, children: [edgeButton("top", { top: -SIDE_BAR_THICKNESS / 2, left: (SIDE_BOX - SIDE_BAR_LENGTH) / 2, width: SIDE_BAR_LENGTH, height: SIDE_BAR_THICKNESS }, "Top border"), edgeButton("bottom", { bottom: -SIDE_BAR_THICKNESS / 2, left: (SIDE_BOX - SIDE_BAR_LENGTH) / 2, width: SIDE_BAR_LENGTH, height: SIDE_BAR_THICKNESS }, "Bottom border"), edgeButton("left", { left: -SIDE_BAR_THICKNESS / 2, top: (SIDE_BOX - SIDE_BAR_LENGTH) / 2, width: SIDE_BAR_THICKNESS, height: SIDE_BAR_LENGTH }, "Left border"), edgeButton("right", { right: -SIDE_BAR_THICKNESS / 2, top: (SIDE_BOX - SIDE_BAR_LENGTH) / 2, width: SIDE_BAR_THICKNESS, height: SIDE_BAR_LENGTH }, "Right border")] })] }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [_jsx("button", { type: "button", style: buttonStyle, onMouseDown: (event) => event.preventDefault(), onClick: () => update({ sides: { top: true, right: true, bottom: true, left: true } }), children: "All sides" }), _jsx("button", { type: "button", style: buttonStyle, onMouseDown: (event) => event.preventDefault(), onClick: () => update({ sides: { top: false, right: false, bottom: false, left: false } }), children: "No border" })] })] }), _jsxs("div", { children: [_jsx("div", { style: fieldLabelStyle, children: "Style" }), _jsx("select", { "aria-label": "Border style", value: draft.style, onChange: (event) => update({ style: event.target.value }), style: selectStyle, children: STYLE_OPTIONS.map((style) => _jsxs("option", { value: style, children: [style[0].toUpperCase(), style.slice(1)] }, style)) })] }), _jsxs("div", { children: [_jsx("div", { style: fieldLabelStyle, children: "Width" }), _jsx("select", { "aria-label": "Border width", value: BORDER_WIDTH_PRESETS.find((preset) => preset.px === draft.widthPx)?.id ?? BORDER_WIDTH_PRESETS[0].id, onChange: (event) => { const preset = BORDER_WIDTH_PRESETS.find((entry) => entry.id === event.target.value); if (preset)
142
+ update({ widthPx: preset.px }); }, style: selectStyle, children: BORDER_WIDTH_PRESETS.map((preset) => _jsx("option", { value: preset.id, children: preset.label }, preset.id)) })] }), _jsxs("div", { children: [_jsx("div", { style: fieldLabelStyle, children: "Colour" }), _jsxs("button", { ref: swatchRef, type: "button", "aria-label": "Border colour", onMouseDown: (event) => event.preventDefault(), onClick: () => {
143
+ hexBeforeNestedPickerRef.current = draft.hex;
144
+ const rect = swatchRef.current?.getBoundingClientRect();
145
+ setColorPickerAnchor({ x: rect?.left ?? x, y: (rect?.bottom ?? y) + 4 });
146
+ }, style: { ...buttonStyle, width: "100%", display: "flex", alignItems: "center", gap: 8, justifyContent: "flex-start" }, children: [_jsx("span", { style: { width: 18, height: 18, borderRadius: 4, border: "1px solid var(--srte-input-border)", background: draft.hex }, "aria-hidden": "true" }), draft.hex] })] }), _jsxs("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 }, children: [_jsx("button", { type: "button", style: buttonStyle, onClick: onCancel, children: "Cancel" }), _jsx("button", { type: "button", onClick: () => onApply(draft), style: { ...buttonStyle, borderColor: "var(--srte-primary)", background: "var(--srte-primary)", color: "var(--srte-on-primary)" }, children: "Apply" })] }), colorPickerAnchor && _jsx(ColorPickerPopover, { x: colorPickerAnchor.x, y: colorPickerAnchor.y, label: "Border colour", initialValue: draft.hex, recentColors: recentColors, onPreview: (hex) => update({ hex }), onApply: (hex) => { update({ hex }); setColorPickerAnchor(null); }, onCancel: () => { update({ hex: hexBeforeNestedPickerRef.current }); setColorPickerAnchor(null); } })] }));
147
+ }
@@ -0,0 +1,25 @@
1
+ export interface TableResizeHandlesProps {
2
+ tableElement: HTMLTableElement;
3
+ /**
4
+ * `currentWidths` is every column's real, currently-rendered width (not
5
+ * just the dragged one) - see the handleUp comment below for why the
6
+ * caller needs this, not just the one changed index.
7
+ */
8
+ onResizeColumn: (index: number, width: number, currentWidths: readonly number[]) => void;
9
+ onResizeRow: (index: number, height: number, adjacent?: {
10
+ index: number;
11
+ height: number;
12
+ }) => void;
13
+ }
14
+ /**
15
+ * Phase 11.5 §2.2: column-width/row-height drag handles calling the
16
+ * existing setTableColumnWidthCommand/setTableRowHeightCommand (row/column
17
+ * reorder already had toolbar buttons - "Move row up/down"/"Move column
18
+ * left/right" - wired since before this phase; resize had no UI at all).
19
+ * Commit-on-release, not a live preview during drag - a deliberately
20
+ * bounded scope for the first pass. Recomputes handle geometry from the
21
+ * live table's actual rendered cell rects on every table layout change via
22
+ * ResizeObserver, since table content (and therefore column/row sizes)
23
+ * changes independently of resize actions.
24
+ */
25
+ export declare function TableResizeHandles({ tableElement, onResizeColumn, onResizeRow }: TableResizeHandlesProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,301 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ const HANDLE_THICKNESS = 6;
4
+ const MIN_SIZE = 20;
5
+ /**
6
+ * Phase 11.5 §2.2: column-width/row-height drag handles calling the
7
+ * existing setTableColumnWidthCommand/setTableRowHeightCommand (row/column
8
+ * reorder already had toolbar buttons - "Move row up/down"/"Move column
9
+ * left/right" - wired since before this phase; resize had no UI at all).
10
+ * Commit-on-release, not a live preview during drag - a deliberately
11
+ * bounded scope for the first pass. Recomputes handle geometry from the
12
+ * live table's actual rendered cell rects on every table layout change via
13
+ * ResizeObserver, since table content (and therefore column/row sizes)
14
+ * changes independently of resize actions.
15
+ */
16
+ export function TableResizeHandles({ tableElement, onResizeColumn, onResizeRow }) {
17
+ const [columns, setColumns] = useState([]);
18
+ const [rows, setRows] = useState([]);
19
+ // `nextStartSize`/`nextIndex` are null for the last row/column's own
20
+ // outer-edge handle - that one still grows/shrinks the table's overall
21
+ // size (no neighbor to redistribute with); every other handle has a
22
+ // real neighbor and redistributes between the two instead.
23
+ const dragRef = useRef(null);
24
+ const [dragging, setDragging] = useState(null);
25
+ // handleUp (below) is defined inside an effect that doesn't depend on
26
+ // `columns`/`rows` (to avoid tearing down the pointer listeners on every
27
+ // ResizeObserver tick) - these refs keep it reading the latest measured
28
+ // sizes anyway, without that staleness risk.
29
+ const columnsRef = useRef([]);
30
+ columnsRef.current = columns;
31
+ const rowsRef = useRef([]);
32
+ rowsRef.current = rows;
33
+ // Shared by the passive observers below (structural/layout changes that
34
+ // happen outside a drag) and by handleMove during an active drag (see
35
+ // that effect for why an active drag can't just wait for these
36
+ // observers to notice).
37
+ const recompute = useCallback(() => {
38
+ const tableRect = tableElement.getBoundingClientRect();
39
+ const firstRowCells = [...tableElement.querySelectorAll("tr")[0]?.children || []];
40
+ const colBoundaries = [];
41
+ firstRowCells.forEach((cell, index) => {
42
+ const rect = cell.getBoundingClientRect();
43
+ colBoundaries.push({ index, position: rect.right, length: tableRect.height, start: tableRect.top, size: rect.width });
44
+ });
45
+ const rowElements = [...tableElement.querySelectorAll(":scope > tr, :scope > tbody > tr")];
46
+ const rowBoundaries = [];
47
+ rowElements.forEach((row, index) => {
48
+ const rect = row.getBoundingClientRect();
49
+ rowBoundaries.push({ index, position: rect.bottom, length: tableRect.width, start: tableRect.left, size: rect.height });
50
+ });
51
+ setColumns(colBoundaries);
52
+ setRows(rowBoundaries);
53
+ }, [tableElement]);
54
+ useEffect(() => {
55
+ recompute();
56
+ const observer = new ResizeObserver(recompute);
57
+ observer.observe(tableElement);
58
+ // ResizeObserver only fires when tableElement's own box size changes -
59
+ // inserting/removing a row or column into a table that stays the same
60
+ // overall width (the common case: width:100%, or a pinned literal
61
+ // width unaffected by redistributing space across one more/fewer
62
+ // column) never changes that box size, so it never fires. Handles
63
+ // then stay stale: wrong count, wrong position, wrong `boundary.size`
64
+ // for whatever column/row the model now has at that index - dragging
65
+ // one computed a wildly wrong new width from a stale start size. A
66
+ // MutationObserver on the table's own structure (row/cell add/remove/
67
+ // reorder) catches exactly the class of change ResizeObserver misses.
68
+ const mutationObserver = new MutationObserver(recompute);
69
+ mutationObserver.observe(tableElement, { childList: true, subtree: true });
70
+ const ownerWindow = tableElement.ownerDocument.defaultView;
71
+ ownerWindow?.addEventListener("scroll", recompute, true);
72
+ return () => {
73
+ observer.disconnect();
74
+ mutationObserver.disconnect();
75
+ ownerWindow?.removeEventListener("scroll", recompute, true);
76
+ };
77
+ }, [tableElement, recompute]);
78
+ useEffect(() => {
79
+ const ownerWindow = tableElement.ownerDocument.defaultView;
80
+ if (!ownerWindow)
81
+ return;
82
+ // For an internal boundary (a real neighbor exists), clamp the raw
83
+ // pointer delta so neither side ever goes below MIN_SIZE, then apply
84
+ // the *same* clamped delta with opposite signs to both sides - their
85
+ // sum is therefore always exactly startSize + nextStartSize, whatever
86
+ // the clamp did. That invariant is what makes "the border just moves"
87
+ // true: the table's own total size never has to change for these.
88
+ //
89
+ // Rows can't use this arithmetic-only approach: a <tr>'s rendered
90
+ // height can never go below what its own cell content needs (an
91
+ // explicit row height behaves as a minimum, not a hard cap, in table
92
+ // layout) - unlike a column's width under table-layout:fixed, which
93
+ // genuinely can be squeezed below its content's natural width. Naively
94
+ // clamping to a fixed MIN_SIZE the way columns do would, the instant a
95
+ // row's *real* content floor was higher than that, still hand the
96
+ // *other* row the full requested delta anyway - the total table
97
+ // height would silently grow instead of staying constant, and the row
98
+ // actually being dragged would appear frozen while its neighbor
99
+ // visibly ballooned (this is what "resize rows... move cursor up
100
+ // then it is effecting below row height" was - see docs/bugs/
101
+ // row-resize-drag-up-only-grows-the-row-below.md). Measuring the real
102
+ // DOM after attempting a shrink, and handing the other row only what
103
+ // was actually ceded, keeps that same invariant true regardless of
104
+ // where a row's real floor turns out to be.
105
+ const resolveSizes = (drag, event) => {
106
+ const rawDelta = drag.kind === "column" ? event.clientX - drag.startClient : event.clientY - drag.startClient;
107
+ if (drag.kind === "column") {
108
+ if (drag.nextStartSize === null) {
109
+ return { size: Math.max(MIN_SIZE, Math.round(drag.startSize + rawDelta)), nextSize: null };
110
+ }
111
+ const minDelta = -(drag.startSize - MIN_SIZE);
112
+ const maxDelta = drag.nextStartSize - MIN_SIZE;
113
+ const delta = Math.min(Math.max(rawDelta, minDelta), maxDelta);
114
+ return { size: Math.round(drag.startSize + delta), nextSize: Math.round(drag.nextStartSize - delta) };
115
+ }
116
+ const rowElements = [...tableElement.querySelectorAll(":scope > tr, :scope > tbody > tr")];
117
+ if (drag.nextStartSize === null || drag.nextIndex === null) {
118
+ const target = Math.max(MIN_SIZE, Math.round(drag.startSize + rawDelta));
119
+ const rowElement = rowElements[drag.index];
120
+ if (rowElement)
121
+ rowElement.style.height = `${target}px`;
122
+ const actual = rowElement ? Math.round(rowElement.getBoundingClientRect().height) : target;
123
+ return { size: actual, nextSize: null };
124
+ }
125
+ const rowElement = rowElements[drag.index];
126
+ const nextRowElement = rowElements[drag.nextIndex];
127
+ if (rawDelta <= 0) {
128
+ // The dragged (above) row is the one shrinking; the row below
129
+ // only grows by however much the above row actually gave up.
130
+ // There's no equivalent fallback for the dragged row itself here -
131
+ // a row that can't shrink just can't, full stop, no matter how
132
+ // flexible the table's total height is willing to be.
133
+ const target = Math.max(MIN_SIZE, Math.round(drag.startSize + rawDelta));
134
+ if (rowElement)
135
+ rowElement.style.height = `${target}px`;
136
+ const actualSize = rowElement ? Math.round(rowElement.getBoundingClientRect().height) : target;
137
+ return { size: actualSize, nextSize: Math.round(drag.nextStartSize - (actualSize - drag.startSize)) };
138
+ }
139
+ // Dragging down: the row below is asked to shrink to pay for the
140
+ // dragged row's growth, exactly like the mirror case above - but
141
+ // unlike a row shrinking, a row *growing* is never floor-limited (a
142
+ // <tr>'s content only ever imposes a minimum, never a maximum), so
143
+ // there's no symmetric "the dragged row just can't grow" outcome to
144
+ // fall back to here. Making the dragged row's growth conditional on
145
+ // the neighbor actually having room to give (the way the shrink
146
+ // case correctly is) meant that on a table where every row already
147
+ // sits at its own content floor - true of almost any freshly
148
+ // created or pasted table with short cell text - internal
149
+ // row-boundary dragging did *nothing at all* in either direction:
150
+ // shrinking had nowhere to go, and growing was blocked because the
151
+ // neighbor had nowhere to give either. See docs/bugs/
152
+ // row-resize-blocked-when-every-row-is-at-its-floor.md. Grow the
153
+ // dragged row by the full requested amount unconditionally, and let
154
+ // the neighbor give up only what it actually can; if that's less
155
+ // than what was asked, the shortfall becomes real table growth -
156
+ // the only place that space could possibly come from - rather than
157
+ // silently refusing the drag altogether.
158
+ const size = Math.max(MIN_SIZE, Math.round(drag.startSize + rawDelta));
159
+ if (rowElement)
160
+ rowElement.style.height = `${size}px`;
161
+ const target = Math.max(MIN_SIZE, Math.round(drag.nextStartSize - rawDelta));
162
+ if (nextRowElement)
163
+ nextRowElement.style.height = `${target}px`;
164
+ const nextSize = nextRowElement ? Math.round(nextRowElement.getBoundingClientRect().height) : target;
165
+ return { size, nextSize };
166
+ };
167
+ const handleMove = (event) => {
168
+ const drag = dragRef.current;
169
+ if (!drag)
170
+ return;
171
+ setDragging({ kind: drag.kind, index: drag.index, position: drag.kind === "column" ? event.clientX : event.clientY });
172
+ // Live preview: mutate the real <col>/<tr> directly during the drag
173
+ // (commit-on-release still only touches the model, via onResizeColumn/
174
+ // onResizeRow below) - previously nothing rendered until pointerup, so
175
+ // the drag looked frozen until it suddenly jumped to the final size.
176
+ // Undoing this on the next real render is automatic: committing
177
+ // triggers the renderer's own re-render from authoritative model
178
+ // state, which overwrites whatever this loop wrote.
179
+ const { size: liveSize, nextSize: liveNextSize } = resolveSizes(drag, event);
180
+ if (drag.kind === "column") {
181
+ const cols = [...(tableElement.querySelector("colgroup")?.children || [])];
182
+ const col = cols[drag.index];
183
+ if (col)
184
+ col.style.width = `${liveSize}px`;
185
+ if (liveNextSize !== null && drag.nextIndex !== null) {
186
+ const nextCol = cols[drag.nextIndex];
187
+ if (nextCol)
188
+ nextCol.style.width = `${liveNextSize}px`;
189
+ }
190
+ // The stylesheet's `table-layout: fixed` + `width: 100%` treats
191
+ // <col> widths as proportions of the table's own rendered width,
192
+ // not literal pixels, unless the table's own width matches their
193
+ // sum (see surface/renderer.ts's table-width fix) - keep that sum
194
+ // live too, or every other column visibly compresses for the
195
+ // duration of the drag even though the fix restores them on
196
+ // release. Redistributing between two columns (liveNextSize !==
197
+ // null) never changes this sum by construction, so only the
198
+ // last column's own resize (liveNextSize === null) needs this.
199
+ if (liveNextSize === null) {
200
+ const total = cols.reduce((sum, candidate, index) => sum + (index === drag.index ? liveSize : (parseFloat(candidate.style.width) || 0)), 0);
201
+ if (total > 0)
202
+ tableElement.style.width = `${total}px`;
203
+ }
204
+ }
205
+ else {
206
+ const rowElements = [...tableElement.querySelectorAll(":scope > tr, :scope > tbody > tr")];
207
+ const rowElement = rowElements[drag.index];
208
+ if (rowElement)
209
+ rowElement.style.height = `${liveSize}px`;
210
+ if (liveNextSize !== null && drag.nextIndex !== null) {
211
+ const nextRow = rowElements[drag.nextIndex];
212
+ if (nextRow)
213
+ nextRow.style.height = `${liveNextSize}px`;
214
+ }
215
+ }
216
+ // The handles' own on-screen position/length come from `columns`/
217
+ // `rows` state, which the effect above only refreshes via
218
+ // ResizeObserver/MutationObserver/scroll - none of which reliably
219
+ // fire here. Column drags happen to nudge the table's own rendered
220
+ // width by sub-pixel rounding (table-layout: fixed redistributing a
221
+ // stray pixel), which incidentally retriggers ResizeObserver; an
222
+ // internal row-boundary drag keeps the table's total height exactly
223
+ // constant by construction (see resolveSizes above), so it never
224
+ // does. Left alone, the row handle (and the blue line it renders)
225
+ // stays frozen at its pre-drag position while the real row border
226
+ // moves live underneath it - reported as a misplaced, "very slow"
227
+ // blue line that overflows past the table/editor as the gap between
228
+ // the frozen handle and the actual, now-taller table grows. Forcing
229
+ // a synchronous re-measure after every live style mutation (a
230
+ // getBoundingClientRect read right after a style write forces the
231
+ // browser to flush layout first, so this always reflects the
232
+ // mutation just above, not a stale value) keeps both column and row
233
+ // handles tracking the real border on every pointermove instead of
234
+ // waiting on an observer that may not fire at all.
235
+ recompute();
236
+ };
237
+ const handleUp = (event) => {
238
+ const drag = dragRef.current;
239
+ if (!drag)
240
+ return;
241
+ const { size, nextSize } = resolveSizes(drag, event);
242
+ if (drag.kind === "column") {
243
+ // A table pasted/created with no columnWidths yet (natural,
244
+ // stretched rendering - see docs/bugs/table-shrinks-after-paste.md)
245
+ // has no real per-column data for the command layer to fall back
246
+ // on; without this, setTableColumnWidthCommand's own fallback
247
+ // (Array(columns).fill(120)) would silently reset every OTHER
248
+ // column to a fabricated 120px default the instant any one column
249
+ // was resized, shrinking the whole table - passing the real,
250
+ // currently-measured width of every column lets the caller seed a
251
+ // complete, accurate columnWidths array instead of guessing.
252
+ const currentWidths = columnsRef.current.map((boundary) => boundary.size);
253
+ if (nextSize !== null && drag.nextIndex !== null)
254
+ currentWidths[drag.nextIndex] = nextSize;
255
+ onResizeColumn(drag.index, size, currentWidths);
256
+ }
257
+ else if (nextSize !== null && drag.nextIndex !== null) {
258
+ onResizeRow(drag.index, size, { index: drag.nextIndex, height: nextSize });
259
+ }
260
+ else
261
+ onResizeRow(drag.index, size);
262
+ dragRef.current = null;
263
+ setDragging(null);
264
+ };
265
+ ownerWindow.addEventListener("pointermove", handleMove);
266
+ ownerWindow.addEventListener("pointerup", handleUp);
267
+ return () => {
268
+ ownerWindow.removeEventListener("pointermove", handleMove);
269
+ ownerWindow.removeEventListener("pointerup", handleUp);
270
+ };
271
+ }, [tableElement, onResizeColumn, onResizeRow, recompute]);
272
+ const startDrag = (kind, boundary) => (event) => {
273
+ event.preventDefault();
274
+ const siblings = kind === "column" ? columnsRef.current : rowsRef.current;
275
+ const next = siblings[boundary.index + 1] ?? null;
276
+ dragRef.current = {
277
+ kind, index: boundary.index, startClient: kind === "column" ? event.clientX : event.clientY, startSize: boundary.size,
278
+ nextIndex: next?.index ?? null, nextStartSize: next?.size ?? null,
279
+ };
280
+ setDragging({ kind, index: boundary.index, position: kind === "column" ? event.clientX : event.clientY });
281
+ };
282
+ return (_jsxs("div", { "data-srte-table-resize-handles": "true", style: { position: "fixed", inset: 0, pointerEvents: "none", zIndex: 60 }, children: [rows.map((boundary) => (_jsx("div", { role: "separator", "aria-orientation": "horizontal", "aria-label": `Resize row ${boundary.index + 1}`, "data-srte-row-resize-handle": boundary.index, onPointerDown: startDrag("row", boundary), style: {
283
+ position: "fixed",
284
+ left: boundary.start,
285
+ top: boundary.position - HANDLE_THICKNESS / 2,
286
+ width: boundary.length,
287
+ height: HANDLE_THICKNESS,
288
+ cursor: "row-resize",
289
+ pointerEvents: "auto",
290
+ background: dragging?.kind === "row" && dragging.index === boundary.index ? "var(--srte-primary)" : "transparent",
291
+ } }, `row-${boundary.index}`))), columns.map((boundary) => (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": `Resize column ${boundary.index + 1}`, "data-srte-column-resize-handle": boundary.index, onPointerDown: startDrag("column", boundary), style: {
292
+ position: "fixed",
293
+ left: boundary.position - HANDLE_THICKNESS / 2,
294
+ top: boundary.start,
295
+ width: HANDLE_THICKNESS,
296
+ height: boundary.length,
297
+ cursor: "col-resize",
298
+ pointerEvents: "auto",
299
+ background: dragging?.kind === "column" && dragging.index === boundary.index ? "var(--srte-primary)" : "transparent",
300
+ } }, `col-${boundary.index}`)))] }));
301
+ }
@@ -0,0 +1,16 @@
1
+ export interface TableSizePickerPopoverProps {
2
+ x: number;
3
+ y: number;
4
+ onInsert: (rows: number, columns: number) => void;
5
+ onCancel: () => void;
6
+ }
7
+ /**
8
+ * "Insert table" size picker - CKEditor's own pattern: hover an N×M grid to
9
+ * preview the size, click a cell to insert immediately (one decisive click,
10
+ * no separate Apply/Confirm step, since a grid cell click is already a
11
+ * single deliberate choice - the same reasoning ColorPickerPopover's
12
+ * recent-swatch buttons and the removed preset grid used). A numeric
13
+ * fallback below covers sizes past the grid's cap without capping what the
14
+ * tool can actually do.
15
+ */
16
+ export declare function TableSizePickerPopover({ x, y, onInsert, onCancel }: TableSizePickerPopoverProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,126 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
3
+ const MAX_GRID = 8;
4
+ const buttonStyle = {
5
+ minHeight: 32,
6
+ padding: "0 10px",
7
+ border: "1px solid var(--srte-input-border)",
8
+ borderRadius: 8,
9
+ background: "var(--srte-input-bg)",
10
+ color: "var(--srte-menu-text)",
11
+ cursor: "pointer",
12
+ fontWeight: 500,
13
+ fontSize: 13,
14
+ };
15
+ const inputStyle = {
16
+ width: 48,
17
+ height: 32,
18
+ boxSizing: "border-box",
19
+ padding: "0 8px",
20
+ border: "1px solid var(--srte-input-border)",
21
+ borderRadius: 8,
22
+ outline: "none",
23
+ background: "var(--srte-input-bg)",
24
+ color: "var(--srte-input-text)",
25
+ font: "inherit",
26
+ textAlign: "center",
27
+ };
28
+ /**
29
+ * "Insert table" size picker - CKEditor's own pattern: hover an N×M grid to
30
+ * preview the size, click a cell to insert immediately (one decisive click,
31
+ * no separate Apply/Confirm step, since a grid cell click is already a
32
+ * single deliberate choice - the same reasoning ColorPickerPopover's
33
+ * recent-swatch buttons and the removed preset grid used). A numeric
34
+ * fallback below covers sizes past the grid's cap without capping what the
35
+ * tool can actually do.
36
+ */
37
+ export function TableSizePickerPopover({ x, y, onInsert, onCancel }) {
38
+ const rootRef = useRef(null);
39
+ const rowsRef = useRef(null);
40
+ const [placement, setPlacement] = useState(null);
41
+ const [hover, setHover] = useState(null);
42
+ const [customRows, setCustomRows] = useState("2");
43
+ const [customColumns, setCustomColumns] = useState("2");
44
+ const onCancelRef = useRef(onCancel);
45
+ onCancelRef.current = onCancel;
46
+ useLayoutEffect(() => {
47
+ const el = rootRef.current;
48
+ if (!el)
49
+ return;
50
+ const margin = 8;
51
+ const { width, height } = el.getBoundingClientRect();
52
+ const viewportWidth = window.innerWidth;
53
+ const viewportHeight = window.innerHeight;
54
+ const overflowsRight = x + width > viewportWidth - margin;
55
+ const left = overflowsRight ? x - width : x;
56
+ const clampedLeft = Math.min(Math.max(margin, left), Math.max(margin, viewportWidth - width - margin));
57
+ const overflowsBottom = y + height > viewportHeight - margin;
58
+ const top = overflowsBottom ? y - height : y;
59
+ const clampedTop = Math.min(Math.max(margin, top), Math.max(margin, viewportHeight - margin));
60
+ setPlacement({ left: clampedLeft, top: clampedTop });
61
+ }, [x, y]);
62
+ useEffect(() => {
63
+ const dismissIfOutside = (event) => {
64
+ if (rootRef.current && !rootRef.current.contains(event.target))
65
+ onCancelRef.current();
66
+ };
67
+ window.addEventListener("pointerdown", dismissIfOutside, true);
68
+ window.addEventListener("mousedown", dismissIfOutside, true);
69
+ return () => {
70
+ window.removeEventListener("pointerdown", dismissIfOutside, true);
71
+ window.removeEventListener("mousedown", dismissIfOutside, true);
72
+ };
73
+ }, []);
74
+ // Escape must dismiss regardless of focus - unlike LinkEditorPopover/
75
+ // ColorPickerPopover, nothing here is autofocused on open (the grid is
76
+ // meant to be hovered, not tabbed into first), so focus stays wherever it
77
+ // was (typically the "Insert table" toolbar button) and the div's own
78
+ // onKeyDown below would never see the keypress.
79
+ useEffect(() => {
80
+ const onWindowKeyDown = (event) => {
81
+ if (event.key === "Escape")
82
+ onCancelRef.current();
83
+ };
84
+ window.addEventListener("keydown", onWindowKeyDown);
85
+ return () => window.removeEventListener("keydown", onWindowKeyDown);
86
+ }, []);
87
+ const insertCustom = () => {
88
+ const rows = Math.min(50, Math.max(1, Math.round(Number(customRows)) || 0));
89
+ const columns = Math.min(50, Math.max(1, Math.round(Number(customColumns)) || 0));
90
+ if (!rows || !columns)
91
+ return;
92
+ onInsert(rows, columns);
93
+ };
94
+ return (_jsxs("div", { ref: rootRef, "data-srte-table-size-popover": "true", role: "dialog", "aria-label": "Insert table", style: {
95
+ position: "fixed",
96
+ left: placement?.left ?? x,
97
+ top: placement?.top ?? y,
98
+ visibility: placement ? "visible" : "hidden",
99
+ zIndex: 70,
100
+ width: 232,
101
+ boxSizing: "border-box",
102
+ background: "var(--srte-menu-bg)",
103
+ color: "var(--srte-menu-text)",
104
+ border: "1px solid var(--srte-border)",
105
+ borderRadius: 12,
106
+ boxShadow: "var(--srte-menu-shadow)",
107
+ padding: 14,
108
+ }, children: [_jsx("div", { style: { fontWeight: 650, marginBottom: 10 }, children: "Insert table" }), _jsx("div", { "data-srte-table-size-grid": "true", style: { display: "grid", gridTemplateColumns: `repeat(${MAX_GRID}, 1fr)`, gap: 3, marginBottom: 8 }, onMouseLeave: () => setHover(null), children: Array.from({ length: MAX_GRID * MAX_GRID }, (_, index) => {
109
+ const row = Math.floor(index / MAX_GRID);
110
+ const column = index % MAX_GRID;
111
+ const active = hover ? row <= hover.row && column <= hover.column : false;
112
+ return (_jsx("button", { type: "button", "data-srte-table-size-cell": `${row + 1}x${column + 1}`, "aria-label": `Insert a ${row + 1} by ${column + 1} table`, onMouseEnter: () => setHover({ row, column }), onFocus: () => setHover({ row, column }), onClick: () => onInsert(row + 1, column + 1), style: {
113
+ width: 20, height: 20, padding: 0, boxSizing: "border-box",
114
+ border: `1px solid ${active ? "var(--srte-primary)" : "var(--srte-input-border)"}`,
115
+ borderRadius: 3,
116
+ background: active ? "var(--srte-primary)" : "var(--srte-input-bg)",
117
+ cursor: "pointer",
118
+ } }, index));
119
+ }) }), _jsx("div", { "data-srte-table-size-label": "true", style: { fontSize: 12, marginBottom: 12, minHeight: 16 }, children: hover ? `${hover.row + 1} × ${hover.column + 1} table` : "Hover to choose a size" }), _jsx("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 6 }, children: "Custom size" }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 12 }, children: [_jsx("input", { ref: rowsRef, type: "number", min: 1, max: 50, "aria-label": "Rows", value: customRows, onChange: (event) => setCustomRows(event.target.value), onKeyDown: (event) => { if (event.key === "Enter") {
120
+ event.preventDefault();
121
+ insertCustom();
122
+ } }, style: inputStyle }), _jsx("span", { children: "\u00D7" }), _jsx("input", { type: "number", min: 1, max: 50, "aria-label": "Columns", value: customColumns, onChange: (event) => setCustomColumns(event.target.value), onKeyDown: (event) => { if (event.key === "Enter") {
123
+ event.preventDefault();
124
+ insertCustom();
125
+ } }, style: inputStyle }), _jsx("button", { type: "button", onClick: insertCustom, style: { ...buttonStyle, marginLeft: "auto", borderColor: "var(--srte-primary)", background: "var(--srte-primary)", color: "var(--srte-on-primary)" }, children: "Insert" })] }), _jsx("div", { style: { display: "flex", justifyContent: "flex-end" }, children: _jsx("button", { type: "button", onClick: onCancel, style: buttonStyle, children: "Cancel" }) })] }));
126
+ }