@jini-ai/ui 0.3.9 → 0.3.11
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/dist/browser/useFileDropTarget.js +1 -1
- package/dist/features/execution/react/components/ByokProviderForm.js +3 -3
- package/dist/features/html-editor/canvas-embed-placeholders.d.ts +4 -0
- package/dist/features/html-editor/canvas-embed-placeholders.js +51 -1
- package/dist/features/html-editor/canvas-style.d.ts +11 -0
- package/dist/features/html-editor/canvas-style.js +16 -1
- package/dist/features/html-editor/index.d.ts +1 -1
- package/dist/features/html-editor/react/components/InteractiveHtmlEditor.d.ts +23 -11
- package/dist/features/html-editor/react/components/InteractiveHtmlEditor.js +5 -3
- package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.d.ts +11 -1
- package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +356 -30
- package/dist/features/html-editor/source-splice.d.ts +56 -0
- package/dist/features/html-editor/source-splice.js +89 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ar.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.bn.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.de.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.en.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.es.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.fa.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.fr.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.hi.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.hu.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.id.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.it.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ja.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ko.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.pl.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.pt-BR.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ru.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.th.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.tr.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.uk.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ur.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +2 -0
- package/dist/features/mcp-ui/surfaces/bridge.js +1 -0
- package/dist/features/mcp-ui/surfaces/confirmation.d.ts +8 -0
- package/dist/features/mcp-ui/surfaces/confirmation.js +65 -6
- package/dist/features/mcp-ui/surfaces/document.d.ts +11 -1
- package/dist/features/mcp-ui/surfaces/document.js +22 -2
- package/dist/features/mcp-ui/surfaces/form.js +12 -11
- package/package.json +4 -3
|
@@ -22,7 +22,7 @@ export function useFileDropTarget(onUploadFiles) {
|
|
|
22
22
|
onUploadFilesRef.current = onUploadFiles;
|
|
23
23
|
const clearDropReadError = useCallback(() => setDropReadError(null), []);
|
|
24
24
|
// This target's own `onDrop`/`onDragLeave` are not guaranteed to run when a drag ends: a host that
|
|
25
|
-
// handles a drop itself in the capture phase and stops propagation (
|
|
25
|
+
// handles a drop itself in the capture phase and stops propagation (a host's folder-drop-to-path) or a
|
|
26
26
|
// drag that ends elsewhere leaves the drag-over state stuck on. Window-capture listeners run before
|
|
27
27
|
// any element handler, so no `stopPropagation` can skip them. Registered only while dragging.
|
|
28
28
|
useEffect(() => {
|
|
@@ -101,8 +101,8 @@ export function ByokProviderForm({ config, onConfigChange, preset, presets = DEF
|
|
|
101
101
|
// child it grew the field and pushed the bottom-aligned button off the input's line every
|
|
102
102
|
// time discovery failed. Same element, same `.jini-field-hint.is-error[role="status"]`
|
|
103
103
|
// identity host suites locate it by — only its parent changed.
|
|
104
|
-
_jsx("span", { className: "jini-field-hint is-error", role: "status", children: t('Could not load live models: {message}', { message: modelDiscovery.message }) })) : null, canTestConnection && (connectionTest.status === 'ok' || connectionTest.status === 'error') ? (_jsx("div", { className: "jini-byok-test-row", children: _jsx("span", { className: `jini-byok-test-status is-${connectionTest.status}`, role: connectionTest.status === 'error' ? 'alert' : 'status', children: connectionTest.status === 'ok'
|
|
105
|
-
? (connectionTest.message
|
|
106
|
-
: connectionTest.message }) })) : null, formFooter ? _jsx("div", { className: "jini-byok-form-footer", children: formFooter }) : null] }));
|
|
104
|
+
_jsx("span", { className: "jini-field-hint is-error", role: "status", children: t('Could not load live models: {message}', { message: t(modelDiscovery.message) }) })) : null, canTestConnection && (connectionTest.status === 'ok' || connectionTest.status === 'error') ? (_jsx("div", { className: "jini-byok-test-row", children: _jsx("span", { className: `jini-byok-test-status is-${connectionTest.status}`, role: connectionTest.status === 'error' ? 'alert' : 'status', children: connectionTest.status === 'ok'
|
|
105
|
+
? (connectionTest.message !== undefined ? t(connectionTest.message) : t('Connection succeeded'))
|
|
106
|
+
: t(connectionTest.message) }) })) : null, formFooter ? _jsx("div", { className: "jini-byok-form-footer", children: formFooter }) : null] }));
|
|
107
107
|
}
|
|
108
108
|
//# sourceMappingURL=ByokProviderForm.js.map
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
* (untouched, still literally empty) marker means there is nothing inside the marker for that re-parse
|
|
30
30
|
* to ever pick up, regardless of whether that `isProtectedElement` gap is ever closed.
|
|
31
31
|
*
|
|
32
|
+
* **Safe to call again after every structural edit** (the hook does, on the tick after any component
|
|
33
|
+
* add or remove): each pass first drops cards whose marker has gone (see `removeStaleCards`), then
|
|
34
|
+
* wraps any marker not already inside a card.
|
|
35
|
+
*
|
|
32
36
|
* Re-entrancy (defensive, mirroring `canvas-content-wrapper.ts`'s own `WRAPPER_ROOT_MARKER` guard): a
|
|
33
37
|
* marker already sitting inside a placeholder card (`el.closest('[' + CARD_ROOT_MARKER + ']')`) is
|
|
34
38
|
* skipped, so a second call — or a caller's own `load` handler firing twice — cannot nest a second card
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
* (untouched, still literally empty) marker means there is nothing inside the marker for that re-parse
|
|
30
30
|
* to ever pick up, regardless of whether that `isProtectedElement` gap is ever closed.
|
|
31
31
|
*
|
|
32
|
+
* **Safe to call again after every structural edit** (the hook does, on the tick after any component
|
|
33
|
+
* add or remove): each pass first drops cards whose marker has gone (see `removeStaleCards`), then
|
|
34
|
+
* wraps any marker not already inside a card.
|
|
35
|
+
*
|
|
32
36
|
* Re-entrancy (defensive, mirroring `canvas-content-wrapper.ts`'s own `WRAPPER_ROOT_MARKER` guard): a
|
|
33
37
|
* marker already sitting inside a placeholder card (`el.closest('[' + CARD_ROOT_MARKER + ']')`) is
|
|
34
38
|
* skipped, so a second call — or a caller's own `load` handler firing twice — cannot nest a second card
|
|
@@ -38,12 +42,31 @@
|
|
|
38
42
|
* `load`) can recognize an already-decorated marker via `el.closest` and skip it — see this file's
|
|
39
43
|
* header. */
|
|
40
44
|
const CARD_ROOT_MARKER = 'data-tovu-embed-placeholder-root';
|
|
45
|
+
/** Flags every child the card itself builds (icon, text), so the one unflagged child is always the
|
|
46
|
+
* marker. {@link OVERLAY_RULE} and the stale-card check in `removeStaleCards` both rely on it. */
|
|
47
|
+
const CARD_CHROME_MARKER = 'data-tovu-embed-placeholder-chrome';
|
|
48
|
+
/** Identifies the one `<style>` this module adds to the canvas document's `<head>`. */
|
|
49
|
+
const OVERLAY_STYLE_MARKER = 'data-tovu-embed-placeholder-style';
|
|
50
|
+
/**
|
|
51
|
+
* Stretches the (empty) marker over its card as a transparent overlay, so a click anywhere on the card
|
|
52
|
+
* lands on the marker element itself. GrapesJS resolves a click by walking up from the event target to
|
|
53
|
+
* the nearest element it rendered; the card is not one of those, so without this a click on the card
|
|
54
|
+
* selected whatever block wraps the embed, and Delete then removed that block together with the embed.
|
|
55
|
+
* With it, the editor selects (and outlines) the embed. A stylesheet rule rather than an inline style,
|
|
56
|
+
* so the marker's own attributes stay untouched. `!important` because the host's theme may style the
|
|
57
|
+
* marker element.
|
|
58
|
+
*/
|
|
59
|
+
const OVERLAY_RULE = `[${CARD_ROOT_MARKER}] > :not([${CARD_CHROME_MARKER}])` +
|
|
60
|
+
' { position: absolute !important; inset: 0 !important; margin: 0 !important; width: auto !important;' +
|
|
61
|
+
' height: auto !important; min-width: 0 !important; max-width: none !important; min-height: 0 !important;' +
|
|
62
|
+
' display: block !important; background: transparent !important; }';
|
|
41
63
|
/** One CSS declaration list, as `property:value` pairs, for the card's own opaque chrome. Deliberately
|
|
42
64
|
* NOT derived from the host's theme in any way — themes are arbitrary and downloadable, and this card
|
|
43
65
|
* must stay legible whether the canvas underneath is a light theme, a dark theme, or (mid-edit) no
|
|
44
66
|
* theme at all. An opaque background plus a fixed, high-contrast foreground guarantees that
|
|
45
67
|
* regardless of what renders behind it, unlike a translucent or theme-token-based treatment would. */
|
|
46
68
|
const CARD_STYLE = [
|
|
69
|
+
'position:relative',
|
|
47
70
|
'display:flex',
|
|
48
71
|
'align-items:flex-start',
|
|
49
72
|
'gap:8px',
|
|
@@ -91,6 +114,7 @@ function buildPlaceholderCard(doc, descriptor) {
|
|
|
91
114
|
card.setAttribute(CARD_ROOT_MARKER, '');
|
|
92
115
|
card.setAttribute('style', CARD_STYLE);
|
|
93
116
|
const text = doc.createElement('div');
|
|
117
|
+
text.setAttribute(CARD_CHROME_MARKER, '');
|
|
94
118
|
const heading = doc.createElement('div');
|
|
95
119
|
heading.setAttribute('style', 'font-weight:600;');
|
|
96
120
|
heading.textContent = `Placeholder — ${descriptor.kindLabel}`;
|
|
@@ -101,7 +125,9 @@ function buildPlaceholderCard(doc, descriptor) {
|
|
|
101
125
|
note.setAttribute('style', 'opacity:.6;margin-top:4px;font-size:11px;');
|
|
102
126
|
note.textContent = 'Shown here for editing only — the real content renders on the published page.';
|
|
103
127
|
text.append(heading, identity, note);
|
|
104
|
-
|
|
128
|
+
const icon = buildPlaceholderIcon(doc);
|
|
129
|
+
icon.setAttribute(CARD_CHROME_MARKER, '');
|
|
130
|
+
card.append(icon, text);
|
|
105
131
|
return card;
|
|
106
132
|
}
|
|
107
133
|
/**
|
|
@@ -136,6 +162,28 @@ function applyResolvedMaxWidth(marker, card) {
|
|
|
136
162
|
if (maxWidth && maxWidth !== 'none')
|
|
137
163
|
card.style.setProperty('max-width', maxWidth);
|
|
138
164
|
}
|
|
165
|
+
/** Adds {@link OVERLAY_RULE} to `doc`'s `<head>` once. Idempotent. */
|
|
166
|
+
function ensureOverlayRule(doc) {
|
|
167
|
+
if (doc.head.querySelector(`style[${OVERLAY_STYLE_MARKER}]`))
|
|
168
|
+
return;
|
|
169
|
+
const style = doc.createElement('style');
|
|
170
|
+
style.setAttribute(OVERLAY_STYLE_MARKER, '');
|
|
171
|
+
style.textContent = OVERLAY_RULE;
|
|
172
|
+
doc.head.appendChild(style);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Removes every card whose marker is no longer inside it. The card lives only in the canvas DOM, so the
|
|
176
|
+
* editor knows nothing about it: deleting the embed removes just the marker element (leaving a ghost
|
|
177
|
+
* card that still reads "Placeholder"), and moving it takes the marker out (leaving an empty card
|
|
178
|
+
* behind). The next pass of `applyCanvasEmbedPlaceholders` then wraps the moved marker again.
|
|
179
|
+
*/
|
|
180
|
+
function removeStaleCards(body) {
|
|
181
|
+
for (const card of Array.from(body.querySelectorAll(`[${CARD_ROOT_MARKER}]`))) {
|
|
182
|
+
const hasMarker = Array.from(card.children).some((child) => !child.hasAttribute(CARD_CHROME_MARKER));
|
|
183
|
+
if (!hasMarker)
|
|
184
|
+
card.remove();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
139
187
|
/**
|
|
140
188
|
* Scans every element under `body` once and, for each one `describe` recognizes (returns a descriptor
|
|
141
189
|
* for), replaces its visible slot with an explicit placeholder card — see this file's header for the
|
|
@@ -155,6 +203,7 @@ function applyResolvedMaxWidth(marker, card) {
|
|
|
155
203
|
*/
|
|
156
204
|
export function applyCanvasEmbedPlaceholders(body, describe) {
|
|
157
205
|
const doc = body.ownerDocument;
|
|
206
|
+
removeStaleCards(body);
|
|
158
207
|
// Snapshotted into an array before any mutation starts: this function inserts new ancestors as it
|
|
159
208
|
// goes, and a live `querySelectorAll` result must never be iterated while the tree it was taken over
|
|
160
209
|
// is being restructured underneath it — same discipline `applyCanvasContentWrapper` applies to
|
|
@@ -185,6 +234,7 @@ export function applyCanvasEmbedPlaceholders(body, describe) {
|
|
|
185
234
|
applyResolvedMaxWidth(el, card);
|
|
186
235
|
parent.insertBefore(card, el);
|
|
187
236
|
card.appendChild(el); // Moves the SAME node (auto-detaches from `parent`) — never cloned.
|
|
237
|
+
ensureOverlayRule(doc);
|
|
188
238
|
}
|
|
189
239
|
}
|
|
190
240
|
//# sourceMappingURL=canvas-embed-placeholders.js.map
|
|
@@ -51,6 +51,17 @@ export interface CanvasStyling {
|
|
|
51
51
|
*/
|
|
52
52
|
contentWrapper?: readonly CanvasContentWrapperNode[];
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* CSS class the canvas body carries while `react/hooks/useInteractiveHtmlEditor.ts`'s
|
|
56
|
+
* `revealCanvasWhenStylesheetsSettle` is still waiting on `canvas.styles`' `<link>` elements to settle
|
|
57
|
+
* (Bug B, Slice B1, this repo's `pages-redo` plan): every Interactive mount re-fetches the theme
|
|
58
|
+
* stylesheet (no browser cache short-circuit is guaranteed), and GrapesJS renders the canvas body
|
|
59
|
+
* without waiting for it, so a slow or restarting API left the canvas showing raw browser-default
|
|
60
|
+
* styling (Times New Roman, blue links) until the stylesheet arrived. Exported so the hook that
|
|
61
|
+
* toggles this class on/off and this module's CSS rule for it never drift onto two different literal
|
|
62
|
+
* strings.
|
|
63
|
+
*/
|
|
64
|
+
export declare const CANVAS_STYLES_PENDING_CLASS = "gjs-canvas-styles-pending";
|
|
54
65
|
/** The `canvas` slice of a GrapesJS editor config — structurally the subset of its `CanvasConfig`
|
|
55
66
|
* this module decides, kept as its own type so the builder is assertable without a live editor. */
|
|
56
67
|
export interface CanvasStyleConfig {
|
|
@@ -25,6 +25,21 @@ const CANVAS_SCROLLBAR_STYLE = [
|
|
|
25
25
|
/** The half of GrapesJS's default `canvas.frameStyle` this module conditionally drops — see the file
|
|
26
26
|
* header for why it cannot simply be left in place. */
|
|
27
27
|
const CANVAS_DEFAULT_BODY_BACKGROUND = "body { background-color: #fff }";
|
|
28
|
+
/**
|
|
29
|
+
* CSS class the canvas body carries while `react/hooks/useInteractiveHtmlEditor.ts`'s
|
|
30
|
+
* `revealCanvasWhenStylesheetsSettle` is still waiting on `canvas.styles`' `<link>` elements to settle
|
|
31
|
+
* (Bug B, Slice B1, this repo's `pages-redo` plan): every Interactive mount re-fetches the theme
|
|
32
|
+
* stylesheet (no browser cache short-circuit is guaranteed), and GrapesJS renders the canvas body
|
|
33
|
+
* without waiting for it, so a slow or restarting API left the canvas showing raw browser-default
|
|
34
|
+
* styling (Times New Roman, blue links) until the stylesheet arrived. Exported so the hook that
|
|
35
|
+
* toggles this class on/off and this module's CSS rule for it never drift onto two different literal
|
|
36
|
+
* strings.
|
|
37
|
+
*/
|
|
38
|
+
export const CANVAS_STYLES_PENDING_CLASS = "gjs-canvas-styles-pending";
|
|
39
|
+
/** The rule that makes {@link CANVAS_STYLES_PENDING_CLASS} actually hide the canvas. Unconditional —
|
|
40
|
+
* unlike {@link CANVAS_DEFAULT_BODY_BACKGROUND}, it doesn't depend on whether the host supplies its
|
|
41
|
+
* own styling, since the class is only ever present while a fetch is genuinely in flight. */
|
|
42
|
+
const CANVAS_STYLES_PENDING_RULE = `.${CANVAS_STYLES_PENDING_CLASS} { visibility: hidden }`;
|
|
28
43
|
/**
|
|
29
44
|
* Whether the host has taken over the canvas document's appearance. Either input is enough: a
|
|
30
45
|
* stylesheet alone is the common case (a real theme's CSS), and raw `css` alone is enough for a host
|
|
@@ -39,7 +54,7 @@ function hostStylesTheCanvas(styling) {
|
|
|
39
54
|
* and followed by the host's raw `css` when supplied.
|
|
40
55
|
*/
|
|
41
56
|
function buildCanvasFrameStyle(styling) {
|
|
42
|
-
const rules = [CANVAS_SCROLLBAR_STYLE];
|
|
57
|
+
const rules = [CANVAS_SCROLLBAR_STYLE, CANVAS_STYLES_PENDING_RULE];
|
|
43
58
|
if (!hostStylesTheCanvas(styling))
|
|
44
59
|
rules.unshift(CANVAS_DEFAULT_BODY_BACKGROUND);
|
|
45
60
|
if (styling.css)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { InteractiveHtmlEditor } from './react/components/InteractiveHtmlEditor.js';
|
|
2
|
-
export type { InteractiveHtmlEditorProps } from './react/components/InteractiveHtmlEditor.js';
|
|
2
|
+
export type { InteractiveHtmlEditorHandle, InteractiveHtmlEditorProps } from './react/components/InteractiveHtmlEditor.js';
|
|
3
3
|
export { useInteractiveHtmlEditor } from './react/hooks/useInteractiveHtmlEditor.js';
|
|
4
4
|
export type { CanvasContentWrapperNode, CanvasStyling } from './canvas-style.js';
|
|
5
5
|
export type { CanvasEmbedPlaceholderDescriptor } from './canvas-embed-placeholders.js';
|
|
@@ -9,10 +9,12 @@ import type { CanvasStyling } from '../../canvas-style.js';
|
|
|
9
9
|
* `<textarea>`. See `react/hooks/useInteractiveHtmlEditor.ts` for why this is uncontrolled
|
|
10
10
|
* (mount-once) rather than reacting to `html`/`isProtectedElement` prop changes after construction.
|
|
11
11
|
*
|
|
12
|
-
* **Scope
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* move/
|
|
12
|
+
* **Scope: text editing, basic formatting, and GrapesJS's own block move/copy/delete.** Selecting a
|
|
13
|
+
* block shows GrapesJS's selection toolbar (move, copy, delete) and Delete removes it; the owner kept
|
|
14
|
+
* these on purpose (2026-09-23). A text edit is saved as a byte-minimal splice of the original
|
|
15
|
+
* `html`; a block move/copy/delete falls back to a cleaned full re-serialization, with a dev warning
|
|
16
|
+
* naming the cause (see the hook's `serializeWithSplice`). No Style Manager, Layers panel or block
|
|
17
|
+
* library is enabled (`panels: { defaults: [] }`, `blockManager: { blocks: [] }` in the hook).
|
|
16
18
|
*
|
|
17
19
|
* **`GJS_STYLE_VARS` below is a consequence of that same choice, not a separate styling decision.**
|
|
18
20
|
* GrapesJS's own vendor CSS unconditionally reserves layout space for the default chrome this
|
|
@@ -27,9 +29,10 @@ import type { CanvasStyling } from '../../canvas-style.js';
|
|
|
27
29
|
* its own stylesheet.
|
|
28
30
|
*
|
|
29
31
|
* **This component has zero knowledge of any host's embed/placeholder conventions.** `isProtectedElement`
|
|
30
|
-
* is an optional predicate the caller supplies to
|
|
31
|
-
* caller likes)
|
|
32
|
-
*
|
|
32
|
+
* is an optional predicate the caller supplies to turn specific elements (identified however the
|
|
33
|
+
* caller likes) into atomic blocks: their content can never be edited, dropped into or styled, but a
|
|
34
|
+
* click selects the element itself (never the block around it), and it can be deliberately moved,
|
|
35
|
+
* copied or deleted. When omitted, no elements are protected.
|
|
33
36
|
* `describeEmbedPlaceholder` is the same idea for a second, independent concern: recognizing an
|
|
34
37
|
* unresolved embed marker and labeling it for `../../canvas-embed-placeholders.ts`'s explicit
|
|
35
38
|
* placeholder card. See `@jini-ai/admin/react`'s `InteractiveHtmlEditor` for a worked example of both:
|
|
@@ -69,11 +72,12 @@ export interface InteractiveHtmlEditorProps {
|
|
|
69
72
|
html: string;
|
|
70
73
|
/** Fires with the editor's current serialized HTML on every content edit. Does not persist
|
|
71
74
|
* anything itself — the caller decides when/whether to save, same as an HTML source textarea's
|
|
72
|
-
* `onChange`.
|
|
75
|
+
* `onChange`. A host that saves or unmounts should `await ref.flush()` first, to sync an RTE
|
|
76
|
+
* session that is still open — see `InteractiveHtmlEditorHandle`. */
|
|
73
77
|
onChange: (html: string) => void;
|
|
74
78
|
className?: string;
|
|
75
|
-
/** Identifies elements
|
|
76
|
-
*
|
|
79
|
+
/** Identifies elements whose content must never be edited or dropped into inside the editor. Each
|
|
80
|
+
* one is selectable as a single block that can be deliberately moved, copied or deleted. Read once, at mount, same as `html`. Predicates that check attributes should use
|
|
77
81
|
* `hasAttributeOnAnyNodeShape` (exported alongside this component) rather than `el.hasAttribute`
|
|
78
82
|
* directly — see that helper's own doc for why. Omit to protect nothing. */
|
|
79
83
|
isProtectedElement?: (el: Element) => boolean;
|
|
@@ -90,5 +94,13 @@ export interface InteractiveHtmlEditorProps {
|
|
|
90
94
|
* of any host's marker convention that `isProtectedElement` documents above. */
|
|
91
95
|
describeEmbedPlaceholder?: (el: Element) => CanvasEmbedPlaceholderDescriptor | undefined;
|
|
92
96
|
}
|
|
93
|
-
|
|
97
|
+
/** Imperative handle exposed via `ref`. A host calls `flush()` right before it saves or unmounts, to
|
|
98
|
+
* sync an open RTE session's still-pending edit into `onChange` first — see `onChange`'s own doc and
|
|
99
|
+
* `useInteractiveHtmlEditor`'s `flush` for the full rationale (GrapesJS only syncs an RTE session's
|
|
100
|
+
* text on close, never on every keystroke). Resolves the flushed HTML, or `undefined` when nothing
|
|
101
|
+
* had changed since mount. */
|
|
102
|
+
export interface InteractiveHtmlEditorHandle {
|
|
103
|
+
flush(): Promise<string | undefined>;
|
|
104
|
+
}
|
|
105
|
+
export declare const InteractiveHtmlEditor: import("react").ForwardRefExoticComponent<InteractiveHtmlEditorProps & import("react").RefAttributes<InteractiveHtmlEditorHandle>>;
|
|
94
106
|
//# sourceMappingURL=InteractiveHtmlEditor.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useImperativeHandle } from 'react';
|
|
2
3
|
import 'grapesjs/dist/css/grapes.min.css';
|
|
3
4
|
import { useInteractiveHtmlEditor } from '../hooks/useInteractiveHtmlEditor.js';
|
|
4
5
|
/** See this file's header for why these exist: they zero out layout space GrapesJS's default CSS
|
|
@@ -7,9 +8,10 @@ const GJS_STYLE_VARS = {
|
|
|
7
8
|
'--gjs-left-width': '0px',
|
|
8
9
|
'--gjs-canvas-top': '0px',
|
|
9
10
|
};
|
|
10
|
-
export function InteractiveHtmlEditor({ html, onChange, className, isProtectedElement, canvasStyling, describeEmbedPlaceholder,
|
|
11
|
-
const { containerRef } = useInteractiveHtmlEditor(html, onChange, isProtectedElement, canvasStyling, describeEmbedPlaceholder);
|
|
11
|
+
export const InteractiveHtmlEditor = forwardRef(function InteractiveHtmlEditor({ html, onChange, className, isProtectedElement, canvasStyling, describeEmbedPlaceholder }, ref) {
|
|
12
|
+
const { containerRef, flush } = useInteractiveHtmlEditor(html, onChange, isProtectedElement, canvasStyling, describeEmbedPlaceholder);
|
|
13
|
+
useImperativeHandle(ref, () => ({ flush }), [flush]);
|
|
12
14
|
const wrapperClassName = className ? `interactive-html-editor ${className}` : 'interactive-html-editor';
|
|
13
15
|
return _jsx("div", { ref: containerRef, className: wrapperClassName, style: GJS_STYLE_VARS });
|
|
14
|
-
}
|
|
16
|
+
});
|
|
15
17
|
//# sourceMappingURL=InteractiveHtmlEditor.js.map
|
|
@@ -2,7 +2,7 @@ import { type CanvasEmbedPlaceholderDescriptor } from '../../canvas-embed-placeh
|
|
|
2
2
|
import { type CanvasStyling } from '../../canvas-style.js';
|
|
3
3
|
/**
|
|
4
4
|
* Owns one GrapesJS `Editor` instance for the lifetime of the calling component's mount. `onChange`
|
|
5
|
-
* fires with the editor's current serialized content (see `
|
|
5
|
+
* fires with the editor's current serialized content (see `serializeWithSplice`) on every content
|
|
6
6
|
* mutation (GrapesJS's `update` event — components, styles, or attributes changing); it is not
|
|
7
7
|
* debounced, so a caller replacing an existing uncontrolled `<textarea>` (which already pushes a full
|
|
8
8
|
* value on every keystroke) sees matching behavior.
|
|
@@ -22,6 +22,15 @@ import { type CanvasStyling } from '../../canvas-style.js';
|
|
|
22
22
|
* `class="frame"` forever, inert and unrendered. `.gjs-frame` — the class `FrameView.render()`
|
|
23
23
|
* assigns — is what callers should select on, never a bare `iframe` under the wrapper.)
|
|
24
24
|
*
|
|
25
|
+
* **`flush()` exists because GrapesJS's RTE only syncs on `disableEditing`, and `update` is
|
|
26
|
+
* double-debounced and never fires after `off()`.** A host that calls `onChange` on every keystroke
|
|
27
|
+
* (like an uncontrolled `<textarea>`) still misses the operator's last edit if they click Save or
|
|
28
|
+
* navigate away while an RTE session is still open: GrapesJS writes that session's text into the
|
|
29
|
+
* component model only when it closes, and by the time a host's save handler runs, this hook has
|
|
30
|
+
* already stopped listening (or the debounce hasn't fired yet). `flush` closes any open RTE session,
|
|
31
|
+
* computes and emits the current content the same way `handleUpdate` does, and hands the result back
|
|
32
|
+
* directly — see its own doc below for exactly what it returns and when.
|
|
33
|
+
*
|
|
25
34
|
* @complexity O(1) setup/teardown. Steady-state cost is GrapesJS's own serialization per edit,
|
|
26
35
|
* proportional to document size — not something this hook controls or should hide a note about
|
|
27
36
|
* beyond that.
|
|
@@ -29,5 +38,6 @@ import { type CanvasStyling } from '../../canvas-style.js';
|
|
|
29
38
|
*/
|
|
30
39
|
export declare function useInteractiveHtmlEditor(html: string, onChange: (html: string) => void, isProtectedElement?: (el: Element) => boolean, canvasStyling?: CanvasStyling, describeEmbedPlaceholder?: (el: Element) => CanvasEmbedPlaceholderDescriptor | undefined): {
|
|
31
40
|
containerRef: import("react").RefObject<HTMLDivElement | null>;
|
|
41
|
+
flush: () => Promise<string | undefined>;
|
|
32
42
|
};
|
|
33
43
|
//# sourceMappingURL=useInteractiveHtmlEditor.d.ts.map
|