kiru 0.54.0-preview.0 → 0.54.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 (178) hide show
  1. package/dist/components/memo.d.ts +1 -3
  2. package/dist/components/memo.d.ts.map +1 -1
  3. package/dist/components/memo.js +2 -2
  4. package/dist/components/memo.js.map +1 -1
  5. package/dist/context.d.ts.map +1 -1
  6. package/dist/context.js +1 -23
  7. package/dist/context.js.map +1 -1
  8. package/dist/dom.d.ts.map +1 -1
  9. package/dist/dom.js +109 -72
  10. package/dist/dom.js.map +1 -1
  11. package/dist/error.d.ts.map +1 -1
  12. package/dist/error.js +2 -4
  13. package/dist/error.js.map +1 -1
  14. package/dist/form/index.d.ts.map +1 -1
  15. package/dist/form/index.js +6 -10
  16. package/dist/form/index.js.map +1 -1
  17. package/dist/globals.d.ts +1 -1
  18. package/dist/globals.d.ts.map +1 -1
  19. package/dist/globals.js.map +1 -1
  20. package/dist/hmr.d.ts +1 -0
  21. package/dist/hmr.d.ts.map +1 -1
  22. package/dist/hmr.js +11 -3
  23. package/dist/hmr.js.map +1 -1
  24. package/dist/hooks/useEffectEvent.d.ts.map +1 -1
  25. package/dist/hooks/useEffectEvent.js.map +1 -1
  26. package/dist/hooks/usePromise.d.ts.map +1 -1
  27. package/dist/hooks/usePromise.js.map +1 -1
  28. package/dist/hooks/utils.d.ts.map +1 -1
  29. package/dist/hooks/utils.js +10 -10
  30. package/dist/hooks/utils.js.map +1 -1
  31. package/dist/hydration.d.ts +6 -13
  32. package/dist/hydration.d.ts.map +1 -1
  33. package/dist/hydration.js +20 -50
  34. package/dist/hydration.js.map +1 -1
  35. package/dist/reconciler.d.ts.map +1 -1
  36. package/dist/reconciler.js +3 -6
  37. package/dist/reconciler.js.map +1 -1
  38. package/dist/recursiveRender.d.ts.map +1 -1
  39. package/dist/recursiveRender.js +9 -8
  40. package/dist/recursiveRender.js.map +1 -1
  41. package/dist/renderToString.d.ts.map +1 -1
  42. package/dist/renderToString.js.map +1 -1
  43. package/dist/router/client/index.d.ts +2 -4
  44. package/dist/router/client/index.d.ts.map +1 -1
  45. package/dist/router/client/index.js +11 -49
  46. package/dist/router/client/index.js.map +1 -1
  47. package/dist/router/context.d.ts +5 -2
  48. package/dist/router/context.d.ts.map +1 -1
  49. package/dist/router/context.js +1 -5
  50. package/dist/router/context.js.map +1 -1
  51. package/dist/router/fileRouter.d.ts.map +1 -1
  52. package/dist/router/fileRouter.js +2 -4
  53. package/dist/router/fileRouter.js.map +1 -1
  54. package/dist/router/fileRouterController.d.ts +2 -2
  55. package/dist/router/fileRouterController.d.ts.map +1 -1
  56. package/dist/router/fileRouterController.js +39 -101
  57. package/dist/router/fileRouterController.js.map +1 -1
  58. package/dist/router/globals.d.ts +0 -3
  59. package/dist/router/globals.d.ts.map +1 -1
  60. package/dist/router/globals.js +0 -3
  61. package/dist/router/globals.js.map +1 -1
  62. package/dist/router/head.d.ts.map +1 -1
  63. package/dist/router/head.js +7 -5
  64. package/dist/router/head.js.map +1 -1
  65. package/dist/router/index.d.ts +1 -2
  66. package/dist/router/index.d.ts.map +1 -1
  67. package/dist/router/index.js +1 -2
  68. package/dist/router/index.js.map +1 -1
  69. package/dist/router/link.js +3 -3
  70. package/dist/router/link.js.map +1 -1
  71. package/dist/router/{ssg → server}/index.d.ts +3 -4
  72. package/dist/router/server/index.d.ts.map +1 -0
  73. package/dist/router/{ssg → server}/index.js +5 -8
  74. package/dist/router/server/index.js.map +1 -0
  75. package/dist/router/types.d.ts +4 -37
  76. package/dist/router/types.d.ts.map +1 -1
  77. package/dist/router/types.internal.d.ts +0 -4
  78. package/dist/router/types.internal.d.ts.map +1 -1
  79. package/dist/router/utils/index.d.ts +3 -8
  80. package/dist/router/utils/index.d.ts.map +1 -1
  81. package/dist/router/utils/index.js +8 -40
  82. package/dist/router/utils/index.js.map +1 -1
  83. package/dist/scheduler.d.ts.map +1 -1
  84. package/dist/scheduler.js +60 -53
  85. package/dist/scheduler.js.map +1 -1
  86. package/dist/signals/base.d.ts +0 -2
  87. package/dist/signals/base.d.ts.map +1 -1
  88. package/dist/signals/base.js +0 -6
  89. package/dist/signals/base.js.map +1 -1
  90. package/dist/signals/computed.d.ts +3 -0
  91. package/dist/signals/computed.d.ts.map +1 -1
  92. package/dist/signals/computed.js +29 -20
  93. package/dist/signals/computed.js.map +1 -1
  94. package/dist/signals/for.d.ts +3 -3
  95. package/dist/signals/for.d.ts.map +1 -1
  96. package/dist/signals/for.js +2 -1
  97. package/dist/signals/for.js.map +1 -1
  98. package/dist/signals/utils.d.ts.map +1 -1
  99. package/dist/signals/utils.js +2 -1
  100. package/dist/signals/utils.js.map +1 -1
  101. package/dist/signals/watch.d.ts.map +1 -1
  102. package/dist/signals/watch.js +18 -22
  103. package/dist/signals/watch.js.map +1 -1
  104. package/dist/ssr/client.d.ts +1 -1
  105. package/dist/ssr/client.d.ts.map +1 -1
  106. package/dist/ssr/client.js +0 -2
  107. package/dist/ssr/client.js.map +1 -1
  108. package/dist/ssr/server.d.ts +2 -1
  109. package/dist/ssr/server.d.ts.map +1 -1
  110. package/dist/ssr/server.js +19 -16
  111. package/dist/ssr/server.js.map +1 -1
  112. package/dist/types.d.ts +0 -7
  113. package/dist/types.d.ts.map +1 -1
  114. package/dist/types.dom.d.ts +3 -3
  115. package/dist/types.dom.d.ts.map +1 -1
  116. package/dist/utils/format.d.ts +1 -2
  117. package/dist/utils/format.d.ts.map +1 -1
  118. package/dist/utils/format.js +1 -4
  119. package/dist/utils/format.js.map +1 -1
  120. package/dist/utils/runtime.d.ts +3 -2
  121. package/dist/utils/runtime.d.ts.map +1 -1
  122. package/dist/utils/runtime.js +5 -2
  123. package/dist/utils/runtime.js.map +1 -1
  124. package/dist/utils/vdom.d.ts.map +1 -1
  125. package/dist/utils/vdom.js +2 -2
  126. package/dist/utils/vdom.js.map +1 -1
  127. package/package.json +4 -8
  128. package/src/components/memo.ts +3 -11
  129. package/src/context.ts +1 -24
  130. package/src/dom.ts +145 -96
  131. package/src/error.ts +2 -4
  132. package/src/form/index.ts +6 -9
  133. package/src/globals.ts +1 -1
  134. package/src/hmr.ts +14 -5
  135. package/src/hooks/useEffectEvent.ts +0 -1
  136. package/src/hooks/usePromise.ts +0 -1
  137. package/src/hooks/utils.ts +12 -12
  138. package/src/hydration.ts +21 -57
  139. package/src/reconciler.ts +2 -6
  140. package/src/recursiveRender.ts +10 -9
  141. package/src/renderToString.ts +0 -1
  142. package/src/router/client/index.ts +14 -100
  143. package/src/router/context.ts +6 -7
  144. package/src/router/fileRouter.ts +2 -6
  145. package/src/router/fileRouterController.ts +39 -159
  146. package/src/router/globals.ts +0 -4
  147. package/src/router/head.ts +7 -5
  148. package/src/router/index.ts +1 -12
  149. package/src/router/link.ts +3 -3
  150. package/src/router/{ssg → server}/index.ts +10 -17
  151. package/src/router/types.internal.ts +0 -5
  152. package/src/router/types.ts +4 -48
  153. package/src/router/utils/index.ts +16 -79
  154. package/src/scheduler.ts +83 -70
  155. package/src/signals/base.ts +0 -8
  156. package/src/signals/computed.ts +30 -18
  157. package/src/signals/for.ts +15 -10
  158. package/src/signals/utils.ts +2 -1
  159. package/src/signals/watch.ts +27 -22
  160. package/src/ssr/client.ts +1 -4
  161. package/src/ssr/server.ts +21 -20
  162. package/src/types.dom.ts +4 -5
  163. package/src/types.ts +0 -10
  164. package/src/utils/format.ts +0 -5
  165. package/src/utils/runtime.ts +6 -2
  166. package/src/utils/vdom.ts +2 -7
  167. package/dist/router/guard.d.ts +0 -17
  168. package/dist/router/guard.d.ts.map +0 -1
  169. package/dist/router/guard.js +0 -45
  170. package/dist/router/guard.js.map +0 -1
  171. package/dist/router/ssg/index.d.ts.map +0 -1
  172. package/dist/router/ssg/index.js.map +0 -1
  173. package/dist/router/ssr/index.d.ts +0 -20
  174. package/dist/router/ssr/index.d.ts.map +0 -1
  175. package/dist/router/ssr/index.js +0 -160
  176. package/dist/router/ssr/index.js.map +0 -1
  177. package/src/router/guard.ts +0 -72
  178. package/src/router/ssr/index.ts +0 -247
