@designtools/next-plugin 0.1.2 → 0.1.3

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.
@@ -121,6 +121,329 @@ function CodeSurface() {
121
121
  }
122
122
  return target;
123
123
  }
124
+ const overlayIds = /* @__PURE__ */ new Set(["tool-highlight", "tool-tooltip", "tool-selected", "codesurface-token-preview"]);
125
+ const semanticTags = /* @__PURE__ */ new Set(["header", "main", "nav", "section", "article", "footer", "aside"]);
126
+ const skipTags = /* @__PURE__ */ new Set([
127
+ "html",
128
+ "body",
129
+ "head",
130
+ // document structure
131
+ "br",
132
+ "hr",
133
+ "wbr",
134
+ // void/formatting
135
+ "template",
136
+ "slot"
137
+ // shadow DOM
138
+ ]);
139
+ const frameworkPatterns = [
140
+ /^Fragment$/,
141
+ /^Suspense$/,
142
+ /^ErrorBoundary$/,
143
+ /^Provider$/,
144
+ /^Consumer$/,
145
+ /Context$/,
146
+ /^ForwardRef$/,
147
+ /^Memo$/,
148
+ /^Lazy$/,
149
+ // Next.js routing internals
150
+ /^InnerLayoutRouter$/,
151
+ /^OuterLayoutRouter$/,
152
+ /^LayoutRouter$/,
153
+ /^RenderFromTemplateContext$/,
154
+ /^TemplateContext$/,
155
+ /^RedirectBoundary$/,
156
+ /^RedirectErrorBoundary$/,
157
+ /^NotFoundBoundary$/,
158
+ /^LoadingBoundary$/,
159
+ /^HTTPAccessFallbackBoundary$/,
160
+ /^HTTPAccessFallbackErrorBoundary$/,
161
+ /^ClientPageRoot$/,
162
+ /^HotReload$/,
163
+ /^ReactDevOverlay$/,
164
+ /^PathnameContextProviderAdapter$/,
165
+ // Next.js App Router internals (segment tree)
166
+ /^SegmentViewNode$/,
167
+ /^SegmentTrieNode$/,
168
+ /^SegmentViewStateNode$/,
169
+ /^SegmentBoundaryTriggerNode$/,
170
+ /^SegmentStateProvider$/,
171
+ /^ScrollAndFocusHandler$/,
172
+ /^InnerScrollAndFocusHandler$/,
173
+ /^AppRouter$/,
174
+ /^Router$/,
175
+ /^Root$/,
176
+ /^ServerRoot$/,
177
+ /^RootErrorBoundary$/,
178
+ /^ErrorBoundaryHandler$/,
179
+ /^AppRouterAnnouncer$/,
180
+ /^HistoryUpdater$/,
181
+ /^RuntimeStyles$/,
182
+ /^DevRootHTTPAccessFallbackBoundary$/,
183
+ /^AppDevOverlayErrorBoundary$/,
184
+ /^ReplaySsrOnlyErrors$/,
185
+ /^HeadManagerContext$/,
186
+ /^Head$/,
187
+ /^MetadataOutlet$/,
188
+ /^AsyncMetadataOutlet$/,
189
+ /^__next_/
190
+ // All __next_ prefixed components
191
+ ];
192
+ function isFrameworkComponent(name) {
193
+ return frameworkPatterns.some((p) => p.test(name));
194
+ }
195
+ function getFiber(el) {
196
+ const key = Object.keys(el).find((k) => k.startsWith("__reactFiber$"));
197
+ return key ? el[key] : null;
198
+ }
199
+ function getDirectText(el) {
200
+ let text = "";
201
+ for (const node of Array.from(el.childNodes)) {
202
+ if (node.nodeType === Node.TEXT_NODE) {
203
+ text += (node.textContent || "").trim();
204
+ }
205
+ }
206
+ return text.slice(0, 40);
207
+ }
208
+ function inferScope(sourcePath) {
209
+ if (!sourcePath) return null;
210
+ const colonIdx = sourcePath.indexOf(":");
211
+ const file = colonIdx > 0 ? sourcePath.slice(0, colonIdx) : sourcePath;
212
+ if (/\/layout\.[tjsx]+$/i.test(file) || /^layout\.[tjsx]+$/i.test(file)) return "layout";
213
+ if (/\/page\.[tjsx]+$/i.test(file) || /^page\.[tjsx]+$/i.test(file)) return "page";
214
+ return null;
215
+ }
216
+ function getScopeForElement(el, parentScope) {
217
+ if (!el) return parentScope;
218
+ const instanceSource = el.getAttribute("data-instance-source");
219
+ const fromInstance = inferScope(instanceSource);
220
+ if (fromInstance) return fromInstance;
221
+ const source = el.getAttribute("data-source");
222
+ const fromSource = inferScope(source);
223
+ if (fromSource) return fromSource;
224
+ return parentScope;
225
+ }
226
+ function buildComponentTree(rootEl) {
227
+ const fiber = getFiber(rootEl);
228
+ if (!fiber) {
229
+ return buildDataSlotTree(rootEl);
230
+ }
231
+ let fiberRoot = fiber;
232
+ while (fiberRoot.return) fiberRoot = fiberRoot.return;
233
+ const results = [];
234
+ walkFiber(fiberRoot.child, results, null);
235
+ return results;
236
+ }
237
+ function walkFiber(fiber, siblings, parentScope) {
238
+ while (fiber) {
239
+ const node = processFiber(fiber, parentScope);
240
+ if (node) {
241
+ siblings.push(node);
242
+ } else {
243
+ if (fiber.child) {
244
+ let childScope = parentScope;
245
+ if (typeof fiber.type === "string" && fiber.stateNode instanceof Element) {
246
+ childScope = getScopeForElement(fiber.stateNode, parentScope);
247
+ } else if (typeof fiber.type === "function" || typeof fiber.type === "object") {
248
+ const hostEl = findOwnHostElement(fiber);
249
+ if (hostEl) {
250
+ childScope = getScopeForElement(hostEl, parentScope);
251
+ }
252
+ }
253
+ walkFiber(fiber.child, siblings, childScope);
254
+ }
255
+ }
256
+ fiber = fiber.sibling;
257
+ }
258
+ }
259
+ function processFiber(fiber, parentScope) {
260
+ if (typeof fiber.type === "string") {
261
+ return processHostFiber(fiber, parentScope);
262
+ }
263
+ if (typeof fiber.type === "function" || typeof fiber.type === "object") {
264
+ return processComponentFiber(fiber, parentScope);
265
+ }
266
+ return null;
267
+ }
268
+ function processHostFiber(fiber, parentScope) {
269
+ const tag = fiber.type;
270
+ const el = fiber.stateNode;
271
+ if (el && el.id && overlayIds.has(el.id)) return null;
272
+ if (["script", "style", "link", "noscript"].includes(tag)) return null;
273
+ const scope = getScopeForElement(el, parentScope);
274
+ const dataSlot = el?.getAttribute("data-slot") || null;
275
+ if (dataSlot) {
276
+ const name = dataSlot.split("-").map((s2) => s2.charAt(0).toUpperCase() + s2.slice(1)).join("");
277
+ const children = [];
278
+ if (fiber.child) walkFiber(fiber.child, children, scope);
279
+ return {
280
+ id: el ? getDomPath(el) : "",
281
+ name,
282
+ type: "component",
283
+ dataSlot,
284
+ source: el?.getAttribute("data-source") || null,
285
+ scope,
286
+ textContent: el ? getDirectText(el) : "",
287
+ children
288
+ };
289
+ }
290
+ if (semanticTags.has(tag)) {
291
+ const children = [];
292
+ if (fiber.child) walkFiber(fiber.child, children, scope);
293
+ const text = el ? getDirectText(el) : "";
294
+ if (children.length > 0 || text) {
295
+ return {
296
+ id: el ? getDomPath(el) : "",
297
+ name: `<${tag}>`,
298
+ type: "element",
299
+ dataSlot: null,
300
+ source: el?.getAttribute("data-source") || null,
301
+ scope,
302
+ textContent: text,
303
+ children
304
+ };
305
+ }
306
+ }
307
+ if (el?.hasAttribute("data-source") && !skipTags.has(tag)) {
308
+ const children = [];
309
+ if (fiber.child) walkFiber(fiber.child, children, scope);
310
+ const text = el ? getDirectText(el) : "";
311
+ return {
312
+ id: getDomPath(el),
313
+ name: `<${tag}>`,
314
+ type: "element",
315
+ dataSlot: null,
316
+ source: el.getAttribute("data-source"),
317
+ scope,
318
+ textContent: text,
319
+ children
320
+ };
321
+ }
322
+ return null;
323
+ }
324
+ function processComponentFiber(fiber, parentScope) {
325
+ const type = fiber.type;
326
+ const name = type?.displayName || type?.name || null;
327
+ if (!name) return null;
328
+ if (isFrameworkComponent(name)) return null;
329
+ if (name === "CodeSurface") return null;
330
+ const hostEl = findOwnHostElement(fiber);
331
+ const hasInstanceSource = hostEl?.getAttribute("data-instance-source");
332
+ const hasDataSlot = hostEl?.getAttribute("data-slot");
333
+ if (!hasInstanceSource && !hasDataSlot) return null;
334
+ const scope = getScopeForElement(hostEl, parentScope);
335
+ const dataSlot = hasDataSlot || null;
336
+ const children = [];
337
+ const hostFiber = dataSlot ? findHostFiber(fiber) : null;
338
+ const childFiber = hostFiber ? hostFiber.child : fiber.child;
339
+ if (childFiber) walkFiber(childFiber, children, scope);
340
+ if (children.length === 1 && !dataSlot && !(hostEl && getDirectText(hostEl))) {
341
+ const child = children[0];
342
+ if (child.type === "component") {
343
+ return child;
344
+ }
345
+ }
346
+ return {
347
+ id: hostEl ? getDomPath(hostEl) : "",
348
+ name,
349
+ type: "component",
350
+ dataSlot,
351
+ source: hostEl?.getAttribute("data-source") || null,
352
+ scope,
353
+ textContent: hostEl ? getDirectText(hostEl) : "",
354
+ children
355
+ };
356
+ }
357
+ function findHostFiber(fiber) {
358
+ let child = fiber.child;
359
+ while (child) {
360
+ if (child.stateNode instanceof Element) return child;
361
+ const tag = child.tag;
362
+ const isComponentBoundary = tag === 0 || tag === 1 || tag === 11 || tag === 14 || tag === 15;
363
+ if (!isComponentBoundary && child.child) {
364
+ const found = findHostFiber(child);
365
+ if (found) return found;
366
+ }
367
+ child = child.sibling;
368
+ }
369
+ return null;
370
+ }
371
+ function findOwnHostElement(fiber) {
372
+ let child = fiber.child;
373
+ while (child) {
374
+ if (child.stateNode instanceof Element) return child.stateNode;
375
+ const tag = child.tag;
376
+ const isComponentBoundary = tag === 0 || tag === 1 || tag === 11 || tag === 14 || tag === 15;
377
+ if (!isComponentBoundary && child.child) {
378
+ const found = findOwnHostElement(child);
379
+ if (found) return found;
380
+ }
381
+ child = child.sibling;
382
+ }
383
+ return null;
384
+ }
385
+ function buildDataSlotTree(root) {
386
+ const results = [];
387
+ for (const child of Array.from(root.children)) {
388
+ walkDomForSlots(child, results, null);
389
+ }
390
+ return results;
391
+ }
392
+ function walkDomForSlots(el, siblings, parentScope) {
393
+ if (el.id && overlayIds.has(el.id)) return;
394
+ const dataSlot = el.getAttribute("data-slot");
395
+ const tag = el.tagName.toLowerCase();
396
+ const scope = getScopeForElement(el, parentScope);
397
+ if (dataSlot) {
398
+ const name = dataSlot.split("-").map((s2) => s2.charAt(0).toUpperCase() + s2.slice(1)).join("");
399
+ const children = [];
400
+ for (const child of Array.from(el.children)) {
401
+ walkDomForSlots(child, children, scope);
402
+ }
403
+ siblings.push({
404
+ id: getDomPath(el),
405
+ name,
406
+ type: "component",
407
+ dataSlot,
408
+ source: el.getAttribute("data-source") || null,
409
+ scope,
410
+ textContent: getDirectText(el),
411
+ children
412
+ });
413
+ } else if (semanticTags.has(tag)) {
414
+ const children = [];
415
+ for (const child of Array.from(el.children)) {
416
+ walkDomForSlots(child, children, scope);
417
+ }
418
+ if (children.length > 0 || getDirectText(el)) {
419
+ siblings.push({
420
+ id: getDomPath(el),
421
+ name: `<${tag}>`,
422
+ type: "element",
423
+ dataSlot: null,
424
+ source: el.getAttribute("data-source") || null,
425
+ scope,
426
+ textContent: getDirectText(el),
427
+ children
428
+ });
429
+ }
430
+ } else {
431
+ for (const child of Array.from(el.children)) {
432
+ walkDomForSlots(child, siblings, scope);
433
+ }
434
+ }
435
+ }
436
+ function sendComponentTree() {
437
+ const tree = buildComponentTree(document.body);
438
+ window.parent.postMessage({ type: "tool:componentTree", tree }, "*");
439
+ }
440
+ let debounceTimer = null;
441
+ function debouncedSendTree() {
442
+ if (debounceTimer) clearTimeout(debounceTimer);
443
+ debounceTimer = setTimeout(sendComponentTree, 300);
444
+ }
445
+ const treeObserver = new MutationObserver(debouncedSendTree);
446
+ treeObserver.observe(document.body, { childList: true, subtree: true });
124
447
  const relevantProps = [
125
448
  "display",
126
449
  "position",
@@ -426,6 +749,38 @@ function CodeSurface() {
426
749
  document.documentElement.classList.remove("dark");
427
750
  }
428
751
  break;
752
+ case "tool:requestComponentTree":
753
+ sendComponentTree();
754
+ break;
755
+ case "tool:highlightByTreeId": {
756
+ const id = msg.id;
757
+ if (!id || !s.highlightOverlay || !s.tooltip) break;
758
+ const target = document.querySelector(id);
759
+ if (target) {
760
+ const rect = target.getBoundingClientRect();
761
+ positionOverlay(s.highlightOverlay, rect);
762
+ const name = getElementName(target);
763
+ s.tooltip.textContent = name;
764
+ s.tooltip.style.display = "block";
765
+ s.tooltip.style.left = `${rect.left}px`;
766
+ s.tooltip.style.top = `${Math.max(0, rect.top - 24)}px`;
767
+ }
768
+ break;
769
+ }
770
+ case "tool:clearHighlight":
771
+ if (s.highlightOverlay) s.highlightOverlay.style.display = "none";
772
+ if (s.tooltip) s.tooltip.style.display = "none";
773
+ break;
774
+ case "tool:selectByTreeId": {
775
+ const id = msg.id;
776
+ if (!id) break;
777
+ const target = document.querySelector(id);
778
+ if (target) {
779
+ const selectable = findSelectableElement(target);
780
+ selectElement(selectable);
781
+ }
782
+ break;
783
+ }
429
784
  }
430
785
  }
431
786
  function notifyPathChanged() {
@@ -445,6 +800,8 @@ function CodeSurface() {
445
800
  document.removeEventListener("click", onClick, true);
446
801
  window.removeEventListener("message", onMessage);
447
802
  window.removeEventListener("popstate", notifyPathChanged);
803
+ treeObserver.disconnect();
804
+ if (debounceTimer) clearTimeout(debounceTimer);
448
805
  if (s.overlayRafId) cancelAnimationFrame(s.overlayRafId);
449
806
  s.tokenPreviewStyle?.remove();
450
807
  s.highlightOverlay?.remove();