@runelight/react 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,824 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { readGBoundaryElementRect } from "@runelight/core/boundary-rect";
4
+ import { createGPreviewErrorMessage, createGPreviewPoolReadyMessage, createGPreviewReadyMessage, createGPreviewRenderAcceptedMessage, createGPreviewResizeMessage, createGPreviewTreeMessage, createGPreviewValuesMessage, isGPreviewRenderMessage, isGPreviewRenderTarget, readRunelightPreviewFrameOverridesFromSearchParams, } from "@runelight/core/preview-protocol";
5
+ import { computeRunelightPreviewFrameGridLayout, runelightPreviewFixedFrameScale, runelightPreviewCardTitleGap, runelightPreviewCardTitleHeight, runelightPreviewCardTitleScreenGap, runelightPreviewCardTitleScreenHeight, runelightPreviewFrameChromeHeight, runelightPreviewFrameGridGap, runelightPreviewFrameGridMinScale, runelightPreviewFrameLabelGap, runelightPreviewFrameLabelMinHeight, runelightPreviewFrameLabelScreenGap, runelightPreviewFrameGridMaxSide, } from "@runelight/core/frame-grid-layout";
6
+ import { GPreviewProvider, createGBoundaryCollector } from "./runtime.js";
7
+ const loadedRunelightPreviewEntriesByLoader = new WeakMap();
8
+ const loadingRunelightPreviewEntriesByLoader = new WeakMap();
9
+ const runelightPreviewStudioFontFamily = '"JetBrains Mono", "IBM Plex Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace';
10
+ const runelightPreviewDefaultViewportSize = { height: 1024, width: 768 };
11
+ const runelightPreviewFrameGridFallbackItem = { height: 160, width: 280 };
12
+ const runelightPreviewStudioColors = {
13
+ canvasBg: "#181818",
14
+ panelBg: "#1e1e1e",
15
+ panelBgElevated: "#282828",
16
+ panelBorder: "#3a3a3a",
17
+ panelBorderSubtle: "#2e2e2e",
18
+ text: "#d4d4d4",
19
+ textMuted: "#a0a0a0",
20
+ textDim: "#8f8f8f",
21
+ textLabel: "#9a9a9a",
22
+ textTitle: "#a8a8a8",
23
+ accent: "#ff8c82",
24
+ accentText: "#e68a7d",
25
+ accentMuted: "rgba(255,140,130,0.28)",
26
+ };
27
+ const visibleChromePreviewSheetStyle = {
28
+ alignContent: "start",
29
+ backgroundColor: runelightPreviewStudioColors.canvasBg,
30
+ boxSizing: "border-box",
31
+ color: runelightPreviewStudioColors.text,
32
+ display: "grid",
33
+ fontFamily: runelightPreviewStudioFontFamily,
34
+ gap: 28,
35
+ minHeight: "100vh",
36
+ padding: 32,
37
+ };
38
+ const hiddenChromePreviewSheetStyle = {
39
+ display: "grid",
40
+ gap: 0,
41
+ padding: 0,
42
+ };
43
+ function contactSheetCardTitleSlotStyle(width) {
44
+ return {
45
+ alignContent: "start",
46
+ alignItems: "start",
47
+ display: "grid",
48
+ height: runelightPreviewCardTitleHeight + runelightPreviewCardTitleGap,
49
+ minWidth: 0,
50
+ overflow: "visible",
51
+ width,
52
+ };
53
+ }
54
+ const contactSheetCardTitleStyle = {
55
+ alignItems: "center",
56
+ color: runelightPreviewStudioColors.accentText,
57
+ display: "flex",
58
+ fontFamily: runelightPreviewStudioFontFamily,
59
+ fontSize: 9,
60
+ fontSynthesis: "none",
61
+ fontWeight: 400,
62
+ gap: 7,
63
+ letterSpacing: "0.06em",
64
+ lineHeight: 1,
65
+ minWidth: 0,
66
+ overflow: "hidden",
67
+ position: "relative",
68
+ textRendering: "geometricPrecision",
69
+ textOverflow: "ellipsis",
70
+ top: runelightPreviewCardTitleHeight + runelightPreviewCardTitleGap,
71
+ transform: `translateY(-${runelightPreviewCardTitleScreenHeight + runelightPreviewCardTitleScreenGap}px)`,
72
+ whiteSpace: "nowrap",
73
+ };
74
+ const contactSheetCardTitleIndicatorStyle = {
75
+ background: runelightPreviewStudioColors.accentText,
76
+ flexShrink: 0,
77
+ height: 9,
78
+ opacity: 1,
79
+ width: 2,
80
+ };
81
+ const contactSheetCardTitleTextStyle = {
82
+ overflow: "hidden",
83
+ textOverflow: "ellipsis",
84
+ whiteSpace: "nowrap",
85
+ };
86
+ function contactSheetComponentGroupStyle(width) {
87
+ return {
88
+ display: "grid",
89
+ gap: 0,
90
+ minWidth: 0,
91
+ width,
92
+ };
93
+ }
94
+ function contactSheetFrameGridStyle(layout) {
95
+ return {
96
+ alignItems: "start",
97
+ display: "grid",
98
+ gap: layout.gap,
99
+ gridTemplateColumns: `repeat(${layout.columns}, ${layout.cellWidth}px)`,
100
+ width: layout.width,
101
+ };
102
+ }
103
+ function contactSheetFrameTileStyle(cellWidth) {
104
+ return {
105
+ alignItems: "start",
106
+ display: "grid",
107
+ gap: runelightPreviewFrameLabelGap,
108
+ justifyItems: "start",
109
+ minWidth: 0,
110
+ width: cellWidth,
111
+ };
112
+ }
113
+ function contactSheetFrameShellStyle(frameLayout, groupLayout) {
114
+ if (!frameLayout.measured) {
115
+ return {
116
+ overflow: "visible",
117
+ position: "relative",
118
+ width: runelightPreviewDefaultViewportSize.width,
119
+ };
120
+ }
121
+ return {
122
+ height: Math.ceil(frameLayout.height * groupLayout.previewScale),
123
+ overflow: "visible",
124
+ position: "relative",
125
+ width: Math.ceil(frameLayout.width * groupLayout.previewScale),
126
+ };
127
+ }
128
+ function contactSheetFrameScaledCanvasStyle(frameLayout, groupLayout) {
129
+ if (!frameLayout.measured) {
130
+ return {
131
+ position: "relative",
132
+ transform: "translateZ(0)",
133
+ transformOrigin: "0 0",
134
+ width: runelightPreviewDefaultViewportSize.width,
135
+ };
136
+ }
137
+ return {
138
+ height: frameLayout.height,
139
+ left: 0,
140
+ position: "absolute",
141
+ top: 0,
142
+ transform: `scale(${formatRunelightPreviewCssNumber(groupLayout.previewScale)})`,
143
+ transformOrigin: "0 0",
144
+ width: frameLayout.width,
145
+ };
146
+ }
147
+ function contactSheetFrameViewportStyle(frameLayout) {
148
+ if (!frameLayout.measured) {
149
+ return {
150
+ overflow: "visible",
151
+ position: "relative",
152
+ width: runelightPreviewDefaultViewportSize.width,
153
+ };
154
+ }
155
+ return {
156
+ height: frameLayout.height,
157
+ overflow: "hidden",
158
+ position: "relative",
159
+ width: frameLayout.width,
160
+ };
161
+ }
162
+ function contactSheetFrameContentStyle(frameLayout) {
163
+ if (!frameLayout.measured) {
164
+ return {
165
+ position: "relative",
166
+ width: runelightPreviewDefaultViewportSize.width,
167
+ };
168
+ }
169
+ return {
170
+ left: -frameLayout.offset.x,
171
+ position: "absolute",
172
+ top: -frameLayout.offset.y,
173
+ width: frameLayout.viewportSize.width,
174
+ };
175
+ }
176
+ const contactSheetFrameLabelSlotStyle = {
177
+ alignContent: "start",
178
+ display: "grid",
179
+ height: runelightPreviewFrameLabelMinHeight,
180
+ justifyItems: "center",
181
+ minWidth: 0,
182
+ overflow: "visible",
183
+ width: "100%",
184
+ };
185
+ const contactSheetFrameLabelStyle = {
186
+ color: runelightPreviewStudioColors.textLabel,
187
+ display: "block",
188
+ fontSize: 9,
189
+ fontWeight: 400,
190
+ letterSpacing: "0.05em",
191
+ lineHeight: 1.35,
192
+ maxWidth: "100%",
193
+ overflow: "hidden",
194
+ textAlign: "center",
195
+ textOverflow: "ellipsis",
196
+ textTransform: "lowercase",
197
+ position: "relative",
198
+ top: -runelightPreviewFrameLabelGap,
199
+ transform: `translateY(${runelightPreviewFrameLabelScreenGap}px)`,
200
+ transformOrigin: "top center",
201
+ whiteSpace: "nowrap",
202
+ };
203
+ const contactSheetMeasuringFrameGridStyle = {
204
+ alignItems: "start",
205
+ display: "grid",
206
+ gap: 20,
207
+ gridTemplateColumns: "repeat(auto-fit, max-content)",
208
+ };
209
+ const runelightVisiblePreviewDocumentStyle = `html, body {
210
+ background: ${runelightPreviewStudioColors.canvasBg} !important;
211
+ margin: 0;
212
+ }`;
213
+ const runelightHiddenPreviewDocumentStyle = `html, body {
214
+ background: transparent !important;
215
+ margin: 0;
216
+ }`;
217
+ export function RunelightReactPreviewClient({ frameName = null, frameOverrides = new Map(), chrome = null, defaultEntry, entry, loadComponent, missingEntryDetail = "Pass ?entry=src/components/.../*.g.tsx to render a Runelight preview.", pool = null, poolMode, sessionId = null, staticMode = false, }) {
218
+ const resolvedPoolMode = poolMode ?? (typeof pool === "boolean" ? pool : pool === "1");
219
+ const routeTarget = React.useMemo(() => ({
220
+ frameName,
221
+ frameOverrides,
222
+ chrome: typeof chrome === "boolean" ? (chrome ? "1" : "0") : chrome,
223
+ entry: entry ?? defaultEntry ?? null,
224
+ poolMode: resolvedPoolMode,
225
+ renderRequestSequence: 0,
226
+ sessionId,
227
+ staticMode,
228
+ }), [frameName, frameOverrides, chrome, defaultEntry, entry, resolvedPoolMode, sessionId, staticMode]);
229
+ const renderTarget = useRunelightPreviewRenderTarget(routeTarget);
230
+ const showChrome = showChromeForPreviewTarget(renderTarget.chrome);
231
+ if (!renderTarget.entry) {
232
+ if (renderTarget.poolMode) {
233
+ return _jsx(RunelightPreviewDocumentBackground, { showChrome: false });
234
+ }
235
+ return (_jsxs(_Fragment, { children: [_jsx(RunelightPreviewDocumentBackground, { showChrome: showChrome }), _jsx(RunelightPreviewMessage, { detail: missingEntryDetail, sessionId: renderTarget.sessionId, title: "Missing entry" })] }));
236
+ }
237
+ return (_jsxs(_Fragment, { children: [_jsx(RunelightPreviewDocumentBackground, { showChrome: showChrome }), _jsx(RunelightEntryPreview, { frameName: renderTarget.frameName, frameOverrides: renderTarget.frameOverrides, entry: renderTarget.entry, loadComponent: loadComponent, sessionId: renderTarget.sessionId, showChrome: showChrome, staticMode: renderTarget.staticMode }, previewRenderTargetKey(renderTarget))] }));
238
+ }
239
+ function RunelightEntryPreview({ frameName, frameOverrides, entry, loadComponent, sessionId, showChrome, staticMode, }) {
240
+ const cachedEntry = readLoadedRunelightPreviewEntry(loadComponent, entry);
241
+ const [loadedEntry, setLoadedEntry] = React.useState(cachedEntry);
242
+ const effectiveLoadedEntry = cachedEntry ?? loadedEntry;
243
+ React.useEffect(() => {
244
+ const cached = readLoadedRunelightPreviewEntry(loadComponent, entry);
245
+ if (cached) {
246
+ setLoadedEntry(cached);
247
+ return;
248
+ }
249
+ let ignore = false;
250
+ loadRunelightPreviewEntry(loadComponent, entry)
251
+ .then((loaded) => {
252
+ if (!ignore) {
253
+ setLoadedEntry(loaded);
254
+ }
255
+ });
256
+ return () => {
257
+ ignore = true;
258
+ };
259
+ }, [entry, loadComponent]);
260
+ if (!effectiveLoadedEntry || effectiveLoadedEntry.entry !== entry) {
261
+ return showChrome ? _jsx(RunelightPreviewMessage, { detail: entry, title: "Loading" }) : null;
262
+ }
263
+ if (!effectiveLoadedEntry.component) {
264
+ return _jsx(RunelightPreviewMessage, { detail: entry, sessionId: sessionId, title: "Unknown Runelight entry" });
265
+ }
266
+ return (_jsx(LoadedRunelightEntryPreview, { frameName: frameName, frameOverrides: frameOverrides, component: effectiveLoadedEntry.component, entry: entry, sessionId: sessionId, showChrome: showChrome, staticMode: staticMode }));
267
+ }
268
+ function readLoadedRunelightPreviewEntry(loadComponent, entry) {
269
+ return loadedRunelightPreviewEntriesByLoader.get(loadComponent)?.get(entry) ?? null;
270
+ }
271
+ function loadRunelightPreviewEntry(loadComponent, entry) {
272
+ let loadedEntries = loadedRunelightPreviewEntriesByLoader.get(loadComponent);
273
+ if (!loadedEntries) {
274
+ loadedEntries = new Map();
275
+ loadedRunelightPreviewEntriesByLoader.set(loadComponent, loadedEntries);
276
+ }
277
+ const loadedEntry = loadedEntries.get(entry);
278
+ if (loadedEntry)
279
+ return Promise.resolve(loadedEntry);
280
+ let loadingEntries = loadingRunelightPreviewEntriesByLoader.get(loadComponent);
281
+ if (!loadingEntries) {
282
+ loadingEntries = new Map();
283
+ loadingRunelightPreviewEntriesByLoader.set(loadComponent, loadingEntries);
284
+ }
285
+ const loadingEntry = loadingEntries.get(entry);
286
+ if (loadingEntry)
287
+ return loadingEntry;
288
+ const nextLoadingEntry = Promise.resolve(loadComponent(entry))
289
+ .then((component) => ({ component: component ?? null, entry }))
290
+ .catch(() => ({ component: null, entry }))
291
+ .then((loaded) => {
292
+ loadedEntries.set(entry, loaded);
293
+ loadingEntries.delete(entry);
294
+ return loaded;
295
+ });
296
+ loadingEntries.set(entry, nextLoadingEntry);
297
+ return nextLoadingEntry;
298
+ }
299
+ function LoadedRunelightEntryPreview({ frameName, frameOverrides, component, entry, sessionId, showChrome, staticMode, }) {
300
+ const collector = React.useMemo(() => createGBoundaryCollector(), []);
301
+ const frames = component.frames ?? {};
302
+ const selectedFrames = frameName ? [[frameName, frames[frameName]]] : Object.entries(frames);
303
+ const renderableFrames = selectedFrames.flatMap(([name, frame]) => (frame ? [{ name, frame }] : []));
304
+ const hasRenderableFrames = selectedFrames.length > 0 && renderableFrames.length === selectedFrames.length;
305
+ useRunelightPreviewProtocolMessages(sessionId, collector, hasRenderableFrames, { staticMode });
306
+ if (!hasRenderableFrames) {
307
+ return _jsx(RunelightPreviewMessage, { detail: frameName ?? "No frames declared", sessionId: sessionId, title: "Unknown Runelight frame" });
308
+ }
309
+ return (_jsx(RunelightReactPreviewFrameSheetInternal, { boundaryCollector: collector, frameOverrides: frameOverrides, component: component, entry: entry, selectedFrames: renderableFrames, showChrome: showChrome }));
310
+ }
311
+ export function RunelightReactPreviewFrameSheet(props) {
312
+ return _jsx(RunelightReactPreviewFrameSheetInternal, { ...props });
313
+ }
314
+ function RunelightReactPreviewFrameSheetInternal({ boundaryCollector, frameOverrides = new Map(), component: Component, entry, selectedFrames, showChrome = true, }) {
315
+ if (!showChrome) {
316
+ return (_jsx("main", { style: hiddenChromePreviewSheetStyle, children: selectedFrames.map(({ name, frame }) => (_jsx("section", { "data-runelight-preview-capture-bounds": selectedFrames.length === 1 ? "true" : undefined, "data-runelight-preview-frame": name, children: _jsx(GPreviewProvider, { boundaryCollector: boundaryCollector, frameOverrides: frameOverridesForFrame(entry, name, frameOverrides), ...previewRuntimeProps(frame), children: _jsx(Component, { ...frame.props }) }) }, name))) }));
317
+ }
318
+ return (_jsx(RunelightPreviewContactSheetFrameGroup, { frameOverrides: frameOverrides, component: Component, entry: entry, selectedFrames: selectedFrames }));
319
+ }
320
+ function RunelightPreviewContactSheetFrameGroup({ frameOverrides, component: Component, entry, selectedFrames, }) {
321
+ const frameModels = React.useMemo(() => selectedFrames.map(({ name, frame }) => ({
322
+ collector: createGBoundaryCollector(),
323
+ frame,
324
+ name,
325
+ })), [selectedFrames]);
326
+ const frameViewportElements = React.useRef(new Map());
327
+ const frameContentElements = React.useRef(new Map());
328
+ const geometryByFrame = useRunelightPreviewContactSheetGeometry({
329
+ coordinate: toComponentCoordinate(entry),
330
+ contentElements: frameContentElements,
331
+ frameModels,
332
+ viewportElements: frameViewportElements,
333
+ });
334
+ const frameLayouts = frameModels.map((model) => runelightPreviewContactSheetFrameLayout(geometryByFrame[model.name]));
335
+ const groupLayout = computeRunelightPreviewFrameGridLayout({
336
+ frameChromeHeight: runelightPreviewFrameChromeHeight,
337
+ gap: runelightPreviewFrameGridGap,
338
+ items: frameLayouts,
339
+ maxSide: runelightPreviewFrameGridMaxSide("desktop", selectedFrames.length),
340
+ minScale: runelightPreviewFrameGridMinScale,
341
+ previewScale: runelightPreviewFixedFrameScale,
342
+ });
343
+ const groupWidth = groupLayout.width;
344
+ const entryTitle = previewEntryTitle(entry);
345
+ return (_jsx("main", { "data-runelight-preview-contact-sheet": "true", style: visibleChromePreviewSheetStyle, children: _jsxs("div", { "data-runelight-preview-capture-bounds": "true", "data-runelight-preview-frame-group": "true", "data-runelight-preview-frame-group-measured": runelightPreviewContactSheetHasMeasuredEveryFrame(frameLayouts) ? "true" : "false", style: contactSheetComponentGroupStyle(groupWidth), children: [_jsx("header", { title: `${entry} / ${selectedFrames.length} ${selectedFrames.length === 1 ? "frame" : "frames"}`, style: contactSheetCardTitleSlotStyle(groupWidth), children: _jsxs("span", { style: contactSheetCardTitleStyle, children: [_jsx("span", { "aria-hidden": "true", style: contactSheetCardTitleIndicatorStyle }), _jsx("span", { style: contactSheetCardTitleTextStyle, children: entryTitle })] }) }), _jsx("div", { "data-runelight-preview-frame-grid-columns": groupLayout.columns, "data-runelight-preview-frame-grid-scale": formatRunelightPreviewCssNumber(groupLayout.previewScale), style: runelightPreviewContactSheetHasMeasuredEveryFrame(frameLayouts)
346
+ ? contactSheetFrameGridStyle(groupLayout)
347
+ : contactSheetMeasuringFrameGridStyle, children: frameModels.map((model, frameIndex) => {
348
+ const frameLayout = frameLayouts[frameIndex] ?? runelightPreviewContactSheetFrameLayout(undefined);
349
+ return (_jsxs("section", { "data-runelight-preview-frame": model.name, "data-runelight-preview-frame-measured": frameLayout.measured ? "true" : "false", style: frameLayout.measured
350
+ ? contactSheetFrameTileStyle(groupLayout.cellWidth)
351
+ : contactSheetFrameTileStyle(frameLayout.width), children: [_jsx("div", { style: contactSheetFrameShellStyle(frameLayout, groupLayout), children: _jsx("div", { style: contactSheetFrameScaledCanvasStyle(frameLayout, groupLayout), children: _jsx("div", { "data-runelight-preview-frame-viewport": model.name, ref: (element) => setRunelightPreviewContactSheetElement(frameViewportElements.current, model.name, element), style: contactSheetFrameViewportStyle(frameLayout), children: _jsx("div", { "data-runelight-preview-frame-content": model.name, ref: (element) => setRunelightPreviewContactSheetElement(frameContentElements.current, model.name, element), style: contactSheetFrameContentStyle(frameLayout), children: _jsx(GPreviewProvider, { boundaryCollector: model.collector, frameOverrides: frameOverridesForFrame(entry, model.name, frameOverrides), ...previewRuntimeProps(model.frame), children: _jsx(Component, { ...model.frame.props }) }) }) }) }) }), _jsx("span", { style: contactSheetFrameLabelSlotStyle, children: _jsx("span", { style: contactSheetFrameLabelStyle, children: model.name }) })] }, model.name));
352
+ }) })] }) }));
353
+ }
354
+ const useRunelightPreviewLayoutEffect = typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
355
+ function useRunelightPreviewContactSheetGeometry(input) {
356
+ const geometrySignature = `${input.coordinate}\n${input.frameModels.map((model) => model.name).join("\n")}`;
357
+ const [geometryState, setGeometryState] = React.useState(() => ({ frames: {}, signature: geometrySignature }));
358
+ const geometryByFrame = geometryState.signature === geometrySignature ? geometryState.frames : {};
359
+ useRunelightPreviewLayoutEffect(() => {
360
+ if (geometryState.signature !== geometrySignature) {
361
+ setGeometryState({ frames: {}, signature: geometrySignature });
362
+ return;
363
+ }
364
+ const nextGeometry = {};
365
+ let hasNextGeometry = false;
366
+ for (const model of input.frameModels) {
367
+ if (geometryByFrame[model.name])
368
+ continue;
369
+ const viewportElement = input.viewportElements.current.get(model.name);
370
+ const contentElement = input.contentElements.current.get(model.name);
371
+ if (!viewportElement || !contentElement)
372
+ continue;
373
+ const geometry = measureRunelightReactPreviewFrameGeometry({
374
+ collector: model.collector,
375
+ contentElement,
376
+ coordinate: input.coordinate,
377
+ viewportElement,
378
+ });
379
+ nextGeometry[model.name] = geometry;
380
+ hasNextGeometry = true;
381
+ }
382
+ if (!hasNextGeometry)
383
+ return;
384
+ setGeometryState((current) => ({
385
+ frames: current.signature === geometrySignature ? { ...current.frames, ...nextGeometry } : nextGeometry,
386
+ signature: geometrySignature,
387
+ }));
388
+ }, [geometryByFrame, geometrySignature, geometryState.signature, input]);
389
+ return geometryByFrame;
390
+ }
391
+ function setRunelightPreviewContactSheetElement(elements, frameName, element) {
392
+ if (element) {
393
+ elements.set(frameName, element);
394
+ }
395
+ else {
396
+ elements.delete(frameName);
397
+ }
398
+ }
399
+ function measureRunelightReactPreviewFrameGeometry(input) {
400
+ const localBoundaryRects = updateRunelightPreviewLocalBoundaryRects(input.collector, input.viewportElement);
401
+ const tree = input.collector.getTree();
402
+ const measuredBoundaryRect = runelightPreviewBoundaryRectForCoordinate(tree, input.coordinate);
403
+ const contentRect = input.contentElement.getBoundingClientRect();
404
+ const viewportSize = runelightPreviewMeasuredViewportSize(input.viewportElement, contentRect, localBoundaryRects);
405
+ const boundaryRect = clipRunelightPreviewBoundaryRectToViewport(measuredBoundaryRect, viewportSize);
406
+ return {
407
+ ...(boundaryRect ? { boundaryRect } : {}),
408
+ viewportSize,
409
+ };
410
+ }
411
+ function updateRunelightPreviewLocalBoundaryRects(collector, viewportElement) {
412
+ const viewportRect = viewportElement.getBoundingClientRect();
413
+ const rects = [];
414
+ for (const element of viewportElement.querySelectorAll("[data-runelight-boundary-id]")) {
415
+ const boundaryId = element.dataset.runelightBoundaryId;
416
+ const rect = readGBoundaryElementRect(element);
417
+ if (!boundaryId || !rect)
418
+ continue;
419
+ const localRect = {
420
+ x: rect.x - viewportRect.x,
421
+ y: rect.y - viewportRect.y,
422
+ width: rect.width,
423
+ height: rect.height,
424
+ };
425
+ collector.updateBoundaryRect(boundaryId, localRect);
426
+ rects.push(localRect);
427
+ }
428
+ return rects;
429
+ }
430
+ function runelightPreviewMeasuredViewportSize(viewportElement, contentRect, boundaryRects) {
431
+ const right = Math.max(1, viewportElement.scrollWidth, contentRect.width, ...boundaryRects.map((rect) => rect.x + rect.width));
432
+ const bottom = Math.max(1, viewportElement.scrollHeight, contentRect.height, ...boundaryRects.map((rect) => rect.y + rect.height));
433
+ return {
434
+ width: Math.ceil(right),
435
+ height: Math.ceil(bottom),
436
+ };
437
+ }
438
+ function runelightPreviewContactSheetFrameLayout(geometry) {
439
+ if (!geometry) {
440
+ return {
441
+ ...runelightPreviewFrameGridFallbackItem,
442
+ measured: false,
443
+ offset: { x: 0, y: 0 },
444
+ viewportSize: runelightPreviewFrameGridFallbackItem,
445
+ };
446
+ }
447
+ const width = previewFrameLayoutWidth(geometry.viewportSize, geometry.boundaryRect);
448
+ const height = previewFrameLayoutHeight(geometry.viewportSize, geometry.boundaryRect);
449
+ return {
450
+ height,
451
+ measured: true,
452
+ offset: previewFrameViewportOffset(geometry.boundaryRect),
453
+ viewportSize: geometry.viewportSize,
454
+ width,
455
+ };
456
+ }
457
+ function runelightPreviewContactSheetHasMeasuredEveryFrame(frames) {
458
+ return frames.every((frame) => frame.measured);
459
+ }
460
+ function previewFrameLayoutHeight(displaySize, rect) {
461
+ return rect ? Math.max(1, Math.ceil(rect.height)) : displaySize.height;
462
+ }
463
+ function previewFrameLayoutWidth(displaySize, rect) {
464
+ return rect ? Math.max(1, Math.ceil(rect.width)) : displaySize.width;
465
+ }
466
+ function previewFrameViewportOffset(rect) {
467
+ return {
468
+ x: Math.max(0, Math.floor(rect?.x ?? 0)),
469
+ y: Math.max(0, Math.floor(rect?.y ?? 0)),
470
+ };
471
+ }
472
+ function clipRunelightPreviewBoundaryRectToViewport(rect, viewport) {
473
+ if (!rect)
474
+ return undefined;
475
+ const left = clampRunelightPreviewNumber(rect.x, 0, viewport.width);
476
+ const top = clampRunelightPreviewNumber(rect.y, 0, viewport.height);
477
+ const right = clampRunelightPreviewNumber(rect.x + rect.width, 0, viewport.width);
478
+ const bottom = clampRunelightPreviewNumber(rect.y + rect.height, 0, viewport.height);
479
+ if (right <= left || bottom <= top)
480
+ return undefined;
481
+ return {
482
+ x: left,
483
+ y: top,
484
+ width: right - left,
485
+ height: bottom - top,
486
+ };
487
+ }
488
+ function runelightPreviewBoundaryRectForCoordinate(tree, coordinate) {
489
+ const node = runelightPreviewBoundaryNodeForCoordinate(tree, coordinate);
490
+ return node?.rect;
491
+ }
492
+ function runelightPreviewBoundaryNodeForCoordinate(tree, coordinate) {
493
+ if (!tree)
494
+ return undefined;
495
+ for (const node of tree) {
496
+ if (node.coordinate === coordinate)
497
+ return node;
498
+ const childMatch = runelightPreviewBoundaryNodeForCoordinate(node.children, coordinate);
499
+ if (childMatch)
500
+ return childMatch;
501
+ }
502
+ return undefined;
503
+ }
504
+ function clampRunelightPreviewNumber(value, min, max) {
505
+ return Math.min(max, Math.max(min, value));
506
+ }
507
+ function formatRunelightPreviewCssNumber(value) {
508
+ const rounded = Math.round(value * 1000) / 1000;
509
+ return String(Object.is(rounded, -0) ? 0 : rounded);
510
+ }
511
+ function RunelightPreviewDocumentBackground({ showChrome }) {
512
+ if (showChrome) {
513
+ return _jsx("style", { children: runelightVisiblePreviewDocumentStyle });
514
+ }
515
+ return _jsx("style", { children: runelightHiddenPreviewDocumentStyle });
516
+ }
517
+ function RunelightPreviewMessage({ detail, sessionId, title, }) {
518
+ React.useEffect(() => {
519
+ if (!sessionId)
520
+ return;
521
+ window.parent.postMessage(createGPreviewErrorMessage(sessionId, new Error(`${title}: ${detail}`)), "*");
522
+ }, [detail, sessionId, title]);
523
+ return (_jsxs("main", { "data-runelight-preview-message": true, style: {
524
+ backgroundColor: runelightPreviewStudioColors.canvasBg,
525
+ color: runelightPreviewStudioColors.text,
526
+ display: "grid",
527
+ fontFamily: runelightPreviewStudioFontFamily,
528
+ gap: 9,
529
+ minHeight: "100vh",
530
+ padding: 32,
531
+ }, children: [_jsx("h1", { style: { fontSize: 13, fontWeight: 500, lineHeight: 1.25, margin: 0 }, children: title }), _jsx("p", { style: { color: runelightPreviewStudioColors.textMuted, fontSize: 11, lineHeight: 1.45, margin: 0 }, children: detail })] }));
532
+ }
533
+ export function readRunelightReactPreviewRouteParams(params) {
534
+ return {
535
+ frameName: params.get("frame"),
536
+ frameOverrides: readRunelightReactPreviewFrameOverrides(params),
537
+ chrome: params.get("chrome"),
538
+ entry: params.get("entry"),
539
+ poolMode: params.get("pool") === "1",
540
+ renderRequestSequence: 0,
541
+ sessionId: params.get("sessionId"),
542
+ staticMode: params.get("static") === "1",
543
+ };
544
+ }
545
+ function readRunelightPreviewMailboxWindow() {
546
+ return typeof window === "undefined" ? null : window;
547
+ }
548
+ let runelightPreviewRenderTargetMailbox = null;
549
+ function useRunelightPreviewRenderTarget(routeTarget) {
550
+ const mailbox = routeTarget.poolMode ? ensureRunelightPreviewRenderTargetMailbox() : null;
551
+ const [messageTarget, setMessageTarget] = React.useState(() => mailbox?.getTarget() ?? null);
552
+ React.useEffect(() => {
553
+ if (!mailbox)
554
+ return;
555
+ const unsubscribe = mailbox.subscribe(setMessageTarget);
556
+ mailbox.announcePoolReady();
557
+ return unsubscribe;
558
+ }, [mailbox]);
559
+ if (!routeTarget.poolMode)
560
+ return routeTarget;
561
+ return messageTarget ?? routeTarget;
562
+ }
563
+ function ensureRunelightPreviewRenderTargetMailbox() {
564
+ const mailboxWindow = readRunelightPreviewMailboxWindow();
565
+ if (!mailboxWindow)
566
+ return null;
567
+ if (runelightPreviewRenderTargetMailbox)
568
+ return runelightPreviewRenderTargetMailbox;
569
+ const subscribers = new Set();
570
+ let mailboxState = createRunelightPreviewRenderTargetMailboxState(mailboxWindow.__runelightPreviewPendingRenderTarget ?? null);
571
+ let poolReadyAnnounced = false;
572
+ const applyRenderTarget = (target, options) => {
573
+ mailboxWindow.__runelightPreviewPendingRenderTarget = target;
574
+ if (options.acknowledge && target.sessionId) {
575
+ mailboxWindow.parent.postMessage(createGPreviewRenderAcceptedMessage(target.sessionId), "*");
576
+ }
577
+ const update = applyRunelightPreviewRenderTargetRequest(mailboxState, target, options);
578
+ mailboxState = update.state;
579
+ if (!update.shouldNotifySubscribers || !mailboxState.currentTarget)
580
+ return;
581
+ for (const subscriber of subscribers)
582
+ subscriber(mailboxState.currentTarget);
583
+ };
584
+ const render = (target) => applyRenderTarget(target, { acknowledge: true });
585
+ const handlePrehydrationRenderTarget = (event) => {
586
+ const target = event.detail;
587
+ if (isGPreviewRenderTarget(target))
588
+ applyRenderTarget(target, { acknowledge: false });
589
+ };
590
+ mailboxWindow.__runelightPreviewRenderTargetMailbox = { render };
591
+ if (mailboxWindow.__runelightPreviewPrehydrationMailboxInstalled) {
592
+ mailboxWindow.addEventListener("runelight:preview-render-target", handlePrehydrationRenderTarget);
593
+ }
594
+ else {
595
+ mailboxWindow.addEventListener("message", (event) => {
596
+ if (!isGPreviewRenderMessage(event.data))
597
+ return;
598
+ render(event.data.target);
599
+ });
600
+ }
601
+ runelightPreviewRenderTargetMailbox = {
602
+ announcePoolReady() {
603
+ if (poolReadyAnnounced)
604
+ return;
605
+ poolReadyAnnounced = true;
606
+ if (mailboxWindow.__runelightPreviewPrehydrationMailboxInstalled)
607
+ return;
608
+ mailboxWindow.setTimeout(() => {
609
+ mailboxWindow.parent.postMessage(createGPreviewPoolReadyMessage(), "*");
610
+ }, 0);
611
+ },
612
+ getTarget() {
613
+ return mailboxState.currentTarget;
614
+ },
615
+ render,
616
+ subscribe(subscriber) {
617
+ subscribers.add(subscriber);
618
+ const target = this.getTarget();
619
+ if (target)
620
+ subscriber(target);
621
+ return () => {
622
+ subscribers.delete(subscriber);
623
+ };
624
+ },
625
+ };
626
+ return runelightPreviewRenderTargetMailbox;
627
+ }
628
+ function previewRouteParamsFromRenderTarget(target, renderRequestSequence) {
629
+ return {
630
+ frameName: target.frameName,
631
+ frameOverrides: new Map(target.frameOverrides ?? []),
632
+ chrome: target.chrome,
633
+ entry: target.entry,
634
+ poolMode: false,
635
+ renderRequestSequence,
636
+ sessionId: target.sessionId,
637
+ staticMode: target.staticMode,
638
+ };
639
+ }
640
+ function createRunelightPreviewRenderTargetMailboxState(target) {
641
+ const currentTarget = target ? previewRouteParamsFromRenderTarget(target, 0) : null;
642
+ return {
643
+ currentTarget,
644
+ currentTargetContentKey: currentTarget ? previewRenderTargetContentKey(currentTarget) : null,
645
+ renderRequestSequence: 0,
646
+ };
647
+ }
648
+ function applyRunelightPreviewRenderTargetRequest(state, target, options) {
649
+ const renderRequestSequence = options.acknowledge ? state.renderRequestSequence + 1 : state.renderRequestSequence;
650
+ const currentTarget = previewRouteParamsFromRenderTarget(target, renderRequestSequence);
651
+ const currentTargetContentKey = previewRenderTargetContentKey(currentTarget);
652
+ const sameContentTarget = state.currentTargetContentKey === currentTargetContentKey;
653
+ if (sameContentTarget && !options.acknowledge) {
654
+ return { shouldNotifySubscribers: false, state };
655
+ }
656
+ return {
657
+ shouldNotifySubscribers: true,
658
+ state: {
659
+ currentTarget,
660
+ currentTargetContentKey,
661
+ renderRequestSequence,
662
+ },
663
+ };
664
+ }
665
+ function showChromeForPreviewTarget(chrome) {
666
+ return chrome === null ? true : chrome !== "0";
667
+ }
668
+ function previewRenderTargetKey(target) {
669
+ return JSON.stringify({
670
+ frameName: target.frameName,
671
+ frameOverrides: [...target.frameOverrides],
672
+ chrome: target.chrome,
673
+ entry: target.entry,
674
+ poolMode: target.poolMode,
675
+ renderRequestSequence: target.renderRequestSequence,
676
+ sessionId: target.sessionId,
677
+ staticMode: target.staticMode,
678
+ });
679
+ }
680
+ function previewRenderTargetContentKey(target) {
681
+ return JSON.stringify({
682
+ frameName: target.frameName,
683
+ frameOverrides: [...target.frameOverrides],
684
+ chrome: target.chrome,
685
+ entry: target.entry,
686
+ poolMode: target.poolMode,
687
+ sessionId: target.sessionId,
688
+ staticMode: target.staticMode,
689
+ });
690
+ }
691
+ function readRunelightReactPreviewFrameOverrides(params) {
692
+ return readRunelightPreviewFrameOverridesFromSearchParams(params);
693
+ }
694
+ function frameOverridesForFrame(entry, frameName, childOverrides) {
695
+ return new Map([...childOverrides, [toComponentCoordinate(entry), frameName]]);
696
+ }
697
+ export function parseRunelightReactPreviewEntry(entry) {
698
+ const [file, exportName] = entry.split("#", 2);
699
+ return { file, exportName: exportName || "default" };
700
+ }
701
+ function previewEntryTitle(entry) {
702
+ const coordinate = parseRunelightReactPreviewEntry(entry);
703
+ const fileName = coordinate.file.split(/[\\/]/).pop() ?? coordinate.file;
704
+ const componentName = fileName.replace(/\.g\.(?:tsx|vue)$/, "");
705
+ return coordinate.exportName === "default" ? componentName : `${componentName} / ${coordinate.exportName}`;
706
+ }
707
+ export function isRunelightReactPreviewComponent(value) {
708
+ return typeof value === "function";
709
+ }
710
+ function previewRuntimeProps(frame) {
711
+ return {
712
+ ...(Object.prototype.hasOwnProperty.call(frame, "scope") ? { scope: frame.scope } : {}),
713
+ ...(frame.providers ? { providerValues: new Map(frame.providers) } : {}),
714
+ };
715
+ }
716
+ function toComponentCoordinate(entry) {
717
+ return entry.includes("#") ? entry : `${entry}#default`;
718
+ }
719
+ function useRunelightPreviewProtocolMessages(sessionId, collector, enabled, options = {}) {
720
+ React.useEffect(() => {
721
+ if (!sessionId || !enabled)
722
+ return;
723
+ let scheduledFrame = 0;
724
+ let settleTimer = 0;
725
+ let settled = false;
726
+ let resizeObserver;
727
+ const settleStaticPreview = () => {
728
+ settled = true;
729
+ window.removeEventListener("message", handleMessage);
730
+ window.removeEventListener("resize", scheduleLayoutPublish);
731
+ resizeObserver?.disconnect();
732
+ if (scheduledFrame)
733
+ window.cancelAnimationFrame(scheduledFrame);
734
+ if (settleTimer)
735
+ window.clearTimeout(settleTimer);
736
+ scheduledFrame = 0;
737
+ settleTimer = 0;
738
+ };
739
+ const publishLayout = () => {
740
+ updateBoundaryRects(collector);
741
+ const tree = collector.getTree();
742
+ window.parent.postMessage(createGPreviewTreeMessage(sessionId, tree), "*");
743
+ window.parent.postMessage(createGPreviewResizeMessage(sessionId, previewContentSize(tree)), "*");
744
+ if (options.staticMode) {
745
+ if (settleTimer)
746
+ window.clearTimeout(settleTimer);
747
+ settleTimer = window.setTimeout(settleStaticPreview, 400);
748
+ }
749
+ };
750
+ const scheduleLayoutPublish = () => {
751
+ if (settled)
752
+ return;
753
+ if (scheduledFrame)
754
+ return;
755
+ scheduledFrame = window.requestAnimationFrame(() => {
756
+ scheduledFrame = 0;
757
+ publishLayout();
758
+ });
759
+ };
760
+ const handleMessage = (event) => {
761
+ if (!isRuntimeValuesRequest(event.data, sessionId))
762
+ return;
763
+ const values = collector.getValues(event.data.boundaryId);
764
+ if (values) {
765
+ window.parent.postMessage(createGPreviewValuesMessage(sessionId, values), "*");
766
+ }
767
+ };
768
+ window.addEventListener("message", handleMessage);
769
+ window.addEventListener("resize", scheduleLayoutPublish);
770
+ resizeObserver = "ResizeObserver" in window ? new ResizeObserver(scheduleLayoutPublish) : undefined;
771
+ resizeObserver?.observe(document.documentElement);
772
+ if (document.body)
773
+ resizeObserver?.observe(document.body);
774
+ window.parent.postMessage(createGPreviewReadyMessage(sessionId), "*");
775
+ publishLayout();
776
+ return () => {
777
+ window.removeEventListener("message", handleMessage);
778
+ window.removeEventListener("resize", scheduleLayoutPublish);
779
+ resizeObserver?.disconnect();
780
+ if (settleTimer)
781
+ window.clearTimeout(settleTimer);
782
+ if (scheduledFrame)
783
+ window.cancelAnimationFrame(scheduledFrame);
784
+ };
785
+ }, [collector, enabled, options.staticMode, sessionId]);
786
+ }
787
+ function previewContentSize(tree) {
788
+ const rects = tree.flatMap(flattenBoundaryRects);
789
+ if (rects.length === 0) {
790
+ return {
791
+ width: document.documentElement.scrollWidth,
792
+ height: document.documentElement.scrollHeight,
793
+ };
794
+ }
795
+ const left = Math.min(0, ...rects.map((rect) => rect.x));
796
+ const top = Math.min(0, ...rects.map((rect) => rect.y));
797
+ const right = Math.max(...rects.map((rect) => rect.x + rect.width));
798
+ const bottom = Math.max(...rects.map((rect) => rect.y + rect.height));
799
+ return {
800
+ width: Math.ceil(right - left),
801
+ height: Math.ceil(bottom - top),
802
+ };
803
+ }
804
+ function flattenBoundaryRects(node) {
805
+ return [...(node.rect ? [node.rect] : []), ...node.children.flatMap(flattenBoundaryRects)];
806
+ }
807
+ function isRuntimeValuesRequest(message, sessionId) {
808
+ return (typeof message === "object" &&
809
+ message !== null &&
810
+ message.type === "runelight:request-values" &&
811
+ message.protocolVersion === 1 &&
812
+ message.sessionId === sessionId &&
813
+ typeof message.boundaryId === "string");
814
+ }
815
+ function updateBoundaryRects(collector) {
816
+ for (const element of document.querySelectorAll("[data-runelight-boundary-id]")) {
817
+ const boundaryId = element.dataset.runelightBoundaryId;
818
+ const rect = readGBoundaryElementRect(element);
819
+ if (boundaryId && rect) {
820
+ collector.updateBoundaryRect(boundaryId, rect);
821
+ }
822
+ }
823
+ }
824
+ //# sourceMappingURL=preview.js.map