@@ -1,6 +1,5 @@
1
1
  import { $MEMO } from "../constants.js"
2
2
  import { createElement } from "../element.js"
3
- import { __DEV__ } from "../env.js"
4
3
 
5
4
  function _arePropsEqual<T extends Record<string, unknown>>(
6
5
  prevProps: T,
@@ -17,12 +16,7 @@ function _arePropsEqual<T extends Record<string, unknown>>(
17
16
 
18
17
  export interface MemoFn<T extends Record<string, unknown> = {}> {
19
18
  (props: T): JSX.Element
20
- [$MEMO]: {
21
- arePropsEqual: (
22
- prevProps: Record<string, unknown>,
23
- nextProps: Record<string, unknown>
24
- ) => boolean
25
- }
19
+ [$MEMO]: (prevProps: T, nextProps: T) => boolean
26
20
  }
27
21
 
28
22
  export function memo<T extends Record<string, unknown> = {}>(
@@ -34,14 +28,12 @@ export function memo<T extends Record<string, unknown> = {}>(
34
28
  return createElement(fn, props)
35
29
  },
36
30
  {
37
- [$MEMO]: { arePropsEqual },
31
+ [$MEMO]: arePropsEqual,
38
32
  displayName: "Kiru.memo",
39
33
  }
40
34
  )
41
35
  }
42
36
 
43
37
  export function isMemoFn(fn: Function & { [$MEMO]?: any }): fn is MemoFn {
44
- return (
45
- typeof fn === "function" && typeof fn[$MEMO]?.arePropsEqual === "function"
46
- )
38
+ return typeof fn[$MEMO] === "function"
47
39
  }
package/src/context.ts CHANGED
@@ -1,10 +1,6 @@
1
- import { $CONTEXT, $CONTEXT_PROVIDER, $HMR_ACCEPT } from "./constants.js"
1
+ import { $CONTEXT, $CONTEXT_PROVIDER } from "./constants.js"
2
2
  import { createElement } from "./element.js"
3
- import { __DEV__ } from "./env.js"
4
- import { GenericHMRAcceptor } from "./hmr.js"
5
3
  import { useState } from "./hooks/useState.js"
6
- import { requestUpdate } from "./scheduler.js"
7
- import { traverseApply } from "./utils/index.js"
8
4
 
9
5
  export function createContext<T>(defaultValue: T): Kiru.Context<T> {
10
6
  const ctx: Kiru.Context<T> = {
@@ -25,25 +21,6 @@ export function createContext<T>(defaultValue: T): Kiru.Context<T> {
25
21
  return this.Provider.displayName || "Anonymous Context"
26
22
  },
27
23
  }
28
- if (__DEV__) {
29
- const asHmrAcceptor = ctx as any as GenericHMRAcceptor<Kiru.Context<T>>
30
- asHmrAcceptor[$HMR_ACCEPT] = {
31
- inject: (prev) => {
32
- const newProvider = ctx.Provider
33
- window.__kiru.apps.forEach((ctx) => {
34
- traverseApply(ctx.rootNode, (vNode) => {
35
- if (vNode.type === prev.Provider) {
36
- vNode.type = newProvider
37
- vNode.hmrUpdated = true
38
- requestUpdate(vNode)
39
- }
40
- })
41
- })
42
- },
43
- destroy: () => {},
44
- provide: () => ctx,
45
- }
46
- }
47
24
 
48
25
  return ctx
49
26
  }
package/src/dom.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  propToHtmlAttr,
6
6
  getVNodeAppContext,
7
7
  setRef,
8
+ isValidTextChild,
8
9
  } from "./utils/index.js"
