acrylblends 0.6.1 → 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.
Files changed (217) hide show
  1. package/README.md +11 -6
  2. package/docs/framework/README.md +3 -2
  3. package/docs/framework/UPSTREAM.md +1 -1
  4. package/docs/framework/api/presets.md +0 -3
  5. package/docs/framework/cordis/acryl-in-a-cordis-app.md +9 -0
  6. package/docs/framework/getting-started/basic.md +5 -4
  7. package/docs/framework/getting-started/desktop.md +41 -0
  8. package/docs/framework/guides/jsx-dom.md +0 -3
  9. package/docs/framework/guides/jsx.md +1 -4
  10. package/docs/framework/guides/middleware.md +1 -1
  11. package/docs/framework/helpers/cookie.md +1 -1
  12. package/docs/framework/helpers/css.md +0 -3
  13. package/docs/framework/helpers/dev.md +0 -3
  14. package/docs/framework/helpers/jwt.md +1 -1
  15. package/docs/framework/helpers/proxy.md +0 -3
  16. package/docs/framework/middleware/builtin/basic-auth.md +6 -6
  17. package/docs/framework/middleware/builtin/cache.md +0 -3
  18. package/docs/framework/middleware/builtin/combine.md +1 -1
  19. package/docs/framework/middleware/builtin/ip-restriction.md +1 -1
  20. package/docs/framework/middleware/builtin/logger.md +1 -1
  21. package/docs/framework/middleware/builtin/mount.md +0 -3
  22. package/docs/framework/upstream.json +3 -3
  23. package/lib/accepts.d.ts +1 -1
  24. package/lib/acryl-base.d.ts +1 -1
  25. package/lib/adapter.d.ts +1 -1
  26. package/lib/{app-CmAYW1Al.d.ts → app-wFwpg-bt.d.ts} +1 -1
  27. package/lib/base-BpQy2Rk7.d.ts +783 -0
  28. package/lib/base-ddiVmiE7.js +721 -0
  29. package/lib/basic-auth.d.ts +1 -1
  30. package/lib/bearer-auth.d.ts +1 -1
  31. package/lib/body-limit.d.ts +1 -1
  32. package/lib/cache.d.ts +43 -0
  33. package/lib/cache.js +169 -0
  34. package/lib/client.d.ts +2 -2
  35. package/lib/combine.d.ts +1 -1
  36. package/lib/common-D8Z4JRCw.d.ts +46 -0
  37. package/lib/components-Ce5FRyHU.js +268 -0
  38. package/lib/components-DYZdxJIJ.js +24 -0
  39. package/lib/compress.d.ts +1 -1
  40. package/lib/conninfo.d.ts +2 -2
  41. package/lib/constants-BR9jM9BC.js +9 -0
  42. package/lib/{context-Bm7rI-oK.d.ts → context-DVQBjuns.d.ts} +1 -1
  43. package/lib/context-DxFdY_un.d.ts +250 -0
  44. package/lib/context-storage.d.ts +1 -1
  45. package/lib/{cookie-ClMaKDQf.d.ts → cookie-CCelEqky.d.ts} +1 -1
  46. package/lib/cookie.d.ts +3 -3
  47. package/lib/cors.d.ts +1 -1
  48. package/lib/csrf.d.ts +1 -1
  49. package/lib/css-D2jb7X8b.js +320 -0
  50. package/lib/css.d.ts +82 -0
  51. package/lib/css.js +134 -0
  52. package/lib/dev.d.ts +19 -0
  53. package/lib/dev.js +50 -0
  54. package/lib/etag.d.ts +1 -1
  55. package/lib/factory.d.ts +2 -2
  56. package/lib/hooks-Bq5q9IMl.js +762 -0
  57. package/lib/html-SllAe0YT.d.ts +46 -0
  58. package/lib/html.d.ts +1 -1
  59. package/lib/index-C850MeBA.d.ts +94 -0
  60. package/lib/{index-oOClm_Xk.d.ts → index-CUTfU69R.d.ts} +3 -3
  61. package/lib/{index-B-3F4wt2.d.ts → index-Cqcdluyn.d.ts} +2 -2
  62. package/lib/{index-UTBmM3GX.d.ts → index-CsvB-F9j.d.ts} +2 -2
  63. package/lib/index.d.ts +4 -4
  64. package/lib/ip-restriction.d.ts +2 -2
  65. package/lib/jsx/dom/client.d.ts +30 -0
  66. package/lib/jsx/dom/client.js +49 -0
  67. package/lib/jsx/dom/css.d.ts +84 -0
  68. package/lib/jsx/dom/css.js +2 -0
  69. package/lib/jsx/dom/jsx-dev-runtime.d.ts +3 -0
  70. package/lib/jsx/dom/jsx-dev-runtime.js +2 -0
  71. package/lib/jsx/dom/jsx-runtime.d.ts +3 -0
  72. package/lib/jsx/dom/jsx-runtime.js +2 -0
  73. package/lib/jsx/dom/server.d.ts +105 -0
  74. package/lib/jsx/dom/server.js +37 -0
  75. package/lib/jsx/dom.d.ts +5 -0
  76. package/lib/jsx/dom.js +74 -0
  77. package/lib/jsx/jsx-dev-runtime.d.ts +6 -0
  78. package/lib/jsx/jsx-dev-runtime.js +14 -0
  79. package/lib/jsx/jsx-runtime.d.ts +10 -0
  80. package/lib/jsx/jsx-runtime.js +30 -0
  81. package/lib/jsx/streaming.d.ts +2 -0
  82. package/lib/jsx/streaming.js +2 -0
  83. package/lib/jsx-dev-runtime-B22O-Npk.d.ts +7 -0
  84. package/lib/jsx-dev-runtime-kAhR_4C7.js +354 -0
  85. package/lib/jsx-runtime-BVVgDSdq.d.ts +1 -0
  86. package/lib/jsx.d.ts +3 -0
  87. package/lib/jsx.js +47 -0
  88. package/lib/jwk.d.ts +4 -4
  89. package/lib/{jwt-Dg493Tai.d.ts → jwt-BARhotDz.d.ts} +1 -1
  90. package/lib/jwt.d.ts +4 -4
  91. package/lib/language.d.ts +1 -1
  92. package/lib/linear-router-C3FPib6h.js +96 -0
  93. package/lib/logger.d.ts +1 -1
  94. package/lib/loopback-only.d.ts +1 -1
  95. package/lib/method-not-allowed.d.ts +2 -2
  96. package/lib/method-override.d.ts +2 -2
  97. package/lib/mount.d.ts +44 -0
  98. package/lib/mount.js +67 -0
  99. package/lib/node-server/conninfo.d.ts +1 -1
  100. package/lib/node-server/serve-static.d.ts +1 -1
  101. package/lib/node-server/serve-static.js +1 -1
  102. package/lib/node-server.d.ts +1 -1
  103. package/lib/node-server.js +1 -1
  104. package/lib/pattern-router-HZxjDGEQ.js +41 -0
  105. package/lib/powered-by.d.ts +1 -1
  106. package/lib/pretty-json.d.ts +1 -1
  107. package/lib/proxy.d.ts +72 -0
  108. package/lib/proxy.js +121 -0
  109. package/lib/quick.d.ts +8 -0
  110. package/lib/quick.js +17 -0
  111. package/lib/request-id.d.ts +1 -1
  112. package/lib/request.d.ts +1 -1
  113. package/lib/route.d.ts +1 -1
  114. package/lib/router/linear-router.js +1 -95
  115. package/lib/router/pattern-router.js +1 -40
  116. package/lib/schema-validator.d.ts +1 -1
  117. package/lib/secure-headers.d.ts +1 -1
  118. package/lib/{serve-static-Dw_m6KxR.js → serve-static-Cw23shXv.js} +1 -0
  119. package/lib/serve-static.d.ts +1 -1
  120. package/lib/streaming.d.ts +1 -1
  121. package/lib/testing.d.ts +4 -4
  122. package/lib/timeout.d.ts +1 -1
  123. package/lib/timing.d.ts +1 -1
  124. package/lib/tiny.d.ts +8 -0
  125. package/lib/tiny.js +15 -0
  126. package/lib/trailing-slash.d.ts +1 -1
  127. package/lib/{types-CHesD1KG.d.ts → types-CQiGmsWW.d.ts} +3 -3
  128. package/lib/{types-CwrQjlaM.d.ts → types-Lqx5BxQF.d.ts} +10 -1
  129. package/lib/{types-CDO6I3B0.d.ts → types-nEL8VPDC.d.ts} +1 -1
  130. package/lib/types.d.ts +1 -1
  131. package/lib/utils/body.d.ts +1 -1
  132. package/lib/utils/cookie.d.ts +1 -1
  133. package/lib/utils/crypto.d.ts +1 -1
  134. package/lib/utils/html.d.ts +1 -45
  135. package/lib/utils/ipaddr.d.ts +1 -1
  136. package/lib/utils/jwt.d.ts +1 -1
  137. package/lib/utils/types.d.ts +2 -2
  138. package/lib/validator.d.ts +2 -2
  139. package/lib/ws.d.ts +1 -1
  140. package/package.json +69 -1
  141. package/plugins/acryl-inspect/upstream.json +1 -1
  142. package/plugins/acryl-node-server/upstream.json +1 -1
  143. package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +506 -0
  144. package/plugins/acryl-router/src/core/helper/css/common.ts +337 -0
  145. package/plugins/acryl-router/src/core/helper/css/index.test.tsx +678 -0
  146. package/plugins/acryl-router/src/core/helper/css/index.ts +262 -0
  147. package/plugins/acryl-router/src/core/helper/dev/index.test.ts +181 -0
  148. package/plugins/acryl-router/src/core/helper/dev/index.ts +79 -0
  149. package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +356 -0
  150. package/plugins/acryl-router/src/core/helper/proxy/index.ts +203 -0
  151. package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +557 -0
  152. package/plugins/acryl-router/src/core/jsx/base.test.tsx +97 -0
  153. package/plugins/acryl-router/src/core/jsx/base.ts +490 -0
  154. package/plugins/acryl-router/src/core/jsx/children.test.ts +52 -0
  155. package/plugins/acryl-router/src/core/jsx/children.ts +20 -0
  156. package/plugins/acryl-router/src/core/jsx/components.test.tsx +936 -0
  157. package/plugins/acryl-router/src/core/jsx/components.ts +257 -0
  158. package/plugins/acryl-router/src/core/jsx/constants.ts +6 -0
  159. package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +655 -0
  160. package/plugins/acryl-router/src/core/jsx/context.ts +266 -0
  161. package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +128 -0
  162. package/plugins/acryl-router/src/core/jsx/dom/client.ts +89 -0
  163. package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +330 -0
  164. package/plugins/acryl-router/src/core/jsx/dom/components.ts +39 -0
  165. package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +184 -0
  166. package/plugins/acryl-router/src/core/jsx/dom/context.ts +52 -0
  167. package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +149 -0
  168. package/plugins/acryl-router/src/core/jsx/dom/css.ts +263 -0
  169. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +172 -0
  170. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.ts +91 -0
  171. package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +3681 -0
  172. package/plugins/acryl-router/src/core/jsx/dom/index.ts +170 -0
  173. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +1344 -0
  174. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.ts +438 -0
  175. package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.ts +23 -0
  176. package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.ts +8 -0
  177. package/plugins/acryl-router/src/core/jsx/dom/render.ts +879 -0
  178. package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +199 -0
  179. package/plugins/acryl-router/src/core/jsx/dom/server.ts +76 -0
  180. package/plugins/acryl-router/src/core/jsx/dom/utils.ts +7 -0
  181. package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +859 -0
  182. package/plugins/acryl-router/src/core/jsx/hooks/index.ts +437 -0
  183. package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +40 -0
  184. package/plugins/acryl-router/src/core/jsx/index.test.tsx +1744 -0
  185. package/plugins/acryl-router/src/core/jsx/index.ts +115 -0
  186. package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.ts +23 -0
  187. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +666 -0
  188. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.ts +210 -0
  189. package/plugins/acryl-router/src/core/jsx/intrinsic-elements.ts +938 -0
  190. package/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts +26 -0
  191. package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +85 -0
  192. package/plugins/acryl-router/src/core/jsx/jsx-runtime.ts +51 -0
  193. package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +1281 -0
  194. package/plugins/acryl-router/src/core/jsx/streaming.ts +231 -0
  195. package/plugins/acryl-router/src/core/jsx/types.ts +41 -0
  196. package/plugins/acryl-router/src/core/jsx/utils.test.ts +366 -0
  197. package/plugins/acryl-router/src/core/jsx/utils.ts +202 -0
  198. package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +1211 -0
  199. package/plugins/acryl-router/src/core/middleware/cache/index.ts +323 -0
  200. package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +334 -0
  201. package/plugins/acryl-router/src/core/middleware/mount/index.ts +108 -0
  202. package/plugins/acryl-router/src/core/preset/quick.test.ts +42 -0
  203. package/plugins/acryl-router/src/core/preset/quick.ts +24 -0
  204. package/plugins/acryl-router/src/core/preset/tiny.test.ts +42 -0
  205. package/plugins/acryl-router/src/core/preset/tiny.ts +20 -0
  206. package/plugins/acryl-router/src/core/utils/types.ts +9 -0
  207. package/plugins/acryl-router/src/helpers/index.ts +3 -0
  208. package/plugins/acryl-router/src/middleware/index.ts +2 -0
  209. package/plugins/acryl-router/src/service.ts +22 -5
  210. package/plugins/acryl-router/tests/jsx.spec.tsx +63 -0
  211. package/plugins/acryl-router/upstream.json +3 -3
  212. package/plugins/acryl-router-http/upstream.json +1 -1
  213. package/plugins/acryl-state/upstream.json +1 -1
  214. package/plugins/desktop/upstream.json +1 -1
  215. package/plugins/desktop-web/upstream.json +1 -1
  216. package/plugins/save/plugin.json +1 -1
  217. 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