@fictjs/runtime 0.9.0 → 0.11.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 (87) hide show
  1. package/dist/advanced.cjs +9 -9
  2. package/dist/advanced.d.cts +4 -4
  3. package/dist/advanced.d.ts +4 -4
  4. package/dist/advanced.js +4 -4
  5. package/dist/{binding-BWchH3Kp.d.cts → binding-DcnhUSQK.d.ts} +5 -3
  6. package/dist/{binding-BWchH3Kp.d.ts → binding-FRyTeLDn.d.cts} +5 -3
  7. package/dist/{chunk-FVX77557.js → chunk-2UR2UWE2.js} +3 -3
  8. package/dist/{chunk-LBE6DC3V.cjs → chunk-44EQF3AR.cjs} +63 -52
  9. package/dist/chunk-44EQF3AR.cjs.map +1 -0
  10. package/dist/{chunk-OAM7HABA.cjs → chunk-4QGEN5SJ.cjs} +340 -263
  11. package/dist/chunk-4QGEN5SJ.cjs.map +1 -0
  12. package/dist/{chunk-PD6IQY2Y.cjs → chunk-C5IE4WUG.cjs} +8 -8
  13. package/dist/{chunk-PD6IQY2Y.cjs.map → chunk-C5IE4WUG.cjs.map} +1 -1
  14. package/dist/{chunk-DXG3TARY.js → chunk-DIK33H5U.js} +202 -30
  15. package/dist/chunk-DIK33H5U.js.map +1 -0
  16. package/dist/{chunk-JVYH76ZX.js → chunk-FESAXMHT.js} +7 -6
  17. package/dist/{chunk-JVYH76ZX.js.map → chunk-FESAXMHT.js.map} +1 -1
  18. package/dist/chunk-FHQZCAAK.cjs +112 -0
  19. package/dist/chunk-FHQZCAAK.cjs.map +1 -0
  20. package/dist/{chunk-UBFDB6OL.cjs → chunk-QNMYVXRL.cjs} +222 -50
  21. package/dist/chunk-QNMYVXRL.cjs.map +1 -0
  22. package/dist/{chunk-N6ODUM2Y.js → chunk-S63VBIWN.js} +27 -16
  23. package/dist/chunk-S63VBIWN.js.map +1 -0
  24. package/dist/{chunk-T2LNV5Q5.js → chunk-WIHNVN6L.js} +153 -76
  25. package/dist/chunk-WIHNVN6L.js.map +1 -0
  26. package/dist/{devtools-BDp76luf.d.ts → devtools-BtIkN77t.d.cts} +14 -2
  27. package/dist/{devtools-5AipK9CX.d.cts → devtools-D2z4llpA.d.ts} +14 -2
  28. package/dist/index.cjs +60 -58
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +5 -5
  31. package/dist/index.d.ts +5 -5
  32. package/dist/index.dev.js +300 -74
  33. package/dist/index.dev.js.map +1 -1
  34. package/dist/index.js +13 -11
  35. package/dist/index.js.map +1 -1
  36. package/dist/internal-list.cjs +4 -4
  37. package/dist/internal-list.d.cts +2 -2
  38. package/dist/internal-list.d.ts +2 -2
  39. package/dist/internal-list.js +3 -3
  40. package/dist/internal.cjs +5 -5
  41. package/dist/internal.d.cts +6 -6
  42. package/dist/internal.d.ts +6 -6
  43. package/dist/internal.js +4 -4
  44. package/dist/jsx-dev-runtime.d.cts +671 -0
  45. package/dist/jsx-dev-runtime.d.ts +671 -0
  46. package/dist/jsx-runtime.d.cts +671 -0
  47. package/dist/jsx-runtime.d.ts +671 -0
  48. package/dist/{list-DL5DOFcO.d.ts → list-BKM6YOPq.d.ts} +2 -2
  49. package/dist/{list-hP7hQ9Vk.d.cts → list-Bi8dDF8Q.d.cts} +2 -2
  50. package/dist/loader.cjs +34 -28
  51. package/dist/loader.cjs.map +1 -1
  52. package/dist/loader.d.cts +2 -2
  53. package/dist/loader.d.ts +2 -2
  54. package/dist/loader.js +17 -11
  55. package/dist/loader.js.map +1 -1
  56. package/dist/{props-BpZz0AOq.d.cts → props-9chMyBGb.d.cts} +2 -2
  57. package/dist/{props-CjLH0JE-.d.ts → props-D1nj2p_3.d.ts} +2 -2
  58. package/dist/{resume-BJ4oHLi_.d.cts → resume-C5IKAIdh.d.ts} +2 -2
  59. package/dist/{resume-CuyJWXP_.d.ts → resume-DPZxmA95.d.cts} +2 -2
  60. package/dist/{scope-jPt5DHRT.d.ts → scope-BSkhJr0a.d.ts} +1 -1
  61. package/dist/{scope-BJCtq8hJ.d.cts → scope-Bn3sxem5.d.cts} +1 -1
  62. package/dist/{signal-C4ISF17w.d.cts → signal-Z4KkDk9h.d.cts} +12 -1
  63. package/dist/{signal-C4ISF17w.d.ts → signal-Z4KkDk9h.d.ts} +12 -1
  64. package/package.json +2 -2
  65. package/src/binding.ts +59 -29
  66. package/src/context.ts +4 -3
  67. package/src/devtools.ts +19 -2
  68. package/src/dom.ts +122 -42
  69. package/src/effect.ts +5 -5
  70. package/src/error-boundary.ts +5 -5
  71. package/src/hooks.ts +13 -5
  72. package/src/lifecycle.ts +48 -3
  73. package/src/list-helpers.ts +30 -13
  74. package/src/loader.ts +20 -12
  75. package/src/node-ops.ts +8 -5
  76. package/src/signal.ts +191 -18
  77. package/src/suspense.ts +5 -4
  78. package/src/transition.ts +9 -3
  79. package/dist/chunk-DXG3TARY.js.map +0 -1
  80. package/dist/chunk-LBE6DC3V.cjs.map +0 -1
  81. package/dist/chunk-N6ODUM2Y.js.map +0 -1
  82. package/dist/chunk-OAM7HABA.cjs.map +0 -1
  83. package/dist/chunk-PG4QX2I2.cjs +0 -111
  84. package/dist/chunk-PG4QX2I2.cjs.map +0 -1
  85. package/dist/chunk-T2LNV5Q5.js.map +0 -1
  86. package/dist/chunk-UBFDB6OL.cjs.map +0 -1
  87. /package/dist/{chunk-FVX77557.js.map → chunk-2UR2UWE2.js.map} +0 -0