9
10
  import {
10
11
  booleanAttributes,
@@ -87,43 +88,35 @@ function createDom(vNode: DomVNode): SomeDom {
87
88
  }
88
89
  function createTextNode(vNode: VNode): Text {
89
90
  const { nodeValue } = vNode.props
90
- if (!Signal.isSignal(nodeValue)) {
91
- return document.createTextNode(nodeValue)
91
+ if (Signal.isSignal(nodeValue)) {
92
+ return createSignalTextNode(vNode, nodeValue)
92
93
  }
93
94
 
95
+ return document.createTextNode(nodeValue)
96
+ }
97
+
98
+ function createSignalTextNode(vNode: VNode, nodeValue: Signal<string>): Text {
94
99
  const value = nodeValue.peek() ?? ""
95
100
  const textNode = document.createTextNode(value)
96
101
  subTextNode(vNode, textNode, nodeValue)
97
102
  return textNode
98
103
  }
99
104
 
100
- function wrapFocusEventHandler(
101
- vNode: VNode,
102
- evtName: "focus" | "blur",
103
- callback: (event: FocusEvent) => void
104
- ) {
105
- const wrappedHandlers = vNodeToWrappedFocusEventHandlersMap.get(vNode) ?? {}
106
- const handler = (wrappedHandlers[evtName] = (event: FocusEvent) => {
105
+ function wrapFocusEventHandler(callback: (event: FocusEvent) => void) {
106
+ return (event: FocusEvent) => {
107
107
  if (persistingFocus) {
108
108
  event.preventDefault()
109
109
  event.stopPropagation()
110
110
  return
111
111
  }
112
112
  callback(event)
113
- })
114
- vNodeToWrappedFocusEventHandlersMap.set(vNode, wrappedHandlers)
115
- return handler
113
+ }
116
114
  }
117
115
 
118
- type WrappedFocusEventMap = {
119
- focus?: (event: FocusEvent) => void
120
- blur?: (event: FocusEvent) => void
116
+ interface VNodeEventListenerObjects {
117
+ [key: string]: EventListenerObject
121
118
  }
122
-
123
- const vNodeToWrappedFocusEventHandlersMap = new WeakMap<
124
- VNode,
125
- WrappedFocusEventMap
126
- >()
119
+ const eventListenerObjects = new WeakMap<VNode, VNodeEventListenerObjects>()
127
120
 
128
121
  function updateDom(vNode: DomVNode) {
129
122
  const { dom, prev, props, cleanups } = vNode
@@ -146,30 +139,39 @@ function updateDom(vNode: DomVNode) {
146
139
  if (!(k in prevProps)) keys.push(k)
147
140
  }
148
141
 
142
+ let events: VNodeEventListenerObjects | undefined
149
143
  for (let i = 0; i < keys.length; i++) {
150
144
  const key = keys[i]
151
145
  const prevVal = prevProps[key]
152
146
  const nextVal = nextProps[key]
153
147
 
154
148
  if (propFilters.isEvent(key)) {
149
+ events ??= eventListenerObjects.get(vNode)
150
+ if (!events) eventListenerObjects.set(vNode, (events = {}))
151
+
155
152
  if (prevVal !== nextVal || isHydration) {
156
153
  const evtName = key.replace(EVENT_PREFIX_REGEX, "")
157
- const isFocus = evtName === "focus" || evtName === "blur"
158
- const wrappedMap = vNodeToWrappedFocusEventHandlersMap.get(vNode)
159
-
160
- if (key in prevProps) {
161
- dom.removeEventListener(
162
- evtName,
163
- isFocus ? wrappedMap?.[evtName] : prevVal
164
- )
154
+ const evtListenerObj = events[evtName]
155
+
156
+ if (!nextVal) {
157
+ if (evtListenerObj) {
158
+ dom.removeEventListener(evtName, evtListenerObj)
159
+ delete events[evtName]
160
+ }
161
+ continue
165
162
  }
166
163
 
167
- if (key in nextProps) {
168
- dom.addEventListener(
169
- evtName,
170
- isFocus ? wrapFocusEventHandler(vNode, evtName, nextVal) : nextVal
171
- )
164
+ let handleEvent = nextVal.bind(void 0)
165
+ if (evtName === "focus" || evtName === "blur") {
166
+ handleEvent = wrapFocusEventHandler(handleEvent)
172
167
  }
168
+
169
+ if (evtListenerObj) {
170
+ evtListenerObj.handleEvent = handleEvent
171
+ continue
172
+ }
173
+
174
+ dom.addEventListener(evtName, (events[evtName] = { handleEvent }))
173
175
  }
174
176
  continue
175
177
  }
@@ -206,7 +208,7 @@ function updateDom(vNode: DomVNode) {
206
208
  }
207
209
  }
208
210
 
209
- function deriveSelectElementValue(dom: HTMLSelectElement) {
211
+ function getSelectElementValue(dom: HTMLSelectElement) {
210
212
  if (dom.multiple) {
211
213
  return Array.from(dom.selectedOptions).map((option) => option.value)
212
214
  }
@@ -231,7 +233,7 @@ const bindAttrToEventMap: Record<string, string> = {
231
233
  playbackRate: "ratechange",
232
234
  currentTime: "timeupdate",
233
235
  }
234
- const numericValueElements = ["progress", "meter", "number", "range"]
236
+ const numericValueInputTypes = new Set(["progress", "meter", "number", "range"])
235
237
 
236
238
  function setSignalProp(
237
239
  vNode: VNode,
@@ -240,71 +242,80 @@ function setSignalProp(
240
242
  signal: Signal<any>,
241
243
  prevValue: unknown
242
244
  ) {
243
- const cleanups = (vNode.cleanups ??= {})
244
245
  const [modifier, attr] = key.split(":")
246
+ const cleanups = (vNode.cleanups ??= {})
245
247
  if (modifier !== "bind") {
246
248
  cleanups[key] = signal.subscribe((value, prev) => {
247
249
  if (value === prev) return
248
250
  setProp(dom, key, value, prev)
249
251
  if (__DEV__) {
250
- window.__kiru.profilingContext?.emit(
251
- "signalAttrUpdate",
252
- getVNodeAppContext(vNode)!
253
- )
252
+ emitSignalAttrUpdate(vNode)
254
253
  }
255
254
  })
255
+ } else {
256
+ const evtName = bindAttrToEventMap[attr]
257
+ if (!evtName) {
258
+ if (__DEV__) {
259
+ console.error(
260
+ `[kiru]: ${attr} is not a valid element binding attribute.`
261
+ )
262
+ }
263
+ return
264
+ }
265
+ cleanups[key] = bindElementProp(vNode, dom, attr, evtName, signal)
266
+ }
256
267
 
257
- const value = signal.peek()
258
- const prev = unwrap(prevValue)
259
- if (value === prev) return
260
- setProp(dom, key, value, prev)
261
- return
268
+ const value = signal.peek()
269
+ const prev = unwrap(prevValue)
270
+ if (value !== prev) {
271
+ setProp(dom, attr ?? modifier, value, prev)
262
272
  }
273
+ }
263
274
 
264
- const evtName = bindAttrToEventMap[attr]
265
- if (!evtName) {
266
- if (__DEV__) {
267
- console.error(`[kiru]: ${attr} is not a valid element binding attribute.`)
268
- }
269
- return
275
+ function createElementValueReader(
276
+ dom: Exclude<SomeDom, Text>,
277
+ signal: Signal<any>
278
+ ) {
279
+ if (dom instanceof HTMLInputElement) {
280
+ return createInputValueReader(dom, signal)
281
+ }
282
+ if (dom instanceof HTMLSelectElement) {
283
+ return () => getSelectElementValue(dom)
284
+ }
285
+ return () => (dom as any).value
286
+ }
287
+
288
+ function bindElementProp(
289
+ vNode: VNode,
290
+ dom: Exclude<SomeDom, Text>,
291
+ attr: string,
292
+ evtName: string,
293
+ signal: Signal<any>
294
+ ): () => void {
295
+ const writeToSignal = (val: any) => {
296
+ signal.sneak(val)
297
+ signal.notify((sub) => sub !== updateFromSignal)
270
298
  }
271
299
 
272
- const isSelect = dom instanceof HTMLSelectElement
273
- const setAttr = isSelect
274
- ? (value: any) => setSelectElementValue(dom, value)
275
- : (value: any) => ((dom as any)[attr] = value)
300
+ const writeToElement =
301
+ dom instanceof HTMLSelectElement && attr === "value"
302
+ ? (value: any) => setSelectElementValue(dom, value)
303
+ : (value: any) => ((dom as any)[attr] = value)
276
304
 
277
- const signalUpdateCallback = (value: any) => {
278
- setAttr(value)
305
+ const updateFromSignal = (value: any) => {
306
+ writeToElement(value)
279
307
  if (__DEV__) {
280
- window.__kiru.profilingContext?.emit(
281
- "signalAttrUpdate",
282
- getVNodeAppContext(vNode)!
283
- )
308
+ emitSignalAttrUpdate(vNode)
284
309
  }
285
310
  }
286
311
 
287
- const setSigFromElement = (val: any) => {
288
- signal.sneak(val)
289
- signal.notify((sub) => sub !== signalUpdateCallback)
290
- }
291
-
292
- let evtHandler: (evt: Event) => void
312
+ let evtHandler: EventListener
293
313
  if (attr === "value") {
294
- const useNumericValue =
295
- numericValueElements.indexOf((dom as HTMLInputElement).type) !== -1
296
- evtHandler = () => {
297
- let val: any = (dom as HTMLInputElement | HTMLSelectElement).value
298
- if (isSelect) {
299
- val = deriveSelectElementValue(dom)
300
- } else if (typeof signal.peek() === "number" && useNumericValue) {
301
- val = (dom as HTMLInputElement).valueAsNumber
302
- }
303
- setSigFromElement(val)
304
- }
314
+ const readValue = createElementValueReader(dom, signal)
315
+ evtHandler = () => writeToSignal(readValue())
305
316
  } else {
306
- evtHandler = (e: Event) => {
307
- const val = (e.target as any)[attr]
317
+ evtHandler = () => {
318
+ const val = (dom as any)[attr]
308
319
  /**
309
320
  * the 'timeupdate' event is fired when the currentTime property is
310
321
  * set (from code OR playback), so we need to prevent unnecessary
@@ -312,22 +323,42 @@ function setSignalProp(
312
323
  * elements with the same signal bound to 'currentTime'
313
324
  */
314
325
  if (attr === "currentTime" && signal.peek() === val) return
315
- setSigFromElement(val)
326
+ writeToSignal(val)
316
327
  }
317
328
  }
318
329
 
319
330
  dom.addEventListener(evtName, evtHandler)
320
- const unsub = signal.subscribe(signalUpdateCallback)
331
+ const unsub = signal.subscribe(updateFromSignal)
321
332
 
322
- cleanups[key] = () => {
333
+ return () => {
323
334
  dom.removeEventListener(evtName, evtHandler)
324
335
  unsub()
325
336
  }
337
+ }
326
338
 
327
- const value = signal.peek()
328
- const prev = unwrap(prevValue)
329
- if (value === prev) return
330
- setProp(dom, attr, value, prev)
339
+ function createInputValueReader(
340
+ dom: HTMLInputElement,
341
+ signal: Signal<any>
342
+ ): () => any {
343
+ const t = dom.type
344
+ const v = signal.peek()
345
+
346
+ if (t === "date" && v instanceof Date) {
347
+ return () => dom.valueAsDate
348
+ }
349
+
350
+ if (numericValueInputTypes.has(t) && typeof v === "number") {
351
+ return () => dom.valueAsNumber
352
+ }
353
+
354
+ return () => dom.value
355
+ }
356
+
357
+ function emitSignalAttrUpdate(vNode: VNode) {
358
+ window.__kiru.profilingContext?.emit(
359
+ "signalAttrUpdate",
360
+ getVNodeAppContext(vNode)!
361
+ )
331
362
  }
332
363
 
333
364
  function subTextNode(vNode: VNode, textNode: Text, signal: Signal<string>) {
@@ -343,21 +374,39 @@ function subTextNode(vNode: VNode, textNode: Text, signal: Signal<string>) {
343
374
  })
344
375
  }
345
376
 
346
- function tryHydrateNullableSignalChild(vNode: VNode): MaybeDom {
347
- if (vNode.type !== "#text" || !Signal.isSignal(vNode.props.nodeValue)) {
348
- return
377
+ /**
378
+ * Creates and inserts an empty signal-bound text node into
379
+ * the dom tree if the signal value is null or undefined.
380
+ */
381
+ function getOrCreateTextNode(vNode: VNode): MaybeDom {
382
+ const sig = vNode.props.nodeValue
383
+ if (!Signal.isSignal(sig)) {
384
+ return hydrationStack.getCurrentChild()
349
385
  }
350
- const value = unwrap(vNode.props.nodeValue)
351
- if (value !== null && value !== undefined) {
352
- return
386
+
387
+ const value = sig.peek()
388
+ if (isValidTextChild(value)) {
389
+ return hydrationStack.getCurrentChild()
390
+ }
391
+
392
+ const dom = createSignalTextNode(vNode, sig)
393
+ const currentChild = hydrationStack.getCurrentChild()
394
+
395
+ if (!currentChild) {
396
+ return hydrationStack.getCurrentParent().appendChild(dom)
353
397
  }
354
- const dom = createTextNode(vNode)
355
- hydrationStack.parent().appendChild(dom)
398
+
399
+ currentChild.before(dom)
356
400
  return dom
357
401
  }
358
402
 
359
403
  function hydrateDom(vNode: VNode) {
360
- const dom = hydrationStack.nextChild() ?? tryHydrateNullableSignalChild(vNode)
404
+ const dom =
405
+ vNode.type === "#text"
406
+ ? getOrCreateTextNode(vNode)
407
+ : hydrationStack.getCurrentChild()
408
+
409
+ hydrationStack.bumpChildIndex()
361
410
 
362
411
  if (!dom) {
363
412
  throw new KiruError({
package/src/error.ts CHANGED
@@ -25,10 +25,8 @@ export class KiruError extends Error {
25
25
  : optionsOrMessage.message
26
26
  super(message)
27
27
  if (typeof optionsOrMessage !== "string") {
28
- if (__DEV__) {
29
- if (optionsOrMessage?.vNode) {
30
- this.customNodeStack = captureErrorStack(optionsOrMessage.vNode)
31
- }
28
+ if (__DEV__ && optionsOrMessage?.vNode) {
29
+ this.customNodeStack = captureErrorStack(optionsOrMessage.vNode)
32
30
  }
33
31
  this.fatal = optionsOrMessage?.fatal
34
32
  }
package/src/form/index.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  safeStringify,
5
5
  shallowCompare,
6
6
  generateRandomID,
7
+ call,
7
8
  } from "../utils/index.js"
8
9
  import { useEffect } from "../hooks/useEffect.js"
9
10
  import { useMemo } from "../hooks/useMemo.js"
@@ -260,7 +261,7 @@ function createFormController<T extends Record<string, unknown>>(
260
261
  }
261
262
  }
262
263
 
263
- formFieldUpdaters.get(name)?.forEach((update) => update())
264
+ formFieldUpdaters.get(name)?.forEach(call)
264
265
  }
265
266
 
266
267
  const setFieldValue = <K extends RecordKey<T>>(
@@ -410,9 +411,7 @@ function createFormController<T extends Record<string, unknown>>(
410
411
  delete formFieldErrors[fieldName as RecordKey<T>]
411
412
  }
412
413
  updateSubscribers()
413
- formFieldUpdaters.forEach((updaters) => {
414
- updaters.forEach((update) => update())
415
- })
414
+ formFieldUpdaters.forEach((updaters) => updaters.forEach(call))
416
415
  }
417
416
 
418
417
  const validateForm = async () => {
@@ -634,11 +633,9 @@ export function useForm<T extends Record<string, unknown> = {}>(
634
633
  "useFormSubscription",
635
634
  { sub: null! as FormStateSubscriber<T> },
636
635
  ({ hook, isInit, isHMR, update }) => {
637
- if (__DEV__) {
638
- if (isHMR) {
639
- isInit = true
640
- hook.cleanup?.()
641
- }
636
+ if (__DEV__ && isHMR) {
637
+ isInit = true
638
+ hook.cleanup?.()
642
639
  }
643
640
  if (isInit) {
644
641
  hook.sub = {
package/src/globals.ts CHANGED
@@ -13,5 +13,5 @@ const renderMode = {
13
13
  }
14
14
 
15
15
  const hydrationMode = {
16
- current: "dynamic" as Kiru.HydrationMode,
16
+ current: "dynamic" as "static" | "dynamic",
17
17
  }
package/src/hmr.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { $HMR_ACCEPT, $DEV_FILE_LINK } from "./constants.js"
2
- import { __DEV__ } from "./env.js"
3
2
  import { traverseApply } from "./utils/index.js"
4
- import { requestUpdate } from "./scheduler.js"
3
+ import { flushSync, requestUpdate } from "./scheduler.js"
5
4
  import { Signal } from "./signals/base.js"
6
5
  import type { WatchEffect } from "./signals/watch.js"
7
6
  import type { Store } from "./store.js"
@@ -25,6 +24,11 @@ type HotVarDesc = {
25
24
  link: string
26
25
  }
27
26
 
27
+ let _isHmrUpdate = false
28
+ export function isHmrUpdate() {
29
+ return _isHmrUpdate
30
+ }
31
+
28
32
  export function isGenericHmrAcceptor(
29
33
  thing: unknown
30
34
  ): thing is GenericHMRAcceptor<any> {
@@ -128,15 +132,20 @@ export function createHMRContext() {
128
132
  if (vNode.type === oldEntry.value) {
129
133
  vNode.type = newEntry.value as any
130
134
  dirtiedApps.add(ctx)
131
- vNode.hmrUpdated = true
132
135
  }
133
136
  })
134
137
  })
135
138
  }
136
139
  }
137
- dirtiedApps.forEach((ctx) => ctx.rootNode && requestUpdate(ctx.rootNode))
138
- isModuleReplacementExecution = false
139
140
 
141
+ if (dirtiedApps.size) {
142
+ _isHmrUpdate = true
143
+ dirtiedApps.forEach((ctx) => requestUpdate(ctx.rootNode))
144
+ flushSync()
145
+ _isHmrUpdate = false
146
+ }
147
+
148
+ isModuleReplacementExecution = false
140
149
  currentModuleMemory = null
141
150
  currentModuleFilePath = null
142
151
  }
@@ -1,6 +1,5 @@
1
1
  import { sideEffectsEnabled } from "../utils/index.js"
2
2
  import { node } from "../globals.js"
3
- import { __DEV__ } from "../env.js"
4
3
  import { useHook } from "./utils.js"
5
4
 
6
5
  /**
@@ -1,5 +1,4 @@
1
1
  import { STREAMED_DATA_EVENT } from "../constants.js"
2
- import { __DEV__ } from "../env.js"
3
2
  import { hydrationMode, renderMode } from "../globals.js"
4
3
  import { Signal, useSignal } from "../signals/base.js"
5
4
  import { cleanupHook, depsRequireChange, useHook } from "./utils.js"
@@ -3,6 +3,7 @@ import { __DEV__ } from "../env.js"
3
3
  import { hookIndex, node } from "../globals.js"
4
4
  import { noop } from "../utils/index.js"
5
5
  import { requestUpdate } from "../scheduler.js"
6
+ import { isHmrUpdate } from "../hmr.js"
6
7
  export {
7
8
  cleanupHook,
8
9
  depsRequireChange,
@@ -115,17 +116,16 @@ function useHook<
115
116
  ): ReturnType<U> {
116
117
  const vNode = getVNodeOrError(hookName)
117
118
 
118
- if (__DEV__) {
119
- if (
120
- currentHookName !== null &&
121
- !nestedHookWarnings.has(hookName + currentHookName)
122
- ) {
123
- nestedHookWarnings.add(hookName + currentHookName)
124
- throw new KiruError({
125
- message: `Nested primitive "useHook" calls are not supported. "${hookName}" was called inside "${currentHookName}". Strange will most certainly happen.`,
126
- vNode,
127
- })
128
- }
119
+ if (
120
+ __DEV__ &&
121
+ currentHookName !== null &&
122
+ !nestedHookWarnings.has(hookName + currentHookName)
123
+ ) {
124
+ nestedHookWarnings.add(hookName + currentHookName)
125
+ throw new KiruError({
126
+ message: `Nested primitive "useHook" calls are not supported. "${hookName}" was called inside "${currentHookName}". Strange will most certainly happen.`,
127
+ vNode,
128
+ })
129
129
  }
130
130
 
131
131
  const queueEffect = (callback: Function, opts?: { immediate?: boolean }) => {
@@ -177,7 +177,7 @@ function useHook<
177
177
  const res = (callback as HookCallback<T>)({
178
178
  hook,
179
179
  isInit: !oldHook,
180
- isHMR: vNode.hmrUpdated,
180
+ isHMR: isHmrUpdate(),
181
181
  update: () => requestUpdate(vNode),
182
182
  queueEffect,
183
183
  vNode,