kiru 1.0.1 → 1.1.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 (145) hide show
  1. package/dist/appHandle.js +2 -2
  2. package/dist/appHandle.js.map +1 -1
  3. package/dist/components/lazy.d.ts.map +1 -1
  4. package/dist/components/lazy.js +2 -2
  5. package/dist/components/lazy.js.map +1 -1
  6. package/dist/components/transition.js +1 -5
  7. package/dist/components/transition.js.map +1 -1
  8. package/dist/devtools.d.ts.map +1 -1
  9. package/dist/devtools.js +6 -2
  10. package/dist/devtools.js.map +1 -1
  11. package/dist/dom/commit.d.ts +5 -0
  12. package/dist/dom/commit.d.ts.map +1 -0
  13. package/dist/dom/commit.js +94 -0
  14. package/dist/dom/commit.js.map +1 -0
  15. package/dist/dom/focus.d.ts +4 -0
  16. package/dist/dom/focus.d.ts.map +1 -0
  17. package/dist/dom/focus.js +32 -0
  18. package/dist/dom/focus.js.map +1 -0
  19. package/dist/dom/index.d.ts +4 -0
  20. package/dist/dom/index.d.ts.map +1 -0
  21. package/dist/dom/index.js +4 -0
  22. package/dist/dom/index.js.map +1 -0
  23. package/dist/dom/nodes.d.ts +12 -0
  24. package/dist/dom/nodes.d.ts.map +1 -0
  25. package/dist/dom/nodes.js +165 -0
  26. package/dist/dom/nodes.js.map +1 -0
  27. package/dist/dom/props.d.ts +8 -0
  28. package/dist/dom/props.d.ts.map +1 -0
  29. package/dist/dom/props.js +675 -0
  30. package/dist/dom/props.js.map +1 -0
  31. package/dist/env.d.ts +2 -0
  32. package/dist/env.d.ts.map +1 -1
  33. package/dist/env.js +2 -0
  34. package/dist/env.js.map +1 -1
  35. package/dist/globalContext.d.ts +3 -8
  36. package/dist/globalContext.d.ts.map +1 -1
  37. package/dist/globalContext.js +4 -16
  38. package/dist/globalContext.js.map +1 -1
  39. package/dist/globals.d.ts +21 -1
  40. package/dist/globals.d.ts.map +1 -1
  41. package/dist/globals.js +22 -2
  42. package/dist/globals.js.map +1 -1
  43. package/dist/hmr.d.ts +17 -2
  44. package/dist/hmr.d.ts.map +1 -1
  45. package/dist/hmr.js +31 -5
  46. package/dist/hmr.js.map +1 -1
  47. package/dist/hooks/index.d.ts +1 -0
  48. package/dist/hooks/index.d.ts.map +1 -1
  49. package/dist/hooks/index.js +1 -0
  50. package/dist/hooks/index.js.map +1 -1
  51. package/dist/hooks/onBeforeMount.d.ts +1 -1
  52. package/dist/hooks/onBeforeMount.d.ts.map +1 -1
  53. package/dist/hooks/onBeforeMount.js +10 -3
  54. package/dist/hooks/onBeforeMount.js.map +1 -1
  55. package/dist/hooks/onCleanup.d.ts +1 -1
  56. package/dist/hooks/onCleanup.d.ts.map +1 -1
  57. package/dist/hooks/onCleanup.js +7 -4
  58. package/dist/hooks/onCleanup.js.map +1 -1
  59. package/dist/hooks/onMount.d.ts +2 -2
  60. package/dist/hooks/onMount.d.ts.map +1 -1
  61. package/dist/hooks/onMount.js +11 -4
  62. package/dist/hooks/onMount.js.map +1 -1
  63. package/dist/hooks/setup.d.ts +13 -0
  64. package/dist/hooks/setup.d.ts.map +1 -0
  65. package/dist/hooks/setup.js +54 -0
  66. package/dist/hooks/setup.js.map +1 -0
  67. package/dist/hooks/utils.d.ts +2 -3
  68. package/dist/hooks/utils.d.ts.map +1 -1
  69. package/dist/hooks/utils.js +9 -14
  70. package/dist/hooks/utils.js.map +1 -1
  71. package/dist/index.d.ts +1 -0
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +4 -3
  74. package/dist/index.js.map +1 -1
  75. package/dist/reconciler.js +3 -3
  76. package/dist/reconciler.js.map +1 -1
  77. package/dist/router/head.js +2 -2
  78. package/dist/router/head.js.map +1 -1
  79. package/dist/router/pageConfig.js +2 -2
  80. package/dist/router/pageConfig.js.map +1 -1
  81. package/dist/scheduler.js +62 -57
  82. package/dist/scheduler.js.map +1 -1
  83. package/dist/signals/base.js +3 -3
  84. package/dist/signals/base.js.map +1 -1
  85. package/dist/signals/effect.d.ts.map +1 -1
  86. package/dist/signals/effect.js +6 -6
  87. package/dist/signals/effect.js.map +1 -1
  88. package/dist/signals/tracking.d.ts +3 -2
  89. package/dist/signals/tracking.d.ts.map +1 -1
  90. package/dist/signals/tracking.js.map +1 -1
  91. package/dist/statefulPromise.js +2 -2
  92. package/dist/statefulPromise.js.map +1 -1
  93. package/dist/types.d.ts +5 -1
  94. package/dist/types.d.ts.map +1 -1
  95. package/dist/types.dom.d.ts +1 -1
  96. package/dist/types.dom.d.ts.map +1 -1
  97. package/dist/utils/format.d.ts.map +1 -1
  98. package/dist/utils/format.js +4 -1
  99. package/dist/utils/format.js.map +1 -1
  100. package/dist/utils/vdom.d.ts +2 -2
  101. package/dist/utils/vdom.d.ts.map +1 -1
  102. package/dist/utils/vdom.js +2 -2
  103. package/dist/utils/vdom.js.map +1 -1
  104. package/dist/viewTransitions.d.ts.map +1 -1
  105. package/dist/viewTransitions.js +2 -1
  106. package/dist/viewTransitions.js.map +1 -1
  107. package/package.json +1 -1
  108. package/src/appHandle.ts +2 -2
  109. package/src/components/lazy.ts +5 -6
  110. package/src/components/transition.ts +2 -6
  111. package/src/devtools.ts +4 -2
  112. package/src/dom/commit.ts +133 -0
  113. package/src/dom/focus.ts +34 -0
  114. package/src/dom/index.ts +3 -0
  115. package/src/dom/nodes.ts +204 -0
  116. package/src/dom/props.ts +818 -0
  117. package/src/env.ts +3 -0
  118. package/src/globalContext.ts +7 -24
  119. package/src/globals.ts +25 -2
  120. package/src/hmr.ts +32 -5
  121. package/src/hooks/index.ts +1 -0
  122. package/src/hooks/onBeforeMount.ts +9 -3
  123. package/src/hooks/onCleanup.ts +10 -4
  124. package/src/hooks/onMount.ts +10 -4
  125. package/src/hooks/setup.ts +70 -0
  126. package/src/hooks/utils.ts +14 -19
  127. package/src/index.ts +4 -2
  128. package/src/reconciler.ts +3 -3
  129. package/src/router/head.ts +2 -2
  130. package/src/router/pageConfig.ts +2 -2
  131. package/src/scheduler.ts +79 -64
  132. package/src/signals/base.ts +3 -3
  133. package/src/signals/effect.ts +5 -7
  134. package/src/signals/tracking.ts +3 -2
  135. package/src/statefulPromise.ts +2 -2
  136. package/src/types.dom.ts +2 -2
  137. package/src/types.ts +7 -1
  138. package/src/utils/format.ts +3 -1
  139. package/src/utils/vdom.ts +2 -2
  140. package/src/viewTransitions.ts +2 -1
  141. package/dist/dom.d.ts +0 -10
  142. package/dist/dom.d.ts.map +0 -1
  143. package/dist/dom.js +0 -601
  144. package/dist/dom.js.map +0 -1
  145. package/src/dom.ts +0 -775
