kiru 0.52.3 → 0.53.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 (150) hide show
  1. package/dist/appContext.d.ts.map +1 -1
  2. package/dist/appContext.js +14 -11
  3. package/dist/appContext.js.map +1 -1
  4. package/dist/components/derive.d.ts +20 -0
  5. package/dist/components/derive.d.ts.map +1 -0
  6. package/dist/components/derive.js +57 -0
  7. package/dist/components/derive.js.map +1 -0
  8. package/dist/components/errorBoundary.d.ts +1 -1
  9. package/dist/components/errorBoundary.d.ts.map +1 -1
  10. package/dist/components/index.d.ts +1 -1
  11. package/dist/components/index.d.ts.map +1 -1
  12. package/dist/components/index.js +1 -1
  13. package/dist/components/index.js.map +1 -1
  14. package/dist/components/memo.d.ts +6 -3
  15. package/dist/components/memo.d.ts.map +1 -1
  16. package/dist/components/memo.js.map +1 -1
  17. package/dist/constants.d.ts +3 -3
  18. package/dist/constants.d.ts.map +1 -1
  19. package/dist/constants.js +3 -3
  20. package/dist/constants.js.map +1 -1
  21. package/dist/dom.d.ts.map +1 -1
  22. package/dist/dom.js +69 -66
  23. package/dist/dom.js.map +1 -1
  24. package/dist/element.d.ts +2 -2
  25. package/dist/element.d.ts.map +1 -1
  26. package/dist/element.js +20 -32
  27. package/dist/element.js.map +1 -1
  28. package/dist/globalContext.d.ts +6 -1
  29. package/dist/globalContext.d.ts.map +1 -1
  30. package/dist/globalContext.js +14 -2
  31. package/dist/globalContext.js.map +1 -1
  32. package/dist/hooks/usePromise.d.ts +4 -9
  33. package/dist/hooks/usePromise.d.ts.map +1 -1
  34. package/dist/hooks/usePromise.js +25 -28
  35. package/dist/hooks/usePromise.js.map +1 -1
  36. package/dist/hooks/useRef.d.ts +2 -2
  37. package/dist/hooks/useRef.d.ts.map +1 -1
  38. package/dist/jsx.d.ts +1 -1
  39. package/dist/jsx.d.ts.map +1 -1
  40. package/dist/reconciler.d.ts +1 -1
  41. package/dist/reconciler.d.ts.map +1 -1
  42. package/dist/reconciler.js +57 -117
  43. package/dist/reconciler.js.map +1 -1
  44. package/dist/recursiveRender.d.ts +4 -3
  45. package/dist/recursiveRender.d.ts.map +1 -1
  46. package/dist/recursiveRender.js +20 -19
  47. package/dist/recursiveRender.js.map +1 -1
  48. package/dist/renderToString.js +2 -2
  49. package/dist/renderToString.js.map +1 -1
  50. package/dist/router/fileRouterController.d.ts +1 -1
  51. package/dist/router/fileRouterController.d.ts.map +1 -1
  52. package/dist/router/fileRouterController.js +11 -9
  53. package/dist/router/fileRouterController.js.map +1 -1
  54. package/dist/router/index.d.ts +1 -1
  55. package/dist/router/index.d.ts.map +1 -1
  56. package/dist/router/scrollStack.d.ts +0 -1
  57. package/dist/router/scrollStack.d.ts.map +1 -1
  58. package/dist/router/scrollStack.js +0 -3
  59. package/dist/router/scrollStack.js.map +1 -1
  60. package/dist/router/utils/index.d.ts +1 -1
  61. package/dist/router/utils/index.d.ts.map +1 -1
  62. package/dist/signals/base.d.ts +1 -3
  63. package/dist/signals/base.d.ts.map +1 -1
  64. package/dist/signals/base.js +3 -3
  65. package/dist/signals/base.js.map +1 -1
  66. package/dist/signals/computed.d.ts +4 -2
  67. package/dist/signals/computed.d.ts.map +1 -1
  68. package/dist/signals/computed.js +29 -6
  69. package/dist/signals/computed.js.map +1 -1
  70. package/dist/signals/for.d.ts +10 -0
  71. package/dist/signals/for.d.ts.map +1 -0
  72. package/dist/signals/for.js +7 -0
  73. package/dist/signals/for.js.map +1 -0
  74. package/dist/signals/index.d.ts +1 -1
  75. package/dist/signals/index.js +1 -1
  76. package/dist/signals/types.d.ts +1 -1
  77. package/dist/signals/types.d.ts.map +1 -1
  78. package/dist/ssr/server.js +13 -13
  79. package/dist/ssr/server.js.map +1 -1
  80. package/dist/types.d.ts +10 -11
  81. package/dist/types.d.ts.map +1 -1
  82. package/dist/types.dom.d.ts +33 -32
  83. package/dist/types.dom.d.ts.map +1 -1
  84. package/dist/types.utils.d.ts +5 -1
  85. package/dist/types.utils.d.ts.map +1 -1
  86. package/dist/utils/format.d.ts +2 -2
  87. package/dist/utils/format.d.ts.map +1 -1
  88. package/dist/utils/format.js +4 -3
  89. package/dist/utils/format.js.map +1 -1
  90. package/dist/utils/index.d.ts +0 -1
  91. package/dist/utils/index.d.ts.map +1 -1
  92. package/dist/utils/index.js +0 -1
  93. package/dist/utils/index.js.map +1 -1
  94. package/dist/utils/promise.d.ts +16 -0
  95. package/dist/utils/promise.d.ts.map +1 -0
  96. package/dist/utils/promise.js +14 -0
  97. package/dist/utils/promise.js.map +1 -0
  98. package/dist/utils/runtime.d.ts +10 -1
  99. package/dist/utils/runtime.d.ts.map +1 -1
  100. package/dist/utils/runtime.js +25 -1
  101. package/dist/utils/runtime.js.map +1 -1
  102. package/dist/utils/vdom.d.ts +4 -4
  103. package/dist/utils/vdom.d.ts.map +1 -1
  104. package/dist/utils/vdom.js +18 -17
  105. package/dist/utils/vdom.js.map +1 -1
  106. package/dist/vNode.d.ts +4 -0
  107. package/dist/vNode.d.ts.map +1 -0
  108. package/dist/vNode.js +22 -0
  109. package/dist/vNode.js.map +1 -0
  110. package/package.json +1 -1
  111. package/src/appContext.ts +15 -13
  112. package/src/components/derive.ts +121 -0
  113. package/src/components/index.ts +1 -1
  114. package/src/components/memo.ts +3 -2
  115. package/src/constants.ts +4 -4
  116. package/src/dom.ts +71 -66
  117. package/src/element.ts +22 -35
  118. package/src/globalContext.ts +24 -3
  119. package/src/hooks/usePromise.ts +32 -41
  120. package/src/hooks/useRef.ts +2 -2
  121. package/src/reconciler.ts +87 -125
  122. package/src/recursiveRender.ts +25 -23
  123. package/src/renderToString.ts +3 -3
  124. package/src/router/fileRouterController.ts +19 -9
  125. package/src/router/scrollStack.ts +23 -26
  126. package/src/signals/base.ts +3 -3
  127. package/src/signals/computed.ts +43 -6
  128. package/src/signals/for.ts +25 -0
  129. package/src/signals/index.ts +1 -1
  130. package/src/signals/types.ts +5 -1
  131. package/src/ssr/server.ts +15 -15
  132. package/src/types.dom.ts +40 -40
  133. package/src/types.ts +11 -11
  134. package/src/types.utils.ts +11 -1
  135. package/src/utils/format.ts +7 -4
  136. package/src/utils/index.ts +0 -2
  137. package/src/utils/promise.ts +26 -0
  138. package/src/utils/runtime.ts +29 -1
  139. package/src/utils/vdom.ts +20 -23
  140. package/src/vNode.ts +30 -0
  141. package/dist/components/suspense.d.ts +0 -24
  142. package/dist/components/suspense.d.ts.map +0 -1
  143. package/dist/components/suspense.js +0 -36
  144. package/dist/components/suspense.js.map +0 -1
  145. package/dist/signals/jsx.d.ts +0 -17
  146. package/dist/signals/jsx.d.ts.map +0 -1
  147. package/dist/signals/jsx.js +0 -11
  148. package/dist/signals/jsx.js.map +0 -1
  149. package/src/components/suspense.ts +0 -72
  150. package/src/signals/jsx.ts +0 -46
