@replohq/sandbox-runtime 1.16.2 → 1.18.0

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 (56) hide show
  1. package/ClientErrorBoundary.js +26 -0
  2. package/DocumentReadyBeacon.js +5 -1
  3. package/_vendor/replo-utils/lib/historyKeys.d.ts +2 -0
  4. package/_vendor/replo-utils/lib/historyKeys.mjs +18 -0
  5. package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +43 -0
  6. package/_vendor/schemas/iframeRpcChannel.mjs +3 -541
  7. package/_vendor/schemas/previewHealthIframeRpc.d.ts +159 -0
  8. package/_vendor/schemas/previewHealthIframeRpc.mjs +123 -0
  9. package/_vendor/schemas/selectModeIframeRpc.d.ts +149 -3
  10. package/_vendor/schemas/selectModeIframeRpc.mjs +72 -2
  11. package/buildMetadata.js +3 -3
  12. package/chunk-KRA4LUIR.mjs +541 -0
  13. package/nextErrorFeed.d.ts +30 -0
  14. package/nextErrorFeed.js +64 -0
  15. package/package.json +5 -12
  16. package/previewHealth.d.ts +25 -0
  17. package/previewHealth.js +431 -0
  18. package/sandbox-runtime.js +0 -2
  19. package/select-mode/EditorSelection.js +5 -1
  20. package/select-mode/fast-refresh-relay.js +82 -3
  21. package/select-mode/fiber.d.ts +6 -0
  22. package/select-mode/fiber.js +33 -0
  23. package/select-mode/hooks/useParentMessaging.js +10 -3
  24. package/select-mode/hooks/useSelectionHover.js +2 -1
  25. package/select-mode/hooks/useSelectionInteractions.js +10 -0
  26. package/select-mode/iframeRpcChannel.d.ts +46 -2
  27. package/select-mode/iframeRpcChannel.js +7 -0
  28. package/select-mode/section-canvas/SectionCanvas.d.ts +7 -0
  29. package/select-mode/section-canvas/SectionCanvas.js +599 -0
  30. package/select-mode/section-canvas/optimisticSectionMove.d.ts +39 -0
  31. package/select-mode/section-canvas/optimisticSectionMove.js +172 -0
  32. package/select-mode/section-canvas/sectionCanvasControls.d.ts +3 -0
  33. package/select-mode/section-canvas/sectionCanvasControls.js +8 -0
  34. package/select-mode/section-canvas/sectionCanvasStore.d.ts +71 -0
  35. package/select-mode/section-canvas/sectionCanvasStore.js +275 -0
  36. package/select-mode/section-canvas/sectionElements.d.ts +16 -0
  37. package/select-mode/section-canvas/sectionElements.js +94 -0
  38. package/select-mode/section-canvas/sectionGhost.d.ts +5 -0
  39. package/select-mode/section-canvas/sectionGhost.js +23 -0
  40. package/select-mode/symbolicate.d.ts +2 -0
  41. package/select-mode/symbolicate.js +18 -1
  42. package/NextErrorReporter.d.ts +0 -1
  43. package/NextErrorReporter.js +0 -131
  44. package/_vendor/schemas/componentPreview.d.ts +0 -43
  45. package/_vendor/schemas/componentPreview.mjs +0 -79
  46. package/_vendor/schemas/generated/componentPreview.d.ts +0 -5
  47. package/_vendor/schemas/generated/componentPreview.mjs +0 -1
  48. package/_vendor/schemas/loaderKeys.d.ts +0 -69
  49. package/next/component-preview-client.d.ts +0 -8
  50. package/next/component-preview-client.js +0 -227
  51. package/next/component-preview-href.d.ts +0 -5
  52. package/next/component-preview-href.js +0 -13
  53. package/next/component-preview-props.d.ts +0 -8
  54. package/next/component-preview-props.js +0 -35
  55. package/next/component-preview.d.ts +0 -11
  56. package/next/component-preview.js +0 -83
