acrylblends 0.6.2 → 0.6.3
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/README.md +4 -1
- package/docs/framework/README.md +3 -2
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/cordis/acryl-in-a-cordis-app.md +6 -0
- package/docs/framework/getting-started/basic.md +1 -0
- package/docs/framework/getting-started/desktop.md +41 -0
- package/docs/framework/guides/jsx-dom.md +0 -3
- package/docs/framework/guides/jsx.md +0 -3
- package/docs/framework/helpers/css.md +0 -3
- package/docs/framework/upstream.json +3 -3
- package/lib/accepts.d.ts +1 -1
- package/lib/acryl-base.d.ts +1 -1
- package/lib/adapter.d.ts +1 -1
- package/lib/{app-BdrVHBlJ.d.ts → app-wFwpg-bt.d.ts} +1 -1
- package/lib/base-BpQy2Rk7.d.ts +783 -0
- package/lib/base-ddiVmiE7.js +721 -0
- package/lib/basic-auth.d.ts +1 -1
- package/lib/bearer-auth.d.ts +1 -1
- package/lib/body-limit.d.ts +1 -1
- package/lib/cache.d.ts +1 -1
- package/lib/client.d.ts +2 -2
- package/lib/combine.d.ts +1 -1
- package/lib/common-D8Z4JRCw.d.ts +46 -0
- package/lib/components-Ce5FRyHU.js +268 -0
- package/lib/components-DYZdxJIJ.js +24 -0
- package/lib/compress.d.ts +1 -1
- package/lib/conninfo.d.ts +2 -2
- package/lib/constants-BR9jM9BC.js +9 -0
- package/lib/{context-B5Y1lLha.d.ts → context-DVQBjuns.d.ts} +1 -1
- package/lib/context-DxFdY_un.d.ts +250 -0
- package/lib/context-storage.d.ts +1 -1
- package/lib/{cookie-D9_nQbsM.d.ts → cookie-CCelEqky.d.ts} +1 -1
- package/lib/cookie.d.ts +3 -3
- package/lib/cors.d.ts +1 -1
- package/lib/csrf.d.ts +1 -1
- package/lib/css-D2jb7X8b.js +320 -0
- package/lib/css.d.ts +82 -0
- package/lib/css.js +134 -0
- package/lib/dev.d.ts +2 -2
- package/lib/etag.d.ts +1 -1
- package/lib/factory.d.ts +2 -2
- package/lib/hooks-Bq5q9IMl.js +762 -0
- package/lib/html-SllAe0YT.d.ts +46 -0
- package/lib/html.d.ts +1 -1
- package/lib/index-C850MeBA.d.ts +94 -0
- package/lib/{index-m12zcmph.d.ts → index-CUTfU69R.d.ts} +3 -3
- package/lib/{index-DtEoak1i.d.ts → index-Cqcdluyn.d.ts} +2 -2
- package/lib/{index-Kqou8Fjh.d.ts → index-CsvB-F9j.d.ts} +2 -2
- package/lib/index.d.ts +4 -4
- package/lib/ip-restriction.d.ts +2 -2
- package/lib/jsx/dom/client.d.ts +30 -0
- package/lib/jsx/dom/client.js +49 -0
- package/lib/jsx/dom/css.d.ts +84 -0
- package/lib/jsx/dom/css.js +2 -0
- package/lib/jsx/dom/jsx-dev-runtime.d.ts +3 -0
- package/lib/jsx/dom/jsx-dev-runtime.js +2 -0
- package/lib/jsx/dom/jsx-runtime.d.ts +3 -0
- package/lib/jsx/dom/jsx-runtime.js +2 -0
- package/lib/jsx/dom/server.d.ts +105 -0
- package/lib/jsx/dom/server.js +37 -0
- package/lib/jsx/dom.d.ts +5 -0
- package/lib/jsx/dom.js +74 -0
- package/lib/jsx/jsx-dev-runtime.d.ts +6 -0
- package/lib/jsx/jsx-dev-runtime.js +14 -0
- package/lib/jsx/jsx-runtime.d.ts +10 -0
- package/lib/jsx/jsx-runtime.js +30 -0
- package/lib/jsx/streaming.d.ts +2 -0
- package/lib/jsx/streaming.js +2 -0
- package/lib/jsx-dev-runtime-B22O-Npk.d.ts +7 -0
- package/lib/jsx-dev-runtime-kAhR_4C7.js +354 -0
- package/lib/jsx-runtime-BVVgDSdq.d.ts +1 -0
- package/lib/jsx.d.ts +3 -0
- package/lib/jsx.js +47 -0
- package/lib/jwk.d.ts +4 -4
- package/lib/{jwt-BA65QhnJ.d.ts → jwt-BARhotDz.d.ts} +1 -1
- package/lib/jwt.d.ts +4 -4
- package/lib/language.d.ts +1 -1
- package/lib/logger.d.ts +1 -1
- package/lib/loopback-only.d.ts +1 -1
- package/lib/method-not-allowed.d.ts +2 -2
- package/lib/method-override.d.ts +2 -2
- package/lib/mount.d.ts +1 -1
- package/lib/node-server/conninfo.d.ts +1 -1
- package/lib/node-server/serve-static.d.ts +1 -1
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.d.ts +1 -1
- package/lib/node-server.js +1 -1
- package/lib/powered-by.d.ts +1 -1
- package/lib/pretty-json.d.ts +1 -1
- package/lib/proxy.d.ts +1 -1
- package/lib/quick.d.ts +1 -1
- package/lib/request-id.d.ts +1 -1
- package/lib/request.d.ts +1 -1
- package/lib/route.d.ts +1 -1
- package/lib/schema-validator.d.ts +1 -1
- package/lib/secure-headers.d.ts +1 -1
- package/lib/{serve-static-Dw_m6KxR.js → serve-static-Cw23shXv.js} +1 -0
- package/lib/serve-static.d.ts +1 -1
- package/lib/streaming.d.ts +1 -1
- package/lib/testing.d.ts +4 -4
- package/lib/timeout.d.ts +1 -1
- package/lib/timing.d.ts +1 -1
- package/lib/tiny.d.ts +1 -1
- package/lib/trailing-slash.d.ts +1 -1
- package/lib/{types-Bd29gh2o.d.ts → types-CQiGmsWW.d.ts} +3 -3
- package/lib/{types-D9DEXMjE.d.ts → types-Lqx5BxQF.d.ts} +8 -1
- package/lib/{types-ZM9gIiKh.d.ts → types-nEL8VPDC.d.ts} +1 -1
- package/lib/types.d.ts +1 -1
- package/lib/utils/body.d.ts +1 -1
- package/lib/utils/cookie.d.ts +1 -1
- package/lib/utils/crypto.d.ts +1 -1
- package/lib/utils/html.d.ts +1 -45
- package/lib/utils/ipaddr.d.ts +1 -1
- package/lib/utils/jwt.d.ts +1 -1
- package/lib/utils/types.d.ts +2 -2
- package/lib/validator.d.ts +2 -2
- package/lib/ws.d.ts +1 -1
- package/package.json +45 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-node-server/upstream.json +1 -1
- package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +506 -0
- package/plugins/acryl-router/src/core/helper/css/common.ts +337 -0
- package/plugins/acryl-router/src/core/helper/css/index.test.tsx +678 -0
- package/plugins/acryl-router/src/core/helper/css/index.ts +262 -0
- package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +557 -0
- package/plugins/acryl-router/src/core/jsx/base.test.tsx +97 -0
- package/plugins/acryl-router/src/core/jsx/base.ts +490 -0
- package/plugins/acryl-router/src/core/jsx/children.test.ts +52 -0
- package/plugins/acryl-router/src/core/jsx/children.ts +20 -0
- package/plugins/acryl-router/src/core/jsx/components.test.tsx +936 -0
- package/plugins/acryl-router/src/core/jsx/components.ts +257 -0
- package/plugins/acryl-router/src/core/jsx/constants.ts +6 -0
- package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +655 -0
- package/plugins/acryl-router/src/core/jsx/context.ts +266 -0
- package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +128 -0
- package/plugins/acryl-router/src/core/jsx/dom/client.ts +89 -0
- package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +330 -0
- package/plugins/acryl-router/src/core/jsx/dom/components.ts +39 -0
- package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +184 -0
- package/plugins/acryl-router/src/core/jsx/dom/context.ts +52 -0
- package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +149 -0
- package/plugins/acryl-router/src/core/jsx/dom/css.ts +263 -0
- package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +172 -0
- package/plugins/acryl-router/src/core/jsx/dom/hooks/index.ts +91 -0
- package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +3681 -0
- package/plugins/acryl-router/src/core/jsx/dom/index.ts +170 -0
- package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +1344 -0
- package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.ts +438 -0
- package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.ts +23 -0
- package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.ts +8 -0
- package/plugins/acryl-router/src/core/jsx/dom/render.ts +879 -0
- package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +199 -0
- package/plugins/acryl-router/src/core/jsx/dom/server.ts +76 -0
- package/plugins/acryl-router/src/core/jsx/dom/utils.ts +7 -0
- package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +859 -0
- package/plugins/acryl-router/src/core/jsx/hooks/index.ts +437 -0
- package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +40 -0
- package/plugins/acryl-router/src/core/jsx/index.test.tsx +1744 -0
- package/plugins/acryl-router/src/core/jsx/index.ts +115 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.ts +23 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +666 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.ts +210 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-elements.ts +938 -0
- package/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts +26 -0
- package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +85 -0
- package/plugins/acryl-router/src/core/jsx/jsx-runtime.ts +51 -0
- package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +1281 -0
- package/plugins/acryl-router/src/core/jsx/streaming.ts +231 -0
- package/plugins/acryl-router/src/core/jsx/types.ts +41 -0
- package/plugins/acryl-router/src/core/jsx/utils.test.ts +366 -0
- package/plugins/acryl-router/src/core/jsx/utils.ts +202 -0
- package/plugins/acryl-router/src/core/utils/types.ts +6 -0
- package/plugins/acryl-router/src/helpers/index.ts +1 -0
- package/plugins/acryl-router/tests/jsx.spec.tsx +63 -0
- package/plugins/acryl-router/upstream.json +3 -3
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/upstream.json +1 -1
- package/plugins/save/plugin.json +1 -1
- package/templates/todo/upstream.json +1 -1
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
import type { Child, FC, JSXNode, Props, MemorableFC } from '../base.ts'
|
|
2
|
+
import { toArray } from '../children.ts'
|
|
3
|
+
import {
|
|
4
|
+
DOM_ERROR_HANDLER,
|
|
5
|
+
DOM_INTERNAL_TAG,
|
|
6
|
+
DOM_MEMO,
|
|
7
|
+
DOM_RENDERER,
|
|
8
|
+
DOM_STASH,
|
|
9
|
+
} from '../constants.ts'
|
|
10
|
+
import type { Context as JSXContext } from '../context.ts'
|
|
11
|
+
import { globalContexts as globalJSXContexts, useContext } from '../context.ts'
|
|
12
|
+
import type { EffectData } from '../hooks/index.ts'
|
|
13
|
+
import { STASH_EFFECT } from '../hooks/index.ts'
|
|
14
|
+
import { normalizeIntrinsicElementKey, styleObjectForEach } from '../utils.ts'
|
|
15
|
+
import { createContext } from './context.ts' // import dom-specific versions
|
|
16
|
+
|
|
17
|
+
const HONO_PORTAL_ELEMENT = '_hp'
|
|
18
|
+
|
|
19
|
+
const eventAliasMap: Record<string, string> = {
|
|
20
|
+
Change: 'Input',
|
|
21
|
+
DoubleClick: 'DblClick',
|
|
22
|
+
} as const
|
|
23
|
+
|
|
24
|
+
const nameSpaceMap: Record<string, string> = {
|
|
25
|
+
svg: '2000/svg',
|
|
26
|
+
math: '1998/Math/MathML',
|
|
27
|
+
} as const
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
export type HasRenderToDom = FC<any> & { [DOM_RENDERER]: FC<any> }
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
export type ErrorHandler = (error: any, retry: () => void) => Child | undefined
|
|
33
|
+
|
|
34
|
+
type Container = HTMLElement | DocumentFragment
|
|
35
|
+
type LocalJSXContexts = [JSXContext<unknown>, unknown][] | undefined
|
|
36
|
+
type SupportedElement = HTMLElement | SVGElement | MathMLElement
|
|
37
|
+
export type PreserveNodeType =
|
|
38
|
+
| 1 // preserve only self
|
|
39
|
+
| 2 // preserve self and children
|
|
40
|
+
|
|
41
|
+
export type NodeObject = {
|
|
42
|
+
pP: Props | undefined // previous props
|
|
43
|
+
nN: Node | undefined // next node
|
|
44
|
+
vC: Node[] // virtual dom children
|
|
45
|
+
pC?: Node[] // previous virtual dom children
|
|
46
|
+
vR: Node[] // virtual dom children to remove
|
|
47
|
+
n?: string // namespace
|
|
48
|
+
f?: boolean // force build
|
|
49
|
+
s?: boolean // skip build and apply
|
|
50
|
+
c: Container | undefined // container
|
|
51
|
+
e: SupportedElement | Text | undefined // rendered element
|
|
52
|
+
p?: PreserveNodeType // preserve HTMLElement if it will be unmounted
|
|
53
|
+
a?: boolean // cancel apply() if true
|
|
54
|
+
o?: NodeObject // original node
|
|
55
|
+
[DOM_STASH]:
|
|
56
|
+
| [
|
|
57
|
+
number, // current hook index
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
|
+
any[][], // stash for hooks
|
|
60
|
+
LocalJSXContexts, // context
|
|
61
|
+
[Context, Function, NodeObject], // [context, error handler, node] for closest error boundary or suspense
|
|
62
|
+
]
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
|
+
| [number, any[][]]
|
|
65
|
+
} & JSXNode
|
|
66
|
+
type NodeString = {
|
|
67
|
+
t: string // text content
|
|
68
|
+
d: boolean // is dirty
|
|
69
|
+
s?: boolean // skip build and apply
|
|
70
|
+
} & {
|
|
71
|
+
e?: Text
|
|
72
|
+
// like a NodeObject
|
|
73
|
+
vC: undefined
|
|
74
|
+
nN: undefined
|
|
75
|
+
p?: true
|
|
76
|
+
// from JSXNode
|
|
77
|
+
key: undefined
|
|
78
|
+
tag: undefined
|
|
79
|
+
}
|
|
80
|
+
export type Node = NodeString | NodeObject
|
|
81
|
+
|
|
82
|
+
export type PendingType =
|
|
83
|
+
| 0 // no pending
|
|
84
|
+
| 1 // global
|
|
85
|
+
| 2 // hook
|
|
86
|
+
export type UpdateHook = (
|
|
87
|
+
context: Context,
|
|
88
|
+
node: Node,
|
|
89
|
+
cb: (context: Context) => void
|
|
90
|
+
) => Promise<void>
|
|
91
|
+
export type Context =
|
|
92
|
+
| [
|
|
93
|
+
PendingType, // PendingType
|
|
94
|
+
boolean, // got an error
|
|
95
|
+
UpdateHook, // update hook
|
|
96
|
+
boolean, // is in view transition
|
|
97
|
+
boolean, // is in top level render
|
|
98
|
+
[Context, Function, NodeObject][], // [context, error handler, node] stack for this context
|
|
99
|
+
]
|
|
100
|
+
| [PendingType, boolean, UpdateHook, boolean]
|
|
101
|
+
| [PendingType, boolean, UpdateHook]
|
|
102
|
+
| [PendingType, boolean]
|
|
103
|
+
| [PendingType]
|
|
104
|
+
| []
|
|
105
|
+
|
|
106
|
+
export const buildDataStack: [Context, Node][] = []
|
|
107
|
+
|
|
108
|
+
const refCleanupMap: WeakMap<Element, () => void> = new WeakMap()
|
|
109
|
+
|
|
110
|
+
let nameSpaceContext: JSXContext<string> | undefined = undefined
|
|
111
|
+
export const getNameSpaceContext = () => nameSpaceContext
|
|
112
|
+
|
|
113
|
+
const isNodeString = (node: Node): node is NodeString => 't' in (node as NodeString)
|
|
114
|
+
|
|
115
|
+
const eventCache: Record<string, [string, boolean]> = {
|
|
116
|
+
// pre-define events that are used very frequently
|
|
117
|
+
onClick: ['click', false],
|
|
118
|
+
}
|
|
119
|
+
const getEventSpec = (key: string): [string, boolean] | undefined => {
|
|
120
|
+
if (!key.startsWith('on')) {
|
|
121
|
+
return undefined
|
|
122
|
+
}
|
|
123
|
+
if (eventCache[key]) {
|
|
124
|
+
return eventCache[key]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const match = key.match(/^on([A-Z][a-zA-Z]+?(?:PointerCapture)?)(Capture)?$/)
|
|
128
|
+
if (match) {
|
|
129
|
+
const [, eventName, capture] = match
|
|
130
|
+
return (eventCache[key] = [(eventAliasMap[eventName] || eventName).toLowerCase(), !!capture])
|
|
131
|
+
}
|
|
132
|
+
return undefined
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const toAttributeName = (element: SupportedElement, key: string): string =>
|
|
136
|
+
nameSpaceContext &&
|
|
137
|
+
element instanceof SVGElement &&
|
|
138
|
+
/[A-Z]/.test(key) &&
|
|
139
|
+
(key in element.style || // Presentation attributes are findable in style object. "clip-path", "font-size", "stroke-width", etc.
|
|
140
|
+
key.match(/^(?:o|pai|str|u|ve)/)) // Other un-deprecated kebab-case attributes. "overline-position", "paint-order", "strikethrough-position", etc.
|
|
141
|
+
? key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
142
|
+
: key
|
|
143
|
+
|
|
144
|
+
const normalizeFormValue = (value: Props['value']) =>
|
|
145
|
+
value === null || value === undefined || value === false ? null : value
|
|
146
|
+
|
|
147
|
+
const applySelectValue = (select: HTMLSelectElement, props: Props): void => {
|
|
148
|
+
if (!('value' in props)) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
select.value = normalizeFormValue(props['value'])
|
|
153
|
+
if (!select.multiple && select.selectedIndex === -1) {
|
|
154
|
+
select.selectedIndex = 0
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Unlike SSR (which uses isValidAttributeName for upfront validation),
|
|
159
|
+
// the DOM renderer relies on try/catch for performance—invalid attribute
|
|
160
|
+
// names are rare at runtime, so avoiding the per-attribute regex check is faster.
|
|
161
|
+
const isIgnorableAttributeError = (error: unknown): boolean =>
|
|
162
|
+
error instanceof DOMException && error.name === 'InvalidCharacterError'
|
|
163
|
+
|
|
164
|
+
const applyProps = (
|
|
165
|
+
container: SupportedElement,
|
|
166
|
+
attributes: Props,
|
|
167
|
+
oldAttributes?: Props
|
|
168
|
+
): void => {
|
|
169
|
+
attributes ||= {}
|
|
170
|
+
for (let key in attributes) {
|
|
171
|
+
const value = attributes[key]
|
|
172
|
+
if (key !== 'children' && (!oldAttributes || oldAttributes[key] !== value)) {
|
|
173
|
+
key = normalizeIntrinsicElementKey(key)
|
|
174
|
+
const eventSpec = getEventSpec(key)
|
|
175
|
+
if (eventSpec) {
|
|
176
|
+
if (oldAttributes?.[key] !== value) {
|
|
177
|
+
if (oldAttributes) {
|
|
178
|
+
container.removeEventListener(eventSpec[0], oldAttributes[key], eventSpec[1])
|
|
179
|
+
}
|
|
180
|
+
if (value != null) {
|
|
181
|
+
if (typeof value !== 'function') {
|
|
182
|
+
throw new Error(`Event handler for "${key}" is not a function`)
|
|
183
|
+
}
|
|
184
|
+
container.addEventListener(eventSpec[0], value, eventSpec[1])
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} else if (key === 'dangerouslySetInnerHTML' && value) {
|
|
188
|
+
container.innerHTML = value.__html
|
|
189
|
+
} else if (key === 'ref') {
|
|
190
|
+
refCleanupMap.get(container)?.()
|
|
191
|
+
let cleanup
|
|
192
|
+
if (typeof value === 'function') {
|
|
193
|
+
cleanup = value(container) || (() => value(null))
|
|
194
|
+
} else if (value && 'current' in value) {
|
|
195
|
+
value.current = container
|
|
196
|
+
cleanup = () => (value.current = null)
|
|
197
|
+
}
|
|
198
|
+
refCleanupMap.set(container, cleanup)
|
|
199
|
+
} else if (key === 'style') {
|
|
200
|
+
const style = container.style
|
|
201
|
+
if (typeof value === 'string') {
|
|
202
|
+
style.cssText = value
|
|
203
|
+
} else {
|
|
204
|
+
style.cssText = ''
|
|
205
|
+
if (value != null) {
|
|
206
|
+
styleObjectForEach(value, style.setProperty.bind(style))
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
if (key === 'value') {
|
|
211
|
+
const nodeName = container.nodeName
|
|
212
|
+
if (nodeName === 'SELECT') {
|
|
213
|
+
// Deferred to applySelectValue() after children are rendered
|
|
214
|
+
continue
|
|
215
|
+
} else if (nodeName === 'INPUT' || nodeName === 'TEXTAREA') {
|
|
216
|
+
;(container as unknown as HTMLInputElement).value = normalizeFormValue(value)
|
|
217
|
+
|
|
218
|
+
if (nodeName === 'TEXTAREA') {
|
|
219
|
+
container.textContent = value
|
|
220
|
+
continue
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} else if (
|
|
224
|
+
(key === 'checked' && container.nodeName === 'INPUT') ||
|
|
225
|
+
(key === 'selected' && container.nodeName === 'OPTION')
|
|
226
|
+
) {
|
|
227
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
228
|
+
;(container as any)[key] = value
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const k = toAttributeName(container, key)
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
if (value === null || value === undefined || value === false) {
|
|
235
|
+
container.removeAttribute(k)
|
|
236
|
+
} else if (value === true) {
|
|
237
|
+
container.setAttribute(k, '')
|
|
238
|
+
} else if (typeof value === 'string' || typeof value === 'number') {
|
|
239
|
+
container.setAttribute(k, value as string)
|
|
240
|
+
} else {
|
|
241
|
+
container.setAttribute(k, value.toString())
|
|
242
|
+
}
|
|
243
|
+
} catch (e) {
|
|
244
|
+
if (!isIgnorableAttributeError(e)) {
|
|
245
|
+
throw e
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (oldAttributes) {
|
|
252
|
+
for (let key in oldAttributes) {
|
|
253
|
+
const value = oldAttributes[key]
|
|
254
|
+
if (key !== 'children' && !(key in attributes)) {
|
|
255
|
+
key = normalizeIntrinsicElementKey(key)
|
|
256
|
+
const eventSpec = getEventSpec(key)
|
|
257
|
+
if (eventSpec) {
|
|
258
|
+
container.removeEventListener(eventSpec[0], value, eventSpec[1])
|
|
259
|
+
} else if (key === 'ref') {
|
|
260
|
+
refCleanupMap.get(container)?.()
|
|
261
|
+
refCleanupMap.delete(container)
|
|
262
|
+
} else {
|
|
263
|
+
try {
|
|
264
|
+
container.removeAttribute(toAttributeName(container, key))
|
|
265
|
+
} catch (e) {
|
|
266
|
+
if (!isIgnorableAttributeError(e)) {
|
|
267
|
+
throw e
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const invokeTag = (context: Context, node: NodeObject): Child[] => {
|
|
277
|
+
node[DOM_STASH][0] = 0
|
|
278
|
+
buildDataStack.push([context, node])
|
|
279
|
+
const func = (node.tag as HasRenderToDom)[DOM_RENDERER] || node.tag
|
|
280
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
281
|
+
const props = (func as any).defaultProps
|
|
282
|
+
? {
|
|
283
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
284
|
+
...(func as any).defaultProps,
|
|
285
|
+
...node.props,
|
|
286
|
+
}
|
|
287
|
+
: node.props
|
|
288
|
+
try {
|
|
289
|
+
return [func.call(null, props) as Child]
|
|
290
|
+
} finally {
|
|
291
|
+
buildDataStack.pop()
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const getNextChildren = (
|
|
296
|
+
node: NodeObject,
|
|
297
|
+
container: Container,
|
|
298
|
+
nextChildren: Node[],
|
|
299
|
+
childrenToRemove: Node[],
|
|
300
|
+
callbacks: EffectData[]
|
|
301
|
+
): void => {
|
|
302
|
+
if (node.vR?.length) {
|
|
303
|
+
childrenToRemove.push(...node.vR)
|
|
304
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
305
|
+
delete (node as any).vR
|
|
306
|
+
}
|
|
307
|
+
if (typeof node.tag === 'function') {
|
|
308
|
+
node[DOM_STASH][1][STASH_EFFECT]?.forEach((data: EffectData) => callbacks.push(data))
|
|
309
|
+
}
|
|
310
|
+
node.vC.forEach((child) => {
|
|
311
|
+
if (isNodeString(child)) {
|
|
312
|
+
nextChildren.push(child)
|
|
313
|
+
} else {
|
|
314
|
+
if (typeof child.tag === 'function' || child.tag === '') {
|
|
315
|
+
child.c = container
|
|
316
|
+
const currentNextChildrenIndex = nextChildren.length
|
|
317
|
+
getNextChildren(child, container, nextChildren, childrenToRemove, callbacks)
|
|
318
|
+
if (child.s) {
|
|
319
|
+
for (let i = currentNextChildrenIndex; i < nextChildren.length; i++) {
|
|
320
|
+
nextChildren[i].s = true
|
|
321
|
+
}
|
|
322
|
+
child.s = false
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
nextChildren.push(child)
|
|
326
|
+
if (child.vR?.length) {
|
|
327
|
+
childrenToRemove.push(...child.vR)
|
|
328
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
329
|
+
delete (child as any).vR
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const findInsertBefore = (node: Node | undefined): SupportedElement | Text | undefined => {
|
|
337
|
+
while (node && (node.tag === HONO_PORTAL_ELEMENT || !node.e)) {
|
|
338
|
+
node = node.tag === HONO_PORTAL_ELEMENT || !node.vC?.[0] ? node.nN : node.vC[0]
|
|
339
|
+
}
|
|
340
|
+
return node?.e
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const removeNode = (node: Node): void => {
|
|
344
|
+
if (!isNodeString(node)) {
|
|
345
|
+
node[DOM_STASH]?.[1][STASH_EFFECT]?.forEach((data: EffectData) => data[2]?.())
|
|
346
|
+
|
|
347
|
+
refCleanupMap.get(node.e as Element)?.()
|
|
348
|
+
if (node.p === 2) {
|
|
349
|
+
node.vC?.forEach((n) => (n.p = 2))
|
|
350
|
+
}
|
|
351
|
+
node.vC?.forEach(removeNode)
|
|
352
|
+
}
|
|
353
|
+
if (!node.p) {
|
|
354
|
+
node.e?.remove()
|
|
355
|
+
delete node.e
|
|
356
|
+
}
|
|
357
|
+
if (typeof node.tag === 'function') {
|
|
358
|
+
updateMap.delete(node)
|
|
359
|
+
fallbackUpdateFnArrayMap.delete(node)
|
|
360
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
361
|
+
delete (node as any)[DOM_STASH][3] // delete explicitly for avoid circular reference
|
|
362
|
+
node.a = true
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const apply = (node: NodeObject, container: Container, isNew: boolean): void => {
|
|
367
|
+
node.c = container
|
|
368
|
+
applyNodeObject(node, container, isNew)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const findChildNodeIndex = (
|
|
372
|
+
childNodes: NodeListOf<ChildNode>,
|
|
373
|
+
child: ChildNode | undefined
|
|
374
|
+
): number | undefined => {
|
|
375
|
+
if (!child) {
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
for (let i = 0, len = childNodes.length; i < len; i++) {
|
|
380
|
+
if (childNodes[i] === child) {
|
|
381
|
+
return i
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const cancelBuild: symbol = Symbol()
|
|
389
|
+
const applyNodeObject = (node: NodeObject, container: Container, isNew: boolean): void => {
|
|
390
|
+
const next: Node[] = []
|
|
391
|
+
const remove: Node[] = []
|
|
392
|
+
const callbacks: EffectData[] = []
|
|
393
|
+
getNextChildren(node, container, next, remove, callbacks)
|
|
394
|
+
remove.forEach(removeNode)
|
|
395
|
+
|
|
396
|
+
const childNodes = (isNew ? undefined : container.childNodes) as NodeListOf<ChildNode>
|
|
397
|
+
let offset: number
|
|
398
|
+
let insertBeforeNode: ChildNode | null = null
|
|
399
|
+
if (isNew) {
|
|
400
|
+
offset = -1
|
|
401
|
+
} else if (!childNodes.length) {
|
|
402
|
+
offset = 0
|
|
403
|
+
} else {
|
|
404
|
+
const offsetByNextNode = findChildNodeIndex(childNodes, findInsertBefore(node.nN))
|
|
405
|
+
if (offsetByNextNode !== undefined) {
|
|
406
|
+
insertBeforeNode = childNodes[offsetByNextNode]
|
|
407
|
+
offset = offsetByNextNode
|
|
408
|
+
} else {
|
|
409
|
+
offset =
|
|
410
|
+
findChildNodeIndex(childNodes, next.find((n) => n.tag !== HONO_PORTAL_ELEMENT && n.e)?.e) ??
|
|
411
|
+
-1
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (offset === -1) {
|
|
415
|
+
isNew = true
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
for (let i = 0, len = next.length; i < len; i++, offset++) {
|
|
420
|
+
const child = next[i]
|
|
421
|
+
|
|
422
|
+
let el: SupportedElement | Text
|
|
423
|
+
if (child.s && child.e) {
|
|
424
|
+
el = child.e
|
|
425
|
+
child.s = false
|
|
426
|
+
} else {
|
|
427
|
+
const isNewLocal = isNew || !child.e
|
|
428
|
+
if (isNodeString(child)) {
|
|
429
|
+
if (child.e && child.d) {
|
|
430
|
+
child.e.textContent = child.t
|
|
431
|
+
}
|
|
432
|
+
child.d = false
|
|
433
|
+
el = child.e ||= document.createTextNode(child.t)
|
|
434
|
+
} else {
|
|
435
|
+
el = child.e ||= child.n
|
|
436
|
+
? (document.createElementNS(child.n, child.tag as string) as SVGElement | MathMLElement)
|
|
437
|
+
: document.createElement(child.tag as string)
|
|
438
|
+
|
|
439
|
+
applyProps(el as HTMLElement, child.props, child.pP)
|
|
440
|
+
applyNodeObject(child, el as HTMLElement, isNewLocal)
|
|
441
|
+
if (child.tag === 'select') {
|
|
442
|
+
applySelectValue(el as HTMLSelectElement, child.props)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (child.tag === HONO_PORTAL_ELEMENT) {
|
|
447
|
+
offset--
|
|
448
|
+
} else if (isNew) {
|
|
449
|
+
if (!el.parentNode) {
|
|
450
|
+
container.appendChild(el)
|
|
451
|
+
}
|
|
452
|
+
} else if (childNodes[offset] !== el && childNodes[offset - 1] !== el) {
|
|
453
|
+
if (childNodes[offset + 1] === el) {
|
|
454
|
+
// Move extra elements to the back of the container. This is to be done efficiently when elements are swapped.
|
|
455
|
+
container.appendChild(childNodes[offset])
|
|
456
|
+
} else {
|
|
457
|
+
container.insertBefore(el, insertBeforeNode || childNodes[offset] || null)
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (node.pP) {
|
|
462
|
+
node.pP = undefined
|
|
463
|
+
}
|
|
464
|
+
if (callbacks.length) {
|
|
465
|
+
const useLayoutEffectCbs: Array<() => void> = []
|
|
466
|
+
const useEffectCbs: Array<() => void> = []
|
|
467
|
+
callbacks.forEach(([, useLayoutEffectCb, , useEffectCb, useInsertionEffectCb]) => {
|
|
468
|
+
if (useLayoutEffectCb) {
|
|
469
|
+
useLayoutEffectCbs.push(useLayoutEffectCb)
|
|
470
|
+
}
|
|
471
|
+
if (useEffectCb) {
|
|
472
|
+
useEffectCbs.push(useEffectCb)
|
|
473
|
+
}
|
|
474
|
+
useInsertionEffectCb?.() // invoke useInsertionEffect callbacks
|
|
475
|
+
})
|
|
476
|
+
useLayoutEffectCbs.forEach((cb) => cb()) // invoke useLayoutEffect callbacks
|
|
477
|
+
if (useEffectCbs.length) {
|
|
478
|
+
requestAnimationFrame(() => {
|
|
479
|
+
useEffectCbs.forEach((cb) => cb()) // invoke useEffect callbacks
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const isSameContext = (
|
|
486
|
+
oldContexts: LocalJSXContexts,
|
|
487
|
+
newContexts: NonNullable<LocalJSXContexts>
|
|
488
|
+
): boolean =>
|
|
489
|
+
!!(
|
|
490
|
+
oldContexts &&
|
|
491
|
+
oldContexts.length === newContexts.length &&
|
|
492
|
+
oldContexts.every((ctx, i) => ctx[1] === newContexts[i][1])
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
const indexChildrenByKey = (children: Node[]): Map<unknown, Node> | undefined => {
|
|
496
|
+
const index = new Map<unknown, Node>()
|
|
497
|
+
// Insert in the original order so map iteration preserves removal order.
|
|
498
|
+
for (const child of children) {
|
|
499
|
+
const key = child.key
|
|
500
|
+
if (index.has(key)) {
|
|
501
|
+
return
|
|
502
|
+
}
|
|
503
|
+
index.set(key, child)
|
|
504
|
+
}
|
|
505
|
+
return index
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const fallbackUpdateFnArrayMap: WeakMap<
|
|
509
|
+
NodeObject,
|
|
510
|
+
Array<() => Promise<NodeObject | undefined>>
|
|
511
|
+
> = new WeakMap<NodeObject, Array<() => Promise<NodeObject | undefined>>>()
|
|
512
|
+
export const build = (context: Context, node: NodeObject, children?: Child[]): void => {
|
|
513
|
+
const buildWithPreviousChildren = !children && node.pC
|
|
514
|
+
if (children) {
|
|
515
|
+
node.pC ||= node.vC
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
let foundErrorHandler: ErrorHandler | undefined
|
|
519
|
+
try {
|
|
520
|
+
children ||=
|
|
521
|
+
typeof node.tag == 'function' ? invokeTag(context, node) : toArray(node.props.children)
|
|
522
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
523
|
+
if ((children[0] as JSXNode)?.tag === '' && (children[0] as any)[DOM_ERROR_HANDLER]) {
|
|
524
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
525
|
+
foundErrorHandler = (children[0] as any)[DOM_ERROR_HANDLER] as ErrorHandler
|
|
526
|
+
context[5]!.push([context, foundErrorHandler, node])
|
|
527
|
+
}
|
|
528
|
+
let oldVChildren: Node[] | undefined = buildWithPreviousChildren
|
|
529
|
+
? [...(node.pC as Node[])]
|
|
530
|
+
: node.vC
|
|
531
|
+
? [...node.vC]
|
|
532
|
+
: undefined
|
|
533
|
+
const vChildren: Node[] = []
|
|
534
|
+
let prevNode: Node | undefined
|
|
535
|
+
let scanBudget = (oldVChildren?.length || 0) * 2
|
|
536
|
+
let oldChildrenByKey: Map<unknown, Node> | undefined
|
|
537
|
+
for (let i = 0; i < children.length; i++) {
|
|
538
|
+
if (Array.isArray(children[i])) {
|
|
539
|
+
children.splice(i, 1, ...((children[i] as unknown[]).flat(Infinity) as Child[]))
|
|
540
|
+
i--
|
|
541
|
+
continue
|
|
542
|
+
}
|
|
543
|
+
let child = buildNode(children[i])
|
|
544
|
+
if (child) {
|
|
545
|
+
if (
|
|
546
|
+
typeof child.tag === 'function' &&
|
|
547
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
548
|
+
!(child.tag as any)[DOM_INTERNAL_TAG]
|
|
549
|
+
) {
|
|
550
|
+
if (globalJSXContexts.length > 0) {
|
|
551
|
+
child[DOM_STASH][2] = globalJSXContexts.map((c) => [c, c.values.at(-1)])
|
|
552
|
+
}
|
|
553
|
+
if (context[5]?.length) {
|
|
554
|
+
child[DOM_STASH][3] = context[5].at(-1) as [Context, ErrorHandler, NodeObject]
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
let oldChild: NodeObject | undefined
|
|
559
|
+
if (oldChildrenByKey && child.key === undefined && !isNodeString(child)) {
|
|
560
|
+
oldVChildren = [...oldChildrenByKey.values()]
|
|
561
|
+
oldChildrenByKey = undefined
|
|
562
|
+
}
|
|
563
|
+
if (oldChildrenByKey) {
|
|
564
|
+
const key = child.key
|
|
565
|
+
const candidate = oldChildrenByKey.get(key)
|
|
566
|
+
// The strict check also preserves the existing behavior for NaN keys.
|
|
567
|
+
if (
|
|
568
|
+
candidate &&
|
|
569
|
+
(isNodeString(child)
|
|
570
|
+
? isNodeString(candidate)
|
|
571
|
+
: candidate.tag === child.tag && candidate.key === key)
|
|
572
|
+
) {
|
|
573
|
+
oldChild = candidate as NodeObject
|
|
574
|
+
oldChildrenByKey.delete(key)
|
|
575
|
+
}
|
|
576
|
+
} else if (oldVChildren && oldVChildren.length) {
|
|
577
|
+
const first = oldVChildren[0]
|
|
578
|
+
const isMatchFirst = isNodeString(child)
|
|
579
|
+
? isNodeString(first)
|
|
580
|
+
: !isNodeString(first) &&
|
|
581
|
+
(child.key !== undefined
|
|
582
|
+
? (first as Node).key === (child as Node).key &&
|
|
583
|
+
(first as Node).tag === (child as Node).tag
|
|
584
|
+
: (first as Node).tag === (child as Node).tag)
|
|
585
|
+
|
|
586
|
+
if (isMatchFirst) {
|
|
587
|
+
oldChild = oldVChildren.shift() as NodeObject
|
|
588
|
+
} else {
|
|
589
|
+
const i = oldVChildren.findIndex(
|
|
590
|
+
isNodeString(child)
|
|
591
|
+
? (c) => isNodeString(c)
|
|
592
|
+
: child.key !== undefined
|
|
593
|
+
? (c) => c.key === (child as Node).key && c.tag === (child as Node).tag
|
|
594
|
+
: (c) => c.tag === (child as Node).tag
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
scanBudget -= i === -1 ? oldVChildren.length : i
|
|
598
|
+
if (i !== -1) {
|
|
599
|
+
oldChild = oldVChildren[i] as NodeObject
|
|
600
|
+
oldVChildren.splice(i, 1)
|
|
601
|
+
}
|
|
602
|
+
// Try indexing once, after searches exceed a linear budget.
|
|
603
|
+
if (scanBudget < 0) {
|
|
604
|
+
scanBudget = Infinity
|
|
605
|
+
oldChildrenByKey = indexChildrenByKey(oldVChildren)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (oldChild) {
|
|
611
|
+
if (isNodeString(child)) {
|
|
612
|
+
if ((oldChild as unknown as NodeString).t !== child.t) {
|
|
613
|
+
;(oldChild as unknown as NodeString).t = child.t // update text content
|
|
614
|
+
;(oldChild as unknown as NodeString).d = true
|
|
615
|
+
}
|
|
616
|
+
child = oldChild
|
|
617
|
+
} else {
|
|
618
|
+
const pP = (oldChild.pP = oldChild.props)
|
|
619
|
+
oldChild.props = child.props
|
|
620
|
+
oldChild.f ||= child.f || node.f
|
|
621
|
+
if (typeof child.tag === 'function') {
|
|
622
|
+
const oldContexts = oldChild[DOM_STASH][2]
|
|
623
|
+
oldChild[DOM_STASH][2] = child[DOM_STASH][2] || []
|
|
624
|
+
oldChild[DOM_STASH][3] = child[DOM_STASH][3]
|
|
625
|
+
|
|
626
|
+
if (
|
|
627
|
+
!oldChild.f &&
|
|
628
|
+
((oldChild.o || oldChild) === child.o || // The code generated by the react compiler is memoized under this condition.
|
|
629
|
+
(oldChild.tag as MemorableFC<unknown>)[DOM_MEMO]?.(pP, oldChild.props)) && // The `memo` function is memoized under this condition.
|
|
630
|
+
isSameContext(oldContexts, oldChild[DOM_STASH][2])
|
|
631
|
+
) {
|
|
632
|
+
oldChild.s = true
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
child = oldChild
|
|
636
|
+
}
|
|
637
|
+
} else if (!isNodeString(child) && nameSpaceContext) {
|
|
638
|
+
const ns = useContext(nameSpaceContext)
|
|
639
|
+
if (ns) {
|
|
640
|
+
child.n = ns
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (!isNodeString(child) && !child.s) {
|
|
645
|
+
build(context, child)
|
|
646
|
+
delete child.f
|
|
647
|
+
}
|
|
648
|
+
vChildren.push(child)
|
|
649
|
+
|
|
650
|
+
if (prevNode && !prevNode.s && !child.s) {
|
|
651
|
+
for (let p = prevNode; p && !isNodeString(p); p = p.vC?.at(-1) as NodeObject) {
|
|
652
|
+
p.nN = child
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
prevNode = child
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
oldVChildren = oldChildrenByKey ? [...oldChildrenByKey.values()] : oldVChildren
|
|
659
|
+
node.vR = buildWithPreviousChildren ? [...node.vC, ...(oldVChildren || [])] : oldVChildren || []
|
|
660
|
+
node.vC = vChildren
|
|
661
|
+
if (buildWithPreviousChildren) {
|
|
662
|
+
delete node.pC
|
|
663
|
+
}
|
|
664
|
+
} catch (e) {
|
|
665
|
+
node.f = true
|
|
666
|
+
if (e === cancelBuild) {
|
|
667
|
+
if (foundErrorHandler) {
|
|
668
|
+
return
|
|
669
|
+
} else {
|
|
670
|
+
throw e
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const [errorHandlerContext, errorHandler, errorHandlerNode] =
|
|
675
|
+
node[DOM_STASH]?.[3] || ([] as unknown as [undefined, undefined])
|
|
676
|
+
|
|
677
|
+
if (errorHandler) {
|
|
678
|
+
const fallbackUpdateFn = () =>
|
|
679
|
+
update([0, false, context[2] as UpdateHook], errorHandlerNode as NodeObject)
|
|
680
|
+
const fallbackUpdateFnArray =
|
|
681
|
+
fallbackUpdateFnArrayMap.get(errorHandlerNode as NodeObject) || []
|
|
682
|
+
fallbackUpdateFnArray.push(fallbackUpdateFn)
|
|
683
|
+
fallbackUpdateFnArrayMap.set(errorHandlerNode as NodeObject, fallbackUpdateFnArray)
|
|
684
|
+
const fallback = errorHandler(e, () => {
|
|
685
|
+
const fnArray = fallbackUpdateFnArrayMap.get(errorHandlerNode as NodeObject)
|
|
686
|
+
if (fnArray) {
|
|
687
|
+
const i = fnArray.indexOf(fallbackUpdateFn)
|
|
688
|
+
if (i !== -1) {
|
|
689
|
+
fnArray.splice(i, 1)
|
|
690
|
+
return fallbackUpdateFn()
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
})
|
|
694
|
+
if (fallback) {
|
|
695
|
+
if (context[0] === 1) {
|
|
696
|
+
// low priority render
|
|
697
|
+
context[1] = true
|
|
698
|
+
} else {
|
|
699
|
+
build(context, errorHandlerNode, [fallback])
|
|
700
|
+
if (
|
|
701
|
+
(errorHandler.length === 1 || context !== errorHandlerContext) &&
|
|
702
|
+
errorHandlerNode.c
|
|
703
|
+
) {
|
|
704
|
+
// render error boundary immediately
|
|
705
|
+
apply(errorHandlerNode, errorHandlerNode.c as Container, false)
|
|
706
|
+
return
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
throw cancelBuild
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
throw e
|
|
714
|
+
} finally {
|
|
715
|
+
if (foundErrorHandler) {
|
|
716
|
+
context[5]!.pop()
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
export const buildNode = (node: Child): Node | undefined => {
|
|
722
|
+
if (node === undefined || node === null || typeof node === 'boolean') {
|
|
723
|
+
return undefined
|
|
724
|
+
} else if (typeof node === 'string' || typeof node === 'number') {
|
|
725
|
+
return { t: node.toString(), d: true } as NodeString
|
|
726
|
+
} else {
|
|
727
|
+
if ('vR' in node) {
|
|
728
|
+
node = {
|
|
729
|
+
tag: (node as NodeObject).tag,
|
|
730
|
+
props: (node as NodeObject).props,
|
|
731
|
+
key: (node as NodeObject).key,
|
|
732
|
+
f: (node as NodeObject).f,
|
|
733
|
+
type: (node as NodeObject).tag,
|
|
734
|
+
ref: (node as NodeObject).props.ref,
|
|
735
|
+
o: (node as NodeObject).o || node,
|
|
736
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
737
|
+
} as any
|
|
738
|
+
}
|
|
739
|
+
if (typeof (node as JSXNode).tag === 'function') {
|
|
740
|
+
;(node as NodeObject)[DOM_STASH] = [0, []]
|
|
741
|
+
} else {
|
|
742
|
+
const ns = nameSpaceMap[(node as JSXNode).tag as string]
|
|
743
|
+
if (ns) {
|
|
744
|
+
nameSpaceContext ||= createContext('')
|
|
745
|
+
;(node as JSXNode).props.children = [
|
|
746
|
+
{
|
|
747
|
+
tag: nameSpaceContext,
|
|
748
|
+
props: {
|
|
749
|
+
value: ((node as NodeObject).n = `http://www.w3.org/${ns}`),
|
|
750
|
+
children: (node as JSXNode).props.children,
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
]
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return node as NodeObject
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const replaceContainer = (node: NodeObject, from: DocumentFragment, to: Container): void => {
|
|
761
|
+
if (node.c === from) {
|
|
762
|
+
node.c = to
|
|
763
|
+
node.vC.forEach((child) => replaceContainer(child as NodeObject, from, to))
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const updateSync = (context: Context, node: NodeObject): void => {
|
|
768
|
+
node[DOM_STASH][2]?.forEach(([c, v]) => {
|
|
769
|
+
c.values.push(v)
|
|
770
|
+
})
|
|
771
|
+
try {
|
|
772
|
+
build(context, node, undefined)
|
|
773
|
+
} catch {
|
|
774
|
+
return
|
|
775
|
+
}
|
|
776
|
+
if (node.a) {
|
|
777
|
+
delete node.a
|
|
778
|
+
return
|
|
779
|
+
}
|
|
780
|
+
node[DOM_STASH][2]?.forEach(([c]) => {
|
|
781
|
+
c.values.pop()
|
|
782
|
+
})
|
|
783
|
+
if (context[0] !== 1 || !context[1]) {
|
|
784
|
+
apply(node, node.c as Container, false)
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
type UpdateMapResolve = (node: NodeObject | undefined) => void
|
|
789
|
+
const updateMap: WeakMap<NodeObject, [UpdateMapResolve, Function]> = new WeakMap<
|
|
790
|
+
NodeObject,
|
|
791
|
+
[UpdateMapResolve, Function]
|
|
792
|
+
>()
|
|
793
|
+
const currentUpdateSets: Set<NodeObject>[] = []
|
|
794
|
+
export const update = async (
|
|
795
|
+
context: Context,
|
|
796
|
+
node: NodeObject
|
|
797
|
+
): Promise<NodeObject | undefined> => {
|
|
798
|
+
context[5] ||= []
|
|
799
|
+
|
|
800
|
+
const existing = updateMap.get(node)
|
|
801
|
+
if (existing) {
|
|
802
|
+
// execute only the last update() call, so the previous update will be canceled.
|
|
803
|
+
existing[0](undefined)
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
let resolve: UpdateMapResolve | undefined
|
|
807
|
+
const promise = new Promise<NodeObject | undefined>((r) => (resolve = r))
|
|
808
|
+
updateMap.set(node, [
|
|
809
|
+
resolve as UpdateMapResolve,
|
|
810
|
+
() => {
|
|
811
|
+
if (context[2]) {
|
|
812
|
+
context[2](context, node, (context) => {
|
|
813
|
+
updateSync(context, node)
|
|
814
|
+
}).then(() => (resolve as UpdateMapResolve)(node))
|
|
815
|
+
} else {
|
|
816
|
+
updateSync(context, node)
|
|
817
|
+
;(resolve as UpdateMapResolve)(node)
|
|
818
|
+
}
|
|
819
|
+
},
|
|
820
|
+
])
|
|
821
|
+
|
|
822
|
+
if (currentUpdateSets.length) {
|
|
823
|
+
;(currentUpdateSets.at(-1) as Set<NodeObject>).add(node)
|
|
824
|
+
} else {
|
|
825
|
+
await Promise.resolve()
|
|
826
|
+
|
|
827
|
+
const latest = updateMap.get(node)
|
|
828
|
+
if (latest) {
|
|
829
|
+
updateMap.delete(node)
|
|
830
|
+
latest[1]()
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return promise
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
export const renderNode = (node: NodeObject, container: Container): void => {
|
|
838
|
+
const context: Context = []
|
|
839
|
+
;(context as Context)[5] = [] // error handler stack
|
|
840
|
+
;(context as Context)[4] = true // start top level render
|
|
841
|
+
build(context, node, undefined)
|
|
842
|
+
;(context as Context)[4] = false // finish top level render
|
|
843
|
+
|
|
844
|
+
const fragment = document.createDocumentFragment()
|
|
845
|
+
apply(node, fragment, true)
|
|
846
|
+
replaceContainer(node, fragment, container)
|
|
847
|
+
container.replaceChildren(fragment)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
export const render = (jsxNode: Child, container: Container): void => {
|
|
851
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
852
|
+
renderNode(buildNode({ tag: '', props: { children: jsxNode } } as any) as NodeObject, container)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
export const flushSync = (callback: () => void): void => {
|
|
856
|
+
const set = new Set<NodeObject>()
|
|
857
|
+
currentUpdateSets.push(set)
|
|
858
|
+
callback()
|
|
859
|
+
set.forEach((node) => {
|
|
860
|
+
const latest = updateMap.get(node)
|
|
861
|
+
if (latest) {
|
|
862
|
+
updateMap.delete(node)
|
|
863
|
+
latest[1]()
|
|
864
|
+
}
|
|
865
|
+
})
|
|
866
|
+
currentUpdateSets.pop()
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
export const createPortal = (children: Child, container: HTMLElement, key?: string): Child =>
|
|
870
|
+
({
|
|
871
|
+
tag: HONO_PORTAL_ELEMENT,
|
|
872
|
+
props: {
|
|
873
|
+
children,
|
|
874
|
+
},
|
|
875
|
+
key,
|
|
876
|
+
e: container,
|
|
877
|
+
p: 1,
|
|
878
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
879
|
+
}) as any
|