smartrte-react 0.3.4 → 1.0.0-beta.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.
- package/CHANGELOG.md +19 -0
- package/README.md +23 -0
- package/dist/adapters/domChecklistCommandBridge.d.ts +6 -0
- package/dist/adapters/domChecklistCommandBridge.js +33 -0
- package/dist/adapters/domCommandBridge.d.ts +19 -0
- package/dist/adapters/domCommandBridge.js +33 -0
- package/dist/adapters/domInlineAtomCommandBridge.d.ts +15 -0
- package/dist/adapters/domInlineAtomCommandBridge.js +205 -0
- package/dist/adapters/domInlineImageCommandBridge.d.ts +14 -0
- package/dist/adapters/domInlineImageCommandBridge.js +6 -0
- package/dist/adapters/domMoveCommandBridge.d.ts +7 -0
- package/dist/adapters/domMoveCommandBridge.js +54 -0
- package/dist/adapters/domSelectionBridge.d.ts +5 -3
- package/dist/adapters/domSelectionBridge.js +96 -44
- package/dist/adapters/domSmartDocument.d.ts +4 -4
- package/dist/adapters/domSmartDocument.js +159 -17
- package/dist/adapters/domTableCommandBridge.d.ts +61 -0
- package/dist/adapters/domTableCommandBridge.js +231 -0
- package/dist/adapters/internalFlags.d.ts +0 -8
- package/dist/adapters/internalFlags.js +0 -24
- package/dist/adapters/legacyListShadowComparator.d.ts +26 -0
- package/dist/adapters/legacyListShadowComparator.js +316 -0
- 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 +129 -0
- package/dist/canonicalEditorRuntime.js +331 -0
- 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 +59 -0
- package/dist/components/CanonicalAuthorityEditor.js +1492 -0
- package/dist/components/ClassicEditorAuthority.d.ts +109 -0
- package/dist/components/ClassicEditorAuthority.js +17 -0
- package/dist/components/ColorPickerPopover.d.ts +39 -0
- package/dist/components/ColorPickerPopover.js +285 -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/MediaManager.d.ts +4 -20
- package/dist/components/MediaManager.js +35 -6
- package/dist/components/MediaOverlay.d.ts +36 -0
- package/dist/components/MediaOverlay.js +163 -0
- package/dist/components/MediaPicker.d.ts +10 -0
- package/dist/components/MediaPicker.js +16 -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 +147 -0
- package/dist/components/TableResizeHandles.d.ts +25 -0
- package/dist/components/TableResizeHandles.js +301 -0
- package/dist/components/TableSizePickerPopover.d.ts +16 -0
- package/dist/components/TableSizePickerPopover.js +126 -0
- package/dist/components/ToolbarPrimitives.d.ts +129 -0
- package/dist/components/ToolbarPrimitives.js +146 -0
- package/dist/components/VersionHistoryPanel.d.ts +9 -0
- package/dist/components/VersionHistoryPanel.js +157 -0
- package/dist/formulaLibrary.d.ts +31 -0
- package/dist/formulaLibrary.js +90 -0
- package/dist/index.d.ts +23 -1
- package/dist/index.js +14 -1
- package/dist/mediaManagerAdapter.d.ts +12 -0
- package/dist/mediaManagerAdapter.js +34 -0
- package/dist/mediaProvider.d.ts +59 -0
- package/dist/mediaProvider.js +1 -0
- 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 +161 -1
- package/dist/versionProvider.d.ts +29 -0
- package/dist/versionProvider.js +1 -0
- package/package.json +28 -14
- package/dist/adapters/coreBold.d.ts +0 -5
- package/dist/adapters/coreBold.js +0 -3
- package/dist/adapters/inlineMarkCoreExecution.d.ts +0 -9
- package/dist/adapters/inlineMarkCoreExecution.js +0 -25
- package/dist/components/ClassicEditor.d.ts +0 -66
- package/dist/components/ClassicEditor.js +0 -6617
- package/dist/standalone/editor.js +0 -841
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { SmartEditorHandle } from "../canonicalEditorRuntime.js";
|
|
3
|
+
import type { MediaProvider } from "../mediaProvider.js";
|
|
4
|
+
import type { MediaPickerComponent } from "./MediaPicker.js";
|
|
5
|
+
import { type CanonicalAuthorityEditorProps } from "./CanonicalAuthorityEditor.js";
|
|
6
|
+
export type ClassicEditorProps = Omit<CanonicalAuthorityEditorProps, "onChange" | "onHtmlChange"> & {
|
|
7
|
+
/** Legacy HTML value. Superseded by defaultValue; retained for source compatibility. */
|
|
8
|
+
value?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Legacy-compat: always fires with a plain HTML string, matching every
|
|
11
|
+
* real pre-canonical caller's actual usage (e.g. Sootr's
|
|
12
|
+
* RichTextEditor.tsx: `onChange: (html: string) => void`, used directly
|
|
13
|
+
* as form state and passed to autosave/upload-on-save pipelines expecting
|
|
14
|
+
* a string). This used to be typed as a union also accepting the new
|
|
15
|
+
* canonical `SmartEditorChange` object, but the implementation only ever
|
|
16
|
+
* invoked it with that object regardless of which shape the caller's
|
|
17
|
+
* function actually expected - a real, live bug (a legacy caller
|
|
18
|
+
* expecting a string got an object with no type error), not just an
|
|
19
|
+
* imprecise type. Fixed by wiring this to the existing debounced HTML
|
|
20
|
+
* serialization (`onHtmlChange`, ~250ms after the last edit - see
|
|
21
|
+
* canonicalEditorRuntime.ts's `scheduleHtmlChange`) instead of the
|
|
22
|
+
* per-transaction `onChange`, since that's the mechanism already built
|
|
23
|
+
* for exactly this purpose. Callers who need the structured
|
|
24
|
+
* per-transaction event should use `CanonicalAuthorityEditorProps`
|
|
25
|
+
* directly, not this legacy-compat surface.
|
|
26
|
+
*/
|
|
27
|
+
onChange?: (html: string) => void;
|
|
28
|
+
mediaProvider?: MediaProvider;
|
|
29
|
+
mediaPicker?: MediaPickerComponent;
|
|
30
|
+
onRuntime?: CanonicalAuthorityEditorProps["onRuntime"];
|
|
31
|
+
/**
|
|
32
|
+
* `table={false}` selects the "simple" capability preset (see
|
|
33
|
+
* capabilityPresets.ts) unless `preset` is also explicitly given, in
|
|
34
|
+
* which case `preset` wins. This is the one legacy toggle real callers
|
|
35
|
+
* actually vary - Sootr's MCQ/Anomaly/PYEQ editors all pass
|
|
36
|
+
* `enableTable={false}` (see docs/SOOTR_MIGRATION_READINESS.md gap #3);
|
|
37
|
+
* nothing in this project's history has ever needed `media`/`formula` to
|
|
38
|
+
* vary independently of each other (both are owned by the single "atom"
|
|
39
|
+
* plugin today), so those two remain accepted-but-ignored, same as
|
|
40
|
+
* before.
|
|
41
|
+
*/
|
|
42
|
+
table?: boolean;
|
|
43
|
+
media?: unknown;
|
|
44
|
+
formula?: unknown;
|
|
45
|
+
fonts?: unknown;
|
|
46
|
+
defaultFont?: unknown;
|
|
47
|
+
preserveFontFamily?: unknown;
|
|
48
|
+
preserveColors?: unknown;
|
|
49
|
+
preserveDocxStyles?: unknown;
|
|
50
|
+
theme?: unknown;
|
|
51
|
+
showFontSize?: unknown;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Canonical authority is unconditionally the only implementation as of
|
|
55
|
+
* Phase 8b closeout (2026-08-12) — the DOM-authoritative legacy rollback
|
|
56
|
+
* path (LegacyClassicEditor, its four rollback bridges, and the
|
|
57
|
+
* canonicalAuthorityFlag rollback switch itself) was retired once the
|
|
58
|
+
* Gate 13/14 replay and production-surface gates passed. This wrapper
|
|
59
|
+
* remains the stable public import path and continues to accept (and
|
|
60
|
+
* ignore) legacy-only configuration props below so existing call sites
|
|
61
|
+
* keep compiling.
|
|
62
|
+
*/
|
|
63
|
+
export declare const ClassicEditor: React.ForwardRefExoticComponent<Omit<CanonicalAuthorityEditorProps, "onChange" | "onHtmlChange"> & {
|
|
64
|
+
/** Legacy HTML value. Superseded by defaultValue; retained for source compatibility. */
|
|
65
|
+
value?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Legacy-compat: always fires with a plain HTML string, matching every
|
|
68
|
+
* real pre-canonical caller's actual usage (e.g. Sootr's
|
|
69
|
+
* RichTextEditor.tsx: `onChange: (html: string) => void`, used directly
|
|
70
|
+
* as form state and passed to autosave/upload-on-save pipelines expecting
|
|
71
|
+
* a string). This used to be typed as a union also accepting the new
|
|
72
|
+
* canonical `SmartEditorChange` object, but the implementation only ever
|
|
73
|
+
* invoked it with that object regardless of which shape the caller's
|
|
74
|
+
* function actually expected - a real, live bug (a legacy caller
|
|
75
|
+
* expecting a string got an object with no type error), not just an
|
|
76
|
+
* imprecise type. Fixed by wiring this to the existing debounced HTML
|
|
77
|
+
* serialization (`onHtmlChange`, ~250ms after the last edit - see
|
|
78
|
+
* canonicalEditorRuntime.ts's `scheduleHtmlChange`) instead of the
|
|
79
|
+
* per-transaction `onChange`, since that's the mechanism already built
|
|
80
|
+
* for exactly this purpose. Callers who need the structured
|
|
81
|
+
* per-transaction event should use `CanonicalAuthorityEditorProps`
|
|
82
|
+
* directly, not this legacy-compat surface.
|
|
83
|
+
*/
|
|
84
|
+
onChange?: (html: string) => void;
|
|
85
|
+
mediaProvider?: MediaProvider;
|
|
86
|
+
mediaPicker?: MediaPickerComponent;
|
|
87
|
+
onRuntime?: CanonicalAuthorityEditorProps["onRuntime"];
|
|
88
|
+
/**
|
|
89
|
+
* `table={false}` selects the "simple" capability preset (see
|
|
90
|
+
* capabilityPresets.ts) unless `preset` is also explicitly given, in
|
|
91
|
+
* which case `preset` wins. This is the one legacy toggle real callers
|
|
92
|
+
* actually vary - Sootr's MCQ/Anomaly/PYEQ editors all pass
|
|
93
|
+
* `enableTable={false}` (see docs/SOOTR_MIGRATION_READINESS.md gap #3);
|
|
94
|
+
* nothing in this project's history has ever needed `media`/`formula` to
|
|
95
|
+
* vary independently of each other (both are owned by the single "atom"
|
|
96
|
+
* plugin today), so those two remain accepted-but-ignored, same as
|
|
97
|
+
* before.
|
|
98
|
+
*/
|
|
99
|
+
table?: boolean;
|
|
100
|
+
media?: unknown;
|
|
101
|
+
formula?: unknown;
|
|
102
|
+
fonts?: unknown;
|
|
103
|
+
defaultFont?: unknown;
|
|
104
|
+
preserveFontFamily?: unknown;
|
|
105
|
+
preserveColors?: unknown;
|
|
106
|
+
preserveDocxStyles?: unknown;
|
|
107
|
+
theme?: unknown;
|
|
108
|
+
showFontSize?: unknown;
|
|
109
|
+
} & React.RefAttributes<SmartEditorHandle>>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { CanonicalAuthorityEditor } from "./CanonicalAuthorityEditor.js";
|
|
4
|
+
/**
|
|
5
|
+
* Canonical authority is unconditionally the only implementation as of
|
|
6
|
+
* Phase 8b closeout (2026-08-12) — the DOM-authoritative legacy rollback
|
|
7
|
+
* path (LegacyClassicEditor, its four rollback bridges, and the
|
|
8
|
+
* canonicalAuthorityFlag rollback switch itself) was retired once the
|
|
9
|
+
* Gate 13/14 replay and production-surface gates passed. This wrapper
|
|
10
|
+
* remains the stable public import path and continues to accept (and
|
|
11
|
+
* ignore) legacy-only configuration props below so existing call sites
|
|
12
|
+
* keep compiling.
|
|
13
|
+
*/
|
|
14
|
+
export const ClassicEditor = forwardRef(function ClassicEditor(props, ref) {
|
|
15
|
+
const { value, onChange, table, preset, media: _media, formula: _formula, fonts: _fonts, defaultFont: _font, preserveFontFamily: _preserveFont, preserveColors: _preserveColors, preserveDocxStyles: _preserveDocx, theme: _theme, showFontSize: _showFontSize, ...canonical } = props;
|
|
16
|
+
return _jsx(CanonicalAuthorityEditor, { ...canonical, ref: ref, defaultValue: props.defaultValue ?? (typeof value === "string" ? value : undefined), preset: preset ?? (table === false ? "simple" : "full"), onHtmlChange: onChange });
|
|
17
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ColorPickerPopoverProps {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
label: string;
|
|
5
|
+
initialValue?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Continuous live-preview callback, fired on every drag frame of the
|
|
8
|
+
* saturation/value square or hue slider (and on every valid typed hex) -
|
|
9
|
+
* distinct from `onApply`, which fires once, when the popover is actually
|
|
10
|
+
* dismissed with a real change staged.
|
|
11
|
+
*/
|
|
12
|
+
onPreview?: (hex: string) => void;
|
|
13
|
+
/** Up to 4 previously-committed colors for this same picker context, most-recent first. */
|
|
14
|
+
recentColors?: readonly string[];
|
|
15
|
+
onApply: (hex: string) => void;
|
|
16
|
+
/** Discard: revert to the color the popover opened with. The only way to *not* commit - every other dismissal (outside click, Escape, the close button) commits whatever is currently staged. */
|
|
17
|
+
onCancel: () => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Phase 11.5 §2.4 originally shipped a native `<input type="color">` as the
|
|
21
|
+
* primary picker (see git history) - reverted here in favor of a fully
|
|
22
|
+
* in-page saturation/value square + hue strip, specifically so the drag
|
|
23
|
+
* surface is visible on the very first click (a native color input needs a
|
|
24
|
+
* *second* click to open its own OS-level dialog) and so every drag frame
|
|
25
|
+
* is a real, page-owned pointer event this component fully controls -
|
|
26
|
+
* sidestepping the whole "which native event means the user is actually
|
|
27
|
+
* done" problem a native input can never answer reliably (see
|
|
28
|
+
* docs/bugs/color-popover-closes-on-first-native-picker-interaction.md and
|
|
29
|
+
* docs/bugs/color-preview-render-steals-focus-from-popover.md, both about
|
|
30
|
+
* that exact native-event ambiguity).
|
|
31
|
+
*
|
|
32
|
+
* Commit model: there is no separate Apply button. Dragging (or typing a
|
|
33
|
+
* valid hex, or clicking a recent swatch) stages a live preview via
|
|
34
|
+
* `onPreview`; closing the popover *any* way other than the explicit
|
|
35
|
+
* Discard button - outside click, Escape, the × button - commits whatever
|
|
36
|
+
* is currently staged via `onApply`. Discard is the only way back to the
|
|
37
|
+
* color the popover opened with.
|
|
38
|
+
*/
|
|
39
|
+
export declare function ColorPickerPopover({ x, y, label, initialValue, recentColors, onPreview, onApply, onCancel }: ColorPickerPopoverProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
const HEX_PATTERN = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
4
|
+
const normalizeHex = (value) => {
|
|
5
|
+
if (!HEX_PATTERN.test(value))
|
|
6
|
+
return null;
|
|
7
|
+
if (value.length === 4)
|
|
8
|
+
return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}`.toLowerCase();
|
|
9
|
+
return value.toLowerCase();
|
|
10
|
+
};
|
|
11
|
+
const hsvToRgb = (h, s, v) => {
|
|
12
|
+
const sf = s / 100, vf = v / 100;
|
|
13
|
+
const c = vf * sf;
|
|
14
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
15
|
+
const m = vf - c;
|
|
16
|
+
const [r, g, b] = h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x];
|
|
17
|
+
return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];
|
|
18
|
+
};
|
|
19
|
+
const rgbToHex = (r, g, b) => `#${[r, g, b].map((channel) => Math.max(0, Math.min(255, channel)).toString(16).padStart(2, "0")).join("")}`;
|
|
20
|
+
const hexToRgb = (hex) => {
|
|
21
|
+
const normalized = normalizeHex(hex);
|
|
22
|
+
if (!normalized)
|
|
23
|
+
return null;
|
|
24
|
+
const value = Number.parseInt(normalized.slice(1), 16);
|
|
25
|
+
return [(value >> 16) & 255, (value >> 8) & 255, value & 255];
|
|
26
|
+
};
|
|
27
|
+
const rgbToHsv = (r, g, b) => {
|
|
28
|
+
const rf = r / 255, gf = g / 255, bf = b / 255;
|
|
29
|
+
const max = Math.max(rf, gf, bf), min = Math.min(rf, gf, bf), d = max - min;
|
|
30
|
+
let h = 0;
|
|
31
|
+
if (d !== 0) {
|
|
32
|
+
if (max === rf)
|
|
33
|
+
h = 60 * (((gf - bf) / d) % 6);
|
|
34
|
+
else if (max === gf)
|
|
35
|
+
h = 60 * ((bf - rf) / d + 2);
|
|
36
|
+
else
|
|
37
|
+
h = 60 * ((rf - gf) / d + 4);
|
|
38
|
+
}
|
|
39
|
+
if (h < 0)
|
|
40
|
+
h += 360;
|
|
41
|
+
return [h, max === 0 ? 0 : (d / max) * 100, max * 100];
|
|
42
|
+
};
|
|
43
|
+
const hexToHsv = (hex) => {
|
|
44
|
+
const rgb = hexToRgb(hex);
|
|
45
|
+
return rgb ? rgbToHsv(...rgb) : [0, 0, 0];
|
|
46
|
+
};
|
|
47
|
+
const buttonStyle = {
|
|
48
|
+
minHeight: 32,
|
|
49
|
+
padding: "0 12px",
|
|
50
|
+
border: "1px solid var(--srte-input-border)",
|
|
51
|
+
borderRadius: 8,
|
|
52
|
+
background: "var(--srte-input-bg)",
|
|
53
|
+
color: "var(--srte-menu-text)",
|
|
54
|
+
cursor: "pointer",
|
|
55
|
+
fontWeight: 500,
|
|
56
|
+
fontSize: 13,
|
|
57
|
+
};
|
|
58
|
+
const inputStyle = {
|
|
59
|
+
width: "100%",
|
|
60
|
+
height: 32,
|
|
61
|
+
boxSizing: "border-box",
|
|
62
|
+
padding: "0 10px",
|
|
63
|
+
border: "1px solid var(--srte-input-border)",
|
|
64
|
+
borderRadius: 8,
|
|
65
|
+
outline: "none",
|
|
66
|
+
background: "var(--srte-input-bg)",
|
|
67
|
+
color: "var(--srte-input-text)",
|
|
68
|
+
font: "inherit",
|
|
69
|
+
};
|
|
70
|
+
/** Drives a square/slider from pointer drag - shared by the SV square and the hue strip below, differing only in how a client offset maps to a value. */
|
|
71
|
+
const useDrag = (onMove) => {
|
|
72
|
+
const ref = useRef(null);
|
|
73
|
+
const onPointerDown = (event) => {
|
|
74
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
75
|
+
onMove(event.clientX, event.clientY, rect);
|
|
76
|
+
const move = (moveEvent) => onMove(moveEvent.clientX, moveEvent.clientY, rect);
|
|
77
|
+
const up = () => {
|
|
78
|
+
window.removeEventListener("pointermove", move);
|
|
79
|
+
window.removeEventListener("pointerup", up);
|
|
80
|
+
};
|
|
81
|
+
window.addEventListener("pointermove", move);
|
|
82
|
+
window.addEventListener("pointerup", up);
|
|
83
|
+
};
|
|
84
|
+
return { ref, onPointerDown };
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Phase 11.5 §2.4 originally shipped a native `<input type="color">` as the
|
|
88
|
+
* primary picker (see git history) - reverted here in favor of a fully
|
|
89
|
+
* in-page saturation/value square + hue strip, specifically so the drag
|
|
90
|
+
* surface is visible on the very first click (a native color input needs a
|
|
91
|
+
* *second* click to open its own OS-level dialog) and so every drag frame
|
|
92
|
+
* is a real, page-owned pointer event this component fully controls -
|
|
93
|
+
* sidestepping the whole "which native event means the user is actually
|
|
94
|
+
* done" problem a native input can never answer reliably (see
|
|
95
|
+
* docs/bugs/color-popover-closes-on-first-native-picker-interaction.md and
|
|
96
|
+
* docs/bugs/color-preview-render-steals-focus-from-popover.md, both about
|
|
97
|
+
* that exact native-event ambiguity).
|
|
98
|
+
*
|
|
99
|
+
* Commit model: there is no separate Apply button. Dragging (or typing a
|
|
100
|
+
* valid hex, or clicking a recent swatch) stages a live preview via
|
|
101
|
+
* `onPreview`; closing the popover *any* way other than the explicit
|
|
102
|
+
* Discard button - outside click, Escape, the × button - commits whatever
|
|
103
|
+
* is currently staged via `onApply`. Discard is the only way back to the
|
|
104
|
+
* color the popover opened with.
|
|
105
|
+
*/
|
|
106
|
+
export function ColorPickerPopover({ x, y, label, initialValue = "#000000", recentColors, onPreview, onApply, onCancel }) {
|
|
107
|
+
const [hsv, setHsv] = useState(() => hexToHsv(initialValue));
|
|
108
|
+
const hex = rgbToHex(...hsvToRgb(hsv[0], hsv[1], hsv[2]));
|
|
109
|
+
const [hexText, setHexText] = useState(hex);
|
|
110
|
+
const [error, setError] = useState("");
|
|
111
|
+
const rootRef = useRef(null);
|
|
112
|
+
const titleId = useId();
|
|
113
|
+
const errorId = useId();
|
|
114
|
+
const [placement, setPlacement] = useState(null);
|
|
115
|
+
// Nothing has actually changed until a drag frame, a valid typed hex, or a
|
|
116
|
+
// recent-swatch click stages one - closing without ever touching anything
|
|
117
|
+
// must be a true no-op, not commit an identical "new" color as a fresh
|
|
118
|
+
// history entry.
|
|
119
|
+
const stagedRef = useRef(false);
|
|
120
|
+
const hexRef = useRef(hex);
|
|
121
|
+
hexRef.current = hex;
|
|
122
|
+
const onApplyRef = useRef(onApply);
|
|
123
|
+
onApplyRef.current = onApply;
|
|
124
|
+
const onCancelRef = useRef(onCancel);
|
|
125
|
+
onCancelRef.current = onCancel;
|
|
126
|
+
useEffect(() => { setHexText(hex); }, [hex]);
|
|
127
|
+
useLayoutEffect(() => {
|
|
128
|
+
const el = rootRef.current;
|
|
129
|
+
if (!el)
|
|
130
|
+
return;
|
|
131
|
+
const margin = 8;
|
|
132
|
+
const { width, height } = el.getBoundingClientRect();
|
|
133
|
+
const viewportWidth = window.innerWidth;
|
|
134
|
+
const viewportHeight = window.innerHeight;
|
|
135
|
+
const overflowsRight = x + width > viewportWidth - margin;
|
|
136
|
+
const left = overflowsRight ? x - width : x;
|
|
137
|
+
const clampedLeft = Math.min(Math.max(margin, left), Math.max(margin, viewportWidth - width - margin));
|
|
138
|
+
const overflowsBottom = y + height > viewportHeight - margin;
|
|
139
|
+
const top = overflowsBottom ? y - height : y;
|
|
140
|
+
const clampedTop = Math.min(Math.max(margin, top), Math.max(margin, viewportHeight - margin));
|
|
141
|
+
setPlacement({ left: clampedLeft, top: clampedTop });
|
|
142
|
+
}, [x, y]);
|
|
143
|
+
const stage = (nextHsv) => {
|
|
144
|
+
stagedRef.current = true;
|
|
145
|
+
setHsv(nextHsv);
|
|
146
|
+
onPreview?.(rgbToHex(...hsvToRgb(nextHsv[0], nextHsv[1], nextHsv[2])));
|
|
147
|
+
};
|
|
148
|
+
const svDrag = useDrag((clientX, clientY, rect) => {
|
|
149
|
+
const s = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width)) * 100;
|
|
150
|
+
const v = 100 - Math.max(0, Math.min(1, (clientY - rect.top) / rect.height)) * 100;
|
|
151
|
+
stage([hsv[0], s, v]);
|
|
152
|
+
});
|
|
153
|
+
const hueDrag = useDrag((clientX, _clientY, rect) => {
|
|
154
|
+
const h = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width)) * 360;
|
|
155
|
+
stage([h, hsv[1], hsv[2]]);
|
|
156
|
+
});
|
|
157
|
+
const commitAndClose = () => {
|
|
158
|
+
if (stagedRef.current)
|
|
159
|
+
onApplyRef.current(hexRef.current);
|
|
160
|
+
else
|
|
161
|
+
onCancelRef.current();
|
|
162
|
+
};
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
const dismissIfOutside = (event) => {
|
|
165
|
+
if (rootRef.current && !rootRef.current.contains(event.target))
|
|
166
|
+
commitAndClose();
|
|
167
|
+
};
|
|
168
|
+
window.addEventListener("pointerdown", dismissIfOutside, true);
|
|
169
|
+
window.addEventListener("mousedown", dismissIfOutside, true);
|
|
170
|
+
return () => {
|
|
171
|
+
window.removeEventListener("pointerdown", dismissIfOutside, true);
|
|
172
|
+
window.removeEventListener("mousedown", dismissIfOutside, true);
|
|
173
|
+
};
|
|
174
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
175
|
+
}, []);
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
const onWindowKeyDown = (event) => {
|
|
178
|
+
if (event.key === "Escape")
|
|
179
|
+
commitAndClose();
|
|
180
|
+
};
|
|
181
|
+
window.addEventListener("keydown", onWindowKeyDown);
|
|
182
|
+
return () => window.removeEventListener("keydown", onWindowKeyDown);
|
|
183
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
184
|
+
}, []);
|
|
185
|
+
const applyRecentColor = (swatch) => {
|
|
186
|
+
const rgb = hexToRgb(swatch);
|
|
187
|
+
if (!rgb)
|
|
188
|
+
return;
|
|
189
|
+
stage(rgbToHsv(...rgb));
|
|
190
|
+
};
|
|
191
|
+
return (_jsxs("div", { ref: rootRef, "data-srte-color-popover": "true", role: "dialog", "aria-labelledby": titleId, style: {
|
|
192
|
+
position: "fixed",
|
|
193
|
+
left: placement?.left ?? x,
|
|
194
|
+
top: placement?.top ?? y,
|
|
195
|
+
visibility: placement ? "visible" : "hidden",
|
|
196
|
+
zIndex: 70,
|
|
197
|
+
width: 224,
|
|
198
|
+
boxSizing: "border-box",
|
|
199
|
+
background: "var(--srte-menu-bg)",
|
|
200
|
+
color: "var(--srte-menu-text)",
|
|
201
|
+
border: "1px solid var(--srte-border)",
|
|
202
|
+
borderRadius: 12,
|
|
203
|
+
boxShadow: "var(--srte-menu-shadow)",
|
|
204
|
+
padding: 14,
|
|
205
|
+
}, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10 }, children: [_jsx("div", { id: titleId, style: { fontWeight: 650 }, children: label }), _jsx("button", { type: "button", "aria-label": "Close color picker", title: "Close (keeps the current color)", onClick: commitAndClose, style: { ...buttonStyle, minWidth: 28, minHeight: 28, padding: 0, border: 0, background: "transparent", fontSize: 18 }, children: "\u00D7" })] }), _jsx("div", { ref: svDrag.ref, "data-srte-color-sv-square": "true", role: "slider", "aria-label": "Saturation and brightness", tabIndex: 0,
|
|
206
|
+
// Without this, clicking to drag focuses this div (it's a focusable
|
|
207
|
+
// slider for keyboard access), which collapses the editor's own
|
|
208
|
+
// native selection before the preview can apply a mark to it - the
|
|
209
|
+
// same focus-stealing class of bug fixed for LinkEditorPopover
|
|
210
|
+
// (docs/bugs/link-overlay-autofocus-steals-editor-focus-on-plain-click.md)
|
|
211
|
+
// and the toolbar dropdowns (docs/bugs/toolbar-dropdown-summary-steals-editor-focus.md).
|
|
212
|
+
onMouseDown: (event) => event.preventDefault(), onPointerDown: svDrag.onPointerDown, onKeyDown: (event) => {
|
|
213
|
+
const step = event.shiftKey ? 10 : 2;
|
|
214
|
+
if (event.key === "ArrowRight")
|
|
215
|
+
stage([hsv[0], Math.min(100, hsv[1] + step), hsv[2]]);
|
|
216
|
+
else if (event.key === "ArrowLeft")
|
|
217
|
+
stage([hsv[0], Math.max(0, hsv[1] - step), hsv[2]]);
|
|
218
|
+
else if (event.key === "ArrowUp")
|
|
219
|
+
stage([hsv[0], hsv[1], Math.min(100, hsv[2] + step)]);
|
|
220
|
+
else if (event.key === "ArrowDown")
|
|
221
|
+
stage([hsv[0], hsv[1], Math.max(0, hsv[2] - step)]);
|
|
222
|
+
else
|
|
223
|
+
return;
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
}, style: {
|
|
226
|
+
position: "relative",
|
|
227
|
+
width: "100%",
|
|
228
|
+
height: 130,
|
|
229
|
+
borderRadius: 8,
|
|
230
|
+
marginBottom: 10,
|
|
231
|
+
cursor: "crosshair",
|
|
232
|
+
touchAction: "none",
|
|
233
|
+
background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(${hsv[0]}, 100%, 50%))`,
|
|
234
|
+
}, children: _jsx("div", { "aria-hidden": "true", style: {
|
|
235
|
+
position: "absolute",
|
|
236
|
+
left: `${hsv[1]}%`, top: `${100 - hsv[2]}%`,
|
|
237
|
+
width: 14, height: 14, borderRadius: "50%",
|
|
238
|
+
border: "2px solid #fff", boxShadow: "0 0 0 1px rgba(0,0,0,0.4)",
|
|
239
|
+
transform: "translate(-50%, -50%)",
|
|
240
|
+
background: hex,
|
|
241
|
+
pointerEvents: "none",
|
|
242
|
+
} }) }), _jsx("div", { ref: hueDrag.ref, "data-srte-color-hue-slider": "true", role: "slider", "aria-label": "Hue", "aria-valuemin": 0, "aria-valuemax": 360, "aria-valuenow": Math.round(hsv[0]), tabIndex: 0, onMouseDown: (event) => event.preventDefault(), onPointerDown: hueDrag.onPointerDown, onKeyDown: (event) => {
|
|
243
|
+
const step = event.shiftKey ? 15 : 3;
|
|
244
|
+
if (event.key === "ArrowRight")
|
|
245
|
+
stage([Math.min(360, hsv[0] + step), hsv[1], hsv[2]]);
|
|
246
|
+
else if (event.key === "ArrowLeft")
|
|
247
|
+
stage([Math.max(0, hsv[0] - step), hsv[1], hsv[2]]);
|
|
248
|
+
else
|
|
249
|
+
return;
|
|
250
|
+
event.preventDefault();
|
|
251
|
+
}, style: {
|
|
252
|
+
position: "relative",
|
|
253
|
+
width: "100%",
|
|
254
|
+
height: 16,
|
|
255
|
+
borderRadius: 8,
|
|
256
|
+
marginBottom: 12,
|
|
257
|
+
cursor: "pointer",
|
|
258
|
+
touchAction: "none",
|
|
259
|
+
background: "linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)",
|
|
260
|
+
}, children: _jsx("div", { "aria-hidden": "true", style: {
|
|
261
|
+
position: "absolute",
|
|
262
|
+
left: `${(hsv[0] / 360) * 100}%`, top: "50%",
|
|
263
|
+
width: 16, height: 16, borderRadius: "50%",
|
|
264
|
+
border: "2px solid #fff", boxShadow: "0 0 0 1px rgba(0,0,0,0.4)",
|
|
265
|
+
transform: "translate(-50%, -50%)",
|
|
266
|
+
background: `hsl(${hsv[0]}, 100%, 50%)`,
|
|
267
|
+
pointerEvents: "none",
|
|
268
|
+
} }) }), _jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: error ? 6 : 12 }, children: [_jsx("div", { "aria-hidden": "true", style: { width: 32, height: 32, borderRadius: 8, border: "1px solid var(--srte-input-border)", background: hex, flexShrink: 0 } }), _jsx("input", { "data-srte-color-hex-input": "true", value: hexText, autoComplete: "off", autoCapitalize: "none", autoCorrect: "off", spellCheck: false, placeholder: "#336699", "aria-label": "Hex color", "aria-invalid": Boolean(error), "aria-describedby": error ? errorId : undefined, onChange: (event) => {
|
|
269
|
+
const value = event.target.value;
|
|
270
|
+
setHexText(value);
|
|
271
|
+
const normalized = normalizeHex(value);
|
|
272
|
+
if (normalized) {
|
|
273
|
+
setError("");
|
|
274
|
+
const rgb = hexToRgb(normalized);
|
|
275
|
+
stage(rgbToHsv(...rgb));
|
|
276
|
+
}
|
|
277
|
+
else if (value.length >= 4) {
|
|
278
|
+
setError("Enter a valid hex color, e.g. #336699.");
|
|
279
|
+
}
|
|
280
|
+
}, style: { ...inputStyle, borderColor: error ? "var(--srte-danger)" : "var(--srte-input-border)" } })] }), error && (_jsx("div", { id: errorId, "data-srte-color-error": "true", role: "alert", style: { color: "var(--srte-danger)", fontSize: 12, marginBottom: 10 }, children: error })), recentColors && recentColors.length > 0 && (_jsxs("div", { style: { marginBottom: 12 }, children: [_jsx("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 6 }, children: "Recently used" }), _jsx("div", { "data-srte-recent-colors": "true", style: { display: "flex", gap: 6 }, children: recentColors.slice(0, 4).map((swatch) => (_jsx("button", { type: "button", "data-srte-recent-color": swatch, "aria-label": `Recently used colour ${swatch}`, title: swatch, onClick: () => applyRecentColor(swatch), style: {
|
|
281
|
+
width: 24, height: 24, padding: 0, borderRadius: 6,
|
|
282
|
+
border: "1px solid var(--srte-input-border)",
|
|
283
|
+
background: swatch, cursor: "pointer",
|
|
284
|
+
} }, swatch))) })] })), _jsx("div", { style: { display: "flex", justifyContent: "flex-end" }, children: _jsx("button", { type: "button", "data-srte-color-discard": "true", onClick: onCancel, style: buttonStyle, children: "Discard" }) })] }));
|
|
285
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CommentThread, ModelDomMapping, PositionLookup } from "smartrte-core/foundation";
|
|
2
|
+
export interface CommentMarkersProps {
|
|
3
|
+
positions: PositionLookup;
|
|
4
|
+
mapping: ModelDomMapping;
|
|
5
|
+
containerElement: HTMLElement;
|
|
6
|
+
threads: readonly CommentThread[];
|
|
7
|
+
activeThreadId: string | null;
|
|
8
|
+
onSelectThread: (threadId: string) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Live inline markers for comment threads, following `TableResizeHandles`'
|
|
12
|
+
* precedent (Phase 12a §1.2 finding): comments need many simultaneous
|
|
13
|
+
* persistent anchors, not the single-instance overlay pattern the other
|
|
14
|
+
* popovers use, so this measures real DOM rects from the live document and
|
|
15
|
+
* re-measures via ResizeObserver/MutationObserver/scroll, exactly like that
|
|
16
|
+
* component's column/row handles do for table boundaries.
|
|
17
|
+
*
|
|
18
|
+
* A thread's range is resolved fresh on every recompute via
|
|
19
|
+
* `resolveAnnotationRange` (not cached) - it is the authoritative "is this
|
|
20
|
+
* thread still anchored to real content" check, matching every other
|
|
21
|
+
* AnnotationRange consumer. A thread whose range no longer resolves (fully
|
|
22
|
+
* orphaned - the content it anchored to is gone) renders nothing here; it
|
|
23
|
+
* still exists in `threads` for a host's thread-list UI to surface.
|
|
24
|
+
*/
|
|
25
|
+
export declare function CommentMarkers({ positions, mapping, containerElement, threads, activeThreadId, onSelectThread }: CommentMarkersProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { resolveAnnotationRange } from "smartrte-core/foundation";
|
|
4
|
+
/**
|
|
5
|
+
* Live inline markers for comment threads, following `TableResizeHandles`'
|
|
6
|
+
* precedent (Phase 12a §1.2 finding): comments need many simultaneous
|
|
7
|
+
* persistent anchors, not the single-instance overlay pattern the other
|
|
8
|
+
* popovers use, so this measures real DOM rects from the live document and
|
|
9
|
+
* re-measures via ResizeObserver/MutationObserver/scroll, exactly like that
|
|
10
|
+
* component's column/row handles do for table boundaries.
|
|
11
|
+
*
|
|
12
|
+
* A thread's range is resolved fresh on every recompute via
|
|
13
|
+
* `resolveAnnotationRange` (not cached) - it is the authoritative "is this
|
|
14
|
+
* thread still anchored to real content" check, matching every other
|
|
15
|
+
* AnnotationRange consumer. A thread whose range no longer resolves (fully
|
|
16
|
+
* orphaned - the content it anchored to is gone) renders nothing here; it
|
|
17
|
+
* still exists in `threads` for a host's thread-list UI to surface.
|
|
18
|
+
*/
|
|
19
|
+
export function CommentMarkers({ positions, mapping, containerElement, threads, activeThreadId, onSelectThread }) {
|
|
20
|
+
const [rects, setRects] = useState([]);
|
|
21
|
+
const [badges, setBadges] = useState([]);
|
|
22
|
+
const recompute = useCallback(() => {
|
|
23
|
+
if (!threads.length) {
|
|
24
|
+
setRects([]);
|
|
25
|
+
setBadges([]);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const nextRects = [];
|
|
29
|
+
const nextBadges = [];
|
|
30
|
+
threads.forEach((thread) => {
|
|
31
|
+
const range = resolveAnnotationRange(thread.range, positions);
|
|
32
|
+
if (!range)
|
|
33
|
+
return;
|
|
34
|
+
const fromDom = mapping.posToDom(range.from);
|
|
35
|
+
const toDom = mapping.posToDom(range.to);
|
|
36
|
+
if (!fromDom || !toDom)
|
|
37
|
+
return;
|
|
38
|
+
const domRange = containerElement.ownerDocument.createRange();
|
|
39
|
+
try {
|
|
40
|
+
domRange.setStart(fromDom.node, fromDom.offset);
|
|
41
|
+
domRange.setEnd(toDom.node, toDom.offset);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const clientRects = [...domRange.getClientRects()];
|
|
47
|
+
clientRects.forEach((rect) => {
|
|
48
|
+
nextRects.push({ threadId: thread.id, resolved: thread.resolved, left: rect.left, top: rect.top, width: rect.width, height: rect.height });
|
|
49
|
+
});
|
|
50
|
+
const anchor = clientRects[clientRects.length - 1] || domRange.getBoundingClientRect();
|
|
51
|
+
nextBadges.push({ threadId: thread.id, resolved: thread.resolved, replyCount: thread.replies.length, left: anchor.right, top: anchor.top });
|
|
52
|
+
});
|
|
53
|
+
setRects(nextRects);
|
|
54
|
+
setBadges(nextBadges);
|
|
55
|
+
}, [positions, mapping, containerElement, threads]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
recompute();
|
|
58
|
+
const observer = new ResizeObserver(recompute);
|
|
59
|
+
observer.observe(containerElement);
|
|
60
|
+
const mutationObserver = new MutationObserver(recompute);
|
|
61
|
+
mutationObserver.observe(containerElement, { childList: true, subtree: true, characterData: true });
|
|
62
|
+
const ownerWindow = containerElement.ownerDocument.defaultView;
|
|
63
|
+
ownerWindow?.addEventListener("scroll", recompute, true);
|
|
64
|
+
ownerWindow?.addEventListener("resize", recompute);
|
|
65
|
+
return () => {
|
|
66
|
+
observer.disconnect();
|
|
67
|
+
mutationObserver.disconnect();
|
|
68
|
+
ownerWindow?.removeEventListener("scroll", recompute, true);
|
|
69
|
+
ownerWindow?.removeEventListener("resize", recompute);
|
|
70
|
+
};
|
|
71
|
+
}, [containerElement, recompute]);
|
|
72
|
+
return (_jsxs("div", { "data-srte-comment-markers": "true", style: { position: "fixed", inset: 0, pointerEvents: "none", zIndex: 55 }, children: [rects.map((rect, index) => (_jsx("div", { "data-srte-comment-highlight": rect.threadId, onPointerDown: () => onSelectThread(rect.threadId), style: {
|
|
73
|
+
position: "fixed",
|
|
74
|
+
left: rect.left,
|
|
75
|
+
top: rect.top,
|
|
76
|
+
width: rect.width,
|
|
77
|
+
height: rect.height,
|
|
78
|
+
background: rect.resolved
|
|
79
|
+
? "var(--srte-comment-highlight-resolved, rgba(148, 148, 148, 0.18))"
|
|
80
|
+
: activeThreadId === rect.threadId
|
|
81
|
+
? "var(--srte-comment-highlight-active, rgba(245, 180, 0, 0.55))"
|
|
82
|
+
: "var(--srte-comment-highlight, rgba(245, 180, 0, 0.28))",
|
|
83
|
+
cursor: "pointer",
|
|
84
|
+
pointerEvents: "auto",
|
|
85
|
+
} }, `${rect.threadId}-${index}`))), badges.map((badge) => (_jsx("button", { type: "button", "aria-label": badge.resolved ? "Open resolved comment thread" : "Open comment thread", "data-srte-comment-badge": badge.threadId, onClick: () => onSelectThread(badge.threadId), style: {
|
|
86
|
+
position: "fixed",
|
|
87
|
+
left: badge.left + 4,
|
|
88
|
+
top: badge.top - 4,
|
|
89
|
+
minWidth: 16,
|
|
90
|
+
height: 16,
|
|
91
|
+
padding: "0 4px",
|
|
92
|
+
borderRadius: 8,
|
|
93
|
+
border: "1px solid var(--srte-border, #d0d0d0)",
|
|
94
|
+
background: badge.resolved
|
|
95
|
+
? "var(--srte-comment-badge-resolved, #e3e3e3)"
|
|
96
|
+
: activeThreadId === badge.threadId
|
|
97
|
+
? "var(--srte-primary)"
|
|
98
|
+
: "var(--srte-comment-badge, #f5b400)",
|
|
99
|
+
color: badge.resolved ? "#555" : "#1a1300",
|
|
100
|
+
fontSize: 10,
|
|
101
|
+
lineHeight: "14px",
|
|
102
|
+
cursor: "pointer",
|
|
103
|
+
pointerEvents: "auto",
|
|
104
|
+
}, children: badge.replyCount }, badge.threadId)))] }));
|
|
105
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AnnotationRange, CommentThread } from "smartrte-core/foundation";
|
|
2
|
+
export interface CommentThreadPanelProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
threads: readonly CommentThread[];
|
|
6
|
+
activeThreadId: string | null;
|
|
7
|
+
onSelectThread: (id: string) => void;
|
|
8
|
+
/** Set when "Add comment" was just clicked against a valid, non-collapsed selection - renders a compose box for a brand-new thread. */
|
|
9
|
+
pendingRange: AnnotationRange | null;
|
|
10
|
+
onCreateThread: (text: string) => void;
|
|
11
|
+
onReply: (threadId: string, text: string) => void;
|
|
12
|
+
onResolve: (threadId: string, resolved: boolean) => void;
|
|
13
|
+
onDelete: (threadId: string) => void;
|
|
14
|
+
busyThreadId: string | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Docked side panel, not a blocking modal like `VersionHistoryPanel`/
|
|
18
|
+
* `MediaManager` - a comment thread is meant to be read and replied to
|
|
19
|
+
* while continuing to edit nearby, not a full-attention dialog. Threads
|
|
20
|
+
* whose range no longer resolves aren't distinguished here with a special
|
|
21
|
+
* flag (per `CommentThread`'s own doc comment, "orphaned" is derived, not
|
|
22
|
+
* stored) - a host wanting to visually flag them can cross-reference
|
|
23
|
+
* `CommentMarkers`' own resolution, which already only renders markers for
|
|
24
|
+
* threads that still resolve.
|
|
25
|
+
*/
|
|
26
|
+
export declare function CommentThreadPanel(props: CommentThreadPanelProps): import("react/jsx-runtime").JSX.Element;
|