@replohq/sandbox-runtime 0.2.0 → 1.0.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 (51) hide show
  1. package/_vendor/schemas/componentPreview.d.ts +68 -0
  2. package/_vendor/schemas/componentPreview.mjs +97 -0
  3. package/_vendor/schemas/generated/componentPreview.d.ts +30 -0
  4. package/_vendor/schemas/generated/componentPreview.mjs +1 -0
  5. package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +8 -0
  6. package/_vendor/schemas/loaderKeys.d.ts +57 -0
  7. package/_vendor/schemas/selectModeIframeRpc.d.ts +21 -1
  8. package/_vendor/schemas/selectModeIframeRpc.mjs +11 -3
  9. package/buildMetadata.d.ts +9 -0
  10. package/buildMetadata.js +7 -0
  11. package/buildMetadata.js.map +7 -0
  12. package/next/component-preview-href.d.ts +5 -0
  13. package/next/component-preview-href.js +14 -0
  14. package/next/component-preview-href.js.map +7 -0
  15. package/next/component-preview-navigation.d.ts +1 -0
  16. package/next/component-preview-navigation.js +66 -0
  17. package/next/component-preview-navigation.js.map +7 -0
  18. package/next/component-preview.d.ts +4 -1
  19. package/next/component-preview.js +49 -14
  20. package/next/component-preview.js.map +2 -2
  21. package/package.json +12 -5
  22. package/select-mode/EditorSelection.js +8 -2
  23. package/select-mode/EditorSelection.js.map +2 -2
  24. package/select-mode/fiber.d.ts +12 -17
  25. package/select-mode/fiber.js +228 -64
  26. package/select-mode/fiber.js.map +2 -2
  27. package/select-mode/hooks/useEditModeStateMachine.js +2 -0
  28. package/select-mode/hooks/useEditModeStateMachine.js.map +2 -2
  29. package/select-mode/hooks/useOptimisticOverrides.js +2 -2
  30. package/select-mode/hooks/useOptimisticOverrides.js.map +2 -2
  31. package/select-mode/hooks/useParentMessaging.js +21 -1
  32. package/select-mode/hooks/useParentMessaging.js.map +2 -2
  33. package/select-mode/hooks/useSelectModeController.js +2 -0
  34. package/select-mode/hooks/useSelectModeController.js.map +2 -2
  35. package/select-mode/hooks/useSelectionHover.d.ts +4 -5
  36. package/select-mode/hooks/useSelectionHover.js +21 -3
  37. package/select-mode/hooks/useSelectionHover.js.map +2 -2
  38. package/select-mode/hooks/useSelectionInteractions.js +12 -11
  39. package/select-mode/hooks/useSelectionInteractions.js.map +2 -2
  40. package/select-mode/hooks/useSelectionScrollSync.d.ts +3 -4
  41. package/select-mode/hooks/useSelectionScrollSync.js +4 -9
  42. package/select-mode/hooks/useSelectionScrollSync.js.map +2 -2
  43. package/select-mode/hooks/useSiblingResolution.d.ts +2 -2
  44. package/select-mode/hooks/useSiblingResolution.js +10 -47
  45. package/select-mode/hooks/useSiblingResolution.js.map +2 -2
  46. package/select-mode/iframeRpcChannel.d.ts +7 -1
  47. package/select-mode/selectModeSession.d.ts +1 -0
  48. package/select-mode/selectModeSession.js.map +2 -2
  49. package/select-mode/selectionFrame.d.ts +13 -17
  50. package/select-mode/selectionFrame.js +18 -5
  51. package/select-mode/selectionFrame.js.map +2 -2
package/package.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "name": "@replohq/sandbox-runtime",
3
- "version": "0.2.0",
3
+ "version": "1.0.0",
4
+ "reploBuild": {
5
+ "packageName": "@replohq/sandbox-runtime",
6
+ "version": "1.0.0",
7
+ "branch": "HEAD",
8
+ "commit": "1818d468bc1a4ba23ac3fad1b296eb8f2f94155c",
9
+ "builtAt": "2026-08-19T03:50:51.141Z"
10
+ },
4
11
  "description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
5
12
  "license": "SEE LICENSE IN LICENSE",
6
13
  "type": "module",
@@ -33,11 +40,11 @@
33
40
  "zod": "^4.3.6"
34
41
  },
35
42
  "peerDependencies": {
36
- "@types/react": ">=18.2.0",
37
- "@types/react-dom": ">=18.2.0",
43
+ "@types/react": ">=19.0.0",
44
+ "@types/react-dom": ">=19.0.0",
38
45
  "next": ">=15.0.0",
39
- "react": ">=18.2.0",
40
- "react-dom": ">=18.2.0"
46
+ "react": ">=19.0.0",
47
+ "react-dom": ">=19.0.0"
41
48
  },