@@ -1,6 +1,6 @@
1
1
  import { __DEV__ } from "../env.js"
2
2
  import { $HMR_ACCEPT } from "../constants.js"
3
- import { useHook } from "../hooks/utils.js"
3
+ import { depsRequireChange, useHook } from "../hooks/utils.js"
4
4
  import { latest } from "../utils/index.js"
5
5
  import { effectQueue, signalSubsMap } from "./globals.js"
6
6
  import { executeWithTracking } from "./effect.js"
@@ -69,6 +69,16 @@ export class ComputedSignal<T> extends Signal<T> {
69
69
  Signal.dispose(signal)
70
70
  }
71
71
 
72
+ static updateGetter<T>(signal: ComputedSignal<T>, getter: (prev?: T) => T) {
73
+ const $computed = latest(signal)
74
+ $computed.$getter = getter
75
+ $computed.$isDirty = true
76
+
77
+ ComputedSignal.run($computed)
78
+ if (Object.is($computed.$value, $computed.$prevValue)) return
79
+ $computed.notify()
80
+ }
81
+
72
82
  private static stop<T>(computed: ComputedSignal<T>) {
73
83
  const { $id, $unsubs } = latest(computed)
74
84
 
@@ -103,20 +113,35 @@ export class ComputedSignal<T> extends Signal<T> {
103
113
  }
104
114
  }