@@ -19,6 +19,17 @@ interface MemoOptions<T> {
19
19
  name?: string;
20
20
  /** Source location */
21
21
  devToolsSource?: string;
22
+ /** Internal memo created by compiler runtime plumbing (hidden from DevTools) */
23
+ internal?: boolean;
24
+ }
25
+ /**
26
+ * Options for creating an effect
27
+ */
28
+ interface EffectOptions {
29
+ /** Debug name */
30
+ name?: string;
31
+ /** Source location */
32
+ devToolsSource?: string;
22
33
  }
23
34
  /**
24
35
  * Signal accessor - function to get/set signal value
@@ -63,4 +74,4 @@ declare function __resetReactiveState(): void;
63
74
  */
64
75
  declare function createSelector<T>(source: () => T, equalityFn?: (a: T, b: T) => boolean): (key: T) => boolean;
65
76
 
66
- export { type ComputedAccessor as C, type MemoOptions as M, type SignalAccessor as S, __resetReactiveState as _, type SignalOptions as a, createSelector as c, effectScope as e, signal as s };
77
+ export { type ComputedAccessor as C, type EffectOptions as E, type MemoOptions as M, type SignalAccessor as S, __resetReactiveState as _, type SignalOptions as a, createSelector as c, effectScope as e, signal as s };
@@ -19,6 +19,17 @@ interface MemoOptions<T> {
19
19
  name?: string;
20
20
  /** Source location */
21
21
  devToolsSource?: string;
22
+ /** Internal memo created by compiler runtime plumbing (hidden from DevTools) */
23
+ internal?: boolean;
24
+ }
25
+ /**
26
+ * Options for creating an effect
27
+ */
28
+ interface EffectOptions {
29
+ /** Debug name */
30
+ name?: string;
31
+ /** Source location */
32
+ devToolsSource?: string;
22
33
  }
