kiru 0.52.4 → 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 (141) 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/index.d.ts +1 -1
  52. package/dist/router/index.d.ts.map +1 -1
  53. package/dist/router/utils/index.d.ts +1 -1
  54. package/dist/router/utils/index.d.ts.map +1 -1
  55. package/dist/signals/base.d.ts +1 -3
  56. package/dist/signals/base.d.ts.map +1 -1
  57. package/dist/signals/base.js +3 -3
  58. package/dist/signals/base.js.map +1 -1
  59. package/dist/signals/computed.d.ts +4 -2
  60. package/dist/signals/computed.d.ts.map +1 -1
  61. package/dist/signals/computed.js +29 -6
  62. package/dist/signals/computed.js.map +1 -1
  63. package/dist/signals/for.d.ts +10 -0
  64. package/dist/signals/for.d.ts.map +1 -0
  65. package/dist/signals/for.js +7 -0
  66. package/dist/signals/for.js.map +1 -0
  67. package/dist/signals/index.d.ts +1 -1
  68. package/dist/signals/index.js +1 -1
  69. package/dist/signals/types.d.ts +1 -1
  70. package/dist/signals/types.d.ts.map +1 -1
  71. package/dist/ssr/server.js +13 -13
  72. package/dist/ssr/server.js.map +1 -1
  73. package/dist/types.d.ts +10 -11
  74. package/dist/types.d.ts.map +1 -1
  75. package/dist/types.dom.d.ts +33 -32
  76. package/dist/types.dom.d.ts.map +1 -1
  77. package/dist/types.utils.d.ts +5 -1
  78. package/dist/types.utils.d.ts.map +1 -1
  79. package/dist/utils/format.d.ts +2 -2
  80. package/dist/utils/format.d.ts.map +1 -1
  81. package/dist/utils/format.js +4 -3
  82. package/dist/utils/format.js.map +1 -1
  83. package/dist/utils/index.d.ts +0 -1
  84. package/dist/utils/index.d.ts.map +1 -1
  85. package/dist/utils/index.js +0 -1
  86. package/dist/utils/index.js.map +1 -1
  87. package/dist/utils/promise.d.ts +16 -0
  88. package/dist/utils/promise.d.ts.map +1 -0
  89. package/dist/utils/promise.js +14 -0
  90. package/dist/utils/promise.js.map +1 -0
  91. package/dist/utils/runtime.d.ts +10 -1
  92. package/dist/utils/runtime.d.ts.map +1 -1
  93. package/dist/utils/runtime.js +25 -1
  94. package/dist/utils/runtime.js.map +1 -1
  95. package/dist/utils/vdom.d.ts +4 -4
  96. package/dist/utils/vdom.d.ts.map +1 -1
  97. package/dist/utils/vdom.js +18 -17
  98. package/dist/utils/vdom.js.map +1 -1
  99. package/dist/vNode.d.ts +4 -0
  100. package/dist/vNode.d.ts.map +1 -0
  101. package/dist/vNode.js +22 -0
  102. package/dist/vNode.js.map +1 -0
  103. package/package.json +1 -1
  104. package/src/appContext.ts +15 -13
  105. package/src/components/derive.ts +121 -0
  106. package/src/components/index.ts +1 -1
  107. package/src/components/memo.ts +3 -2
  108. package/src/constants.ts +4 -4
  109. package/src/dom.ts +71 -66
  110. package/src/element.ts +22 -35
  111. package/src/globalContext.ts +24 -3
  112. package/src/hooks/usePromise.ts +32 -41
  113. package/src/hooks/useRef.ts +2 -2
  114. package/src/reconciler.ts +87 -125
  115. package/src/recursiveRender.ts +25 -23
  116. package/src/renderToString.ts +3 -3
  117. package/src/signals/base.ts +3 -3
  118. package/src/signals/computed.ts +43 -6
  119. package/src/signals/for.ts +25 -0
  120. package/src/signals/index.ts +1 -1
  121. package/src/signals/types.ts +5 -1
  122. package/src/ssr/server.ts +15 -15
  123. package/src/types.dom.ts +40 -40
  124. package/src/types.ts +11 -11
  125. package/src/types.utils.ts +11 -1
  126. package/src/utils/format.ts +7 -4
  127. package/src/utils/index.ts +0 -2
  128. package/src/utils/promise.ts +26 -0
  129. package/src/utils/runtime.ts +29 -1
  130. package/src/utils/vdom.ts +20 -23
  131. package/src/vNode.ts +30 -0
  132. package/dist/components/suspense.d.ts +0 -24
  133. package/dist/components/suspense.d.ts.map +0 -1
  134. package/dist/components/suspense.js +0 -36
  135. package/dist/components/suspense.js.map +0 -1
  136. package/dist/signals/jsx.d.ts +0 -17
  137. package/dist/signals/jsx.d.ts.map +0 -1
  138. package/dist/signals/jsx.js +0 -11
  139. package/dist/signals/jsx.js.map +0 -1
  140. package/src/components/suspense.ts +0 -72
  141. package/src/signals/jsx.ts +0 -46
