kiru 1.0.1 → 1.1.1
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.
- package/dist/appHandle.js +2 -2
- package/dist/appHandle.js.map +1 -1
- package/dist/components/lazy.d.ts.map +1 -1
- package/dist/components/lazy.js +2 -2
- package/dist/components/lazy.js.map +1 -1
- package/dist/components/transition.js +1 -5
- package/dist/components/transition.js.map +1 -1
- package/dist/devtools.d.ts.map +1 -1
- package/dist/devtools.js +6 -2
- package/dist/devtools.js.map +1 -1
- package/dist/dom/commit.d.ts +5 -0
- package/dist/dom/commit.d.ts.map +1 -0
- package/dist/dom/commit.js +94 -0
- package/dist/dom/commit.js.map +1 -0
- package/dist/dom/focus.d.ts +4 -0
- package/dist/dom/focus.d.ts.map +1 -0
- package/dist/dom/focus.js +32 -0
- package/dist/dom/focus.js.map +1 -0
- package/dist/dom/index.d.ts +4 -0
- package/dist/dom/index.d.ts.map +1 -0
- package/dist/dom/index.js +4 -0
- package/dist/dom/index.js.map +1 -0
- package/dist/dom/nodes.d.ts +12 -0
- package/dist/dom/nodes.d.ts.map +1 -0
- package/dist/dom/nodes.js +166 -0
- package/dist/dom/nodes.js.map +1 -0
- package/dist/dom/props.d.ts +8 -0
- package/dist/dom/props.d.ts.map +1 -0
- package/dist/dom/props.js +675 -0
- package/dist/dom/props.js.map +1 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +2 -0
- package/dist/env.js.map +1 -1
- package/dist/globalContext.d.ts +3 -8
- package/dist/globalContext.d.ts.map +1 -1
- package/dist/globalContext.js +4 -16
- package/dist/globalContext.js.map +1 -1
- package/dist/globals.d.ts +21 -1
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +22 -2
- package/dist/globals.js.map +1 -1
- package/dist/hmr.d.ts +17 -2
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +31 -5
- package/dist/hmr.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/onBeforeMount.d.ts +1 -1
- package/dist/hooks/onBeforeMount.d.ts.map +1 -1
- package/dist/hooks/onBeforeMount.js +10 -3
- package/dist/hooks/onBeforeMount.js.map +1 -1
- package/dist/hooks/onCleanup.d.ts +1 -1
- package/dist/hooks/onCleanup.d.ts.map +1 -1
- package/dist/hooks/onCleanup.js +7 -4
- package/dist/hooks/onCleanup.js.map +1 -1
- package/dist/hooks/onMount.d.ts +2 -2
- package/dist/hooks/onMount.d.ts.map +1 -1
- package/dist/hooks/onMount.js +11 -4
- package/dist/hooks/onMount.js.map +1 -1
- package/dist/hooks/setup.d.ts +13 -0
- package/dist/hooks/setup.d.ts.map +1 -0
- package/dist/hooks/setup.js +54 -0
- package/dist/hooks/setup.js.map +1 -0
- package/dist/hooks/utils.d.ts +2 -3
- package/dist/hooks/utils.d.ts.map +1 -1
- package/dist/hooks/utils.js +9 -14
- package/dist/hooks/utils.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/reconciler.js +3 -3
- package/dist/reconciler.js.map +1 -1
- package/dist/router/head.js +2 -2
- package/dist/router/head.js.map +1 -1
- package/dist/router/pageConfig.js +2 -2
- package/dist/router/pageConfig.js.map +1 -1
- package/dist/scheduler.js +62 -57
- package/dist/scheduler.js.map +1 -1
- package/dist/signals/base.js +3 -3
- package/dist/signals/base.js.map +1 -1
- package/dist/signals/effect.d.ts.map +1 -1
- package/dist/signals/effect.js +6 -6
- package/dist/signals/effect.js.map +1 -1
- package/dist/signals/tracking.d.ts +3 -2
- package/dist/signals/tracking.d.ts.map +1 -1
- package/dist/signals/tracking.js.map +1 -1
- package/dist/statefulPromise.js +2 -2
- package/dist/statefulPromise.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.dom.d.ts +1 -1
- package/dist/types.dom.d.ts.map +1 -1
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +4 -1
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/vdom.d.ts +2 -2
- package/dist/utils/vdom.d.ts.map +1 -1
- package/dist/utils/vdom.js +2 -2
- package/dist/utils/vdom.js.map +1 -1
- package/dist/viewTransitions.d.ts.map +1 -1
- package/dist/viewTransitions.js +2 -1
- package/dist/viewTransitions.js.map +1 -1
- package/package.json +1 -1
- package/src/appHandle.ts +2 -2
- package/src/components/lazy.ts +5 -6
- package/src/components/transition.ts +2 -6
- package/src/devtools.ts +4 -2
- package/src/dom/commit.ts +133 -0
- package/src/dom/focus.ts +34 -0
- package/src/dom/index.ts +3 -0
- package/src/dom/nodes.ts +205 -0
- package/src/dom/props.ts +818 -0
- package/src/env.ts +3 -0
- package/src/globalContext.ts +7 -24
- package/src/globals.ts +25 -2
- package/src/hmr.ts +32 -5
- package/src/hooks/index.ts +1 -0
- package/src/hooks/onBeforeMount.ts +9 -3
- package/src/hooks/onCleanup.ts +10 -4
- package/src/hooks/onMount.ts +10 -4
- package/src/hooks/setup.ts +70 -0
- package/src/hooks/utils.ts +14 -19
- package/src/index.ts +4 -2
- package/src/reconciler.ts +3 -3
- package/src/router/head.ts +2 -2
- package/src/router/pageConfig.ts +2 -2
- package/src/scheduler.ts +79 -64
- package/src/signals/base.ts +3 -3
- package/src/signals/effect.ts +5 -7
- package/src/signals/tracking.ts +3 -2
- package/src/statefulPromise.ts +2 -2
- package/src/types.dom.ts +2 -2
- package/src/types.ts +7 -1
- package/src/utils/format.ts +3 -1
- package/src/utils/vdom.ts +2 -2
- package/src/viewTransitions.ts +2 -1
- package/dist/dom.d.ts +0 -10
- package/dist/dom.d.ts.map +0 -1
- package/dist/dom.js +0 -601
- package/dist/dom.js.map +0 -1
- package/src/dom.ts +0 -775
package/src/dom.ts
DELETED
|
@@ -1,775 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
traverseApply,
|
|
3
|
-
commitSnapshot,
|
|
4
|
-
propFilters,
|
|
5
|
-
propToHtmlAttr,
|
|
6
|
-
getVNodeApp,
|
|
7
|
-
setRef,
|
|
8
|
-
isValidTextChild,
|
|
9
|
-
registerVNodeCleanup,
|
|
10
|
-
call,
|
|
11
|
-
} from "./utils/index.js"
|
|
12
|
-
import {
|
|
13
|
-
booleanAttributes,
|
|
14
|
-
FLAG_PLACEMENT,
|
|
15
|
-
FLAG_UPDATE,
|
|
16
|
-
FLAG_STATIC_DOM,
|
|
17
|
-
svgTags,
|
|
18
|
-
EVENT_PREFIX_REGEX,
|
|
19
|
-
} from "./constants.js"
|
|
20
|
-
import { Signal } from "./signals/base.js"
|
|
21
|
-
import { unwrap } from "./signals/utils.js"
|
|
22
|
-
import { renderMode } from "./globals.js"
|
|
23
|
-
import { hydrationStack } from "./hydration.js"
|
|
24
|
-
import { StyleObject } from "./types.dom.js"
|
|
25
|
-
import { __DEV__ } from "./env.js"
|
|
26
|
-
import { KiruError } from "./error.js"
|
|
27
|
-
import type {
|
|
28
|
-
DomVNode,
|
|
29
|
-
ElementVNode,
|
|
30
|
-
MaybeDom,
|
|
31
|
-
SomeDom,
|
|
32
|
-
SomeElement,
|
|
33
|
-
} from "./types.utils"
|
|
34
|
-
import type { AppHandle } from "./appHandle.js"
|
|
35
|
-
|
|
36
|
-
export {
|
|
37
|
-
commitWork,
|
|
38
|
-
onBeforeFlushDomChanges,
|
|
39
|
-
onAfterFlushDomChanges,
|
|
40
|
-
commitDeletion,
|
|
41
|
-
createDom,
|
|
42
|
-
hydrateDom,
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
type VNode = Kiru.VNode
|
|
46
|
-
type HostNode = {
|
|
47
|
-
node: ElementVNode
|
|
48
|
-
lastChild?: SomeDom
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let persistingFocus = false
|
|
52
|
-
let didBlurActiveElement = false
|
|
53
|
-
const postHookCleanups: (() => void)[] = []
|
|
54
|
-
|
|
55
|
-
const placementBlurHandler = (event: Event) => {
|
|
56
|
-
event.preventDefault()
|
|
57
|
-
event.stopPropagation()
|
|
58
|
-
didBlurActiveElement = true
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
let currentActiveElement: Element | null = null
|
|
62
|
-
function onBeforeFlushDomChanges() {
|
|
63
|
-
persistingFocus = true
|
|
64
|
-
currentActiveElement = document.activeElement
|
|
65
|
-
if (currentActiveElement && currentActiveElement !== document.body) {
|
|
66
|
-
currentActiveElement.addEventListener("blur", placementBlurHandler)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function onAfterFlushDomChanges() {
|
|
71
|
-
if (didBlurActiveElement) {
|
|
72
|
-
currentActiveElement!.removeEventListener("blur", placementBlurHandler)
|
|
73
|
-
if (currentActiveElement!.isConnected) {
|
|
74
|
-
;(currentActiveElement as any).focus()
|
|
75
|
-
}
|
|
76
|
-
didBlurActiveElement = false
|
|
77
|
-
}
|
|
78
|
-
persistingFocus = false
|
|
79
|
-
queueMicrotask(() => {
|
|
80
|
-
postHookCleanups.forEach(call)
|
|
81
|
-
postHookCleanups.length = 0
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function createDom(vNode: DomVNode): SomeDom {
|
|
86
|
-
const t = vNode.type
|
|
87
|
-
const dom =
|
|
88
|
-
t == "#text"
|
|
89
|
-
? createTextNode(vNode)
|
|
90
|
-
: svgTags.has(t)
|
|
91
|
-
? document.createElementNS("http://www.w3.org/2000/svg", t)
|
|
92
|
-
: document.createElement(t)
|
|
93
|
-
|
|
94
|
-
return dom
|
|
95
|
-
}
|
|
96
|
-
function createTextNode(vNode: VNode): Text {
|
|
97
|
-
const { nodeValue } = vNode.props
|
|
98
|
-
if (Signal.isSignal(nodeValue)) {
|
|
99
|
-
return createSignalTextNode(vNode, nodeValue)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return document.createTextNode(nodeValue)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function createSignalTextNode(vNode: VNode, nodeValue: Signal<string>): Text {
|
|
106
|
-
const value = nodeValue.peek() ?? ""
|
|
107
|
-
const textNode = document.createTextNode(value)
|
|
108
|
-
subTextNode(vNode, textNode, nodeValue)
|
|
109
|
-
return textNode
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function wrapFocusEventHandler(callback: (event: FocusEvent) => void) {
|
|
113
|
-
return (event: FocusEvent) => {
|
|
114
|
-
if (persistingFocus) {
|
|
115
|
-
event.preventDefault()
|
|
116
|
-
event.stopPropagation()
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
callback(event)
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
interface VNodeEventListenerObjects {
|
|
124
|
-
[key: string]: EventListenerObject
|
|
125
|
-
}
|
|
126
|
-
const eventListenerObjects = new WeakMap<VNode, VNodeEventListenerObjects>()
|
|
127
|
-
|
|
128
|
-
function updateDom(vNode: DomVNode) {
|
|
129
|
-
const { dom, prev, props, cleanups } = vNode
|
|
130
|
-
const prevProps = prev?.props ?? {}
|
|
131
|
-
const nextProps = props ?? {}
|
|
132
|
-
const isHydration = renderMode.current === "hydrate"
|
|
133
|
-
|
|
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
|
|
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
|
-
let events: VNodeEventListenerObjects | undefined
|
|
150
|
-
for (let i = 0; i < keys.length; i++) {
|
|
151
|
-
const key = keys[i]
|
|
152
|
-
const prevVal = prevProps[key]
|
|
153
|
-
const nextVal = nextProps[key]
|
|
154
|
-
|
|
155
|
-
if (propFilters.isEvent(key)) {
|
|
156
|
-
events ??= eventListenerObjects.get(vNode)
|
|
157
|
-
if (!events) eventListenerObjects.set(vNode, (events = {}))
|
|
158
|
-
|
|
159
|
-
if (prevVal !== nextVal || isHydration) {
|
|
160
|
-
const evtName = key.replace(EVENT_PREFIX_REGEX, "")
|
|
161
|
-
const evtListenerObj = events[evtName]
|
|
162
|
-
|
|
163
|
-
if (!nextVal) {
|
|
164
|
-
if (evtListenerObj) {
|
|
165
|
-
dom.removeEventListener(evtName, evtListenerObj)
|
|
166
|
-
delete events[evtName]
|
|
167
|
-
}
|
|
168
|
-
continue
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
let handleEvent = nextVal.bind(void 0)
|
|
172
|
-
if (evtName === "focus" || evtName === "blur") {
|
|
173
|
-
handleEvent = wrapFocusEventHandler(handleEvent)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (evtListenerObj) {
|
|
177
|
-
evtListenerObj.handleEvent = handleEvent
|
|
178
|
-
continue
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
dom.addEventListener(evtName, (events[evtName] = { handleEvent }))
|
|
182
|
-
}
|
|
183
|
-
continue
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (propFilters.isInternalProp(key) && key !== "innerHTML") {
|
|
187
|
-
continue
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (prevVal === nextVal) {
|
|
191
|
-
continue
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (Signal.isSignal(prevVal) && cleanups) {
|
|
195
|
-
const disposer = cleanups[key]
|
|
196
|
-
if (disposer) {
|
|
197
|
-
disposer()
|
|
198
|
-
delete cleanups[key]
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (Signal.isSignal(nextVal)) {
|
|
203
|
-
setSignalProp(vNode, dom, key, nextVal, prevVal)
|
|
204
|
-
continue
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
setProp(dom, key, nextVal, prevVal)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const prevRef = prevProps.ref
|
|
211
|
-
const nextRef = nextProps.ref
|
|
212
|
-
if (prevRef !== nextRef) {
|
|
213
|
-
if (prevRef) setRef(prevRef, null)
|
|
214
|
-
if (nextRef) setRef(nextRef, dom)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function getSelectElementValue(dom: HTMLSelectElement) {
|
|
219
|
-
if (dom.multiple) {
|
|
220
|
-
return Array.from(dom.selectedOptions).map((option) => option.value)
|
|
221
|
-
}
|
|
222
|
-
return dom.value
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function setSelectElementValue(dom: HTMLSelectElement, value: any) {
|
|
226
|
-
if (!dom.multiple || value === undefined || value === null || value === "") {
|
|
227
|
-
dom.value = value
|
|
228
|
-
return
|
|
229
|
-
}
|
|
230
|
-
Array.from(dom.options).forEach((option) => {
|
|
231
|
-
option.selected = value.indexOf(option.value) > -1
|
|
232
|
-
})
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const bindAttrToEventMap: Record<string, string> = {
|
|
236
|
-
value: "input",
|
|
237
|
-
checked: "change",
|
|
238
|
-
open: "toggle",
|
|
239
|
-
volume: "volumechange",
|
|
240
|
-
playbackRate: "ratechange",
|
|
241
|
-
currentTime: "timeupdate",
|
|
242
|
-
}
|
|
243
|
-
const numericValueInputTypes = new Set(["progress", "meter", "number", "range"])
|
|
244
|
-
|
|
245
|
-
function setSignalProp(
|
|
246
|
-
vNode: VNode,
|
|
247
|
-
dom: Exclude<SomeDom, Text>,
|
|
248
|
-
key: string,
|
|
249
|
-
signal: Signal<any>,
|
|
250
|
-
prevValue: unknown
|
|
251
|
-
) {
|
|
252
|
-
const [modifier, attr] = key.split(":")
|
|
253
|
-
if (modifier !== "bind") {
|
|
254
|
-
const unsub = signal.subscribe((value, prev) => {
|
|
255
|
-
if (value === prev) return
|
|
256
|
-
setProp(dom, key, value, prev)
|
|
257
|
-
if (__DEV__) {
|
|
258
|
-
emitSignalAttrUpdate(vNode)
|
|
259
|
-
}
|
|
260
|
-
})
|
|
261
|
-
registerVNodeCleanup(vNode, key, unsub)
|
|
262
|
-
} else {
|
|
263
|
-
const evtName = bindAttrToEventMap[attr]
|
|
264
|
-
if (!evtName) {
|
|
265
|
-
if (__DEV__) {
|
|
266
|
-
console.error(
|
|
267
|
-
`[kiru]: ${attr} is not a valid element binding attribute.`
|
|
268
|
-
)
|
|
269
|
-
}
|
|
270
|
-
return
|
|
271
|
-
}
|
|
272
|
-
const cleanup = bindElementProp(vNode, dom, attr, evtName, signal)
|
|
273
|
-
registerVNodeCleanup(vNode, key, cleanup)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const value = signal.peek()
|
|
277
|
-
const prev = unwrap(prevValue)
|
|
278
|
-
if (value !== prev) {
|
|
279
|
-
setProp(dom, attr ?? modifier, value, prev)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function createElementValueReader(
|
|
284
|
-
dom: Exclude<SomeDom, Text>,
|
|
285
|
-
signal: Signal<any>
|
|
286
|
-
) {
|
|
287
|
-
if (dom instanceof HTMLInputElement) {
|
|
288
|
-
return createInputValueReader(dom, signal)
|
|
289
|
-
}
|
|
290
|
-
if (dom instanceof HTMLSelectElement) {
|
|
291
|
-
return () => getSelectElementValue(dom)
|
|
292
|
-
}
|
|
293
|
-
return () => (dom as any).value
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function bindElementProp(
|
|
297
|
-
vNode: VNode,
|
|
298
|
-
dom: Exclude<SomeDom, Text>,
|
|
299
|
-
attr: string,
|
|
300
|
-
evtName: string,
|
|
301
|
-
signal: Signal<any>
|
|
302
|
-
): () => void {
|
|
303
|
-
const writeToSignal = (val: any) => {
|
|
304
|
-
signal.sneak(val)
|
|
305
|
-
signal.notify((sub) => sub !== updateFromSignal)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const writeToElement =
|
|
309
|
-
dom instanceof HTMLSelectElement && attr === "value"
|
|
310
|
-
? (value: any) => setSelectElementValue(dom, value)
|
|
311
|
-
: (value: any) => ((dom as any)[attr] = value)
|
|
312
|
-
|
|
313
|
-
const updateFromSignal = (value: any) => {
|
|
314
|
-
writeToElement(value)
|
|
315
|
-
if (__DEV__) {
|
|
316
|
-
emitSignalAttrUpdate(vNode)
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
let evtHandler: EventListener
|
|
321
|
-
if (attr === "value") {
|
|
322
|
-
const readValue = createElementValueReader(dom, signal)
|
|
323
|
-
evtHandler = () => writeToSignal(readValue())
|
|
324
|
-
} else {
|
|
325
|
-
evtHandler = () => {
|
|
326
|
-
const val = (dom as any)[attr]
|
|
327
|
-
/**
|
|
328
|
-
* the 'timeupdate' event is fired when the currentTime property is
|
|
329
|
-
* set (from code OR playback), so we need to prevent unnecessary
|
|
330
|
-
* signal updates to avoid a feedback loop when there are multiple
|
|
331
|
-
* elements with the same signal bound to 'currentTime'
|
|
332
|
-
*/
|
|
333
|
-
if (attr === "currentTime" && signal.peek() === val) return
|
|
334
|
-
writeToSignal(val)
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
dom.addEventListener(evtName, evtHandler)
|
|
339
|
-
const unsub = signal.subscribe(updateFromSignal)
|
|
340
|
-
|
|
341
|
-
return () => {
|
|
342
|
-
dom.removeEventListener(evtName, evtHandler)
|
|
343
|
-
unsub()
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function createInputValueReader(
|
|
348
|
-
dom: HTMLInputElement,
|
|
349
|
-
signal: Signal<any>
|
|
350
|
-
): () => any {
|
|
351
|
-
const t = dom.type
|
|
352
|
-
const v = signal.peek()
|
|
353
|
-
|
|
354
|
-
if (t === "date" && v instanceof Date) {
|
|
355
|
-
return () => dom.valueAsDate
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (numericValueInputTypes.has(t) && typeof v === "number") {
|
|
359
|
-
return () => dom.valueAsNumber
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return () => dom.value
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function emitSignalAttrUpdate(vNode: VNode) {
|
|
366
|
-
window.__kiru.profilingContext?.emit("signalAttrUpdate", getVNodeApp(vNode)!)
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function subTextNode(vNode: VNode, textNode: Text, signal: Signal<string>) {
|
|
370
|
-
const cleanup = signal.subscribe((value, prev) => {
|
|
371
|
-
if (value === prev) return
|
|
372
|
-
textNode.nodeValue = value
|
|
373
|
-
if (__DEV__) {
|
|
374
|
-
window.__kiru.profilingContext?.emit(
|
|
375
|
-
"signalTextUpdate",
|
|
376
|
-
getVNodeApp(vNode)!
|
|
377
|
-
)
|
|
378
|
-
}
|
|
379
|
-
})
|
|
380
|
-
registerVNodeCleanup(vNode, "nodeValue", cleanup)
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Creates and inserts an empty signal-bound text node into
|
|
385
|
-
* the dom tree if the signal value is null or undefined.
|
|
386
|
-
*/
|
|
387
|
-
function getOrCreateTextNode(vNode: VNode): MaybeDom {
|
|
388
|
-
const sig = vNode.props.nodeValue
|
|
389
|
-
if (!Signal.isSignal(sig)) {
|
|
390
|
-
return hydrationStack.getCurrentChild()
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const value = sig.peek()
|
|
394
|
-
if (isValidTextChild(value)) {
|
|
395
|
-
return hydrationStack.getCurrentChild()
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const dom = createSignalTextNode(vNode, sig)
|
|
399
|
-
const currentChild = hydrationStack.getCurrentChild()
|
|
400
|
-
|
|
401
|
-
if (!currentChild) {
|
|
402
|
-
return hydrationStack.getCurrentParent().appendChild(dom)
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
currentChild.before(dom)
|
|
406
|
-
return dom
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function hydrateDom(vNode: VNode) {
|
|
410
|
-
const dom =
|
|
411
|
-
vNode.type === "#text"
|
|
412
|
-
? getOrCreateTextNode(vNode)
|
|
413
|
-
: hydrationStack.getCurrentChild()
|
|
414
|
-
|
|
415
|
-
hydrationStack.bumpChildIndex()
|
|
416
|
-
|
|
417
|
-
if (!dom) {
|
|
418
|
-
throw new KiruError({
|
|
419
|
-
message: `Hydration mismatch - no node found`,
|
|
420
|
-
vNode,
|
|
421
|
-
})
|
|
422
|
-
}
|
|
423
|
-
let nodeName = dom.nodeName
|
|
424
|
-
if (!svgTags.has(nodeName)) {
|
|
425
|
-
nodeName = nodeName.toLowerCase()
|
|
426
|
-
}
|
|
427
|
-
if ((vNode.type as string) !== nodeName) {
|
|
428
|
-
throw new KiruError({
|
|
429
|
-
message: `Hydration mismatch - expected node of type ${vNode.type.toString()} but received ${nodeName}`,
|
|
430
|
-
vNode,
|
|
431
|
-
})
|
|
432
|
-
}
|
|
433
|
-
vNode.dom = dom
|
|
434
|
-
if (vNode.type !== "#text" && !(vNode.flags & FLAG_STATIC_DOM)) {
|
|
435
|
-
updateDom(vNode as DomVNode)
|
|
436
|
-
return
|
|
437
|
-
}
|
|
438
|
-
if (Signal.isSignal(vNode.props.nodeValue)) {
|
|
439
|
-
subTextNode(vNode, dom as Text, vNode.props.nodeValue)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
let prev = vNode
|
|
443
|
-
let sibling = vNode.sibling
|
|
444
|
-
while (sibling && sibling.type === "#text") {
|
|
445
|
-
const sib = sibling
|
|
446
|
-
hydrationStack.bumpChildIndex()
|
|
447
|
-
const prevText = String(unwrap(prev.props.nodeValue) ?? "")
|
|
448
|
-
const dom = (prev.dom as Text).splitText(prevText.length)
|
|
449
|
-
sib.dom = dom
|
|
450
|
-
if (Signal.isSignal(sib.props.nodeValue)) {
|
|
451
|
-
subTextNode(sib, dom, sib.props.nodeValue)
|
|
452
|
-
}
|
|
453
|
-
prev = sibling
|
|
454
|
-
sibling = sibling.sibling
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function handleAttributeRemoval(
|
|
459
|
-
element: Element,
|
|
460
|
-
key: string,
|
|
461
|
-
value: unknown,
|
|
462
|
-
isBoolAttr = false
|
|
463
|
-
) {
|
|
464
|
-
if (value === null) {
|
|
465
|
-
element.removeAttribute(key)
|
|
466
|
-
return true
|
|
467
|
-
}
|
|
468
|
-
switch (typeof value) {
|
|
469
|
-
case "undefined":
|
|
470
|
-
case "function":
|
|
471
|
-
case "symbol": {
|
|
472
|
-
element.removeAttribute(key)
|
|
473
|
-
return true
|
|
474
|
-
}
|
|
475
|
-
case "boolean": {
|
|
476
|
-
if (isBoolAttr && !value) {
|
|
477
|
-
element.removeAttribute(key)
|
|
478
|
-
return true
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
return false
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
function setDomAttribute(element: Element, key: string, value: unknown) {
|
|
487
|
-
const isBoolAttr = booleanAttributes.has(key)
|
|
488
|
-
|
|
489
|
-
if (handleAttributeRemoval(element, key, value, isBoolAttr)) return
|
|
490
|
-
|
|
491
|
-
element.setAttribute(
|
|
492
|
-
key,
|
|
493
|
-
isBoolAttr && typeof value === "boolean" ? "" : String(value)
|
|
494
|
-
)
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const explicitValueElementTags = ["INPUT", "TEXTAREA"]
|
|
498
|
-
|
|
499
|
-
const needsExplicitValueSet = (
|
|
500
|
-
element: SomeElement
|
|
501
|
-
): element is HTMLInputElement | HTMLTextAreaElement => {
|
|
502
|
-
return explicitValueElementTags.indexOf(element.nodeName) > -1
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function setProp(
|
|
506
|
-
element: SomeElement,
|
|
507
|
-
key: string,
|
|
508
|
-
value: unknown,
|
|
509
|
-
prev: unknown
|
|
510
|
-
) {
|
|
511
|
-
switch (key) {
|
|
512
|
-
case "style":
|
|
513
|
-
return setStyleProp(element, value, prev)
|
|
514
|
-
case "className":
|
|
515
|
-
return setClassName(element, value)
|
|
516
|
-
case "innerHTML":
|
|
517
|
-
return setInnerHTML(element, value)
|
|
518
|
-
case "muted":
|
|
519
|
-
;(element as HTMLMediaElement).muted = Boolean(value)
|
|
520
|
-
return
|
|
521
|
-
case "value":
|
|
522
|
-
if (element.nodeName === "SELECT") {
|
|
523
|
-
return setSelectElementValue(element as HTMLSelectElement, value)
|
|
524
|
-
}
|
|
525
|
-
const strVal = value === undefined || value === null ? "" : String(value)
|
|
526
|
-
if (needsExplicitValueSet(element)) {
|
|
527
|
-
element.value = strVal
|
|
528
|
-
return
|
|
529
|
-
}
|
|
530
|
-
element.setAttribute("value", strVal)
|
|
531
|
-
return
|
|
532
|
-
case "checked":
|
|
533
|
-
if (element.nodeName === "INPUT") {
|
|
534
|
-
;(element as HTMLInputElement).checked = Boolean(value)
|
|
535
|
-
return
|
|
536
|
-
}
|
|
537
|
-
element.setAttribute("checked", String(value))
|
|
538
|
-
return
|
|
539
|
-
default:
|
|
540
|
-
return setDomAttribute(element, propToHtmlAttr(key), value)
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function setInnerHTML(element: SomeElement, value: unknown) {
|
|
545
|
-
if (value === null || value === undefined || typeof value === "boolean") {
|
|
546
|
-
element.innerHTML = ""
|
|
547
|
-
return
|
|
548
|
-
}
|
|
549
|
-
element.innerHTML = String(value)
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function setClassName(element: SomeElement, value: unknown) {
|
|
553
|
-
const val = unwrap(value)
|
|
554
|
-
if (!val) {
|
|
555
|
-
return element.removeAttribute("class")
|
|
556
|
-
}
|
|
557
|
-
element.setAttribute("class", val as string)
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
function setStyleProp(element: SomeElement, value: unknown, prev: unknown) {
|
|
561
|
-
if (handleAttributeRemoval(element, "style", value)) return
|
|
562
|
-
|
|
563
|
-
if (typeof value === "string") {
|
|
564
|
-
element.setAttribute("style", value)
|
|
565
|
-
return
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
let prevStyle: StyleObject = {}
|
|
569
|
-
if (typeof prev === "string") {
|
|
570
|
-
element.setAttribute("style", "")
|
|
571
|
-
} else if (typeof prev === "object" && !!prev) {
|
|
572
|
-
prevStyle = prev as StyleObject
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
const nextStyle = value as StyleObject
|
|
576
|
-
const keys = new Set([
|
|
577
|
-
...Object.keys(prevStyle),
|
|
578
|
-
...Object.keys(nextStyle),
|
|
579
|
-
]) as Set<keyof StyleObject>
|
|
580
|
-
|
|
581
|
-
keys.forEach((k) => {
|
|
582
|
-
const prev = prevStyle[k]
|
|
583
|
-
const next = nextStyle[k]
|
|
584
|
-
if (prev === next) return
|
|
585
|
-
|
|
586
|
-
if (next === undefined) {
|
|
587
|
-
element.style[k as any] = ""
|
|
588
|
-
return
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
element.style[k as any] = next as any
|
|
592
|
-
})
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function getDomParent(vNode: VNode): ElementVNode {
|
|
596
|
-
let parentNode: VNode | null = vNode.parent
|
|
597
|
-
let parentNodeElement = parentNode?.dom
|
|
598
|
-
while (parentNode && !parentNodeElement) {
|
|
599
|
-
parentNode = parentNode.parent
|
|
600
|
-
parentNodeElement = parentNode?.dom
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
if (!parentNodeElement || !parentNode) {
|
|
604
|
-
// handle app entry
|
|
605
|
-
if (!vNode.parent && vNode.dom) {
|
|
606
|
-
return vNode as ElementVNode
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
throw new KiruError({
|
|
610
|
-
message: "No DOM parent found while attempting to place node.",
|
|
611
|
-
vNode: vNode,
|
|
612
|
-
})
|
|
613
|
-
}
|
|
614
|
-
return parentNode as ElementVNode
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
function placeDom(vNode: DomVNode, hostNode: HostNode) {
|
|
618
|
-
const { node: parentVNodeWithDom, lastChild } = hostNode
|
|
619
|
-
const dom = vNode.dom
|
|
620
|
-
if (lastChild) {
|
|
621
|
-
lastChild.after(dom)
|
|
622
|
-
return
|
|
623
|
-
}
|
|
624
|
-
// TODO: we can probably skip the 'next sibling search' if we're appending
|
|
625
|
-
const nextSiblingDom = getNextSiblingDom(vNode, parentVNodeWithDom)
|
|
626
|
-
if (nextSiblingDom) {
|
|
627
|
-
parentVNodeWithDom.dom.insertBefore(dom, nextSiblingDom)
|
|
628
|
-
return
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
parentVNodeWithDom.dom.appendChild(dom)
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function getNextSiblingDom(vNode: VNode, parent: ElementVNode): MaybeDom {
|
|
635
|
-
let node: VNode | null = vNode
|
|
636
|
-
|
|
637
|
-
while (node) {
|
|
638
|
-
let sibling = node.sibling
|
|
639
|
-
|
|
640
|
-
while (sibling) {
|
|
641
|
-
// Skip unmounted, to-be-placed & static nodes
|
|
642
|
-
if (!(sibling.flags & (FLAG_PLACEMENT | FLAG_STATIC_DOM))) {
|
|
643
|
-
// Descend into the child to find host dom
|
|
644
|
-
const dom = findFirstHostDom(sibling)
|
|
645
|
-
if (dom?.isConnected) return dom
|
|
646
|
-
}
|
|
647
|
-
sibling = sibling.sibling
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// Move up to parent — but don't escape portal boundary
|
|
651
|
-
node = node.parent
|
|
652
|
-
if (!node || node.flags & FLAG_STATIC_DOM || node === parent) {
|
|
653
|
-
return
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
return
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
function findFirstHostDom(vNode: VNode): MaybeDom {
|
|
661
|
-
let node: VNode | null = vNode
|
|
662
|
-
|
|
663
|
-
while (node) {
|
|
664
|
-
if (node.dom) return node.dom
|
|
665
|
-
if (node.flags & FLAG_STATIC_DOM) return // Don't descend into portals
|
|
666
|
-
node = node.child
|
|
667
|
-
}
|
|
668
|
-
return
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function commitWork(vNode: VNode) {
|
|
672
|
-
if (renderMode.current === "hydrate") {
|
|
673
|
-
return traverseApply(vNode, commitSnapshot)
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
const host: HostNode = {
|
|
677
|
-
node: vNode.dom ? (vNode as ElementVNode) : getDomParent(vNode),
|
|
678
|
-
}
|
|
679
|
-
commitWork_impl(vNode, host, (vNode.flags & FLAG_PLACEMENT) > 0)
|
|
680
|
-
if (vNode.dom && !(vNode.flags & FLAG_STATIC_DOM)) {
|
|
681
|
-
commitDom(vNode as DomVNode, host, false)
|
|
682
|
-
}
|
|
683
|
-
commitSnapshot(vNode)
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
function commitWork_impl(
|
|
687
|
-
vNode: VNode,
|
|
688
|
-
currentHostNode: HostNode,
|
|
689
|
-
inheritsPlacement: boolean
|
|
690
|
-
) {
|
|
691
|
-
let child: VNode | null = vNode.child
|
|
692
|
-
while (child) {
|
|
693
|
-
if (child.dom) {
|
|
694
|
-
commitWork_impl(child, { node: child as ElementVNode }, false)
|
|
695
|
-
if (!(child.flags & FLAG_STATIC_DOM)) {
|
|
696
|
-
commitDom(child as DomVNode, currentHostNode, inheritsPlacement)
|
|
697
|
-
}
|
|
698
|
-
} else {
|
|
699
|
-
commitWork_impl(
|
|
700
|
-
child,
|
|
701
|
-
currentHostNode,
|
|
702
|
-
(child.flags & FLAG_PLACEMENT) > 0 || inheritsPlacement
|
|
703
|
-
)
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
commitSnapshot(child)
|
|
707
|
-
child = child.sibling
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
function commitDom(
|
|
712
|
-
vNode: DomVNode,
|
|
713
|
-
hostNode: HostNode,
|
|
714
|
-
inheritsPlacement: boolean
|
|
715
|
-
) {
|
|
716
|
-
if (
|
|
717
|
-
inheritsPlacement ||
|
|
718
|
-
!vNode.dom.isConnected ||
|
|
719
|
-
vNode.flags & FLAG_PLACEMENT
|
|
720
|
-
) {
|
|
721
|
-
placeDom(vNode, hostNode)
|
|
722
|
-
}
|
|
723
|
-
if (!vNode.prev || vNode.flags & FLAG_UPDATE) {
|
|
724
|
-
updateDom(vNode)
|
|
725
|
-
}
|
|
726
|
-
hostNode.lastChild = vNode.dom
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function commitDeletion(vNode: VNode) {
|
|
730
|
-
if (vNode === vNode.parent?.child) {
|
|
731
|
-
vNode.parent.child = vNode.sibling
|
|
732
|
-
}
|
|
733
|
-
let app: AppHandle
|
|
734
|
-
if (__DEV__) {
|
|
735
|
-
app = getVNodeApp(vNode)!
|
|
736
|
-
}
|
|
737
|
-
traverseApply(vNode, (node) => {
|
|
738
|
-
const {
|
|
739
|
-
subs,
|
|
740
|
-
cleanups,
|
|
741
|
-
dom,
|
|
742
|
-
props: { ref },
|
|
743
|
-
hooks,
|
|
744
|
-
} = node
|
|
745
|
-
|
|
746
|
-
subs?.forEach((unsub) => unsub())
|
|
747
|
-
if (cleanups) Object.values(cleanups).forEach((c) => c())
|
|
748
|
-
if (hooks) {
|
|
749
|
-
const { preCleanups, postCleanups } = hooks
|
|
750
|
-
|
|
751
|
-
preCleanups.forEach(call)
|
|
752
|
-
postHookCleanups.push(...postCleanups)
|
|
753
|
-
preCleanups.length = postCleanups.length = 0
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
if (__DEV__) {
|
|
757
|
-
window.__kiru.profilingContext?.emit("removeNode", app)
|
|
758
|
-
if (dom instanceof Element) {
|
|
759
|
-
delete dom.__kiruNode
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
if (dom) {
|
|
764
|
-
if (dom.isConnected && !(node.flags & FLAG_STATIC_DOM)) {
|
|
765
|
-
dom.remove()
|
|
766
|
-
}
|
|
767
|
-
if (ref) {
|
|
768
|
-
setRef(ref, null)
|
|
769
|
-
}
|
|
770
|
-
delete node.dom
|
|
771
|
-
}
|
|
772
|
-
})
|
|
773
|
-
|
|
774
|
-
vNode.parent = null
|
|
775
|
-
}
|