@gtsx/core 0.0.1

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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/dist/analyzer.d.ts +31 -0
  3. package/dist/analyzer.d.ts.map +1 -0
  4. package/dist/analyzer.js +397 -0
  5. package/dist/analyzer.js.map +1 -0
  6. package/dist/boundary-rect.d.ts +3 -0
  7. package/dist/boundary-rect.d.ts.map +1 -0
  8. package/dist/boundary-rect.js +70 -0
  9. package/dist/boundary-rect.js.map +1 -0
  10. package/dist/browser-capture.d.ts +8 -0
  11. package/dist/browser-capture.d.ts.map +1 -0
  12. package/dist/browser-capture.js +47 -0
  13. package/dist/browser-capture.js.map +1 -0
  14. package/dist/cli.d.ts +19 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +666 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/config-model.d.ts +6 -0
  19. package/dist/config-model.d.ts.map +1 -0
  20. package/dist/config-model.js +25 -0
  21. package/dist/config-model.js.map +1 -0
  22. package/dist/config-types.d.ts +32 -0
  23. package/dist/config-types.d.ts.map +1 -0
  24. package/dist/config-types.js +2 -0
  25. package/dist/config-types.js.map +1 -0
  26. package/dist/config.d.ts +10 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +79 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/define-config.d.ts +3 -0
  31. package/dist/define-config.d.ts.map +1 -0
  32. package/dist/define-config.js +4 -0
  33. package/dist/define-config.js.map +1 -0
  34. package/dist/index.d.ts +13 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +7 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/init.d.ts +11 -0
  39. package/dist/init.d.ts.map +1 -0
  40. package/dist/init.js +84 -0
  41. package/dist/init.js.map +1 -0
  42. package/dist/preview-protocol.d.ts +78 -0
  43. package/dist/preview-protocol.d.ts.map +1 -0
  44. package/dist/preview-protocol.js +95 -0
  45. package/dist/preview-protocol.js.map +1 -0
  46. package/dist/project-index.d.ts +35 -0
  47. package/dist/project-index.d.ts.map +1 -0
  48. package/dist/project-index.js +289 -0
  49. package/dist/project-index.js.map +1 -0
  50. package/dist/project-scope.d.ts +8 -0
  51. package/dist/project-scope.d.ts.map +1 -0
  52. package/dist/project-scope.js +56 -0
  53. package/dist/project-scope.js.map +1 -0
  54. package/dist/react-transform.d.ts +15 -0
  55. package/dist/react-transform.d.ts.map +1 -0
  56. package/dist/react-transform.js +138 -0
  57. package/dist/react-transform.js.map +1 -0
  58. package/dist/runtime-values.d.ts +73 -0
  59. package/dist/runtime-values.d.ts.map +1 -0
  60. package/dist/runtime-values.js +124 -0
  61. package/dist/runtime-values.js.map +1 -0
  62. package/dist/runtime.d.ts +44 -0
  63. package/dist/runtime.d.ts.map +1 -0
  64. package/dist/runtime.js +218 -0
  65. package/dist/runtime.js.map +1 -0
  66. package/dist/script-adapter.d.ts +22 -0
  67. package/dist/script-adapter.d.ts.map +1 -0
  68. package/dist/script-adapter.js +80 -0
  69. package/dist/script-adapter.js.map +1 -0
  70. package/dist/studio-client.d.ts +112 -0
  71. package/dist/studio-client.d.ts.map +1 -0
  72. package/dist/studio-client.js +1342 -0
  73. package/dist/studio-client.js.map +1 -0
  74. package/dist/studio-manifest-model.d.ts +41 -0
  75. package/dist/studio-manifest-model.d.ts.map +1 -0
  76. package/dist/studio-manifest-model.js +24 -0
  77. package/dist/studio-manifest-model.js.map +1 -0
  78. package/dist/studio-manifest.d.ts +23 -0
  79. package/dist/studio-manifest.d.ts.map +1 -0
  80. package/dist/studio-manifest.js +57 -0
  81. package/dist/studio-manifest.js.map +1 -0
  82. package/dist/types.d.ts +35 -0
  83. package/dist/types.d.ts.map +1 -0
  84. package/dist/types.js +2 -0
  85. package/dist/types.js.map +1 -0
  86. package/package.json +84 -0