105
115
 
106
- export const computed = <T>(
116
+ export function computed<T>(
107
117
  getter: (prev?: T) => T,
108
118
  displayName?: string
109
- ): ComputedSignal<T> => {
119
+ ): ComputedSignal<T> {
110
120
  return new ComputedSignal(getter, displayName)
111
121
  }
112
122
 
113
- export const useComputed = <T>(
123
+ export function useComputed<T>(
124
+ getter: (prev?: T) => T,
125
+ displayName?: string
126
+ ): ComputedSignal<T>
127
+
128
+ export function useComputed<T>(
114
129
  getter: (prev?: T) => T,
130
+ deps?: unknown[],
115
131
  displayName?: string
116
- ) => {
132
+ ): ComputedSignal<T>
133
+
134
+ export function useComputed<T>(
135
+ getter: (prev?: T) => T,
136
+ depsOrDisplayName?: string | unknown[],
137
+ displayName?: string
138
+ ) {
117
139
  return useHook(
118
140
  "useComputedSignal",
119
- { signal: null! as ComputedSignal<T> },
141
+ {
142
+ signal: null! as ComputedSignal<T>,
143
+ deps: void 0 as unknown[] | undefined,
144
+ },
120
145
  ({ hook, isInit, isHMR }) => {
121
146
  if (__DEV__) {
122
147
  hook.dev = {
@@ -134,8 +159,20 @@ export const useComputed = <T>(
134
159
  }
135
160
  }
136
161
  if (isInit) {
162
+ if (typeof depsOrDisplayName === "string") {
163
+ displayName = depsOrDisplayName
164
+ depsOrDisplayName = void 0
165
+ }
166
+ hook.deps = depsOrDisplayName
137
167
  hook.cleanup = () => ComputedSignal.dispose(hook.signal)
138
168
  hook.signal = computed(getter, displayName)
169
+ } else if (
170
+ hook.deps &&
171
+ typeof depsOrDisplayName !== "string" &&
172
+ depsRequireChange(hook.deps, depsOrDisplayName)
173
+ ) {
174
+ hook.deps = depsOrDisplayName
175
+ ComputedSignal.updateGetter(hook.signal, getter)
139
176
  }
140
177
 
141
178
  return hook.signal
@@ -0,0 +1,25 @@
1
+ import type { Signal } from "./base.js"
2
+
3
+ type InferArraySignalItemType<T extends Signal<any[]>> = T extends Signal<
4
+ infer V
5
+ >
6
+ ? V extends Array<infer W>
7
+ ? W
8
+ : never
9
+ : never
10
+
11
+ type ForProps<T extends Signal<any[]>, U = InferArraySignalItemType<T>> = {
12
+ each: T
13
+ fallback?: JSX.Element
14
+ children: (value: U, index: number, array: U[]) => JSX.Element
15
+ }
16
+
17
+ export function For<T extends Signal<any[]>>({
18
+ each,
19
+ fallback,
20
+ children,
21
+ }: ForProps<T>) {
22
+ const items = each.value
23
+ if (items.length === 0) return fallback
24
+ return items.map(children)
25
+ }
@@ -8,5 +8,5 @@ export { Signal, signal, useSignal } from "./base.js"
8
8
  export { ComputedSignal, computed, useComputed } from "./computed.js"
9
9
  export { WatchEffect, watch, useWatch } from "./watch.js"
10
10
  export { unwrap, tick } from "./utils.js"
11
- export * from "./jsx.js"
11
+ export * from "./for.js"
12
12
  export * from "./types.js"
@@ -6,5 +6,9 @@ export type ReadonlySignal<T> = Signal<T> & {
6
6
  export type SignalSubscriber<T = unknown> = (value: T, prevValue?: T) => void
7
7
 
8
8
  export type SignalValues<T extends readonly Signal<unknown>[]> = {
9
- [I in keyof T]: T[I] extends Signal<infer V> ? V : never
9
+ [I in keyof T]: T[I] extends Signal<infer V>
10
+ ? V extends Kiru.StatefulPromise<infer P>
11
+ ? P
12
+ : V
13
+ : never
10
14
  }
package/src/ssr/server.ts CHANGED
@@ -1,19 +1,19 @@
1
1
  import { Readable } from "node:stream"
2
2
  import { Fragment } from "../element.js"
3
3
  import { renderMode } from "../globals.js"
4
- import { PREFETCHED_DATA_EVENT } from "../constants.js"
4
+ import { STREAMED_DATA_EVENT } from "../constants.js"
5
5
  import { __DEV__ } from "../env.js"
6
- import { recursiveRender, RecursiveRenderContext } from "../recursiveRender.js"
6
+ import { headlessRender, HeadlessRenderContext } from "../recursiveRender.js"
7
7
 
8
- const PREFETCH_EVENTS_SETUP = `
8
+ const STREAMED_DATA_SETUP = `
9
9
  <script type="text/javascript">
10
10
  const d = document,
11
- m = (window["${PREFETCHED_DATA_EVENT}"] ??= new Map());
12
- d.querySelectorAll("[x-data]").forEach((p) => {
11
+ m = (window["${STREAMED_DATA_EVENT}"] ??= new Map());
12
+ d.querySelectorAll("[k-data]").forEach((p) => {
13
13
  const id = p.getAttribute("id");
14
14
  const { data, error } = JSON.parse(p.innerHTML);
15
15
  m.set(id, { data, error });
16
- const event = new CustomEvent("${PREFETCHED_DATA_EVENT}", { detail: { id, data, error } });
16
+ const event = new CustomEvent("${STREAMED_DATA_EVENT}", { detail: { id, data, error } });
17
17
  window.dispatchEvent(event);
18
18
  p.remove();
19
19
  });
@@ -27,17 +27,17 @@ export function renderToReadableStream(element: JSX.Element): {
27
27
  } {
28
28
  const stream = new Readable({ read() {} })
29
29
  const rootNode = Fragment({ children: element })
30
- const prefetchPromises = new Set<Kiru.StatefulPromise<unknown>>()
31
- const pendingWritePromises: Promise<unknown>[] = []
30
+ const streamPromises = new Set<Kiru.StatefulPromise<unknown>>()
31
+ const pendingWritePromises: Promise<void>[] = []
32
32
 
33
33
  let immediate = ""
34
34
 
35
- const ctx: RecursiveRenderContext = {
35
+ const ctx: HeadlessRenderContext = {
36
36
  write: (chunk) => (immediate += chunk),
37
- onPending(data) {
37
+ onStreamData(data) {
38
38
  for (const promise of data) {
39
- if (prefetchPromises.has(promise)) continue
40
- prefetchPromises.add(promise)
39
+ if (streamPromises.has(promise)) continue
40
+ streamPromises.add(promise)
41
41
 
42
42
  const writePromise = promise
43
43
  .then(() => ({ data: promise.value }))
@@ -45,7 +45,7 @@ export function renderToReadableStream(element: JSX.Element): {
45
45
  .then((value) => {
46
46
  const content = JSON.stringify(value)
47
47
  stream.push(
48
- `<script id="${promise.id}" x-data type="application/json">${content}</script>`
48
+ `<script id="${promise.id}" k-data type="application/json">${content}</script>`
49
49
  )
50
50
  })
51
51
 
@@ -56,12 +56,12 @@ export function renderToReadableStream(element: JSX.Element): {
56
56
 
57
57
  const prev = renderMode.current
58
58
  renderMode.current = "stream"
59
- recursiveRender(ctx, rootNode, null, 0)
59
+ headlessRender(ctx, rootNode, null, 0)
60
60
  renderMode.current = prev
61
61
 
62
62
  if (pendingWritePromises.length > 0) {
63
63
  Promise.all(pendingWritePromises).then(() => {
64
- stream.push(PREFETCH_EVENTS_SETUP)
64
+ stream.push(STREAMED_DATA_SETUP)
65
65
  stream.push(null)
66
66
  })
67
67
  } else {
package/src/types.dom.ts CHANGED
@@ -352,6 +352,8 @@ type NoChildElementElement =
352
352
  | HTMLTrackElement
353
353
  | HTMLTextAreaElement
354
354
 
355
+ type DomElement = Element
356
+
355
357
  declare global {
356
358
  namespace Kiru {
357
359
  type DOMEvent<E = Event, C = unknown, T = unknown> = Omit<
@@ -368,98 +370,96 @@ declare global {
368
370
  bivarianceHack(event: E): void
369
371
  }["bivarianceHack"]
370
372
 
371
- interface BaseEvent<T extends Element = Element>
373
+ interface BaseEvent<T extends DomElement = DomElement>
372
374
  extends DOMEvent<Event, T> {}
373
375
 
374
- interface AnimationEvent<T extends Element = Element>
376
+ interface AnimationEvent<T extends DomElement = DomElement>
375
377
  extends DOMEvent<NativeAnimationEvent, T> {}
376
378
 
377
- interface ClipboardEvent<T extends Element = Element>
379
+ interface ClipboardEvent<T extends DomElement = DomElement>
378
380
  extends DOMEvent<NativeClipboardEvent, T> {}
379
381
 
380
- interface CompositionEvent<T extends Element = Element>
382
+ interface CompositionEvent<T extends DomElement = DomElement>
381
383
  extends DOMEvent<NativeCompositionEvent, T> {}
382
384
 
383
- interface DragEvent<T extends Element = Element>
385
+ interface DragEvent<T extends DomElement = DomElement>
384
386
  extends DOMEvent<NativeDragEvent, T> {}
385
387
 
386
- interface FocusEvent<T extends Element = Element>
388
+ interface FocusEvent<T extends DomElement = DomElement>
387
389
  extends DOMEvent<NativeFocusEvent, T> {}
388
390
 
389
- interface FormEvent<T extends Element = Element>
391
+ interface FormEvent<T extends DomElement = DomElement>
390
392
  extends DOMEvent<Event, T> {}
391
393
 
392
- interface KeyboardEvent<T extends Element = Element>
394
+ interface KeyboardEvent<T extends DomElement = DomElement>
393
395
  extends DOMEvent<NativeKeyboardEvent, T> {}
394
396
 
395
- interface MouseEvent<T extends Element = Element>
397
+ interface MouseEvent<T extends DomElement = DomElement>
396
398
  extends DOMEvent<NativeMouseEvent, T> {}
397
399
 
398
- interface PointerEvent<T extends Element = Element>
400
+ interface PointerEvent<T extends DomElement = DomElement>
399
401
  extends DOMEvent<NativePointerEvent, T> {}
400
402
 
401
- interface SubmitEvent<T extends Element = Element>
403
+ interface SubmitEvent<T extends DomElement = DomElement>
402
404
  extends DOMEvent<NativeSubmitEvent, T> {}
403
405
 
404
- interface TouchEvent<T extends Element = Element>
406
+ interface TouchEvent<T extends DomElement = DomElement>
405
407
  extends DOMEvent<NativeTouchEvent, T> {}
406
408
 
407
- interface ToggleEvent<T extends Element = Element>
409
+ interface ToggleEvent<T extends DomElement = DomElement>
408
410
  extends DOMEvent<NativeToggleEvent, T> {}
409
411
 
410
- interface TransitionEvent<T extends Element = Element>
412
+ interface TransitionEvent<T extends DomElement = DomElement>
411
413
  extends DOMEvent<NativeTransitionEvent, T> {}
412
414
 
413
- interface UIEvent<T extends Element = Element>
415
+ interface UIEvent<T extends DomElement = DomElement>
414
416
  extends DOMEvent<NativeUIEvent, T> {}
415
417
 
416
- interface WheelEvent<T extends Element = Element>
418
+ interface WheelEvent<T extends DomElement = DomElement>
417
419
  extends DOMEvent<NativeWheelEvent, T> {}
418
420
 
419
- type BaseEventHandler<T extends Element = Element> = EventHandler<
421
+ type BaseEventHandler<T extends DomElement = DomElement> = EventHandler<
420
422
  BaseEvent<T>
421
423
  >
422
424
 
423
- type ClipboardEventHandler<T extends Element = Element> = EventHandler<
424
- ClipboardEvent<T>
425
- >
426
- type CompositionEventHandler<T extends Element = Element> = EventHandler<
427
- CompositionEvent<T>
428
- >
429
- type DragEventHandler<T extends Element = Element> = EventHandler<
425
+ type ClipboardEventHandler<T extends DomElement = DomElement> =
426
+ EventHandler<ClipboardEvent<T>>
427
+ type CompositionEventHandler<T extends DomElement = DomElement> =
428
+ EventHandler<CompositionEvent<T>>
429
+ type DragEventHandler<T extends DomElement = DomElement> = EventHandler<
430
430
  DragEvent<T>
431
431
  >
432
- type FocusEventHandler<T extends Element = Element> = EventHandler<
432
+ type FocusEventHandler<T extends DomElement = DomElement> = EventHandler<
433
433
  FocusEvent<T>
434
434
  >
435
- type FormEventHandler<T extends Element = Element> = EventHandler<
435
+ type FormEventHandler<T extends DomElement = DomElement> = EventHandler<
436
436
  FormEvent<T>
437
437
  >
438
- type KeyboardEventHandler<T extends Element = Element> = EventHandler<
438
+ type KeyboardEventHandler<T extends DomElement = DomElement> = EventHandler<
439
439
  KeyboardEvent<T>
440
440
  >
441
- type MouseEventHandler<T extends Element = Element> = EventHandler<
441
+ type MouseEventHandler<T extends DomElement = DomElement> = EventHandler<
442
442
  MouseEvent<T>
443
443
  >
444
- type TouchEventHandler<T extends Element = Element> = EventHandler<
444
+ type TouchEventHandler<T extends DomElement = DomElement> = EventHandler<
445
445
  TouchEvent<T>
446
446
  >
447
- type PointerEventHandler<T extends Element = Element> = EventHandler<
447
+ type PointerEventHandler<T extends DomElement = DomElement> = EventHandler<
448
448
  PointerEvent<T>
449
449
  >
450
- type UIEventHandler<T extends Element = Element> = EventHandler<UIEvent<T>>
451
- type WheelEventHandler<T extends Element = Element> = EventHandler<
452
- WheelEvent<T>
450
+ type UIEventHandler<T extends DomElement = DomElement> = EventHandler<
451
+ UIEvent<T>
453
452
  >
454
- type AnimationEventHandler<T extends Element = Element> = EventHandler<
455
- AnimationEvent<T>
453
+ type WheelEventHandler<T extends DomElement = DomElement> = EventHandler<
454
+ WheelEvent<T>
456
455
  >
457
- type ToggleEventHandler<T extends Element = Element> = EventHandler<
456
+ type AnimationEventHandler<T extends DomElement = DomElement> =
457
+ EventHandler<AnimationEvent<T>>
458
+ type ToggleEventHandler<T extends DomElement = DomElement> = EventHandler<
458
459
  ToggleEvent<T>
459
460
  >
460
- type TransitionEventHandler<T extends Element = Element> = EventHandler<
461
- TransitionEvent<T>
462
- >
461
+ type TransitionEventHandler<T extends DomElement = DomElement> =
462
+ EventHandler<TransitionEvent<T>>
463
463
 
464
464
  type CustomEventAttributes = {
465
465
  [Key in keyof Kiru.CustomEvents as `on:${Key}`]?: (
@@ -467,7 +467,7 @@ declare global {
467
467
  ) => void
468
468
  }
469
469
 
470
- interface EventAttributes<T extends Element = Element>
470
+ interface EventAttributes<T extends DomElement = DomElement>
471
471
  extends CustomEventAttributes {
472
472
  // Clipboard Events
473
473
  oncopy?: ClipboardEventHandler<T> | undefined
package/src/types.ts CHANGED
@@ -103,7 +103,7 @@ declare global {
103
103
 
104
104
  type Element =
105
105
  | Element[]
106
- | Kiru.VNode
106
+ | Kiru.Element
107
107
  | PrimitiveChild
108
108
  | Kiru.Signal<PrimitiveChild>
109
109
 
@@ -156,16 +156,13 @@ declare global {
156
156
  }
157
157
  }
158
158
  interface RefObject<T> {
159
- readonly current: T | null
160
- }
161
- interface MutableRefObject<T> {
162
159
  current: T
163
160
  }
164
161
  type RefCallback<T> = {
165
162
  bivarianceHack(instance: T | null): void
166
163
  }["bivarianceHack"]
167
164
 
168
- type Ref<T> = RefCallback<T> | RefObject<T> | null | undefined
165
+ type Ref<T> = RefCallback<T> | RefObject<T>
169
166
 
170
167
  interface PromiseState<T> {
171
168
  id: string
@@ -187,25 +184,27 @@ declare global {
187
184
  | typeof $CONTEXT_PROVIDER
188
185
  | typeof $ERROR_BOUNDARY
189
186
 
190
- interface VNode {
191
- app?: AppContext
192
- dom?: SomeDom
193
- lastChildDom?: SomeDom
187
+ interface Element {
194
188
  type: Function | ExoticSymbol | "#text" | (string & {})
189
+ key: JSX.ElementKey | null
195
190
  props: {
196
191
  [key: string]: any
197
192
  children?: unknown
198
- key?: JSX.ElementKey
199
193
  ref?: Kiru.Ref<unknown>
200
194
  }
195
+ }
196
+
197
+ interface VNode extends Element {
198
+ app?: AppContext
199
+ dom?: SomeDom
201
200
  index: number
202
201
  depth: number
202
+ flags: number
203
203
  parent: VNode | null
204
204
  child: VNode | null
205
205
  sibling: VNode | null
206
206
  prev: VNodeSnapshot | null
207
207
  deletions: VNode[] | null
208
- flags: number
209
208
  hooks?: Hook<unknown>[]
210
209
  subs?: Set<Function>
211
210
  cleanups?: Record<string, Function>
@@ -223,6 +222,7 @@ declare global {
223
222
  }
224
223
  interface VNodeSnapshot {
225
224
  props: Kiru.VNode["props"]
225
+ key: Kiru.VNode["key"]
226
226
  memoizedProps: Kiru.VNode["memoizedProps"]
227
227
  index: number
228
228
  }
@@ -8,7 +8,7 @@ export type MaybeElement = SomeElement | undefined
8
8
  export type MaybeDom = SomeDom | undefined
9
9
 
10
10
  export interface FunctionVNode extends Kiru.VNode {
11
- type: (...args: any) => JSX.Element
11
+ type: (props: Record<string, unknown>) => JSX.Element
12
12
  }
13
13
 
14
14
  export interface ElementVNode extends Kiru.VNode {
@@ -65,3 +65,13 @@ export type AsyncTaskState<T, E extends Error = Error> =
65
65
  export type Guard<T, K extends keyof T> = {
66
66
  [P in K]: T[P]
67
67
  }
68
+
69
+ export type ArrayHas<T extends any[], U> =
70
+ // does the union of element types intersect U?
71
+ Extract<T[number], U> extends never ? false : true
72
+
73
+ export type RecordHas<T extends Record<string, any>, U> = [
74
+ Extract<T[keyof T], U>
75
+ ] extends [never]
76
+ ? false
77
+ : true
@@ -34,11 +34,14 @@ function encodeHtmlEntities(text: string): string {
34
34
  .replace(REGEX_SLASH, "&#47;")
35
35
  }
36
36
 
37
+ const internalProps = new Set(["children", "ref", "key", "innerHTML"])
37
38
  const propFilters = {
38
- internalProps: ["children", "ref", "key", "innerHTML"],
39
+ isInternalProp: (
40
+ key: string
41
+ ): key is "children" | "ref" | "key" | "innerHTML" => internalProps.has(key),
39
42
  isEvent: (key: string) => key.startsWith("on"),
40
- isProperty: (key: string) =>
41
- !propFilters.internalProps.includes(key) && !propFilters.isEvent(key),
43
+ isStringRenderableProperty: (key: string) =>
44
+ !internalProps.has(key) && !propFilters.isEvent(key),
42
45
  }
43
46
 
44
47
  function propToHtmlAttr(key: string): string {
@@ -110,7 +113,7 @@ function propsToElementAttributes(props: Record<string, unknown>): string {
110
113
  if (!!val) attrs.push(`style="${stylePropToString(val)}"`)
111
114
  }
112
115
 
113
- const keys = Object.keys(rest).filter(propFilters.isProperty)
116
+ const keys = Object.keys(rest).filter(propFilters.isStringRenderableProperty)
114
117
  for (let i = 0; i < keys.length; i++) {
115
118
  let k = keys[i]
116
119
  let val = unwrap(props[k])
@@ -4,5 +4,3 @@ export * from "./format.js"
4
4
  export * from "./runtime.js"
5
5
  export * from "./vdom.js"
6
6
  export * from "./generateId.js"
7
-
8
- export const noop = Object.freeze(() => {})
@@ -0,0 +1,26 @@
1
+ import { $STREAM_DATA } from "../constants.js"
2
+
3
+ export interface StreamDataThrowValue {
4
+ [$STREAM_DATA]: {
5
+ fallback?: JSX.Element
6
+ data: Kiru.StatefulPromise<unknown>[]
7
+ }
8
+ }
9
+
10
+ /**
11
+ * Returns true if the value is a {@link StreamDataThrowValue}
12
+ */
13
+ export function isStreamDataThrowValue(
14
+ value: unknown
15
+ ): value is StreamDataThrowValue {
16
+ return typeof value === "object" && !!value && $STREAM_DATA in value
17
+ }
18
+
19
+ /**
20
+ * Returns true if the value is a {@link Kiru.StatefulPromise}
21
+ */
22
+ export function isStatefulPromise(
23
+ thing: unknown
24
+ ): thing is Kiru.StatefulPromise<unknown> {
25
+ return thing instanceof Promise && "id" in thing && "state" in thing
26
+ }
@@ -1,7 +1,10 @@
1
+ import { Signal } from "../signals/base.js"
1
2
  import { __DEV__ } from "../env.js"
2
3
  import { renderMode } from "../globals.js"
3
4
 
4
- export { latest, sideEffectsEnabled }
5
+ export { noop, latest, composeRefs, setRef, sideEffectsEnabled }
6
+
7
+ const noop = Object.freeze(() => {})
5
8
 
6
9
  /**
7
10
  * This is a no-op in production. It is used to get the latest
@@ -17,6 +20,31 @@ function latest<T extends Exclude<object, null>>(thing: T): T {
17
20
  return tgt
18
21
  }
19
22
 
23
+ /**
24
+ * Composes multiple refs into a single ref callback.
25
+ */
26
+
27
+ function composeRefs<T>(...refs: Array<Kiru.Ref<T>>): Kiru.RefCallback<T> {
28
+ return (value: T) => {
29
+ refs.forEach((ref) => setRef(ref, value))
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Sets the value of a ref.
35
+ */
36
+ function setRef<T>(ref: Kiru.Ref<T>, value: T): void {
37
+ if (typeof ref === "function") {
38
+ ref(value)
39
+ return
40
+ }
41
+ if (Signal.isSignal(ref)) {
42
+ ref.value = value
43
+ return
44
+ }
45
+ ref.current = value
46
+ }
47
+
20
48
  /**
21
49
  * Returns false if called during "stream" or "string" render modes.
22
50
  */
package/src/utils/vdom.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { Signal } from "../signals/base.js"
2
1
  import {
3
2
  FLAG_DELETION,
4
3
  $FRAGMENT,
@@ -12,10 +11,12 @@ import { KiruError } from "../error.js"
12
11
  import { node } from "../globals.js"
13
12
  import type { AppContext } from "../appContext.js"
14
13
  import type { ErrorBoundaryNode } from "../types.utils.js"
14
+ import { isMemoFn } from "../components/memo.js"
15
15
 
16
16
  export {
17
- cloneVNode,
17
+ cloneElement,
18
18
  isVNodeDeleted,
19
+ isElement,
19
20
  isVNode,
20
21
  isValidTextChild,
21
22
  isExoticType,
@@ -31,17 +32,16 @@ export {
31
32
  findParent,
32
33
  findParentErrorBoundary,
33
34
  assertValidElementProps,
34
- isValidElementKeyProp,
35
- isValidElementRefProp,
35
+ normalizeElementKey,
36
36
  }
37
37
 
38
- function cloneVNode(vNode: Kiru.VNode): Kiru.VNode {
38
+ function cloneElement(vNode: Kiru.VNode): Kiru.Element {
39
39
  const children = vNode.props.children
40
40
  let clonedChildren: unknown
41
41
  if (isVNode(children)) {
42
- clonedChildren = cloneVNode(children)
42
+ clonedChildren = cloneElement(children)
43
43
  } else if (Array.isArray(children)) {
44
- clonedChildren = children.map((c) => (isVNode(c) ? cloneVNode(c) : c))
44
+ clonedChildren = children.map((c) => (isVNode(c) ? cloneElement(c) : c))
45
45
  }
46
46
 
47
47
  return createElement(vNode.type, { ...vNode.props, children: clonedChildren })
@@ -55,6 +55,10 @@ function isVNode(thing: unknown): thing is Kiru.VNode {
55
55
  return typeof thing === "object" && thing !== null && "type" in thing
56
56
  }
57
57
 
58
+ function isElement(thing: unknown): thing is Kiru.Element {
59
+ return typeof thing === "object" && thing !== null && "type" in thing
60
+ }
61
+
58
62
  function isValidTextChild(thing: unknown): thing is string | number | bigint {
59
63
  return (
60
64
  (typeof thing === "string" && thing !== "") ||
@@ -84,11 +88,7 @@ function isLazy(vNode: Kiru.VNode): boolean {
84
88
  }
85
89
 
86
90
  function isMemo(vNode: Kiru.VNode): boolean {
87
- return (
88
- typeof vNode.type === "function" &&
89
- "displayName" in vNode.type &&
90
- vNode.type.displayName === "Kiru.memo"
91
- )
91
+ return typeof vNode.type === "function" && isMemoFn(vNode.type)
92
92
  }
93
93
 
94
94
  function isContextProvider(
@@ -116,10 +116,11 @@ function getVNodeAppContext(vNode: Kiru.VNode): AppContext | null {
116
116
  function commitSnapshot(vNode: Kiru.VNode): void {
117
117
  const {
118
118
  props: { children, ...props },
119
+ key,
119
120
  memoizedProps,
120
121
  index,
121
122
  } = vNode
122
- vNode.prev = { props, memoizedProps, index }
123
+ vNode.prev = { props, key, memoizedProps, index }
123
124
  vNode.flags &= ~(FLAG_UPDATE | FLAG_PLACEMENT | FLAG_DELETION)
124
125
  }
125
126
 
@@ -187,14 +188,10 @@ function assertValidElementProps(vNode: Kiru.VNode) {
187
188
  }
188
189
  }
189
190
 
190
- function isValidElementKeyProp(thing: unknown): thing is string | number {
191
- return typeof thing === "string" || typeof thing === "number"
192
- }
193
-
194
- function isValidElementRefProp(thing: unknown): thing is Kiru.Ref<any> {
195
- return (
196
- typeof thing === "function" ||
197
- (typeof thing === "object" && !!thing && "current" in thing) ||
198
- Signal.isSignal(thing)
199
- )
191
+ function normalizeElementKey(thing: unknown): JSX.ElementKey | null {
192
+ if (thing === undefined) return null
193
+ if (typeof thing === "string" || typeof thing === "number") {
194
+ return thing
195
+ }
196
+ return null
200
197
  }