@pure-ds/core 0.7.60 → 0.7.61
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/types/public/assets/pds/components/pds-render.d.ts +21 -0
- package/dist/types/public/assets/pds/components/pds-render.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-config.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-enhancers-meta.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
- package/package.json +1 -1
- package/packages/pds-cli/bin/pds-setup-mcp.js +27 -115
- package/packages/pds-cli/bin/postinstall.mjs +16 -0
- package/packages/pds-cli/lib/pds-mcp-core.js +150 -3
- package/packages/pds-cli/lib/setup-mcp-config.js +72 -0
- package/public/assets/js/app.js +1 -1
- package/public/assets/js/pds-enhancers.js +1 -1
- package/public/assets/js/pds-manager.js +89 -54
- package/public/assets/pds/components/pds-render.js +768 -0
- package/public/assets/pds/core/pds-enhancers.js +1 -1
- package/public/assets/pds/core/pds-manager.js +89 -54
- package/public/assets/pds/icons/pds-icons.svg +8 -0
- package/src/js/pds-core/pds-config.js +2 -0
- package/src/js/pds-core/pds-enhancers-meta.js +6 -0
- package/src/js/pds-core/pds-enhancers.js +60 -0
- package/src/js/pds-core/pds-generator.js +35 -0
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
import { PDS, html, State } from "#pds";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* <pds-render> - Pure Design System sandboxed renderer
|
|
5
|
+
*
|
|
6
|
+
* A first-class PDS web component that embeds an isolated iframe with full PDS
|
|
7
|
+
* support: tokens, components, enhancements, theme switching, and preset switching.
|
|
8
|
+
*
|
|
9
|
+
* Attributes (observed, reactive):
|
|
10
|
+
* theme - "light" | "dark" | "system" (default: "light")
|
|
11
|
+
* preset - any PDS preset ID (default: "default")
|
|
12
|
+
* height - CSS length for iframe height (default: "200px")
|
|
13
|
+
* src-base - base URL for PDS assets (default: auto-detected)
|
|
14
|
+
* padding - body padding inside iframe (default: "1rem")
|
|
15
|
+
* background - override body background (default: "")
|
|
16
|
+
* viewport - "mobile" (375 px) | "tablet" (768 px) | "desktop" (window width)
|
|
17
|
+
* sets the virtual viewport width for auto-zoom (default: "desktop")
|
|
18
|
+
* locale - BCP-47 language tag set on <html lang> (default: "en")
|
|
19
|
+
* expanded - forces fullscreen preview mode when present
|
|
20
|
+
*
|
|
21
|
+
* Properties (live-update without full reload):
|
|
22
|
+
* html - HTML string to render in the iframe body
|
|
23
|
+
* css - CSS string injected as <style> in the iframe
|
|
24
|
+
* js - JS module string executed after PDS is ready
|
|
25
|
+
* JS changes force a full iframe reload
|
|
26
|
+
* expanded - reflected fullscreen state
|
|
27
|
+
*
|
|
28
|
+
* Slots:
|
|
29
|
+
* expand-icon - overrides the default expand icon
|
|
30
|
+
* collapse-icon - overrides the default collapse icon
|
|
31
|
+
*
|
|
32
|
+
* CSS Parts:
|
|
33
|
+
* frame, iframe, overlay, toolbar, expand-button, collapse-button,
|
|
34
|
+
* expand-icon, collapse-icon
|
|
35
|
+
*
|
|
36
|
+
* Events:
|
|
37
|
+
* pds-render-ready - fired when the iframe is initialized and PDS is running
|
|
38
|
+
* pds-render-error - fired if the iframe script throws; detail.error = error object
|
|
39
|
+
*
|
|
40
|
+
* Methods:
|
|
41
|
+
* setContent(content, options?) - batch updates html/css/js via a single public contract
|
|
42
|
+
* update(content, options?) - alias of setContent for API ergonomics
|
|
43
|
+
* reload() - force full srcdoc rebuild
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {object} PdsRenderContent
|
|
48
|
+
* @property {string} [html] HTML string rendered in the iframe body.
|
|
49
|
+
* @property {string} [css] CSS text injected into the iframe user stylesheet.
|
|
50
|
+
* @property {string} [js] JavaScript module source evaluated in the iframe runtime.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {object} PdsRenderSetContentOptions
|
|
55
|
+
* @property {boolean} [reload=false] Force full iframe rebuild after applying content.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
const LAYERS = ["primitives", "components", "utilities"];
|
|
59
|
+
|
|
60
|
+
const COMPONENT_CSS = /*css*/ `
|
|
61
|
+
@layer pds-render {
|
|
62
|
+
:host {
|
|
63
|
+
display: block;
|
|
64
|
+
position: relative;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
border-radius: var(--radius-md, 0.375rem);
|
|
67
|
+
background: var(--pds-render-surface, var(--color-surface-base, Canvas));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
:host([bordered]) {
|
|
71
|
+
border: var(--border-width-thin, 1px) solid var(--pds-render-border-color, var(--color-border, currentColor));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
:host([expanded]) {
|
|
75
|
+
position: fixed !important;
|
|
76
|
+
inset: 0 !important;
|
|
77
|
+
inline-size: 100vw !important;
|
|
78
|
+
block-size: 100vh !important;
|
|
79
|
+
z-index: var(--z-modal, 9999);
|
|
80
|
+
border-radius: 0 !important;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.zoom-wrapper {
|
|
85
|
+
position: relative;
|
|
86
|
+
transform-origin: top left;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
iframe {
|
|
90
|
+
display: block;
|
|
91
|
+
border: none;
|
|
92
|
+
background: var(--pds-render-frame-background, transparent);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.overlay {
|
|
96
|
+
display: flex;
|
|
97
|
+
position: absolute;
|
|
98
|
+
inset: 0;
|
|
99
|
+
align-items: center;
|
|
100
|
+
justify-content: center;
|
|
101
|
+
opacity: 0;
|
|
102
|
+
pointer-events: none;
|
|
103
|
+
transform: none;
|
|
104
|
+
isolation: isolate;
|
|
105
|
+
transition:
|
|
106
|
+
opacity var(--transition-fast, 0.22s) ease,
|
|
107
|
+
background var(--transition-fast, 0.22s) ease;
|
|
108
|
+
background: color-mix(in oklab, var(--color-scrim, CanvasText) 0%, transparent);
|
|
109
|
+
z-index: 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
:host([resizable]) .overlay {
|
|
113
|
+
pointer-events: auto;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
:host([resizable]:hover) .overlay {
|
|
117
|
+
opacity: 1;
|
|
118
|
+
background: color-mix(in oklab, var(--color-scrim, CanvasText) 42%, transparent);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
:host([expanded]) .overlay {
|
|
122
|
+
display: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.toolbar {
|
|
126
|
+
transform: none;
|
|
127
|
+
isolation: isolate;
|
|
128
|
+
display: none;
|
|
129
|
+
position: absolute;
|
|
130
|
+
inset-block-start: var(--spacing-5, 1.25rem);
|
|
131
|
+
inset-inline-end: var(--spacing-5, 1.25rem);
|
|
132
|
+
opacity: 0.2;
|
|
133
|
+
transition: opacity var(--transition-fast, 0.22s) ease;
|
|
134
|
+
z-index: 2;
|
|
135
|
+
pointer-events: none;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
:host([expanded]) .toolbar {
|
|
139
|
+
display: flex;
|
|
140
|
+
pointer-events: auto;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
:host([expanded]) .toolbar:hover {
|
|
144
|
+
opacity: 0.9;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.control-button {
|
|
148
|
+
appearance: none;
|
|
149
|
+
display: inline-flex;
|
|
150
|
+
align-items: center;
|
|
151
|
+
justify-content: center;
|
|
152
|
+
padding: 0;
|
|
153
|
+
border: 0;
|
|
154
|
+
background: none;
|
|
155
|
+
color: var(--pds-render-control-color, var(--color-static-white, #fff));
|
|
156
|
+
cursor: pointer;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.control-button:focus-visible {
|
|
160
|
+
outline: var(--focus-outline, 2px solid currentColor);
|
|
161
|
+
outline-offset: var(--spacing-1, 0.25rem);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.overlay-btn {
|
|
165
|
+
filter: drop-shadow(0 0 1rem color-mix(in oklab, black 85%, transparent));
|
|
166
|
+
transform: scale(5);
|
|
167
|
+
transform-origin: center;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.toolbar-btn {
|
|
171
|
+
filter:
|
|
172
|
+
drop-shadow(0 0 0.5rem color-mix(in oklab, black 100%, transparent))
|
|
173
|
+
drop-shadow(0 0 1rem color-mix(in oklab, black 90%, transparent))
|
|
174
|
+
drop-shadow(0 0 2rem color-mix(in oklab, black 80%, transparent));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.icon-wrap {
|
|
178
|
+
display: inline-flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
justify-content: center;
|
|
181
|
+
inline-size: 100%;
|
|
182
|
+
block-size: 100%;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.overlay-btn .icon-wrap {
|
|
186
|
+
inline-size: 1rem;
|
|
187
|
+
block-size: 1rem;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.toolbar-btn .icon-wrap {
|
|
191
|
+
inline-size: 5rem;
|
|
192
|
+
block-size: 5rem;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
slot[name="expand-icon"],
|
|
196
|
+
slot[name="collapse-icon"] {
|
|
197
|
+
display: inline-flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
justify-content: center;
|
|
200
|
+
inline-size: 100%;
|
|
201
|
+
block-size: 100%;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
::slotted([slot="expand-icon"]),
|
|
205
|
+
::slotted([slot="collapse-icon"]) {
|
|
206
|
+
display: inline-flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
justify-content: center;
|
|
209
|
+
inline-size: 100%;
|
|
210
|
+
block-size: 100%;
|
|
211
|
+
color: currentColor;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
svg {
|
|
215
|
+
display: block;
|
|
216
|
+
inline-size: 100%;
|
|
217
|
+
block-size: 100%;
|
|
218
|
+
fill: currentColor;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
[hidden] {
|
|
222
|
+
display: none !important;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
`;
|
|
226
|
+
|
|
227
|
+
class PdsRender extends HTMLElement {
|
|
228
|
+
static #componentSheet;
|
|
229
|
+
|
|
230
|
+
static get observedAttributes() {
|
|
231
|
+
return [
|
|
232
|
+
"theme",
|
|
233
|
+
"preset",
|
|
234
|
+
"height",
|
|
235
|
+
"aspect-ratio",
|
|
236
|
+
"src-base",
|
|
237
|
+
"padding",
|
|
238
|
+
"background",
|
|
239
|
+
"zoom",
|
|
240
|
+
"viewport",
|
|
241
|
+
"locale",
|
|
242
|
+
"resizable",
|
|
243
|
+
"expanded",
|
|
244
|
+
];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
constructor() {
|
|
248
|
+
super();
|
|
249
|
+
this._html = "";
|
|
250
|
+
this._css = "";
|
|
251
|
+
this._js = "";
|
|
252
|
+
this._ready = false;
|
|
253
|
+
this._iframeReady = false;
|
|
254
|
+
this._pendingMessages = [];
|
|
255
|
+
this._bodyOverflow = null;
|
|
256
|
+
this._adoptPromise = null;
|
|
257
|
+
|
|
258
|
+
this._state = new State({ expanded: this.hasAttribute("expanded") }, this);
|
|
259
|
+
this._state.on("change:expanded", () => {
|
|
260
|
+
this._applyExpandedState(Boolean(this._state.expanded));
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
this._messageHandler = (e) => {
|
|
264
|
+
if (e.source !== this._iframe?.contentWindow) return;
|
|
265
|
+
const { type, detail } = e.data || {};
|
|
266
|
+
if (type === "pds-render:ready") {
|
|
267
|
+
this._iframeReady = true;
|
|
268
|
+
for (const msg of this._pendingMessages) this._send(msg);
|
|
269
|
+
this._pendingMessages = [];
|
|
270
|
+
this.dispatchEvent(new CustomEvent("pds-render-ready", { bubbles: true, composed: true }));
|
|
271
|
+
}
|
|
272
|
+
if (type === "pds-render:error") {
|
|
273
|
+
this.dispatchEvent(
|
|
274
|
+
new CustomEvent("pds-render-error", {
|
|
275
|
+
bubbles: true,
|
|
276
|
+
composed: true,
|
|
277
|
+
detail: { error: detail },
|
|
278
|
+
}),
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
this._escHandler = (e) => {
|
|
284
|
+
if (e.key === "Escape" && this.expanded) this._toggleExpanded(false);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
this._root = this.attachShadow({ mode: "open" });
|
|
288
|
+
this._renderShadow();
|
|
289
|
+
this._cacheShadowRefs();
|
|
290
|
+
this._syncExpandedControls();
|
|
291
|
+
this._adoptStyles().catch((error) => {
|
|
292
|
+
console.warn("[pds-render] adoptLayers failed", error);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
connectedCallback() {
|
|
297
|
+
this._ready = true;
|
|
298
|
+
|
|
299
|
+
for (const [pub, priv] of [["html", "_html"], ["css", "_css"], ["js", "_js"]]) {
|
|
300
|
+
if (Object.prototype.hasOwnProperty.call(this, pub)) {
|
|
301
|
+
const val = this[pub];
|
|
302
|
+
delete this[pub];
|
|
303
|
+
this[priv] = String(val || "");
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
window.addEventListener("message", this._messageHandler);
|
|
308
|
+
window.addEventListener("keydown", this._escHandler);
|
|
309
|
+
|
|
310
|
+
this._resizeObserver = new ResizeObserver(() => {
|
|
311
|
+
if (this.getAttribute("zoom") === null) this._applyZoom();
|
|
312
|
+
});
|
|
313
|
+
this._resizeObserver.observe(this);
|
|
314
|
+
|
|
315
|
+
this._applyExpandedState(this.expanded, { syncAttribute: false });
|
|
316
|
+
this._buildSrcdoc();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
disconnectedCallback() {
|
|
320
|
+
window.removeEventListener("message", this._messageHandler);
|
|
321
|
+
window.removeEventListener("keydown", this._escHandler);
|
|
322
|
+
this._resizeObserver?.disconnect();
|
|
323
|
+
this._setBodyScrollLocked(false);
|
|
324
|
+
this._ready = false;
|
|
325
|
+
this._iframeReady = false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
329
|
+
if (name === "expanded") {
|
|
330
|
+
const expanded = newVal !== null;
|
|
331
|
+
if (this._state.expanded !== expanded) {
|
|
332
|
+
this._state.expanded = expanded;
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!this._ready || oldVal === newVal) return;
|
|
338
|
+
if (name === "resizable") return;
|
|
339
|
+
if (name === "height" || name === "aspect-ratio" || name === "zoom" || name === "viewport") {
|
|
340
|
+
this._applyZoom();
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (name === "theme" && this._iframeReady) {
|
|
344
|
+
this._post({ type: "pds-render:set-theme", value: newVal || "light" });
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (name === "preset" && this._iframeReady) {
|
|
348
|
+
this._post({ type: "pds-render:set-preset", value: newVal || "default" });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (name === "locale") {
|
|
352
|
+
if (this._iframeReady) {
|
|
353
|
+
this._post({ type: "pds-render:set-locale", value: newVal || "en" });
|
|
354
|
+
} else {
|
|
355
|
+
this._buildSrcdoc();
|
|
356
|
+
}
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
this._buildSrcdoc();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
get html() {
|
|
363
|
+
return this._html;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
set html(value) {
|
|
367
|
+
this.setContent({ html: value });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
get css() {
|
|
371
|
+
return this._css;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
set css(value) {
|
|
375
|
+
this.setContent({ css: value });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
get js() {
|
|
379
|
+
return this._js;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
set js(value) {
|
|
383
|
+
this.setContent({ js: value }, { reload: true });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Batch update iframe content using the public component contract.
|
|
388
|
+
* Prefer this method when applying multiple fields at once.
|
|
389
|
+
*
|
|
390
|
+
* @param {PdsRenderContent} content Content payload with any combination of html/css/js.
|
|
391
|
+
* @param {PdsRenderSetContentOptions} [options] Content update options.
|
|
392
|
+
*/
|
|
393
|
+
setContent(content = {}, options = {}) {
|
|
394
|
+
const next = content && typeof content === "object" ? content : {};
|
|
395
|
+
const { reload = false } = options || {};
|
|
396
|
+
|
|
397
|
+
const hasHtml = Object.prototype.hasOwnProperty.call(next, "html");
|
|
398
|
+
const hasCss = Object.prototype.hasOwnProperty.call(next, "css");
|
|
399
|
+
const hasJs = Object.prototype.hasOwnProperty.call(next, "js");
|
|
400
|
+
|
|
401
|
+
const nextHtml = hasHtml ? String(next.html || "") : this._html;
|
|
402
|
+
const nextCss = hasCss ? String(next.css || "") : this._css;
|
|
403
|
+
const nextJs = hasJs ? String(next.js || "") : this._js;
|
|
404
|
+
|
|
405
|
+
const htmlChanged = hasHtml && nextHtml !== this._html;
|
|
406
|
+
const cssChanged = hasCss && nextCss !== this._css;
|
|
407
|
+
const jsChanged = hasJs && nextJs !== this._js;
|
|
408
|
+
|
|
409
|
+
if (hasHtml) this._html = nextHtml;
|
|
410
|
+
if (hasCss) this._css = nextCss;
|
|
411
|
+
if (hasJs) this._js = nextJs;
|
|
412
|
+
|
|
413
|
+
if (!this._ready || !(htmlChanged || cssChanged || jsChanged || reload)) return;
|
|
414
|
+
|
|
415
|
+
if (reload || jsChanged) {
|
|
416
|
+
this.reload();
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (this._iframeReady) {
|
|
421
|
+
if (htmlChanged) this._post({ type: "pds-render:set-html", value: this._html });
|
|
422
|
+
if (cssChanged) this._post({ type: "pds-render:set-css", value: this._css });
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
this._buildSrcdoc();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Alias for setContent to support fluent consumer APIs.
|
|
431
|
+
*
|
|
432
|
+
* @param {PdsRenderContent} content Content payload with any combination of html/css/js.
|
|
433
|
+
* @param {PdsRenderSetContentOptions} [options] Content update options.
|
|
434
|
+
*/
|
|
435
|
+
update(content = {}, options = {}) {
|
|
436
|
+
this.setContent(content, options);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
get expanded() {
|
|
440
|
+
return Boolean(this._state.expanded);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
set expanded(value) {
|
|
444
|
+
this.toggleAttribute("expanded", Boolean(value));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
reload() {
|
|
448
|
+
this._iframeReady = false;
|
|
449
|
+
this._pendingMessages = [];
|
|
450
|
+
this._buildSrcdoc();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
_renderShadow() {
|
|
454
|
+
const expanded = this.expanded;
|
|
455
|
+
this._root.replaceChildren(
|
|
456
|
+
html`
|
|
457
|
+
<div class="zoom-wrapper" part="frame">
|
|
458
|
+
<iframe title="PDS Render" part="iframe"></iframe>
|
|
459
|
+
</div>
|
|
460
|
+
<div class="overlay" part="overlay" aria-hidden="${expanded ? "true" : "false"}">
|
|
461
|
+
<button
|
|
462
|
+
type="button"
|
|
463
|
+
class="control-button overlay-btn"
|
|
464
|
+
part="expand-button"
|
|
465
|
+
aria-label="Expand preview"
|
|
466
|
+
?hidden=${expanded}
|
|
467
|
+
@click=${(e) => {
|
|
468
|
+
e.stopPropagation();
|
|
469
|
+
this._toggleExpanded(true);
|
|
470
|
+
}}
|
|
471
|
+
>
|
|
472
|
+
<span class="icon-wrap" part="expand-icon">
|
|
473
|
+
<slot name="expand-icon">
|
|
474
|
+
<svg viewBox="0 0 256 256" aria-hidden="true">
|
|
475
|
+
<path d="M216,48V88a8,8,0,0,1-16,0V56H168a8,8,0,0,1,0-16h40A8,8,0,0,1,216,48ZM88,200H56V168a8,8,0,0,0-16,0v40a8,8,0,0,0,8,8H88a8,8,0,0,0,0-16Zm120-40a8,8,0,0,0-8,8v32H168a8,8,0,0,0,0,16h40a8,8,0,0,0,8-8V168A8,8,0,0,0,208,160ZM88,40H48a8,8,0,0,0-8,8V88a8,8,0,0,0,16,0V56H88a8,8,0,0,0,0-16Z"></path>
|
|
476
|
+
</svg>
|
|
477
|
+
</slot>
|
|
478
|
+
</span>
|
|
479
|
+
</button>
|
|
480
|
+
</div>
|
|
481
|
+
<div class="toolbar" part="toolbar" aria-hidden="${expanded ? "false" : "true"}">
|
|
482
|
+
<button
|
|
483
|
+
type="button"
|
|
484
|
+
class="control-button toolbar-btn"
|
|
485
|
+
part="collapse-button"
|
|
486
|
+
aria-label="Exit fullscreen preview"
|
|
487
|
+
?hidden=${!expanded}
|
|
488
|
+
@click=${(e) => {
|
|
489
|
+
e.stopPropagation();
|
|
490
|
+
this._toggleExpanded(false);
|
|
491
|
+
}}
|
|
492
|
+
>
|
|
493
|
+
<span class="icon-wrap" part="collapse-icon">
|
|
494
|
+
<slot name="collapse-icon">
|
|
495
|
+
<svg viewBox="0 0 256 256" aria-hidden="true">
|
|
496
|
+
<path d="M152,96V48a8,8,0,0,1,16,0V88h40a8,8,0,0,1,0,16H160A8,8,0,0,1,152,96ZM96,152H48a8,8,0,0,0,0,16H88v40a8,8,0,0,0,16,0V160A8,8,0,0,0,96,152Zm112,0H160a8,8,0,0,0-8,8v48a8,8,0,0,0,16,0V168h40a8,8,0,0,0,0-16ZM96,40a8,8,0,0,0-8,8V88H48a8,8,0,0,0,0,16H96a8,8,0,0,0,8-8V48A8,8,0,0,0,96,40Z"></path>
|
|
497
|
+
</svg>
|
|
498
|
+
</slot>
|
|
499
|
+
</span>
|
|
500
|
+
</button>
|
|
501
|
+
</div>
|
|
502
|
+
`,
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
_cacheShadowRefs() {
|
|
507
|
+
this._zoomWrapper = this._root.querySelector(".zoom-wrapper");
|
|
508
|
+
this._iframe = this._root.querySelector("iframe");
|
|
509
|
+
this._overlay = this._root.querySelector(".overlay");
|
|
510
|
+
this._toolbar = this._root.querySelector(".toolbar");
|
|
511
|
+
this._overlayBtn = this._root.querySelector(".overlay-btn");
|
|
512
|
+
this._toolbarBtn = this._root.querySelector(".toolbar-btn");
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async _adoptStyles() {
|
|
516
|
+
if (this._adoptPromise) return this._adoptPromise;
|
|
517
|
+
if (!PdsRender.#componentSheet) {
|
|
518
|
+
PdsRender.#componentSheet = PDS.createStylesheet(COMPONENT_CSS);
|
|
519
|
+
}
|
|
520
|
+
this._adoptPromise = PDS.adoptLayers(this._root, LAYERS, [PdsRender.#componentSheet]);
|
|
521
|
+
return this._adoptPromise;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
_applyExpandedState(expanded, options = {}) {
|
|
525
|
+
const { syncAttribute = true } = options;
|
|
526
|
+
|
|
527
|
+
if (syncAttribute) {
|
|
528
|
+
this.toggleAttribute("expanded", expanded);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
this._syncExpandedControls();
|
|
532
|
+
|
|
533
|
+
if (!this.isConnected) return;
|
|
534
|
+
|
|
535
|
+
this._setBodyScrollLocked(expanded);
|
|
536
|
+
|
|
537
|
+
if (this._ready) {
|
|
538
|
+
this._applyZoom();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
_syncExpandedControls() {
|
|
543
|
+
const expanded = this.expanded;
|
|
544
|
+
this._overlay?.setAttribute("aria-hidden", String(expanded));
|
|
545
|
+
this._toolbar?.setAttribute("aria-hidden", String(!expanded));
|
|
546
|
+
if (this._overlayBtn) this._overlayBtn.hidden = expanded;
|
|
547
|
+
if (this._toolbarBtn) this._toolbarBtn.hidden = !expanded;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
_setBodyScrollLocked(locked) {
|
|
551
|
+
if (locked) {
|
|
552
|
+
if (this._bodyOverflow === null) {
|
|
553
|
+
this._bodyOverflow = document.body.style.overflow;
|
|
554
|
+
}
|
|
555
|
+
document.body.style.overflow = "hidden";
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (this._bodyOverflow !== null) {
|
|
560
|
+
document.body.style.overflow = this._bodyOverflow;
|
|
561
|
+
this._bodyOverflow = null;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
_effectiveZoom() {
|
|
566
|
+
const attr = this.getAttribute("zoom");
|
|
567
|
+
if (attr !== null) return Math.max(0.05, Math.min(1, parseFloat(attr) || 1));
|
|
568
|
+
const viewport = this.getAttribute("viewport") || "desktop";
|
|
569
|
+
const refWidth = viewport === "mobile"
|
|
570
|
+
? 375
|
|
571
|
+
: viewport === "tablet"
|
|
572
|
+
? 768
|
|
573
|
+
: (window.innerWidth || 1280);
|
|
574
|
+
const width = this.offsetWidth;
|
|
575
|
+
if (!width) return 1;
|
|
576
|
+
return Math.min(1, width / refWidth);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
_applyZoom() {
|
|
580
|
+
if (!this._zoomWrapper || !this._iframe) return;
|
|
581
|
+
|
|
582
|
+
if (this.expanded) {
|
|
583
|
+
const width = window.innerWidth;
|
|
584
|
+
const height = window.innerHeight;
|
|
585
|
+
this._zoomWrapper.style.transform = "";
|
|
586
|
+
this._zoomWrapper.style.width = `${width}px`;
|
|
587
|
+
this._zoomWrapper.style.height = `${height}px`;
|
|
588
|
+
this._iframe.style.width = `${width}px`;
|
|
589
|
+
this._iframe.style.height = `${height}px`;
|
|
590
|
+
this.style.height = "";
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const zoom = this._effectiveZoom();
|
|
595
|
+
const hostWidth = this.offsetWidth || 300;
|
|
596
|
+
|
|
597
|
+
const arAttr = this.getAttribute("aspect-ratio");
|
|
598
|
+
let displayHeight;
|
|
599
|
+
if (arAttr) {
|
|
600
|
+
const [aspectWidth, aspectHeight] = arAttr.split("/").map(Number);
|
|
601
|
+
displayHeight = Math.round(hostWidth * ((aspectHeight || 1) / (aspectWidth || 1)));
|
|
602
|
+
this.style.height = `${displayHeight}px`;
|
|
603
|
+
} else {
|
|
604
|
+
displayHeight = parseFloat(this.getAttribute("height")) || 200;
|
|
605
|
+
this.style.height = `${displayHeight}px`;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const iframeWidth = Math.round(hostWidth / zoom);
|
|
609
|
+
const iframeHeight = Math.round(displayHeight / zoom);
|
|
610
|
+
|
|
611
|
+
this._zoomWrapper.style.transform = zoom < 1 ? `scale(${zoom})` : "";
|
|
612
|
+
this._zoomWrapper.style.width = `${iframeWidth}px`;
|
|
613
|
+
this._zoomWrapper.style.height = `${iframeHeight}px`;
|
|
614
|
+
this._iframe.style.width = `${iframeWidth}px`;
|
|
615
|
+
this._iframe.style.height = `${iframeHeight}px`;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
_toggleExpanded(force) {
|
|
619
|
+
const expanded = typeof force === "boolean" ? force : !this.expanded;
|
|
620
|
+
|
|
621
|
+
const doToggle = () => {
|
|
622
|
+
this._state.expanded = expanded;
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
if (!document.startViewTransition) {
|
|
626
|
+
doToggle();
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (!document.getElementById("__pds-render-vt-style")) {
|
|
631
|
+
const style = document.createElement("style");
|
|
632
|
+
style.id = "__pds-render-vt-style";
|
|
633
|
+
style.textContent = [
|
|
634
|
+
"::view-transition-group(pds-render-expand){animation-duration:0.4s;animation-timing-function:cubic-bezier(0.4,0,0.2,1)}",
|
|
635
|
+
"::view-transition-old(pds-render-expand){animation:none;mix-blend-mode:normal}",
|
|
636
|
+
"::view-transition-new(pds-render-expand){animation:none;mix-blend-mode:normal}",
|
|
637
|
+
].join("\n");
|
|
638
|
+
document.head.appendChild(style);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
this.style.viewTransitionName = "pds-render-expand";
|
|
642
|
+
const transition = document.startViewTransition(doToggle);
|
|
643
|
+
transition.finished.finally(() => {
|
|
644
|
+
this.style.viewTransitionName = "";
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
_resolveSrcBase() {
|
|
649
|
+
const attr = this.getAttribute("src-base");
|
|
650
|
+
if (attr) return attr.endsWith("/") ? attr : `${attr}/`;
|
|
651
|
+
const scripts = document.querySelectorAll("script[src]");
|
|
652
|
+
for (const script of scripts) {
|
|
653
|
+
if (script.src.includes("/components/pds-render")) {
|
|
654
|
+
return script.src.replace(/\/components\/pds-render\.js.*$/, "/");
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return `${window.location.origin}/assets/pds/`;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
_buildSrcdoc() {
|
|
661
|
+
if (!this._ready || !this._iframe) return;
|
|
662
|
+
this._iframeReady = false;
|
|
663
|
+
this._pendingMessages = [];
|
|
664
|
+
|
|
665
|
+
const theme = this.getAttribute("theme") || "light";
|
|
666
|
+
const preset = this.getAttribute("preset") || "default";
|
|
667
|
+
const padding = this.getAttribute("padding") || "1rem";
|
|
668
|
+
const background = this.getAttribute("background") || "";
|
|
669
|
+
const lang = this.getAttribute("locale") || "en";
|
|
670
|
+
const base = this._resolveSrcBase();
|
|
671
|
+
|
|
672
|
+
this._applyZoom();
|
|
673
|
+
|
|
674
|
+
const cssUrl = `${base}styles/pds-styles.css`;
|
|
675
|
+
const coreUrl = `${base}core.js`;
|
|
676
|
+
const litUrl = `${base}external/lit.js`;
|
|
677
|
+
|
|
678
|
+
const presetInit = preset && preset !== "default"
|
|
679
|
+
? `try { await PDS.applyLivePreset(${JSON.stringify(preset)}, { persist: false }); } catch(e) { notify('error', e); }`
|
|
680
|
+
: "";
|
|
681
|
+
|
|
682
|
+
const userJs = this._js
|
|
683
|
+
? `try {\n${this._js}\n} catch(e) { notify('error', e); }`
|
|
684
|
+
: "";
|
|
685
|
+
|
|
686
|
+
const srcdoc = `<!DOCTYPE html>
|
|
687
|
+
<html lang="${lang}" data-theme="${theme}">
|
|
688
|
+
<head>
|
|
689
|
+
<meta charset="UTF-8">
|
|
690
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
691
|
+
<link rel="stylesheet" href="${cssUrl}">
|
|
692
|
+
<script type="importmap">
|
|
693
|
+
{"imports":{"#pds":"${coreUrl}","#pds/lit":"${litUrl}"}}
|
|
694
|
+
</script>
|
|
695
|
+
<style>
|
|
696
|
+
*,*::before,*::after{box-sizing:border-box}
|
|
697
|
+
html,body{margin:0;padding:0}
|
|
698
|
+
body{padding:${padding};${background ? `background:${background};` : ""}min-height:100vh}
|
|
699
|
+
</style>
|
|
700
|
+
<style id="__user-css">${this._css}</style>
|
|
701
|
+
</head>
|
|
702
|
+
<body>${this._html}
|
|
703
|
+
<script type="module">
|
|
704
|
+
import { PDS } from '#pds';
|
|
705
|
+
|
|
706
|
+
const __pdsBase = ${JSON.stringify(base)};
|
|
707
|
+
|
|
708
|
+
function notify(type, detail) {
|
|
709
|
+
window.parent.postMessage({ type: 'pds-render:' + type, detail: detail ? String(detail) : null }, '*');
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
await PDS.start({ mode: 'live', preset: 'default' });
|
|
714
|
+
} catch(e) {}
|
|
715
|
+
|
|
716
|
+
${presetInit}
|
|
717
|
+
|
|
718
|
+
${userJs}
|
|
719
|
+
|
|
720
|
+
window.addEventListener('message', async (e) => {
|
|
721
|
+
if (!e.data || typeof e.data !== 'object') return;
|
|
722
|
+
const { type, value } = e.data;
|
|
723
|
+
|
|
724
|
+
if (type === 'pds-render:set-html') {
|
|
725
|
+
document.body.innerHTML = value;
|
|
726
|
+
}
|
|
727
|
+
if (type === 'pds-render:set-css') {
|
|
728
|
+
const style = document.getElementById('__user-css');
|
|
729
|
+
if (style) style.textContent = value;
|
|
730
|
+
}
|
|
731
|
+
if (type === 'pds-render:set-theme') {
|
|
732
|
+
document.documentElement.setAttribute('data-theme', value);
|
|
733
|
+
try { PDS.theme = value; } catch {}
|
|
734
|
+
}
|
|
735
|
+
if (type === 'pds-render:set-preset') {
|
|
736
|
+
try { await PDS.applyLivePreset(value, { persist: false }); } catch(e) { notify('error', e); }
|
|
737
|
+
}
|
|
738
|
+
if (type === 'pds-render:set-locale') {
|
|
739
|
+
document.documentElement.setAttribute('lang', value);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
notify('ready', null);
|
|
744
|
+
</script>
|
|
745
|
+
</body>
|
|
746
|
+
</html>`;
|
|
747
|
+
|
|
748
|
+
this._iframe.srcdoc = srcdoc;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
_post(msg) {
|
|
752
|
+
if (this._iframeReady) {
|
|
753
|
+
this._send(msg);
|
|
754
|
+
} else {
|
|
755
|
+
this._pendingMessages.push(msg);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
_send(msg) {
|
|
760
|
+
try {
|
|
761
|
+
this._iframe.contentWindow?.postMessage(msg, "*");
|
|
762
|
+
} catch {}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (!customElements.get("pds-render")) {
|
|
767
|
+
customElements.define("pds-render", PdsRender);
|
|
768
|
+
}
|