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.
Files changed (147) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +205 -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 +65 -1
  25. package/dist/canonicalEditorRuntime.js +63 -5
  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 +56 -1
  33. package/dist/components/CanonicalAuthorityEditor.js +1304 -116
  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 +287 -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/MediaDetailsPopover.d.ts +35 -0
  49. package/dist/components/MediaDetailsPopover.js +136 -0
  50. package/dist/components/MediaManager.d.ts +4 -20
  51. package/dist/components/MediaManager.js +40 -11
  52. package/dist/components/MediaOverlay.d.ts +27 -0
  53. package/dist/components/MediaOverlay.js +228 -0
  54. package/dist/components/SpecialCharacterPopover.d.ts +18 -0
  55. package/dist/components/SpecialCharacterPopover.js +99 -0
  56. package/dist/components/StructuralSuggestionMarkers.d.ts +20 -0
  57. package/dist/components/StructuralSuggestionMarkers.js +56 -0
  58. package/dist/components/SuggestionPanel.d.ts +26 -0
  59. package/dist/components/SuggestionPanel.js +33 -0
  60. package/dist/components/TableBorderPopover.d.ts +59 -0
  61. package/dist/components/TableBorderPopover.js +149 -0
  62. package/dist/components/TableResizeHandles.d.ts +25 -0
  63. package/dist/components/TableResizeHandles.js +323 -0
  64. package/dist/components/TableSizePickerPopover.d.ts +16 -0
  65. package/dist/components/TableSizePickerPopover.js +126 -0
  66. package/dist/components/ToolbarPrimitives.d.ts +161 -0
  67. package/dist/components/ToolbarPrimitives.js +237 -0
  68. package/dist/components/VersionHistoryPanel.d.ts +9 -0
  69. package/dist/components/VersionHistoryPanel.js +157 -0
  70. package/dist/components/fixedPositioning.d.ts +25 -0
  71. package/dist/components/fixedPositioning.js +42 -0
  72. package/dist/formulaLibrary.d.ts +31 -0
  73. package/dist/formulaLibrary.js +90 -0
  74. package/dist/index.d.ts +25 -24
  75. package/dist/index.js +15 -11
  76. package/dist/mediaManagerAdapter.d.ts +12 -0
  77. package/dist/mediaManagerAdapter.js +34 -0
  78. package/dist/mediaProvider.d.ts +21 -1
  79. package/dist/specialCharacters.d.ts +22 -0
  80. package/dist/specialCharacters.js +86 -0
  81. package/dist/standalone/classic-editor-embed.js +19 -12
  82. package/dist/suggestionProvider.d.ts +17 -0
  83. package/dist/suggestionProvider.js +1 -0
  84. package/dist/theme.d.ts +1 -1
  85. package/dist/theme.js +219 -30
  86. package/dist/toolbarTools.d.ts +95 -0
  87. package/dist/toolbarTools.js +17 -0
  88. package/dist/versionProvider.d.ts +29 -0
  89. package/dist/versionProvider.js +1 -0
  90. package/package.json +28 -14
  91. package/dist/adapters/canonicalInlineCommandBridge.d.ts +0 -15
  92. package/dist/adapters/canonicalInlineCommandBridge.js +0 -269
  93. package/dist/adapters/canonicalListCommandBridge.d.ts +0 -26
  94. package/dist/adapters/canonicalListCommandBridge.js +0 -430
  95. package/dist/adapters/coreBold.d.ts +0 -5
  96. package/dist/adapters/coreBold.js +0 -3
  97. package/dist/adapters/documentFormats.d.ts +0 -26
  98. package/dist/adapters/documentFormats.js +0 -49
  99. package/dist/adapters/docxFormat.d.ts +0 -5
  100. package/dist/adapters/docxFormat.js +0 -272
  101. package/dist/adapters/domBlockCommandBridge.d.ts +0 -19
  102. package/dist/adapters/domBlockCommandBridge.js +0 -79
  103. package/dist/adapters/inlineMarkCoreExecution.d.ts +0 -9
  104. package/dist/adapters/inlineMarkCoreExecution.js +0 -25
  105. package/dist/adapters/pdfFormat.d.ts +0 -23
  106. package/dist/adapters/pdfFormat.js +0 -204
  107. package/dist/adapters/portableDocxAtoms.d.ts +0 -4
  108. package/dist/adapters/portableDocxAtoms.js +0 -58
  109. package/dist/adapters/styledDocxFormat.d.ts +0 -8
  110. package/dist/adapters/styledDocxFormat.js +0 -190
  111. package/dist/builtInFormatDefinitions.d.ts +0 -5
  112. package/dist/builtInFormatDefinitions.js +0 -82
  113. package/dist/canonicalAuthorityFlag.d.ts +0 -32
  114. package/dist/canonicalAuthorityFlag.js +0 -49
  115. package/dist/canonicalClipboardRuntime.d.ts +0 -15
  116. package/dist/canonicalClipboardRuntime.js +0 -76
  117. package/dist/components/ClassicEditor.d.ts +0 -76
  118. package/dist/components/ClassicEditor.js +0 -6729
  119. package/dist/editorController.d.ts +0 -78
  120. package/dist/editorController.js +0 -298
  121. package/dist/formatFidelity.d.ts +0 -14
  122. package/dist/formatFidelity.js +0 -107
  123. package/dist/formatRuntime.d.ts +0 -50
  124. package/dist/formatRuntime.js +0 -20
  125. package/dist/pluginRuntime.d.ts +0 -68
  126. package/dist/pluginRuntime.js +0 -78
  127. package/dist/standalone/editor.js +0 -1068
  128. package/dist/test-harness/atomShadowComparator.d.ts +0 -14
  129. package/dist/test-harness/atomShadowComparator.js +0 -63
  130. package/dist/test-harness/blockShadowComparator.d.ts +0 -30
  131. package/dist/test-harness/blockShadowComparator.js +0 -140
  132. package/dist/test-harness/clipboardShadowComparator.d.ts +0 -12
  133. package/dist/test-harness/clipboardShadowComparator.js +0 -46
  134. package/dist/test-harness/inlineShadowComparator.d.ts +0 -32
  135. package/dist/test-harness/inlineShadowComparator.js +0 -152
  136. package/dist/test-harness/legacyAtomEngine.d.ts +0 -10
  137. package/dist/test-harness/legacyAtomEngine.js +0 -15
  138. package/dist/test-harness/legacyBlockEngine.d.ts +0 -31
  139. package/dist/test-harness/legacyBlockEngine.js +0 -31
  140. package/dist/test-harness/legacyClipboardEngine.d.ts +0 -14
  141. package/dist/test-harness/legacyClipboardEngine.js +0 -67
  142. package/dist/test-harness/legacyInlineEngine.d.ts +0 -19
  143. package/dist/test-harness/legacyInlineEngine.js +0 -49
  144. package/dist/test-harness/legacyTableEngine.d.ts +0 -12
  145. package/dist/test-harness/legacyTableEngine.js +0 -25
  146. package/dist/test-harness/tableShadowComparator.d.ts +0 -29
  147. package/dist/test-harness/tableShadowComparator.js +0 -101