package/src/appContext.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { FLAG_STATIC_DOM } from "./constants.js"
2
- import { createElement } from "./element.js"
3
2
  import { __DEV__ } from "./env.js"
4
- import { renderRootSync } from "./scheduler.js"
3
+ import { renderRootSync, requestUpdate } from "./scheduler.js"
4
+ import { createVNode } from "./vNode.js"
5
5
 
6
6
  type VNode = Kiru.VNode
7
7
 
@@ -34,19 +34,11 @@ export function mount(
34
34
  )
35
35
  }
36
36
  }
37
- const rootNode = createElement(container.nodeName.toLowerCase(), {})
38
- if (__DEV__) {
39
- container.__kiruNode = rootNode
40
- }
41
-
42
- rootNode.dom = container
43
- rootNode.flags |= FLAG_STATIC_DOM
44
-
37
+ const rootNode = createRootNode(container)
45
38
  const id = appId++
46
- const name = options?.name ?? `App-${id}`
47
39
  const appContext: AppContext = {
48
40
  id,
49
- name,
41
+ name: options?.name ?? `App-${id}`,
50
42
  rootNode,
51
43
  render,
52
44
  unmount,
@@ -72,7 +64,7 @@ export function mount(
72
64
  }
73
65
 
74
66
  render(children)
75
- window.__kiru.emit("mount", appContext)
67
+ window.__kiru.emit("mount", appContext, requestUpdate)
76
68
  if (__DEV__) {
77
69
  queueMicrotask(() => {
78
70
  window.dispatchEvent(new Event("kiru:ready"))
@@ -81,3 +73,13 @@ export function mount(
81
73
 
82
74
  return appContext
83
75
  }
76
+
77
+ function createRootNode(container: HTMLElement): Kiru.VNode {
78
+ const node = createVNode(container.nodeName.toLowerCase())
79
+ node.flags |= FLAG_STATIC_DOM
80
+ node.dom = container
81
+ if (__DEV__) {
82
+ container.__kiruNode = node
83
+ }
84
+ return node
85
+ }
@@ -0,0 +1,121 @@
1
+ import { node } from "../globals.js"
2
+ import { $STREAM_DATA } from "../constants.js"
3
+ import { requestUpdate } from "../scheduler.js"
4
+ import { useRef } from "../hooks/index.js"
5
+ import { Signal } from "../signals/index.js"
6
+ import { sideEffectsEnabled } from "../utils/index.js"
7
+ import type { RecordHas } from "../types.utils"
8
+ import { isStatefulPromise, StreamDataThrowValue } from "../utils/promise.js"
9
+
10
+ export type Derivable =
11
+ | Kiru.Signal<unknown>
12
+ | Kiru.StatefulPromise<unknown>
13
+ | Record<string, Kiru.Signal<unknown> | Kiru.StatefulPromise<unknown>>
14
+
15
+ type InnerOf<T> = T extends Kiru.Signal<infer V>
16
+ ? V
17
+ : T extends Kiru.StatefulPromise<infer P>
18
+ ? P
19
+ : never
20
+
21
+ type UnwrapDerive<T extends Derivable> = T extends
22
+ | Kiru.Signal<unknown>
23
+ | Kiru.StatefulPromise<any>
24
+ ? InnerOf<T>
25
+ : { [K in keyof T]: InnerOf<T[K]> }
26
+
27
+ type RecordHasPromise<T extends Record<string, any>> = RecordHas<
28
+ T,
29
+ Kiru.StatefulPromise<any>
30
+ >
31
+
32
+ type ChildFn<T> = (value: T) => JSX.Children
33
+ type ChildFnWithStale<T> = (value: T, isStale: boolean) => JSX.Children
34
+
35
+ export type DeriveFallbackMode = "swr" | "fallback"
36
+
37
+ export interface DeriveProps<
38
+ T extends Derivable,
39
+ Mode extends DeriveFallbackMode = "fallback"
40
+ > {
41
+ from: T
42
+ mode?: Mode
43
+ children: T extends Kiru.StatefulPromise<infer U>
44
+ ? Mode extends "swr"
45
+ ? ChildFnWithStale<U>
46
+ : ChildFn<U>
47
+ : T extends Record<string, any>
48
+ ? RecordHasPromise<T> extends true
49
+ ? Mode extends "swr"
50
+ ? ChildFnWithStale<UnwrapDerive<T>>
51
+ : ChildFn<UnwrapDerive<T>>
52
+ : ChildFn<UnwrapDerive<T>>
53
+ : ChildFn<UnwrapDerive<T>>
54
+ fallback?: T extends Kiru.StatefulPromise<any>
55
+ ? JSX.Element
56
+ : T extends Record<string, any>
57
+ ? RecordHasPromise<T> extends true
58
+ ? JSX.Element
59
+ : never
60
+ : never
61
+ }
62
+
63
+ export function Derive<
64
+ T extends Derivable,
65
+ U extends DeriveFallbackMode = "swr"
66
+ >(props: DeriveProps<T, U>) {
67
+ const { from, children, fallback, mode } = props
68
+ const prevSuccess = useRef<UnwrapDerive<T> | null>(null)
69
+
70
+ const promises = new Set<Kiru.StatefulPromise<any>>()
71
+ let value: UnwrapDerive<T>
72
+
73
+ if (isStatefulPromise(from)) {
74
+ promises.add(from)
75
+ value = from.value as UnwrapDerive<T>
76
+ } else if (Signal.isSignal(from)) {
77
+ value = from.value as UnwrapDerive<T>
78
+ } else {
79
+ const out: Record<string, any> = {}
80
+ for (const key in from) {
81
+ const v = from[key]
82
+ if (isStatefulPromise(v)) promises.add(v)
83
+ out[key] = (v as Signal<unknown> | Kiru.StatefulPromise<unknown>).value
84
+ }
85
+ value = out as UnwrapDerive<T>
86
+ }
87
+
88
+ if (promises.size === 0) {
89
+ return (children as ChildFn<UnwrapDerive<T>>)(value)
90
+ }
91
+
92
+ if (!sideEffectsEnabled()) {
93
+ throw {
94
+ [$STREAM_DATA]: {
95
+ fallback,
96
+ data: Array.from(promises),
97
+ },
98
+ } satisfies StreamDataThrowValue
99
+ }
100
+
101
+ for (const p of promises) {
102
+ if (p.state === "rejected") {
103
+ throw p.error
104
+ }
105
+ if (p.state === "pending") {
106
+ const nodeRef = node.current!
107
+ Promise.allSettled(promises).then(() => requestUpdate(nodeRef))
108
+
109
+ if (mode !== "fallback" && prevSuccess.current) {
110
+ return (children as ChildFnWithStale<UnwrapDerive<T>>)(
111
+ prevSuccess.current,
112
+ true
113
+ )
114
+ }
115
+ return fallback
116
+ }
117
+ }
118
+
119
+ prevSuccess.current = value
120
+ return (children as ChildFnWithStale<UnwrapDerive<T>>)(value, false)
121
+ }
@@ -1,6 +1,6 @@
1
1
  export { ErrorBoundary, type ErrorBoundaryProps } from "./errorBoundary.js"
2
+ export * from "./derive.js"
2
3
  export * from "./lazy.js"
3
4
  export { memo } from "./memo.js"
4
5
  export * from "./portal.js"
5
- export * from "./suspense.js"
6
6
  export * from "./transition.js"
@@ -15,7 +15,8 @@ function _arePropsEqual<T extends Record<string, unknown>>(
15
15
  return true
16
16
  }
17
17
 
18
- export type MemoFn = Function & {
18
+ export interface MemoFn<T extends Record<string, unknown> = {}> {
19
+ (props: T): JSX.Element
19
20
  [$MEMO]: {
20
21
  arePropsEqual: (
21
22
  prevProps: Record<string, unknown>,
@@ -39,7 +40,7 @@ export function memo<T extends Record<string, unknown> = {}>(
39
40
  )
40
41
  }
41
42
 
42
- export function isMemoFn(fn: any): fn is MemoFn {
43
+ export function isMemoFn(fn: Function & { [$MEMO]?: any }): fn is MemoFn {
43
44
  return (
44
45
  typeof fn === "function" && typeof fn[$MEMO]?.arePropsEqual === "function"
45
46
  )
package/src/constants.ts CHANGED
@@ -7,10 +7,10 @@ export {
7
7
  $HMR_ACCEPT,
8
8
  $MEMO,
9
9
  $ERROR_BOUNDARY,
10
- $SUSPENSE_THROW,
10
+ $STREAM_DATA,
11
11
  $DEV_FILE_LINK,
12
12
  CONSECUTIVE_DIRTY_LIMIT,
13
- PREFETCHED_DATA_EVENT,
13
+ STREAMED_DATA_EVENT,
14
14
  EVENT_PREFIX_REGEX,
15
15
  FLAG_UPDATE,
16
16
  FLAG_PLACEMENT,
@@ -31,11 +31,11 @@ const $KIRU_ERROR = Symbol.for("kiru.error")
31
31
  const $HMR_ACCEPT = Symbol.for("kiru.hmrAccept")
32
32
  const $MEMO = Symbol.for("kiru.memo")
33
33
  const $ERROR_BOUNDARY = Symbol.for("kiru.errorBoundary")
34
- const $SUSPENSE_THROW = Symbol.for("kiru.suspenseThrow")
34
+ const $STREAM_DATA = Symbol.for("kiru.streamData")
35
35
  const $DEV_FILE_LINK = Symbol.for("kiru.devFileLink")
36
36
 
37
37
  const CONSECUTIVE_DIRTY_LIMIT = 50
38
- const PREFETCHED_DATA_EVENT = "kiru:prefetched"
38
+ const STREAMED_DATA_EVENT = "kiru:deferred"
39
39
 
40
40
  const FLAG_UPDATE = 1 << 1
41
41
  const FLAG_PLACEMENT = 1 << 2
package/src/dom.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  propFilters,
5
5
  propToHtmlAttr,
6
6
  getVNodeAppContext,
7
+ setRef,
7
8
  } from "./utils/index.js"
8
9
  import {
9
10
  booleanAttributes,
@@ -73,21 +74,6 @@ function onAfterFlushDomChanges() {
73
74
  persistingFocus = false
74
75
  }
75
76
 
76
- function setDomRef(ref: Kiru.Ref<SomeDom | null>, value: SomeDom | null) {
77
- if (typeof ref === "function") {
78
- ref(value)
79
- return
80
- }
81
- if (Signal.isSignal(ref)) {
82
- ref.sneak(value)
83
- ref.notify({
84
- filter: (sub) => typeof sub === "function",
85
- })
86
- return
87
- }
88
- ;(ref as Kiru.MutableRefObject<SomeDom | null>).current = value
89
- }
90
-
91
77
  function createDom(vNode: DomVNode): SomeDom {
92
78
  const t = vNode.type
93
79
  const dom =
@@ -139,75 +125,85 @@ const vNodeToWrappedFocusEventHandlersMap = new WeakMap<
139
125
  WrappedFocusEventMap
140
126
  >()
141
127
 
142
- function updateDom(vNode: VNode) {
143
- const dom = vNode.dom as SomeDom
144
- const prevProps: Record<string, any> = vNode.prev?.props ?? {}
145
- const nextProps: Record<string, any> = vNode.props ?? {}
146
- const keys = new Set([...Object.keys(prevProps), ...Object.keys(nextProps)])
128
+ function updateDom(vNode: DomVNode) {
129
+ const { dom, prev, props, cleanups } = vNode
130
+ const prevProps = prev?.props ?? {}
131
+ const nextProps = props ?? {}
147
132
  const isHydration = renderMode.current === "hydrate"
148
133
 
149
- keys.forEach((key) => {
150
- const prev = prevProps[key],
151
- next = nextProps[key]
152
- if (propFilters.internalProps.includes(key) && key !== "innerHTML") {
153
- if (key === "ref" && prev !== next) {
154
- if (prev) {
155
- setDomRef(prev, null)
156
- }
157
- if (next) {
158
- setDomRef(next, dom)
159
- }
160
- }
161
- return
134
+ // TEXT NODE SHORT-PATH
135
+ if (dom instanceof Text) {
136
+ const nextVal = nextProps.nodeValue
137
+ if (!Signal.isSignal(nextVal) && dom.nodeValue !== nextVal) {
138
+ dom.nodeValue = nextVal
162
139
  }
140
+ return
141
+ }
142
+
143
+ const keys: string[] = []
144
+ for (const k in prevProps) keys.push(k)
145
+ for (const k in nextProps) {
146
+ if (!(k in prevProps)) keys.push(k)
147
+ }
148
+
149
+ for (let i = 0; i < keys.length; i++) {
150
+ const key = keys[i]
151
+ const prevVal = prevProps[key]
152
+ const nextVal = nextProps[key]
163
153
 
164
154
  if (propFilters.isEvent(key)) {
165
- if (prev !== next || renderMode.current === "hydrate") {
155
+ if (prevVal !== nextVal || isHydration) {
166
156
  const evtName = key.replace(EVENT_PREFIX_REGEX, "")
157
+ const isFocus = evtName === "focus" || evtName === "blur"
158
+ const wrappedMap = vNodeToWrappedFocusEventHandlersMap.get(vNode)
167
159
 
168
- const isFocusEvent = evtName === "focus" || evtName === "blur"
169
160
  if (key in prevProps) {
170
161
  dom.removeEventListener(
171
162
  evtName,
172
- isFocusEvent
173
- ? vNodeToWrappedFocusEventHandlersMap.get(vNode)?.[evtName]
174
- : prev
163
+ isFocus ? wrappedMap?.[evtName] : prevVal
175
164
  )
176
165
  }
166
+
177
167
  if (key in nextProps) {
178
168
  dom.addEventListener(
179
169
  evtName,
180
- isFocusEvent ? wrapFocusEventHandler(vNode, evtName, next) : next
170
+ isFocus ? wrapFocusEventHandler(vNode, evtName, nextVal) : nextVal
181
171
  )
182
172
  }
183
173
  }
184
- return
174
+ continue
185
175
  }
186
176
 
187
- if (!(dom instanceof Text)) {
188
- if (prev === next || (isHydration && dom.getAttribute(key) === next)) {
189
- return
190
- }
177
+ if (propFilters.isInternalProp(key) && key !== "innerHTML") {
178
+ continue
179
+ }
191
180
 
192
- if (Signal.isSignal(prev) && vNode.cleanups) {
193
- const v = vNode.cleanups[key]
194
- v && (v(), delete vNode.cleanups[key])
195
- }
196
- if (Signal.isSignal(next)) {
197
- return setSignalProp(vNode, dom, key, next, prev)
198
- }
199
- setProp(dom, key, next, prev)
200
- return
181
+ if (prevVal === nextVal) {
182
+ continue
201
183
  }
202
- if (Signal.isSignal(next)) {
203
- // signal textNodes are handled via 'subTextNode'.
204
- return
184
+
185
+ if (Signal.isSignal(prevVal) && cleanups) {
186
+ const disposer = cleanups[key]
187
+ if (disposer) {
188
+ disposer()
189
+ delete cleanups[key]
190
+ }
205
191
  }
206
- // text node
207
- if (dom.nodeValue !== next) {
208
- dom.nodeValue = next
192
+
193
+ if (Signal.isSignal(nextVal)) {
194
+ setSignalProp(vNode, dom, key, nextVal, prevVal)
195
+ continue
209
196
  }
210
- })
197
+
198
+ setProp(dom, key, nextVal, prevVal)
199
+ }
200
+
201
+ const prevRef = prevProps.ref
202
+ const nextRef = nextProps.ref
203
+ if (prevRef !== nextRef) {
204
+ if (prevRef) setRef(prevRef, null)
205
+ if (nextRef) setRef(nextRef, dom)
206
+ }
211
207
  }
212
208
 
213
209
  function deriveSelectElementValue(dom: HTMLSelectElement) {
@@ -248,6 +244,7 @@ function setSignalProp(
248
244
  const [modifier, attr] = key.split(":")
249
245
  if (modifier !== "bind") {
250
246
  cleanups[key] = signal.subscribe((value, prev) => {
247
+ if (value === prev) return
251
248
  setProp(dom, key, value, prev)
252
249
  if (__DEV__) {
253
250
  window.__kiru.profilingContext?.emit(
@@ -257,7 +254,11 @@ function setSignalProp(
257
254
  }
258
255
  })
259
256
 
260
- return setProp(dom, key, signal.peek(), unwrap(prevValue))
257
+ const value = signal.peek()
258
+ const prev = unwrap(prevValue)
259
+ if (value === prev) return
260
+ setProp(dom, key, value, prev)
261
+ return
261
262
  }
262
263
 
263
264
  const evtName = bindAttrToEventMap[attr]
@@ -285,7 +286,7 @@ function setSignalProp(
285
286
 
286
287
  const setSigFromElement = (val: any) => {
287
288
  signal.sneak(val)
288
- signal.notify({ filter: (sub) => sub !== signalUpdateCallback })
289
+ signal.notify((sub) => sub !== signalUpdateCallback)
289
290
  }
290
291
 
291
292
  let evtHandler: (evt: Event) => void
@@ -323,7 +324,10 @@ function setSignalProp(
323
324
  unsub()
324
325
  }
325
326
 
326
- return setProp(dom, attr, signal.peek(), unwrap(prevValue))
327
+ const value = signal.peek()
328
+ const prev = unwrap(prevValue)
329
+ if (value === prev) return
330
+ setProp(dom, attr, value, prev)
327
331
  }
328
332
 
329
333
  function subTextNode(vNode: VNode, textNode: Text, signal: Signal<string>) {
@@ -373,7 +377,7 @@ function hydrateDom(vNode: VNode) {
373
377
  }
374
378
  vNode.dom = dom
375
379
  if (vNode.type !== "#text" && !(vNode.flags & FLAG_STATIC_DOM)) {
376
- updateDom(vNode)
380
+ updateDom(vNode as DomVNode)
377
381
  return
378
382
  }
379
383
  if (Signal.isSignal(vNode.props.nodeValue)) {
@@ -449,7 +453,6 @@ function setProp(
449
453
  value: unknown,
450
454
  prev: unknown
451
455
  ) {
452
- if (value === prev) return
453
456
  switch (key) {
454
457
  case "style":
455
458
  return setStyleProp(element, value, prev)
@@ -706,10 +709,12 @@ function commitDeletion(vNode: VNode) {
706
709
  }
707
710
 
708
711
  if (dom) {
709
- if (ref) setDomRef(ref as Kiru.Ref<SomeDom>, null)
710
712
  if (dom.isConnected && !(node.flags & FLAG_STATIC_DOM)) {
711
713
  dom.remove()
712
714
  }
715
+ if (ref) {
716
+ setRef(ref, null)
717
+ }
713
718
  delete node.dom
714
719
  }
715
720
  })
package/src/element.ts CHANGED
@@ -1,46 +1,29 @@
1
- import { $FRAGMENT, $MEMO, FLAG_MEMO } from "./constants.js"
2
- import { isMemoFn } from "./components/memo.js"
3
- import { isValidElementKeyProp, isValidElementRefProp } from "./utils/index.js"
1
+ import { $FRAGMENT } from "./constants.js"
2
+ import { normalizeElementKey } from "./utils/index.js"
4
3
 
5
4
  export function createElement<T extends Kiru.VNode["type"]>(
6
5
  type: T,
7
6
  props: null | Record<string, unknown> = null,
8
7
  ...children: unknown[]
9
- ): Kiru.VNode {
10
- if ((type as any) === Fragment) {
11
- return Fragment({ children: children as any, ...props })
12
- }
13
- const node: Kiru.VNode = {
14
- type,
15
- flags: 0,
16
- index: 0,
17
- depth: 0,
18
- props: {},
19
- parent: null,
20
- sibling: null,
21
- child: null,
22
- prev: null,
23
- deletions: null,
24
- }
25
- if (isMemoFn(type)) {
26
- node.flags |= FLAG_MEMO
27
- node.arePropsEqual = type[$MEMO].arePropsEqual
8
+ ): Kiru.Element {
9
+ if ((type as unknown) === Fragment) {
10
+ type = $FRAGMENT as T
28
11
  }
12
+ const p = props === null ? {} : props
13
+ const key = normalizeElementKey(p.key)
29
14
 
30
- if (props !== null) {
31
- const { key, ref, ...rest } = props
32
- if (isValidElementKeyProp(key)) node.props.key = key.toString()
33
- if (isValidElementRefProp(ref)) node.props.ref = ref
34
- Object.assign(node.props, rest)
15
+ const len = children.length
16
+ if (len === 1) {
17
+ p.children = children[0]
18
+ } else if (len > 1) {
19
+ p.children = children
35
20
  }
36
21
 
37
- const _children =
38
- children.length === 1 ? children[0] : children.length > 1 ? children : null
39
- if (_children !== null) {
40
- node.props.children = _children
22
+ return {
23
+ type,
24
+ key,
25
+ props: p,
41
26
  }
42
-
43
- return node
44
27
  }
45
28
 
46
29
  export function Fragment({
@@ -49,6 +32,10 @@ export function Fragment({
49
32
  }: {
50
33
  children: JSX.Children
51
34
  key?: JSX.ElementKey
52
- }): Kiru.VNode {
53
- return createElement($FRAGMENT, key ? { key } : null, children)
35
+ }): Kiru.Element {
36
+ return {
37
+ type: $FRAGMENT,
38
+ key: normalizeElementKey(key),
39
+ props: { children },
40
+ }
54
41
  }
@@ -6,6 +6,7 @@ import type { FileRouterController } from "./router/fileRouterController"
6
6
  import type { AppContext } from "./appContext"
7
7
  import type { Store } from "./store"
8
8
  import type { SWRCache } from "./swr"
9
+ import type { requestUpdate } from "./index.js"
9
10
 
10
11
  export { createKiruGlobalContext, type GlobalKiruEvent, type KiruGlobalContext }
11
12
 
@@ -56,7 +57,7 @@ function createReactiveMap<V>(): ReactiveMap<V> {
56
57
  type Evt =
57
58
  | {
58
59
  name: "mount"
59
- data?: undefined
60
+ data?: typeof requestUpdate
60
61
  }
61
62
  | {
62
63
  name: "unmount"
@@ -73,6 +74,10 @@ type Evt =
73
74
 
74
75
  type GlobalKiruEvent = Evt["name"]
75
76
 
77
+ interface SchedulerInterface {
78
+ requestUpdate: (vNode: Kiru.VNode) => void
79
+ }
80
+
76
81
  interface KiruGlobalContext {
77
82
  readonly apps: AppContext[]
78
83
  emit<T extends Evt>(event: T["name"], ctx: AppContext, data?: T["data"]): void
@@ -91,10 +96,15 @@ interface KiruGlobalContext {
91
96
  fileRouterInstance?: {
92
97
  current: FileRouterController | null
93
98
  }
99
+ getSchedulerInterface?: (app: AppContext) => SchedulerInterface | null
94
100
  }
95
101
 
96
102
  function createKiruGlobalContext(): KiruGlobalContext {
97
103
  const contexts = new Set<AppContext>()
104
+ const contextToSchedulerInterface = new WeakMap<
105
+ AppContext,
106
+ SchedulerInterface
107
+ >()
98
108
  const listeners = new Map<
99
109
  GlobalKiruEvent,
100
110
  Set<(ctx: AppContext, data?: Evt["data"]) => void>
@@ -134,14 +144,25 @@ function createKiruGlobalContext(): KiruGlobalContext {
134
144
  }
135
145
 
136
146
  // Initialize event listeners
137
- on("mount", (ctx) => contexts.add(ctx))
138
- on("unmount", (ctx) => contexts.delete(ctx))
147
+ on("mount", (ctx, requestUpdate) => {
148
+ contexts.add(ctx)
149
+ if (requestUpdate && typeof requestUpdate === "function") {
150
+ contextToSchedulerInterface.set(ctx, { requestUpdate })
151
+ }
152
+ })
153
+ on("unmount", (ctx) => {
154
+ contexts.delete(ctx)
155
+ contextToSchedulerInterface.delete(ctx)
156
+ })
139
157
 
140
158
  if (__DEV__) {
141
159
  globalContext.HMRContext = createHMRContext()
142
160
  globalContext.profilingContext = createProfilingContext()
143
161
  globalContext.stores = createReactiveMap()
144
162
  globalContext.fileRouterInstance = fileRouterInstance
163
+ globalContext.getSchedulerInterface = (app) => {
164
+ return contextToSchedulerInterface.get(app) ?? null
165
+ }
145
166
  }
146
167
 
147
168
  return globalContext