@@ -0,0 +1,172 @@
1
+ const SETTLE_TRANSITION = "transform 180ms cubic-bezier(0.2, 0, 0, 1)";
2
+ function getTargetPosition({
3
+ indices,
4
+ from,
5
+ to
6
+ }) {
7
+ return indices.filter((index) => {
8
+ return index !== from && (index < from ? index : index - 1) < to;
9
+ }).length;
10
+ }
11
+ function getMovedSectionOffsets({
12
+ sections,
13
+ from,
14
+ to
15
+ }) {
16
+ const fromPosition = sections.findIndex((section) => section.index === from);
17
+ const toPosition = getTargetPosition({
18
+ indices: sections.map((section) => section.index),
19
+ from,
20
+ to
21
+ });
22
+ const offsets = /* @__PURE__ */ new Map();
23
+ if (fromPosition === -1 || fromPosition === toPosition) {
24
+ return offsets;
25
+ }
26
+ const low = Math.min(fromPosition, toPosition);
27
+ const high = Math.max(fromPosition, toPosition);
28
+ const original = sections.slice(low, high + 1);
29
+ const reordered = [...original];
30
+ const [moved] = reordered.splice(fromPosition - low, 1);
31
+ reordered.splice(toPosition - low, 0, moved);
32
+ let cursor = original[0].top;
33
+ reordered.forEach((section, slot) => {
34
+ offsets.set(section.index, cursor - section.top);
35
+ const gapAfterSlot = slot < original.length - 1 ? original[slot + 1].top - original[slot].bottom : 0;
36
+ cursor += section.bottom - section.top + gapAfterSlot;
37
+ });
38
+ return offsets;
39
+ }
40
+ function getSectionViewportBox(elements) {
41
+ const rects = (elements ?? []).filter((element) => element.isConnected).map((element) => element.getBoundingClientRect()).filter((rect) => rect.width !== 0 || rect.height !== 0);
42
+ if (rects.length === 0) {
43
+ return null;
44
+ }
45
+ return {
46
+ top: Math.min(...rects.map((rect) => rect.top)),
47
+ bottom: Math.max(...rects.map((rect) => rect.bottom)),
48
+ left: Math.min(...rects.map((rect) => rect.left)),
49
+ right: Math.max(...rects.map((rect) => rect.right))
50
+ };
51
+ }
52
+ function startOptimisticSectionMove({
53
+ sections,
54
+ from,
55
+ to,
56
+ onLanded
57
+ }) {
58
+ const placed = sections.flatMap((section) => {
59
+ const box = getSectionViewportBox(section.elements);
60
+ return box ? [{ ...section, ...box }] : [];
61
+ });
62
+ const offsets = getMovedSectionOffsets({ sections: placed, from, to });
63
+ const shifted = [];
64
+ for (const section of placed) {
65
+ const offset = offsets.get(section.index);
66
+ if (!offset) {
67
+ continue;
68
+ }
69
+ for (const element of section.elements) {
70
+ const { transform, transition, position, zIndex } = element.style;
71
+ shifted.push({
72
+ element,
73
+ saved: { transform, transition, position, zIndex }
74
+ });
75
+ element.style.transition = SETTLE_TRANSITION;
76
+ element.style.transform = `${transform} translateY(${offset}px)`.trim();
77
+ if (section.index === from) {
78
+ element.style.position = position || "relative";
79
+ element.style.zIndex = "1";
80
+ }
81
+ }
82
+ }
83
+ const order = placed.map((section) => section.index);
84
+ const fromPosition = order.indexOf(from);
85
+ const reordered = [...order];
86
+ reordered.splice(fromPosition, 1);
87
+ reordered.splice(getTargetPosition({ indices: order, from, to }), 0, from);
88
+ const textBySection = new Map(
89
+ placed.map((section) => [
90
+ section.index,
91
+ section.elements.map((element) => element.textContent ?? "").join("\n")
92
+ ])
93
+ );
94
+ const watchedSlots = placed.flatMap((section, slot) => {
95
+ const expected = textBySection.get(reordered[slot]);
96
+ return expected === textBySection.get(section.index) ? [] : [{ elements: section.elements, expected }];
97
+ });
98
+ const containers = new Set(
99
+ placed.flatMap((section) => {
100
+ return section.elements.flatMap((element) => {
101
+ return element.parentNode ? [element.parentNode] : [];
102
+ });
103
+ })
104
+ );
105
+ let isDone = false;
106
+ let fallbackTimeout;
107
+ const finish = ({ animate }) => {
108
+ if (isDone) {
109
+ return;
110
+ }
111
+ isDone = true;
112
+ observer.disconnect();
113
+ clearTimeout(fallbackTimeout);
114
+ for (const { element, saved } of shifted) {
115
+ element.style.transition = animate ? SETTLE_TRANSITION : "none";
116
+ element.style.transform = saved.transform;
117
+ element.style.position = saved.position;
118
+ element.style.zIndex = saved.zIndex;
119
+ }
120
+ const restoreTransitions = () => {
121
+ for (const { element, saved } of shifted) {
122
+ element.style.transition = saved.transition;
123
+ }
124
+ };
125
+ if (animate) {
126
+ setTimeout(restoreTransitions, 200);
127
+ } else {
128
+ restoreTransitions();
129
+ }
130
+ };
131
+ const hasLanded = (records) => {
132
+ if (records.some((record) => {
133
+ return record.type === "childList" && containers.has(record.target);
134
+ })) {
135
+ return true;
136
+ }
137
+ return watchedSlots.length > 0 && watchedSlots.every(({ elements, expected }) => {
138
+ return elements.some((element) => !element.isConnected) || elements.map((element) => element.textContent ?? "").join("\n") === expected;
139
+ });
140
+ };
141
+ const observer = new MutationObserver((records) => {
142
+ if (hasLanded(records)) {
143
+ finish({ animate: false });
144
+ onLanded();
145
+ }
146
+ });
147
+ for (const container of containers) {
148
+ observer.observe(container, {
149
+ childList: true,
150
+ subtree: true,
151
+ characterData: true
152
+ });
153
+ }
154
+ return {
155
+ revert: () => finish({ animate: true }),
156
+ settle: () => {
157
+ if (isDone) {
158
+ return;
159
+ }
160
+ clearTimeout(fallbackTimeout);
161
+ fallbackTimeout = setTimeout(() => {
162
+ finish({ animate: false });
163
+ onLanded();
164
+ }, 8e3);
165
+ }
166
+ };
167
+ }
168
+ export {
169
+ getMovedSectionOffsets,
170
+ getSectionViewportBox,
171
+ startOptimisticSectionMove
172
+ };
@@ -0,0 +1,3 @@
1
+ export declare const SECTION_CANVAS_ATTRIBUTE = "data-replo-section-canvas";
2
+ /** The canvas's own buttons and drag handles, which Select Mode's click, drag and hover must leave alone. */
3
+ export declare function isSectionCanvasControl(target: EventTarget | null): boolean;
@@ -0,0 +1,8 @@
1
+ const SECTION_CANVAS_ATTRIBUTE = "data-replo-section-canvas";
2
+ function isSectionCanvasControl(target) {
3
+ return target instanceof Element && Boolean(target.closest(`[${SECTION_CANVAS_ATTRIBUTE}]`));
4
+ }
5
+ export {
6
+ SECTION_CANVAS_ATTRIBUTE,
7
+ isSectionCanvasControl
8
+ };
@@ -0,0 +1,71 @@
1
+ import type { SectionCanvasRequest, SectionCanvasState } from "../../_vendor/schemas/generated/selectModeIframeRpc";
2
+ export type SectionDrag = {
3
+ index: number;
4
+ pointerId: number;
5
+ startX: number;
6
+ startY: number;
7
+ x: number;
8
+ y: number;
9
+ isActive: boolean;
10
+ ghostSrc: string | null;
11
+ gap: number | null;
12
+ };
13
+ type PendingMove = {
14
+ moveId: string;
15
+ settle: () => void;
16
+ revert: () => void;
17
+ };
18
+ type SectionCanvasStoreState = {
19
+ canvas: SectionCanvasState | null;
20
+ elementsByIndex: ReadonlyMap<number, readonly HTMLElement[]>;
21
+ hoveredIndex: number | null;
22
+ drag: SectionDrag | null;
23
+ pendingMove: PendingMove | null;
24
+ geometryVersion: number;
25
+ actions: {
26
+ setCanvas: (canvas: SectionCanvasState | null) => void;
27
+ resolveSections: () => Promise<void>;
28
+ handlePreviewRefresh: () => void;
29
+ bumpGeometry: () => void;
30
+ setHoveredIndex: (index: number | null) => void;
31
+ request: (request: SectionCanvasRequest) => void;
32
+ beginDrag: (input: {
33
+ index: number;
34
+ pointerId: number;
35
+ x: number;
36
+ y: number;
37
+ }) => void;
38
+ updateDrag: (input: {
39
+ x: number;
40
+ y: number;
41
+ }) => void;
42
+ endDrag: (input: {
43
+ shouldDrop: boolean;
44
+ }) => void;
45
+ move: (input: {
46
+ index: number;
47
+ to: number;
48
+ }) => void;
49
+ settleMove: (input: {
50
+ moveId: string;
51
+ outcome: "persisted" | "failed";
52
+ }) => void;
53
+ };
54
+ };
55
+ export declare const GHOST_WIDTH_PX = 240;
56
+ /** On-screen sections in page order, with their boxes. */
57
+ export declare function getPlacedSections(state: Pick<SectionCanvasStoreState, "canvas" | "elementsByIndex">): {
58
+ section: import("../../_vendor/schemas/generated/selectModeIframeRpc").SectionCanvasSection;
59
+ box: import("./optimisticSectionMove").ViewportBox;
60
+ }[];
61
+ /**
62
+ * The insertion points between on-screen sections: `at` is the index a new or moved section goes
63
+ * before in the page's list, `y` the viewport line drawn for it.
64
+ */
65
+ export declare function getSectionGaps(placed: ReturnType<typeof getPlacedSections>, sectionCount: number): {
66
+ at: number;
67
+ y: number;
68
+ }[];
69
+ export declare const sectionCanvasStore: import("zustand/traditional").UseBoundStoreWithEqualityFn<import("zustand").StoreApi<SectionCanvasStoreState>>;
70
+ export declare function useSectionCanvasStore<Selected>(selector: (state: SectionCanvasStoreState) => Selected): Selected;
71
+ export {};
@@ -0,0 +1,275 @@
1
+ "use client";
2
+ import { createShallowStore } from "../../_vendor/replo-utils/react/zustand.mjs";
3
+ import { getWebsiteBuilderRpc } from "../iframeRpcChannel";
4
+ import { getDocumentScrollOffset } from "../interactions/selectionGeometry";
5
+ import { useShallowStore } from "../useShallowStore";
6
+ import {
7
+ getSectionViewportBox,
8
+ startOptimisticSectionMove
9
+ } from "./optimisticSectionMove";
10
+ import { findSectionElements } from "./sectionElements";
11
+ import { captureSectionGhost } from "./sectionGhost";
12
+ const DRAG_THRESHOLD_PX = 4;
13
+ const GHOST_WIDTH_PX = 240;
14
+ let resolveToken = 0;
15
+ let moveCounter = 0;
16
+ function sameSections(previous, next) {
17
+ if (!previous || !next || previous.pageFilePath !== next.pageFilePath) {
18
+ return false;
19
+ }
20
+ return previous.sections.length === next.sections.length && previous.sections.every((section, position) => {
21
+ const other = next.sections[position];
22
+ return section.index === other.index && section.startLine === other.startLine && section.endLine === other.endLine;
23
+ });
24
+ }
25
+ function getPlacedSections(state) {
26
+ return (state.canvas?.sections ?? []).flatMap((section) => {
27
+ const box = getSectionViewportBox(state.elementsByIndex.get(section.index));
28
+ return box ? [{ section, box }] : [];
29
+ });
30
+ }
31
+ function getSectionGaps(placed, sectionCount) {
32
+ const gaps = placed.map(({ section, box }, position) => {
33
+ const previous = placed[position - 1];
34
+ return {
35
+ at: section.index,
36
+ y: previous ? (previous.box.bottom + box.top) / 2 : box.top
37
+ };
38
+ });
39
+ const last = placed.at(-1);
40
+ if (last) {
41
+ gaps.push({
42
+ at: Math.max(last.section.index + 1, sectionCount),
43
+ y: last.box.bottom
44
+ });
45
+ }
46
+ return gaps;
47
+ }
48
+ function getDropGap({
49
+ state,
50
+ index,
51
+ y
52
+ }) {
53
+ const gaps = getSectionGaps(
54
+ getPlacedSections(state),
55
+ state.canvas?.sections.length ?? 0
56
+ );
57
+ let nearest = null;
58
+ for (const gap of gaps) {
59
+ const distance = Math.abs(gap.y - y);
60
+ if (!nearest || distance < nearest.distance) {
61
+ nearest = { at: gap.at, distance };
62
+ }
63
+ }
64
+ if (!nearest || nearest.at === index || nearest.at === index + 1) {
65
+ return null;
66
+ }
67
+ return nearest.at;
68
+ }
69
+ function reportLayout(state) {
70
+ if (!state.canvas) {
71
+ return;
72
+ }
73
+ const scroll = getDocumentScrollOffset();
74
+ const sections = getPlacedSections(state).map(
75
+ ({ section, box }) => {
76
+ const left = box.left + scroll.left;
77
+ const top = box.top + scroll.top;
78
+ const width = box.right - box.left;
79
+ const height = box.bottom - box.top;
80
+ return {
81
+ index: section.index,
82
+ startLine: section.startLine,
83
+ endLine: section.endLine,
84
+ rect: {
85
+ x: left,
86
+ y: top,
87
+ left,
88
+ top,
89
+ right: left + width,
90
+ bottom: top + height,
91
+ width,
92
+ height
93
+ }
94
+ };
95
+ }
96
+ );
97
+ getWebsiteBuilderRpc()?.sectionCanvasLayoutChanged({ sections });
98
+ }
99
+ const sectionCanvasStore = createShallowStore(
100
+ (set, get) => ({
101
+ canvas: null,
102
+ elementsByIndex: /* @__PURE__ */ new Map(),
103
+ hoveredIndex: null,
104
+ drag: null,
105
+ pendingMove: null,
106
+ geometryVersion: 0,
107
+ actions: {
108
+ setCanvas: (canvas) => {
109
+ const previous = get().canvas;
110
+ set({ canvas });
111
+ if (!canvas) {
112
+ resolveToken += 1;
113
+ get().pendingMove?.revert();
114
+ set({
115
+ elementsByIndex: /* @__PURE__ */ new Map(),
116
+ hoveredIndex: null,
117
+ drag: null,
118
+ pendingMove: null
119
+ });
120
+ return;
121
+ }
122
+ if (canvas.isDisabled && get().drag) {
123
+ set({ drag: null });
124
+ }
125
+ if (!sameSections(previous, canvas) && !get().pendingMove) {
126
+ void get().actions.resolveSections();
127
+ }
128
+ },
129
+ resolveSections: async () => {
130
+ const canvas = get().canvas;
131
+ if (!canvas || typeof document === "undefined" || !document.body) {
132
+ return;
133
+ }
134
+ const token = ++resolveToken;
135
+ const elementsByIndex = await findSectionElements({
136
+ root: document.body,
137
+ pageFilePath: canvas.pageFilePath,
138
+ sections: canvas.sections
139
+ }).catch(() => /* @__PURE__ */ new Map());
140
+ if (token !== resolveToken) {
141
+ return;
142
+ }
143
+ set((state) => ({
144
+ elementsByIndex,
145
+ geometryVersion: state.geometryVersion + 1
146
+ }));
147
+ reportLayout(get());
148
+ },
149
+ handlePreviewRefresh: () => {
150
+ if (!get().canvas || get().pendingMove) {
151
+ return;
152
+ }
153
+ void get().actions.resolveSections();
154
+ },
155
+ bumpGeometry: () => {
156
+ set((state) => ({ geometryVersion: state.geometryVersion + 1 }));
157
+ },
158
+ setHoveredIndex: (hoveredIndex) => {
159
+ if (get().hoveredIndex !== hoveredIndex) {
160
+ set({ hoveredIndex });
161
+ }
162
+ },
163
+ request: (request) => {
164
+ getWebsiteBuilderRpc()?.sectionCanvasRequest({ request });
165
+ },
166
+ beginDrag: ({ index, pointerId, x, y }) => {
167
+ const elements = get().elementsByIndex.get(index) ?? [];
168
+ set({
169
+ drag: {
170
+ index,
171
+ pointerId,
172
+ startX: x,
173
+ startY: y,
174
+ x,
175
+ y,
176
+ isActive: false,
177
+ ghostSrc: null,
178
+ gap: null
179
+ }
180
+ });
181
+ const scale = get().canvas?.previewScale ?? 1;
182
+ void captureSectionGhost({
183
+ elements,
184
+ width: GHOST_WIDTH_PX / scale
185
+ }).then((ghostSrc) => {
186
+ const drag = get().drag;
187
+ if (ghostSrc && drag?.pointerId === pointerId && drag.index === index) {
188
+ set({ drag: { ...drag, ghostSrc } });
189
+ }
190
+ });
191
+ },
192
+ updateDrag: ({ x, y }) => {
193
+ const state = get();
194
+ const drag = state.drag;
195
+ if (!drag) {
196
+ return;
197
+ }
198
+ const scale = state.canvas?.previewScale ?? 1;
199
+ const isActive = drag.isActive || Math.hypot(x - drag.startX, y - drag.startY) * scale > DRAG_THRESHOLD_PX;
200
+ set({
201
+ drag: {
202
+ ...drag,
203
+ x,
204
+ y,
205
+ isActive,
206
+ gap: isActive ? getDropGap({ state, index: drag.index, y }) : null
207
+ }
208
+ });
209
+ },
210
+ endDrag: ({ shouldDrop }) => {
211
+ const drag = get().drag;
212
+ set({ drag: null });
213
+ if (!shouldDrop || !drag?.isActive || drag.gap === null) {
214
+ return;
215
+ }
216
+ get().actions.move({
217
+ index: drag.index,
218
+ to: drag.gap > drag.index ? drag.gap - 1 : drag.gap
219
+ });
220
+ },
221
+ move: ({ index, to }) => {
222
+ const state = get();
223
+ const sectionCount = state.canvas?.sections.length ?? 0;
224
+ if (!state.canvas || state.canvas.isDisabled || state.pendingMove || state.canvas.sections[index]?.isLocked !== false || to === index || to < 0 || to >= sectionCount) {
225
+ return;
226
+ }
227
+ moveCounter += 1;
228
+ const moveId = `section-move-${Date.now()}-${moveCounter}`;
229
+ const optimistic = startOptimisticSectionMove({
230
+ sections: state.canvas.sections.map((section) => ({
231
+ index: section.index,
232
+ elements: [...state.elementsByIndex.get(section.index) ?? []]
233
+ })),
234
+ from: index,
235
+ to,
236
+ onLanded: () => {
237
+ if (get().pendingMove?.moveId !== moveId) {
238
+ return;
239
+ }
240
+ set({ pendingMove: null });
241
+ void get().actions.resolveSections();
242
+ }
243
+ });
244
+ set({
245
+ pendingMove: { moveId, ...optimistic },
246
+ hoveredIndex: null
247
+ });
248
+ get().actions.request({ type: "move", moveId, index, to });
249
+ },
250
+ settleMove: ({ moveId, outcome }) => {
251
+ const pendingMove = get().pendingMove;
252
+ if (pendingMove?.moveId !== moveId) {
253
+ return;
254
+ }
255
+ if (outcome === "persisted") {
256
+ pendingMove.settle();
257
+ return;
258
+ }
259
+ pendingMove.revert();
260
+ set({ pendingMove: null });
261
+ void get().actions.resolveSections();
262
+ }
263
+ }
264
+ })
265
+ );
266
+ function useSectionCanvasStore(selector) {
267
+ return useShallowStore(sectionCanvasStore, selector);
268
+ }
269
+ export {
270
+ GHOST_WIDTH_PX,
271
+ getPlacedSections,
272
+ getSectionGaps,
273
+ sectionCanvasStore,
274
+ useSectionCanvasStore
275
+ };
@@ -0,0 +1,16 @@
1
+ import type { SelectModeSourceLocation } from "../../_vendor/schemas/generated/selectMode";
2
+ import type { SectionCanvasSection } from "../../_vendor/schemas/generated/selectModeIframeRpc";
3
+ type Resolve = (locations: readonly SelectModeSourceLocation[]) => Promise<SelectModeSourceLocation[]>;
4
+ /**
5
+ * The DOM elements each page section renders, found from source, not layout: an element belongs to
6
+ * a section when a JSX call site in its owner chain lies in the page file inside that section's lines. Walks down from <body> a level at a time, symbolicating each level
7
+ * in one request and stopping at the first element that matches, so a section's descendants are
8
+ * never read.
9
+ */
10
+ export declare function findSectionElements({ root, pageFilePath, sections, resolve, }: {
11
+ root: Element;
12
+ pageFilePath: string;
13
+ sections: readonly Pick<SectionCanvasSection, "index" | "startLine" | "endLine">[];
14
+ resolve?: Resolve;
15
+ }): Promise<Map<number, HTMLElement[]>>;
16
+ export {};
@@ -0,0 +1,94 @@
1
+ import { isSectionCanvasPageFile } from "../../_vendor/schemas/selectModeIframeRpc.mjs";
2
+ import { getElementSourceChain } from "../fiber";
3
+ import { symbolicateSourceLocations } from "../symbolicate";
4
+ import { isSectionCanvasControl } from "./sectionCanvasControls";
5
+ const MAX_DEPTH = 14;
6
+ const MAX_ELEMENTS = 2e3;
7
+ const SKIPPED_TAGS = /* @__PURE__ */ new Set([
8
+ "SCRIPT",
9
+ "STYLE",
10
+ "LINK",
11
+ "META",
12
+ "TEMPLATE",
13
+ "NOSCRIPT"
14
+ ]);
15
+ async function findSectionElements({
16
+ root,
17
+ pageFilePath,
18
+ sections,
19
+ resolve = symbolicateSourceLocations
20
+ }) {
21
+ const found = /* @__PURE__ */ new Map();
22
+ const resolved = /* @__PURE__ */ new Map();
23
+ let level = Array.from(root.children);
24
+ let visited = 0;
25
+ for (let depth = 0; depth < MAX_DEPTH && level.length > 0; depth += 1) {
26
+ const candidates = level.flatMap((element) => {
27
+ if (!(element instanceof HTMLElement) || SKIPPED_TAGS.has(element.tagName) || isSectionCanvasControl(element)) {
28
+ return [];
29
+ }
30
+ const chain = getElementSourceChain(element).map(
31
+ (source) => ({
32
+ filePath: source.fileName,
33
+ lineNumber: source.lineNumber,
34
+ columnNumber: source.columnNumber
35
+ })
36
+ );
37
+ return [{ element, chain }];
38
+ });
39
+ visited += candidates.length;
40
+ const pending = /* @__PURE__ */ new Map();
41
+ for (const { chain } of candidates) {
42
+ for (const location of chain) {
43
+ const key = locationKey(location);
44
+ if (!resolved.has(key)) {
45
+ pending.set(key, location);
46
+ }
47
+ }
48
+ }
49
+ if (pending.size > 0) {
50
+ const keys = [...pending.keys()];
51
+ const results = await resolve([...pending.values()]);
52
+ keys.forEach((key, index) => {
53
+ resolved.set(key, results[index] ?? pending.get(key));
54
+ });
55
+ }
56
+ const next = [];
57
+ for (const { element, chain } of candidates) {
58
+ let section;
59
+ for (const location of chain) {
60
+ const original = resolved.get(locationKey(location));
61
+ if (!original || !isSectionCanvasPageFile({
62
+ filePath: original.filePath,
63
+ pageFilePath
64
+ })) {
65
+ continue;
66
+ }
67
+ section = sections.find((candidate) => {
68
+ return candidate.startLine <= original.lineNumber && original.lineNumber <= candidate.endLine;
69
+ });
70
+ if (section) {
71
+ break;
72
+ }
73
+ }
74
+ if (section) {
75
+ const elements = found.get(section.index) ?? [];
76
+ elements.push(element);
77
+ found.set(section.index, elements);
78
+ continue;
79
+ }
80
+ next.push(...Array.from(element.children));
81
+ }
82
+ if (visited >= MAX_ELEMENTS) {
83
+ break;
84
+ }
85
+ level = next;
86
+ }
87
+ return found;
88
+ }
89
+ function locationKey(location) {
90
+ return `${location.filePath}:${location.lineNumber}:${location.columnNumber ?? ""}`;
91
+ }
92
+ export {
93
+ findSectionElements
94
+ };
@@ -0,0 +1,5 @@
1
+ /** A small picture of a section to drag around, or null when it can't be drawn. */
2
+ export declare function captureSectionGhost({ elements, width, }: {
3
+ elements: readonly HTMLElement[];
4
+ width: number;
5
+ }): Promise<string | null>;
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ async function captureSectionGhost({
3
+ elements,
4
+ width
5
+ }) {
6
+ const element = elements.find((candidate) => candidate.isConnected);
7
+ if (!element) {
8
+ return null;
9
+ }
10
+ const rect = element.getBoundingClientRect();
11
+ if (rect.width === 0 || rect.height === 0) {
12
+ return null;
13
+ }
14
+ try {
15
+ const { snapdom } = await import("@zumer/snapdom");
16
+ return await snapdom.toRaw(element, { width, embedFonts: false });
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+ export {
22
+ captureSectionGhost
23
+ };
@@ -1,4 +1,6 @@
1
1
  import type { SelectedElement, SelectModeSourceLocation } from "../_vendor/schemas/generated/selectMode";
2
2
  export declare function invalidateSymbolicationCache(): void;
3
+ /** Resolves compiled locations to original files in one batched request; unresolved ones come back unchanged. */
4
+ export declare function symbolicateSourceLocations(locations: readonly SelectModeSourceLocation[]): Promise<SelectModeSourceLocation[]>;
3
5
  export declare function isUserSourceLocation(location: SelectModeSourceLocation | undefined): boolean;
4
6
  export declare function symbolicateSelectedElement(selectedElement: SelectedElement): Promise<SelectedElement>;