@@ -0,0 +1,136 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
3
+ import { normalizeLinkInput } from "smartrte-core/legacy";
4
+ const LICENSE_TYPE_PRESETS = ["CC BY", "CC BY-SA", "CC BY-ND", "CC BY-NC", "CC BY-NC-SA", "CC BY-NC-ND", "CC0 (Public Domain)", "All rights reserved"];
5
+ const inputStyle = {
6
+ width: "100%", height: 34, boxSizing: "border-box", marginTop: 4, padding: "0 9px",
7
+ border: "1px solid var(--srte-input-border)", borderRadius: 8, outline: "none",
8
+ background: "var(--srte-input-bg)", color: "var(--srte-input-text)", font: "inherit", fontSize: 13,
9
+ };
10
+ const selectStyle = { ...inputStyle, cursor: "pointer" };
11
+ const labelStyle = { display: "block", fontSize: 12, fontWeight: 600 };
12
+ const buttonStyle = {
13
+ minHeight: 34, padding: "0 12px", border: "1px solid var(--srte-input-border)", borderRadius: 8,
14
+ background: "var(--srte-input-bg)", color: "var(--srte-menu-text)", cursor: "pointer", fontWeight: 500, fontSize: 13,
15
+ };
16
+ const sectionHeaderStyle = {
17
+ fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.04em", opacity: 0.7,
18
+ marginTop: 4, paddingTop: 10, borderTop: "1px solid var(--srte-border)",
19
+ };
20
+ const alignButton = (current, value, label, onClick) => (_jsx("button", { type: "button", "aria-label": label, "aria-pressed": current === value, onClick: onClick, style: {
21
+ ...buttonStyle,
22
+ flex: 1,
23
+ minHeight: 32,
24
+ padding: 0,
25
+ background: current === value ? "var(--srte-accent-bg)" : "var(--srte-input-bg)",
26
+ borderColor: current === value ? "var(--srte-primary)" : "var(--srte-input-border)",
27
+ color: current === value ? "var(--srte-primary-pressed)" : "var(--srte-menu-text)",
28
+ }, children: label }, label));
29
+ /**
30
+ * "Media details" - replaces the crude window.prompt("Alt text") edit path
31
+ * (docs/bugs/media-details-old-editor-field-parity.md) with a real settings
32
+ * panel matching the old editor's own field set, following this project's
33
+ * established compact-popover pattern (position:fixed + JS-measured
34
+ * placement, Apply/Cancel commit - same shape as TableBorderPopover for a
35
+ * multi-field dialog like this one, rather than LinkEditorPopover's simpler
36
+ * live-Enter-to-apply form).
37
+ *
38
+ * Opened directly by an image's right-click interaction; the details panel
39
+ * is the image editing surface, while resize handles remain available
40
+ * independently around the selected image.
41
+ */
42
+ export function MediaDetailsPopover({ x, y, initial, onApply, onCancel }) {
43
+ const [draft, setDraft] = useState(initial);
44
+ const [licenseTypeCustom, setLicenseTypeCustom] = useState(!!initial.licenseType && !LICENSE_TYPE_PRESETS.includes(initial.licenseType));
45
+ const [error, setError] = useState("");
46
+ const rootRef = useRef(null);
47
+ const [placement, setPlacement] = useState(null);
48
+ const onCancelRef = useRef(onCancel);
49
+ onCancelRef.current = onCancel;
50
+ useLayoutEffect(() => {
51
+ const el = rootRef.current;
52
+ if (!el)
53
+ return;
54
+ const margin = 8;
55
+ const { width, height } = el.getBoundingClientRect();
56
+ const viewportWidth = window.innerWidth;
57
+ const viewportHeight = window.innerHeight;
58
+ const overflowsRight = x + width > viewportWidth - margin;
59
+ const left = overflowsRight ? x - width : x;
60
+ const clampedLeft = Math.min(Math.max(margin, left), Math.max(margin, viewportWidth - width - margin));
61
+ const overflowsBottom = y + height > viewportHeight - margin;
62
+ const top = overflowsBottom ? Math.max(margin, y - height) : y;
63
+ const clampedTop = Math.min(Math.max(margin, top), Math.max(margin, viewportHeight - height - margin));
64
+ setPlacement({ left: clampedLeft, top: clampedTop });
65
+ }, [x, y]);
66
+ useEffect(() => {
67
+ const dismissIfOutside = (event) => {
68
+ const target = event.target;
69
+ const elementTarget = target instanceof Element ? target : target.parentElement;
70
+ // Resize handles live outside this dialog and must receive their own
71
+ // pointer gesture without the editor closing on the first pointerdown.
72
+ if (elementTarget?.closest("[data-srte-media-resize-handle-direction]"))
73
+ return;
74
+ if (!rootRef.current?.contains(target))
75
+ onCancelRef.current();
76
+ };
77
+ window.addEventListener("pointerdown", dismissIfOutside, true);
78
+ window.addEventListener("mousedown", dismissIfOutside, true);
79
+ return () => {
80
+ window.removeEventListener("pointerdown", dismissIfOutside, true);
81
+ window.removeEventListener("mousedown", dismissIfOutside, true);
82
+ };
83
+ }, []);
84
+ useEffect(() => {
85
+ const onWindowKeyDown = (event) => { if (event.key === "Escape")
86
+ onCancelRef.current(); };
87
+ window.addEventListener("keydown", onWindowKeyDown);
88
+ return () => window.removeEventListener("keydown", onWindowKeyDown);
89
+ }, []);
90
+ const update = (patch) => setDraft((current) => ({ ...current, ...patch }));
91
+ const apply = () => {
92
+ if (!draft.alt.trim()) {
93
+ setError("Enter alt text.");
94
+ return;
95
+ }
96
+ const trimmedHref = draft.href.trim();
97
+ if (!trimmedHref) {
98
+ onApply({ ...draft, href: "" });
99
+ return;
100
+ }
101
+ const normalized = normalizeLinkInput(trimmedHref);
102
+ if (!normalized.href) {
103
+ setError("Enter a safe, valid web URL, email address, phone number, or page anchor for the link, or clear it.");
104
+ return;
105
+ }
106
+ onApply({ ...draft, href: normalized.href });
107
+ };
108
+ return (_jsxs("div", { ref: rootRef, "data-srte-media-details-popover": "true", role: "dialog", "aria-label": "Media details", style: {
109
+ position: "fixed",
110
+ left: placement?.left ?? x,
111
+ top: placement?.top ?? y,
112
+ visibility: placement ? "visible" : "hidden",
113
+ zIndex: 72,
114
+ width: 320,
115
+ maxWidth: "calc(100vw - 16px)",
116
+ maxHeight: "calc(100vh - 16px)",
117
+ overflowY: "auto",
118
+ boxSizing: "border-box",
119
+ background: "var(--srte-menu-bg)",
120
+ color: "var(--srte-menu-text)",
121
+ border: "1px solid var(--srte-border)",
122
+ borderRadius: 12,
123
+ boxShadow: "var(--srte-menu-shadow)",
124
+ padding: 14,
125
+ display: "flex",
126
+ flexDirection: "column",
127
+ gap: 10,
128
+ }, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [_jsx("div", { style: { fontWeight: 650 }, children: "Media details" }), _jsx("button", { type: "button", "aria-label": "Close media details", onClick: onCancel, style: { ...buttonStyle, minWidth: 26, minHeight: 26, padding: 0, border: 0, background: "transparent", fontSize: 18 }, children: "\u00D7" })] }), _jsxs("label", { style: labelStyle, children: ["Alt text", _jsx("input", { "data-srte-media-alt-input": "true", value: draft.alt, onChange: (event) => { update({ alt: event.target.value }); setError(""); }, style: { ...inputStyle, borderColor: error ? "var(--srte-danger)" : "var(--srte-input-border)" } })] }), error && _jsx("div", { role: "alert", style: { color: "var(--srte-danger)", fontSize: 12 }, children: error }), _jsxs("label", { style: labelStyle, children: ["Link", _jsx("input", { "data-srte-media-href-input": "true", value: draft.href, placeholder: "Paste a URL", inputMode: "url", autoCapitalize: "none", autoCorrect: "off", spellCheck: false, onChange: (event) => { update({ href: event.target.value }); setError(""); }, style: { ...inputStyle, borderColor: error ? "var(--srte-danger)" : "var(--srte-input-border)" } })] }), _jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 13, cursor: "pointer" }, children: [_jsx("input", { "data-srte-media-new-tab-input": "true", type: "checkbox", checked: draft.openInNewTab, disabled: !draft.href.trim(), onChange: (event) => update({ openInNewTab: event.target.checked }) }), "Open in a new tab"] }), _jsxs("div", { style: { display: "flex", gap: 8 }, children: [_jsxs("label", { style: { ...labelStyle, flex: 1 }, children: ["Width (px)", _jsx("input", { "data-srte-media-width-input": "true", type: "number", min: 16, value: draft.width ?? "", onChange: (event) => update({ width: event.target.value === "" ? undefined : Math.max(16, Number(event.target.value)) }), style: inputStyle })] }), _jsxs("label", { style: { ...labelStyle, flex: 1 }, children: ["Corner radius (px)", _jsx("input", { "data-srte-media-radius-input": "true", type: "number", min: 0, value: draft.borderRadius ?? "", onChange: (event) => update({ borderRadius: event.target.value === "" ? undefined : Math.max(0, Number(event.target.value)) }), style: inputStyle })] })] }), _jsxs("div", { style: labelStyle, children: ["Align", _jsxs("div", { style: { display: "flex", gap: 6, marginTop: 4 }, children: [alignButton(draft.align, undefined, "None", () => update({ align: undefined })), alignButton(draft.align, "left", "Left", () => update({ align: "left" })), alignButton(draft.align, "center", "Center", () => update({ align: "center" })), alignButton(draft.align, "right", "Right", () => update({ align: "right" }))] })] }), _jsx("div", { style: sectionHeaderStyle, children: "License" }), _jsxs("label", { style: labelStyle, children: ["Description", _jsx("input", { "data-srte-media-license-description-input": "true", value: draft.licenseDescription, onChange: (event) => update({ licenseDescription: event.target.value }), style: inputStyle })] }), _jsxs("label", { style: labelStyle, children: ["Source URL", _jsx("input", { "data-srte-media-license-source-input": "true", value: draft.licenseSourceUrl, onChange: (event) => update({ licenseSourceUrl: event.target.value }), style: inputStyle })] }), _jsxs("label", { style: labelStyle, children: ["Type", _jsxs("select", { "data-srte-media-license-type-select": "true", value: licenseTypeCustom ? "custom" : draft.licenseType, onChange: (event) => {
129
+ if (event.target.value === "custom") {
130
+ setLicenseTypeCustom(true);
131
+ return;
132
+ }
133
+ setLicenseTypeCustom(false);
134
+ update({ licenseType: event.target.value });
135
+ }, style: selectStyle, children: [_jsx("option", { value: "", children: "(none)" }), LICENSE_TYPE_PRESETS.map((preset) => _jsx("option", { value: preset, children: preset }, preset)), _jsx("option", { value: "custom", children: "Custom\u2026" })] }), licenseTypeCustom && (_jsx("input", { "data-srte-media-license-type-custom-input": "true", value: draft.licenseType, placeholder: "License type", onChange: (event) => update({ licenseType: event.target.value }), style: inputStyle }))] }), _jsxs("label", { style: labelStyle, children: ["Version", _jsx("input", { "data-srte-media-license-version-input": "true", value: draft.licenseVersion, onChange: (event) => update({ licenseVersion: event.target.value }), style: inputStyle })] }), _jsxs("label", { style: labelStyle, children: ["Attribution", _jsx("input", { "data-srte-media-license-attribution-input": "true", value: draft.licenseAttribution, onChange: (event) => update({ licenseAttribution: event.target.value }), style: inputStyle })] }), _jsxs("div", { style: { display: "flex", justifyContent: "flex-end", gap: 7, marginTop: 4 }, children: [_jsx("button", { type: "button", onClick: onCancel, style: buttonStyle, children: "Cancel" }), _jsx("button", { type: "button", onClick: apply, style: { ...buttonStyle, borderColor: "var(--srte-primary)", background: "var(--srte-primary)", color: "var(--srte-on-primary)" }, children: "Apply" })] })] }));
136
+ }
@@ -1,23 +1,5 @@
1
- export type MediaItem = {
2
- id: string;
3
- url: string;
4
- width?: number;
5
- height?: number;
6
- sizeBytes?: number;
7
- mimeType?: string;
8
- hashHex?: string;
9
- createdAt?: string;
10
- title?: string;
11
- alt?: string;
12
- tags?: string[];
13
- license?: {
14
- author?: string;
15
- licenseType?: string;
16
- licenseText?: string;
17
- sourceUrl?: string;
18
- workName?: string;
19
- };
20
- };
1
+ import type { MediaItem } from "../mediaProvider.js";
2
+ export type { MediaItem } from "../mediaProvider.js";
21
3
  export type MediaSearchQuery = {
22
4
  q?: string;
23
5
  tags?: string[];
@@ -29,6 +11,8 @@ export type MediaSearchQuery = {
29
11
  export type MediaManagerAdapter = {
30
12
  upload: (files: File[]) => Promise<MediaItem[]>;
31
13
  search: (query: MediaSearchQuery) => Promise<MediaItem[]>;
14
+ /** Optional - MediaProvider.remove exists but nothing here called it before Phase 11.5 §2.1. */
15
+ remove?: (id: string) => Promise<void>;
32
16
  };
33
17
  export declare function MediaManager(props: {
34
18
  open: boolean;
@@ -8,6 +8,7 @@ export function MediaManager(props) {
8
8
  const [query, setQuery] = useState("");
9
9
  const [results, setResults] = useState([]);
10
10
  const [infoItem, setInfoItem] = useState(null);
11
+ const [removing, setRemoving] = useState(false);
11
12
  const fileInputRef = useRef(null);
12
13
  useEffect(() => {
13
14
  if (!open)
@@ -31,6 +32,24 @@ export function MediaManager(props) {
31
32
  setError("Failed to search media.");
32
33
  }
33
34
  };
35
+ /** Phase 11.5 §2.1: MediaProvider.remove already existed; nothing here ever called it. */
36
+ const handleRemove = async (item) => {
37
+ if (!adapter.remove)
38
+ return;
39
+ setRemoving(true);
40
+ setError(null);
41
+ try {
42
+ await adapter.remove(item.id);
43
+ setResults((current) => current.filter((entry) => entry.id !== item.id));
44
+ setInfoItem(null);
45
+ }
46
+ catch (e) {
47
+ setError("Failed to delete media.");
48
+ }
49
+ finally {
50
+ setRemoving(false);
51
+ }
52
+ };
34
53
  const computeSha256Hex = async (file) => {
35
54
  const buf = await file.arrayBuffer();
36
55
  const digest = await crypto.subtle.digest("SHA-256", buf);
@@ -99,7 +118,7 @@ export function MediaManager(props) {
99
118
  alignItems: "center",
100
119
  justifyContent: "center",
101
120
  zIndex: 80,
102
- }, onClick: onClose, children: _jsxs("div", { style: {
121
+ }, onClick: onClose, children: _jsxs("div", { role: "dialog", "aria-label": "Media library", "data-srte-media-manager": "true", style: {
103
122
  background: "var(--srte-modal-bg)",
104
123
  color: "var(--srte-modal-text)",
105
124
  width: 820,
@@ -115,19 +134,19 @@ export function MediaManager(props) {
115
134
  justifyContent: "space-between",
116
135
  padding: "10px 14px",
117
136
  borderBottom: "1px solid var(--srte-border-light)",
118
- }, children: [_jsxs("div", { style: { display: "flex", gap: 8 }, children: [_jsx("button", { onClick: () => setActiveTab("upload"), style: {
137
+ }, children: [_jsxs("div", { style: { display: "flex", gap: 8 }, children: [_jsx("button", { type: "button", onClick: () => setActiveTab("upload"), style: {
119
138
  padding: "6px 10px",
120
139
  borderRadius: 6,
121
140
  border: "1px solid var(--srte-border)",
122
- background: activeTab === "upload" ? "var(--srte-surface-subtle)" : "var(--srte-input-bg)",
141
+ background: activeTab === "upload" ? "var(--srte-muted)" : "var(--srte-input-bg)",
123
142
  color: "var(--srte-input-text)",
124
- }, children: "Upload" }), _jsx("button", { onClick: () => setActiveTab("library"), style: {
143
+ }, children: "Upload" }), _jsx("button", { type: "button", onClick: () => setActiveTab("library"), style: {
125
144
  padding: "6px 10px",
126
145
  borderRadius: 6,
127
146
  border: "1px solid var(--srte-border)",
128
- background: activeTab === "library" ? "var(--srte-surface-subtle)" : "var(--srte-input-bg)",
147
+ background: activeTab === "library" ? "var(--srte-muted)" : "var(--srte-input-bg)",
129
148
  color: "var(--srte-input-text)",
130
- }, children: "Library" })] }), _jsx("button", { onClick: onClose, children: "\u2715" })] }), error && (_jsx("div", { style: { color: "var(--srte-danger)", padding: "8px 14px" }, children: error })), activeTab === "upload" ? (_jsxs("div", { style: { padding: 16 }, children: [_jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", multiple: true, style: { display: "none" }, onChange: (e) => handleUploadFiles(e.currentTarget.files) }), _jsx("div", { onClick: () => fileInputRef.current?.click(), onDragOver: (e) => {
149
+ }, children: "Library" })] }), _jsx("button", { type: "button", "aria-label": "Close media library", onClick: onClose, children: "\u2715" })] }), error && (_jsx("div", { style: { color: "var(--srte-danger)", padding: "8px 14px" }, children: error })), activeTab === "upload" ? (_jsxs("div", { style: { padding: 16 }, children: [_jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", multiple: true, style: { display: "none" }, onChange: (e) => handleUploadFiles(e.currentTarget.files) }), _jsx("div", { onClick: () => fileInputRef.current?.click(), onDragOver: (e) => {
131
150
  e.preventDefault();
132
151
  }, onDrop: (e) => {
133
152
  e.preventDefault();
@@ -138,7 +157,7 @@ export function MediaManager(props) {
138
157
  padding: 24,
139
158
  textAlign: "center",
140
159
  color: "var(--srte-text-muted)",
141
- background: "var(--srte-surface-subtle)",
160
+ background: "var(--srte-muted)",
142
161
  cursor: uploading ? "default" : "pointer",
143
162
  opacity: uploading ? 0.7 : 1,
144
163
  }, children: uploading ? "Uploading…" : "Click or drag images to upload" })] })) : (_jsxs("div", { style: {
@@ -153,7 +172,7 @@ export function MediaManager(props) {
153
172
  borderRadius: 6,
154
173
  background: "var(--srte-input-bg)",
155
174
  color: "var(--srte-input-text)",
156
- } }), _jsx("button", { onClick: performSearch, children: "Search" })] }), _jsx("div", { style: {
175
+ } }), _jsx("button", { type: "button", onClick: performSearch, children: "Search" })] }), _jsx("div", { style: {
157
176
  display: "grid",
158
177
  gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))",
159
178
  gap: 12,
@@ -188,7 +207,7 @@ export function MediaManager(props) {
188
207
  height: 24,
189
208
  border: "1px solid var(--srte-border)",
190
209
  borderRadius: 999,
191
- background: "var(--srte-surface-subtle)",
210
+ background: "var(--srte-muted)",
192
211
  color: "var(--srte-input-text)",
193
212
  cursor: "pointer",
194
213
  flex: "0 0 auto",
@@ -196,7 +215,7 @@ export function MediaManager(props) {
196
215
  fontSize: 10,
197
216
  padding: "1px 5px",
198
217
  borderRadius: 999,
199
- background: "var(--srte-surface-subtle)",
218
+ background: "var(--srte-muted)",
200
219
  color: "var(--srte-text-muted)",
201
220
  }, children: tag }, tag))) }))] }, it.id || it.url))) })] })), infoItem && (_jsx("div", { style: {
202
221
  position: "fixed",
@@ -223,10 +242,20 @@ export function MediaManager(props) {
223
242
  ["MIME type", infoItem.mimeType],
224
243
  ["Size", infoItem.sizeBytes ? `${Math.round(infoItem.sizeBytes / 1024)} KB` : undefined],
225
244
  ["Created", infoItem.createdAt],
245
+ ["Used", infoItem.usageCount !== undefined ? `${infoItem.usageCount} time${infoItem.usageCount === 1 ? "" : "s"}` : undefined],
226
246
  ["Tags", infoItem.tags?.join(", ")],
227
247
  ["Work", infoItem.license?.workName],
228
248
  ["Author", infoItem.license?.author],
229
249
  ["License", [infoItem.license?.licenseType, infoItem.license?.licenseText].filter(Boolean).join(" - ")],
230
250
  ["Source", infoItem.license?.sourceUrl],
231
- ].filter(([, value]) => value).map(([label, value]) => (_jsxs("div", { style: { display: "grid", gridTemplateColumns: "92px 1fr", gap: 8, fontSize: 12, marginBottom: 6 }, children: [_jsx("div", { style: { color: "var(--srte-text-muted)" }, children: label }), _jsx("div", { style: { overflowWrap: "anywhere" }, children: value })] }, label)))] }) }))] }) }));
251
+ ].filter(([, value]) => value).map(([label, value]) => (_jsxs("div", { style: { display: "grid", gridTemplateColumns: "92px 1fr", gap: 8, fontSize: 12, marginBottom: 6 }, children: [_jsx("div", { style: { color: "var(--srte-text-muted)" }, children: label }), _jsx("div", { style: { overflowWrap: "anywhere" }, children: value })] }, label))), adapter.remove && (_jsx("button", { type: "button", "data-srte-media-delete": "true", disabled: removing, onClick: () => void handleRemove(infoItem), style: {
252
+ marginTop: 8,
253
+ padding: "6px 12px",
254
+ borderRadius: 6,
255
+ border: "1px solid var(--srte-danger)",
256
+ background: "transparent",
257
+ color: "var(--srte-danger)",
258
+ cursor: removing ? "default" : "pointer",
259
+ opacity: removing ? 0.6 : 1,
260
+ }, children: removing ? "Deleting…" : "Delete" }))] }) }))] }) }));
232
261
  }
