kiru 0.44.4

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 (70) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +5 -0
  3. package/package.json +81 -0
  4. package/src/appContext.ts +186 -0
  5. package/src/cloneVNode.ts +14 -0
  6. package/src/constants.ts +146 -0
  7. package/src/context.ts +56 -0
  8. package/src/dom.ts +712 -0
  9. package/src/element.ts +54 -0
  10. package/src/env.ts +6 -0
  11. package/src/error.ts +85 -0
  12. package/src/flags.ts +15 -0
  13. package/src/form/index.ts +662 -0
  14. package/src/form/types.ts +261 -0
  15. package/src/form/utils.ts +19 -0
  16. package/src/generateId.ts +19 -0
  17. package/src/globalContext.ts +161 -0
  18. package/src/globals.ts +21 -0
  19. package/src/hmr.ts +178 -0
  20. package/src/hooks/index.ts +14 -0
  21. package/src/hooks/useAsync.ts +136 -0
  22. package/src/hooks/useCallback.ts +31 -0
  23. package/src/hooks/useContext.ts +79 -0
  24. package/src/hooks/useEffect.ts +44 -0
  25. package/src/hooks/useEffectEvent.ts +24 -0
  26. package/src/hooks/useId.ts +42 -0
  27. package/src/hooks/useLayoutEffect.ts +47 -0
  28. package/src/hooks/useMemo.ts +33 -0
  29. package/src/hooks/useReducer.ts +50 -0
  30. package/src/hooks/useRef.ts +40 -0
  31. package/src/hooks/useState.ts +62 -0
  32. package/src/hooks/useSyncExternalStore.ts +59 -0
  33. package/src/hooks/useViewTransition.ts +26 -0
  34. package/src/hooks/utils.ts +259 -0
  35. package/src/hydration.ts +67 -0
  36. package/src/index.ts +61 -0
  37. package/src/jsx.ts +11 -0
  38. package/src/lazy.ts +238 -0
  39. package/src/memo.ts +48 -0
  40. package/src/portal.ts +43 -0
  41. package/src/profiling.ts +105 -0
  42. package/src/props.ts +36 -0
  43. package/src/reconciler.ts +531 -0
  44. package/src/renderToString.ts +91 -0
  45. package/src/router/index.ts +2 -0
  46. package/src/router/route.ts +51 -0
  47. package/src/router/router.ts +275 -0
  48. package/src/router/routerUtils.ts +49 -0
  49. package/src/scheduler.ts +522 -0
  50. package/src/signals/base.ts +237 -0
  51. package/src/signals/computed.ts +139 -0
  52. package/src/signals/effect.ts +60 -0
  53. package/src/signals/globals.ts +11 -0
  54. package/src/signals/index.ts +12 -0
  55. package/src/signals/jsx.ts +45 -0
  56. package/src/signals/types.ts +10 -0
  57. package/src/signals/utils.ts +12 -0
  58. package/src/signals/watch.ts +151 -0
  59. package/src/ssr/client.ts +29 -0
  60. package/src/ssr/hydrationBoundary.ts +63 -0
  61. package/src/ssr/index.ts +1 -0
  62. package/src/ssr/server.ts +124 -0
  63. package/src/store.ts +241 -0
  64. package/src/swr.ts +360 -0
  65. package/src/transition.ts +80 -0
  66. package/src/types.dom.ts +1250 -0
  67. package/src/types.ts +209 -0
  68. package/src/types.utils.ts +39 -0
  69. package/src/utils.ts +581 -0
  70. package/src/warning.ts +9 -0