23
34
  /**
24
35
  * Signal accessor - function to get/set signal value
@@ -63,4 +74,4 @@ declare function __resetReactiveState(): void;
63
74
  */
64
75
  declare function createSelector<T>(source: () => T, equalityFn?: (a: T, b: T) => boolean): (key: T) => boolean;
65
76
 
66
- export { type ComputedAccessor as C, type MemoOptions as M, type SignalAccessor as S, __resetReactiveState as _, type SignalOptions as a, createSelector as c, effectScope as e, signal as s };
77
+ export { type ComputedAccessor as C, type EffectOptions as E, type MemoOptions as M, type SignalAccessor as S, __resetReactiveState as _, type SignalOptions as a, createSelector as c, effectScope as e, signal as s };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fictjs/runtime",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Fict reactive runtime",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -70,7 +70,7 @@
70
70
  "author": "unadlib",
71
71
  "license": "MIT",
72
72
  "scripts": {
73
- "build": "tsup",
73
+ "build": "pnpm run clean && tsup",
74
74
  "dev": "tsup --watch",
75
75
  "test": "vitest run",
76
76
  "test:stress": "FICT_RUNTIME_STRESS=1 vitest run test/runtime-stability.stress.test.ts",
package/src/binding.ts CHANGED
@@ -302,8 +302,17 @@ export function callEventHandler(
302
302
  * createTextBinding(() => $count())
303
303
  * ```
304
304
  */
305
- export function createTextBinding(value: MaybeReactive<unknown>): Text {
306
- const text = document.createTextNode('')
305
+ export function createTextBinding(
306
+ value: MaybeReactive<unknown>,
307
+ owner?: Document | Node | null,
308
+ ): Text {
309
+ const textOwnerDocument =
310
+ owner && 'nodeType' in owner
311
+ ? owner.nodeType === 9
312
+ ? (owner as Document)
313
+ : ((owner as Node).ownerDocument ?? document)
314
+ : document
315
+ const text = textOwnerDocument.createTextNode('')
307
316
 
308
317
  if (isReactive(value)) {
309
318
  // Reactive: create effect to update text when value changes
@@ -706,6 +715,7 @@ export function insert(
706
715
  createElementFn?: CreateElementFn,
707
716
  ): Cleanup {
708
717
  const hostRoot = getCurrentRoot()
718
+ const parentOwnerDocument = parent.ownerDocument ?? document
709
719
  let marker: Node
710
720
  let ownsMarker = false
711
721
  let createFn: CreateElementFn | undefined = createElementFn
@@ -714,11 +724,12 @@ export function insert(
714
724
  marker = markerOrCreateElement
715
725
  createFn = createElementFn
716
726
  } else {
717
- marker = document.createComment('fict:insert')
727
+ marker = parentOwnerDocument.createComment('fict:insert')
718
728
  parent.appendChild(marker)
719
729
  createFn = markerOrCreateElement as CreateElementFn | undefined
720
730
  ownsMarker = true
721
731
  }
732
+ const markerOwnerDocument = marker.ownerDocument ?? parentOwnerDocument
722
733
 
723
734
  let currentNodes: Node[] = []
724
735
  let currentText: Text | null = null
@@ -733,7 +744,7 @@ export function insert(
733
744
 
734
745
  const setTextNode = (textValue: string, shouldInsert: boolean, parentNode: ParentNode & Node) => {
735
746
  if (!currentText) {
736
- currentText = document.createTextNode(textValue)
747
+ currentText = (parentNode.ownerDocument ?? markerOwnerDocument).createTextNode(textValue)
737
748
  } else if (currentText.data !== textValue) {
738
749
  currentText.data = textValue
739
750
  }
@@ -789,6 +800,7 @@ export function insert(
789
800
  let handledError = false
790
801
  try {
791
802
  let newNode: Node | Node[]
803
+ const ownerDocument = parentNode?.ownerDocument ?? markerOwnerDocument
792
804
 
793
805
  if (value instanceof Node) {
794
806
  newNode = value
@@ -799,18 +811,18 @@ export function insert(
799
811
  if (createFn) {
800
812
  const mapped: Node[] = []
801
813
  for (const item of value) {
802
- mapped.push(...toNodeArray(createFn(item as any)))
814
+ mapped.push(...toNodeArray(createFn(item as any), ownerDocument))
803
815
  }
804
816
  newNode = mapped
805
817
  } else {
806
- newNode = document.createTextNode(String(value))
818
+ newNode = ownerDocument.createTextNode(String(value))
807
819
  }
808
820
  }
809
821
  } else {
810
- newNode = createFn ? createFn(value) : document.createTextNode(String(value))
822
+ newNode = createFn ? createFn(value) : ownerDocument.createTextNode(String(value))
811
823
  }
812
824
 
813
- nodes = toNodeArray(newNode)
825
+ nodes = toNodeArray(newNode, ownerDocument)
814
826
  if (root.suspended) {
815
827
  handledError = true
816
828
  destroyRoot(root)
@@ -867,6 +879,7 @@ export function insertBetween(
867
879
  createElementFn?: CreateElementFn,
868
880
  ): Cleanup {
869
881
  const hostRoot = getCurrentRoot()
882
+ const markerOwnerDocument = start.ownerDocument ?? end.ownerDocument ?? document
870
883
  let currentNodes: Node[] = []
871
884
  let currentText: Text | null = null
872
885
  let currentRoot: RootContext | null = null
@@ -890,8 +903,9 @@ export function insertBetween(
890
903
  }
891
904
 
892
905
  const setTextNode = (textValue: string, shouldInsert: boolean) => {
906
+ const parentNode = start.parentNode as (ParentNode & Node) | null
893
907
  if (!currentText) {
894
- currentText = document.createTextNode(textValue)
908
+ currentText = (parentNode?.ownerDocument ?? markerOwnerDocument).createTextNode(textValue)
895
909
  } else if (currentText.data !== textValue) {
896
910
  currentText.data = textValue
897
911
  }
@@ -906,7 +920,6 @@ export function insertBetween(
906
920
  }
907
921
 
908
922
  clearCurrentNodes()
909
- const parentNode = start.parentNode as (ParentNode & Node) | null
910
923
  if (parentNode) {
911
924
  insertNodesBefore(parentNode, [currentText], end)
912
925
  currentNodes = [currentText]
@@ -959,6 +972,7 @@ export function insertBetween(
959
972
  let handledError = false
960
973
  try {
961
974
  let newNode: Node | Node[] = undefined as unknown as Node | Node[]
975
+ const ownerDocument = parentNode?.ownerDocument ?? markerOwnerDocument
962
976
  const createValue = () => {
963
977
  if (value instanceof Node) {
964
978
  return value
@@ -970,24 +984,31 @@ export function insertBetween(
970
984
  if (createElementFn) {
971
985
  const mapped: Node[] = []
972
986
  for (const item of value) {
973
- mapped.push(...toNodeArray(createElementFn(item as any)))
987
+ mapped.push(...toNodeArray(createElementFn(item as any), ownerDocument))
974
988
  }
975
989
  return mapped
976
990
  }
977
- return document.createTextNode(String(value))
991
+ return ownerDocument.createTextNode(String(value))
978
992
  }
979
- return createElementFn ? createElementFn(value) : document.createTextNode(String(value))
993
+ return createElementFn
994
+ ? createElementFn(value)
995
+ : ownerDocument.createTextNode(String(value))
980
996
  }
981
997
 
982
998
  if (initialHydrating && isHydratingActive() && parentNode) {
983
- withHydrationRange(start.nextSibling, end, parentNode.ownerDocument ?? document, () => {
984
- newNode = createValue()
985
- })
999
+ withHydrationRange(
1000
+ start.nextSibling,
1001
+ end,
1002
+ parentNode.ownerDocument ?? markerOwnerDocument,
1003
+ () => {
1004
+ newNode = createValue()
1005
+ },
1006
+ )
986
1007
  } else {
987
1008
  newNode = createValue()
988
1009
  }
989
1010
 
990
- nodes = toNodeArray(newNode)
1011
+ nodes = toNodeArray(newNode, ownerDocument)
991
1012
  if (root.suspended) {
992
1013
  handledError = true
993
1014
  destroyRoot(root)
@@ -1048,7 +1069,7 @@ export function createChildBinding(
1048
1069
  getValue: () => FictNode,
1049
1070
  createElementFn: CreateElementFn,
1050
1071
  ): BindingHandle {
1051
- const marker = document.createComment('fict:child')
1072
+ const marker = (parent.ownerDocument ?? document).createComment('fict:child')
1052
1073
  parent.appendChild(marker)
1053
1074
  const hostRoot = getCurrentRoot()
1054
1075
 
@@ -1066,7 +1087,7 @@ export function createChildBinding(
1066
1087
  }
1067
1088
 
1068
1089
  const output = createElementFn(value)
1069
- nodes = toNodeArray(output)
1090
+ nodes = toNodeArray(output, marker.ownerDocument ?? parent.ownerDocument ?? document)
1070
1091
  const parentNode = marker.parentNode as (ParentNode & Node) | null
1071
1092
  if (parentNode) {
1072
1093
  insertNodesBefore(parentNode, nodes, marker)
@@ -1244,7 +1265,7 @@ function globalEventHandler(e: Event): void {
1244
1265
  Object.defineProperty(e, 'currentTarget', {
1245
1266
  configurable: true,
1246
1267
  get() {
1247
- return node || document
1268
+ return node || oriCurrentTarget || asNode(oriTarget)?.ownerDocument || document
1248
1269
  },
1249
1270
  })
1250
1271
 
@@ -1756,14 +1777,21 @@ export function createConditional(
1756
1777
  options?: ConditionalBindingOptions,
1757
1778
  ): BindingHandle {
1758
1779
  const trackBranchReads = options?.trackBranchReads === true
1780
+ const hostRoot = getCurrentRoot()
1759
1781
  const useProvided = !!(startOverride && endOverride)
1760
- const startMarker = useProvided ? startOverride! : document.createComment('fict:cond:start')
1761
- const endMarker = useProvided ? endOverride! : document.createComment('fict:cond:end')
1762
- const fragment = useProvided ? startMarker : document.createDocumentFragment()
1782
+ const markerOwnerDocument =
1783
+ startOverride?.ownerDocument ??
1784
+ endOverride?.ownerDocument ??
1785
+ hostRoot?.ownerDocument ??
1786
+ document
1787
+ const startMarker = useProvided
1788
+ ? startOverride!
1789
+ : markerOwnerDocument.createComment('fict:cond:start')
1790
+ const endMarker = useProvided ? endOverride! : markerOwnerDocument.createComment('fict:cond:end')
1791
+ const fragment = useProvided ? startMarker : markerOwnerDocument.createDocumentFragment()
1763
1792
  if (!useProvided) {
1764
1793
  ;(fragment as DocumentFragment).append(startMarker, endMarker)
1765
1794
  }
1766
- const hostRoot = getCurrentRoot()
1767
1795
 
1768
1796
  let currentNodes: Node[] = []
1769
1797
  let currentRoot: RootContext | null = null
@@ -1815,7 +1843,7 @@ export function createConditional(
1815
1843
  withHydrationRange(
1816
1844
  startMarker.nextSibling,
1817
1845
  endMarker,
1818
- parent.ownerDocument ?? document,
1846
+ parent.ownerDocument ?? markerOwnerDocument,
1819
1847
  () => {
1820
1848
  const output = trackBranchReads ? render() : untrack(render)
1821
1849
  if (output == null || output === false) {
@@ -1921,7 +1949,7 @@ export function createConditional(
1921
1949
  return
1922
1950
  }
1923
1951
  const el = createElementFn(scratchOutput)
1924
- const nodes = toNodeArray(el)
1952
+ const nodes = toNodeArray(el, parent.ownerDocument ?? markerOwnerDocument)
1925
1953
  insertNodesBefore(parent, nodes, endMarker)
1926
1954
  currentNodes = nodes
1927
1955
  } catch (err) {
@@ -1974,7 +2002,7 @@ export function createConditional(
1974
2002
  return
1975
2003
  }
1976
2004
  const el = createElementFn(output)
1977
- const nodes = toNodeArray(el)
2005
+ const nodes = toNodeArray(el, parent.ownerDocument ?? markerOwnerDocument)
1978
2006
  insertNodesBefore(parent, nodes, endMarker)
1979
2007
  currentNodes = nodes
1980
2008
  } catch (err) {
@@ -2071,7 +2099,8 @@ export function createPortal(
2071
2099
  // This is needed because createRenderEffect will push/pop its own root context
2072
2100
  const parentRoot = getCurrentRoot()
2073
2101
 
2074
- const marker = document.createComment('fict:portal')
2102
+ const markerOwnerDocument = container.ownerDocument ?? document
2103
+ const marker = markerOwnerDocument.createComment('fict:portal')
2075
2104
  container.appendChild(marker)
2076
2105
 
2077
2106
  let currentNodes: Node[] = []
@@ -2090,13 +2119,14 @@ export function createPortal(
2090
2119
 
2091
2120
  // Create new content
2092
2121
  const root = createRootContext(parentRoot)
2122
+ root.ownerDocument = container.ownerDocument ?? parentRoot?.ownerDocument ?? document
2093
2123
  const prev = pushRoot(root)
2094
2124
  let handledError = false
2095
2125
  try {
2096
2126
  const output = render()
2097
2127
  if (output != null && output !== false) {
2098
2128
  const el = createElementFn(output)
2099
- const nodes = toNodeArray(el)
2129
+ const nodes = toNodeArray(el, markerOwnerDocument)
2100
2130
  if (marker.parentNode) {
2101
2131
  insertNodesBefore(marker.parentNode as ParentNode & Node, nodes, marker)
2102
2132
  }
package/src/context.ts CHANGED
@@ -174,8 +174,9 @@ export function createContext<T>(defaultValue: T): Context<T> {
174
174
  contextMap.set(id, props.value)
175
175
 
176
176
  // Create DOM structure
177
- const fragment = document.createDocumentFragment()
178
- const marker = document.createComment('fict:ctx')
177
+ const markerOwnerDocument = providerRoot.ownerDocument ?? hostRoot?.ownerDocument ?? document
178
+ const fragment = markerOwnerDocument.createDocumentFragment()
179
+ const marker = markerOwnerDocument.createComment('fict:ctx')
179
180
  fragment.appendChild(marker)
180
181
 
181
182
  let cleanup: (() => void) | undefined
@@ -200,7 +201,7 @@ export function createContext<T>(defaultValue: T): Context<T> {
200
201
  let nodes: Node[] = []
201
202
  try {
202
203
  const output = createElement(children)
203
- nodes = toNodeArray(output)
204
+ nodes = toNodeArray(output, markerOwnerDocument)
204
205
  const parentNode = marker.parentNode as (ParentNode & Node) | null
205
206
  if (parentNode) {
206
207
  insertNodesBefore(parentNode, nodes, marker)
package/src/devtools.ts CHANGED
@@ -5,18 +5,35 @@ export interface FictDevtoolsHook {
5
5
  options?: { name?: string; source?: string; ownerId?: number },
6
6
  ) => void
7
7
  updateSignal: (id: number, value: unknown) => void
8
+ disposeSignal?: (id: number) => void
8
9
  registerComputed: (
9
10
  id: number,
10
11
  value: unknown,
11
- options?: { name?: string; source?: string; ownerId?: number; hasValue?: boolean },
12
+ options?: {
13
+ name?: string
14
+ source?: string
15
+ ownerId?: number
16
+ hasValue?: boolean
17
+ internal?: boolean
18
+ },
12
19
  ) => void
13
20
  updateComputed: (id: number, value: unknown) => void
21
+ disposeComputed?: (id: number) => void
14
22
  registerEffect: (id: number, options?: { ownerId?: number; source?: string }) => void
15
- effectRun: (id: number) => void
23
+ effectRun: (id: number, duration?: number) => void
24
+ effectCleanup?: (id: number) => void
25
+ disposeEffect?: (id: number) => void
16
26
  /** Track a dependency relationship between subscriber and dependency */
17
27
  trackDependency?: (subscriberId: number, dependencyId: number) => void
18
28
  /** Remove a dependency relationship when unlinked */
19
29
  untrackDependency?: (subscriberId: number, dependencyId: number) => void
30
+ registerRoot?: (id: number, name?: string) => void
31
+ disposeRoot?: (id: number) => void
32
+ rootSuspend?: (id: number, suspended: boolean) => void
33
+ batchStart?: () => void
34
+ batchEnd?: () => void
35
+ flushStart?: () => void
36
+ flushEnd?: () => void
20
37
  cycleDetected?: (payload: { reason: string; detail?: Record<string, unknown> }) => void
21
38
 
22
39
  // Component lifecycle