@@ -0,0 +1,27 @@
1
+ export interface MediaOverlayProps {
2
+ atomElement: HTMLElement;
3
+ alt: string;
4
+ width?: number;
5
+ height?: number;
6
+ src?: string;
7
+ x?: number;
8
+ y?: number;
9
+ /** Render only the handles when the media details editor owns the interaction surface. */
10
+ showMenu?: boolean;
11
+ /** False for atoms with no width/height concept (e.g. formula, sized by KaTeX from source/font-size) - hides resize controls and handles, keeping Edit/Delete. */
12
+ resizable?: boolean;
13
+ /** False for atoms with no editable fields at all (divider, page break) - hides the Alt/Size/Source info block and the Edit button, keeping only Delete. */
14
+ editable?: boolean;
15
+ onEdit: () => void;
16
+ onResize: (by: number) => void;
17
+ onResizeTo: (width: number, height: number) => void;
18
+ onDelete: () => void;
19
+ onDismiss: () => void;
20
+ }
21
+ /**
22
+ * Media resize handles stay anchored to the selected media element and
23
+ * preview size changes live, committing one canonical resize operation when
24
+ * the pointer is released. The quick-action menu is optional because image
25
+ * right-clicks use MediaDetailsPopover directly.
26
+ */
27
+ export declare function MediaOverlay({ atomElement, alt, width, height, src, x, y, showMenu, resizable, editable, onEdit, onResize, onResizeTo, onDelete, onDismiss }: MediaOverlayProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,228 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
3
+ const RESIZE_DIRECTIONS = ["n", "ne", "e", "se", "s", "sw", "w", "nw"];
4
+ const HANDLE_SIZE = 10;
5
+ const MIN_ATOM_SIZE = 24;
6
+ const buttonStyle = {
7
+ minHeight: 32,
8
+ padding: "0 10px",
9
+ border: "1px solid var(--srte-input-border)",
10
+ borderRadius: 8,
11
+ background: "var(--srte-input-bg)",
12
+ color: "var(--srte-menu-text)",
13
+ cursor: "pointer",
14
+ fontWeight: 500,
15
+ fontSize: 13,
16
+ };
17
+ const cursorFor = (direction) => {
18
+ if (direction === "n" || direction === "s")
19
+ return "ns-resize";
20
+ if (direction === "e" || direction === "w")
21
+ return "ew-resize";
22
+ return direction === "ne" || direction === "sw" ? "nesw-resize" : "nwse-resize";
23
+ };
24
+ const isCorner = (direction) => direction.length === 2;
25
+ const visibleBoundsFor = (atomElement, modelWidth, modelHeight) => {
26
+ const rect = atomElement.getBoundingClientRect();
27
+ const editorElement = atomElement.closest(".srte-editor");
28
+ const editorRect = editorElement?.getBoundingClientRect();
29
+ const inset = HANDLE_SIZE / 2;
30
+ // Broken or still-loading images can expose only a tiny fallback box.
31
+ // Use the canonical dimensions for that case, but use the real rendered
32
+ // dimensions whenever the image has a meaningful layout box.
33
+ const imageWidth = rect.width >= MIN_ATOM_SIZE ? rect.width : modelWidth && modelWidth >= MIN_ATOM_SIZE ? modelWidth : rect.width;
34
+ const imageHeight = rect.height >= MIN_ATOM_SIZE ? rect.height : modelHeight && modelHeight >= MIN_ATOM_SIZE ? modelHeight : rect.height;
35
+ const left = Math.max(rect.left, editorRect ? editorRect.left + inset : rect.left);
36
+ const top = Math.max(rect.top, editorRect ? editorRect.top + inset : rect.top);
37
+ const right = Math.min(rect.left + imageWidth, editorRect ? editorRect.right - inset : rect.left + imageWidth);
38
+ const bottom = Math.min(rect.top + imageHeight, editorRect ? editorRect.bottom - inset : rect.top + imageHeight);
39
+ if (right <= left || bottom <= top)
40
+ return null;
41
+ return { left, top, width: right - left, height: bottom - top };
42
+ };
43
+ const dimensionsForDrag = (drag, clientX, clientY) => {
44
+ const deltaX = clientX - drag.startClientX;
45
+ const deltaY = clientY - drag.startClientY;
46
+ const widthDelta = drag.direction.includes("e") ? deltaX : drag.direction.includes("w") ? -deltaX : 0;
47
+ const heightDelta = drag.direction.includes("s") ? deltaY : drag.direction.includes("n") ? -deltaY : 0;
48
+ if (!isCorner(drag.direction)) {
49
+ return {
50
+ width: Math.max(MIN_ATOM_SIZE, Math.round(drag.startWidth + widthDelta)),
51
+ height: Math.max(MIN_ATOM_SIZE, Math.round(drag.startHeight + heightDelta)),
52
+ };
53
+ }
54
+ // Corner handles keep the same aspect ratio as the current image. Use the
55
+ // axis with the larger proportional movement so dragging either diagonal
56
+ // direction feels natural, including when one pointer axis barely moves.
57
+ const widthScale = (drag.startWidth + widthDelta) / drag.startWidth;
58
+ const heightScale = (drag.startHeight + heightDelta) / drag.startHeight;
59
+ const scale = Math.max(MIN_ATOM_SIZE / drag.startWidth, MIN_ATOM_SIZE / drag.startHeight, Math.abs(widthDelta / drag.startWidth) >= Math.abs(heightDelta / drag.startHeight) ? widthScale : heightScale);
60
+ return {
61
+ width: Math.max(MIN_ATOM_SIZE, Math.round(drag.startWidth * scale)),
62
+ height: Math.max(MIN_ATOM_SIZE, Math.round(drag.startHeight * scale)),
63
+ };
64
+ };
65
+ const boundsForDirection = (bounds, direction) => {
66
+ const half = HANDLE_SIZE / 2;
67
+ const centerX = bounds.left + bounds.width / 2 - half;
68
+ const centerY = bounds.top + bounds.height / 2 - half;
69
+ const right = bounds.left + bounds.width - half;
70
+ const bottom = bounds.top + bounds.height - half;
71
+ const left = direction.includes("w") ? bounds.left - half : direction.includes("e") ? right : centerX;
72
+ const top = direction.includes("n") ? bounds.top - half : direction.includes("s") ? bottom : centerY;
73
+ return { left, top };
74
+ };
75
+ /**
76
+ * Media resize handles stay anchored to the selected media element and
77
+ * preview size changes live, committing one canonical resize operation when
78
+ * the pointer is released. The quick-action menu is optional because image
79
+ * right-clicks use MediaDetailsPopover directly.
80
+ */
81
+ export function MediaOverlay({ atomElement, alt, width, height, src, x, y, showMenu = true, resizable = true, editable = true, onEdit, onResize, onResizeTo, onDelete, onDismiss }) {
82
+ const rootRef = useRef(null);
83
+ const dragRef = useRef(null);
84
+ const [placement, setPlacement] = useState(null);
85
+ const [bounds, setBounds] = useState(null);
86
+ const [dragging, setDragging] = useState(false);
87
+ useLayoutEffect(() => {
88
+ const recompute = () => {
89
+ const rect = atomElement.getBoundingClientRect();
90
+ const margin = 8;
91
+ const viewportWidth = atomElement.ownerDocument.defaultView?.innerWidth ?? 0;
92
+ const viewportHeight = atomElement.ownerDocument.defaultView?.innerHeight ?? 0;
93
+ const panelWidth = rootRef.current?.getBoundingClientRect().width ?? 260;
94
+ const panelHeight = rootRef.current?.getBoundingClientRect().height ?? 160;
95
+ const preferredLeft = x ?? rect.left;
96
+ const preferredTop = y ?? rect.bottom + 6;
97
+ const left = preferredLeft + panelWidth > viewportWidth - margin ? preferredLeft - panelWidth : preferredLeft;
98
+ const top = preferredTop + panelHeight > viewportHeight - margin ? preferredTop - panelHeight : preferredTop;
99
+ setPlacement({
100
+ left: Math.min(Math.max(margin, left), Math.max(margin, viewportWidth - panelWidth - margin)),
101
+ top: Math.min(Math.max(margin, top), Math.max(margin, viewportHeight - panelHeight - margin)),
102
+ });
103
+ // Handles are fixed siblings of the contenteditable surface, so they
104
+ // do not inherit the editor's overflow clipping. Use the visible
105
+ // intersection with the editor viewport to keep every handle inside
106
+ // that boundary when a tall image extends below the scroll area.
107
+ setBounds(visibleBoundsFor(atomElement, width, height));
108
+ };
109
+ recompute();
110
+ const observer = new ResizeObserver(recompute);
111
+ observer.observe(atomElement);
112
+ const ownerWindow = atomElement.ownerDocument.defaultView;
113
+ ownerWindow?.addEventListener("scroll", recompute, true);
114
+ ownerWindow?.addEventListener("resize", recompute);
115
+ return () => {
116
+ observer.disconnect();
117
+ ownerWindow?.removeEventListener("scroll", recompute, true);
118
+ ownerWindow?.removeEventListener("resize", recompute);
119
+ };
120
+ }, [atomElement, height, width, x, y]);
121
+ useEffect(() => {
122
+ if (showMenu && placement)
123
+ rootRef.current?.focus();
124
+ }, [placement, showMenu]);
125
+ useEffect(() => {
126
+ const ownerWindow = atomElement.ownerDocument.defaultView;
127
+ if (!ownerWindow)
128
+ return;
129
+ const handleMove = (event) => {
130
+ const drag = dragRef.current;
131
+ if (!drag)
132
+ return;
133
+ const dimensions = dimensionsForDrag(drag, event.clientX, event.clientY);
134
+ atomElement.style.width = `${dimensions.width}px`;
135
+ atomElement.style.height = `${dimensions.height}px`;
136
+ setBounds(visibleBoundsFor(atomElement));
137
+ };
138
+ const handleUp = (event) => {
139
+ const drag = dragRef.current;
140
+ if (!drag)
141
+ return;
142
+ const dimensions = dimensionsForDrag(drag, event.clientX, event.clientY);
143
+ dragRef.current = null;
144
+ setDragging(false);
145
+ // The model transaction below becomes authoritative. Clear the live
146
+ // preview first so a stale inline style cannot override later updates.
147
+ atomElement.style.removeProperty("width");
148
+ atomElement.style.removeProperty("height");
149
+ onResizeTo(dimensions.width, dimensions.height);
150
+ };
151
+ ownerWindow.addEventListener("pointermove", handleMove);
152
+ ownerWindow.addEventListener("pointerup", handleUp);
153
+ ownerWindow.addEventListener("pointercancel", handleUp);
154
+ return () => {
155
+ ownerWindow.removeEventListener("pointermove", handleMove);
156
+ ownerWindow.removeEventListener("pointerup", handleUp);
157
+ ownerWindow.removeEventListener("pointercancel", handleUp);
158
+ };
159
+ }, [atomElement, onResizeTo]);
160
+ const onDismissRef = useRef(onDismiss);
161
+ onDismissRef.current = onDismiss;
162
+ useEffect(() => {
163
+ if (!showMenu)
164
+ return;
165
+ const dismissIfOutside = (event) => {
166
+ const target = event.target;
167
+ const elementTarget = target instanceof Element ? target : target.parentElement;
168
+ const isResizeHandle = Boolean(elementTarget?.closest("[data-srte-media-resize-handle-direction]"));
169
+ if (dragRef.current)
170
+ return;
171
+ if (rootRef.current && !rootRef.current.contains(target) && !atomElement.contains(target) && !isResizeHandle)
172
+ onDismissRef.current();
173
+ };
174
+ window.addEventListener("pointerdown", dismissIfOutside, true);
175
+ window.addEventListener("mousedown", dismissIfOutside, true);
176
+ return () => {
177
+ window.removeEventListener("pointerdown", dismissIfOutside, true);
178
+ window.removeEventListener("mousedown", dismissIfOutside, true);
179
+ };
180
+ }, [atomElement, showMenu]);
181
+ return (_jsxs(_Fragment, { children: [resizable && bounds && RESIZE_DIRECTIONS.map((direction) => (_jsx("div", { "data-srte-media-resize-handle": "true", "data-srte-media-resize-handle-direction": direction, role: "separator", "aria-label": `Resize media ${direction}`, onPointerDown: (event) => {
182
+ event.preventDefault();
183
+ event.stopPropagation();
184
+ const startWidth = width && width >= MIN_ATOM_SIZE ? width : bounds.width;
185
+ const startHeight = height && height >= MIN_ATOM_SIZE ? height : bounds.height;
186
+ dragRef.current = {
187
+ direction,
188
+ startClientX: event.clientX,
189
+ startClientY: event.clientY,
190
+ startWidth,
191
+ startHeight,
192
+ };
193
+ setDragging(true);
194
+ }, style: {
195
+ ...boundsForDirection(bounds, direction),
196
+ position: "fixed",
197
+ visibility: "visible",
198
+ width: HANDLE_SIZE,
199
+ height: HANDLE_SIZE,
200
+ boxSizing: "border-box",
201
+ borderRadius: 3,
202
+ background: dragging ? "var(--srte-primary)" : "var(--srte-menu-bg)",
203
+ border: "2px solid var(--srte-primary)",
204
+ cursor: cursorFor(direction),
205
+ zIndex: 81,
206
+ touchAction: "none",
207
+ } }, direction))), showMenu && _jsxs("div", { ref: rootRef, "data-srte-media-overlay": "true", role: "dialog", "aria-label": "Media actions", tabIndex: -1, style: {
208
+ position: "fixed",
209
+ left: placement?.left ?? x ?? 0,
210
+ top: placement?.top ?? y ?? 0,
211
+ visibility: placement ? "visible" : "hidden",
212
+ zIndex: 80,
213
+ width: 260,
214
+ maxWidth: "calc(100vw - 16px)",
215
+ boxSizing: "border-box",
216
+ background: "var(--srte-menu-bg)",
217
+ color: "var(--srte-menu-text)",
218
+ border: "1px solid var(--srte-border)",
219
+ borderRadius: 10,
220
+ boxShadow: "var(--srte-menu-shadow)",
221
+ padding: 10,
222
+ }, onKeyDown: (event) => {
223
+ if (event.key === "Escape") {
224
+ event.preventDefault();
225
+ onDismiss();
226
+ }
227
+ }, children: [editable && _jsxs("div", { style: { fontSize: 12, marginBottom: 8, display: "grid", gap: 2, opacity: 0.85 }, children: [_jsxs("div", { children: [_jsx("strong", { children: "Alt text:" }), " ", alt || "(none)"] }), width && height && _jsxs("div", { children: [_jsx("strong", { children: "Size:" }), " ", Math.round(width), "\u00D7", Math.round(height)] }), src && _jsxs("div", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [_jsx("strong", { children: "Source:" }), " ", src] })] }), _jsxs("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: [editable && _jsx("button", { type: "button", style: buttonStyle, onClick: onEdit, children: "Edit" }), resizable && _jsx("button", { type: "button", style: buttonStyle, onClick: () => onResize(20), children: "Resize +" }), resizable && _jsx("button", { type: "button", style: buttonStyle, onClick: () => onResize(-20), children: "Resize \u03B2\u02C6\u2019" }), _jsx("button", { type: "button", style: { ...buttonStyle, color: "var(--srte-danger)", marginLeft: "auto" }, onClick: onDelete, children: "Delete" })] })] })] }));
228
+ }
@@ -0,0 +1,18 @@
1
+ export interface SpecialCharacterPopoverProps {
2
+ x: number;
3
+ y: number;
4
+ /** Up to 8 previously-inserted characters, most-recent first - same recency pattern as ColorPickerPopover's recent colors. */
5
+ recentCharacters?: readonly string[];
6
+ onInsert: (char: string) => void;
7
+ onCancel: () => void;
8
+ }
9
+ /**
10
+ * Special character picker: six standardized-Unicode-block categories
11
+ * (~200 characters total - see specialCharacters.ts's own doc comment),
12
+ * reusing the grid-with-hover-feedback shape already established by
13
+ * ColorPickerPopover/TableSizePickerPopover rather than a new component
14
+ * pattern. Insertion is immediate on click (a character, unlike a dragged
15
+ * color, is already a single complete choice) via a real, page-owned text
16
+ * insert - see `insertSpecialCharacter` in CanonicalAuthorityEditor.tsx.
17
+ */
18
+ export declare function SpecialCharacterPopover({ x, y, recentCharacters, onInsert, onCancel }: SpecialCharacterPopoverProps): import("react/jsx-runtime").JSX.Element;