package/src/portal.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { __DEV__ } from "./env.js"
2
+ import { KaiokenError } from "./error.js"
3
+ import { renderMode } from "./globals.js"
4
+ import { useVNode } from "./hooks/utils.js"
5
+ import { getVNodeAppContext } from "./utils.js"
6
+
7
+ export { Portal, isPortal }
8
+
9
+ type PortalProps = {
10
+ children?: JSX.Children
11
+ container: HTMLElement | (() => HTMLElement)
12
+ }
13
+
14
+ function Portal({ children, container }: PortalProps) {
15
+ const vNode = useVNode()
16
+ switch (renderMode.current) {
17
+ case "dom":
18
+ vNode.dom = typeof container === "function" ? container() : container
19
+ if (!(vNode.dom instanceof HTMLElement)) {
20
+ if (__DEV__) {
21
+ throw new KaiokenError({
22
+ message: `Invalid portal container, expected HTMLElement, got ${vNode.dom}`,
23
+ vNode: vNode,
24
+ })
25
+ }
26
+ return null
27
+ }
28
+ return children
29
+ case "hydrate":
30
+ const ctx = getVNodeAppContext(vNode)
31
+ ctx.scheduler?.nextIdle(() => ctx.requestUpdate(vNode))
32
+ return null
33
+ case "stream":
34
+ case "string":
35
+ return null
36
+ }
37
+ }
38
+
39
+ function isPortal(
40
+ node: Kaioken.VNode
41
+ ): node is Kaioken.VNode & { type: typeof Portal } {
42
+ return node.type === Portal
43
+ }
@@ -0,0 +1,105 @@
1
+ import type { AppContext } from "./appContext"
2
+
3
+ const MAX_TICKS = 100
4
+
5
+ const ProfilingEvents = [
6
+ "updateNode",
7
+ "createNode",
8
+ "removeNode",
9
+ "update",
10
+ "updateDirtied",
11
+ "signalTextUpdate",
12
+ "signalAttrUpdate",
13
+ ] as const
14
+
15
+ export type ProfilingEvent = (typeof ProfilingEvents)[number]
16
+
17
+ type TickTS = {
18
+ start: number
19
+ end: number
20
+ }
21
+
22
+ type ProfilingEventListener = (app: AppContext) => void
23
+
24
+ export function createProfilingContext() {
25
+ const eventListeners = new Map<ProfilingEvent, Set<ProfilingEventListener>>()
26
+ const appStats: Map<
27
+ AppContext,
28
+ {
29
+ timestamps: TickTS[]
30
+ mountDuration: number
31
+ totalTicks: number
32
+ }
33
+ > = new Map()
34
+ return {
35
+ appStats,
36
+ emit: (event: ProfilingEvent, app: AppContext) => {
37
+ eventListeners.get(event)?.forEach((listener) => listener(app))
38
+ },
39
+ addEventListener: (
40
+ event: ProfilingEvent,
41
+ listener: ProfilingEventListener
42
+ ) => {
43
+ if (!eventListeners.has(event)) {
44
+ eventListeners.set(event, new Set())
45
+ }
46
+ eventListeners.get(event)!.add(listener)
47
+ },
48
+ removeEventListener: (
49
+ event: ProfilingEvent,
50
+ listener: ProfilingEventListener
51
+ ) => {
52
+ if (!eventListeners.has(event)) return
53
+ eventListeners.get(event)!.delete(listener)
54
+ },
55
+ mountDuration: (app: AppContext) => {
56
+ const stats = appStats.get(app)
57
+ if (!stats) return 0
58
+ return stats.mountDuration
59
+ },
60
+ totalTicks: (app: AppContext) => {
61
+ const stats = appStats.get(app)
62
+ if (!stats) return 0
63
+ return stats.totalTicks
64
+ },
65
+ lastTickDuration: (app: AppContext) => {
66
+ const stats = appStats.get(app)
67
+ if (!stats) return 0
68
+ const last = stats.timestamps[stats.timestamps.length - 1]
69
+ return last.end - last.start
70
+ },
71
+ averageTickDuration: (app: AppContext) => {
72
+ const stats = appStats.get(app)
73
+ if (!stats) return 0
74
+ const completeTicks = stats.timestamps.filter((ts) => ts.end !== Infinity)
75
+ return (
76
+ completeTicks.reduce((a, b) => a + (b.end - b.start), 0) /
77
+ completeTicks.length
78
+ )
79
+ },
80
+ beginTick: (app: AppContext) => {
81
+ if (!appStats.has(app)) {
82
+ appStats.set(app, {
83
+ mountDuration: Infinity,
84
+ timestamps: [],
85
+ totalTicks: 0,
86
+ })
87
+ }
88
+ const stats = appStats.get(app)!
89
+ stats.totalTicks++
90
+ stats.timestamps.push({ start: performance.now(), end: Infinity })
91
+ },
92
+ endTick: (app: AppContext) => {
93
+ if (!appStats.has(app)) return
94
+ const stats = appStats.get(app)!
95
+
96
+ const last = stats.timestamps[stats.timestamps.length - 1]
97
+ last.end = performance.now()
98
+
99
+ if (stats.mountDuration === Infinity) {
100
+ stats.mountDuration = last.end - last.start
101
+ }
102
+ if (stats.timestamps.length > MAX_TICKS) stats.timestamps.shift()
103
+ },
104
+ }
105
+ }
package/src/props.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { KaiokenError } from "./error.js"
2
+ import { Signal } from "./signals/base.js"
3
+
4
+ export function assertValidElementProps(vNode: Kaioken.VNode) {
5
+ if ("children" in vNode.props && vNode.props.innerHTML) {
6
+ throw new KaiokenError({
7
+ message: "Cannot use both children and innerHTML on an element",
8
+ vNode,
9
+ })
10
+ }
11
+
12
+ for (const key in vNode.props) {
13
+ if ("bind:" + key in vNode.props) {
14
+ throw new KaiokenError({
15
+ message: `Cannot use both bind:${key} and ${key} on an element`,
16
+ vNode,
17
+ })
18
+ }
19
+ }
20
+ }
21
+
22
+ export function isValidElementKeyProp(
23
+ thing: unknown
24
+ ): thing is string | number {
25
+ return typeof thing === "string" || typeof thing === "number"
26
+ }
27
+
28
+ export function isValidElementRefProp(
29
+ thing: unknown
30
+ ): thing is Kaioken.Ref<any> {
31
+ return (
32
+ typeof thing === "function" ||
33
+ (typeof thing === "object" && !!thing && "current" in thing) ||
34
+ Signal.isSignal(thing)
35
+ )
36
+ }
@@ -0,0 +1,531 @@
1
+ import { FLAG, $FRAGMENT } from "./constants.js"
2
+ import { isVNode, latest } from "./utils.js"
3
+ import { Signal } from "./signals/base.js"
4
+ import { __DEV__ } from "./env.js"
5
+ import { createElement, Fragment } from "./element.js"
6
+ import { flags } from "./flags.js"
7
+ import { ctx } from "./globals.js"
8
+
9
+ type VNode = Kaioken.VNode
10
+
11
+ export function reconcileChildren(parent: VNode, children: unknown) {
12
+ if (Array.isArray(children)) {
13
+ if (__DEV__) {
14
+ // array children are 'tagged' during parent reconciliation pass
15
+ if ($LIST_CHILD in children) {
16
+ checkForMissingKeys(parent, children)
17
+ }
18
+ checkForDuplicateKeys(parent, children)
19
+ }
20
+ return reconcileChildrenArray(parent, children)
21
+ }
22
+ return reconcileSingleChild(parent, children)
23
+ }
24
+
25
+ function reconcileSingleChild(parent: VNode, child: unknown) {
26
+ const deletions: VNode[] = (parent.deletions = [])
27
+ const oldChild = parent.child
28
+ if (oldChild === null) {
29
+ return createChild(parent, child)
30
+ }
31
+ const oldSibling = oldChild.sibling
32
+ const newNode = updateSlot(parent, oldChild, child)
33
+ if (newNode !== null) {
34
+ if (oldChild && oldChild !== newNode && !newNode.prev) {
35
+ deleteRemainingChildren(parent, oldChild)
36
+ } else if (oldSibling) {
37
+ deleteRemainingChildren(parent, oldSibling)
38
+ }
39
+ return newNode
40
+ }
41
+ {
42
+ // handle keyed children array -> keyed child
43
+ const existingChildren = mapRemainingChildren(oldChild)
44
+ const newNode = updateFromMap(existingChildren, parent, 0, child)
45
+ if (newNode !== null) {
46
+ if (newNode.prev !== null) {
47
+ // node persisted, remove it from the list so it doesn't get deleted
48
+ existingChildren.delete(
49
+ newNode.prev.props.key === undefined
50
+ ? newNode.prev.index
51
+ : newNode.prev.props.key
52
+ )
53
+ }
54
+ placeChild(newNode, 0, 0)
55
+ }
56
+ existingChildren.forEach((child) => deletions.push(child))
57
+ return newNode
58
+ }
59
+ }
60
+
61
+ function reconcileChildrenArray(parent: VNode, children: unknown[]) {
62
+ const deletions: VNode[] = (parent.deletions = [])
63
+ let resultingChild: VNode | null = null
64
+ let prevNewChild: VNode | null = null
65
+
66
+ let oldChild = parent.child
67
+ let nextOldChild = null
68
+ let lastPlacedIndex = 0
69
+ let newIdx = 0
70
+
71
+ for (; !!oldChild && newIdx < children.length; newIdx++) {
72
+ if (oldChild.index > newIdx) {
73
+ nextOldChild = oldChild
74
+ oldChild = null
75
+ } else {
76
+ nextOldChild = oldChild.sibling
77
+ }
78
+ const newChild = updateSlot(parent, oldChild, children[newIdx])
79
+ if (newChild === null) {
80
+ if (oldChild === null) {
81
+ oldChild = nextOldChild
82
+ }
83
+ break
84
+ }
85
+ if (oldChild && !newChild.prev) {
86
+ deletions.push(oldChild)
87
+ }
88
+ lastPlacedIndex = placeChild(newChild, lastPlacedIndex, newIdx)
89
+ if (prevNewChild === null) {
90
+ resultingChild = newChild
91
+ } else {
92
+ prevNewChild.sibling = newChild
93
+ }
94
+ prevNewChild = newChild
95
+ oldChild = nextOldChild
96
+ }
97
+
98
+ // matched all children?
99
+ if (newIdx === children.length) {
100
+ deleteRemainingChildren(parent, oldChild)
101
+ return resultingChild
102
+ }
103
+
104
+ // just some good ol' insertions, baby
105
+ if (oldChild === null) {
106
+ for (; newIdx < children.length; newIdx++) {
107
+ const newNode = createChild(parent, children[newIdx])
108
+ if (newNode === null) continue
109
+ lastPlacedIndex = placeChild(newNode, lastPlacedIndex, newIdx)
110
+ if (prevNewChild === null) {
111
+ resultingChild = newNode
112
+ } else {
113
+ prevNewChild.sibling = newNode
114
+ }
115
+ prevNewChild = newNode
116
+ }
117
+ return resultingChild
118
+ }
119
+
120
+ // deal with mismatched keys / unmatched children
121
+ const existingChildren = mapRemainingChildren(oldChild)
122
+
123
+ for (; newIdx < children.length; newIdx++) {
124
+ const newNode = updateFromMap(
125
+ existingChildren,
126
+ parent,
127
+ newIdx,
128
+ children[newIdx]
129
+ )
130
+ if (newNode !== null) {
131
+ if (newNode.prev !== null) {
132
+ // node persisted, remove it from the list so it doesn't get deleted
133
+ existingChildren.delete(
134
+ newNode.prev.props.key === undefined
135
+ ? newNode.prev.index
136
+ : newNode.prev.props.key
137
+ )
138
+ }
139
+ lastPlacedIndex = placeChild(newNode, lastPlacedIndex, newIdx)
140
+ if (prevNewChild === null) {
141
+ resultingChild = newNode
142
+ } else {
143
+ prevNewChild.sibling = newNode
144
+ }
145
+ prevNewChild = newNode
146
+ }
147
+ }
148
+
149
+ existingChildren.forEach((child) => deletions.push(child))
150
+ return resultingChild
151
+ }
152
+
153
+ function updateSlot(parent: VNode, oldChild: VNode | null, child: unknown) {
154
+ // Update the node if the keys match, otherwise return null.
155
+ const key = oldChild?.props.key
156
+ if (
157
+ (typeof child === "string" && child !== "") ||
158
+ typeof child === "number" ||
159
+ typeof child === "bigint"
160
+ ) {
161
+ if (key !== undefined) return null
162
+ if (
163
+ oldChild?.type === "#text" &&
164
+ Signal.isSignal(oldChild.props.nodeValue)
165
+ ) {
166
+ return null
167
+ }
168
+ return updateTextNode(parent, oldChild, "" + child)
169
+ }
170
+ if (Signal.isSignal(child)) {
171
+ if (!!oldChild && oldChild.props.nodeValue !== child) return null
172
+ return updateTextNode(parent, oldChild, child)
173
+ }
174
+ if (isVNode(child)) {
175
+ if (child.props.key !== key) return null
176
+ return updateNode(parent, oldChild, child)
177
+ }
178
+ if (Array.isArray(child)) {
179
+ if (key !== undefined) return null
180
+ if (__DEV__) {
181
+ markListChild(child)
182
+ }
183
+ return updateFragment(parent, oldChild, child)
184
+ }
185
+ return null
186
+ }
187
+
188
+ function updateTextNode(
189
+ parent: VNode,
190
+ oldChild: VNode | null,
191
+ content: string | Signal<JSX.PrimitiveChild>
192
+ ) {
193
+ if (oldChild === null || oldChild.type !== "#text") {
194
+ if (__DEV__) {
195
+ emitCreateNode()
196
+ }
197
+ const newChild = createElement("#text", { nodeValue: content })
198
+ setParent(newChild, parent)
199
+ return newChild
200
+ } else {
201
+ if (__DEV__) {
202
+ emitUpdateNode()
203
+ }
204
+ oldChild.props.nodeValue = content
205
+ oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
206
+ oldChild.sibling = null
207
+ return oldChild
208
+ }
209
+ }
210
+
211
+ function updateNode(parent: VNode, oldChild: VNode | null, newChild: VNode) {
212
+ let nodeType = newChild.type
213
+ if (__DEV__) {
214
+ if (typeof nodeType === "function") {
215
+ nodeType = latest(nodeType)
216
+ }
217
+ }
218
+ if (nodeType === $FRAGMENT) {
219
+ return updateFragment(
220
+ parent,
221
+ oldChild,
222
+ (newChild.props.children as VNode[]) || [],
223
+ newChild.props
224
+ )
225
+ }
226
+ if (oldChild?.type === nodeType) {
227
+ if (__DEV__) {
228
+ emitUpdateNode()
229
+ }
230
+ oldChild.index = 0
231
+ oldChild.props = newChild.props
232
+ oldChild.sibling = null
233
+ oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
234
+ oldChild.memoizedProps = newChild.memoizedProps
235
+ return oldChild
236
+ }
237
+ if (__DEV__) {
238
+ emitCreateNode()
239
+ }
240
+ const created = createElement(nodeType, newChild.props)
241
+ setParent(created, parent)
242
+ return created
243
+ }
244
+
245
+ function updateFragment(
246
+ parent: VNode,
247
+ oldChild: VNode | null,
248
+ children: unknown[],
249
+ newProps = {}
250
+ ) {
251
+ if (oldChild === null || oldChild.type !== $FRAGMENT) {
252
+ if (__DEV__) {
253
+ emitCreateNode()
254
+ }
255
+ const el = createElement($FRAGMENT, { children, ...newProps })
256
+ setParent(el, parent)
257
+ return el
258
+ }
259
+ if (__DEV__) {
260
+ emitUpdateNode()
261
+ }
262
+ oldChild.props = { ...oldChild.props, ...newProps, children }
263
+ oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
264
+ oldChild.sibling = null
265
+ return oldChild
266
+ }
267
+
268
+ function createChild(parent: VNode, child: unknown): VNode | null {
269
+ if (
270
+ (typeof child === "string" && child !== "") ||
271
+ typeof child === "number" ||
272
+ typeof child === "bigint"
273
+ ) {
274
+ if (__DEV__) {
275
+ emitCreateNode()
276
+ }
277
+ const el = createElement("#text", {
278
+ nodeValue: "" + child,
279
+ })
280
+ setParent(el, parent)
281
+ return el
282
+ }
283
+
284
+ if (Signal.isSignal(child)) {
285
+ if (__DEV__) {
286
+ emitCreateNode()
287
+ }
288
+ const el = createElement("#text", {
289
+ nodeValue: child,
290
+ })
291
+ setParent(el, parent)
292
+ return el
293
+ }
294
+
295
+ if (isVNode(child)) {
296
+ if (__DEV__) {
297
+ emitCreateNode()
298
+ }
299
+ const newNode = createElement(child.type, child.props)
300
+ setParent(newNode, parent)
301
+ newNode.flags = flags.set(newNode.flags, FLAG.PLACEMENT)
302
+ return newNode
303
+ }
304
+
305
+ if (Array.isArray(child)) {
306
+ if (__DEV__) {
307
+ emitCreateNode()
308
+ markListChild(child)
309
+ }
310
+ const el = Fragment({ children: child })
311
+ setParent(el, parent)
312
+ return el
313
+ }
314
+
315
+ return null
316
+ }
317
+
318
+ function placeChild(
319
+ child: VNode | null,
320
+ lastPlacedIndex: number,
321
+ newIndex: number
322
+ ): number {
323
+ if (child === null) return lastPlacedIndex
324
+ child.index = newIndex
325
+ if (child.prev !== null) {
326
+ const oldIndex = child.prev.index
327
+ if (oldIndex < lastPlacedIndex) {
328
+ child.flags = flags.set(child.flags, FLAG.PLACEMENT)
329
+ return lastPlacedIndex
330
+ } else {
331
+ return oldIndex
332
+ }
333
+ } else {
334
+ child.flags = flags.set(child.flags, FLAG.PLACEMENT)
335
+ return lastPlacedIndex
336
+ }
337
+ }
338
+
339
+ function updateFromMap(
340
+ existingChildren: Map<JSX.ElementKey, VNode>,
341
+ parent: VNode,
342
+ index: number,
343
+ child: any
344
+ ): VNode | null {
345
+ const isSig = Signal.isSignal(child)
346
+ if (
347
+ isSig ||
348
+ (typeof child === "string" && child !== "") ||
349
+ typeof child === "number" ||
350
+ typeof child === "bigint"
351
+ ) {
352
+ const oldChild = existingChildren.get(index)
353
+ if (oldChild) {
354
+ if (oldChild.props.nodeValue === child) {
355
+ oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
356
+ oldChild.props.nodeValue = child
357
+ return oldChild
358
+ }
359
+ if (
360
+ oldChild.type === "#text" &&
361
+ Signal.isSignal(oldChild.props.nodeValue)
362
+ ) {
363
+ oldChild.cleanups?.["nodeValue"]?.()
364
+ }
365
+ }
366
+
367
+ if (__DEV__) {
368
+ emitCreateNode()
369
+ }
370
+ const newChild = createElement("#text", {
371
+ nodeValue: child,
372
+ })
373
+ setParent(newChild, parent)
374
+ newChild.flags = flags.set(newChild.flags, FLAG.PLACEMENT)
375
+ newChild.index = index
376
+ return newChild
377
+ }
378
+
379
+ if (isVNode(child)) {
380
+ const oldChild = existingChildren.get(
381
+ child.props.key === undefined ? index : child.props.key
382
+ )
383
+ if (oldChild?.type === child.type) {
384
+ if (__DEV__) {
385
+ emitUpdateNode()
386
+ }
387
+ oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
388
+ oldChild.props = child.props
389
+ oldChild.sibling = null
390
+ oldChild.index = index
391
+ return oldChild
392
+ } else {
393
+ if (__DEV__) {
394
+ emitCreateNode()
395
+ }
396
+ const newChild = createElement(child.type, child.props)
397
+ setParent(newChild, parent)
398
+ newChild.flags = flags.set(newChild.flags, FLAG.PLACEMENT)
399
+ newChild.index = index
400
+ return newChild
401
+ }
402
+ }
403
+
404
+ if (Array.isArray(child)) {
405
+ const oldChild = existingChildren.get(index)
406
+ if (__DEV__) {
407
+ markListChild(child)
408
+ }
409
+ if (oldChild) {
410
+ if (__DEV__) {
411
+ emitUpdateNode()
412
+ }
413
+ oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
414
+ oldChild.props.children = child
415
+ return oldChild
416
+ } else {
417
+ if (__DEV__) {
418
+ emitCreateNode()
419
+ }
420
+ const newChild = Fragment({ children: child })
421
+ setParent(newChild, parent)
422
+ newChild.flags = flags.set(newChild.flags, FLAG.PLACEMENT)
423
+ newChild.index = index
424
+ return newChild
425
+ }
426
+ }
427
+
428
+ return null
429
+ }
430
+
431
+ function setParent(child: Kaioken.VNode, parent: Kaioken.VNode) {
432
+ child.parent = parent
433
+ child.depth = parent.depth + 1
434
+ if (parent.isMemoized || flags.get(parent.flags, FLAG.HAS_MEMO_ANCESTOR)) {
435
+ child.flags = flags.set(child.flags, FLAG.HAS_MEMO_ANCESTOR)
436
+ }
437
+ }
438
+
439
+ function emitUpdateNode() {
440
+ if (!("window" in globalThis)) return
441
+ window.__kaioken?.profilingContext?.emit("updateNode", ctx.current)
442
+ }
443
+
444
+ function emitCreateNode() {
445
+ if (!("window" in globalThis)) return
446
+ window.__kaioken?.profilingContext?.emit("createNode", ctx.current)
447
+ }
448
+
449
+ const $LIST_CHILD = Symbol("kaioken:marked-list-child")
450
+ function markListChild(children: unknown[]) {
451
+ Object.assign(children, { [$LIST_CHILD]: true })
452
+ }
453
+
454
+ function mapRemainingChildren(child: VNode | null) {
455
+ const map: Map<JSX.ElementKey, VNode> = new Map()
456
+ while (child) {
457
+ map.set(
458
+ child.props.key === undefined ? child.index : child.props.key,
459
+ child
460
+ )
461
+ child = child.sibling
462
+ }
463
+ return map
464
+ }
465
+
466
+ function deleteRemainingChildren(parent: VNode, child: VNode | null) {
467
+ while (child) {
468
+ parent.deletions!.push(child)
469
+ child = child.sibling
470
+ }
471
+ }
472
+
473
+ function checkForDuplicateKeys(parent: VNode, children: unknown[]) {
474
+ const keys = new Set<string>()
475
+ let warned = false
476
+ for (const child of children) {
477
+ if (!isVNode(child)) continue
478
+ const key = child.props.key
479
+ if (typeof key === "string") {
480
+ if (!warned && keys.has(key)) {
481
+ const fn = getNearestParentFcTag(parent)
482
+ keyWarning(
483
+ `${fn} component produced a child in a list with a duplicate key prop: "${key}". Keys should be unique so that components maintain their identity across updates`
484
+ )
485
+ warned = true
486
+ }
487
+ keys.add(key)
488
+ }
489
+ }
490
+ }
491
+
492
+ function checkForMissingKeys(parent: VNode, children: unknown[]) {
493
+ let hasKey = false
494
+ let hasMissingKey = false
495
+ for (const child of children) {
496
+ if (!isVNode(child)) continue
497
+ const key = child.props.key
498
+ if (typeof key === "string") {
499
+ hasKey = true
500
+ } else {
501
+ hasMissingKey = true
502
+ }
503
+ }
504
+ if (hasMissingKey && hasKey) {
505
+ const fn = getNearestParentFcTag(parent)
506
+ keyWarning(
507
+ `${fn} component produced a child in a list without a valid key prop`
508
+ )
509
+ }
510
+ }
511
+
512
+ function keyWarning(str: string) {
513
+ const formatted = `[kaioken]: ${str}. See https://kaioken.dev/keys-warning for more information.`
514
+ console.error(formatted)
515
+ }
516
+
517
+ const parentFcTagLookups = new WeakMap<VNode, string>()
518
+ function getNearestParentFcTag(vNode: VNode) {
519
+ if (parentFcTagLookups.has(vNode)) {
520
+ return parentFcTagLookups.get(vNode)
521
+ }
522
+ let p: VNode | null = vNode.parent
523
+ let fn: (Function & { displayName?: string }) | undefined
524
+ while (!fn && p) {
525
+ if (typeof p.type === "function") fn = p.type
526
+ p = p.parent
527
+ }
528
+ const tag = `<${fn?.displayName || fn?.name || "Anonymous Function"} />`
529
+ parentFcTagLookups.set(vNode, tag)
530
+ return tag
531
+ }