smartrte-react 0.3.5 → 1.0.0-beta.10
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.
- package/CHANGELOG.md +55 -0
- package/README.md +205 -14
- package/dist/adapters/domChecklistCommandBridge.js +1 -1
- package/dist/adapters/domCommandBridge.d.ts +2 -2
- package/dist/adapters/domCommandBridge.js +2 -2
- package/dist/adapters/domInlineAtomCommandBridge.d.ts +10 -9
- package/dist/adapters/domInlineAtomCommandBridge.js +154 -50
- package/dist/adapters/domInlineImageCommandBridge.d.ts +7 -7
- package/dist/adapters/domInlineImageCommandBridge.js +6 -72
- package/dist/adapters/domMoveCommandBridge.js +1 -1
- package/dist/adapters/domSelectionBridge.d.ts +3 -3
- package/dist/adapters/domSmartDocument.d.ts +4 -4
- package/dist/adapters/domSmartDocument.js +2 -2
- package/dist/adapters/domTableCommandBridge.d.ts +42 -16
- package/dist/adapters/domTableCommandBridge.js +203 -173
- package/dist/adapters/internalFlags.d.ts +0 -8
- package/dist/adapters/internalFlags.js +0 -24
- package/dist/adapters/legacyListShadowComparator.d.ts +1 -1
- package/dist/adapters/legacyListShadowComparator.js +1 -1
- package/dist/adapters/pdfPrint.d.ts +8 -0
- package/dist/adapters/pdfPrint.js +22 -0
- package/dist/adapters/shadowMode.d.ts +3 -3
- package/dist/adapters/shadowMode.js +1 -1
- package/dist/canonicalEditorRuntime.d.ts +65 -1
- package/dist/canonicalEditorRuntime.js +63 -5
- package/dist/capabilityPresets.d.ts +27 -0
- package/dist/capabilityPresets.js +21 -0
- package/dist/collabTransport.d.ts +66 -0
- package/dist/collabTransport.js +1 -0
- package/dist/commentProvider.d.ts +24 -0
- package/dist/commentProvider.js +1 -0
- package/dist/components/CanonicalAuthorityEditor.d.ts +56 -1
- package/dist/components/CanonicalAuthorityEditor.js +1304 -116
- package/dist/components/ClassicEditorAuthority.d.ts +93 -22
- package/dist/components/ClassicEditorAuthority.js +11 -43
- package/dist/components/ColorPickerPopover.d.ts +39 -0
- package/dist/components/ColorPickerPopover.js +287 -0
- package/dist/components/CommentMarkers.d.ts +25 -0
- package/dist/components/CommentMarkers.js +105 -0
- package/dist/components/CommentThreadPanel.d.ts +26 -0
- package/dist/components/CommentThreadPanel.js +47 -0
- package/dist/components/ContextMenu.d.ts +23 -0
- package/dist/components/ContextMenu.js +141 -0
- package/dist/components/FormulaLibraryPopover.d.ts +16 -0
- package/dist/components/FormulaLibraryPopover.js +107 -0
- package/dist/components/LinkEditorPopover.d.ts +3 -1
- package/dist/components/LinkEditorPopover.js +22 -6
- package/dist/components/MediaDetailsPopover.d.ts +35 -0
- package/dist/components/MediaDetailsPopover.js +136 -0
- package/dist/components/MediaManager.d.ts +4 -20
- package/dist/components/MediaManager.js +40 -11
- package/dist/components/MediaOverlay.d.ts +27 -0
- package/dist/components/MediaOverlay.js +228 -0
- package/dist/components/SpecialCharacterPopover.d.ts +18 -0
- package/dist/components/SpecialCharacterPopover.js +99 -0
- package/dist/components/StructuralSuggestionMarkers.d.ts +20 -0
- package/dist/components/StructuralSuggestionMarkers.js +56 -0
- package/dist/components/SuggestionPanel.d.ts +26 -0
- package/dist/components/SuggestionPanel.js +33 -0
- package/dist/components/TableBorderPopover.d.ts +59 -0
- package/dist/components/TableBorderPopover.js +149 -0
- package/dist/components/TableResizeHandles.d.ts +25 -0
- package/dist/components/TableResizeHandles.js +323 -0
- package/dist/components/TableSizePickerPopover.d.ts +16 -0
- package/dist/components/TableSizePickerPopover.js +126 -0
- package/dist/components/ToolbarPrimitives.d.ts +161 -0
- package/dist/components/ToolbarPrimitives.js +237 -0
- package/dist/components/VersionHistoryPanel.d.ts +9 -0
- package/dist/components/VersionHistoryPanel.js +157 -0
- package/dist/components/fixedPositioning.d.ts +25 -0
- package/dist/components/fixedPositioning.js +42 -0
- package/dist/formulaLibrary.d.ts +31 -0
- package/dist/formulaLibrary.js +90 -0
- package/dist/index.d.ts +25 -24
- package/dist/index.js +15 -11
- package/dist/mediaManagerAdapter.d.ts +12 -0
- package/dist/mediaManagerAdapter.js +34 -0
- package/dist/mediaProvider.d.ts +21 -1
- package/dist/specialCharacters.d.ts +22 -0
- package/dist/specialCharacters.js +86 -0
- package/dist/standalone/classic-editor-embed.js +19 -12
- package/dist/suggestionProvider.d.ts +17 -0
- package/dist/suggestionProvider.js +1 -0
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +219 -30
- package/dist/toolbarTools.d.ts +95 -0
- package/dist/toolbarTools.js +17 -0
- package/dist/versionProvider.d.ts +29 -0
- package/dist/versionProvider.js +1 -0
- package/package.json +28 -14
- package/dist/adapters/canonicalInlineCommandBridge.d.ts +0 -15
- package/dist/adapters/canonicalInlineCommandBridge.js +0 -269
- package/dist/adapters/canonicalListCommandBridge.d.ts +0 -26
- package/dist/adapters/canonicalListCommandBridge.js +0 -430
- package/dist/adapters/coreBold.d.ts +0 -5
- package/dist/adapters/coreBold.js +0 -3
- package/dist/adapters/documentFormats.d.ts +0 -26
- package/dist/adapters/documentFormats.js +0 -49
- package/dist/adapters/docxFormat.d.ts +0 -5
- package/dist/adapters/docxFormat.js +0 -272
- package/dist/adapters/domBlockCommandBridge.d.ts +0 -19
- package/dist/adapters/domBlockCommandBridge.js +0 -79
- package/dist/adapters/inlineMarkCoreExecution.d.ts +0 -9
- package/dist/adapters/inlineMarkCoreExecution.js +0 -25
- package/dist/adapters/pdfFormat.d.ts +0 -23
- package/dist/adapters/pdfFormat.js +0 -204
- package/dist/adapters/portableDocxAtoms.d.ts +0 -4
- package/dist/adapters/portableDocxAtoms.js +0 -58
- package/dist/adapters/styledDocxFormat.d.ts +0 -8
- package/dist/adapters/styledDocxFormat.js +0 -190
- package/dist/builtInFormatDefinitions.d.ts +0 -5
- package/dist/builtInFormatDefinitions.js +0 -82
- package/dist/canonicalAuthorityFlag.d.ts +0 -32
- package/dist/canonicalAuthorityFlag.js +0 -49
- package/dist/canonicalClipboardRuntime.d.ts +0 -15
- package/dist/canonicalClipboardRuntime.js +0 -76
- package/dist/components/ClassicEditor.d.ts +0 -76
- package/dist/components/ClassicEditor.js +0 -6729
- package/dist/editorController.d.ts +0 -78
- package/dist/editorController.js +0 -298
- package/dist/formatFidelity.d.ts +0 -14
- package/dist/formatFidelity.js +0 -107
- package/dist/formatRuntime.d.ts +0 -50
- package/dist/formatRuntime.js +0 -20
- package/dist/pluginRuntime.d.ts +0 -68
- package/dist/pluginRuntime.js +0 -78
- package/dist/standalone/editor.js +0 -1068
- package/dist/test-harness/atomShadowComparator.d.ts +0 -14
- package/dist/test-harness/atomShadowComparator.js +0 -63
- package/dist/test-harness/blockShadowComparator.d.ts +0 -30
- package/dist/test-harness/blockShadowComparator.js +0 -140
- package/dist/test-harness/clipboardShadowComparator.d.ts +0 -12
- package/dist/test-harness/clipboardShadowComparator.js +0 -46
- package/dist/test-harness/inlineShadowComparator.d.ts +0 -32
- package/dist/test-harness/inlineShadowComparator.js +0 -152
- package/dist/test-harness/legacyAtomEngine.d.ts +0 -10
- package/dist/test-harness/legacyAtomEngine.js +0 -15
- package/dist/test-harness/legacyBlockEngine.d.ts +0 -31
- package/dist/test-harness/legacyBlockEngine.js +0 -31
- package/dist/test-harness/legacyClipboardEngine.d.ts +0 -14
- package/dist/test-harness/legacyClipboardEngine.js +0 -67
- package/dist/test-harness/legacyInlineEngine.d.ts +0 -19
- package/dist/test-harness/legacyInlineEngine.js +0 -49
- package/dist/test-harness/legacyTableEngine.d.ts +0 -12
- package/dist/test-harness/legacyTableEngine.js +0 -25
- package/dist/test-harness/tableShadowComparator.d.ts +0 -29
- package/dist/test-harness/tableShadowComparator.js +0 -101
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { SPECIAL_CHARACTERS, SPECIAL_CHARACTER_CATEGORIES } from "../specialCharacters.js";
|
|
4
|
+
const RECENT_ROW_LIMIT = 8;
|
|
5
|
+
/**
|
|
6
|
+
* Special character picker: six standardized-Unicode-block categories
|
|
7
|
+
* (~200 characters total - see specialCharacters.ts's own doc comment),
|
|
8
|
+
* reusing the grid-with-hover-feedback shape already established by
|
|
9
|
+
* ColorPickerPopover/TableSizePickerPopover rather than a new component
|
|
10
|
+
* pattern. Insertion is immediate on click (a character, unlike a dragged
|
|
11
|
+
* color, is already a single complete choice) via a real, page-owned text
|
|
12
|
+
* insert - see `insertSpecialCharacter` in CanonicalAuthorityEditor.tsx.
|
|
13
|
+
*/
|
|
14
|
+
export function SpecialCharacterPopover({ x, y, recentCharacters, onInsert, onCancel }) {
|
|
15
|
+
const [category, setCategory] = useState("greek");
|
|
16
|
+
const [query, setQuery] = useState("");
|
|
17
|
+
const rootRef = useRef(null);
|
|
18
|
+
const [placement, setPlacement] = useState(null);
|
|
19
|
+
const onCancelRef = useRef(onCancel);
|
|
20
|
+
onCancelRef.current = onCancel;
|
|
21
|
+
useLayoutEffect(() => {
|
|
22
|
+
const el = rootRef.current;
|
|
23
|
+
if (!el)
|
|
24
|
+
return;
|
|
25
|
+
const margin = 8;
|
|
26
|
+
const { width, height } = el.getBoundingClientRect();
|
|
27
|
+
const viewportWidth = window.innerWidth;
|
|
28
|
+
const viewportHeight = window.innerHeight;
|
|
29
|
+
const overflowsRight = x + width > viewportWidth - margin;
|
|
30
|
+
const left = overflowsRight ? x - width : x;
|
|
31
|
+
const clampedLeft = Math.min(Math.max(margin, left), Math.max(margin, viewportWidth - width - margin));
|
|
32
|
+
const overflowsBottom = y + height > viewportHeight - margin;
|
|
33
|
+
const top = overflowsBottom ? y - height : y;
|
|
34
|
+
const clampedTop = Math.min(Math.max(margin, top), Math.max(margin, viewportHeight - margin));
|
|
35
|
+
setPlacement({ left: clampedLeft, top: clampedTop, maxHeight: viewportHeight - clampedTop - margin });
|
|
36
|
+
}, [x, y]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const dismissIfOutside = (event) => {
|
|
39
|
+
if (rootRef.current && !rootRef.current.contains(event.target))
|
|
40
|
+
onCancelRef.current();
|
|
41
|
+
};
|
|
42
|
+
window.addEventListener("pointerdown", dismissIfOutside, true);
|
|
43
|
+
window.addEventListener("mousedown", dismissIfOutside, true);
|
|
44
|
+
return () => {
|
|
45
|
+
window.removeEventListener("pointerdown", dismissIfOutside, true);
|
|
46
|
+
window.removeEventListener("mousedown", dismissIfOutside, true);
|
|
47
|
+
};
|
|
48
|
+
}, []);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const onWindowKeyDown = (event) => {
|
|
51
|
+
if (event.key === "Escape")
|
|
52
|
+
onCancelRef.current();
|
|
53
|
+
};
|
|
54
|
+
window.addEventListener("keydown", onWindowKeyDown);
|
|
55
|
+
return () => window.removeEventListener("keydown", onWindowKeyDown);
|
|
56
|
+
}, []);
|
|
57
|
+
const needle = query.trim().toLowerCase();
|
|
58
|
+
const entries = useMemo(() => {
|
|
59
|
+
if (needle)
|
|
60
|
+
return SPECIAL_CHARACTERS.filter((entry) => entry.name.toLowerCase().includes(needle));
|
|
61
|
+
return SPECIAL_CHARACTERS.filter((entry) => entry.category === category);
|
|
62
|
+
}, [category, needle]);
|
|
63
|
+
return (_jsxs("div", { ref: rootRef, "data-srte-special-char-popover": "true", role: "dialog", "aria-label": "Special characters", style: {
|
|
64
|
+
position: "fixed",
|
|
65
|
+
left: placement?.left ?? x,
|
|
66
|
+
top: placement?.top ?? y,
|
|
67
|
+
visibility: placement ? "visible" : "hidden",
|
|
68
|
+
zIndex: 70,
|
|
69
|
+
width: 380,
|
|
70
|
+
maxWidth: "calc(100vw - 16px)",
|
|
71
|
+
maxHeight: placement ? placement.maxHeight : "calc(100vh - 16px)",
|
|
72
|
+
display: "flex",
|
|
73
|
+
flexDirection: "column",
|
|
74
|
+
boxSizing: "border-box",
|
|
75
|
+
background: "var(--srte-menu-bg)",
|
|
76
|
+
color: "var(--srte-menu-text)",
|
|
77
|
+
border: "1px solid var(--srte-border)",
|
|
78
|
+
borderRadius: 12,
|
|
79
|
+
boxShadow: "var(--srte-menu-shadow)",
|
|
80
|
+
overflow: "hidden",
|
|
81
|
+
}, children: [_jsx("div", { style: { padding: "12px 14px 8px", borderBottom: "1px solid var(--srte-border)" }, children: _jsx("input", { "data-srte-special-char-search": "true", value: query, onChange: (event) => setQuery(event.target.value), placeholder: "Search by name\u2026 (e.g. \u201Calpha\u201D)", "aria-label": "Search special characters", autoComplete: "off", style: {
|
|
82
|
+
width: "100%", height: 34, boxSizing: "border-box", padding: "0 10px",
|
|
83
|
+
border: "1px solid var(--srte-input-border)", borderRadius: 8, outline: "none",
|
|
84
|
+
background: "var(--srte-input-bg)", color: "var(--srte-input-text)", font: "inherit", fontSize: 13.5,
|
|
85
|
+
} }) }), _jsxs("div", { style: { display: "flex", flex: 1, minHeight: 0 }, children: [!needle && (_jsx("div", { role: "tablist", "aria-label": "Character category", style: { display: "flex", flexDirection: "column", gap: 2, padding: 8, borderRight: "1px solid var(--srte-border)", minWidth: 108 }, children: SPECIAL_CHARACTER_CATEGORIES.map((entry) => (_jsx("button", { type: "button", role: "tab", "aria-selected": category === entry.id, onMouseDown: (event) => event.preventDefault(), onClick: () => setCategory(entry.id), style: {
|
|
86
|
+
fontSize: 12.5, fontWeight: 600, textAlign: "left", padding: "7px 9px", borderRadius: 7,
|
|
87
|
+
border: "none", cursor: "pointer",
|
|
88
|
+
background: category === entry.id ? "var(--srte-accent-bg)" : "transparent",
|
|
89
|
+
color: category === entry.id ? "var(--srte-primary)" : "var(--srte-menu-text)",
|
|
90
|
+
}, children: entry.label }, entry.id))) })), _jsx("div", { style: { flex: 1, overflowY: "auto" }, children: _jsxs("div", { "data-srte-special-char-grid": "true", style: { display: "grid", gridTemplateColumns: "repeat(8, 1fr)", gap: 6, padding: 12 }, children: [entries.map((entry) => (_jsx("button", { type: "button", "data-srte-special-char": entry.char, "aria-label": entry.name, title: entry.name, onMouseDown: (event) => event.preventDefault(), onClick: () => onInsert(entry.char), style: {
|
|
91
|
+
aspectRatio: "1", display: "flex", alignItems: "center", justifyContent: "center",
|
|
92
|
+
fontSize: 16, border: "1px solid var(--srte-input-border)", borderRadius: 7,
|
|
93
|
+
background: "var(--srte-input-bg)", color: "var(--srte-input-text)", cursor: "pointer",
|
|
94
|
+
}, children: entry.char }, `${entry.category}-${entry.char}`))), entries.length === 0 && (_jsxs("div", { style: { gridColumn: "1 / -1", fontSize: 13, opacity: 0.7, padding: "8px 2px" }, children: ["No characters match \u201C", query, "\u201D."] }))] }) })] }), recentCharacters && recentCharacters.length > 0 && (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, padding: "8px 12px", borderTop: "1px solid var(--srte-border)" }, children: [_jsx("span", { style: { fontSize: 10.5, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.04em", color: "var(--srte-menu-text)", opacity: 0.7, marginRight: 2 }, children: "Recent" }), recentCharacters.slice(0, RECENT_ROW_LIMIT).map((char, index) => (_jsx("button", { type: "button", "data-srte-recent-special-char": char, "aria-label": `Recently used ${char}`, onMouseDown: (event) => event.preventDefault(), onClick: () => onInsert(char), style: {
|
|
95
|
+
width: 26, height: 26, display: "flex", alignItems: "center", justifyContent: "center",
|
|
96
|
+
fontSize: 14, border: "1px solid var(--srte-input-border)", borderRadius: 6,
|
|
97
|
+
background: "var(--srte-input-bg)", color: "var(--srte-input-text)", cursor: "pointer",
|
|
98
|
+
}, children: char }, `${char}-${index}`)))] }))] }));
|
|
99
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ModelDomMapping, PositionLookup, StructuralSuggestion } from "smartrte-core/foundation";
|
|
2
|
+
export interface StructuralSuggestionMarkersProps {
|
|
3
|
+
positions: PositionLookup;
|
|
4
|
+
mapping: ModelDomMapping;
|
|
5
|
+
containerElement: HTMLElement;
|
|
6
|
+
suggestions: readonly StructuralSuggestion[];
|
|
7
|
+
activeSuggestionId: string | null;
|
|
8
|
+
onSelectSuggestion: (id: string) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Live overlay for structural (whole-node) suggestions, the AnnotationRange
|
|
12
|
+
* half of Phase 12a §2.3's hybrid architecture. Simpler than CommentMarkers:
|
|
13
|
+
* a structural suggestion always anchors to exactly one whole node, so this
|
|
14
|
+
* measures that single element's own rect directly via mapping.nodeToDom
|
|
15
|
+
* rather than resolving a Range across DOM text positions. Re-measures on
|
|
16
|
+
* the same ResizeObserver/MutationObserver/scroll schedule as
|
|
17
|
+
* CommentMarkers/TableResizeHandles, for the same reason: live DOM layout
|
|
18
|
+
* changes independently of React re-renders.
|
|
19
|
+
*/
|
|
20
|
+
export declare function StructuralSuggestionMarkers({ positions, mapping, containerElement, suggestions, activeSuggestionId, onSelectSuggestion }: StructuralSuggestionMarkersProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { resolveAnnotationRange } from "smartrte-core/foundation";
|
|
4
|
+
/**
|
|
5
|
+
* Live overlay for structural (whole-node) suggestions, the AnnotationRange
|
|
6
|
+
* half of Phase 12a §2.3's hybrid architecture. Simpler than CommentMarkers:
|
|
7
|
+
* a structural suggestion always anchors to exactly one whole node, so this
|
|
8
|
+
* measures that single element's own rect directly via mapping.nodeToDom
|
|
9
|
+
* rather than resolving a Range across DOM text positions. Re-measures on
|
|
10
|
+
* the same ResizeObserver/MutationObserver/scroll schedule as
|
|
11
|
+
* CommentMarkers/TableResizeHandles, for the same reason: live DOM layout
|
|
12
|
+
* changes independently of React re-renders.
|
|
13
|
+
*/
|
|
14
|
+
export function StructuralSuggestionMarkers({ positions, mapping, containerElement, suggestions, activeSuggestionId, onSelectSuggestion }) {
|
|
15
|
+
const [rects, setRects] = useState([]);
|
|
16
|
+
const recompute = useCallback(() => {
|
|
17
|
+
if (!suggestions.length) {
|
|
18
|
+
setRects([]);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const next = [];
|
|
22
|
+
suggestions.forEach((suggestion) => {
|
|
23
|
+
if (!resolveAnnotationRange(suggestion.range, positions))
|
|
24
|
+
return;
|
|
25
|
+
const element = mapping.nodeToDom(suggestion.range.startId);
|
|
26
|
+
if (!element)
|
|
27
|
+
return;
|
|
28
|
+
const rect = element.getBoundingClientRect();
|
|
29
|
+
next.push({ id: suggestion.id, left: rect.left, top: rect.top, width: rect.width, height: rect.height });
|
|
30
|
+
});
|
|
31
|
+
setRects(next);
|
|
32
|
+
}, [positions, mapping, suggestions]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
recompute();
|
|
35
|
+
const observer = new ResizeObserver(recompute);
|
|
36
|
+
observer.observe(containerElement);
|
|
37
|
+
const mutationObserver = new MutationObserver(recompute);
|
|
38
|
+
mutationObserver.observe(containerElement, { childList: true, subtree: true, characterData: true });
|
|
39
|
+
const ownerWindow = containerElement.ownerDocument.defaultView;
|
|
40
|
+
ownerWindow?.addEventListener("scroll", recompute, true);
|
|
41
|
+
ownerWindow?.addEventListener("resize", recompute);
|
|
42
|
+
return () => {
|
|
43
|
+
observer.disconnect();
|
|
44
|
+
mutationObserver.disconnect();
|
|
45
|
+
ownerWindow?.removeEventListener("scroll", recompute, true);
|
|
46
|
+
ownerWindow?.removeEventListener("resize", recompute);
|
|
47
|
+
};
|
|
48
|
+
}, [containerElement, recompute]);
|
|
49
|
+
return (_jsx("div", { "data-srte-suggestion-markers": "true", style: { position: "fixed", inset: 0, pointerEvents: "none", zIndex: 55 }, children: rects.map((rect) => (_jsx("div", { "data-srte-suggestion-highlight": rect.id, onPointerDown: () => onSelectSuggestion(rect.id), style: {
|
|
50
|
+
position: "fixed", left: rect.left, top: rect.top, width: rect.width, height: rect.height,
|
|
51
|
+
background: activeSuggestionId === rect.id ? "var(--srte-suggestion-highlight-active, rgba(220, 60, 60, 0.28))" : "var(--srte-suggestion-highlight, rgba(220, 60, 60, 0.14))",
|
|
52
|
+
borderLeft: "3px solid var(--srte-suggestion-border, #dc3c3c)",
|
|
53
|
+
cursor: "pointer",
|
|
54
|
+
pointerEvents: "auto",
|
|
55
|
+
} }, rect.id))) }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SmartDocument, StructuralSuggestion } from "smartrte-core/foundation";
|
|
2
|
+
export interface SuggestionPanelProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
document: SmartDocument;
|
|
6
|
+
structuralSuggestions: readonly StructuralSuggestion[];
|
|
7
|
+
activeId: string | null;
|
|
8
|
+
onSelect: (id: string) => void;
|
|
9
|
+
/** Set when "Suggest insertion" was just clicked at a real caret position - renders a compose box for the proposed text. */
|
|
10
|
+
pendingInsert: boolean;
|
|
11
|
+
onSubmitInsert: (text: string) => void;
|
|
12
|
+
onAcceptInline: (id: string) => void;
|
|
13
|
+
onRejectInline: (id: string) => void;
|
|
14
|
+
onAcceptStructural: (suggestion: StructuralSuggestion) => void;
|
|
15
|
+
onRejectStructural: (suggestion: StructuralSuggestion) => void;
|
|
16
|
+
busyId: string | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Docked side panel listing every pending suggestion - both inline
|
|
20
|
+
* (discovered fresh each render via listInlineSuggestions, since those
|
|
21
|
+
* live as marks inside `document` itself, not a separate list the way
|
|
22
|
+
* comments/versions do) and structural (an explicit prop, since those are
|
|
23
|
+
* an out-of-band record like CommentThread). Non-modal, same reasoning as
|
|
24
|
+
* CommentThreadPanel: reviewing shouldn't block continuing to edit nearby.
|
|
25
|
+
*/
|
|
26
|
+
export declare function SuggestionPanel(props: SuggestionPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { listInlineSuggestions } from "smartrte-core/foundation";
|
|
4
|
+
const formatTimestamp = (createdAt) => new Date(createdAt).toLocaleString();
|
|
5
|
+
/**
|
|
6
|
+
* Docked side panel listing every pending suggestion - both inline
|
|
7
|
+
* (discovered fresh each render via listInlineSuggestions, since those
|
|
8
|
+
* live as marks inside `document` itself, not a separate list the way
|
|
9
|
+
* comments/versions do) and structural (an explicit prop, since those are
|
|
10
|
+
* an out-of-band record like CommentThread). Non-modal, same reasoning as
|
|
11
|
+
* CommentThreadPanel: reviewing shouldn't block continuing to edit nearby.
|
|
12
|
+
*/
|
|
13
|
+
export function SuggestionPanel(props) {
|
|
14
|
+
const { open, onClose, document, structuralSuggestions, activeId, onSelect, pendingInsert, onSubmitInsert, onAcceptInline, onRejectInline, onAcceptStructural, onRejectStructural, busyId, } = props;
|
|
15
|
+
const [draft, setDraft] = useState("");
|
|
16
|
+
if (!open)
|
|
17
|
+
return null;
|
|
18
|
+
const inline = listInlineSuggestions(document);
|
|
19
|
+
const submitInsert = () => {
|
|
20
|
+
const text = draft.trim();
|
|
21
|
+
if (!text)
|
|
22
|
+
return;
|
|
23
|
+
onSubmitInsert(text);
|
|
24
|
+
setDraft("");
|
|
25
|
+
};
|
|
26
|
+
const isEmpty = inline.length === 0 && structuralSuggestions.length === 0;
|
|
27
|
+
return (_jsxs("div", { role: "complementary", "aria-label": "Suggestions", "data-srte-suggestion-panel": "true", style: {
|
|
28
|
+
position: "fixed", top: 0, right: 0, bottom: 0, width: 320, maxWidth: "90vw",
|
|
29
|
+
background: "var(--srte-modal-bg)", color: "var(--srte-modal-text)",
|
|
30
|
+
borderLeft: "1px solid var(--srte-border-light)", boxShadow: "var(--srte-menu-shadow)",
|
|
31
|
+
display: "flex", flexDirection: "column", zIndex: 70,
|
|
32
|
+
}, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 14px", borderBottom: "1px solid var(--srte-border-light)" }, children: [_jsx("div", { style: { fontWeight: 600 }, children: "Suggestions" }), _jsx("button", { type: "button", "aria-label": "Close suggestions", onClick: onClose, children: "\u2715" })] }), pendingInsert && (_jsxs("div", { "data-srte-suggestion-compose": "true", style: { padding: "10px 14px", borderBottom: "1px solid var(--srte-border-light)" }, children: [_jsx("textarea", { value: draft, onChange: (event) => setDraft(event.target.value), placeholder: "Suggest inserting\u2026", rows: 2, style: { width: "100%", padding: "6px 8px", border: "1px solid var(--srte-border)", borderRadius: 6, background: "var(--srte-input-bg)", color: "var(--srte-input-text)", resize: "vertical" } }), _jsx("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 6 }, children: _jsx("button", { type: "button", "data-srte-suggestion-compose-submit": "true", disabled: !draft.trim(), onClick: submitInsert, children: "Suggest" }) })] })), _jsxs("div", { style: { flex: 1, overflowY: "auto", padding: "0 14px 14px" }, children: [isEmpty && _jsx("div", { style: { color: "var(--srte-text-muted)", padding: "12px 0" }, children: "No pending suggestions." }), inline.map((entry) => (_jsxs("div", { "data-srte-suggestion-entry": entry.id, onClick: () => onSelect(entry.id), style: { padding: "10px 0", borderBottom: "1px solid var(--srte-border-light)", outline: activeId === entry.id ? "2px solid var(--srte-primary)" : "none" }, children: [_jsxs("div", { style: { fontSize: 12, fontWeight: 600 }, children: [entry.authorId, " suggests ", entry.kind === "insert" ? "inserting" : "deleting"] }), _jsxs("div", { style: { fontSize: 13, whiteSpace: "pre-wrap" }, children: ["\"", entry.text, "\""] }), _jsx("div", { style: { fontSize: 11, color: "var(--srte-text-muted)" }, children: formatTimestamp(entry.createdAt) }), _jsxs("div", { style: { display: "flex", gap: 8, marginTop: 6 }, children: [_jsx("button", { type: "button", "data-srte-suggestion-accept": entry.id, disabled: busyId === entry.id, onClick: (event) => { event.stopPropagation(); onAcceptInline(entry.id); }, children: "Accept" }), _jsx("button", { type: "button", "data-srte-suggestion-reject": entry.id, disabled: busyId === entry.id, onClick: (event) => { event.stopPropagation(); onRejectInline(entry.id); }, style: { color: "var(--srte-danger)" }, children: "Reject" })] })] }, entry.id))), structuralSuggestions.map((suggestion) => (_jsxs("div", { "data-srte-suggestion-entry": suggestion.id, onClick: () => onSelect(suggestion.id), style: { padding: "10px 0", borderBottom: "1px solid var(--srte-border-light)", outline: activeId === suggestion.id ? "2px solid var(--srte-primary)" : "none" }, children: [_jsxs("div", { style: { fontSize: 12, fontWeight: 600 }, children: [suggestion.authorId, " suggests removing this block"] }), _jsx("div", { style: { fontSize: 11, color: "var(--srte-text-muted)" }, children: formatTimestamp(suggestion.createdAt) }), _jsxs("div", { style: { display: "flex", gap: 8, marginTop: 6 }, children: [_jsx("button", { type: "button", "data-srte-suggestion-accept": suggestion.id, disabled: busyId === suggestion.id, onClick: (event) => { event.stopPropagation(); onAcceptStructural(suggestion); }, children: "Accept" }), _jsx("button", { type: "button", "data-srte-suggestion-reject": suggestion.id, disabled: busyId === suggestion.id, onClick: (event) => { event.stopPropagation(); onRejectStructural(suggestion); }, style: { color: "var(--srte-danger)" }, children: "Reject" })] })] }, suggestion.id)))] })] }));
|
|
33
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type BorderStyle = "solid" | "dashed" | "dotted";
|
|
2
|
+
export interface BorderSides {
|
|
3
|
+
top: boolean;
|
|
4
|
+
right: boolean;
|
|
5
|
+
bottom: boolean;
|
|
6
|
+
left: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface BorderDraft {
|
|
9
|
+
sides: BorderSides;
|
|
10
|
+
style: BorderStyle;
|
|
11
|
+
widthPx: number;
|
|
12
|
+
hex: string;
|
|
13
|
+
}
|
|
14
|
+
export interface TableBorderPopoverProps {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
initial: BorderDraft;
|
|
18
|
+
/** Up to 4 previously-committed border colours, most-recent first - same pattern as the other colour buckets. */
|
|
19
|
+
recentColors?: readonly string[];
|
|
20
|
+
/** Fires on every change (a side toggled, style/width picked, or a colour drag frame) - the caller live-previews it on the real cell(s), same pattern as the plain colour picker's onPreview. */
|
|
21
|
+
onPreview: (draft: BorderDraft) => void;
|
|
22
|
+
/** Fires once, only from the explicit Apply button - unlike ColorPickerPopover, this composite dialog (four independent fields assembled together) commits on a deliberate action, not on any dismissal. */
|
|
23
|
+
onApply: (draft: BorderDraft) => void;
|
|
24
|
+
/** Fires from Cancel, Escape, or an outside click - reverts to `initial`. */
|
|
25
|
+
onCancel: () => void;
|
|
26
|
+
}
|
|
27
|
+
export declare const BORDER_WIDTH_PRESETS: readonly [{
|
|
28
|
+
readonly id: "thin";
|
|
29
|
+
readonly label: "Thin";
|
|
30
|
+
readonly px: 2;
|
|
31
|
+
}, {
|
|
32
|
+
readonly id: "medium";
|
|
33
|
+
readonly label: "Medium";
|
|
34
|
+
readonly px: 3;
|
|
35
|
+
}, {
|
|
36
|
+
readonly id: "thick";
|
|
37
|
+
readonly label: "Thick";
|
|
38
|
+
readonly px: 4;
|
|
39
|
+
}];
|
|
40
|
+
/**
|
|
41
|
+
* "Border options" - one popover combining every dimension of a cell
|
|
42
|
+
* border, replacing what used to be a separate toolbar-dropdown width
|
|
43
|
+
* <select> and a standalone "Border colour" item. table_cell.attrs only
|
|
44
|
+
* ever stored ONE uniform border for all four sides (`borders`) - "which
|
|
45
|
+
* sides get a border" (per-cell top/right/bottom/left) needed new per-side
|
|
46
|
+
* schema attrs (borderTop/Right/Bottom/Left), added specifically to back
|
|
47
|
+
* this control (see docs/bugs/table-cell-border-color-width-no-ui.md's
|
|
48
|
+
* addendum). One style/width/colour choice applies to whichever sides are
|
|
49
|
+
* toggled on here, rather than letting all four vary independently - real
|
|
50
|
+
* border dialogs (Word, Google Docs) default to this simpler mode too, and
|
|
51
|
+
* a fully independent 4-way style/width/colour matrix wasn't asked for.
|
|
52
|
+
*
|
|
53
|
+
* The colour swatch opens the existing ColorPickerPopover completely
|
|
54
|
+
* unmodified, as a nested popover anchored to the swatch - the dismiss/
|
|
55
|
+
* Escape handling below explicitly accounts for it being a DOM sibling
|
|
56
|
+
* (both are position:fixed overlays, so `rootRef.contains()` alone would
|
|
57
|
+
* never recognize a click inside it as "inside" this popover).
|
|
58
|
+
*/
|
|
59
|
+
export declare function TableBorderPopover({ x, y, initial, recentColors, onPreview, onApply, onCancel }: TableBorderPopoverProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
import { getFixedPositioningOrigin } from "./fixedPositioning.js";
|
|
5
|
+
// Widths start at 2px, not 1px - table_cell's border-collapse table (see
|
|
6
|
+
// theme.ts's th/td rule) already renders a 1px ambient default gridline on
|
|
7
|
+
// every cell. A custom border tied at 1px loses CSS border-collapse's
|
|
8
|
+
// conflict resolution on shared edges half the time (whichever side faces
|
|
9
|
+
// an earlier-in-table-order neighbor keeps the neighbor's default border,
|
|
10
|
+
// confirmed empirically: 1px, and even a fractional 1.5px, lost this way in
|
|
11
|
+
// every engine tested) - only a width strictly greater than the ambient
|
|
12
|
+
// default reliably wins on every side regardless of table position. 2px is
|
|
13
|
+
// the thinnest value that's actually renderable as a real, always-visible
|
|
14
|
+
// custom border under this constraint - see
|
|
15
|
+
// docs/bugs/table-cell-border-thin-preset-loses-border-collapse-tie.md.
|
|
16
|
+
export const BORDER_WIDTH_PRESETS = [
|
|
17
|
+
{ id: "thin", label: "Thin", px: 2 },
|
|
18
|
+
{ id: "medium", label: "Medium", px: 3 },
|
|
19
|
+
{ id: "thick", label: "Thick", px: 4 },
|
|
20
|
+
];
|
|
21
|
+
const STYLE_OPTIONS = ["solid", "dashed", "dotted"];
|
|
22
|
+
const buttonStyle = {
|
|
23
|
+
minHeight: 32, padding: "0 10px", border: "1px solid var(--srte-input-border)", borderRadius: 8,
|
|
24
|
+
background: "var(--srte-input-bg)", color: "var(--srte-menu-text)", cursor: "pointer", fontWeight: 500, fontSize: 13,
|
|
25
|
+
};
|
|
26
|
+
const selectStyle = {
|
|
27
|
+
width: "100%", height: 32, boxSizing: "border-box", padding: "0 8px", borderRadius: 8,
|
|
28
|
+
border: "1px solid var(--srte-input-border)", background: "var(--srte-input-bg)", color: "var(--srte-input-text)",
|
|
29
|
+
font: "inherit", fontSize: 13, cursor: "pointer",
|
|
30
|
+
};
|
|
31
|
+
const fieldLabelStyle = { fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.04em", opacity: 0.7, marginBottom: 4 };
|
|
32
|
+
const SIDE_BAR_LENGTH = 40;
|
|
33
|
+
const SIDE_BAR_THICKNESS = 8;
|
|
34
|
+
const SIDE_BOX = 64;
|
|
35
|
+
/**
|
|
36
|
+
* "Border options" - one popover combining every dimension of a cell
|
|
37
|
+
* border, replacing what used to be a separate toolbar-dropdown width
|
|
38
|
+
* <select> and a standalone "Border colour" item. table_cell.attrs only
|
|
39
|
+
* ever stored ONE uniform border for all four sides (`borders`) - "which
|
|
40
|
+
* sides get a border" (per-cell top/right/bottom/left) needed new per-side
|
|
41
|
+
* schema attrs (borderTop/Right/Bottom/Left), added specifically to back
|
|
42
|
+
* this control (see docs/bugs/table-cell-border-color-width-no-ui.md's
|
|
43
|
+
* addendum). One style/width/colour choice applies to whichever sides are
|
|
44
|
+
* toggled on here, rather than letting all four vary independently - real
|
|
45
|
+
* border dialogs (Word, Google Docs) default to this simpler mode too, and
|
|
46
|
+
* a fully independent 4-way style/width/colour matrix wasn't asked for.
|
|
47
|
+
*
|
|
48
|
+
* The colour swatch opens the existing ColorPickerPopover completely
|
|
49
|
+
* unmodified, as a nested popover anchored to the swatch - the dismiss/
|
|
50
|
+
* Escape handling below explicitly accounts for it being a DOM sibling
|
|
51
|
+
* (both are position:fixed overlays, so `rootRef.contains()` alone would
|
|
52
|
+
* never recognize a click inside it as "inside" this popover).
|
|
53
|
+
*/
|
|
54
|
+
export function TableBorderPopover({ x, y, initial, recentColors, onPreview, onApply, onCancel }) {
|
|
55
|
+
const [draft, setDraft] = useState(initial);
|
|
56
|
+
const [colorPickerAnchor, setColorPickerAnchor] = useState(null);
|
|
57
|
+
const hexBeforeNestedPickerRef = useRef(initial.hex);
|
|
58
|
+
const rootRef = useRef(null);
|
|
59
|
+
const swatchRef = useRef(null);
|
|
60
|
+
const [placement, setPlacement] = useState(null);
|
|
61
|
+
const onCancelRef = useRef(onCancel);
|
|
62
|
+
onCancelRef.current = onCancel;
|
|
63
|
+
const onPreviewRef = useRef(onPreview);
|
|
64
|
+
onPreviewRef.current = onPreview;
|
|
65
|
+
useLayoutEffect(() => {
|
|
66
|
+
const el = rootRef.current;
|
|
67
|
+
if (!el)
|
|
68
|
+
return;
|
|
69
|
+
const margin = 8;
|
|
70
|
+
const { width, height } = el.getBoundingClientRect();
|
|
71
|
+
const viewportWidth = window.innerWidth;
|
|
72
|
+
const viewportHeight = window.innerHeight;
|
|
73
|
+
const overflowsRight = x + width > viewportWidth - margin;
|
|
74
|
+
const left = overflowsRight ? x - width : x;
|
|
75
|
+
const clampedLeft = Math.min(Math.max(margin, left), Math.max(margin, viewportWidth - width - margin));
|
|
76
|
+
const overflowsBottom = y + height > viewportHeight - margin;
|
|
77
|
+
const top = overflowsBottom ? y - height : y;
|
|
78
|
+
const clampedTop = Math.min(Math.max(margin, top), Math.max(margin, viewportHeight - margin));
|
|
79
|
+
const origin = getFixedPositioningOrigin(el);
|
|
80
|
+
setPlacement({ left: clampedLeft - origin.left, top: clampedTop - origin.top });
|
|
81
|
+
}, [x, y]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const dismissIfOutside = (event) => {
|
|
84
|
+
const target = event.target;
|
|
85
|
+
if (rootRef.current?.contains(target))
|
|
86
|
+
return;
|
|
87
|
+
// The nested colour picker is a DOM sibling (both are position:fixed
|
|
88
|
+
// overlays), not a descendant of rootRef - without this, clicking
|
|
89
|
+
// inside it would look like an "outside" click on this popover.
|
|
90
|
+
if (colorPickerAnchor && document.querySelector('[data-srte-color-popover="true"]')?.contains(target))
|
|
91
|
+
return;
|
|
92
|
+
onCancelRef.current();
|
|
93
|
+
};
|
|
94
|
+
window.addEventListener("pointerdown", dismissIfOutside, true);
|
|
95
|
+
window.addEventListener("mousedown", dismissIfOutside, true);
|
|
96
|
+
return () => {
|
|
97
|
+
window.removeEventListener("pointerdown", dismissIfOutside, true);
|
|
98
|
+
window.removeEventListener("mousedown", dismissIfOutside, true);
|
|
99
|
+
};
|
|
100
|
+
}, [colorPickerAnchor]);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const onWindowKeyDown = (event) => {
|
|
103
|
+
if (event.key !== "Escape")
|
|
104
|
+
return;
|
|
105
|
+
// While the nested colour picker is open, let its own Escape handler
|
|
106
|
+
// commit/dismiss just that picker first - a second Escape closes this
|
|
107
|
+
// whole dialog.
|
|
108
|
+
if (colorPickerAnchor)
|
|
109
|
+
return;
|
|
110
|
+
onCancelRef.current();
|
|
111
|
+
};
|
|
112
|
+
window.addEventListener("keydown", onWindowKeyDown);
|
|
113
|
+
return () => window.removeEventListener("keydown", onWindowKeyDown);
|
|
114
|
+
}, [colorPickerAnchor]);
|
|
115
|
+
const update = (patch) => {
|
|
116
|
+
const next = { ...draft, ...patch };
|
|
117
|
+
setDraft(next);
|
|
118
|
+
onPreviewRef.current(next);
|
|
119
|
+
};
|
|
120
|
+
const toggleSide = (side) => update({ sides: { ...draft.sides, [side]: !draft.sides[side] } });
|
|
121
|
+
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: {
|
|
122
|
+
position: "absolute", border: "none", borderRadius: 3, cursor: "pointer", padding: 0,
|
|
123
|
+
background: draft.sides[side] ? "var(--srte-primary)" : "var(--srte-input-border)",
|
|
124
|
+
...style,
|
|
125
|
+
} }));
|
|
126
|
+
return (_jsxs("div", { ref: rootRef, "data-srte-table-border-popover": "true", role: "dialog", "aria-label": "Border options", style: {
|
|
127
|
+
position: "fixed",
|
|
128
|
+
left: placement?.left ?? x,
|
|
129
|
+
top: placement?.top ?? y,
|
|
130
|
+
visibility: placement ? "visible" : "hidden",
|
|
131
|
+
zIndex: 70,
|
|
132
|
+
width: 240,
|
|
133
|
+
boxSizing: "border-box",
|
|
134
|
+
background: "var(--srte-menu-bg)",
|
|
135
|
+
color: "var(--srte-menu-text)",
|
|
136
|
+
border: "1px solid var(--srte-border)",
|
|
137
|
+
borderRadius: 12,
|
|
138
|
+
boxShadow: "var(--srte-menu-shadow)",
|
|
139
|
+
padding: 14,
|
|
140
|
+
display: "flex",
|
|
141
|
+
flexDirection: "column",
|
|
142
|
+
gap: 12,
|
|
143
|
+
}, 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)
|
|
144
|
+
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: () => {
|
|
145
|
+
hexBeforeNestedPickerRef.current = draft.hex;
|
|
146
|
+
const rect = swatchRef.current?.getBoundingClientRect();
|
|
147
|
+
setColorPickerAnchor({ x: rect?.left ?? x, y: (rect?.bottom ?? y) + 4 });
|
|
148
|
+
}, 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); } })] }));
|
|
149
|
+
}
|
|
@@ -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;
|