@@ -0,0 +1,1342 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { G_PREVIEW_PROTOCOL_VERSION, createGPreviewRequestValuesMessage, } from "./preview-protocol.js";
5
+ export function applyStudioPreviewMessage(state, message) {
6
+ if (message.protocolVersion !== G_PREVIEW_PROTOCOL_VERSION || message.sessionId !== state.expectedSessionId) {
7
+ return state;
8
+ }
9
+ if (message.type === "gtsx:ready") {
10
+ return { ...state, ready: true };
11
+ }
12
+ if (message.type === "gtsx:tree") {
13
+ return { ...state, tree: message.tree };
14
+ }
15
+ if (message.type === "gtsx:resize") {
16
+ return { ...state, size: message.size };
17
+ }
18
+ if (message.type === "gtsx:error") {
19
+ return { ...state, error: message.error };
20
+ }
21
+ if (message.type === "gtsx:values") {
22
+ return {
23
+ ...state,
24
+ valuesByBoundaryId: {
25
+ ...state.valuesByBoundaryId,
26
+ [message.values.boundaryId]: message.values,
27
+ },
28
+ };
29
+ }
30
+ return state;
31
+ }
32
+ export function applyStudioPreviewMessageToFrameStates(frameStates, message, activeSessionIds) {
33
+ if (!activeSessionIds.has(message.sessionId))
34
+ return frameStates;
35
+ const currentFrameState = frameStates[message.sessionId] ?? {
36
+ expectedSessionId: message.sessionId,
37
+ ready: false,
38
+ };
39
+ return {
40
+ ...frameStates,
41
+ [message.sessionId]: applyStudioPreviewMessage(currentFrameState, message),
42
+ };
43
+ }
44
+ export function createStudioWorkspaceState(manifest, selection) {
45
+ const selected = resolveSelection(manifest, selection);
46
+ return {
47
+ canvasViewportPreset: "tablet",
48
+ columns: [{ components: selected.components }],
49
+ selectedCaseByCoordinate: {},
50
+ selectedCoordinatePath: [],
51
+ selectedRuntimeInstanceByCoordinate: {},
52
+ selectedViewportPresetByCoordinate: {},
53
+ };
54
+ }
55
+ export function selectStudioComponent(state, manifest, coordinate, tree) {
56
+ const selectedColumnIndex = state.columns.findIndex((column) => column.components.some((component) => component.coordinate === coordinate));
57
+ if (selectedColumnIndex < 0)
58
+ return state;
59
+ const nextColumns = state.columns.slice(0, selectedColumnIndex + 1);
60
+ const selectedPath = [...state.selectedCoordinatePath.slice(0, selectedColumnIndex), coordinate];
61
+ const childComponents = directChildComponentsForCoordinate(manifest, tree, coordinate);
62
+ if (childComponents.length > 0) {
63
+ nextColumns.push({ components: childComponents });
64
+ }
65
+ return {
66
+ canvasViewportPreset: canvasViewportPresetForWorkspace(state),
67
+ columns: nextColumns,
68
+ selectedCaseByCoordinate: state.selectedCaseByCoordinate,
69
+ selectedCoordinatePath: selectedPath,
70
+ selectedRuntimeInstanceByCoordinate: state.selectedRuntimeInstanceByCoordinate,
71
+ selectedViewportPresetByCoordinate: state.selectedViewportPresetByCoordinate,
72
+ };
73
+ }
74
+ export function selectedStudioCaseName(state, component) {
75
+ return state.selectedCaseByCoordinate[component.coordinate] ?? component.cases[0]?.name ?? "No cases";
76
+ }
77
+ export function changeStudioComponentCase(state, coordinate, caseName, options = {}) {
78
+ const selectedColumnIndex = state.columns.findIndex((column) => column.components.some((component) => component.coordinate === coordinate));
79
+ const columns = options.keepDrilldown || selectedColumnIndex < 0 ? state.columns : state.columns.slice(0, selectedColumnIndex + 1);
80
+ return {
81
+ canvasViewportPreset: canvasViewportPresetForWorkspace(state),
82
+ columns,
83
+ selectedCaseByCoordinate: {
84
+ ...state.selectedCaseByCoordinate,
85
+ [coordinate]: caseName,
86
+ },
87
+ selectedCoordinatePath: options.keepDrilldown || selectedColumnIndex < 0
88
+ ? state.selectedCoordinatePath
89
+ : [...state.selectedCoordinatePath.slice(0, selectedColumnIndex), coordinate],
90
+ selectedRuntimeInstanceByCoordinate: {},
91
+ selectedViewportPresetByCoordinate: state.selectedViewportPresetByCoordinate,
92
+ };
93
+ }
94
+ export function selectStudioRuntimeInstance(state, coordinate, boundaryId) {
95
+ return {
96
+ ...state,
97
+ selectedRuntimeInstanceByCoordinate: {
98
+ ...state.selectedRuntimeInstanceByCoordinate,
99
+ [coordinate]: boundaryId,
100
+ },
101
+ };
102
+ }
103
+ export function changeStudioViewportPreset(state, coordinate, preset) {
104
+ return {
105
+ ...state,
106
+ canvasViewportPreset: preset,
107
+ selectedViewportPresetByCoordinate: {
108
+ ...state.selectedViewportPresetByCoordinate,
109
+ [coordinate]: preset,
110
+ },
111
+ };
112
+ }
113
+ export function changeStudioCanvasViewportPreset(state, preset) {
114
+ return {
115
+ ...state,
116
+ canvasViewportPreset: preset,
117
+ selectedViewportPresetByCoordinate: {},
118
+ };
119
+ }
120
+ export function createStudioWorkspaceUrlSearchParams(selection, workspace) {
121
+ const params = new URLSearchParams();
122
+ if (selection)
123
+ params.set("selection", selection);
124
+ const canvasViewportPreset = canvasViewportPresetForWorkspace(workspace);
125
+ if (canvasViewportPreset !== "tablet")
126
+ params.set("canvasViewport", canvasViewportPreset);
127
+ for (const coordinate of workspace.selectedCoordinatePath) {
128
+ params.append("path", coordinate);
129
+ }
130
+ for (const coordinate of workspace.selectedCoordinatePath) {
131
+ const caseName = workspace.selectedCaseByCoordinate[coordinate];
132
+ if (caseName)
133
+ params.append("case", `${coordinate}:${caseName}`);
134
+ const boundaryId = workspace.selectedRuntimeInstanceByCoordinate[coordinate];
135
+ if (boundaryId)
136
+ params.append("instance", `${coordinate}:${boundaryId}`);
137
+ const viewport = workspace.selectedViewportPresetByCoordinate[coordinate];
138
+ if (viewport !== undefined)
139
+ params.append("viewport", `${coordinate}:${viewport}`);
140
+ }
141
+ return params;
142
+ }
143
+ export function createStudioWorkspaceStateFromUrl(manifest, params) {
144
+ const selection = params.get("selection") ?? undefined;
145
+ const resolvedSelection = resolveSelection(manifest, selection);
146
+ const rawPath = params.getAll("path");
147
+ const selectedCoordinatePath = rawPath.filter((coordinate) => Boolean(findManifestComponent(manifest, coordinate)));
148
+ const pathCoordinates = new Set(selectedCoordinatePath);
149
+ const selectedCaseByCoordinate = selectedCasesFromUrl(manifest, params, pathCoordinates);
150
+ const selectedRuntimeInstanceByCoordinate = selectedRuntimeInstancesFromUrl(manifest, params, pathCoordinates);
151
+ const selectedViewportPresetByCoordinate = selectedViewportPresetsFromUrl(manifest, params, pathCoordinates);
152
+ const canvasViewportPreset = canvasViewportPresetFromUrl(params, selectedViewportPresetByCoordinate, selectedCoordinatePath);
153
+ const hasInvalidUrlState = Boolean(selection && selection !== resolvedSelection.id) ||
154
+ rawPath.length !== selectedCoordinatePath.length ||
155
+ hasInvalidSelectedCase(manifest, params, pathCoordinates) ||
156
+ hasInvalidSelectedRuntimeInstance(manifest, params, pathCoordinates) ||
157
+ hasInvalidCanvasViewportPreset(params);
158
+ const warning = hasInvalidUrlState ? "Invalid Studio URL state was ignored." : undefined;
159
+ if (selectedCoordinatePath.length === 0) {
160
+ return {
161
+ selection: resolvedSelection.id,
162
+ workspace: {
163
+ canvasViewportPreset,
164
+ columns: [{ components: resolvedSelection.components }],
165
+ selectedCaseByCoordinate,
166
+ selectedCoordinatePath: [],
167
+ selectedRuntimeInstanceByCoordinate,
168
+ selectedViewportPresetByCoordinate,
169
+ },
170
+ ...(warning ? { warning } : {}),
171
+ };
172
+ }
173
+ return {
174
+ selection: resolvedSelection.id,
175
+ workspace: {
176
+ canvasViewportPreset,
177
+ columns: [
178
+ { components: resolvedSelection.components },
179
+ ...selectedCoordinatePath.slice(1).map((coordinate) => {
180
+ const component = findManifestComponent(manifest, coordinate);
181
+ return { components: component ? [component] : [] };
182
+ }),
183
+ ],
184
+ selectedCaseByCoordinate,
185
+ selectedCoordinatePath,
186
+ selectedRuntimeInstanceByCoordinate,
187
+ selectedViewportPresetByCoordinate,
188
+ },
189
+ ...(warning ? { warning } : {}),
190
+ };
191
+ }
192
+ export function createStudioRuntimeValuesRequest(manifest, workspace, boundaryId) {
193
+ const selectedCoordinate = workspace.selectedCoordinatePath.at(-1);
194
+ if (!selectedCoordinate)
195
+ return undefined;
196
+ const sourceCoordinate = workspace.selectedCoordinatePath.at(-2) ?? selectedCoordinate;
197
+ const sourceComponent = findManifestComponent(manifest, sourceCoordinate);
198
+ if (!sourceComponent)
199
+ return undefined;
200
+ const sourceCaseName = selectedStudioCaseName(workspace, sourceComponent);
201
+ const sessionId = previewSessionId(sourceComponent, sourceCaseName, previewCaseOverridesForComponent(workspace, sourceComponent));
202
+ return {
203
+ sessionId,
204
+ message: createGPreviewRequestValuesMessage(sessionId, boundaryId),
205
+ };
206
+ }
207
+ function selectedCasesFromUrl(manifest, params, pathCoordinates) {
208
+ const selectedCases = {};
209
+ for (const value of params.getAll("case")) {
210
+ const parsed = parseCoordinateValuePair(manifest, value);
211
+ if (!parsed)
212
+ continue;
213
+ const component = findManifestComponent(manifest, parsed.coordinate);
214
+ if (pathCoordinates.has(parsed.coordinate) && component?.cases.some((testCase) => testCase.name === parsed.value)) {
215
+ selectedCases[parsed.coordinate] = parsed.value;
216
+ }
217
+ }
218
+ return selectedCases;
219
+ }
220
+ function selectedRuntimeInstancesFromUrl(manifest, params, pathCoordinates) {
221
+ const selectedInstances = {};
222
+ for (const value of params.getAll("instance")) {
223
+ const parsed = parseCoordinateValuePair(manifest, value);
224
+ if (parsed && pathCoordinates.has(parsed.coordinate) && findManifestComponent(manifest, parsed.coordinate)) {
225
+ selectedInstances[parsed.coordinate] = parsed.value;
226
+ }
227
+ }
228
+ return selectedInstances;
229
+ }
230
+ function selectedViewportPresetsFromUrl(manifest, params, pathCoordinates) {
231
+ const selectedPresets = {};
232
+ for (const value of params.getAll("viewport")) {
233
+ const parsed = parseCoordinateValuePair(manifest, value);
234
+ if (parsed && pathCoordinates.has(parsed.coordinate) && isStudioViewportPreset(parsed.value)) {
235
+ selectedPresets[parsed.coordinate] = parsed.value;
236
+ }
237
+ }
238
+ return selectedPresets;
239
+ }
240
+ function canvasViewportPresetFromUrl(params, selectedViewportPresetByCoordinate, selectedCoordinatePath) {
241
+ const value = params.get("canvasViewport");
242
+ if (value && isStudioViewportPreset(value))
243
+ return value;
244
+ const legacyCoordinate = [...selectedCoordinatePath].reverse().find((coordinate) => selectedViewportPresetByCoordinate[coordinate]);
245
+ return legacyCoordinate ? selectedViewportPresetByCoordinate[legacyCoordinate] : "tablet";
246
+ }
247
+ function isStudioViewportPreset(value) {
248
+ return value === "phone" || value === "tablet" || value === "desktop";
249
+ }
250
+ function hasInvalidCanvasViewportPreset(params) {
251
+ const value = params.get("canvasViewport");
252
+ return Boolean(value && !isStudioViewportPreset(value));
253
+ }
254
+ function hasInvalidSelectedCase(manifest, params, pathCoordinates) {
255
+ return params.getAll("case").some((value) => {
256
+ const parsed = parseCoordinateValuePair(manifest, value);
257
+ if (!parsed || !pathCoordinates.has(parsed.coordinate))
258
+ return true;
259
+ const component = findManifestComponent(manifest, parsed.coordinate);
260
+ return !component?.cases.some((testCase) => testCase.name === parsed.value);
261
+ });
262
+ }
263
+ function hasInvalidSelectedRuntimeInstance(manifest, params, pathCoordinates) {
264
+ return params.getAll("instance").some((value) => {
265
+ const parsed = parseCoordinateValuePair(manifest, value);
266
+ return !parsed || !pathCoordinates.has(parsed.coordinate);
267
+ });
268
+ }
269
+ function parseCoordinateValuePair(manifest, value) {
270
+ const coordinate = manifest.files
271
+ .flatMap((file) => file.components)
272
+ .map((component) => component.coordinate)
273
+ .sort((left, right) => right.length - left.length)
274
+ .find((candidate) => value.startsWith(`${candidate}:`));
275
+ if (!coordinate)
276
+ return undefined;
277
+ return {
278
+ coordinate,
279
+ value: value.slice(coordinate.length + 1),
280
+ };
281
+ }
282
+ function directChildComponentsForCoordinate(manifest, tree, coordinate) {
283
+ const node = findBoundaryNode(tree, coordinate);
284
+ if (!node)
285
+ return [];
286
+ const componentsByCoordinate = new Map(manifest.files.flatMap((file) => file.components).map((component) => [component.coordinate, component]));
287
+ const seen = new Set();
288
+ const components = [];
289
+ for (const child of node.children) {
290
+ if (seen.has(child.coordinate))
291
+ continue;
292
+ const component = componentsByCoordinate.get(child.coordinate);
293
+ if (component) {
294
+ seen.add(child.coordinate);
295
+ components.push(component);
296
+ }
297
+ }
298
+ return components;
299
+ }
300
+ function findBoundaryNode(tree, coordinate) {
301
+ for (const node of tree) {
302
+ if (node.coordinate === coordinate)
303
+ return node;
304
+ const childMatch = findBoundaryNode(node.children, coordinate);
305
+ if (childMatch)
306
+ return childMatch;
307
+ }
308
+ return undefined;
309
+ }
310
+ export function StudioShell(props) {
311
+ const initialUrlState = React.useMemo(() => createStudioWorkspaceStateFromUrl(props.manifest, initialStudioUrlSearchParams(props.selection, props.urlSearch)), [props.manifest, props.selection, props.urlSearch]);
312
+ const [selection, setSelection] = React.useState(initialUrlState.selection);
313
+ const [urlWarning, setUrlWarning] = React.useState(initialUrlState.warning);
314
+ const [workspace, setWorkspace] = React.useState(initialUrlState.workspace);
315
+ const [frameStates, setFrameStates] = React.useState({});
316
+ const previewFrames = React.useRef(new Map());
317
+ const sessionIds = React.useMemo(() => currentPreviewSessionIds(workspace), [workspace]);
318
+ const selectionRef = React.useRef(selection);
319
+ React.useEffect(() => {
320
+ selectionRef.current = selection;
321
+ }, [selection]);
322
+ React.useEffect(() => {
323
+ const handleMessage = (event) => {
324
+ const message = event.data;
325
+ if (!isGPreviewProtocolMessage(message) || !sessionIds.has(message.sessionId))
326
+ return;
327
+ setFrameStates((current) => applyStudioPreviewMessageToFrameStates(current, message, sessionIds));
328
+ };
329
+ window.addEventListener("message", handleMessage);
330
+ return () => window.removeEventListener("message", handleMessage);
331
+ }, [sessionIds]);
332
+ React.useEffect(() => {
333
+ const handlePopState = () => {
334
+ const restored = createStudioWorkspaceStateFromUrl(props.manifest, new URLSearchParams(window.location.search));
335
+ setSelection(restored.selection);
336
+ setUrlWarning(restored.warning);
337
+ setWorkspace(restored.workspace);
338
+ setFrameStates({});
339
+ };
340
+ window.addEventListener("popstate", handlePopState);
341
+ return () => window.removeEventListener("popstate", handlePopState);
342
+ }, [props.manifest]);
343
+ const commitWorkspace = React.useCallback((updater) => {
344
+ setWorkspace((current) => {
345
+ const next = updater(current);
346
+ pushStudioWorkspaceUrlState(selectionRef.current, next);
347
+ return next;
348
+ });
349
+ }, []);
350
+ return (_jsx(StudioWorkspaceView, { frameStates: frameStates, manifest: props.manifest, onSelectComponent: (component, frameState) => {
351
+ commitWorkspace((current) => selectStudioComponent(current, props.manifest, component.coordinate, frameState?.tree ?? []));
352
+ }, onChangeCase: (component, caseName, options) => {
353
+ commitWorkspace((current) => changeStudioComponentCase(current, component.coordinate, caseName, options));
354
+ }, onChangeCanvasViewportPreset: (preset) => {
355
+ commitWorkspace((current) => changeStudioCanvasViewportPreset(current, preset));
356
+ }, onChangeSelection: (nextSelection) => {
357
+ const params = new URLSearchParams();
358
+ params.set("selection", nextSelection);
359
+ const canvasViewportPreset = canvasViewportPresetForWorkspace(workspace);
360
+ if (canvasViewportPreset !== "tablet")
361
+ params.set("canvasViewport", canvasViewportPreset);
362
+ const nextUrlState = createStudioWorkspaceStateFromUrl(props.manifest, params);
363
+ selectionRef.current = nextUrlState.selection;
364
+ setSelection(nextUrlState.selection);
365
+ setUrlWarning(nextUrlState.warning);
366
+ setWorkspace(nextUrlState.workspace);
367
+ setFrameStates({});
368
+ pushStudioWorkspaceUrlState(nextUrlState.selection, nextUrlState.workspace);
369
+ }, onChangeViewportPreset: (component, preset) => {
370
+ commitWorkspace((current) => changeStudioViewportPreset(current, component.coordinate, preset));
371
+ }, onPreviewFrameMount: (sessionId, frame) => {
372
+ if (frame) {
373
+ previewFrames.current.set(sessionId, frame);
374
+ }
375
+ else {
376
+ previewFrames.current.delete(sessionId);
377
+ }
378
+ }, onRequestValues: (request) => {
379
+ previewFrames.current.get(request.sessionId)?.contentWindow?.postMessage(request.message, "*");
380
+ }, onSelectRuntimeInstance: (component, boundaryId) => {
381
+ commitWorkspace((current) => selectStudioRuntimeInstance(current, component.coordinate, boundaryId));
382
+ }, selection: selection, urlWarning: urlWarning, workspace: workspace }));
383
+ }
384
+ function initialStudioUrlSearchParams(selection, urlSearch) {
385
+ if (urlSearch !== undefined)
386
+ return new URLSearchParams(urlSearch);
387
+ if (typeof window !== "undefined" && window.location.search) {
388
+ return new URLSearchParams(window.location.search);
389
+ }
390
+ const params = new URLSearchParams();
391
+ if (selection)
392
+ params.set("selection", selection);
393
+ return params;
394
+ }
395
+ function pushStudioWorkspaceUrlState(selection, workspace) {
396
+ if (typeof window === "undefined")
397
+ return;
398
+ const params = createStudioWorkspaceUrlSearchParams(selection, workspace);
399
+ const search = params.toString();
400
+ const nextUrl = `${window.location.pathname}${search ? `?${search}` : ""}`;
401
+ const currentUrl = `${window.location.pathname}${window.location.search}`;
402
+ if (nextUrl !== currentUrl) {
403
+ window.history.pushState({ gtsxStudio: true }, "", nextUrl);
404
+ }
405
+ }
406
+ const inspectorSectionTitleStyle = {
407
+ color: "#4b5563",
408
+ fontSize: 11,
409
+ fontWeight: 800,
410
+ letterSpacing: 0.8,
411
+ margin: "0 0 8px",
412
+ textTransform: "uppercase",
413
+ };
414
+ function clamp(value, min, max) {
415
+ return Math.min(max, Math.max(min, value));
416
+ }
417
+ const studioCanvasMinScale = 0.2;
418
+ const studioCanvasMaxScale = 2.5;
419
+ const studioPreviewPreloadMargin = 1200;
420
+ const studioCasePreviewScale = 0.25;
421
+ const studioCasePreviewWidth = 192;
422
+ export function applyStudioCanvasWheel(current, input) {
423
+ if (!input.ctrlKey && !input.metaKey) {
424
+ return {
425
+ ...current,
426
+ x: current.x - input.deltaX,
427
+ y: current.y - input.deltaY,
428
+ };
429
+ }
430
+ const viewportX = input.clientX - input.viewportLeft;
431
+ const viewportY = input.clientY - input.viewportTop;
432
+ const focalCanvasX = (viewportX - current.x) / current.scale;
433
+ const focalCanvasY = (viewportY - current.y) / current.scale;
434
+ const wheelDelta = -input.deltaY * wheelDeltaModeMultiplier(input.deltaMode) * 10;
435
+ const nextScale = clamp(current.scale * 2 ** wheelDelta, studioCanvasMinScale, studioCanvasMaxScale);
436
+ return {
437
+ scale: nextScale,
438
+ x: viewportX - focalCanvasX * nextScale,
439
+ y: viewportY - focalCanvasY * nextScale,
440
+ };
441
+ }
442
+ export function applyStudioCardSelectionAction(current, action) {
443
+ if (action.type === "clear")
444
+ return undefined;
445
+ if (action.source === "keyboard")
446
+ return undefined;
447
+ if (action.coordinate === current)
448
+ return current;
449
+ return action.coordinate;
450
+ }
451
+ function wheelDeltaModeMultiplier(deltaMode) {
452
+ if (deltaMode === 1)
453
+ return 0.05;
454
+ if (deltaMode === 2)
455
+ return 1;
456
+ return 0.002;
457
+ }
458
+ export function StudioWorkspaceView(props) {
459
+ const selected = resolveSelection(props.manifest, props.selection);
460
+ const [selectedCardCoordinate, setSelectedCardCoordinate] = React.useState();
461
+ const canvasViewportPreset = canvasViewportPresetForWorkspace(props.workspace);
462
+ const [canvas, setCanvas] = React.useState({ x: 40, y: 40, scale: 1 });
463
+ const panRef = React.useRef(null);
464
+ const canvasViewportRef = React.useRef(null);
465
+ const selectedCardComponent = selectedCardCoordinate ? findManifestComponent(props.manifest, selectedCardCoordinate) : undefined;
466
+ const selectedCaseName = selectedCardComponent ? selectedStudioCaseName(props.workspace, selectedCardComponent) : undefined;
467
+ React.useEffect(() => {
468
+ setSelectedCardCoordinate(undefined);
469
+ }, [props.selection]);
470
+ React.useEffect(() => {
471
+ const viewport = canvasViewportRef.current;
472
+ if (!viewport)
473
+ return;
474
+ const handleWheel = (event) => {
475
+ event.preventDefault();
476
+ const rect = viewport.getBoundingClientRect();
477
+ setCanvas((current) => applyStudioCanvasWheel(current, {
478
+ clientX: event.clientX,
479
+ clientY: event.clientY,
480
+ ctrlKey: event.ctrlKey,
481
+ deltaMode: event.deltaMode,
482
+ deltaX: event.deltaX,
483
+ deltaY: event.deltaY,
484
+ metaKey: event.metaKey,
485
+ viewportLeft: rect.left,
486
+ viewportTop: rect.top,
487
+ }));
488
+ };
489
+ viewport.addEventListener("wheel", handleWheel, { passive: false });
490
+ return () => viewport.removeEventListener("wheel", handleWheel);
491
+ }, []);
492
+ return (_jsxs("main", { style: {
493
+ display: "grid",
494
+ gridTemplateColumns: "210px minmax(0, 1fr)",
495
+ height: "100vh",
496
+ overflow: "hidden",
497
+ background: "#f5f6f8",
498
+ color: "#1f2328",
499
+ fontFamily: "ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif",
500
+ }, children: [_jsxs("aside", { style: {
501
+ background: "#fbfcfe",
502
+ borderRight: "1px solid #d8dee8",
503
+ boxShadow: "1px 0 0 rgba(255,255,255,0.8) inset",
504
+ minHeight: 0,
505
+ overflow: "auto",
506
+ padding: "18px 12px",
507
+ }, children: [_jsxs("div", { style: { display: "grid", gap: 3, marginBottom: 22 }, children: [_jsx("h1", { style: { fontSize: 18, letterSpacing: -0.2, lineHeight: 1.1, margin: 0 }, children: "GTSX Studio" }), _jsx("p", { style: { color: "#6b7280", fontSize: 12, margin: 0 }, children: "Component workspace" })] }), props.urlWarning ? (_jsx("p", { role: "status", style: {
508
+ background: "#fff8c5",
509
+ border: "1px solid #d4a72c",
510
+ borderRadius: 10,
511
+ color: "#5a1e02",
512
+ fontSize: 12,
513
+ lineHeight: 1.45,
514
+ padding: 10,
515
+ }, children: props.urlWarning })) : null, _jsx("nav", { "aria-label": "GTSX component index", style: { display: "grid", gap: 16 }, children: props.manifest.files.map((file) => (_jsx(FileGroupLink, { file: file, manifest: props.manifest, onChangeSelection: props.onChangeSelection
516
+ ? (nextSelection) => {
517
+ setSelectedCardCoordinate((current) => applyStudioCardSelectionAction(current, { type: "clear" }));
518
+ props.onChangeSelection?.(nextSelection);
519
+ }
520
+ : undefined, selectedId: selected.id }, file.path))) })] }), _jsx("section", { style: { display: "grid", minHeight: 0, minWidth: 0 }, children: _jsxs("div", { "aria-label": "GTSX canvas viewport", "data-gtsx-canvas-viewport": true, onPointerDown: (event) => {
521
+ if (event.target.closest("a,button,iframe"))
522
+ return;
523
+ setSelectedCardCoordinate((current) => applyStudioCardSelectionAction(current, { type: "clear" }));
524
+ panRef.current = {
525
+ pointerId: event.pointerId,
526
+ startX: event.clientX,
527
+ startY: event.clientY,
528
+ originX: canvas.x,
529
+ originY: canvas.y,
530
+ };
531
+ try {
532
+ event.currentTarget.setPointerCapture(event.pointerId);
533
+ }
534
+ catch {
535
+ // Browsers can cancel trackpad pointer streams before React handles them.
536
+ }
537
+ }, onPointerMove: (event) => {
538
+ const pan = panRef.current;
539
+ if (!pan || pan.pointerId !== event.pointerId)
540
+ return;
541
+ setCanvas((current) => ({
542
+ ...current,
543
+ x: pan.originX + event.clientX - pan.startX,
544
+ y: pan.originY + event.clientY - pan.startY,
545
+ }));
546
+ }, onPointerUp: (event) => {
547
+ if (panRef.current?.pointerId === event.pointerId)
548
+ panRef.current = null;
549
+ }, onPointerCancel: (event) => {
550
+ if (panRef.current?.pointerId === event.pointerId)
551
+ panRef.current = null;
552
+ }, ref: canvasViewportRef, style: {
553
+ backgroundColor: "#f5f6f8",
554
+ backgroundImage: "radial-gradient(circle at 1px 1px, rgba(31,35,40,0.10) 1px, transparent 0)",
555
+ backgroundSize: "24px 24px",
556
+ cursor: panRef.current ? "grabbing" : "grab",
557
+ height: "100%",
558
+ minHeight: 0,
559
+ overscrollBehavior: "contain",
560
+ overflow: "hidden",
561
+ position: "relative",
562
+ touchAction: "none",
563
+ }, role: "application", tabIndex: 0, children: [_jsx(ViewportPresetTabs, { onChange: (preset) => {
564
+ if (props.onChangeCanvasViewportPreset) {
565
+ props.onChangeCanvasViewportPreset(preset);
566
+ }
567
+ else {
568
+ for (const component of visibleWorkspaceComponents(props.workspace))
569
+ props.onChangeViewportPreset?.(component, preset);
570
+ }
571
+ }, selectedPreset: canvasViewportPreset }), _jsx("div", { "data-gtsx-canvas-surface": true, style: {
572
+ alignItems: "flex-start",
573
+ display: "flex",
574
+ gap: 40,
575
+ left: 0,
576
+ padding: "0 80px 80px 0",
577
+ position: "absolute",
578
+ top: 0,
579
+ transform: `translate(${canvas.x}px, ${canvas.y}px) scale(${canvas.scale})`,
580
+ transformOrigin: "0 0",
581
+ }, children: props.workspace.columns.map((column, columnIndex) => (_jsx("section", { "data-gtsx-column-index": columnIndex, style: { display: "grid", gap: 10, width: "max-content" }, children: column.components.map((component) => {
582
+ const caseName = selectedStudioCaseName(props.workspace, component);
583
+ const sessionId = previewSessionId(component, caseName, previewCaseOverridesForComponent(props.workspace, component));
584
+ return (_jsx(ComponentCard, { component: component, frameState: props.frameStates?.[sessionId], manifest: props.manifest, onPreviewFrameMount: props.onPreviewFrameMount, onSelect: (selectedComponent, frameState, source) => {
585
+ setSelectedCardCoordinate((current) => applyStudioCardSelectionAction(current, {
586
+ type: "activate-card",
587
+ coordinate: selectedComponent.coordinate,
588
+ source,
589
+ }));
590
+ props.onSelectComponent?.(selectedComponent, frameState);
591
+ }, selected: selectedCardCoordinate === component.coordinate, selectedCaseName: caseName, viewportPreset: canvasViewportPreset, workspace: props.workspace }, component.coordinate));
592
+ }) }, columnIndex))) }), selectedCardComponent && selectedCaseName ? (_jsx(SelectedComponentCasesSidebar, { component: selectedCardComponent, manifest: props.manifest, onChangeCase: props.onChangeCase, selectedCaseName: selectedCaseName })) : null] }) })] }));
593
+ }
594
+ function ViewportPresetTabs(props) {
595
+ const presets = ["phone", "tablet", "desktop"];
596
+ const selectedIndex = Math.max(0, presets.indexOf(props.selectedPreset));
597
+ return (_jsxs("div", { "aria-label": "Viewport", "data-gtsx-floating-viewport-controls": true, style: {
598
+ background: "rgba(255,255,255,0.82)",
599
+ border: "1px solid rgba(216,222,232,0.92)",
600
+ borderRadius: 999,
601
+ boxShadow: "0 10px 30px rgba(31,35,40,0.12)",
602
+ display: "grid",
603
+ gridTemplateColumns: `repeat(${presets.length}, 34px)`,
604
+ left: "50%",
605
+ padding: 3,
606
+ position: "absolute",
607
+ top: 16,
608
+ transform: "translateX(-50%)",
609
+ zIndex: 3,
610
+ }, children: [_jsx("span", { "aria-hidden": "true", "data-gtsx-viewport-tab-highlight": true, style: {
611
+ background: "#ffffff",
612
+ border: "1px solid #d8dee8",
613
+ borderRadius: 999,
614
+ boxShadow: "0 3px 10px rgba(31,35,40,0.12)",
615
+ height: 28,
616
+ left: 3,
617
+ position: "absolute",
618
+ top: 3,
619
+ transform: `translateX(${selectedIndex * 34}px)`,
620
+ transition: "transform 120ms ease",
621
+ width: 32,
622
+ } }), presets.map((preset) => (_jsx("button", { "aria-label": `Viewport ${preset}`, "data-gtsx-viewport-control": preset, onClick: () => props.onChange(preset), style: {
623
+ alignItems: "center",
624
+ background: "transparent",
625
+ border: 0,
626
+ color: props.selectedPreset === preset ? "#0969da" : "#57606a",
627
+ cursor: "pointer",
628
+ display: "grid",
629
+ height: 28,
630
+ justifyItems: "center",
631
+ padding: 0,
632
+ position: "relative",
633
+ width: 32,
634
+ zIndex: 1,
635
+ }, title: preset, type: "button", children: _jsx(ViewportPresetIcon, { preset: preset }) }, preset)))] }));
636
+ }
637
+ function ViewportPresetIcon(props) {
638
+ if (props.preset === "phone") {
639
+ return (_jsx("span", { "aria-hidden": "true", style: {
640
+ border: "1.5px solid currentColor",
641
+ borderRadius: 3,
642
+ display: "block",
643
+ height: 16,
644
+ width: 9,
645
+ } }));
646
+ }
647
+ if (props.preset === "tablet") {
648
+ return (_jsx("span", { "aria-hidden": "true", style: {
649
+ border: "1.5px solid currentColor",
650
+ borderRadius: 3,
651
+ display: "block",
652
+ height: 15,
653
+ width: 12,
654
+ } }));
655
+ }
656
+ return (_jsx("span", { "aria-hidden": "true", style: {
657
+ border: "1.5px solid currentColor",
658
+ borderRadius: 2,
659
+ display: "block",
660
+ height: 11,
661
+ width: 18,
662
+ } }));
663
+ }
664
+ function FileGroupLink(props) {
665
+ const fileSelection = `file:${props.file.path}`;
666
+ const fileName = props.file.path.split("/").pop() ?? props.file.path;
667
+ const directoryName = props.file.path.includes("/") ? props.file.path.slice(0, props.file.path.lastIndexOf("/")) : "";
668
+ return (_jsxs("section", { style: { display: "grid", gap: 8 }, children: [_jsxs("a", { href: `?selection=${encodeURIComponent(fileSelection)}`, onClick: (event) => {
669
+ if (!props.onChangeSelection)
670
+ return;
671
+ event.preventDefault();
672
+ props.onChangeSelection(fileSelection);
673
+ }, style: {
674
+ color: props.selectedId === fileSelection ? "#0969da" : "#57606a",
675
+ display: "grid",
676
+ gap: 2,
677
+ fontSize: 12,
678
+ fontWeight: 750,
679
+ lineHeight: 1.35,
680
+ overflowWrap: "anywhere",
681
+ textDecoration: "none",
682
+ }, children: [_jsx("span", { children: fileName }), directoryName ? (_jsx("span", { style: { color: "#8b949e", fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 10, fontWeight: 500 }, children: directoryName })) : null] }), _jsx("div", { style: { display: "grid", gap: 7 }, children: props.file.components.map((component) => {
683
+ const componentSelection = `component:${component.coordinate}`;
684
+ const isSelected = props.selectedId === componentSelection;
685
+ return (_jsx("a", { href: `?selection=${encodeURIComponent(componentSelection)}`, onClick: (event) => {
686
+ if (!props.onChangeSelection)
687
+ return;
688
+ event.preventDefault();
689
+ props.onChangeSelection(componentSelection);
690
+ }, style: {
691
+ background: isSelected ? "#eaf4ff" : "#ffffff",
692
+ border: "1px solid",
693
+ borderColor: isSelected ? "#8ec5ff" : "#d8dee8",
694
+ borderRadius: 12,
695
+ boxShadow: isSelected ? "0 6px 18px rgba(9,105,218,0.12)" : "0 1px 2px rgba(31,35,40,0.04)",
696
+ color: "#1f2328",
697
+ display: "block",
698
+ overflow: "hidden",
699
+ padding: 8,
700
+ textDecoration: "none",
701
+ }, title: component.componentName, children: _jsx(SidebarComponentPreview, { component: component, manifest: props.manifest }) }, component.coordinate));
702
+ }) })] }));
703
+ }
704
+ function SidebarComponentPreview(props) {
705
+ const previewUrl = sidebarPreviewUrlForComponent(props.manifest, props.component);
706
+ const sessionId = sidebarPreviewSessionId(props.component);
707
+ const containerRef = React.useRef(null);
708
+ const [shouldLoad, setShouldLoad] = React.useState(false);
709
+ const [boundaryRect, setBoundaryRect] = React.useState();
710
+ React.useEffect(() => {
711
+ const container = containerRef.current;
712
+ if (!container)
713
+ return;
714
+ if (!("IntersectionObserver" in window)) {
715
+ setShouldLoad(true);
716
+ return;
717
+ }
718
+ const observer = new IntersectionObserver((entries) => {
719
+ if (entries.some((entry) => entry.isIntersecting)) {
720
+ setShouldLoad(true);
721
+ observer.disconnect();
722
+ }
723
+ }, { rootMargin: "500px" });
724
+ observer.observe(container);
725
+ return () => observer.disconnect();
726
+ }, []);
727
+ React.useEffect(() => {
728
+ if (!shouldLoad)
729
+ return;
730
+ const handleMessage = (event) => {
731
+ const message = event.data;
732
+ if (!isGPreviewProtocolMessage(message) || message.sessionId !== sessionId || message.type !== "gtsx:tree")
733
+ return;
734
+ setBoundaryRect(findBoundaryNode(message.tree, props.component.coordinate)?.rect);
735
+ };
736
+ window.addEventListener("message", handleMessage);
737
+ return () => window.removeEventListener("message", handleMessage);
738
+ }, [props.component.coordinate, sessionId, shouldLoad]);
739
+ const height = boundaryRect ? Math.max(1, Math.ceil((Math.max(0, boundaryRect.y) + boundaryRect.height) * 0.24)) : 96;
740
+ return (_jsx("div", { "aria-hidden": "true", "data-gtsx-sidebar-preview-coordinate": props.component.coordinate, "data-gtsx-sidebar-preview-loaded": shouldLoad ? "true" : undefined, "data-gtsx-viewport-preset": "tablet", ref: containerRef, style: {
741
+ background: "#f5f6f8",
742
+ height,
743
+ overflow: "hidden",
744
+ position: "relative",
745
+ width: 184.32,
746
+ }, children: previewUrl && shouldLoad ? (_jsx("iframe", { "data-gtsx-sidebar-preview-frame": "true", src: previewUrl, style: {
747
+ background: "transparent",
748
+ border: 0,
749
+ height: 1024,
750
+ left: 0,
751
+ pointerEvents: "none",
752
+ position: "absolute",
753
+ top: 0,
754
+ transform: "scale(0.24)",
755
+ transformOrigin: "0 0",
756
+ width: 768,
757
+ }, tabIndex: -1, title: `${props.component.componentName} thumbnail` })) : null }));
758
+ }
759
+ function sidebarPreviewUrlForComponent(manifest, component) {
760
+ const caseName = component.cases[0]?.name;
761
+ if (!caseName)
762
+ return undefined;
763
+ const params = new URLSearchParams({
764
+ entry: component.coordinate,
765
+ case: caseName,
766
+ chrome: "0",
767
+ sessionId: sidebarPreviewSessionId(component),
768
+ });
769
+ return `${manifest.routes.preview}?${params.toString()}`;
770
+ }
771
+ function sidebarPreviewSessionId(component) {
772
+ return `sidebar:${component.coordinate}:${component.cases[0]?.name ?? "No cases"}`;
773
+ }
774
+ function SelectedComponentCasesSidebar(props) {
775
+ return (_jsx("aside", { "aria-label": `${props.component.componentName} cases`, "data-gtsx-case-sidebar": props.component.coordinate, onPointerDown: (event) => event.stopPropagation(), style: {
776
+ background: "transparent",
777
+ border: 0,
778
+ boxShadow: "none",
779
+ display: "grid",
780
+ gap: 14,
781
+ maxHeight: "calc(100% - 96px)",
782
+ overflow: "auto",
783
+ padding: 0,
784
+ position: "absolute",
785
+ right: 12,
786
+ top: 72,
787
+ width: 200,
788
+ zIndex: 4,
789
+ }, children: props.component.cases.map((testCase) => (_jsx(CasePreviewCard, { component: props.component, manifest: props.manifest, onChangeCase: props.onChangeCase, selected: props.selectedCaseName === testCase.name, testCaseName: testCase.name }, testCase.name))) }));
790
+ }
791
+ function CasePreviewCard(props) {
792
+ const sessionId = casePreviewSessionId(props.component, props.testCaseName);
793
+ const previewUrl = casePreviewUrlForComponent(props.manifest, props.component, props.testCaseName);
794
+ const [boundaryRect, setBoundaryRect] = React.useState();
795
+ React.useEffect(() => {
796
+ const handleMessage = (event) => {
797
+ const message = event.data;
798
+ if (!isGPreviewProtocolMessage(message) || message.sessionId !== sessionId || message.type !== "gtsx:tree")
799
+ return;
800
+ setBoundaryRect(findBoundaryNode(message.tree, props.component.coordinate)?.rect);
801
+ };
802
+ window.addEventListener("message", handleMessage);
803
+ return () => window.removeEventListener("message", handleMessage);
804
+ }, [props.component.coordinate, sessionId]);
805
+ const height = boundaryRect ? Math.max(64, Math.ceil(boundaryRect.height * studioCasePreviewScale) + 32) : 112;
806
+ const iframeOffset = boundaryRect
807
+ ? {
808
+ left: (studioCasePreviewWidth - boundaryRect.width * studioCasePreviewScale) / 2 - boundaryRect.x * studioCasePreviewScale,
809
+ top: (height - boundaryRect.height * studioCasePreviewScale) / 2 - boundaryRect.y * studioCasePreviewScale,
810
+ }
811
+ : { left: 0, top: 0 };
812
+ return (_jsxs("div", { "data-gtsx-case-preview-card": props.testCaseName, "data-gtsx-case-preview-selected": props.selected ? "true" : undefined, onKeyDown: (event) => {
813
+ if (event.key !== "Enter" && event.key !== " ")
814
+ return;
815
+ event.preventDefault();
816
+ props.onChangeCase?.(props.component, props.testCaseName, { keepDrilldown: true });
817
+ }, onClick: () => props.onChangeCase?.(props.component, props.testCaseName, { keepDrilldown: true }), role: "button", style: {
818
+ background: "transparent",
819
+ border: 0,
820
+ boxShadow: "none",
821
+ color: "#1f2328",
822
+ cursor: props.onChangeCase ? "pointer" : "default",
823
+ display: "grid",
824
+ gap: 6,
825
+ padding: 0,
826
+ textAlign: "left",
827
+ }, tabIndex: 0, children: [_jsx("strong", { style: { fontSize: 12, lineHeight: 1.2 }, children: props.testCaseName }), _jsx("div", { "aria-hidden": "true", "data-gtsx-case-preview-frame": props.testCaseName, style: {
828
+ background: "#ffffff",
829
+ border: "1px solid",
830
+ borderColor: props.selected ? "#0d99ff" : "transparent",
831
+ borderRadius: 10,
832
+ boxShadow: props.selected ? "0 0 0 4px rgba(13, 153, 255, 0.18)" : "none",
833
+ height,
834
+ overflow: "hidden",
835
+ position: "relative",
836
+ width: studioCasePreviewWidth,
837
+ }, children: _jsx("iframe", { src: previewUrl, style: {
838
+ background: "transparent",
839
+ border: 0,
840
+ height: 1024,
841
+ left: iframeOffset.left,
842
+ pointerEvents: "none",
843
+ position: "absolute",
844
+ top: iframeOffset.top,
845
+ transform: `scale(${studioCasePreviewScale})`,
846
+ transformOrigin: "0 0",
847
+ width: 768,
848
+ }, tabIndex: -1, title: `${props.component.componentName} ${props.testCaseName} preview` }) })] }));
849
+ }
850
+ function casePreviewUrlForComponent(manifest, component, caseName) {
851
+ const params = new URLSearchParams({
852
+ entry: component.coordinate,
853
+ case: caseName,
854
+ chrome: "0",
855
+ sessionId: casePreviewSessionId(component, caseName),
856
+ });
857
+ return `${manifest.routes.preview}?${params.toString()}`;
858
+ }
859
+ function casePreviewSessionId(component, caseName) {
860
+ return `case:${component.coordinate}:${caseName}`;
861
+ }
862
+ function ComponentCard(props) {
863
+ const defaultCase = props.selectedCaseName;
864
+ const previewError = getPreviewError(props.component);
865
+ const caseOverrides = previewCaseOverridesForComponent(props.workspace, props.component);
866
+ const sessionId = previewSessionId(props.component, defaultCase, caseOverrides);
867
+ const previewSize = previewFrameSize(props.viewportPreset, props.frameState?.size);
868
+ const [measuredSize, setMeasuredSize] = React.useState();
869
+ const displaySize = mergePreviewFrameSize(previewSize, measuredSize, props.viewportPreset);
870
+ const previewUrl = previewUrlForComponent(props.manifest, props.component, defaultCase, caseOverrides);
871
+ const cardWidth = componentCardLayoutWidth(displaySize, props.frameState?.tree, props.component.coordinate);
872
+ const selectedBoundaryRect = props.selected ? selectedBoundaryRectForComponent(props.frameState?.tree, props.component.coordinate) : undefined;
873
+ return (_jsxs("article", { "aria-pressed": props.selected, "data-gtsx-card-coordinate": props.component.coordinate, "data-gtsx-card-selected": props.selected ? "true" : undefined, onKeyDown: (event) => {
874
+ if (event.key !== "Enter" && event.key !== " ")
875
+ return;
876
+ event.preventDefault();
877
+ props.onSelect?.(props.component, props.frameState, "keyboard");
878
+ }, role: "button", style: {
879
+ cursor: props.onSelect ? "pointer" : "default",
880
+ display: "grid",
881
+ gap: 6,
882
+ width: cardWidth,
883
+ }, tabIndex: 0, children: [_jsx("strong", { style: {
884
+ color: "inherit",
885
+ fontSize: 13,
886
+ letterSpacing: -0.05,
887
+ lineHeight: 1.2,
888
+ overflow: "hidden",
889
+ textOverflow: "ellipsis",
890
+ whiteSpace: "nowrap",
891
+ }, children: props.component.componentName }), previewError || props.frameState?.error ? (_jsx(PreviewError, { caseName: defaultCase, coordinate: props.component.coordinate, error: props.frameState?.error ?? { message: previewError ?? "Unknown preview error" }, previewUrl: previewUrl })) : (_jsx(LazyPreviewFrame, { "data-gtsx-preview-session-id": sessionId, boundaryRect: selectedBoundaryRectForComponent(props.frameState?.tree, props.component.coordinate), coordinate: props.component.coordinate, frameState: props.frameState, onMeasureSize: setMeasuredSize, onSelect: () => props.onSelect?.(props.component, props.frameState, "pointer"), onPreviewFrameMount: props.onPreviewFrameMount, previewUrl: previewUrl, selectedBoundaryRect: selectedBoundaryRect, size: displaySize, sessionId: sessionId, title: `${props.component.componentName} preview`, viewportPreset: props.viewportPreset }))] }));
892
+ }
893
+ export function componentCardLayoutWidth(displaySize, tree, coordinate) {
894
+ const rect = tree ? findBoundaryNode(tree, coordinate)?.rect : undefined;
895
+ if (rect)
896
+ return Math.max(280, Math.ceil(Math.max(0, rect.x) + rect.width));
897
+ return typeof displaySize.width === "number" ? displaySize.width + 28 : 520;
898
+ }
899
+ function canvasViewportPresetForWorkspace(workspace) {
900
+ if (workspace.canvasViewportPreset)
901
+ return workspace.canvasViewportPreset;
902
+ const selectedCoordinate = workspace.selectedCoordinatePath.at(-1);
903
+ if (selectedCoordinate)
904
+ return workspace.selectedViewportPresetByCoordinate[selectedCoordinate] ?? "tablet";
905
+ return Object.values(workspace.selectedViewportPresetByCoordinate)[0] ?? "tablet";
906
+ }
907
+ function visibleWorkspaceComponents(workspace) {
908
+ const seen = new Set();
909
+ const components = [];
910
+ for (const component of workspace.columns.flatMap((column) => column.components)) {
911
+ if (seen.has(component.coordinate))
912
+ continue;
913
+ seen.add(component.coordinate);
914
+ components.push(component);
915
+ }
916
+ return components;
917
+ }
918
+ function selectedBoundaryRectForComponent(tree, coordinate) {
919
+ return tree ? findBoundaryNode(tree, coordinate)?.rect : undefined;
920
+ }
921
+ function LazyPreviewFrame(props) {
922
+ const containerRef = React.useRef(null);
923
+ const [shouldLoad, setShouldLoad] = React.useState(false);
924
+ const requestedSlot = React.useMemo(() => ({
925
+ previewUrl: props.previewUrl,
926
+ sessionId: props.sessionId,
927
+ title: props.title,
928
+ }), [props.previewUrl, props.sessionId, props.title]);
929
+ const [activeSlot, setActiveSlot] = React.useState(requestedSlot);
930
+ const [pendingSlot, setPendingSlot] = React.useState();
931
+ const layoutHeight = previewFrameLayoutHeight(props.size, props.boundaryRect);
932
+ React.useEffect(() => {
933
+ const container = containerRef.current;
934
+ if (!container)
935
+ return;
936
+ if (!("IntersectionObserver" in window)) {
937
+ setShouldLoad(true);
938
+ return;
939
+ }
940
+ if (isElementNearViewport(container, studioPreviewPreloadMargin)) {
941
+ setShouldLoad(true);
942
+ return;
943
+ }
944
+ const observer = new IntersectionObserver((entries) => {
945
+ if (entries.some((entry) => entry.isIntersecting)) {
946
+ setShouldLoad(true);
947
+ observer.disconnect();
948
+ }
949
+ }, { rootMargin: `${studioPreviewPreloadMargin}px` });
950
+ observer.observe(container);
951
+ return () => observer.disconnect();
952
+ }, []);
953
+ React.useEffect(() => {
954
+ if (!shouldLoad)
955
+ return;
956
+ if (isSamePreviewFrameSlot(activeSlot, requestedSlot)) {
957
+ setPendingSlot(undefined);
958
+ return;
959
+ }
960
+ setPendingSlot((current) => {
961
+ if (current && isSamePreviewFrameSlot(current, requestedSlot))
962
+ return current;
963
+ return requestedSlot;
964
+ });
965
+ }, [activeSlot, requestedSlot, shouldLoad]);
966
+ React.useEffect(() => {
967
+ if (!pendingSlot || !isSamePreviewFrameSlot(pendingSlot, requestedSlot))
968
+ return;
969
+ if (!props.frameState?.tree)
970
+ return;
971
+ setActiveSlot(pendingSlot);
972
+ setPendingSlot(undefined);
973
+ }, [pendingSlot, props.frameState?.tree, requestedSlot]);
974
+ const frameSlots = shouldLoad
975
+ ? [
976
+ { active: true, slot: activeSlot },
977
+ ...(pendingSlot && !isSamePreviewFrameSlot(pendingSlot, activeSlot) ? [{ active: false, slot: pendingSlot }] : []),
978
+ ]
979
+ : [];
980
+ return (_jsxs("div", { "data-gtsx-preview-session-id": props["data-gtsx-preview-session-id"], "data-gtsx-preview-src": props.previewUrl, "data-gtsx-viewport-preset": props.viewportPreset, ref: containerRef, style: {
981
+ height: layoutHeight,
982
+ overflow: "visible",
983
+ position: "relative",
984
+ width: props.size.width,
985
+ }, children: [frameSlots.map(({ active, slot }) => (_jsx(BufferedPreviewIframe, { active: active, onMeasureSize: props.onMeasureSize, onPreviewFrameMount: props.onPreviewFrameMount, size: props.size, slot: slot }, previewFrameSlotKey(slot)))), props.boundaryRect ? _jsx(ComponentBoundsHitTarget, { coordinate: props.coordinate, onSelect: props.onSelect, rect: props.boundaryRect }) : null, props.selectedBoundaryRect ? _jsx(SelectedBoundaryOutline, { rect: props.selectedBoundaryRect }) : null] }));
986
+ }
987
+ function BufferedPreviewIframe(props) {
988
+ const [frameElement, setFrameElement] = React.useState(null);
989
+ React.useEffect(() => {
990
+ if (!frameElement)
991
+ return;
992
+ const measure = () => {
993
+ const size = measureIframeContentSize(frameElement);
994
+ if (size)
995
+ props.onMeasureSize?.(size);
996
+ };
997
+ const timers = [window.setTimeout(measure, 0), window.setTimeout(measure, 80), window.setTimeout(measure, 250)];
998
+ return () => {
999
+ for (const timer of timers)
1000
+ window.clearTimeout(timer);
1001
+ };
1002
+ }, [frameElement, props.onMeasureSize]);
1003
+ return (_jsx("iframe", { "aria-hidden": props.active ? undefined : true, loading: "eager", onLoad: (event) => {
1004
+ const frame = event.currentTarget;
1005
+ const measure = () => {
1006
+ const size = measureIframeContentSize(frame);
1007
+ if (size)
1008
+ props.onMeasureSize?.(size);
1009
+ };
1010
+ measure();
1011
+ window.setTimeout(measure, 80);
1012
+ }, ref: (frame) => {
1013
+ setFrameElement(frame);
1014
+ props.onPreviewFrameMount?.(props.slot.sessionId, frame);
1015
+ }, src: props.slot.previewUrl, style: {
1016
+ background: "transparent",
1017
+ border: 0,
1018
+ height: props.size.height,
1019
+ opacity: props.active ? 1 : 0,
1020
+ pointerEvents: "none",
1021
+ position: "absolute",
1022
+ transition: "opacity 80ms linear",
1023
+ width: props.size.width,
1024
+ zIndex: props.active ? 1 : 0,
1025
+ }, tabIndex: props.active ? undefined : -1, title: props.slot.title }));
1026
+ }
1027
+ function isSamePreviewFrameSlot(left, right) {
1028
+ return left.previewUrl === right.previewUrl && left.sessionId === right.sessionId;
1029
+ }
1030
+ function previewFrameSlotKey(slot) {
1031
+ return `${slot.sessionId}\n${slot.previewUrl}`;
1032
+ }
1033
+ function isElementNearViewport(element, margin) {
1034
+ const rect = element.getBoundingClientRect();
1035
+ return rect.bottom >= -margin && rect.right >= -margin && rect.top <= window.innerHeight + margin && rect.left <= window.innerWidth + margin;
1036
+ }
1037
+ function ComponentBoundsHitTarget(props) {
1038
+ return (_jsx("div", { "aria-hidden": "true", "data-gtsx-card-select-coordinate": props.coordinate, "data-gtsx-card-select-target": "component-bounds", onClick: (event) => {
1039
+ event.stopPropagation();
1040
+ props.onSelect?.();
1041
+ }, onPointerDown: (event) => event.stopPropagation(), style: {
1042
+ height: props.rect.height,
1043
+ left: props.rect.x,
1044
+ pointerEvents: "auto",
1045
+ position: "absolute",
1046
+ top: props.rect.y,
1047
+ width: props.rect.width,
1048
+ zIndex: 2,
1049
+ } }));
1050
+ }
1051
+ function SelectedBoundaryOutline(props) {
1052
+ return (_jsx("div", { "aria-hidden": "true", "data-gtsx-selection-outline": "true", style: {
1053
+ height: props.rect.height,
1054
+ left: props.rect.x,
1055
+ outline: "1px solid #0d99ff",
1056
+ pointerEvents: "none",
1057
+ position: "absolute",
1058
+ top: props.rect.y,
1059
+ width: props.rect.width,
1060
+ zIndex: 1,
1061
+ } }));
1062
+ }
1063
+ function mergePreviewFrameSize(reported, _measured, _preset) {
1064
+ return reported;
1065
+ }
1066
+ function previewFrameLayoutHeight(displaySize, rect) {
1067
+ if (!rect)
1068
+ return displaySize.height;
1069
+ return Math.max(1, Math.ceil(Math.max(0, rect.y) + rect.height));
1070
+ }
1071
+ function measureIframeContentSize(frame) {
1072
+ const documentValue = frame.contentDocument;
1073
+ if (!documentValue)
1074
+ return undefined;
1075
+ const rects = [...documentValue.querySelectorAll("*")]
1076
+ .map((element) => element.getBoundingClientRect())
1077
+ .filter((rect) => rect.width > 0 || rect.height > 0);
1078
+ if (rects.length === 0) {
1079
+ return {
1080
+ width: documentValue.documentElement.scrollWidth,
1081
+ height: documentValue.documentElement.scrollHeight,
1082
+ };
1083
+ }
1084
+ const left = Math.min(0, ...rects.map((rect) => rect.left));
1085
+ const top = Math.min(0, ...rects.map((rect) => rect.top));
1086
+ const right = Math.max(documentValue.documentElement.scrollWidth, ...rects.map((rect) => rect.right));
1087
+ const bottom = Math.max(documentValue.documentElement.scrollHeight, ...rects.map((rect) => rect.bottom));
1088
+ return {
1089
+ width: Math.ceil(right - left + 24),
1090
+ height: Math.ceil(bottom - top),
1091
+ };
1092
+ }
1093
+ function previewFrameSize(preset, reportedSize) {
1094
+ if (preset === "phone")
1095
+ return { width: 390, height: 844 };
1096
+ if (preset === "tablet")
1097
+ return { width: 768, height: 1024 };
1098
+ if (preset === "desktop")
1099
+ return { width: 1280, height: 900 };
1100
+ return { width: 768, height: clamp(reportedSize?.height ?? 1024, 160, 1200) };
1101
+ }
1102
+ function PreviewError(props) {
1103
+ return (_jsxs("div", { role: "status", style: { background: "#fff8c5", border: "1px solid #d4a72c", borderRadius: 8, color: "#5a1e02", padding: 12 }, children: [_jsx("strong", { children: "Preview unavailable" }), _jsx("p", { style: { margin: "6px 0 0" }, children: props.error.message }), props.error.stack ? _jsx("pre", { style: { whiteSpace: "pre-wrap" }, children: props.error.stack }) : null, _jsxs("dl", { style: { display: "grid", gap: 4, margin: "8px 0 0" }, children: [_jsxs("div", { children: [_jsx("dt", { children: "Entry" }), _jsx("dd", { style: { margin: 0 }, children: props.coordinate })] }), _jsxs("div", { children: [_jsx("dt", { children: "Case" }), _jsx("dd", { style: { margin: 0 }, children: props.caseName })] }), _jsxs("div", { children: [_jsx("dt", { children: "Preview URL" }), _jsx("dd", { style: { margin: 0 }, children: _jsx("code", { children: props.previewUrl }) })] })] })] }));
1104
+ }
1105
+ function StudioInspector(props) {
1106
+ if (!props.component) {
1107
+ return (_jsxs("aside", { style: { background: "#fbfcfe", borderLeft: "1px solid #d8dee8", minHeight: 0, overflow: "auto", padding: 20 }, children: [_jsx("h2", { style: { fontSize: 16, margin: 0 }, children: "Inspector" }), _jsx("p", { style: { color: "#6b7280", fontSize: 13 }, children: "Select a GTSX component." })] }));
1108
+ }
1109
+ const selectedCase = selectedStudioCaseName(props.workspace, props.component);
1110
+ const selectedViewportPreset = props.workspace.selectedViewportPresetByCoordinate[props.component.coordinate] ?? "tablet";
1111
+ const instances = runtimeInstancesForSelectedComponent(props.manifest, props.workspace, props.frameStates);
1112
+ const selectedValues = selectedRuntimeValuesSnapshot(props.manifest, props.workspace, props.frameStates);
1113
+ return (_jsxs("aside", { style: { background: "#fbfcfe", borderLeft: "1px solid #d8dee8", minHeight: 0, overflow: "auto", padding: "22px 18px" }, children: [_jsx("h2", { style: { fontSize: 16, letterSpacing: -0.1, margin: "0 0 18px" }, children: "Inspector" }), _jsxs("section", { style: { borderBottom: "1px solid #e5e7eb", display: "grid", gap: 5, marginBottom: 18, paddingBottom: 16 }, children: [_jsx("strong", { style: { fontSize: 14 }, children: props.component.componentName }), _jsx("code", { style: {
1114
+ color: "#6b7280",
1115
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
1116
+ fontSize: 11,
1117
+ lineHeight: 1.4,
1118
+ overflowWrap: "anywhere",
1119
+ }, children: props.component.coordinate })] }), _jsxs("section", { children: [_jsx("h3", { style: inspectorSectionTitleStyle, children: "Cases" }), _jsx("div", { style: { display: "grid", gap: 8 }, children: props.component.cases.map((testCase) => (_jsx("button", { "data-gtsx-case-control": testCase.name, onClick: () => props.onChangeCase?.(props.component, testCase.name), style: {
1120
+ background: selectedCase === testCase.name ? "#eaf4ff" : "#ffffff",
1121
+ border: "1px solid",
1122
+ borderColor: selectedCase === testCase.name ? "#8ec5ff" : "#d8dee8",
1123
+ borderRadius: 10,
1124
+ color: "#24292f",
1125
+ cursor: props.onChangeCase ? "pointer" : "default",
1126
+ fontWeight: selectedCase === testCase.name ? 700 : 500,
1127
+ padding: "9px 10px",
1128
+ textAlign: "left",
1129
+ }, type: "button", children: testCase.name }, testCase.name))) })] }), _jsxs("section", { style: { marginTop: 20 }, children: [_jsx("h3", { style: inspectorSectionTitleStyle, children: "Viewport" }), _jsx("div", { style: { display: "grid", gap: 8 }, children: ["phone", "tablet", "desktop"].map((preset) => (_jsx("button", { "data-gtsx-viewport-control": preset, onClick: () => props.onChangeViewportPreset?.(props.component, preset), style: {
1130
+ background: selectedViewportPreset === preset ? "#eaf4ff" : "#ffffff",
1131
+ border: "1px solid",
1132
+ borderColor: selectedViewportPreset === preset ? "#8ec5ff" : "#d8dee8",
1133
+ borderRadius: 10,
1134
+ color: "#24292f",
1135
+ cursor: props.onChangeViewportPreset ? "pointer" : "default",
1136
+ fontWeight: selectedViewportPreset === preset ? 700 : 500,
1137
+ padding: "9px 10px",
1138
+ textAlign: "left",
1139
+ }, type: "button", children: preset }, preset))) })] }), _jsxs("section", { style: { marginTop: 20 }, children: [_jsx("h3", { style: inspectorSectionTitleStyle, children: "Instances" }), instances.length > 0 ? (_jsx("ol", { style: { display: "grid", gap: 8, listStyle: "none", margin: 0, padding: 0 }, children: instances.map((instance) => (_jsxs("li", { "data-gtsx-runtime-instance-id": instance.boundaryId, style: {
1140
+ background: "#ffffff",
1141
+ border: "1px solid #d8dee8",
1142
+ borderRadius: 10,
1143
+ display: "grid",
1144
+ gap: 4,
1145
+ padding: "10px",
1146
+ }, children: [_jsx("button", { onClick: () => {
1147
+ props.onSelectRuntimeInstance?.(props.component, instance.boundaryId);
1148
+ const request = createStudioRuntimeValuesRequest(props.manifest, props.workspace, instance.boundaryId);
1149
+ if (request)
1150
+ props.onRequestValues?.(request);
1151
+ }, style: {
1152
+ background: "transparent",
1153
+ border: 0,
1154
+ color: "inherit",
1155
+ cursor: props.onRequestValues ? "pointer" : "default",
1156
+ font: "inherit",
1157
+ padding: 0,
1158
+ textAlign: "left",
1159
+ }, type: "button", children: _jsx("strong", { style: { fontSize: 12 }, children: instance.boundaryId }) }), _jsxs("span", { style: { color: "#6b7280", fontSize: 11, lineHeight: 1.35, overflowWrap: "anywhere" }, children: ["Parent: ", instance.parentPath.join(" > ") || "root"] }), instance.rect ? (_jsxs("span", { style: { color: "#6b7280", fontSize: 11 }, children: [instance.rect.width, "x", instance.rect.height, " at ", instance.rect.x, ",", instance.rect.y] })) : null] }, instance.boundaryId))) })) : (_jsx("p", { style: { color: "#6b7280", fontSize: 12, margin: 0 }, children: "No runtime instances reported yet." }))] }), _jsxs("section", { style: { marginTop: 20 }, children: [_jsx("h3", { style: inspectorSectionTitleStyle, children: "Values" }), selectedValues ? (_jsxs("div", { style: { display: "grid", gap: 12 }, children: [_jsx(RuntimeValueSection, { label: "Props", value: selectedValues.props }), selectedValues.scope ? _jsx(RuntimeValueSection, { label: "Scope", value: selectedValues.scope }) : null, _jsxs("section", { children: [_jsx("h4", { style: { fontSize: 12, margin: "0 0 6px" }, children: "Provider Values" }), selectedValues.providerValues.length > 0 ? (_jsx("div", { style: { display: "grid", gap: 8 }, children: selectedValues.providerValues.map((providerValue) => (_jsx(RuntimeValueSection, { label: providerValue.providerName, value: providerValue.value }, providerValue.providerName))) })) : (_jsx("p", { style: { color: "#6b7280", fontSize: 12, margin: 0 }, children: "No provider values reported." }))] })] })) : (_jsx("p", { style: { color: "#6b7280", fontSize: 12, margin: 0 }, children: "Select an instance to request runtime values." }))] })] }));
1160
+ }
1161
+ function RuntimeValueSection(props) {
1162
+ return (_jsxs("section", { children: [_jsx("h4", { style: { fontSize: 12, margin: "0 0 6px" }, children: props.label }), _jsx(SerializedRuntimeValueView, { value: props.value })] }));
1163
+ }
1164
+ function SerializedRuntimeValueView(props) {
1165
+ const value = props.value;
1166
+ if (value.type === "object") {
1167
+ return _jsx(RuntimeEntries, { entries: value.entries });
1168
+ }
1169
+ if (value.type === "array") {
1170
+ return _jsx(RuntimeEntries, { entries: value.values.map((item, index) => ({ key: `[${index}]`, value: item })) });
1171
+ }
1172
+ if (value.type === "map") {
1173
+ return (_jsx(RuntimeEntries, { entries: value.entries.map(([entryKey, entryValue], index) => ({
1174
+ key: `entry ${index}`,
1175
+ value: { type: "array", values: [entryKey, entryValue] },
1176
+ })) }));
1177
+ }
1178
+ if (value.type === "set") {
1179
+ return _jsx(RuntimeEntries, { entries: value.values.map((item, index) => ({ key: `value ${index}`, value: item })) });
1180
+ }
1181
+ if (value.type === "react-element") {
1182
+ return (_jsx(RuntimeEntries, { entries: [
1183
+ { key: "type", value: { type: "string", value: value.elementType } },
1184
+ { key: "props", value: value.props },
1185
+ ] }));
1186
+ }
1187
+ return _jsx("span", { style: { color: "#57606a", fontSize: 12 }, children: runtimeValueLabel(value) });
1188
+ }
1189
+ function RuntimeEntries(props) {
1190
+ return (_jsx("dl", { style: { display: "grid", gap: 6, margin: 0 }, children: props.entries.map((entry) => (_jsxs("div", { style: { display: "grid", gap: 2 }, children: [_jsx("dt", { style: { color: "#57606a", fontSize: 12 }, children: entry.key }), _jsx("dd", { style: { margin: 0, paddingLeft: 8 }, children: _jsx(SerializedRuntimeValueView, { value: entry.value }) })] }, entry.key))) }));
1191
+ }
1192
+ function runtimeValueLabel(value) {
1193
+ if (value.type === "undefined")
1194
+ return "undefined";
1195
+ if (value.type === "null")
1196
+ return "null";
1197
+ if (value.type === "string")
1198
+ return value.value;
1199
+ if (value.type === "number" || value.type === "boolean")
1200
+ return String(value.value);
1201
+ if (value.type === "bigint")
1202
+ return `${value.value}n`;
1203
+ if (value.type === "symbol" || value.type === "function")
1204
+ return value.displayName;
1205
+ if (value.type === "date")
1206
+ return value.value;
1207
+ if (value.type === "error")
1208
+ return `${value.name}: ${value.message}`;
1209
+ if (value.type === "circular")
1210
+ return `Circular reference to ${value.path}`;
1211
+ return "Truncated at max depth";
1212
+ }
1213
+ function runtimeInstancesForSelectedComponent(manifest, workspace, frameStates) {
1214
+ const selectedCoordinate = workspace.selectedCoordinatePath.at(-1);
1215
+ if (!selectedCoordinate)
1216
+ return [];
1217
+ const parentCoordinate = workspace.selectedCoordinatePath.at(-2);
1218
+ if (parentCoordinate) {
1219
+ const parentComponent = findManifestComponent(manifest, parentCoordinate);
1220
+ if (!parentComponent)
1221
+ return [];
1222
+ const parentCaseName = selectedStudioCaseName(workspace, parentComponent);
1223
+ const parentFrameState = frameStates?.[previewSessionId(parentComponent, parentCaseName, previewCaseOverridesForComponent(workspace, parentComponent))];
1224
+ return findBoundaryNodes(parentFrameState?.tree ?? [], parentCoordinate).flatMap((parentNode) => parentNode.children
1225
+ .filter((child) => child.coordinate === selectedCoordinate)
1226
+ .map((child) => toRuntimeInstance(child, [parentCoordinate])));
1227
+ }
1228
+ const selectedComponent = findManifestComponent(manifest, selectedCoordinate);
1229
+ if (!selectedComponent)
1230
+ return [];
1231
+ const selectedCaseName = selectedStudioCaseName(workspace, selectedComponent);
1232
+ const selectedFrameState = frameStates?.[previewSessionId(selectedComponent, selectedCaseName, previewCaseOverridesForComponent(workspace, selectedComponent))];
1233
+ return findBoundaryNodes(selectedFrameState?.tree ?? [], selectedCoordinate).map((node) => toRuntimeInstance(node, []));
1234
+ }
1235
+ function selectedRuntimeValuesSnapshot(manifest, workspace, frameStates) {
1236
+ const selectedCoordinate = workspace.selectedCoordinatePath.at(-1);
1237
+ if (!selectedCoordinate)
1238
+ return undefined;
1239
+ const selectedBoundaryId = workspace.selectedRuntimeInstanceByCoordinate[selectedCoordinate];
1240
+ if (!selectedBoundaryId)
1241
+ return undefined;
1242
+ const sourceCoordinate = workspace.selectedCoordinatePath.at(-2) ?? selectedCoordinate;
1243
+ const sourceComponent = findManifestComponent(manifest, sourceCoordinate);
1244
+ if (!sourceComponent)
1245
+ return undefined;
1246
+ const sourceCaseName = selectedStudioCaseName(workspace, sourceComponent);
1247
+ const sourceFrameState = frameStates?.[previewSessionId(sourceComponent, sourceCaseName, previewCaseOverridesForComponent(workspace, sourceComponent))];
1248
+ return sourceFrameState?.valuesByBoundaryId?.[selectedBoundaryId];
1249
+ }
1250
+ function toRuntimeInstance(node, parentPath) {
1251
+ return {
1252
+ boundaryId: node.id,
1253
+ coordinate: node.coordinate,
1254
+ parentPath,
1255
+ ...(node.rect ? { rect: node.rect } : {}),
1256
+ };
1257
+ }
1258
+ function findManifestComponent(manifest, coordinate) {
1259
+ return manifest.files.flatMap((file) => file.components).find((component) => component.coordinate === coordinate);
1260
+ }
1261
+ function findBoundaryNodes(tree, coordinate) {
1262
+ return tree.flatMap((node) => {
1263
+ const childMatches = findBoundaryNodes(node.children, coordinate);
1264
+ return node.coordinate === coordinate ? [node, ...childMatches] : childMatches;
1265
+ });
1266
+ }
1267
+ function selectedInspectorComponent(manifest, workspace) {
1268
+ const selectedCoordinate = workspace.selectedCoordinatePath.at(-1);
1269
+ if (selectedCoordinate) {
1270
+ return manifest.files.flatMap((file) => file.components).find((component) => component.coordinate === selectedCoordinate);
1271
+ }
1272
+ return workspace.columns[0]?.components[0];
1273
+ }
1274
+ function getPreviewError(component) {
1275
+ if (component.diagnostics.length > 0) {
1276
+ return component.diagnostics.map((diagnostic) => diagnostic.code).join(", ");
1277
+ }
1278
+ if (!component.cases[0]) {
1279
+ return "missing-case";
1280
+ }
1281
+ return undefined;
1282
+ }
1283
+ function previewUrlForComponent(manifest, component, caseName, caseOverrides = []) {
1284
+ const params = new URLSearchParams({
1285
+ entry: component.coordinate,
1286
+ case: caseName,
1287
+ chrome: "0",
1288
+ sessionId: previewSessionId(component, caseName, caseOverrides),
1289
+ });
1290
+ for (const [coordinate, overrideCaseName] of caseOverrides) {
1291
+ params.append("gcase", `${coordinate}:${overrideCaseName}`);
1292
+ }
1293
+ return `${manifest.routes.preview}?${params.toString()}`;
1294
+ }
1295
+ function previewSessionId(component, caseName, caseOverrides = []) {
1296
+ if (caseOverrides.length === 0)
1297
+ return `${component.coordinate}:${caseName}`;
1298
+ return `${component.coordinate}:${caseName}|${caseOverrides
1299
+ .map(([coordinate, overrideCaseName]) => `${coordinate}:${overrideCaseName}`)
1300
+ .join("|")}`;
1301
+ }
1302
+ function previewCaseOverridesForComponent(workspace, component) {
1303
+ const componentPathIndex = workspace.selectedCoordinatePath.indexOf(component.coordinate);
1304
+ if (componentPathIndex < 0)
1305
+ return [];
1306
+ return workspace.selectedCoordinatePath
1307
+ .slice(componentPathIndex + 1)
1308
+ .flatMap((coordinate) => {
1309
+ const caseName = workspace.selectedCaseByCoordinate[coordinate];
1310
+ return caseName ? [[coordinate, caseName]] : [];
1311
+ });
1312
+ }
1313
+ function currentPreviewSessionIds(workspace) {
1314
+ return new Set(workspace.columns.flatMap((column) => column.components.flatMap((component) => {
1315
+ const caseName = selectedStudioCaseName(workspace, component);
1316
+ return caseName !== "No cases" ? [previewSessionId(component, caseName, previewCaseOverridesForComponent(workspace, component))] : [];
1317
+ })));
1318
+ }
1319
+ function isGPreviewProtocolMessage(value) {
1320
+ return (typeof value === "object" &&
1321
+ value !== null &&
1322
+ "type" in value &&
1323
+ typeof value.type === "string" &&
1324
+ value.type.startsWith("gtsx:"));
1325
+ }
1326
+ function resolveSelection(manifest, selection) {
1327
+ if (selection?.startsWith("component:")) {
1328
+ const coordinate = selection.slice("component:".length);
1329
+ const component = manifest.files.flatMap((file) => file.components).find((candidate) => candidate.coordinate === coordinate);
1330
+ if (component)
1331
+ return { id: selection, components: [component] };
1332
+ }
1333
+ if (selection?.startsWith("file:")) {
1334
+ const filePath = selection.slice("file:".length);
1335
+ const file = manifest.files.find((candidate) => candidate.path === filePath);
1336
+ if (file)
1337
+ return { id: selection, components: file.components };
1338
+ }
1339
+ const firstFile = manifest.files[0];
1340
+ return { id: firstFile ? `file:${firstFile.path}` : "", components: firstFile?.components ?? [] };
1341
+ }
1342
+ //# sourceMappingURL=studio-client.js.map