42
49
  "publishConfig": {
43
50
  "registry": "https://registry.npmjs.org/",
@@ -1,6 +1,9 @@
1
1
  "use client";
2
2
  import { useCallback, useEffect } from "react";
3
- import { buildElementDescriptor } from "./descriptor/elementDescriptor";
3
+ import {
4
+ buildElementDescriptor,
5
+ buildSelector
6
+ } from "./descriptor/elementDescriptor";
4
7
  import { installFastRefreshRelay } from "./fast-refresh-relay";
5
8
  import { useCrosshairCursor } from "./hooks/useCrosshairCursor";
6
9
  import { useEditModeStateMachine } from "./hooks/useEditModeStateMachine";
@@ -10,6 +13,7 @@ import { useSelectionHover } from "./hooks/useSelectionHover";
10
13
  import { useSelectionInteractions } from "./hooks/useSelectionInteractions";
11
14
  import { useSelectionScrollSync } from "./hooks/useSelectionScrollSync";
12
15
  import { useSelectModeController } from "./hooks/useSelectModeController";
16
+ import { getRepetitionSiblingElements } from "./hooks/useSiblingResolution";
13
17
  import { getWebsiteBuilderRpc } from "./iframeRpcChannel";
14
18
  import { symbolicateSelectedElement } from "./symbolicate";
15
19
  function EditorSelection() {
@@ -40,10 +44,11 @@ function EditorSelection() {
40
44
  if (!(element instanceof Element) || !element.isConnected) {
41
45
  return;
42
46
  }
47
+ const siblingElements = getRepetitionSiblingElements({ element });
43
48
  const descriptor = buildElementDescriptor({
44
49
  element,
45
50
  clickPosition: session.clickPosition ?? (session.data.kind === "react-element" ? session.data.clickPosition : { x: 0, y: 0 }),
46
- siblingSelectors: session.data.kind === "react-element" ? session.data.siblingSelectors : []
51
+ siblingSelectors: siblingElements.map(buildSelector)
47
52
  });
48
53
  if (descriptor.kind !== "react-element") {
49
54
  return;
@@ -51,6 +56,7 @@ function EditorSelection() {
51
56
  interactionSessionRef.current = {
52
57
  ...session,
53
58
  element,
59
+ siblingElements,
54
60
  data: descriptor
55
61
  };
56
62
  void symbolicateSelectedElement(descriptor).then((symbolicated) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../select-mode/EditorSelection.tsx"],
4
- "sourcesContent": ["\"use client\";\n\nimport { useCallback, useEffect } from \"react\";\n\nimport { buildElementDescriptor } from \"./descriptor/elementDescriptor\";\nimport { installFastRefreshRelay } from \"./fast-refresh-relay\";\nimport { useCrosshairCursor } from \"./hooks/useCrosshairCursor\";\nimport { useEditModeStateMachine } from \"./hooks/useEditModeStateMachine\";\nimport { useOptimisticOverrides } from \"./hooks/useOptimisticOverrides\";\nimport { useParentMessaging } from \"./hooks/useParentMessaging\";\nimport { useSelectionHover } from \"./hooks/useSelectionHover\";\nimport { useSelectionInteractions } from \"./hooks/useSelectionInteractions\";\nimport { useSelectionScrollSync } from \"./hooks/useSelectionScrollSync\";\nimport { useSelectModeController } from \"./hooks/useSelectModeController\";\nimport { getWebsiteBuilderRpc } from \"./iframeRpcChannel\";\nimport { symbolicateSelectedElement } from \"./symbolicate\";\n\n/**\n * Mounts the iframe-side selectMode controller. This is a render-less\n * component \u2014 every responsibility is delegated to a dedicated hook so each\n * concern (parent messaging, cursor styling, pointer interactions, hover\n * tracking, scroll syncing) can be read in isolation:\n *\n * - `useParentMessaging` \u2014 inbound messages from the parent (set mode,\n * set selection type) + outbound `IFRAME_READY` handshake.\n * - `useCrosshairCursor` \u2014 crosshair cursor + disable text/drag selection\n * while in selection mode.\n * - `useSelectionInteractions` \u2014 pointer-down / move / up state machine\n * that produces `ELEMENT_SELECTED`, `AREA_SELECTED`, and live\n * `DRAG_RECT_UPDATED` messages.\n * - `useSelectionHover` \u2014 hover-element rect updates.\n * - `useSelectionScrollSync` \u2014 keeps the parent overlay aligned with the\n * selected element/area when the iframe scrolls.\n *\n * The `interactionSessionRef` is the single source of truth for the\n * in-progress drag/selection \u2014 every hook reads/writes it via ref so we\n * don't trigger React re-renders during interaction-heavy operations.\n *\n * @author Kevin O'Connell 2026-04-17\n */\nexport function EditorSelection() {\n const {\n displayMode,\n interactionSessionRef,\n setDisplayMode,\n setSelectionType,\n } = useSelectModeController();\n const { beginEditing, editingElementRef, formatCommand, revert } =\n useEditModeStateMachine({\n displayMode,\n interactionSessionRef,\n });\n const {\n applyOptimisticUpdate,\n markCommitted,\n reapplyOverrides,\n resolveSessionOverrides,\n revertOverride,\n } = useOptimisticOverrides();\n\n const postSelectedElementSnapshot = useCallback(() => {\n const session = interactionSessionRef.current;\n if (session.type !== \"element\") {\n return;\n }\n\n const selectedElement =\n session.data.kind === \"react-element\"\n ? document.querySelector(session.data.selector)\n : null;\n const element = selectedElement ?? session.element;\n if (!(element instanceof Element) || !element.isConnected) {\n return;\n }\n\n const descriptor = buildElementDescriptor({\n element,\n clickPosition:\n session.clickPosition ??\n (session.data.kind === \"react-element\"\n ? session.data.clickPosition\n : { x: 0, y: 0 }),\n siblingSelectors:\n session.data.kind === \"react-element\"\n ? session.data.siblingSelectors\n : [],\n });\n if (descriptor.kind !== \"react-element\") {\n return;\n }\n\n interactionSessionRef.current = {\n ...session,\n element,\n data: descriptor,\n };\n\n void symbolicateSelectedElement(descriptor).then((symbolicated) => {\n getWebsiteBuilderRpc()?.elementSelected({ data: symbolicated });\n });\n }, [interactionSessionRef]);\n\n // eslint-disable-next-line replo/no-use-effect -- Installs a window-level console bridge for the preview iframe.\n useEffect(() => {\n installFastRefreshRelay({\n onComplete: () => {\n // Re-apply before re-symbolicating so the snapshot reads the\n // optimistic content, not the source the refresh just repainted.\n reapplyOverrides({ skipElement: editingElementRef.current });\n postSelectedElementSnapshot();\n },\n });\n }, [editingElementRef, postSelectedElementSnapshot, reapplyOverrides]);\n\n useParentMessaging({\n applyOptimisticUpdate,\n markCommitted,\n onExitEditMode: revert,\n onFormatCommand: formatCommand,\n resolveSessionOverrides,\n revertOverride,\n setDisplayMode,\n setSelectionType,\n });\n useCrosshairCursor({ displayMode });\n useSelectionInteractions({\n displayMode,\n editingElementRef,\n interactionSessionRef,\n onBeginEditing: beginEditing,\n });\n useSelectionHover({ displayMode, interactionSessionRef });\n useSelectionScrollSync({\n displayMode,\n interactionSessionRef,\n });\n\n return null;\n}\n"],
5
- "mappings": ";AAEA,SAAS,aAAa,iBAAiB;AAEvC,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,gCAAgC;AACzC,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AACxC,SAAS,4BAA4B;AACrC,SAAS,kCAAkC;AAyBpC,SAAS,kBAAkB;AAChC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,wBAAwB;AAC5B,QAAM,EAAE,cAAc,mBAAmB,eAAe,OAAO,IAC7D,wBAAwB;AAAA,IACtB;AAAA,IACA;AAAA,EACF,CAAC;AACH,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,uBAAuB;AAE3B,QAAM,8BAA8B,YAAY,MAAM;AACpD,UAAM,UAAU,sBAAsB;AACtC,QAAI,QAAQ,SAAS,WAAW;AAC9B;AAAA,IACF;AAEA,UAAM,kBACJ,QAAQ,KAAK,SAAS,kBAClB,SAAS,cAAc,QAAQ,KAAK,QAAQ,IAC5C;AACN,UAAM,UAAU,mBAAmB,QAAQ;AAC3C,QAAI,EAAE,mBAAmB,YAAY,CAAC,QAAQ,aAAa;AACzD;AAAA,IACF;AAEA,UAAM,aAAa,uBAAuB;AAAA,MACxC;AAAA,MACA,eACE,QAAQ,kBACP,QAAQ,KAAK,SAAS,kBACnB,QAAQ,KAAK,gBACb,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,MACnB,kBACE,QAAQ,KAAK,SAAS,kBAClB,QAAQ,KAAK,mBACb,CAAC;AAAA,IACT,CAAC;AACD,QAAI,WAAW,SAAS,iBAAiB;AACvC;AAAA,IACF;AAEA,0BAAsB,UAAU;AAAA,MAC9B,GAAG;AAAA,MACH;AAAA,MACA,MAAM;AAAA,IACR;AAEA,SAAK,2BAA2B,UAAU,EAAE,KAAK,CAAC,iBAAiB;AACjE,2BAAqB,GAAG,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAAA,IAChE,CAAC;AAAA,EACH,GAAG,CAAC,qBAAqB,CAAC;AAG1B,YAAU,MAAM;AACd,4BAAwB;AAAA,MACtB,YAAY,MAAM;AAGhB,yBAAiB,EAAE,aAAa,kBAAkB,QAAQ,CAAC;AAC3D,oCAA4B;AAAA,MAC9B;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,mBAAmB,6BAA6B,gBAAgB,CAAC;AAErE,qBAAmB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,qBAAmB,EAAE,YAAY,CAAC;AAClC,2BAAyB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,EAClB,CAAC;AACD,oBAAkB,EAAE,aAAa,sBAAsB,CAAC;AACxD,yBAAuB;AAAA,IACrB;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO;AACT;",
4
+ "sourcesContent": ["\"use client\";\n\nimport { useCallback, useEffect } from \"react\";\n\nimport {\n buildElementDescriptor,\n buildSelector,\n} from \"./descriptor/elementDescriptor\";\nimport { installFastRefreshRelay } from \"./fast-refresh-relay\";\nimport { useCrosshairCursor } from \"./hooks/useCrosshairCursor\";\nimport { useEditModeStateMachine } from \"./hooks/useEditModeStateMachine\";\nimport { useOptimisticOverrides } from \"./hooks/useOptimisticOverrides\";\nimport { useParentMessaging } from \"./hooks/useParentMessaging\";\nimport { useSelectionHover } from \"./hooks/useSelectionHover\";\nimport { useSelectionInteractions } from \"./hooks/useSelectionInteractions\";\nimport { useSelectionScrollSync } from \"./hooks/useSelectionScrollSync\";\nimport { useSelectModeController } from \"./hooks/useSelectModeController\";\nimport { getRepetitionSiblingElements } from \"./hooks/useSiblingResolution\";\nimport { getWebsiteBuilderRpc } from \"./iframeRpcChannel\";\nimport { symbolicateSelectedElement } from \"./symbolicate\";\n\n/**\n * Mounts the iframe-side selectMode controller. This is a render-less\n * component \u2014 every responsibility is delegated to a dedicated hook so each\n * concern (parent messaging, cursor styling, pointer interactions, hover\n * tracking, scroll syncing) can be read in isolation:\n *\n * - `useParentMessaging` \u2014 inbound messages from the parent (set mode,\n * set selection type) + outbound `IFRAME_READY` handshake.\n * - `useCrosshairCursor` \u2014 crosshair cursor + disable text/drag selection\n * while in selection mode.\n * - `useSelectionInteractions` \u2014 pointer-down / move / up state machine\n * that produces `ELEMENT_SELECTED`, `AREA_SELECTED`, and live\n * `DRAG_RECT_UPDATED` messages.\n * - `useSelectionHover` \u2014 hover-element rect updates.\n * - `useSelectionScrollSync` \u2014 keeps the parent overlay aligned with the\n * selected element/area when the iframe scrolls.\n *\n * The `interactionSessionRef` is the single source of truth for the\n * in-progress drag/selection \u2014 every hook reads/writes it via ref so we\n * don't trigger React re-renders during interaction-heavy operations.\n *\n * @author Kevin O'Connell 2026-04-17\n */\nexport function EditorSelection() {\n const {\n displayMode,\n interactionSessionRef,\n setDisplayMode,\n setSelectionType,\n } = useSelectModeController();\n const { beginEditing, editingElementRef, formatCommand, revert } =\n useEditModeStateMachine({\n displayMode,\n interactionSessionRef,\n });\n const {\n applyOptimisticUpdate,\n markCommitted,\n reapplyOverrides,\n resolveSessionOverrides,\n revertOverride,\n } = useOptimisticOverrides();\n\n const postSelectedElementSnapshot = useCallback(() => {\n const session = interactionSessionRef.current;\n if (session.type !== \"element\") {\n return;\n }\n\n const selectedElement =\n session.data.kind === \"react-element\"\n ? document.querySelector(session.data.selector)\n : null;\n const element = selectedElement ?? session.element;\n if (!(element instanceof Element) || !element.isConnected) {\n return;\n }\n\n // A fast refresh can replace the repeated DOM nodes, so the group is\n // re-resolved from the (possibly re-created) element rather than carried\n // over from the stale session.\n const siblingElements = getRepetitionSiblingElements({ element });\n const descriptor = buildElementDescriptor({\n element,\n clickPosition:\n session.clickPosition ??\n (session.data.kind === \"react-element\"\n ? session.data.clickPosition\n : { x: 0, y: 0 }),\n siblingSelectors: siblingElements.map(buildSelector),\n });\n if (descriptor.kind !== \"react-element\") {\n return;\n }\n\n interactionSessionRef.current = {\n ...session,\n element,\n siblingElements,\n data: descriptor,\n };\n\n void symbolicateSelectedElement(descriptor).then((symbolicated) => {\n getWebsiteBuilderRpc()?.elementSelected({ data: symbolicated });\n });\n }, [interactionSessionRef]);\n\n // eslint-disable-next-line replo/no-use-effect -- Installs a window-level console bridge for the preview iframe.\n useEffect(() => {\n installFastRefreshRelay({\n onComplete: () => {\n // Re-apply before re-symbolicating so the snapshot reads the\n // optimistic content, not the source the refresh just repainted.\n reapplyOverrides({ skipElement: editingElementRef.current });\n postSelectedElementSnapshot();\n },\n });\n }, [editingElementRef, postSelectedElementSnapshot, reapplyOverrides]);\n\n useParentMessaging({\n applyOptimisticUpdate,\n markCommitted,\n onExitEditMode: revert,\n onFormatCommand: formatCommand,\n resolveSessionOverrides,\n revertOverride,\n setDisplayMode,\n setSelectionType,\n });\n useCrosshairCursor({ displayMode });\n useSelectionInteractions({\n displayMode,\n editingElementRef,\n interactionSessionRef,\n onBeginEditing: beginEditing,\n });\n useSelectionHover({ displayMode, interactionSessionRef });\n useSelectionScrollSync({\n displayMode,\n interactionSessionRef,\n });\n\n return null;\n}\n"],
5
+ "mappings": ";AAEA,SAAS,aAAa,iBAAiB;AAEvC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,gCAAgC;AACzC,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AACxC,SAAS,oCAAoC;AAC7C,SAAS,4BAA4B;AACrC,SAAS,kCAAkC;AAyBpC,SAAS,kBAAkB;AAChC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,wBAAwB;AAC5B,QAAM,EAAE,cAAc,mBAAmB,eAAe,OAAO,IAC7D,wBAAwB;AAAA,IACtB;AAAA,IACA;AAAA,EACF,CAAC;AACH,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,uBAAuB;AAE3B,QAAM,8BAA8B,YAAY,MAAM;AACpD,UAAM,UAAU,sBAAsB;AACtC,QAAI,QAAQ,SAAS,WAAW;AAC9B;AAAA,IACF;AAEA,UAAM,kBACJ,QAAQ,KAAK,SAAS,kBAClB,SAAS,cAAc,QAAQ,KAAK,QAAQ,IAC5C;AACN,UAAM,UAAU,mBAAmB,QAAQ;AAC3C,QAAI,EAAE,mBAAmB,YAAY,CAAC,QAAQ,aAAa;AACzD;AAAA,IACF;AAKA,UAAM,kBAAkB,6BAA6B,EAAE,QAAQ,CAAC;AAChE,UAAM,aAAa,uBAAuB;AAAA,MACxC;AAAA,MACA,eACE,QAAQ,kBACP,QAAQ,KAAK,SAAS,kBACnB,QAAQ,KAAK,gBACb,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,MACnB,kBAAkB,gBAAgB,IAAI,aAAa;AAAA,IACrD,CAAC;AACD,QAAI,WAAW,SAAS,iBAAiB;AACvC;AAAA,IACF;AAEA,0BAAsB,UAAU;AAAA,MAC9B,GAAG;AAAA,MACH;AAAA,MACA;AAAA,MACA,MAAM;AAAA,IACR;AAEA,SAAK,2BAA2B,UAAU,EAAE,KAAK,CAAC,iBAAiB;AACjE,2BAAqB,GAAG,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAAA,IAChE,CAAC;AAAA,EACH,GAAG,CAAC,qBAAqB,CAAC;AAG1B,YAAU,MAAM;AACd,4BAAwB;AAAA,MACtB,YAAY,MAAM;AAGhB,yBAAiB,EAAE,aAAa,kBAAkB,QAAQ,CAAC;AAC3D,oCAA4B;AAAA,MAC9B;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,mBAAmB,6BAA6B,gBAAgB,CAAC;AAErE,qBAAmB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,qBAAmB,EAAE,YAAY,CAAC;AAClC,2BAAyB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,EAClB,CAAC;AACD,oBAAkB,EAAE,aAAa,sBAAsB,CAAC;AACxD,yBAAuB;AAAA,IACrB;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,9 @@
1
+ export interface DebugSource {
2
+ fileName: string;
3
+ lineNumber: number;
4
+ columnNumber?: number;
5
+ }
1
6
  export interface ReactFiber {
2
- _debugSource?: {
3
- fileName: string;
4
- lineNumber: number;
5
- columnNumber?: number;
6
- };
7
7
  _debugStack?: unknown;
8
8
  _debugOwner?: ReactFiber;
9
9
  type?: {
@@ -11,17 +11,17 @@ export interface ReactFiber {
11
11
  displayName?: string;
12
12
  } | string | ((...args: unknown[]) => unknown) | null;
13
13
  return?: ReactFiber | null;
14
+ child?: ReactFiber | null;
15
+ sibling?: ReactFiber | null;
16
+ alternate?: ReactFiber | null;
17
+ stateNode?: unknown;
14
18
  memoizedProps?: Record<string, unknown>;
15
19
  pendingProps?: Record<string, unknown>;
16
20
  }
17
21
  export declare function getFiberFromElement(element: HTMLElement): ReactFiber | null;
18
22
  export declare function getComponentName(fiber: ReactFiber | null | undefined): string | undefined;
19
- export declare function findDebugSource(fiber: ReactFiber, maxDepth?: number): {
20
- source: ReactFiber["_debugSource"];
21
- componentName?: string;
22
- } | null;
23
- export declare function findDebugSourceReact19(fiber: ReactFiber): {
24
- source: ReactFiber["_debugSource"];
23
+ export declare function findDebugSource(fiber: ReactFiber): {
24
+ source: DebugSource;
25
25
  componentName?: string;
26
26
  } | null;
27
27
  export declare function getSourceLocation(element: HTMLElement): {
@@ -30,12 +30,7 @@ export declare function getSourceLocation(element: HTMLElement): {
30
30
  columnNumber: number | undefined;
31
31
  componentName: string | undefined;
32
32
  } | undefined;
33
- export declare function getHostSourceIdentity(element: HTMLElement): {
34
- filePath: string;
35
- lineNumber: number;
36
- columnNumber: number | undefined;
37
- tagName: string;
38
- } | null;
33
+ export declare function getRepetitionHostNodes(element: HTMLElement): HTMLElement[];
39
34
  export declare function getFiberChain(element: HTMLElement): {
40
35
  sourceLocation: {
41
36
  filePath: string;
@@ -2,6 +2,10 @@ import { z } from "zod";
2
2
  const MAX_FIBER_CHAIN_DEPTH = 80;
3
3
  const MAX_SERIALIZED_PROP_COUNT = 24;
4
4
  const MAX_SERIALIZED_STRING_LENGTH = 500;
5
+ const MAX_SIBLING_SCAN = 5e3;
6
+ const MAX_SUBTREE_SCAN = 5e3;
7
+ const MAX_ROOT_CLIMB_DEPTH = 1e3;
8
+ const MAX_CURRENT_TREE_SCAN = 5e4;
5
9
  const REACT_INTERNAL_FRAME_PATTERNS = [
6
10
  /react.*jsx-dev-runtime/,
7
11
  /react.*jsx-runtime/,
@@ -20,9 +24,9 @@ function isReactInternalFrame(fileName) {
20
24
  const REACT_INTERNAL_METHOD_PATTERNS = [
21
25
  /(?:^|\.)jsxDEV(?:Impl)?$/,
22
26
  /(?:^|\.)jsxs?$/,
23
- /(?:^|\.)react_stack_bottom_frame$/,
24
27
  /(?:^|\.)fakeJSXCallSite$/
25
28
  ];
29
+ const REACT_STACK_BOTTOM_FRAME_PATTERN = /react[-_]stack[-_]bottom[-_]frame/;
26
30
  function isReactInternalMethod(methodName) {
27
31
  if (!methodName) {
28
32
  return false;
@@ -49,7 +53,12 @@ function parseDebugStackSource(debugStack) {
49
53
  if (!line) {
50
54
  continue;
51
55
  }
52
- const framedMatch = line.match(/^at\s+(.+?)\s+\((.*):(\d+):(\d+)\)$/);
56
+ if (REACT_STACK_BOTTOM_FRAME_PATTERN.test(line)) {
57
+ return null;
58
+ }
59
+ const framedMatch = line.match(/^at\s+(.+?)\s+\((.*):(\d+):(\d+)\)$/) ?? // Method names never contain `/`, which keeps `@`-versioned path
60
+ // segments (`.pnpm/react@19.2.4/…`) from being read as a method.
61
+ line.match(/^([^@/]*)@(.+):(\d+):(\d+)$/);
53
62
  const stackFrameMatch = framedMatch ?? line.match(/^at\s+(?:.*\()?(.*):(\d+):(\d+)\)?$/) ?? line.match(/^(.*):(\d+):(\d+)$/);
54
63
  if (!stackFrameMatch) {
55
64
  continue;
@@ -99,30 +108,6 @@ function getFiberFromElement(element) {
99
108
  if (fiberKey) {
100
109
  return element[fiberKey] ?? null;
101
110
  }
102
- const instanceKey = keys.find((key) => {
103
- return key.startsWith("__reactInternalInstance$");
104
- });
105
- if (instanceKey) {
106
- return (
107
- // Legacy cast - may or may not be necessary
108
- // eslint-disable-next-line replo/no-as-cast
109
- element[instanceKey] ?? null
110
- );
111
- }
112
- const possibleFiberKey = keys.find((key) => {
113
- if (!key.startsWith("__react")) {
114
- return false;
115
- }
116
- const value = element[key];
117
- return value && typeof value === "object" && "_debugSource" in value;
118
- });
119
- if (possibleFiberKey) {
120
- return (
121
- // Legacy cast - may or may not be necessary
122
- // eslint-disable-next-line replo/no-as-cast
123
- element[possibleFiberKey] ?? null
124
- );
125
- }
126
111
  return null;
127
112
  }
128
113
  function getComponentName(fiber) {
@@ -140,28 +125,7 @@ function getComponentName(fiber) {
140
125
  }
141
126
  return void 0;
142
127
  }
143
- function findDebugSource(fiber, maxDepth = 50) {
144
- let current = fiber;
145
- let depth = 0;
146
- while (current && depth < maxDepth) {
147
- if (current._debugSource) {
148
- return {
149
- source: current._debugSource,
150
- componentName: getComponentName(current)
151
- };
152
- }
153
- if (current._debugOwner?._debugSource) {
154
- return {
155
- source: current._debugOwner._debugSource,
156
- componentName: getComponentName(current._debugOwner)
157
- };
158
- }
159
- current = current.return;
160
- depth++;
161
- }
162
- return null;
163
- }
164
- function findDebugSourceReact19(fiber) {
128
+ function findDebugSource(fiber) {
165
129
  let current = fiber;
166
130
  let depth = 0;
167
131
  const maxDepth = 50;
@@ -183,7 +147,7 @@ function getSourceLocation(element) {
183
147
  while (currentElement) {
184
148
  const fiber = getFiberFromElement(currentElement);
185
149
  if (fiber) {
186
- const debugInfo = findDebugSource(fiber) ?? findDebugSourceReact19(fiber);
150
+ const debugInfo = findDebugSource(fiber);
187
151
  if (debugInfo?.source) {
188
152
  return {
189
153
  filePath: debugInfo.source.fileName,
@@ -198,27 +162,228 @@ function getSourceLocation(element) {
198
162
  return void 0;
199
163
  }
200
164
  function getDebugSource(fiber) {
201
- const debugInfo = findDebugSource(fiber) ?? findDebugSourceReact19(fiber);
202
- return debugInfo?.source;
165
+ return findDebugSource(fiber)?.source;
203
166
  }
204
167
  function getTagName(fiber) {
205
168
  return z.string().safeParse(fiber.type).data?.toLowerCase();
206
169
  }
207
- function getHostSourceIdentity(element) {
208
- const fiber = getFiberFromElement(element);
209
- if (!fiber) {
210
- return null;
170
+ const fiberSourceKeyCache = /* @__PURE__ */ new WeakMap();
171
+ function getFiberSourceKey(fiber) {
172
+ const cached = fiberSourceKeyCache.get(fiber);
173
+ if (cached !== void 0) {
174
+ return cached;
211
175
  }
212
176
  const source = getDebugSource(fiber);
213
- if (!source) {
177
+ const key = source ? `${source.fileName}:${source.lineNumber}:${source.columnNumber ?? ""}` : null;
178
+ fiberSourceKeyCache.set(fiber, key);
179
+ return key;
180
+ }
181
+ function findRepetitionGroup(fiber) {
182
+ const parent = fiber.return;
183
+ if (!parent) {
184
+ return null;
185
+ }
186
+ const type = fiber.type;
187
+ const candidates = [];
188
+ let child = parent.child;
189
+ let scanned = 0;
190
+ while (child && scanned < MAX_SIBLING_SCAN) {
191
+ if (child.type === type) {
192
+ candidates.push(child);
193
+ }
194
+ child = child.sibling;
195
+ scanned += 1;
196
+ }
197
+ if (candidates.length < 2) {
198
+ return null;
199
+ }
200
+ const sourceKey = getFiberSourceKey(fiber);
201
+ if (!sourceKey) {
202
+ return null;
203
+ }
204
+ const members = candidates.filter((candidate) => {
205
+ return getFiberSourceKey(candidate) === sourceKey;
206
+ });
207
+ return members.length >= 2 ? members : null;
208
+ }
209
+ function descendChildPath(fiber, path) {
210
+ let current = fiber;
211
+ for (const childIndex of path) {
212
+ if (!current) {
213
+ return null;
214
+ }
215
+ let child = current.child;
216
+ let index = 0;
217
+ while (child && index < childIndex) {
218
+ child = child.sibling;
219
+ index += 1;
220
+ }
221
+ if (!child) {
222
+ return null;
223
+ }
224
+ current = child;
225
+ }
226
+ return current ?? null;
227
+ }
228
+ function getHostNode(fiber) {
229
+ return fiber?.stateNode instanceof HTMLElement ? fiber.stateNode : null;
230
+ }
231
+ function isMatchingHost(fiber, type, sourceKey) {
232
+ if (!fiber || !(fiber.stateNode instanceof HTMLElement)) {
233
+ return false;
234
+ }
235
+ return fiber.type === type && getFiberSourceKey(fiber) === sourceKey;
236
+ }
237
+ function findHostInSubtree(root, type, sourceKey) {
238
+ const stack = [];
239
+ let child = root.child;
240
+ while (child) {
241
+ stack.push(child);
242
+ child = child.sibling;
243
+ }
244
+ let scanned = 0;
245
+ while (stack.length > 0 && scanned < MAX_SUBTREE_SCAN) {
246
+ const current = stack.pop();
247
+ scanned += 1;
248
+ if (!current) {
249
+ continue;
250
+ }
251
+ if (isMatchingHost(current, type, sourceKey)) {
252
+ return getHostNode(current);
253
+ }
254
+ let next = current.child;
255
+ while (next) {
256
+ stack.push(next);
257
+ next = next.sibling;
258
+ }
259
+ }
260
+ return null;
261
+ }
262
+ function resolveCurrentFiberByParentLinkage(fiber) {
263
+ const alternate = fiber.alternate;
264
+ if (!alternate) {
265
+ return fiber;
266
+ }
267
+ let child = fiber.return?.child;
268
+ let scanned = 0;
269
+ while (child && scanned < MAX_SIBLING_SCAN) {
270
+ if (child === fiber) {
271
+ return fiber;
272
+ }
273
+ child = child.sibling;
274
+ scanned += 1;
275
+ }
276
+ return alternate;
277
+ }
278
+ function getFiberRootCurrent(stateNode) {
279
+ if (!stateNode || typeof stateNode !== "object" || !("current" in stateNode)) {
214
280
  return null;
215
281
  }
216
- return {
217
- filePath: source.fileName,
218
- lineNumber: source.lineNumber,
219
- columnNumber: source.columnNumber,
220
- tagName: element.tagName.toLowerCase()
221
- };
282
+ const current = Reflect.get(stateNode, "current");
283
+ if (current && typeof current === "object" && "child" in current) {
284
+ return current;
285
+ }
286
+ return null;
287
+ }
288
+ function resolveCurrentFiber(fiber) {
289
+ const alternate = fiber.alternate;
290
+ if (!alternate) {
291
+ return fiber;
292
+ }
293
+ let rootFiber = fiber;
294
+ let climbed = 0;
295
+ while (rootFiber.return && climbed < MAX_ROOT_CLIMB_DEPTH) {
296
+ rootFiber = rootFiber.return;
297
+ climbed += 1;
298
+ }
299
+ const currentRoot = rootFiber.return ? null : getFiberRootCurrent(rootFiber.stateNode);
300
+ if (!currentRoot) {
301
+ return resolveCurrentFiberByParentLinkage(fiber);
302
+ }
303
+ const stack = [currentRoot];
304
+ let scanned = 0;
305
+ while (stack.length > 0 && scanned < MAX_CURRENT_TREE_SCAN) {
306
+ const node = stack.pop();
307
+ scanned += 1;
308
+ if (!node) {
309
+ continue;
310
+ }
311
+ if (node === fiber || node === alternate) {
312
+ return node;
313
+ }
314
+ if (node.child) {
315
+ stack.push(node.child);
316
+ }
317
+ if (node.sibling) {
318
+ stack.push(node.sibling);
319
+ }
320
+ }
321
+ if (scanned >= MAX_CURRENT_TREE_SCAN) {
322
+ return resolveCurrentFiberByParentLinkage(fiber);
323
+ }
324
+ return fiber;
325
+ }
326
+ function getRepetitionHostNodes(element) {
327
+ const cachedFiber = getFiberFromElement(element);
328
+ if (!cachedFiber) {
329
+ return [element];
330
+ }
331
+ const selectedFiber = resolveCurrentFiber(cachedFiber);
332
+ const climb = [];
333
+ let cursor = selectedFiber;
334
+ let depth = 0;
335
+ let unit = null;
336
+ let group = null;
337
+ while (cursor && depth < MAX_FIBER_CHAIN_DEPTH) {
338
+ climb.push(cursor);
339
+ const candidate = findRepetitionGroup(cursor);
340
+ if (candidate) {
341
+ unit = cursor;
342
+ group = candidate;
343
+ break;
344
+ }
345
+ cursor = cursor.return;
346
+ depth += 1;
347
+ }
348
+ if (!unit || !group) {
349
+ return [element];
350
+ }
351
+ const downward = climb.slice(0, climb.indexOf(unit) + 1).reverse();
352
+ let pathFromUnit = [];
353
+ for (let i = 0; i < downward.length - 1 && pathFromUnit; i += 1) {
354
+ const parentFiber = downward[i];
355
+ const targetFiber = downward[i + 1];
356
+ if (!parentFiber || !targetFiber) {
357
+ break;
358
+ }
359
+ let child = parentFiber.child;
360
+ let index = 0;
361
+ while (child && child !== targetFiber && child !== targetFiber.alternate) {
362
+ child = child.sibling;
363
+ index += 1;
364
+ }
365
+ if (child) {
366
+ pathFromUnit.push(index);
367
+ } else {
368
+ pathFromUnit = null;
369
+ }
370
+ }
371
+ const selectedType = selectedFiber.type;
372
+ const selectedSourceKey = getFiberSourceKey(selectedFiber);
373
+ const nodes = [];
374
+ const seen = /* @__PURE__ */ new Set();
375
+ for (const member of group) {
376
+ const landed = pathFromUnit ? descendChildPath(member, pathFromUnit) : null;
377
+ const node = isMatchingHost(landed, selectedType, selectedSourceKey) ? getHostNode(landed) : findHostInSubtree(member, selectedType, selectedSourceKey);
378
+ if (node && !seen.has(node)) {
379
+ seen.add(node);
380
+ nodes.push(node);
381
+ }
382
+ }
383
+ if (!seen.has(element)) {
384
+ nodes.push(element);
385
+ }
386
+ return nodes;
222
387
  }
223
388
  function getSerializablePropValue(value) {
224
389
  if (value === null || typeof value === "number" || typeof value === "boolean") {
@@ -296,11 +461,10 @@ function getFiberChain(element) {
296
461
  }
297
462
  export {
298
463
  findDebugSource,
299
- findDebugSourceReact19,
300
464
  getComponentName,
301
465
  getFiberChain,
302
466
  getFiberFromElement,
303
- getHostSourceIdentity,
467
+ getRepetitionHostNodes,
304
468
  getSourceLocation
305
469
  };
306
470
  //# sourceMappingURL=fiber.js.map