@elytracms/studio 0.0.63 → 0.0.64
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/components/builder/canvas-chrome-css.d.ts +54 -0
- package/dist/components/builder/canvas-chrome-css.js +164 -0
- package/dist/components/builder/canvas-chrome-css.js.map +1 -0
- package/dist/components/builder/canvas-chrome.d.ts +122 -18
- package/dist/components/builder/canvas-chrome.js +148 -82
- package/dist/components/builder/canvas-chrome.js.map +1 -1
- package/dist/components/builder/canvas-render-context.d.ts +13 -0
- package/dist/components/builder/canvas-render-context.js +7 -5
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/canvas-stylesheet.d.ts +1 -1
- package/dist/components/builder/canvas-stylesheet.js +16 -9
- package/dist/components/builder/canvas-stylesheet.js.map +1 -1
- package/dist/components/builder/canvas-theme-notice.d.ts +6 -0
- package/dist/components/builder/canvas-theme-notice.js +46 -0
- package/dist/components/builder/canvas-theme-notice.js.map +1 -0
- package/dist/components/builder/canvas.js +17 -10
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/rich-text-toolbar.d.ts +23 -0
- package/dist/components/builder/rich-text-toolbar.js +24 -17
- package/dist/components/builder/rich-text-toolbar.js.map +1 -1
- package/dist/components/builder/spacing-handles.d.ts +31 -0
- package/dist/components/builder/spacing-handles.js +49 -37
- package/dist/components/builder/spacing-handles.js.map +1 -1
- package/dist/components/components/component-preview.d.ts +2 -1
- package/dist/components/components/component-preview.js +24 -25
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +4 -0
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +4 -0
- package/dist/lib/i18n/messages.en.js +5 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/persistence/backend-mode.d.ts +7 -0
- package/dist/lib/persistence/backend-mode.js +9 -0
- package/dist/lib/persistence/backend-mode.js.map +1 -1
- package/dist/lib/persistence/index.d.ts +1 -1
- package/dist/lib/persistence/index.js +1 -1
- package/dist/lib/persistence/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,96 +1,113 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CANVAS_BACKDROP_LAYER_STATEMENT, canvasChromeCss } from './canvas-chrome-css.js';
|
|
2
|
+
export { canvasChromeCss, CANVAS_BACKDROP_LAYER_STATEMENT } from './canvas-chrome-css.js';
|
|
2
3
|
/**
|
|
3
|
-
* The canvas iframe's document chrome (EC-389
|
|
4
|
-
* `BuilderCanvas`'s `wireUp` (house pattern, EC-330):
|
|
5
|
-
* the preview document lives here, in ONE pinned
|
|
6
|
-
* without jsdom.
|
|
4
|
+
* The canvas iframe's document chrome (EC-389, reshaped by EC-414) — the
|
|
5
|
+
* framework-free half of `BuilderCanvas`'s `wireUp` (house pattern, EC-330):
|
|
6
|
+
* everything injected into the preview document lives here, in ONE pinned
|
|
7
|
+
* cascade order, unit-testable without jsdom.
|
|
8
|
+
*
|
|
9
|
+
* EC-414 (AD-A/AD-C): the studio design system is leaving the canvas document.
|
|
10
|
+
* Which world a project lives in is decided by a RUNTIME PROBE of the loaded
|
|
11
|
+
* canvas stylesheet — no flag, no config, zero consumer breakage:
|
|
12
|
+
*
|
|
13
|
+
* - `legacy` (marker tokens present): the merged pre-EC-414 canvas.css that
|
|
14
|
+
* bundles `@elytracms/studio/styles.css` with the consumer theme. The
|
|
15
|
+
* EC-389 body-class default and today's behavior apply EXACTLY.
|
|
16
|
+
* - `new` (markers absent): the consumer's standalone full-Tailwind theme
|
|
17
|
+
* entry. No studio utilities exist in the document, so the body-class
|
|
18
|
+
* default retires and the neutral hand-written backdrop applies instead.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The studio design system's marker tokens (AD-C probe). These are the plain
|
|
22
|
+
* `:root` custom properties from the studio's generated `tokens.css`, which any
|
|
23
|
+
* merged legacy build carries VERBATIM — unlike `@theme inline` variables
|
|
24
|
+
* (e.g. `--color-panel`), which Tailwind v4 inlines into utilities and never
|
|
25
|
+
* emits as custom properties (verified against the repo's Tailwind 4.3.2; the
|
|
26
|
+
* task file's `--color-panel` example probe would classify every legacy build
|
|
27
|
+
* as new). Two tokens ANDed so a consumer theme that happens to define one
|
|
28
|
+
* generic name (`--panel`) cannot be mistaken for the studio design system.
|
|
29
|
+
*/
|
|
30
|
+
export const CANVAS_LEGACY_MARKER_TOKENS = ['--panel', '--grid-line'];
|
|
31
|
+
/**
|
|
32
|
+
* A Tailwind v4 build unconditionally emits `--default-font-family` on
|
|
33
|
+
* `:root, :host` (preflight consumes it) — its absence after a successful
|
|
34
|
+
* stylesheet load means the canvas stylesheet is not a Tailwind theme entry at
|
|
35
|
+
* all (AD-D item 2's "defines no @theme tokens" invalid state).
|
|
7
36
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
font: 12px/1 ui-sans-serif, system-ui, sans-serif; outline: none; }
|
|
44
|
-
/* Locale-fallback chips (EC-176): a node whose bound content is currently
|
|
45
|
-
served via locale fallback wears a small corner chip naming the locale it
|
|
46
|
-
actually came from — the canvas never silently mixes locales. */
|
|
47
|
-
.ec-editable[data-ec-locale-fallback] { position: relative; }
|
|
48
|
-
.ec-editable[data-ec-locale-fallback]::after {
|
|
49
|
-
content: attr(data-ec-locale-fallback);
|
|
50
|
-
position: absolute; top: 2px; right: 2px; z-index: 5; pointer-events: none;
|
|
51
|
-
padding: 0 4px; border-radius: 4px;
|
|
52
|
-
font: 600 9px/1.5 ui-sans-serif, system-ui, sans-serif;
|
|
53
|
-
letter-spacing: 0.05em; text-transform: uppercase;
|
|
54
|
-
color: #fbbf24; background: rgba(180,83,9,0.25); border: 1px solid rgba(245,158,11,0.45);
|
|
55
|
-
}
|
|
56
|
-
/* Drag & drop states (EC-160/EC-162): eligibility comes from the shared pure
|
|
57
|
-
rules (lib/dnd/drop-rules); these classes only *show* the resolution.
|
|
58
|
-
Rejection is explicit and visible during the drag — never a silent failed
|
|
59
|
-
drop. Before/after bars follow the parent's flow axis (data-ec-drop-axis);
|
|
60
|
-
left/right are the wrap-into-columns gesture (violet, paired highlight). */
|
|
61
|
-
.ec-editable[data-ec-drop='into'] { outline: 2px solid #34d399; outline-offset: -2px; background-color: rgba(52, 211, 153, 0.08); }
|
|
62
|
-
.ec-editable[data-ec-drop='before'] { box-shadow: 0 -3px 0 0 #38bdf8; }
|
|
63
|
-
.ec-editable[data-ec-drop='after'] { box-shadow: 0 3px 0 0 #38bdf8; }
|
|
64
|
-
.ec-editable[data-ec-drop='before'][data-ec-drop-axis='x'] { box-shadow: -3px 0 0 0 #38bdf8; }
|
|
65
|
-
.ec-editable[data-ec-drop='after'][data-ec-drop-axis='x'] { box-shadow: 3px 0 0 0 #38bdf8; }
|
|
66
|
-
.ec-editable[data-ec-drop='left'] { box-shadow: -3px 0 0 0 #a78bfa; background-color: rgba(167, 139, 250, 0.1); }
|
|
67
|
-
.ec-editable[data-ec-drop='right'] { box-shadow: 3px 0 0 0 #a78bfa; background-color: rgba(167, 139, 250, 0.1); }
|
|
68
|
-
.ec-editable[data-ec-drop='blocked'] { outline: 2px solid #ef4444; outline-offset: -2px; background-color: rgba(239, 68, 68, 0.08); cursor: not-allowed; }
|
|
69
|
-
`;
|
|
37
|
+
export const CANVAS_TAILWIND_MARKER_TOKEN = '--default-font-family';
|
|
38
|
+
const present = (value) => typeof value === 'string' && value.trim() !== '';
|
|
39
|
+
/**
|
|
40
|
+
* AD-C pure core: classify the canvas stylesheet world from computed-token
|
|
41
|
+
* presence. Legacy needs EVERY legacy marker; a loaded stylesheet with no
|
|
42
|
+
* Tailwind marker is the explicit `no-theme-tokens` invalid state.
|
|
43
|
+
*/
|
|
44
|
+
export function classifyCanvasTheme(sample) {
|
|
45
|
+
if (sample.loadFailed)
|
|
46
|
+
return { kind: 'stylesheet-error' };
|
|
47
|
+
if (sample.legacyMarkers.length > 0 && sample.legacyMarkers.every(present)) {
|
|
48
|
+
return { kind: 'legacy' };
|
|
49
|
+
}
|
|
50
|
+
if (present(sample.tailwindMarker))
|
|
51
|
+
return { kind: 'new' };
|
|
52
|
+
return { kind: 'no-theme-tokens' };
|
|
53
|
+
}
|
|
54
|
+
/** The world a status renders as: only the legacy merged stylesheet keeps the
|
|
55
|
+
* EC-389 body-class default; every other state gets the neutral backdrop. */
|
|
56
|
+
export function canvasWorldOf(status) {
|
|
57
|
+
return status.kind === 'legacy' ? 'legacy' : 'new';
|
|
58
|
+
}
|
|
59
|
+
/** Sample the live document's computed root styles for {@link classifyCanvasTheme}. */
|
|
60
|
+
export function probeCanvasTheme(doc, loadFailed = false) {
|
|
61
|
+
const root = doc.documentElement;
|
|
62
|
+
const win = doc.defaultView;
|
|
63
|
+
if (!root || !win)
|
|
64
|
+
return { kind: 'new' };
|
|
65
|
+
const computed = win.getComputedStyle(root);
|
|
66
|
+
return classifyCanvasTheme({
|
|
67
|
+
loadFailed,
|
|
68
|
+
legacyMarkers: CANVAS_LEGACY_MARKER_TOKENS.map((token) => computed.getPropertyValue(token)),
|
|
69
|
+
tailwindMarker: computed.getPropertyValue(CANVAS_TAILWIND_MARKER_TOKEN),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
70
72
|
/**
|
|
71
|
-
* The studio's DEFAULT canvas body classes —
|
|
72
|
-
*
|
|
73
|
-
*
|
|
73
|
+
* The studio's DEFAULT canvas body classes — LEGACY world only (EC-389 → EC-414):
|
|
74
|
+
* a consumer's `defineStudio({ canvas: { bodyClassName } })` REPLACES it
|
|
75
|
+
* wholesale (including `''` to drop it), because these utility classes
|
|
74
76
|
* out-cascade a site theme's `@layer base body` rules — keeping them additive
|
|
75
|
-
* would make the site's own page background unreachable (the NK-004 bug).
|
|
77
|
+
* would make the site's own page background unreachable (the NK-004 bug). In
|
|
78
|
+
* the NEW world the default retires entirely (the utilities don't exist there);
|
|
79
|
+
* the chrome stylesheet's neutral backdrop takes over.
|
|
76
80
|
*/
|
|
77
81
|
export const DEFAULT_CANVAS_BODY_CLASS = 'bg-background text-foreground font-sans';
|
|
78
82
|
/**
|
|
79
|
-
* Apply the canvas document chrome in the PINNED cascade order (EC-389):
|
|
83
|
+
* Apply the canvas document chrome in the PINNED cascade order (EC-389/EC-414):
|
|
80
84
|
*
|
|
81
|
-
* 1. `ec-
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
+
* 1. `ec-layer-order` — the backdrop layer STATEMENT, pinning the chrome's
|
|
86
|
+
* `ec-canvas-backdrop` layer to the bottom of the layer order (it must
|
|
87
|
+
* precede the consumer stylesheet in document order to lose to it);
|
|
88
|
+
* 2. `ec-app-css` — the canvas stylesheet `<link>` (the consumer theme; in
|
|
89
|
+
* the legacy world also the studio design system);
|
|
90
|
+
* 3. `ec-outline-css` — the studio's editing chrome (world-composed:
|
|
91
|
+
* backdrop + selection/DnD/toolbar/containment styles);
|
|
92
|
+
* 4. `ec-kit-tokens` — an instantiated starter kit's `tokensCss` (kit provides…);
|
|
93
|
+
* 5. `ec-canvas-theme` — the consumer's `canvas.tokensCss` (…consumer overrides).
|
|
85
94
|
*
|
|
86
95
|
* Later elements win the cascade at equal specificity, so studio defaults <
|
|
87
96
|
* kit tokens < consumer config — the customization-seam ordering. The body
|
|
88
97
|
* class is the consumer's `bodyClassName` when given (even `''`), else the
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
98
|
+
* legacy default in the legacy world and empty in the new world (AD-C). Safe
|
|
99
|
+
* to run repeatedly (iframe onLoad + the already-loaded fast path + the probe
|
|
100
|
+
* settling): links/styles are deduped by id, the chrome CSS is re-composed in
|
|
101
|
+
* place when the world flips, and the two token styles are re-appended IN
|
|
102
|
+
* ORDER so a re-wire never flips who wins.
|
|
92
103
|
*/
|
|
93
104
|
export function applyCanvasChrome(d, inputs) {
|
|
105
|
+
if (!d.getElementById('ec-layer-order')) {
|
|
106
|
+
const style = d.createElement('style');
|
|
107
|
+
style.id = 'ec-layer-order';
|
|
108
|
+
style.textContent = CANVAS_BACKDROP_LAYER_STATEMENT;
|
|
109
|
+
d.head.appendChild(style);
|
|
110
|
+
}
|
|
94
111
|
if (inputs.stylesheetUrl && !d.getElementById('ec-app-css')) {
|
|
95
112
|
const link = d.createElement('link');
|
|
96
113
|
link.id = 'ec-app-css';
|
|
@@ -98,17 +115,25 @@ export function applyCanvasChrome(d, inputs) {
|
|
|
98
115
|
link.href = inputs.stylesheetUrl;
|
|
99
116
|
d.head.appendChild(link);
|
|
100
117
|
}
|
|
101
|
-
|
|
118
|
+
const chromeCss = canvasChromeCss(inputs.world);
|
|
119
|
+
const outline = d.getElementById('ec-outline-css');
|
|
120
|
+
if (outline) {
|
|
121
|
+
// Probe settled on the other world: re-compose in place (position stable).
|
|
122
|
+
if (outline.textContent !== chromeCss)
|
|
123
|
+
outline.textContent = chromeCss;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
102
126
|
const style = d.createElement('style');
|
|
103
127
|
style.id = 'ec-outline-css';
|
|
104
|
-
style.textContent =
|
|
128
|
+
style.textContent = chromeCss;
|
|
105
129
|
d.head.appendChild(style);
|
|
106
130
|
}
|
|
107
131
|
// Both token layers re-append (remove + add) on every wire, in sequence, so
|
|
108
132
|
// the consumer element always lands after the kit element.
|
|
109
133
|
appendCssLayer(d, 'ec-kit-tokens', inputs.kitTokensCss);
|
|
110
134
|
appendCssLayer(d, 'ec-canvas-theme', inputs.theme?.tokensCss);
|
|
111
|
-
d.body.className =
|
|
135
|
+
d.body.className =
|
|
136
|
+
inputs.theme?.bodyClassName ?? (inputs.world === 'legacy' ? DEFAULT_CANVAS_BODY_CLASS : '');
|
|
112
137
|
}
|
|
113
138
|
/** Replace-append one identified `<style>` layer; absent CSS removes a stale element. */
|
|
114
139
|
function appendCssLayer(d, id, css) {
|
|
@@ -122,4 +147,45 @@ function appendCssLayer(d, id, css) {
|
|
|
122
147
|
style.textContent = css;
|
|
123
148
|
d.head.appendChild(style);
|
|
124
149
|
}
|
|
150
|
+
/** Links whose settle listeners are already attached (re-wires must not stack them). */
|
|
151
|
+
const wiredLinks = new WeakSet();
|
|
152
|
+
/**
|
|
153
|
+
* The DOM-level wiring around {@link applyCanvasChrome}: applies the chrome
|
|
154
|
+
* immediately with the CURRENT classification, then re-applies once the
|
|
155
|
+
* `ec-app-css` link settles (the AD-C probe reads computed custom properties,
|
|
156
|
+
* which exist only after the stylesheet loads). Before the stylesheet arrives
|
|
157
|
+
* neither world's styling is resolvable anyway (no consumer CSS yet), so the
|
|
158
|
+
* pre-settle pass is visually neutral in both worlds.
|
|
159
|
+
*/
|
|
160
|
+
export function wireCanvasChrome(doc, options) {
|
|
161
|
+
const apply = (settled, loadFailed) => {
|
|
162
|
+
const status = options.stylesheetUrl
|
|
163
|
+
? probeCanvasTheme(doc, loadFailed)
|
|
164
|
+
: { kind: 'no-stylesheet' };
|
|
165
|
+
applyCanvasChrome(doc, {
|
|
166
|
+
stylesheetUrl: options.stylesheetUrl,
|
|
167
|
+
world: canvasWorldOf(status),
|
|
168
|
+
...(options.kitTokensCss !== undefined ? { kitTokensCss: options.kitTokensCss } : {}),
|
|
169
|
+
...(options.theme !== undefined ? { theme: options.theme } : {}),
|
|
170
|
+
});
|
|
171
|
+
if (settled)
|
|
172
|
+
options.onStatus?.(status);
|
|
173
|
+
};
|
|
174
|
+
if (!options.stylesheetUrl) {
|
|
175
|
+
apply(true, false);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// First pass injects the link (and everything else) with the current sample.
|
|
179
|
+
const already = doc.getElementById('ec-app-css');
|
|
180
|
+
const loaded = already?.sheet != null;
|
|
181
|
+
apply(loaded, false);
|
|
182
|
+
if (loaded)
|
|
183
|
+
return;
|
|
184
|
+
const link = doc.getElementById('ec-app-css');
|
|
185
|
+
if (!link || wiredLinks.has(link))
|
|
186
|
+
return;
|
|
187
|
+
wiredLinks.add(link);
|
|
188
|
+
link.addEventListener('load', () => apply(true, false), { once: true });
|
|
189
|
+
link.addEventListener('error', () => apply(true, true), { once: true });
|
|
190
|
+
}
|
|
125
191
|
//# sourceMappingURL=canvas-chrome.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas-chrome.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-chrome.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAG9D;;;;;GAKG;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;wCAqBa,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCzD,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,yCAAyC,CAAA;AAmClF;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,CAAuB,EAAE,MAA0B;IACnF,IAAI,MAAM,CAAC,aAAa,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,CAAC;QAC5D,MAAM,IAAI,GAAG,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;QACpC,IAAI,CAAC,EAAE,GAAG,YAAY,CAAA;QACtB,IAAI,CAAC,GAAG,GAAG,YAAY,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,aAAa,CAAA;QAChC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;IACD,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;QACtC,KAAK,CAAC,EAAE,GAAG,gBAAgB,CAAA;QAC3B,KAAK,CAAC,WAAW,GAAG,WAAW,CAAA;QAC/B,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAC3B,CAAC;IACD,4EAA4E;IAC5E,2DAA2D;IAC3D,cAAc,CAAC,CAAC,EAAE,eAAe,EAAE,MAAM,CAAC,YAAY,CAAC,CAAA;IACvD,cAAc,CAAC,CAAC,EAAE,iBAAiB,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IAC7D,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,KAAK,EAAE,aAAa,IAAI,yBAAyB,CAAA;AAC7E,CAAC;AAED,yFAAyF;AACzF,SAAS,cAAc,CAAC,CAAuB,EAAE,EAAU,EAAE,GAAuB;IAClF,MAAM,QAAQ,GAAG,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC,CAAA;IACrC,IAAI,QAAQ;QAAE,QAAQ,CAAC,MAAM,EAAE,CAAA;IAC/B,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAM;IAC3C,MAAM,KAAK,GAAG,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;IACtC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAA;IACb,KAAK,CAAC,WAAW,GAAG,GAAG,CAAA;IACvB,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;AAC3B,CAAC","sourcesContent":["import { PRESERVE_NODE_ATTR } from '@elytracms/core/rich-text'\nimport type { StudioCanvasTheme } from '../app-views/studio-config'\n\n/**\n * The canvas iframe's document chrome (EC-389) — the framework-free half of\n * `BuilderCanvas`'s `wireUp` (house pattern, EC-330): everything injected into\n * the preview document lives here, in ONE pinned cascade order, unit-testable\n * without jsdom.\n */\n\n/** Selection/hover outlines, scoped to the per-node editable wrappers. */\nexport const OUTLINE_CSS = `\n /* The canvas backdrop is deliberately always light/site-like (see the\n wireUp note in canvas.tsx) — force the native form-control/scrollbar theme\n to match, so a page with real inputs never shows dark native chrome\n floating on a light page. */\n html { color-scheme: light; }\n body { margin: 0; padding: 16px; }\n .ec-editable { outline-offset: -1px; cursor: default; }\n .ec-editable[data-hovered]:not([data-selected]) { outline: 1px dashed rgba(130,150,190,0.75); }\n .ec-editable[data-selected] { outline: 2px solid #5b8cff; }\n /* Inline editing (EC-158 text, EC-159 rich text): the rendered element\n itself becomes the editor — no overlay, no replacement input — so\n typography, alignment, and layout are pixel-identical between edit and\n render states. The wrapper keeps the selection outline; the host only\n gets a caret. pre-wrap applies to PLAIN text hosts only — rich-text\n markup must keep its rendered whitespace handling (no layout shift). */\n .ec-editable[data-ec-editing] { outline: 2px solid #5b8cff; }\n .ec-editable [data-ec-edit] { outline: none; cursor: text; }\n .ec-editable [data-ec-edit='text'] { white-space: pre-wrap; }\n /* Preserved atoms during a rich-text session (custom blocks, unknown nodes,\n images, embeds): visible and rendered, but not text-editable. */\n .ec-editable [data-ec-edit='rich'] [${PRESERVE_NODE_ATTR}] { user-select: none; }\n /* Floating mark toolbar (EC-159), portaled into the iframe. */\n .ec-rt-toolbar { color-scheme: dark; }\n .ec-rt-toolbar-row { display: flex; align-items: center; gap: 2px; padding: 3px;\n border: 1px solid rgba(130,150,190,0.4); border-radius: 8px;\n background: rgb(24,26,32); box-shadow: 0 4px 16px rgba(0,0,0,0.4); }\n .ec-rt-toolbar-btn { min-width: 26px; height: 24px; padding: 0 7px; border: 0; border-radius: 5px;\n background: transparent; color: rgb(225,228,236); font: 600 12px/1 ui-sans-serif, system-ui, sans-serif;\n cursor: pointer; }\n .ec-rt-toolbar-btn:hover { background: rgba(130,150,190,0.25); }\n .ec-rt-toolbar-btn[data-active] { background: #5b8cff; color: white; }\n .ec-rt-toolbar-input { width: 200px; height: 24px; padding: 0 7px; border-radius: 5px;\n border: 1px solid rgba(130,150,190,0.4); background: rgb(16,18,22); color: rgb(225,228,236);\n font: 12px/1 ui-sans-serif, system-ui, sans-serif; outline: none; }\n /* Locale-fallback chips (EC-176): a node whose bound content is currently\n served via locale fallback wears a small corner chip naming the locale it\n actually came from — the canvas never silently mixes locales. */\n .ec-editable[data-ec-locale-fallback] { position: relative; }\n .ec-editable[data-ec-locale-fallback]::after {\n content: attr(data-ec-locale-fallback);\n position: absolute; top: 2px; right: 2px; z-index: 5; pointer-events: none;\n padding: 0 4px; border-radius: 4px;\n font: 600 9px/1.5 ui-sans-serif, system-ui, sans-serif;\n letter-spacing: 0.05em; text-transform: uppercase;\n color: #fbbf24; background: rgba(180,83,9,0.25); border: 1px solid rgba(245,158,11,0.45);\n }\n /* Drag & drop states (EC-160/EC-162): eligibility comes from the shared pure\n rules (lib/dnd/drop-rules); these classes only *show* the resolution.\n Rejection is explicit and visible during the drag — never a silent failed\n drop. Before/after bars follow the parent's flow axis (data-ec-drop-axis);\n left/right are the wrap-into-columns gesture (violet, paired highlight). */\n .ec-editable[data-ec-drop='into'] { outline: 2px solid #34d399; outline-offset: -2px; background-color: rgba(52, 211, 153, 0.08); }\n .ec-editable[data-ec-drop='before'] { box-shadow: 0 -3px 0 0 #38bdf8; }\n .ec-editable[data-ec-drop='after'] { box-shadow: 0 3px 0 0 #38bdf8; }\n .ec-editable[data-ec-drop='before'][data-ec-drop-axis='x'] { box-shadow: -3px 0 0 0 #38bdf8; }\n .ec-editable[data-ec-drop='after'][data-ec-drop-axis='x'] { box-shadow: 3px 0 0 0 #38bdf8; }\n .ec-editable[data-ec-drop='left'] { box-shadow: -3px 0 0 0 #a78bfa; background-color: rgba(167, 139, 250, 0.1); }\n .ec-editable[data-ec-drop='right'] { box-shadow: 3px 0 0 0 #a78bfa; background-color: rgba(167, 139, 250, 0.1); }\n .ec-editable[data-ec-drop='blocked'] { outline: 2px solid #ef4444; outline-offset: -2px; background-color: rgba(239, 68, 68, 0.08); cursor: not-allowed; }\n`\n\n/**\n * The studio's DEFAULT canvas body classes — a replaceable default (EC-389\n * decision): a consumer's `defineStudio({ canvas: { bodyClassName } })` REPLACES\n * it wholesale (including `''` to drop it), because these utility classes\n * out-cascade a site theme's `@layer base body` rules — keeping them additive\n * would make the site's own page background unreachable (the NK-004 bug).\n */\nexport const DEFAULT_CANVAS_BODY_CLASS = 'bg-background text-foreground font-sans'\n\n/** Everything injected into the canvas preview document. */\nexport interface CanvasChromeInputs {\n /** The consuming app's compiled canvas stylesheet URL; `null` → no link (tests/SSR). */\n stylesheetUrl: string | null\n /** An instantiated starter kit's brand token palette (EC-117), when the project has one. */\n kitTokensCss?: string\n /** The consumer's canvas theme from `defineStudio({ canvas })` (EC-389). */\n theme?: StudioCanvasTheme\n}\n\n/** The head element surface the chrome creates (link/style). */\nexport interface CanvasChromeElement {\n id: string\n rel?: string\n href?: string\n textContent: string | null\n remove(): void\n}\n\n/**\n * The document surface `applyCanvasChrome` needs (the `canvas-dnd` house\n * pattern: a narrow structural type so the core is unit-testable with a\n * hand-rolled fake — no jsdom). A real iframe `Document` satisfies it.\n */\nexport interface CanvasChromeDocument {\n getElementById(id: string): CanvasChromeElement | null\n createElement(tag: string): CanvasChromeElement\n // `object` (not CanvasChromeElement): the real DOM `appendChild<T extends Node>`\n // is only bivariance-compatible with a parameter every Node satisfies.\n head: { appendChild(node: object): void }\n body: { className: string }\n}\n\n/**\n * Apply the canvas document chrome in the PINNED cascade order (EC-389):\n *\n * 1. `ec-app-css` — the canvas stylesheet `<link>` (site delivery CSS + studio CSS);\n * 2. `ec-outline-css` — the studio's editing chrome (selection/DnD/toolbar styles);\n * 3. `ec-kit-tokens` — an instantiated starter kit's `tokensCss` (kit provides…);\n * 4. `ec-canvas-theme` — the consumer's `canvas.tokensCss` (…consumer overrides).\n *\n * Later elements win the cascade at equal specificity, so studio defaults <\n * kit tokens < consumer config — the customization-seam ordering. The body\n * class is the consumer's `bodyClassName` when given (even `''`), else the\n * studio default. Safe to run repeatedly (iframe onLoad + the already-loaded\n * fast path): links/styles are deduped by id, and the two token styles are\n * re-appended IN ORDER so a re-wire never flips who wins.\n */\nexport function applyCanvasChrome(d: CanvasChromeDocument, inputs: CanvasChromeInputs): void {\n if (inputs.stylesheetUrl && !d.getElementById('ec-app-css')) {\n const link = d.createElement('link')\n link.id = 'ec-app-css'\n link.rel = 'stylesheet'\n link.href = inputs.stylesheetUrl\n d.head.appendChild(link)\n }\n if (!d.getElementById('ec-outline-css')) {\n const style = d.createElement('style')\n style.id = 'ec-outline-css'\n style.textContent = OUTLINE_CSS\n d.head.appendChild(style)\n }\n // Both token layers re-append (remove + add) on every wire, in sequence, so\n // the consumer element always lands after the kit element.\n appendCssLayer(d, 'ec-kit-tokens', inputs.kitTokensCss)\n appendCssLayer(d, 'ec-canvas-theme', inputs.theme?.tokensCss)\n d.body.className = inputs.theme?.bodyClassName ?? DEFAULT_CANVAS_BODY_CLASS\n}\n\n/** Replace-append one identified `<style>` layer; absent CSS removes a stale element. */\nfunction appendCssLayer(d: CanvasChromeDocument, id: string, css: string | undefined): void {\n const existing = d.getElementById(id)\n if (existing) existing.remove()\n if (css === undefined || css === '') return\n const style = d.createElement('style')\n style.id = id\n style.textContent = css\n d.head.appendChild(style)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"canvas-chrome.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-chrome.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StudioCanvasTheme } from '../app-views/studio-config'\nimport { CANVAS_BACKDROP_LAYER_STATEMENT, canvasChromeCss } from './canvas-chrome-css'\nimport type { CanvasWorld } from './canvas-chrome-css'\n\nexport { canvasChromeCss, CANVAS_BACKDROP_LAYER_STATEMENT } from './canvas-chrome-css'\nexport type { CanvasWorld } from './canvas-chrome-css'\n\n/**\n * The canvas iframe's document chrome (EC-389, reshaped by EC-414) — the\n * framework-free half of `BuilderCanvas`'s `wireUp` (house pattern, EC-330):\n * everything injected into the preview document lives here, in ONE pinned\n * cascade order, unit-testable without jsdom.\n *\n * EC-414 (AD-A/AD-C): the studio design system is leaving the canvas document.\n * Which world a project lives in is decided by a RUNTIME PROBE of the loaded\n * canvas stylesheet — no flag, no config, zero consumer breakage:\n *\n * - `legacy` (marker tokens present): the merged pre-EC-414 canvas.css that\n * bundles `@elytracms/studio/styles.css` with the consumer theme. The\n * EC-389 body-class default and today's behavior apply EXACTLY.\n * - `new` (markers absent): the consumer's standalone full-Tailwind theme\n * entry. No studio utilities exist in the document, so the body-class\n * default retires and the neutral hand-written backdrop applies instead.\n */\n\n/**\n * The studio design system's marker tokens (AD-C probe). These are the plain\n * `:root` custom properties from the studio's generated `tokens.css`, which any\n * merged legacy build carries VERBATIM — unlike `@theme inline` variables\n * (e.g. `--color-panel`), which Tailwind v4 inlines into utilities and never\n * emits as custom properties (verified against the repo's Tailwind 4.3.2; the\n * task file's `--color-panel` example probe would classify every legacy build\n * as new). Two tokens ANDed so a consumer theme that happens to define one\n * generic name (`--panel`) cannot be mistaken for the studio design system.\n */\nexport const CANVAS_LEGACY_MARKER_TOKENS = ['--panel', '--grid-line'] as const\n\n/**\n * A Tailwind v4 build unconditionally emits `--default-font-family` on\n * `:root, :host` (preflight consumes it) — its absence after a successful\n * stylesheet load means the canvas stylesheet is not a Tailwind theme entry at\n * all (AD-D item 2's \"defines no @theme tokens\" invalid state).\n */\nexport const CANVAS_TAILWIND_MARKER_TOKEN = '--default-font-family'\n\n/** The settled classification of the canvas stylesheet (AD-C + AD-D item 2). */\nexport type CanvasThemeStatus =\n /** Pre-EC-414 merged canvas.css — studio design system + consumer theme. */\n | { kind: 'legacy' }\n /** The EC-414 shape: a standalone consumer Tailwind theme entry. */\n | { kind: 'new' }\n /** The app never provided a canvas stylesheet (`setCanvasStylesheetUrl`). */\n | { kind: 'no-stylesheet' }\n /** The stylesheet `<link>` failed to load. */\n | { kind: 'stylesheet-error' }\n /** Loaded, but defines no Tailwind theme tokens at all. */\n | { kind: 'no-theme-tokens' }\n\n/** The raw computed-style sample the pure classifier decides from. */\nexport interface CanvasThemeProbeSample {\n /** The `ec-app-css` link fired `error` (or never resolved). */\n loadFailed: boolean\n /** Computed values of {@link CANVAS_LEGACY_MARKER_TOKENS}, in order. */\n legacyMarkers: readonly string[]\n /** Computed value of {@link CANVAS_TAILWIND_MARKER_TOKEN}. */\n tailwindMarker: string\n}\n\nconst present = (value: string | null | undefined): boolean =>\n typeof value === 'string' && value.trim() !== ''\n\n/**\n * AD-C pure core: classify the canvas stylesheet world from computed-token\n * presence. Legacy needs EVERY legacy marker; a loaded stylesheet with no\n * Tailwind marker is the explicit `no-theme-tokens` invalid state.\n */\nexport function classifyCanvasTheme(sample: CanvasThemeProbeSample): CanvasThemeStatus {\n if (sample.loadFailed) return { kind: 'stylesheet-error' }\n if (sample.legacyMarkers.length > 0 && sample.legacyMarkers.every(present)) {\n return { kind: 'legacy' }\n }\n if (present(sample.tailwindMarker)) return { kind: 'new' }\n return { kind: 'no-theme-tokens' }\n}\n\n/** The world a status renders as: only the legacy merged stylesheet keeps the\n * EC-389 body-class default; every other state gets the neutral backdrop. */\nexport function canvasWorldOf(status: CanvasThemeStatus): CanvasWorld {\n return status.kind === 'legacy' ? 'legacy' : 'new'\n}\n\n/** Sample the live document's computed root styles for {@link classifyCanvasTheme}. */\nexport function probeCanvasTheme(doc: Document, loadFailed = false): CanvasThemeStatus {\n const root = doc.documentElement\n const win = doc.defaultView\n if (!root || !win) return { kind: 'new' }\n const computed = win.getComputedStyle(root)\n return classifyCanvasTheme({\n loadFailed,\n legacyMarkers: CANVAS_LEGACY_MARKER_TOKENS.map((token) => computed.getPropertyValue(token)),\n tailwindMarker: computed.getPropertyValue(CANVAS_TAILWIND_MARKER_TOKEN),\n })\n}\n\n/**\n * The studio's DEFAULT canvas body classes — LEGACY world only (EC-389 → EC-414):\n * a consumer's `defineStudio({ canvas: { bodyClassName } })` REPLACES it\n * wholesale (including `''` to drop it), because these utility classes\n * out-cascade a site theme's `@layer base body` rules — keeping them additive\n * would make the site's own page background unreachable (the NK-004 bug). In\n * the NEW world the default retires entirely (the utilities don't exist there);\n * the chrome stylesheet's neutral backdrop takes over.\n */\nexport const DEFAULT_CANVAS_BODY_CLASS = 'bg-background text-foreground font-sans'\n\n/** Everything injected into the canvas preview document. */\nexport interface CanvasChromeInputs {\n /** The consuming app's compiled canvas stylesheet URL; `null` → no link (tests/SSR). */\n stylesheetUrl: string | null\n /** Which stylesheet world the document is in (AD-C probe; see `wireCanvasChrome`). */\n world: CanvasWorld\n /** An instantiated starter kit's brand token palette (EC-117), when the project has one. */\n kitTokensCss?: string\n /** The consumer's canvas theme from `defineStudio({ canvas })` (EC-389). */\n theme?: StudioCanvasTheme\n}\n\n/** The head element surface the chrome creates (link/style). */\nexport interface CanvasChromeElement {\n id: string\n rel?: string\n href?: string\n textContent: string | null\n remove(): void\n}\n\n/**\n * The document surface `applyCanvasChrome` needs (the `canvas-dnd` house\n * pattern: a narrow structural type so the core is unit-testable with a\n * hand-rolled fake — no jsdom). A real iframe `Document` satisfies it.\n */\nexport interface CanvasChromeDocument {\n getElementById(id: string): CanvasChromeElement | null\n createElement(tag: string): CanvasChromeElement\n // `object` (not CanvasChromeElement): the real DOM `appendChild<T extends Node>`\n // is only bivariance-compatible with a parameter every Node satisfies.\n head: { appendChild(node: object): void }\n body: { className: string }\n}\n\n/**\n * Apply the canvas document chrome in the PINNED cascade order (EC-389/EC-414):\n *\n * 1. `ec-layer-order` — the backdrop layer STATEMENT, pinning the chrome's\n * `ec-canvas-backdrop` layer to the bottom of the layer order (it must\n * precede the consumer stylesheet in document order to lose to it);\n * 2. `ec-app-css` — the canvas stylesheet `<link>` (the consumer theme; in\n * the legacy world also the studio design system);\n * 3. `ec-outline-css` — the studio's editing chrome (world-composed:\n * backdrop + selection/DnD/toolbar/containment styles);\n * 4. `ec-kit-tokens` — an instantiated starter kit's `tokensCss` (kit provides…);\n * 5. `ec-canvas-theme` — the consumer's `canvas.tokensCss` (…consumer overrides).\n *\n * Later elements win the cascade at equal specificity, so studio defaults <\n * kit tokens < consumer config — the customization-seam ordering. The body\n * class is the consumer's `bodyClassName` when given (even `''`), else the\n * legacy default in the legacy world and empty in the new world (AD-C). Safe\n * to run repeatedly (iframe onLoad + the already-loaded fast path + the probe\n * settling): links/styles are deduped by id, the chrome CSS is re-composed in\n * place when the world flips, and the two token styles are re-appended IN\n * ORDER so a re-wire never flips who wins.\n */\nexport function applyCanvasChrome(d: CanvasChromeDocument, inputs: CanvasChromeInputs): void {\n if (!d.getElementById('ec-layer-order')) {\n const style = d.createElement('style')\n style.id = 'ec-layer-order'\n style.textContent = CANVAS_BACKDROP_LAYER_STATEMENT\n d.head.appendChild(style)\n }\n if (inputs.stylesheetUrl && !d.getElementById('ec-app-css')) {\n const link = d.createElement('link')\n link.id = 'ec-app-css'\n link.rel = 'stylesheet'\n link.href = inputs.stylesheetUrl\n d.head.appendChild(link)\n }\n const chromeCss = canvasChromeCss(inputs.world)\n const outline = d.getElementById('ec-outline-css')\n if (outline) {\n // Probe settled on the other world: re-compose in place (position stable).\n if (outline.textContent !== chromeCss) outline.textContent = chromeCss\n } else {\n const style = d.createElement('style')\n style.id = 'ec-outline-css'\n style.textContent = chromeCss\n d.head.appendChild(style)\n }\n // Both token layers re-append (remove + add) on every wire, in sequence, so\n // the consumer element always lands after the kit element.\n appendCssLayer(d, 'ec-kit-tokens', inputs.kitTokensCss)\n appendCssLayer(d, 'ec-canvas-theme', inputs.theme?.tokensCss)\n d.body.className =\n inputs.theme?.bodyClassName ?? (inputs.world === 'legacy' ? DEFAULT_CANVAS_BODY_CLASS : '')\n}\n\n/** Replace-append one identified `<style>` layer; absent CSS removes a stale element. */\nfunction appendCssLayer(d: CanvasChromeDocument, id: string, css: string | undefined): void {\n const existing = d.getElementById(id)\n if (existing) existing.remove()\n if (css === undefined || css === '') return\n const style = d.createElement('style')\n style.id = id\n style.textContent = css\n d.head.appendChild(style)\n}\n\n/** `applyCanvasChrome` inputs minus the probe-owned `world`, plus the settle callback. */\nexport interface WireCanvasChromeOptions {\n stylesheetUrl: string | null\n kitTokensCss?: string\n theme?: StudioCanvasTheme\n /**\n * Fired when the classification SETTLES — the stylesheet finished loading\n * (or failing), or there is no stylesheet to wait for. Feeds the AD-D dev\n * notice; may fire again on re-wires (callers dedupe by kind).\n */\n onStatus?: (status: CanvasThemeStatus) => void\n}\n\n/** Links whose settle listeners are already attached (re-wires must not stack them). */\nconst wiredLinks = new WeakSet<object>()\n\n/**\n * The DOM-level wiring around {@link applyCanvasChrome}: applies the chrome\n * immediately with the CURRENT classification, then re-applies once the\n * `ec-app-css` link settles (the AD-C probe reads computed custom properties,\n * which exist only after the stylesheet loads). Before the stylesheet arrives\n * neither world's styling is resolvable anyway (no consumer CSS yet), so the\n * pre-settle pass is visually neutral in both worlds.\n */\nexport function wireCanvasChrome(doc: Document, options: WireCanvasChromeOptions): void {\n const apply = (settled: boolean, loadFailed: boolean) => {\n const status: CanvasThemeStatus = options.stylesheetUrl\n ? probeCanvasTheme(doc, loadFailed)\n : { kind: 'no-stylesheet' }\n applyCanvasChrome(doc, {\n stylesheetUrl: options.stylesheetUrl,\n world: canvasWorldOf(status),\n ...(options.kitTokensCss !== undefined ? { kitTokensCss: options.kitTokensCss } : {}),\n ...(options.theme !== undefined ? { theme: options.theme } : {}),\n })\n if (settled) options.onStatus?.(status)\n }\n\n if (!options.stylesheetUrl) {\n apply(true, false)\n return\n }\n // First pass injects the link (and everything else) with the current sample.\n const already = doc.getElementById('ec-app-css') as HTMLLinkElement | null\n const loaded = already?.sheet != null\n apply(loaded, false)\n if (loaded) return\n const link = doc.getElementById('ec-app-css') as HTMLLinkElement | null\n if (!link || wiredLinks.has(link)) return\n wiredLinks.add(link)\n link.addEventListener('load', () => apply(true, false), { once: true })\n link.addEventListener('error', () => apply(true, true), { once: true })\n}\n"]}
|
|
@@ -81,3 +81,16 @@ export declare function useCanvasRenderContext(args: {
|
|
|
81
81
|
ctx: RenderContext;
|
|
82
82
|
pageNodeIds: ReadonlySet<string>;
|
|
83
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* In-canvas placeholder for a transcluding embed whose target doesn't resolve
|
|
86
|
+
* (EC-300/AD-16) — shown in place of the empty section so a hard reference never
|
|
87
|
+
* silently vanishes. Rendered INTO the iframe (portal), which carries only the
|
|
88
|
+
* project's stylesheet, so it uses inline styles rather than studio UI classes
|
|
89
|
+
* (the EC-414 AD-B chrome contract), with the `.ec-chrome` containment recipe
|
|
90
|
+
* on its root. Exported for the chrome contract test. The node stays
|
|
91
|
+
* selectable; the inspector's `element` relation chip (EC-296) deep-links to
|
|
92
|
+
* the reusable to edit or publish it.
|
|
93
|
+
*/
|
|
94
|
+
export declare function UnresolvedReusablePlaceholder({ status }: {
|
|
95
|
+
status: 'unpublished' | 'missing';
|
|
96
|
+
}): import("react").JSX.Element;
|
|
@@ -170,18 +170,20 @@ export function useCanvasRenderContext(args) {
|
|
|
170
170
|
* In-canvas placeholder for a transcluding embed whose target doesn't resolve
|
|
171
171
|
* (EC-300/AD-16) — shown in place of the empty section so a hard reference never
|
|
172
172
|
* silently vanishes. Rendered INTO the iframe (portal), which carries only the
|
|
173
|
-
* project's stylesheet, so it uses inline styles rather than studio UI classes
|
|
174
|
-
*
|
|
175
|
-
*
|
|
173
|
+
* project's stylesheet, so it uses inline styles rather than studio UI classes
|
|
174
|
+
* (the EC-414 AD-B chrome contract), with the `.ec-chrome` containment recipe
|
|
175
|
+
* on its root. Exported for the chrome contract test. The node stays
|
|
176
|
+
* selectable; the inspector's `element` relation chip (EC-296) deep-links to
|
|
177
|
+
* the reusable to edit or publish it.
|
|
176
178
|
*/
|
|
177
|
-
function UnresolvedReusablePlaceholder({ status }) {
|
|
179
|
+
export function UnresolvedReusablePlaceholder({ status }) {
|
|
178
180
|
const title = status === 'unpublished'
|
|
179
181
|
? translate('builder.canvas.reusableUnpublishedTitle')
|
|
180
182
|
: translate('builder.canvas.reusableMissingTitle');
|
|
181
183
|
const hint = status === 'unpublished'
|
|
182
184
|
? translate('builder.canvas.reusableUnpublishedHint')
|
|
183
185
|
: translate('builder.canvas.reusableMissingHint');
|
|
184
|
-
return (_jsxs("div", { style: {
|
|
186
|
+
return (_jsxs("div", { className: "ec-chrome", style: {
|
|
185
187
|
boxSizing: 'border-box',
|
|
186
188
|
border: '1px dashed rgba(120,120,120,0.5)',
|
|
187
189
|
borderRadius: 8,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas-render-context.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-render-context.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type {\n RenderContext,\n ResolveAsset,\n ResolveBinding,\n ResolveLink,\n ResolveListing,\n ResolveRelation,\n} from '@elytracms/core/runtime-renderer'\nimport type { BindingReference, ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport {\n resolveCanvasLink,\n resolveCanvasListing,\n resolveCanvasRelation,\n useData,\n} from '@elytracms/studio/lib/data'\nimport type { CanvasPerspective } from '@elytracms/studio/lib/data'\nimport { cmsBindingFallback, useStudioLocale } from '@elytracms/studio/lib/locale'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { useReuse } from './reuse-context'\nimport { reuseEmbedRef } from './reuse'\nimport type { ReuseCapability } from './reuse'\n\n/**\n * The canvas's `RenderContext` (EC-330, extracted from `BuilderCanvas`): the\n * seven injected resolvers that make the iframe preview match delivery —\n * bindings, assets (EC-195), relations (EC-254), listings (EC-255), rich-text\n * links (EC-291) — plus the per-node decorator that stamps the editable\n * wrappers (selection/hover state, locale-fallback chips EC-176, rich-text\n * remount epochs, and the EC-300 unresolved-reusable placeholder).\n *\n * The context assembly is a pure factory ({@link createCanvasRenderContext})\n * over pure decision helpers ({@link unresolvedReuseStatus},\n * {@link firstBindingFallbackLocale}), so the decoration and resolver wiring\n * are unit-testable without providers; {@link useCanvasRenderContext} binds\n * them to the live stores.\n */\n\n/** Collect every node id under a root (the page subtree). */\nexport function collectIds(node: ComponentNode, out: Set<string> = new Set()): Set<string> {\n out.add(node.id)\n for (const children of Object.values(node.slots ?? {})) {\n for (const child of children) collectIds(child, out)\n }\n return out\n}\n\n/**\n * EC-300/AD-16: a transcluding embed whose target doesn't resolve in the active\n * perspective renders a placeholder, never an empty section. `unpublished` = the\n * target exists as a draft but isn't live (Published view of a draft-only reusable);\n * `missing` = no resolvable target at all (deleted). `null` = resolves fine, or the\n * node isn't an embed / has no target picked yet (its own empty state owns that).\n */\nexport function unresolvedReuseStatus(\n node: ComponentNode,\n capability: ReuseCapability | null | undefined,\n resolveAt: (ref: { collection: string; id: string }, perspective: CanvasPerspective) => unknown,\n perspective: CanvasPerspective,\n): 'unpublished' | 'missing' | null {\n if (!capability) return null\n const ref = reuseEmbedRef(node, capability)\n if (!ref) return null\n if (resolveAt(ref, perspective)) return null\n return resolveAt(ref, 'draft') ? 'unpublished' : 'missing'\n}\n\n/**\n * Locale-fallback chips (EC-176): the locale the node's first fallback-served\n * bound prop actually came from, or undefined when every bound value is a real\n * translation (or nothing is bound). Pure over an injected per-binding decision\n * (the hook wires `cmsBindingFallback` — the same decision function as the\n * inspector's Content tab, so chip and label can never disagree).\n */\nexport function firstBindingFallbackLocale(\n node: ComponentNode,\n fallbackOf: (binding: BindingReference) => { sourceLocale: string } | null,\n): string | undefined {\n for (const value of Object.values(node.props ?? {})) {\n if (value.kind !== 'binding') continue\n const fallback = fallbackOf(value.binding)\n if (fallback) return fallback.sourceLocale\n }\n return undefined\n}\n\n/** Everything the pure context factory needs — resolvers plus decoration state. */\nexport interface CanvasRenderContextDeps {\n registry: RenderContext['registry']\n implementations: RenderContext['implementations']\n resolveBinding: ResolveBinding\n resolveAsset: ResolveAsset\n resolveRelation: ResolveRelation\n resolveListing: ResolveListing\n resolveLink: ResolveLink\n /** Only page nodes are editable; layout chrome must not be decorated. */\n pageNodeIds: ReadonlySet<string>\n selectedId: string | null\n hoveredId: string | null\n /** Per-node remount epochs from the inline rich-text editing (EC-159). */\n richEditEpochs: Record<string, number>\n unresolvedReuse(node: ComponentNode): 'unpublished' | 'missing' | null\n localeFallbackOf(node: ComponentNode): string | undefined\n}\n\n/**\n * Assemble the canvas `RenderContext`. Pure (no hooks): the same deps produce\n * the same context, so the decorator's wrapper markup is pinnable in tests.\n */\nexport function createCanvasRenderContext(deps: CanvasRenderContextDeps): RenderContext {\n return {\n registry: deps.registry,\n implementations: deps.implementations,\n resolveBinding: deps.resolveBinding,\n resolveAsset: deps.resolveAsset,\n resolveRelation: deps.resolveRelation,\n resolveListing: deps.resolveListing,\n resolveLink: deps.resolveLink,\n decorateNode: (node, rendered) => {\n if (!deps.pageNodeIds.has(node.id)) return rendered\n // An unresolved hard reference shows the placeholder in place of the empty\n // transclusion; the node stays selectable, so its inspector relation chip\n // (EC-296) deep-links to the reusable to edit/publish it.\n const unresolved = deps.unresolvedReuse(node)\n return (\n <div\n // The epoch suffix forces a remount after a rich-text editing\n // session mutated this node's rendered DOM (see richEditEpochs).\n key={`${node.id}#${deps.richEditEpochs[node.id] ?? 0}`}\n data-ec-node-id={node.id}\n className=\"ec-editable\"\n data-selected={node.id === deps.selectedId || undefined}\n data-hovered={node.id === deps.hoveredId || undefined}\n data-ec-locale-fallback={deps.localeFallbackOf(node)}\n >\n {unresolved ? <UnresolvedReusablePlaceholder status={unresolved} /> : rendered}\n </div>\n )\n },\n }\n}\n\n/**\n * The live canvas render context: binds the pure factory to the studio's\n * stores (data sources, asset library, active locale, reuse capability) for\n * the given perspective. Returns the context plus the page-node id set the\n * canvas also needs for spacing-handle gating.\n */\nexport function useCanvasRenderContext(args: {\n /** The edited composition root (`builder.present`). */\n present: ComponentNode\n selectedId: string | null\n hoveredId: string | null\n studio: StudioRegistry\n perspective: CanvasPerspective\n richEditEpochs: Record<string, number>\n}): { ctx: RenderContext; pageNodeIds: ReadonlySet<string> } {\n const { present, selectedId, hoveredId, studio, perspective, richEditEpochs } = args\n const data = useData()\n\n // Only page nodes are editable; layout chrome (rendered around the page) is\n // read-only context and must not be selectable (it isn't in the page tree).\n const pageNodeIds = useMemo(() => collectIds(present), [present])\n\n // Bridge the renderer's untyped ItemContext (item: unknown) to the binding\n // layer's RepeaterItemContext (item: JsonValue). The renderer feeds back values\n // it resolved from JSON payloads, so the item is a JsonValue.\n const resolveBinding: ResolveBinding = useCallback(\n (ref, item) =>\n data.resolveValue(ref, item ? { item: item.item as JsonValue, index: item.index } : undefined),\n [data],\n )\n\n // EC-195: resolve an `asset`-typed Image prop (an asset id) to its url +\n // intrinsic dimensions from the live asset library, so the canvas previews\n // exactly what the host renders. A record without a resolved url (e.g. a\n // never-served seed) → null → the implementation's visible empty state.\n const assetsStore = useOptionalAssets()\n const resolveAsset: ResolveAsset = useCallback(\n (assetId) => {\n const record = assetsStore?.assets.find((a) => a.id === assetId)\n if (!record?.url) return null\n return {\n url: record.url,\n width: record.width ?? null,\n height: record.height ?? null,\n alt: record.alt ?? null,\n focalPoint: record.focalPoint ?? null,\n }\n },\n [assetsStore],\n )\n\n // Locale-fallback chips (EC-176): a node whose bound content currently\n // previews via EC-018 locale fallback wears a corner chip naming the locale\n // the value actually came from. Same decision function as the inspector's\n // Content tab (`cmsBindingFallback`), so chip and label can never disagree.\n const { locale: activeLocale } = useStudioLocale()\n // EC-300: the reuse capability identifies a transcluding embed + its relation prop,\n // so the canvas can detect a HARD reference whose target doesn't resolve and show a\n // placeholder instead of an empty section. Null-safe (no provider / no capability).\n const reuse = useReuse()\n\n // EC-254: populate a block's `relation` prop (the editor's document pick — a\n // ProductGrid's products, a Reusable's element) to delivery shape from the\n // active content session, in the canvas's active locale, so the canvas previews\n // picked documents exactly as the host renders them. The studio analog of the\n // host's baked relations map. A missing/invisible target → null → the block's\n // own empty state.\n const resolveRelation: ResolveRelation = useCallback(\n (ref) => resolveCanvasRelation(ref, activeLocale, perspective),\n [activeLocale, perspective],\n )\n\n // EC-255: resolve a listing block's query (a ProductGrid keyed to a category) to\n // its documents from the active content session, in the canvas's active locale, so\n // the canvas previews a dynamic grid exactly as the host renders it. The studio\n // analog of the host's baked listings map; an empty/invalid query → [] → the\n // block's own empty state.\n const resolveListing: ResolveListing = useCallback(\n (query) => resolveCanvasListing(query, activeLocale, perspective),\n [activeLocale, perspective],\n )\n\n // EC-291: resolve a rich-text internal link (`{type:'reference'}`) to its target's\n // URL path from the active content session, in the canvas's active locale — the\n // studio analog of the host's baked links map. A missing/unpublished/unroutable\n // target → null → the rich-text renderer shows the visible `unresolved-ref` issue.\n const resolveLink: ResolveLink = useCallback(\n (ref) => resolveCanvasLink(ref, activeLocale, perspective),\n [activeLocale, perspective],\n )\n\n const unresolvedReuse = useCallback(\n (node: ComponentNode): 'unpublished' | 'missing' | null =>\n unresolvedReuseStatus(\n node,\n reuse?.capability,\n (ref, p) => resolveCanvasRelation(ref, activeLocale, p),\n perspective,\n ),\n [reuse, activeLocale, perspective],\n )\n\n const localeFallbackOf = useCallback(\n (node: ComponentNode): string | undefined =>\n firstBindingFallbackLocale(node, (binding) =>\n cmsBindingFallback(binding, data.getSource(binding.sourceId), activeLocale),\n ),\n [data, activeLocale],\n )\n\n const ctx: RenderContext = useMemo(\n () =>\n createCanvasRenderContext({\n registry: studio.registry,\n implementations: studio.implementations,\n resolveBinding,\n resolveAsset,\n resolveRelation,\n resolveListing,\n resolveLink,\n pageNodeIds,\n selectedId,\n hoveredId,\n richEditEpochs,\n unresolvedReuse,\n localeFallbackOf,\n }),\n [studio, resolveBinding, resolveAsset, resolveRelation, resolveListing, resolveLink, unresolvedReuse, pageNodeIds, selectedId, hoveredId, localeFallbackOf, richEditEpochs],\n )\n\n return { ctx, pageNodeIds }\n}\n\n/**\n * In-canvas placeholder for a transcluding embed whose target doesn't resolve\n * (EC-300/AD-16) — shown in place of the empty section so a hard reference never\n * silently vanishes. Rendered INTO the iframe (portal), which carries only the\n * project's stylesheet, so it uses inline styles rather than studio UI classes.\n * The node stays selectable; the inspector's `element` relation chip (EC-296)\n * deep-links to the reusable to edit or publish it.\n */\nfunction UnresolvedReusablePlaceholder({ status }: { status: 'unpublished' | 'missing' }) {\n const title =\n status === 'unpublished'\n ? translate('builder.canvas.reusableUnpublishedTitle')\n : translate('builder.canvas.reusableMissingTitle')\n const hint =\n status === 'unpublished'\n ? translate('builder.canvas.reusableUnpublishedHint')\n : translate('builder.canvas.reusableMissingHint')\n return (\n <div\n style={{\n boxSizing: 'border-box',\n border: '1px dashed rgba(120,120,120,0.5)',\n borderRadius: 8,\n padding: '24px 28px',\n margin: '12px 16px',\n background: 'rgba(127,127,127,0.06)',\n color: 'rgba(90,90,90,0.95)',\n font: '14px/1.5 system-ui, -apple-system, sans-serif',\n textAlign: 'center',\n }}\n >\n <div style={{ fontWeight: 600 }}>⚠ {title}</div>\n <div style={{ marginTop: 6, fontSize: 13, opacity: 0.85 }}>{hint}</div>\n </div>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"canvas-render-context.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-render-context.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type {\n RenderContext,\n ResolveAsset,\n ResolveBinding,\n ResolveLink,\n ResolveListing,\n ResolveRelation,\n} from '@elytracms/core/runtime-renderer'\nimport type { BindingReference, ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport {\n resolveCanvasLink,\n resolveCanvasListing,\n resolveCanvasRelation,\n useData,\n} from '@elytracms/studio/lib/data'\nimport type { CanvasPerspective } from '@elytracms/studio/lib/data'\nimport { cmsBindingFallback, useStudioLocale } from '@elytracms/studio/lib/locale'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { useReuse } from './reuse-context'\nimport { reuseEmbedRef } from './reuse'\nimport type { ReuseCapability } from './reuse'\n\n/**\n * The canvas's `RenderContext` (EC-330, extracted from `BuilderCanvas`): the\n * seven injected resolvers that make the iframe preview match delivery —\n * bindings, assets (EC-195), relations (EC-254), listings (EC-255), rich-text\n * links (EC-291) — plus the per-node decorator that stamps the editable\n * wrappers (selection/hover state, locale-fallback chips EC-176, rich-text\n * remount epochs, and the EC-300 unresolved-reusable placeholder).\n *\n * The context assembly is a pure factory ({@link createCanvasRenderContext})\n * over pure decision helpers ({@link unresolvedReuseStatus},\n * {@link firstBindingFallbackLocale}), so the decoration and resolver wiring\n * are unit-testable without providers; {@link useCanvasRenderContext} binds\n * them to the live stores.\n */\n\n/** Collect every node id under a root (the page subtree). */\nexport function collectIds(node: ComponentNode, out: Set<string> = new Set()): Set<string> {\n out.add(node.id)\n for (const children of Object.values(node.slots ?? {})) {\n for (const child of children) collectIds(child, out)\n }\n return out\n}\n\n/**\n * EC-300/AD-16: a transcluding embed whose target doesn't resolve in the active\n * perspective renders a placeholder, never an empty section. `unpublished` = the\n * target exists as a draft but isn't live (Published view of a draft-only reusable);\n * `missing` = no resolvable target at all (deleted). `null` = resolves fine, or the\n * node isn't an embed / has no target picked yet (its own empty state owns that).\n */\nexport function unresolvedReuseStatus(\n node: ComponentNode,\n capability: ReuseCapability | null | undefined,\n resolveAt: (ref: { collection: string; id: string }, perspective: CanvasPerspective) => unknown,\n perspective: CanvasPerspective,\n): 'unpublished' | 'missing' | null {\n if (!capability) return null\n const ref = reuseEmbedRef(node, capability)\n if (!ref) return null\n if (resolveAt(ref, perspective)) return null\n return resolveAt(ref, 'draft') ? 'unpublished' : 'missing'\n}\n\n/**\n * Locale-fallback chips (EC-176): the locale the node's first fallback-served\n * bound prop actually came from, or undefined when every bound value is a real\n * translation (or nothing is bound). Pure over an injected per-binding decision\n * (the hook wires `cmsBindingFallback` — the same decision function as the\n * inspector's Content tab, so chip and label can never disagree).\n */\nexport function firstBindingFallbackLocale(\n node: ComponentNode,\n fallbackOf: (binding: BindingReference) => { sourceLocale: string } | null,\n): string | undefined {\n for (const value of Object.values(node.props ?? {})) {\n if (value.kind !== 'binding') continue\n const fallback = fallbackOf(value.binding)\n if (fallback) return fallback.sourceLocale\n }\n return undefined\n}\n\n/** Everything the pure context factory needs — resolvers plus decoration state. */\nexport interface CanvasRenderContextDeps {\n registry: RenderContext['registry']\n implementations: RenderContext['implementations']\n resolveBinding: ResolveBinding\n resolveAsset: ResolveAsset\n resolveRelation: ResolveRelation\n resolveListing: ResolveListing\n resolveLink: ResolveLink\n /** Only page nodes are editable; layout chrome must not be decorated. */\n pageNodeIds: ReadonlySet<string>\n selectedId: string | null\n hoveredId: string | null\n /** Per-node remount epochs from the inline rich-text editing (EC-159). */\n richEditEpochs: Record<string, number>\n unresolvedReuse(node: ComponentNode): 'unpublished' | 'missing' | null\n localeFallbackOf(node: ComponentNode): string | undefined\n}\n\n/**\n * Assemble the canvas `RenderContext`. Pure (no hooks): the same deps produce\n * the same context, so the decorator's wrapper markup is pinnable in tests.\n */\nexport function createCanvasRenderContext(deps: CanvasRenderContextDeps): RenderContext {\n return {\n registry: deps.registry,\n implementations: deps.implementations,\n resolveBinding: deps.resolveBinding,\n resolveAsset: deps.resolveAsset,\n resolveRelation: deps.resolveRelation,\n resolveListing: deps.resolveListing,\n resolveLink: deps.resolveLink,\n decorateNode: (node, rendered) => {\n if (!deps.pageNodeIds.has(node.id)) return rendered\n // An unresolved hard reference shows the placeholder in place of the empty\n // transclusion; the node stays selectable, so its inspector relation chip\n // (EC-296) deep-links to the reusable to edit/publish it.\n const unresolved = deps.unresolvedReuse(node)\n return (\n <div\n // The epoch suffix forces a remount after a rich-text editing\n // session mutated this node's rendered DOM (see richEditEpochs).\n key={`${node.id}#${deps.richEditEpochs[node.id] ?? 0}`}\n data-ec-node-id={node.id}\n className=\"ec-editable\"\n data-selected={node.id === deps.selectedId || undefined}\n data-hovered={node.id === deps.hoveredId || undefined}\n data-ec-locale-fallback={deps.localeFallbackOf(node)}\n >\n {unresolved ? <UnresolvedReusablePlaceholder status={unresolved} /> : rendered}\n </div>\n )\n },\n }\n}\n\n/**\n * The live canvas render context: binds the pure factory to the studio's\n * stores (data sources, asset library, active locale, reuse capability) for\n * the given perspective. Returns the context plus the page-node id set the\n * canvas also needs for spacing-handle gating.\n */\nexport function useCanvasRenderContext(args: {\n /** The edited composition root (`builder.present`). */\n present: ComponentNode\n selectedId: string | null\n hoveredId: string | null\n studio: StudioRegistry\n perspective: CanvasPerspective\n richEditEpochs: Record<string, number>\n}): { ctx: RenderContext; pageNodeIds: ReadonlySet<string> } {\n const { present, selectedId, hoveredId, studio, perspective, richEditEpochs } = args\n const data = useData()\n\n // Only page nodes are editable; layout chrome (rendered around the page) is\n // read-only context and must not be selectable (it isn't in the page tree).\n const pageNodeIds = useMemo(() => collectIds(present), [present])\n\n // Bridge the renderer's untyped ItemContext (item: unknown) to the binding\n // layer's RepeaterItemContext (item: JsonValue). The renderer feeds back values\n // it resolved from JSON payloads, so the item is a JsonValue.\n const resolveBinding: ResolveBinding = useCallback(\n (ref, item) =>\n data.resolveValue(ref, item ? { item: item.item as JsonValue, index: item.index } : undefined),\n [data],\n )\n\n // EC-195: resolve an `asset`-typed Image prop (an asset id) to its url +\n // intrinsic dimensions from the live asset library, so the canvas previews\n // exactly what the host renders. A record without a resolved url (e.g. a\n // never-served seed) → null → the implementation's visible empty state.\n const assetsStore = useOptionalAssets()\n const resolveAsset: ResolveAsset = useCallback(\n (assetId) => {\n const record = assetsStore?.assets.find((a) => a.id === assetId)\n if (!record?.url) return null\n return {\n url: record.url,\n width: record.width ?? null,\n height: record.height ?? null,\n alt: record.alt ?? null,\n focalPoint: record.focalPoint ?? null,\n }\n },\n [assetsStore],\n )\n\n // Locale-fallback chips (EC-176): a node whose bound content currently\n // previews via EC-018 locale fallback wears a corner chip naming the locale\n // the value actually came from. Same decision function as the inspector's\n // Content tab (`cmsBindingFallback`), so chip and label can never disagree.\n const { locale: activeLocale } = useStudioLocale()\n // EC-300: the reuse capability identifies a transcluding embed + its relation prop,\n // so the canvas can detect a HARD reference whose target doesn't resolve and show a\n // placeholder instead of an empty section. Null-safe (no provider / no capability).\n const reuse = useReuse()\n\n // EC-254: populate a block's `relation` prop (the editor's document pick — a\n // ProductGrid's products, a Reusable's element) to delivery shape from the\n // active content session, in the canvas's active locale, so the canvas previews\n // picked documents exactly as the host renders them. The studio analog of the\n // host's baked relations map. A missing/invisible target → null → the block's\n // own empty state.\n const resolveRelation: ResolveRelation = useCallback(\n (ref) => resolveCanvasRelation(ref, activeLocale, perspective),\n [activeLocale, perspective],\n )\n\n // EC-255: resolve a listing block's query (a ProductGrid keyed to a category) to\n // its documents from the active content session, in the canvas's active locale, so\n // the canvas previews a dynamic grid exactly as the host renders it. The studio\n // analog of the host's baked listings map; an empty/invalid query → [] → the\n // block's own empty state.\n const resolveListing: ResolveListing = useCallback(\n (query) => resolveCanvasListing(query, activeLocale, perspective),\n [activeLocale, perspective],\n )\n\n // EC-291: resolve a rich-text internal link (`{type:'reference'}`) to its target's\n // URL path from the active content session, in the canvas's active locale — the\n // studio analog of the host's baked links map. A missing/unpublished/unroutable\n // target → null → the rich-text renderer shows the visible `unresolved-ref` issue.\n const resolveLink: ResolveLink = useCallback(\n (ref) => resolveCanvasLink(ref, activeLocale, perspective),\n [activeLocale, perspective],\n )\n\n const unresolvedReuse = useCallback(\n (node: ComponentNode): 'unpublished' | 'missing' | null =>\n unresolvedReuseStatus(\n node,\n reuse?.capability,\n (ref, p) => resolveCanvasRelation(ref, activeLocale, p),\n perspective,\n ),\n [reuse, activeLocale, perspective],\n )\n\n const localeFallbackOf = useCallback(\n (node: ComponentNode): string | undefined =>\n firstBindingFallbackLocale(node, (binding) =>\n cmsBindingFallback(binding, data.getSource(binding.sourceId), activeLocale),\n ),\n [data, activeLocale],\n )\n\n const ctx: RenderContext = useMemo(\n () =>\n createCanvasRenderContext({\n registry: studio.registry,\n implementations: studio.implementations,\n resolveBinding,\n resolveAsset,\n resolveRelation,\n resolveListing,\n resolveLink,\n pageNodeIds,\n selectedId,\n hoveredId,\n richEditEpochs,\n unresolvedReuse,\n localeFallbackOf,\n }),\n [studio, resolveBinding, resolveAsset, resolveRelation, resolveListing, resolveLink, unresolvedReuse, pageNodeIds, selectedId, hoveredId, localeFallbackOf, richEditEpochs],\n )\n\n return { ctx, pageNodeIds }\n}\n\n/**\n * In-canvas placeholder for a transcluding embed whose target doesn't resolve\n * (EC-300/AD-16) — shown in place of the empty section so a hard reference never\n * silently vanishes. Rendered INTO the iframe (portal), which carries only the\n * project's stylesheet, so it uses inline styles rather than studio UI classes\n * (the EC-414 AD-B chrome contract), with the `.ec-chrome` containment recipe\n * on its root. Exported for the chrome contract test. The node stays\n * selectable; the inspector's `element` relation chip (EC-296) deep-links to\n * the reusable to edit or publish it.\n */\nexport function UnresolvedReusablePlaceholder({ status }: { status: 'unpublished' | 'missing' }) {\n const title =\n status === 'unpublished'\n ? translate('builder.canvas.reusableUnpublishedTitle')\n : translate('builder.canvas.reusableMissingTitle')\n const hint =\n status === 'unpublished'\n ? translate('builder.canvas.reusableUnpublishedHint')\n : translate('builder.canvas.reusableMissingHint')\n return (\n <div\n className=\"ec-chrome\"\n style={{\n boxSizing: 'border-box',\n border: '1px dashed rgba(120,120,120,0.5)',\n borderRadius: 8,\n padding: '24px 28px',\n margin: '12px 16px',\n background: 'rgba(127,127,127,0.06)',\n color: 'rgba(90,90,90,0.95)',\n font: '14px/1.5 system-ui, -apple-system, sans-serif',\n textAlign: 'center',\n }}\n >\n <div style={{ fontWeight: 600 }}>⚠ {title}</div>\n <div style={{ marginTop: 6, fontSize: 13, opacity: 0.85 }}>{hint}</div>\n </div>\n )\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** The consuming app supplies its compiled
|
|
1
|
+
/** The consuming app supplies its compiled canvas stylesheet URL at mount. */
|
|
2
2
|
export declare function setCanvasStylesheetUrl(url: string): void;
|
|
3
3
|
/** The injected stylesheet URL, or `null` when the app has not supplied one. */
|
|
4
4
|
export declare function getCanvasStylesheetUrl(): string | null;
|
|
@@ -3,17 +3,24 @@
|
|
|
3
3
|
* The canvas renders the project graph inside an ISOLATED iframe, so this stylesheet is
|
|
4
4
|
* scoped to that document alone — it is NOT the studio chrome stylesheet, and reusing the
|
|
5
5
|
* chrome's stylesheet here is a mistake (it makes the consuming app's delivery CSS and the
|
|
6
|
-
* studio UI share one file, the only way a leak between them becomes possible).
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
6
|
+
* studio UI share one file, the only way a leak between them becomes possible).
|
|
7
|
+
*
|
|
8
|
+
* EC-414 (AD-A): the preview stylesheet is the consuming app's DELIVERY theme — its
|
|
9
|
+
* standalone full-Tailwind theme entry (`components/theme.css`, or the frontend's own
|
|
10
|
+
* self-contained entry), compiled by the app's own build. The studio design system does
|
|
11
|
+
* NOT belong in it: everything the studio portals into the iframe (RichTextToolbar,
|
|
12
|
+
* SpacingHandles, placeholders) is styled by the engine-injected chrome stylesheet
|
|
13
|
+
* (`canvas-chrome-css.ts`), so the canvas resolves exactly what delivery resolves.
|
|
14
|
+
* Legacy merged stylesheets (theme + `@elytracms/studio/styles.css` in one build) keep
|
|
15
|
+
* working via the runtime world probe in `canvas-chrome.ts` (AD-C).
|
|
16
|
+
*
|
|
17
|
+
* The URL is a BUILD artifact of the consuming app, so the package cannot import it —
|
|
18
|
+
* the app injects its own compiled URL once at mount via {@link setCanvasStylesheetUrl},
|
|
19
|
+
* and the canvas reads it here. Unset (e.g. tests / SSR) → the canvas simply skips the
|
|
20
|
+
* stylesheet link (and dev builds surface the explicit `no-stylesheet` state).
|
|
14
21
|
*/
|
|
15
22
|
let stylesheetUrl = null;
|
|
16
|
-
/** The consuming app supplies its compiled
|
|
23
|
+
/** The consuming app supplies its compiled canvas stylesheet URL at mount. */
|
|
17
24
|
export function setCanvasStylesheetUrl(url) {
|
|
18
25
|
stylesheetUrl = url;
|
|
19
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas-stylesheet.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-stylesheet.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"canvas-stylesheet.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-stylesheet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,IAAI,aAAa,GAAkB,IAAI,CAAA;AAEvC,8EAA8E;AAC9E,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAChD,aAAa,GAAG,GAAG,CAAA;AACrB,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,sBAAsB;IACpC,OAAO,aAAa,CAAA;AACtB,CAAC","sourcesContent":["/**\n * The PREVIEW stylesheet URL, injected into the canvas iframe (EC-117 / EC-223 Stage 4).\n * The canvas renders the project graph inside an ISOLATED iframe, so this stylesheet is\n * scoped to that document alone — it is NOT the studio chrome stylesheet, and reusing the\n * chrome's stylesheet here is a mistake (it makes the consuming app's delivery CSS and the\n * studio UI share one file, the only way a leak between them becomes possible).\n *\n * EC-414 (AD-A): the preview stylesheet is the consuming app's DELIVERY theme — its\n * standalone full-Tailwind theme entry (`components/theme.css`, or the frontend's own\n * self-contained entry), compiled by the app's own build. The studio design system does\n * NOT belong in it: everything the studio portals into the iframe (RichTextToolbar,\n * SpacingHandles, placeholders) is styled by the engine-injected chrome stylesheet\n * (`canvas-chrome-css.ts`), so the canvas resolves exactly what delivery resolves.\n * Legacy merged stylesheets (theme + `@elytracms/studio/styles.css` in one build) keep\n * working via the runtime world probe in `canvas-chrome.ts` (AD-C).\n *\n * The URL is a BUILD artifact of the consuming app, so the package cannot import it —\n * the app injects its own compiled URL once at mount via {@link setCanvasStylesheetUrl},\n * and the canvas reads it here. Unset (e.g. tests / SSR) → the canvas simply skips the\n * stylesheet link (and dev builds surface the explicit `no-stylesheet` state).\n */\nlet stylesheetUrl: string | null = null\n\n/** The consuming app supplies its compiled canvas stylesheet URL at mount. */\nexport function setCanvasStylesheetUrl(url: string): void {\n stylesheetUrl = url\n}\n\n/** The injected stylesheet URL, or `null` when the app has not supplied one. */\nexport function getCanvasStylesheetUrl(): string | null {\n return stylesheetUrl\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CanvasThemeStatus } from './canvas-chrome.js';
|
|
2
|
+
/** Test hook: reset the session-sticky dismissals. */
|
|
3
|
+
export declare function resetCanvasThemeNoticeForTests(): void;
|
|
4
|
+
export declare function CanvasThemeNotice({ status }: {
|
|
5
|
+
status: CanvasThemeStatus | null;
|
|
6
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { AlertTriangle, X } from 'lucide-react';
|
|
4
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
5
|
+
import { isDevRuntime } from '@elytracms/studio/lib/persistence';
|
|
6
|
+
import { Row, Text } from '@elytracms/studio/ui';
|
|
7
|
+
/**
|
|
8
|
+
* EC-414 AD-D item 2 — the canvas-theme runtime notice, in the established
|
|
9
|
+
* validation-surface style (the `ClipboardNoticeOverlay` shape: a dismissible,
|
|
10
|
+
* NEVER-blocking overlay above the canvas, in the STUDIO document — studio UI
|
|
11
|
+
* primitives are fine here, this is not in-frame chrome). Dev builds only:
|
|
12
|
+
*
|
|
13
|
+
* - `legacy`: the probe found the merged pre-EC-414 canvas.css — a one-time
|
|
14
|
+
* notice naming the migration (the canvas keeps working exactly as before).
|
|
15
|
+
* - `stylesheet-error` / `no-theme-tokens` / `no-stylesheet`: the canvas
|
|
16
|
+
* stylesheet is broken or missing — the invalid state is named explicitly
|
|
17
|
+
* instead of silently rendering unstyled blocks.
|
|
18
|
+
*
|
|
19
|
+
* "One-time": dismissing a kind suppresses it for the rest of the studio
|
|
20
|
+
* session (module state survives canvas remounts/navigation).
|
|
21
|
+
*/
|
|
22
|
+
const dismissedKinds = new Set();
|
|
23
|
+
/** Test hook: reset the session-sticky dismissals. */
|
|
24
|
+
export function resetCanvasThemeNoticeForTests() {
|
|
25
|
+
dismissedKinds.clear();
|
|
26
|
+
}
|
|
27
|
+
const NOTICE_KEYS = {
|
|
28
|
+
legacy: 'builder.canvasTheme.legacyNotice',
|
|
29
|
+
'stylesheet-error': 'builder.canvasTheme.stylesheetErrorNotice',
|
|
30
|
+
'no-theme-tokens': 'builder.canvasTheme.noThemeTokensNotice',
|
|
31
|
+
'no-stylesheet': 'builder.canvasTheme.noStylesheetNotice',
|
|
32
|
+
};
|
|
33
|
+
export function CanvasThemeNotice({ status }) {
|
|
34
|
+
// Local state only forces the re-render on dismiss; the truth is module-level.
|
|
35
|
+
const [, setDismissedAt] = useState(0);
|
|
36
|
+
if (!status || !isDevRuntime())
|
|
37
|
+
return null;
|
|
38
|
+
const messageKey = NOTICE_KEYS[status.kind];
|
|
39
|
+
if (!messageKey || dismissedKinds.has(status.kind))
|
|
40
|
+
return null;
|
|
41
|
+
return (_jsx("div", { "data-testid": "canvas-theme-notice", "data-notice-kind": status.kind, className: "absolute top-4 left-1/2 z-20 w-max max-w-[min(460px,90%)] -translate-x-1/2", children: _jsx("div", { className: "rounded-lg border border-border bg-popover p-2 text-popover-foreground shadow-lg", children: _jsxs(Row, { gap: "2", align: "start", children: [_jsx(AlertTriangle, { className: "mt-0.5 size-3.5 shrink-0 text-warning", "aria-hidden": true }), _jsx(Text, { size: "xs", className: "min-w-0 flex-1", children: translate(messageKey) }), _jsx("button", { type: "button", "aria-label": translate('common.dismiss'), onClick: () => {
|
|
42
|
+
dismissedKinds.add(status.kind);
|
|
43
|
+
setDismissedAt(Date.now());
|
|
44
|
+
}, className: "shrink-0 rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground", children: _jsx(X, { className: "size-3.5" }) })] }) }) }));
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=canvas-theme-notice.js.map
|