smartrte-react 1.0.0-beta.8 → 1.0.0-beta.9
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 +4 -0
- package/dist/components/ColorPickerPopover.js +3 -1
- package/dist/components/TableBorderPopover.js +3 -1
- package/dist/components/ToolbarPrimitives.d.ts +6 -3
- package/dist/components/ToolbarPrimitives.js +11 -5
- package/dist/components/fixedPositioning.d.ts +25 -0
- package/dist/components/fixedPositioning.js +42 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.9
|
|
4
|
+
|
|
5
|
+
- Fix toolbar dropdowns, the mobile "More tools" kebab menu, and the text/background colour and table-border popovers rendering far from their own trigger when the editor is embedded inside a host container that has its own CSS `transform` on an ancestor - e.g. a Radix/shadcn `Dialog`'s own centering transform. These overlays now correctly account for the resulting change of containing block instead of assuming it's always the viewport.
|
|
6
|
+
|
|
3
7
|
## 1.0.0-beta.8
|
|
4
8
|
|
|
5
9
|
- Add a `tools` prop (`ClassicEditor`, `CanonicalAuthorityEditor`, and the standalone embed) letting a host hide any individual toolbar tool — Bold, Video, Version history, Review, and around 40 others — without wrapping or restyling the component. Every tool is visible by default; composes with existing gates (a required provider or capability must still be present). See the README's "Hiding individual toolbar tools" section for the full list and usage.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { getFixedPositioningOrigin } from "./fixedPositioning.js";
|
|
3
4
|
const HEX_PATTERN = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
4
5
|
const normalizeHex = (value) => {
|
|
5
6
|
if (!HEX_PATTERN.test(value))
|
|
@@ -138,7 +139,8 @@ export function ColorPickerPopover({ x, y, label, initialValue = "#000000", rece
|
|
|
138
139
|
const overflowsBottom = y + height > viewportHeight - margin;
|
|
139
140
|
const top = overflowsBottom ? y - height : y;
|
|
140
141
|
const clampedTop = Math.min(Math.max(margin, top), Math.max(margin, viewportHeight - margin));
|
|
141
|
-
|
|
142
|
+
const origin = getFixedPositioningOrigin(el);
|
|
143
|
+
setPlacement({ left: clampedLeft - origin.left, top: clampedTop - origin.top });
|
|
142
144
|
}, [x, y]);
|
|
143
145
|
const stage = (nextHsv) => {
|
|
144
146
|
stagedRef.current = true;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
3
|
import { ColorPickerPopover } from "./ColorPickerPopover.js";
|
|
4
|
+
import { getFixedPositioningOrigin } from "./fixedPositioning.js";
|
|
4
5
|
// Widths start at 2px, not 1px - table_cell's border-collapse table (see
|
|
5
6
|
// theme.ts's th/td rule) already renders a 1px ambient default gridline on
|
|
6
7
|
// every cell. A custom border tied at 1px loses CSS border-collapse's
|
|
@@ -75,7 +76,8 @@ export function TableBorderPopover({ x, y, initial, recentColors, onPreview, onA
|
|
|
75
76
|
const overflowsBottom = y + height > viewportHeight - margin;
|
|
76
77
|
const top = overflowsBottom ? y - height : y;
|
|
77
78
|
const clampedTop = Math.min(Math.max(margin, top), Math.max(margin, viewportHeight - margin));
|
|
78
|
-
|
|
79
|
+
const origin = getFixedPositioningOrigin(el);
|
|
80
|
+
setPlacement({ left: clampedLeft - origin.left, top: clampedTop - origin.top });
|
|
79
81
|
}, [x, y]);
|
|
80
82
|
useEffect(() => {
|
|
81
83
|
const dismissIfOutside = (event) => {
|
|
@@ -110,9 +110,12 @@ export declare function ToolbarMenuItem({ icon, label, ariaLabel, pressed, disab
|
|
|
110
110
|
* anchored inside that container gets visually clipped the moment it needs
|
|
111
111
|
* to extend past the container's own bounds, which is exactly when a
|
|
112
112
|
* narrow host is most likely to need the dropdown in the first place.
|
|
113
|
-
* `position: fixed`'s containing block is the viewport
|
|
114
|
-
*
|
|
115
|
-
*
|
|
113
|
+
* `position: fixed`'s containing block is normally the viewport, so this
|
|
114
|
+
* escapes ANY ancestor's overflow clipping regardless of DOM depth - unless
|
|
115
|
+
* an ancestor has its own transform/perspective/filter, which becomes the
|
|
116
|
+
* containing block instead (see fixedPositioning.ts, and
|
|
117
|
+
* docs/bugs/toolbar-menu-misplaced-inside-transformed-ancestor.md); the
|
|
118
|
+
* placement math below accounts for that case too.
|
|
116
119
|
*/
|
|
117
120
|
export declare function ToolbarDropdown({ icon, label, priority, children }: {
|
|
118
121
|
icon?: ToolbarIconKey;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
3
|
import { AlignCenter, AlignJustify, AlignLeft, AlignRight, ALargeSmall, Baseline, Bold, CheckSquare, ChevronDown, Code, Columns3, Download, Eye, FileJson, History, Image as ImageIcon, IndentDecrease, IndentIncrease, Italic, Link2, ListChecks, ListOrdered, ListRestart, List as ListIcon, MessageSquare, MessageSquarePlus, MoreHorizontal, MoveDown, MoveLeft, MoveRight, MoveUp, Music, Omega, PanelTop, PenLine, Pilcrow, Quote, Redo2, Rows3, Square, Sigma, Strikethrough, Subscript, Superscript, Table2, Trash2, Type, Underline, Undo2, Unlink2, Upload, Video, Pencil, ZoomIn, ZoomOut, } from "lucide-react";
|
|
4
|
+
import { getFixedPositioningOrigin } from "./fixedPositioning.js";
|
|
4
5
|
/**
|
|
5
6
|
* The toolbar's icon set (Phase: Direction B toolbar redesign, 2026-08-29).
|
|
6
7
|
* Lucide, per the design proposal's §11 recommendation - MIT-licensed,
|
|
@@ -128,9 +129,12 @@ const useDismissDetailsOnOutsideClick = (ref) => {
|
|
|
128
129
|
* anchored inside that container gets visually clipped the moment it needs
|
|
129
130
|
* to extend past the container's own bounds, which is exactly when a
|
|
130
131
|
* narrow host is most likely to need the dropdown in the first place.
|
|
131
|
-
* `position: fixed`'s containing block is the viewport
|
|
132
|
-
*
|
|
133
|
-
*
|
|
132
|
+
* `position: fixed`'s containing block is normally the viewport, so this
|
|
133
|
+
* escapes ANY ancestor's overflow clipping regardless of DOM depth - unless
|
|
134
|
+
* an ancestor has its own transform/perspective/filter, which becomes the
|
|
135
|
+
* containing block instead (see fixedPositioning.ts, and
|
|
136
|
+
* docs/bugs/toolbar-menu-misplaced-inside-transformed-ancestor.md); the
|
|
137
|
+
* placement math below accounts for that case too.
|
|
134
138
|
*/
|
|
135
139
|
export function ToolbarDropdown({ icon, label, priority, children }) {
|
|
136
140
|
const detailsRef = useRef(null);
|
|
@@ -155,7 +159,8 @@ export function ToolbarDropdown({ icon, label, priority, children }) {
|
|
|
155
159
|
const left = Math.min(Math.max(margin, triggerRect.left), Math.max(margin, viewportWidth - menuWidth - margin));
|
|
156
160
|
const overflowsBottom = triggerRect.bottom + 6 + menuHeight > viewportHeight - margin;
|
|
157
161
|
const top = overflowsBottom ? Math.max(margin, triggerRect.top - menuHeight - 6) : triggerRect.bottom + 6;
|
|
158
|
-
|
|
162
|
+
const origin = getFixedPositioningOrigin(details);
|
|
163
|
+
setPlacement({ left: left - origin.left, top: top - origin.top });
|
|
159
164
|
};
|
|
160
165
|
recompute();
|
|
161
166
|
details.addEventListener("toggle", recompute);
|
|
@@ -218,7 +223,8 @@ export function MobileMoreMenu({ children }) {
|
|
|
218
223
|
const left = Math.min(Math.max(margin, preferredLeft), Math.max(margin, viewportWidth - menuWidth - margin));
|
|
219
224
|
const overflowsBottom = triggerRect.bottom + 6 + menuHeight > viewportHeight - margin;
|
|
220
225
|
const top = overflowsBottom ? Math.max(margin, triggerRect.top - menuHeight - 6) : triggerRect.bottom + 6;
|
|
221
|
-
|
|
226
|
+
const origin = getFixedPositioningOrigin(details);
|
|
227
|
+
setPlacement({ left: left - origin.left, top: top - origin.top });
|
|
222
228
|
};
|
|
223
229
|
recompute();
|
|
224
230
|
details.addEventListener("toggle", recompute);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CSS `transform`, `perspective`, `filter`, `backdrop-filter`, or a
|
|
3
|
+
* `will-change` naming one of those, on an ancestor makes that ancestor the
|
|
4
|
+
* containing block for `position: fixed` descendants instead of the
|
|
5
|
+
* viewport (CSS Transforms spec's rendering model) - most commonly a
|
|
6
|
+
* Radix/shadcn `Dialog`'s centering transform (`translate-x-[-50%]
|
|
7
|
+
* translate-y-[-50%]`), which any host may legitimately wrap this editor
|
|
8
|
+
* in. When that happens, `left`/`top` set on a fixed-positioned menu/popover
|
|
9
|
+
* are resolved against that ancestor's border box (as rendered,
|
|
10
|
+
* post-transform) instead of the viewport, silently producing coordinates
|
|
11
|
+
* that are internally consistent but land far from the trigger with no
|
|
12
|
+
* error - confirmed live via a Sootr host rendering the editor inside such
|
|
13
|
+
* a dialog.
|
|
14
|
+
*
|
|
15
|
+
* `getBoundingClientRect()` always returns viewport-relative coordinates
|
|
16
|
+
* regardless of transforms, so every placement calculation in this codebase
|
|
17
|
+
* computes coordinates in viewport space first, then subtracts this
|
|
18
|
+
* origin - the real containing block's own viewport-relative top-left - so
|
|
19
|
+
* the applied `left`/`top` land correctly under either containing block.
|
|
20
|
+
*/
|
|
21
|
+
export declare function findFixedPositioningContainer(el: HTMLElement): HTMLElement | null;
|
|
22
|
+
export declare function getFixedPositioningOrigin(el: HTMLElement): {
|
|
23
|
+
left: number;
|
|
24
|
+
top: number;
|
|
25
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CSS `transform`, `perspective`, `filter`, `backdrop-filter`, or a
|
|
3
|
+
* `will-change` naming one of those, on an ancestor makes that ancestor the
|
|
4
|
+
* containing block for `position: fixed` descendants instead of the
|
|
5
|
+
* viewport (CSS Transforms spec's rendering model) - most commonly a
|
|
6
|
+
* Radix/shadcn `Dialog`'s centering transform (`translate-x-[-50%]
|
|
7
|
+
* translate-y-[-50%]`), which any host may legitimately wrap this editor
|
|
8
|
+
* in. When that happens, `left`/`top` set on a fixed-positioned menu/popover
|
|
9
|
+
* are resolved against that ancestor's border box (as rendered,
|
|
10
|
+
* post-transform) instead of the viewport, silently producing coordinates
|
|
11
|
+
* that are internally consistent but land far from the trigger with no
|
|
12
|
+
* error - confirmed live via a Sootr host rendering the editor inside such
|
|
13
|
+
* a dialog.
|
|
14
|
+
*
|
|
15
|
+
* `getBoundingClientRect()` always returns viewport-relative coordinates
|
|
16
|
+
* regardless of transforms, so every placement calculation in this codebase
|
|
17
|
+
* computes coordinates in viewport space first, then subtracts this
|
|
18
|
+
* origin - the real containing block's own viewport-relative top-left - so
|
|
19
|
+
* the applied `left`/`top` land correctly under either containing block.
|
|
20
|
+
*/
|
|
21
|
+
export function findFixedPositioningContainer(el) {
|
|
22
|
+
let node = el.parentElement;
|
|
23
|
+
while (node) {
|
|
24
|
+
const style = window.getComputedStyle(node);
|
|
25
|
+
if ((style.transform && style.transform !== "none") ||
|
|
26
|
+
(style.perspective && style.perspective !== "none") ||
|
|
27
|
+
(style.filter && style.filter !== "none") ||
|
|
28
|
+
(style.backdropFilter && style.backdropFilter !== "none") ||
|
|
29
|
+
(style.willChange && /transform|perspective|filter/.test(style.willChange))) {
|
|
30
|
+
return node;
|
|
31
|
+
}
|
|
32
|
+
node = node.parentElement;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
export function getFixedPositioningOrigin(el) {
|
|
37
|
+
const container = findFixedPositioningContainer(el);
|
|
38
|
+
if (!container)
|
|
39
|
+
return { left: 0, top: 0 };
|
|
40
|
+
const rect = container.getBoundingClientRect();
|
|
41
|
+
return { left: rect.left, top: rect.top };
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smartrte-react",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.9",
|
|
4
4
|
"description": "A powerful, feature-rich Rich Text Editor for React with support for tables, mathematical formulas (LaTeX/KaTeX), and media management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|