package/src/devtools.ts CHANGED
@@ -2,15 +2,17 @@ import type { DebuggerEntry } from "./globalContext"
2
2
 
3
3
  export namespace DevTools {
4
4
  export const track = (signal: Kiru.Signal<unknown>, label?: string) => {
5
+ if (!("window" in globalThis)) return
5
6
  window.__kiru.devtools?.track(signal, label)
6
7
  }
7
8
  export const untrack = (signal: Kiru.Signal<unknown>) => {
9
+ if (!("window" in globalThis)) return
8
10
  window.__kiru.devtools?.untrack(signal)
9
11
  }
10
12
  export const subscribe = (
11
13
  callback: (entries: Set<DebuggerEntry>) => void
12
14
  ) => {
13
- if (!window.__kiru.devtools) return () => {}
14
- return window.__kiru.devtools?.subscribe(callback)
15
+ if (!("window" in globalThis) || !window.__kiru.devtools) return () => {}
16
+ return window.__kiru.devtools.subscribe(callback)
15
17
  }
16
18
  }
@@ -0,0 +1,133 @@
1
+ import {
2
+ traverseApply,
3
+ commitSnapshot,
4
+ getVNodeApp,
5
+ setRef,
6
+ call,
7
+ } from "../utils/index.js"
8
+ import { FLAG_PLACEMENT, FLAG_STATIC_DOM, FLAG_UPDATE } from "../constants.js"
9
+ import { __DEV__ } from "../env.js"
10
+ import { postEffectCleanups, renderMode } from "../globals.js"
11
+ import { isHmrUpdate } from "../hmr.js"
12
+ import { unmountDomProps, updateDomProps } from "./props.js"
13
+ import { HostNode, getDomParent, placeDom } from "./nodes.js"
14
+ import type { AppHandle } from "../appHandle.js"
15
+ import type { DomVNode, ElementVNode } from "../types.utils"
16
+
17
+ export { commitWork, commitDeletion }
18
+
19
+ type VNode = Kiru.VNode
20
+
21
+ function commitWork(vNode: VNode) {
22
+ if (renderMode.current === "hydrate") {
23
+ return traverseApply(vNode, commitSnapshot)
24
+ }
25
+
26
+ const host: HostNode = {
27
+ node: vNode.dom ? (vNode as ElementVNode) : getDomParent(vNode),
28
+ }
29
+ commitWork_impl(vNode, host, (vNode.flags & FLAG_PLACEMENT) > 0)
30
+ if (vNode.dom && !(vNode.flags & FLAG_STATIC_DOM)) {
31
+ commitDom(vNode as DomVNode, host, false)
32
+ }
33
+ commitSnapshot(vNode)
34
+ }
35
+
36
+ function commitWork_impl(
37
+ vNode: VNode,
38
+ currentHostNode: HostNode,
39
+ inheritsPlacement: boolean
40
+ ) {
41
+ let child: VNode | null = vNode.child
42
+ while (child) {
43
+ if (child.dom) {
44
+ commitWork_impl(child, { node: child as ElementVNode }, false)
45
+ if (!(child.flags & FLAG_STATIC_DOM)) {
46
+ commitDom(child as DomVNode, currentHostNode, inheritsPlacement)
47
+ }
48
+ } else {
49
+ commitWork_impl(
50
+ child,
51
+ currentHostNode,
52
+ (child.flags & FLAG_PLACEMENT) > 0 || inheritsPlacement
53
+ )
54
+ }
55
+
56
+ commitSnapshot(child)
57
+ child = child.sibling
58
+ }
59
+ }
60
+
61
+ function commitDom(
62
+ vNode: DomVNode,
63
+ hostNode: HostNode,
64
+ inheritsPlacement: boolean
65
+ ) {
66
+ if (
67
+ inheritsPlacement ||
68
+ !vNode.dom.isConnected ||
69
+ vNode.flags & FLAG_PLACEMENT
70
+ ) {
71
+ placeDom(vNode, hostNode)
72
+ }
73
+ // During HMR we want to fully unmount previous props (events, signal
74
+ // subscriptions, style listeners, refs, etc.) before applying the new ones,
75
+ // so that we don't merge stale props with the new shape.
76
+ if (__DEV__ && vNode.prev && isHmrUpdate()) {
77
+ const { dom, prev, cleanups } = vNode
78
+ unmountDomProps(vNode, dom, prev.props, cleanups)
79
+ vNode.prev = null
80
+ }
81
+ if (!vNode.prev || vNode.flags & FLAG_UPDATE) {
82
+ updateDomProps(vNode)
83
+ }
84
+ hostNode.lastChild = vNode.dom
85
+ }
86
+
87
+ function commitDeletion(vNode: VNode) {
88
+ if (vNode === vNode.parent?.child) {
89
+ vNode.parent.child = vNode.sibling
90
+ }
91
+ let app: AppHandle
92
+ if (__DEV__) {
93
+ app = getVNodeApp(vNode)!
94
+ }
95
+ traverseApply(vNode, (node) => {
96
+ const {
97
+ subs,
98
+ cleanups,
99
+ dom,
100
+ props: { ref },
101
+ hooks,
102
+ } = node
103
+
104
+ subs?.forEach((unsub) => unsub())
105
+ if (cleanups) Object.values(cleanups).forEach((c) => c())
106
+ if (hooks) {
107
+ const { preCleanups, postCleanups } = hooks
108
+
109
+ postEffectCleanups.push(...postCleanups)
110
+ preCleanups.forEach(call)
111
+ preCleanups.length = postCleanups.length = 0
112
+ }
113
+
114
+ if (__DEV__) {
115
+ window.__kiru.profilingContext?.emit("removeNode", app)
116
+ if (dom instanceof Element) {
117
+ delete dom.__kiruNode
118
+ }
119
+ }
120
+
121
+ if (dom) {
122
+ if (dom.isConnected && !(node.flags & FLAG_STATIC_DOM)) {
123
+ dom.remove()
124
+ }
125
+ if (ref) {
126
+ setRef(ref, null)
127
+ }
128
+ delete node.dom
129
+ }
130
+ })
131
+
132
+ vNode.parent = null
133
+ }
@@ -0,0 +1,34 @@
1
+ let focussedElement: HTMLElement | null = null
2
+
3
+ export function captureFocus() {
4
+ const el = document.activeElement
5
+ if (el === document.body || !(el instanceof HTMLElement)) {
6
+ return
7
+ }
8
+ el.addEventListener("blur", placementBlurHandler)
9
+ focussedElement = el
10
+ }
11
+
12
+ export function reinstateFocus() {
13
+ if (focussedElement) {
14
+ focussedElement.removeEventListener("blur", placementBlurHandler)
15
+ if (focussedElement.isConnected) focussedElement.focus()
16
+ focussedElement = null
17
+ }
18
+ }
19
+
20
+ export function wrapFocusEventHandler(callback: (event: FocusEvent) => void) {
21
+ return (event: FocusEvent) => {
22
+ if (focussedElement) {
23
+ event.preventDefault()
24
+ event.stopPropagation()
25
+ return
26
+ }
27
+ callback(event)
28
+ }
29
+ }
30
+
31
+ function placementBlurHandler(event: FocusEvent) {
32
+ event.preventDefault()
33
+ event.stopPropagation()
34
+ }
@@ -0,0 +1,3 @@
1
+ export { commitDeletion, commitWork } from "./commit.js"
2
+ export { reinstateFocus, captureFocus } from "./focus.js"
3
+ export { createDom, hydrateDom } from "./nodes.js"
@@ -0,0 +1,204 @@
1
+ import { svgTags, FLAG_PLACEMENT, FLAG_STATIC_DOM } from "../constants.js"
2
+ import { Signal } from "../signals/base.js"
3
+ import { unwrap } from "../signals/utils.js"
4
+ import { hydrationStack } from "../hydration.js"
5
+ import {
6
+ getVNodeApp,
7
+ isValidTextChild,
8
+ registerVNodeCleanup,
9
+ } from "../utils/index.js"
10
+ import { KiruError } from "../error.js"
11
+ import { __DEV__, isBrowser } from "../env.js"
12
+ import type { DomVNode, ElementVNode, MaybeDom, SomeDom } from "../types.utils"
13
+
14
+ export { createDom, hydrateDom, getDomParent, placeDom }
15
+
16
+ type VNode = Kiru.VNode
17
+
18
+ export type HostNode = {
19
+ node: ElementVNode
20
+ lastChild?: SomeDom
21
+ }
22
+
23
+ function createDom(vNode: DomVNode): SomeDom {
24
+ const t = vNode.type
25
+ const dom =
26
+ t == "#text"
27
+ ? createTextNode(vNode)
28
+ : svgTags.has(t)
29
+ ? document.createElementNS("http://www.w3.org/2000/svg", t)
30
+ : document.createElement(t)
31
+
32
+ return dom
33
+ }
34
+
35
+ function hydrateDom(vNode: VNode) {
36
+ const dom =
37
+ vNode.type === "#text"
38
+ ? getOrCreateTextNode(vNode)
39
+ : hydrationStack.getCurrentChild()
40
+
41
+ hydrationStack.bumpChildIndex()
42
+
43
+ if (!dom) {
44
+ throw new KiruError({
45
+ message: `Hydration mismatch - no node found`,
46
+ vNode,
47
+ })
48
+ }
49
+ let nodeName = dom.nodeName
50
+ if (!svgTags.has(nodeName)) {
51
+ nodeName = nodeName.toLowerCase()
52
+ }
53
+ if ((vNode.type as string) !== nodeName) {
54
+ throw new KiruError({
55
+ message: `Hydration mismatch - expected node of type ${vNode.type.toString()} but received ${nodeName}`,
56
+ vNode,
57
+ })
58
+ }
59
+ vNode.dom = dom
60
+ if (vNode.type !== "#text" && !(vNode.flags & FLAG_STATIC_DOM)) {
61
+ // updateDom is called later during commit phase
62
+ return
63
+ }
64
+ if (Signal.isSignal(vNode.props.nodeValue)) {
65
+ subTextNode(vNode, dom as Text, vNode.props.nodeValue)
66
+ }
67
+
68
+ let prev = vNode
69
+ let sibling = vNode.sibling
70
+ while (sibling && sibling.type === "#text") {
71
+ const sib = sibling
72
+ hydrationStack.bumpChildIndex()
73
+ const prevText = String(unwrap(prev.props.nodeValue) ?? "")
74
+ const dom = (prev.dom as Text).splitText(prevText.length)
75
+ sib.dom = dom
76
+ if (Signal.isSignal(sib.props.nodeValue)) {
77
+ subTextNode(sib, dom, sib.props.nodeValue)
78
+ }
79
+ prev = sibling
80
+ sibling = sibling.sibling
81
+ }
82
+ }
83
+
84
+ function getDomParent(vNode: VNode): ElementVNode {
85
+ let parentNode: VNode | null = vNode.parent
86
+ let parentNodeElement = parentNode?.dom
87
+ while (parentNode && !parentNodeElement) {
88
+ parentNode = parentNode.parent
89
+ parentNodeElement = parentNode?.dom
90
+ }
91
+
92
+ if (!parentNodeElement || !parentNode) {
93
+ if (!vNode.parent && vNode.dom) {
94
+ return vNode as ElementVNode
95
+ }
96
+
97
+ throw new KiruError({
98
+ message: "No DOM parent found while attempting to place node.",
99
+ vNode: vNode,
100
+ })
101
+ }
102
+ return parentNode as ElementVNode
103
+ }
104
+
105
+ function placeDom(vNode: DomVNode, hostNode: HostNode) {
106
+ const { node: parentVNodeWithDom, lastChild } = hostNode
107
+ const dom = vNode.dom
108
+ if (lastChild) {
109
+ lastChild.after(dom)
110
+ return
111
+ }
112
+ const nextSiblingDom = getNextSiblingDom(vNode, parentVNodeWithDom)
113
+ if (nextSiblingDom) {
114
+ parentVNodeWithDom.dom.insertBefore(dom, nextSiblingDom)
115
+ return
116
+ }
117
+
118
+ parentVNodeWithDom.dom.appendChild(dom)
119
+ }
120
+
121
+ function getNextSiblingDom(vNode: VNode, parent: ElementVNode): MaybeDom {
122
+ let node: VNode | null = vNode
123
+
124
+ while (node) {
125
+ let sibling = node.sibling
126
+
127
+ while (sibling) {
128
+ if (!(sibling.flags & (FLAG_PLACEMENT | FLAG_STATIC_DOM))) {
129
+ const dom = findFirstHostDom(sibling)
130
+ if (dom?.isConnected) return dom
131
+ }
132
+ sibling = sibling.sibling
133
+ }
134
+
135
+ node = node.parent
136
+ if (!node || node.flags & FLAG_STATIC_DOM || node === parent) {
137
+ return
138
+ }
139
+ }
140
+
141
+ return
142
+ }
143
+
144
+ function findFirstHostDom(vNode: VNode): MaybeDom {
145
+ let node: VNode | null = vNode
146
+ while (node) {
147
+ if (node.dom) return node.dom
148
+ if (node.flags & FLAG_STATIC_DOM) return
149
+ node = node.child
150
+ }
151
+ return
152
+ }
153
+
154
+ function getOrCreateTextNode(vNode: VNode): MaybeDom {
155
+ const sig = vNode.props.nodeValue
156
+ if (!Signal.isSignal(sig)) {
157
+ return hydrationStack.getCurrentChild()
158
+ }
159
+
160
+ const value = sig.peek()
161
+ if (isValidTextChild(value)) {
162
+ return hydrationStack.getCurrentChild()
163
+ }
164
+
165
+ const dom = createSignalTextNode(vNode, sig)
166
+ const currentChild = hydrationStack.getCurrentChild()
167
+
168
+ if (!currentChild) {
169
+ return hydrationStack.getCurrentParent().appendChild(dom)
170
+ }
171
+
172
+ currentChild.before(dom)
173
+ return dom
174
+ }
175
+
176
+ function subTextNode(vNode: VNode, textNode: Text, signal: Signal<string>) {
177
+ const cleanup = signal.subscribe((value, prev) => {
178
+ if (value === prev) return
179
+ textNode.nodeValue = value
180
+ if (__DEV__ && isBrowser) {
181
+ window.__kiru?.profilingContext?.emit(
182
+ "signalTextUpdate",
183
+ getVNodeApp(vNode)!
184
+ )
185
+ }
186
+ })
187
+ registerVNodeCleanup(vNode, "nodeValue", cleanup)
188
+ }
189
+
190
+ function createTextNode(vNode: VNode): Text {
191
+ const { nodeValue } = vNode.props
192
+ if (Signal.isSignal(nodeValue)) {
193
+ return createSignalTextNode(vNode, nodeValue)
194
+ }
195
+
196
+ return document.createTextNode(nodeValue)
197
+ }
198
+
199
+ function createSignalTextNode(vNode: VNode, nodeValue: Signal<string>): Text {
200
+ const value = nodeValue.peek() ?? ""
201
+ const textNode = document.createTextNode(value)
202
+ subTextNode(vNode, textNode, nodeValue)
203
+ return textNode
204
+ }