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.
Files changed (181) hide show
  1. package/README.md +4 -1
  2. package/docs/framework/README.md +3 -2
  3. package/docs/framework/UPSTREAM.md +1 -1
  4. package/docs/framework/cordis/acryl-in-a-cordis-app.md +6 -0
  5. package/docs/framework/getting-started/basic.md +1 -0
  6. package/docs/framework/getting-started/desktop.md +41 -0
  7. package/docs/framework/guides/jsx-dom.md +0 -3
  8. package/docs/framework/guides/jsx.md +0 -3
  9. package/docs/framework/helpers/css.md +0 -3
  10. package/docs/framework/upstream.json +3 -3
  11. package/lib/accepts.d.ts +1 -1
  12. package/lib/acryl-base.d.ts +1 -1
  13. package/lib/adapter.d.ts +1 -1
  14. package/lib/{app-BdrVHBlJ.d.ts → app-wFwpg-bt.d.ts} +1 -1
  15. package/lib/base-BpQy2Rk7.d.ts +783 -0
  16. package/lib/base-ddiVmiE7.js +721 -0
  17. package/lib/basic-auth.d.ts +1 -1
  18. package/lib/bearer-auth.d.ts +1 -1
  19. package/lib/body-limit.d.ts +1 -1
  20. package/lib/cache.d.ts +1 -1
  21. package/lib/client.d.ts +2 -2
  22. package/lib/combine.d.ts +1 -1
  23. package/lib/common-D8Z4JRCw.d.ts +46 -0
  24. package/lib/components-Ce5FRyHU.js +268 -0
  25. package/lib/components-DYZdxJIJ.js +24 -0
  26. package/lib/compress.d.ts +1 -1
  27. package/lib/conninfo.d.ts +2 -2
  28. package/lib/constants-BR9jM9BC.js +9 -0
  29. package/lib/{context-B5Y1lLha.d.ts → context-DVQBjuns.d.ts} +1 -1
  30. package/lib/context-DxFdY_un.d.ts +250 -0
  31. package/lib/context-storage.d.ts +1 -1
  32. package/lib/{cookie-D9_nQbsM.d.ts → cookie-CCelEqky.d.ts} +1 -1
  33. package/lib/cookie.d.ts +3 -3
  34. package/lib/cors.d.ts +1 -1
  35. package/lib/csrf.d.ts +1 -1
  36. package/lib/css-D2jb7X8b.js +320 -0
  37. package/lib/css.d.ts +82 -0
  38. package/lib/css.js +134 -0
  39. package/lib/dev.d.ts +2 -2
  40. package/lib/etag.d.ts +1 -1
  41. package/lib/factory.d.ts +2 -2
  42. package/lib/hooks-Bq5q9IMl.js +762 -0
  43. package/lib/html-SllAe0YT.d.ts +46 -0
  44. package/lib/html.d.ts +1 -1
  45. package/lib/index-C850MeBA.d.ts +94 -0
  46. package/lib/{index-m12zcmph.d.ts → index-CUTfU69R.d.ts} +3 -3
  47. package/lib/{index-DtEoak1i.d.ts → index-Cqcdluyn.d.ts} +2 -2
  48. package/lib/{index-Kqou8Fjh.d.ts → index-CsvB-F9j.d.ts} +2 -2
  49. package/lib/index.d.ts +4 -4
  50. package/lib/ip-restriction.d.ts +2 -2
  51. package/lib/jsx/dom/client.d.ts +30 -0
  52. package/lib/jsx/dom/client.js +49 -0
  53. package/lib/jsx/dom/css.d.ts +84 -0
  54. package/lib/jsx/dom/css.js +2 -0
  55. package/lib/jsx/dom/jsx-dev-runtime.d.ts +3 -0
  56. package/lib/jsx/dom/jsx-dev-runtime.js +2 -0
  57. package/lib/jsx/dom/jsx-runtime.d.ts +3 -0
  58. package/lib/jsx/dom/jsx-runtime.js +2 -0
  59. package/lib/jsx/dom/server.d.ts +105 -0
  60. package/lib/jsx/dom/server.js +37 -0
  61. package/lib/jsx/dom.d.ts +5 -0
  62. package/lib/jsx/dom.js +74 -0
  63. package/lib/jsx/jsx-dev-runtime.d.ts +6 -0
  64. package/lib/jsx/jsx-dev-runtime.js +14 -0
  65. package/lib/jsx/jsx-runtime.d.ts +10 -0
  66. package/lib/jsx/jsx-runtime.js +30 -0
  67. package/lib/jsx/streaming.d.ts +2 -0
  68. package/lib/jsx/streaming.js +2 -0
  69. package/lib/jsx-dev-runtime-B22O-Npk.d.ts +7 -0
  70. package/lib/jsx-dev-runtime-kAhR_4C7.js +354 -0
  71. package/lib/jsx-runtime-BVVgDSdq.d.ts +1 -0
  72. package/lib/jsx.d.ts +3 -0
  73. package/lib/jsx.js +47 -0
  74. package/lib/jwk.d.ts +4 -4
  75. package/lib/{jwt-BA65QhnJ.d.ts → jwt-BARhotDz.d.ts} +1 -1
  76. package/lib/jwt.d.ts +4 -4
  77. package/lib/language.d.ts +1 -1
  78. package/lib/logger.d.ts +1 -1
  79. package/lib/loopback-only.d.ts +1 -1
  80. package/lib/method-not-allowed.d.ts +2 -2
  81. package/lib/method-override.d.ts +2 -2
  82. package/lib/mount.d.ts +1 -1
  83. package/lib/node-server/conninfo.d.ts +1 -1
  84. package/lib/node-server/serve-static.d.ts +1 -1
  85. package/lib/node-server/serve-static.js +1 -1
  86. package/lib/node-server.d.ts +1 -1
  87. package/lib/node-server.js +1 -1
  88. package/lib/powered-by.d.ts +1 -1
  89. package/lib/pretty-json.d.ts +1 -1
  90. package/lib/proxy.d.ts +1 -1
  91. package/lib/quick.d.ts +1 -1
  92. package/lib/request-id.d.ts +1 -1
  93. package/lib/request.d.ts +1 -1
  94. package/lib/route.d.ts +1 -1
  95. package/lib/schema-validator.d.ts +1 -1
  96. package/lib/secure-headers.d.ts +1 -1
  97. package/lib/{serve-static-Dw_m6KxR.js → serve-static-Cw23shXv.js} +1 -0
  98. package/lib/serve-static.d.ts +1 -1
  99. package/lib/streaming.d.ts +1 -1
  100. package/lib/testing.d.ts +4 -4
  101. package/lib/timeout.d.ts +1 -1
  102. package/lib/timing.d.ts +1 -1
  103. package/lib/tiny.d.ts +1 -1
  104. package/lib/trailing-slash.d.ts +1 -1
  105. package/lib/{types-Bd29gh2o.d.ts → types-CQiGmsWW.d.ts} +3 -3
  106. package/lib/{types-D9DEXMjE.d.ts → types-Lqx5BxQF.d.ts} +8 -1
  107. package/lib/{types-ZM9gIiKh.d.ts → types-nEL8VPDC.d.ts} +1 -1
  108. package/lib/types.d.ts +1 -1
  109. package/lib/utils/body.d.ts +1 -1
  110. package/lib/utils/cookie.d.ts +1 -1
  111. package/lib/utils/crypto.d.ts +1 -1
  112. package/lib/utils/html.d.ts +1 -45
  113. package/lib/utils/ipaddr.d.ts +1 -1
  114. package/lib/utils/jwt.d.ts +1 -1
  115. package/lib/utils/types.d.ts +2 -2
  116. package/lib/validator.d.ts +2 -2
  117. package/lib/ws.d.ts +1 -1
  118. package/package.json +45 -1
  119. package/plugins/acryl-inspect/upstream.json +1 -1
  120. package/plugins/acryl-node-server/upstream.json +1 -1
  121. package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +506 -0
  122. package/plugins/acryl-router/src/core/helper/css/common.ts +337 -0
  123. package/plugins/acryl-router/src/core/helper/css/index.test.tsx +678 -0
  124. package/plugins/acryl-router/src/core/helper/css/index.ts +262 -0
  125. package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +557 -0
  126. package/plugins/acryl-router/src/core/jsx/base.test.tsx +97 -0
  127. package/plugins/acryl-router/src/core/jsx/base.ts +490 -0
  128. package/plugins/acryl-router/src/core/jsx/children.test.ts +52 -0
  129. package/plugins/acryl-router/src/core/jsx/children.ts +20 -0
  130. package/plugins/acryl-router/src/core/jsx/components.test.tsx +936 -0
  131. package/plugins/acryl-router/src/core/jsx/components.ts +257 -0
  132. package/plugins/acryl-router/src/core/jsx/constants.ts +6 -0
  133. package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +655 -0
  134. package/plugins/acryl-router/src/core/jsx/context.ts +266 -0
  135. package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +128 -0
  136. package/plugins/acryl-router/src/core/jsx/dom/client.ts +89 -0
  137. package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +330 -0
  138. package/plugins/acryl-router/src/core/jsx/dom/components.ts +39 -0
  139. package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +184 -0
  140. package/plugins/acryl-router/src/core/jsx/dom/context.ts +52 -0
  141. package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +149 -0
  142. package/plugins/acryl-router/src/core/jsx/dom/css.ts +263 -0
  143. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +172 -0
  144. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.ts +91 -0
  145. package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +3681 -0
  146. package/plugins/acryl-router/src/core/jsx/dom/index.ts +170 -0
  147. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +1344 -0
  148. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.ts +438 -0
  149. package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.ts +23 -0
  150. package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.ts +8 -0
  151. package/plugins/acryl-router/src/core/jsx/dom/render.ts +879 -0
  152. package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +199 -0
  153. package/plugins/acryl-router/src/core/jsx/dom/server.ts +76 -0
  154. package/plugins/acryl-router/src/core/jsx/dom/utils.ts +7 -0
  155. package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +859 -0
  156. package/plugins/acryl-router/src/core/jsx/hooks/index.ts +437 -0
  157. package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +40 -0
  158. package/plugins/acryl-router/src/core/jsx/index.test.tsx +1744 -0
  159. package/plugins/acryl-router/src/core/jsx/index.ts +115 -0
  160. package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.ts +23 -0
  161. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +666 -0
  162. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.ts +210 -0
  163. package/plugins/acryl-router/src/core/jsx/intrinsic-elements.ts +938 -0
  164. package/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts +26 -0
  165. package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +85 -0
  166. package/plugins/acryl-router/src/core/jsx/jsx-runtime.ts +51 -0
  167. package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +1281 -0
  168. package/plugins/acryl-router/src/core/jsx/streaming.ts +231 -0
  169. package/plugins/acryl-router/src/core/jsx/types.ts +41 -0
  170. package/plugins/acryl-router/src/core/jsx/utils.test.ts +366 -0
  171. package/plugins/acryl-router/src/core/jsx/utils.ts +202 -0
  172. package/plugins/acryl-router/src/core/utils/types.ts +6 -0
  173. package/plugins/acryl-router/src/helpers/index.ts +1 -0
  174. package/plugins/acryl-router/tests/jsx.spec.tsx +63 -0
  175. package/plugins/acryl-router/upstream.json +3 -3
  176. package/plugins/acryl-router-http/upstream.json +1 -1
  177. package/plugins/acryl-state/upstream.json +1 -1
  178. package/plugins/desktop/upstream.json +1 -1
  179. package/plugins/desktop-web/upstream.json +1 -1
  180. package/plugins/save/plugin.json +1 -1
  181. package/templates/todo/upstream.json +1 -1
@@ -0,0 +1,490 @@
1
+ import { raw } from '../helper/html/index.ts'
2
+ import { escapeToBuffer, resolveCallbackSync, stringBufferToString } from '../utils/html.ts'
3
+ import type { HtmlEscaped, HtmlEscapedString, StringBufferWithCallbacks } from '../utils/html.ts'
4
+ import { DOM_RENDERER, DOM_MEMO } from './constants.ts'
5
+ import {
6
+ captureRenderContext,
7
+ createContext,
8
+ globalContexts,
9
+ runWithRenderContext,
10
+ useContext,
11
+ } from './context.ts'
12
+ import type { Context } from './context.ts'
13
+ import { domRenderers } from './intrinsic-element/common.ts'
14
+ import * as intrinsicElementTags from './intrinsic-element/components.ts'
15
+ import type { JSX as AcrylJSX } from './intrinsic-elements.ts'
16
+ import {
17
+ isValidAttributeName,
18
+ isValidTagName,
19
+ normalizeIntrinsicElementKey,
20
+ styleObjectForEach,
21
+ } from './utils.ts'
22
+
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ export type Props = Record<string, any>
25
+ type FunctionComponentResult =
26
+ | HtmlEscapedString
27
+ | JSXNode
28
+ | Child[]
29
+ | Promise<HtmlEscapedString | JSXNode | Child[]>
30
+ | null
31
+ export type FC<P = Props> = {
32
+ (props: P): FunctionComponentResult
33
+ defaultProps?: Partial<P> | undefined
34
+ displayName?: string | undefined
35
+ }
36
+ export type DOMAttributes = AcrylJSX.HTMLAttributes
37
+
38
+ // eslint-disable-next-line @typescript-eslint/no-namespace
39
+ export namespace JSX {
40
+ export type Element = HtmlEscapedString | Promise<HtmlEscapedString>
41
+ export type ElementType = string | ((props: never) => FunctionComponentResult)
42
+ export interface ElementChildrenAttribute {
43
+ children: Child
44
+ }
45
+ // `AcrylJSX.IntrinsicElements`, not the top-level alias: the bundled dts emit renames the namespace
46
+ // import correctly but drops an import alias that collides with this member name (TS2310).
47
+ export interface IntrinsicElements extends AcrylJSX.IntrinsicElements {
48
+ [tagName: string]: Props
49
+ }
50
+ export interface IntrinsicAttributes {
51
+ key?: string | number | bigint | null | undefined
52
+ }
53
+ }
54
+
55
+ let nameSpaceContext: Context<string> | undefined = undefined
56
+ export const getNameSpaceContext = () => nameSpaceContext
57
+
58
+ const toSVGAttributeName = (key: string): string =>
59
+ /[A-Z]/.test(key) &&
60
+ // Presentation attributes are findable in style object. "clip-path", "font-size", "stroke-width", etc.
61
+ // Or other un-deprecated kebab-case attributes. "overline-position", "paint-order", "strikethrough-position", etc.
62
+ key.match(
63
+ /^(?:al|basel|clip(?:Path|Rule)$|co|do|fill|fl|fo|gl|let|lig|i|marker[EMS]|o|pai|pointe|sh|st[or]|text[^L]|tr|u|ve|w)/
64
+ )
65
+ ? key.replace(/([A-Z])/g, '-$1').toLowerCase()
66
+ : key
67
+
68
+ const emptyTags = [
69
+ 'area',
70
+ 'base',
71
+ 'br',
72
+ 'col',
73
+ 'embed',
74
+ 'hr',
75
+ 'img',
76
+ 'input',
77
+ 'keygen',
78
+ 'link',
79
+ 'meta',
80
+ 'param',
81
+ 'source',
82
+ 'track',
83
+ 'wbr',
84
+ ]
85
+ export const booleanAttributes = [
86
+ 'allowfullscreen',
87
+ 'async',
88
+ 'autofocus',
89
+ 'autoplay',
90
+ 'checked',
91
+ 'controls',
92
+ 'default',
93
+ 'defer',
94
+ 'disabled',
95
+ 'download',
96
+ 'formnovalidate',
97
+ 'hidden',
98
+ 'inert',
99
+ 'ismap',
100
+ 'itemscope',
101
+ 'loop',
102
+ 'multiple',
103
+ 'muted',
104
+ 'nomodule',
105
+ 'novalidate',
106
+ 'open',
107
+ 'playsinline',
108
+ 'readonly',
109
+ 'required',
110
+ 'reversed',
111
+ 'selected',
112
+ ]
113
+
114
+ type SuspendedContext = <T>(callback: () => T) => T
115
+
116
+ const resolveFunctionComponentResult = (
117
+ result: Promise<string | JSXNode | Child[]>,
118
+ suspendedContext?: SuspendedContext
119
+ ): Promise<string> =>
120
+ result.then((resolved) => {
121
+ if (
122
+ typeof resolved !== 'string' &&
123
+ !Array.isArray(resolved) &&
124
+ !(resolved instanceof JSXNode)
125
+ ) {
126
+ return resolved
127
+ }
128
+ const children = Array.isArray(resolved) ? resolved : [resolved as Child]
129
+ const render = () => {
130
+ const buffer: StringBufferWithCallbacks = [''] as StringBufferWithCallbacks
131
+ childrenToStringToBuffer(children, buffer)
132
+ return buffer.length === 1
133
+ ? raw(buffer[0], buffer.callbacks)
134
+ : stringBufferToString(buffer, buffer.callbacks)
135
+ }
136
+ return suspendedContext ? suspendedContext(render) : runWithRenderContext(render)
137
+ })
138
+
139
+ const childrenToStringToBuffer = (children: Child[], buffer: StringBufferWithCallbacks): void => {
140
+ for (let i = 0, len = children.length; i < len; i++) {
141
+ const child = children[i]
142
+ if (typeof child === 'string') {
143
+ escapeToBuffer(child, buffer)
144
+ } else if (typeof child === 'boolean' || child === null || child === undefined) {
145
+ continue
146
+ } else if (child instanceof JSXNode) {
147
+ child.toStringToBuffer(buffer)
148
+ } else if (typeof child === 'number') {
149
+ ;(buffer[0] as string) += child
150
+ } else if ((child as unknown as HtmlEscaped).isEscaped) {
151
+ ;(buffer[0] as string) += child
152
+ const callbacks = (child as unknown as HtmlEscapedString).callbacks
153
+ if (callbacks) {
154
+ buffer.callbacks ||= []
155
+ buffer.callbacks.push(...callbacks)
156
+ }
157
+ } else if (child instanceof Promise) {
158
+ buffer.unshift('', child)
159
+ } else {
160
+ // `child` type is `Child[]`, so stringify recursively
161
+ childrenToStringToBuffer(child, buffer)
162
+ }
163
+ }
164
+ }
165
+
166
+ export type Child =
167
+ | string
168
+ | Promise<string>
169
+ | number
170
+ | JSXNode
171
+ | null
172
+ | undefined
173
+ | boolean
174
+ | Child[]
175
+
176
+ export const renderChildren = (children: Child[]): HtmlEscapedString | Promise<HtmlEscapedString> =>
177
+ runWithRenderContext(() => {
178
+ const buffer: StringBufferWithCallbacks = [''] as StringBufferWithCallbacks
179
+ childrenToStringToBuffer(children, buffer)
180
+ return buffer.length === 1
181
+ ? raw(buffer[0], buffer.callbacks)
182
+ : stringBufferToString(buffer, buffer.callbacks)
183
+ })
184
+
185
+ export const isUntrustedObject = (value: unknown): boolean =>
186
+ typeof value === 'object' &&
187
+ value !== null &&
188
+ !Array.isArray(value) &&
189
+ !(value instanceof JSXNode) &&
190
+ !(value instanceof Promise) &&
191
+ !(value as HtmlEscaped).isEscaped &&
192
+ typeof (value as { toString?: unknown }).toString === 'function'
193
+
194
+ export const renderUntrustedObject = (
195
+ value: unknown
196
+ ): HtmlEscapedString | Promise<HtmlEscapedString> => {
197
+ const stringified = (value as { toString(): unknown }).toString()
198
+ const escape = (result: unknown) => renderChildren([String(result)])
199
+ return stringified instanceof Promise ? stringified.then(escape) : escape(stringified)
200
+ }
201
+
202
+ export class JSXNode implements HtmlEscaped {
203
+ tag: string | Function
204
+ props: Props
205
+ key?: string
206
+ children: Child[]
207
+ isEscaped: true = true as const
208
+ constructor(tag: string | Function, props: Props, children: Child[]) {
209
+ if (typeof tag !== 'function' && !isValidTagName(tag)) {
210
+ throw new Error(`Invalid JSX tag name: ${tag}`)
211
+ }
212
+ this.tag = tag
213
+ this.props = props
214
+ this.children = children
215
+ }
216
+
217
+ get type(): string | Function {
218
+ return this.tag as string
219
+ }
220
+
221
+ // Added for compatibility with libraries that rely on React's internal structure
222
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
223
+ get ref(): any {
224
+ return this.props.ref || null
225
+ }
226
+
227
+ toString(): string | Promise<string> {
228
+ const render = () => {
229
+ const buffer: StringBufferWithCallbacks = [''] as StringBufferWithCallbacks
230
+ this.toStringToBuffer(buffer)
231
+ return buffer.length === 1
232
+ ? 'callbacks' in buffer
233
+ ? resolveCallbackSync(raw(buffer[0], buffer.callbacks)).toString()
234
+ : buffer[0]
235
+ : stringBufferToString(buffer, buffer.callbacks)
236
+ }
237
+ return runWithRenderContext(render)
238
+ }
239
+
240
+ toStringToBuffer(buffer: StringBufferWithCallbacks): void {
241
+ const tag = this.tag as string
242
+ const props = this.props
243
+ let { children } = this
244
+
245
+ buffer[0] += `<${tag}`
246
+
247
+ const normalizeKey: (key: string) => string =
248
+ tag === 'svg' || (nameSpaceContext && useContext(nameSpaceContext) === 'svg')
249
+ ? (key) => toSVGAttributeName(normalizeIntrinsicElementKey(key))
250
+ : (key) => normalizeIntrinsicElementKey(key)
251
+ for (let [key, v] of Object.entries(props)) {
252
+ key = normalizeKey(key)
253
+ if (!isValidAttributeName(key)) {
254
+ continue
255
+ }
256
+ if (key === 'children') {
257
+ // skip children
258
+ } else if (key === 'style' && typeof v === 'object') {
259
+ // object to style strings
260
+ let styleStr = ''
261
+ styleObjectForEach(v, (property, value) => {
262
+ if (value != null) {
263
+ styleStr += `${styleStr ? ';' : ''}${property}:${value}`
264
+ }
265
+ })
266
+ buffer[0] += ' style="'
267
+ escapeToBuffer(styleStr, buffer)
268
+ buffer[0] += '"'
269
+ } else if (typeof v === 'string') {
270
+ buffer[0] += ` ${key}="`
271
+ escapeToBuffer(v, buffer)
272
+ buffer[0] += '"'
273
+ } else if (v === null || v === undefined) {
274
+ // Do nothing
275
+ } else if (typeof v === 'number' || (v as HtmlEscaped).isEscaped) {
276
+ buffer[0] += ` ${key}="${v}"`
277
+ } else if (typeof v === 'boolean' && booleanAttributes.includes(key)) {
278
+ if (v) {
279
+ buffer[0] += ` ${key}=""`
280
+ }
281
+ } else if (key === 'dangerouslySetInnerHTML') {
282
+ if (children.length > 0) {
283
+ throw new Error('Can only set one of `children` or `props.dangerouslySetInnerHTML`.')
284
+ }
285
+
286
+ children = [raw(v.__html)]
287
+ } else if (v instanceof Promise) {
288
+ buffer[0] += ` ${key}="`
289
+ buffer.unshift('"', v)
290
+ } else if (typeof v === 'function') {
291
+ if (!key.startsWith('on') && key !== 'ref') {
292
+ throw new Error(`Invalid prop '${key}' of type 'function' supplied to '${tag}'.`)
293
+ }
294
+ // maybe event handler for client components, just ignore in server components
295
+ } else {
296
+ buffer[0] += ` ${key}="`
297
+ escapeToBuffer(v.toString(), buffer)
298
+ buffer[0] += '"'
299
+ }
300
+ }
301
+
302
+ if (emptyTags.includes(tag as string) && children.length === 0) {
303
+ buffer[0] += '/>'
304
+ return
305
+ }
306
+
307
+ buffer[0] += '>'
308
+
309
+ childrenToStringToBuffer(children, buffer)
310
+
311
+ buffer[0] += `</${tag}>`
312
+ }
313
+ }
314
+
315
+ class JSXFunctionNode extends JSXNode {
316
+ override toStringToBuffer(buffer: StringBufferWithCallbacks): void {
317
+ const { children } = this
318
+
319
+ const props = { ...this.props }
320
+ if (children.length) {
321
+ props.children = children.length === 1 ? children[0] : children
322
+ }
323
+
324
+ const res = (this.tag as Function).call(null, props)
325
+
326
+ if (typeof res === 'boolean' || res == null) {
327
+ // boolean or null or undefined
328
+ return
329
+ } else if (res instanceof Promise) {
330
+ if (globalContexts.length === 0) {
331
+ buffer.unshift('', resolveFunctionComponentResult(res))
332
+ } else {
333
+ // save the current context state for resuming the suspended subtree
334
+ buffer.unshift('', resolveFunctionComponentResult(res, captureRenderContext()))
335
+ }
336
+ } else if (res instanceof JSXNode) {
337
+ res.toStringToBuffer(buffer)
338
+ } else if (Array.isArray(res)) {
339
+ childrenToStringToBuffer(res, buffer)
340
+ } else if (typeof res === 'number' || (res as HtmlEscaped).isEscaped) {
341
+ buffer[0] += res
342
+ if (res.callbacks) {
343
+ buffer.callbacks ||= []
344
+ buffer.callbacks.push(...res.callbacks)
345
+ }
346
+ } else {
347
+ escapeToBuffer(res, buffer)
348
+ }
349
+ }
350
+ }
351
+
352
+ export class JSXFragmentNode extends JSXNode {
353
+ override toStringToBuffer(buffer: StringBufferWithCallbacks): void {
354
+ childrenToStringToBuffer(this.children, buffer)
355
+ }
356
+ }
357
+
358
+ export const jsx = (tag: string | Function, props: Props | null, ...children: Child[]): JSXNode => {
359
+ props ??= {}
360
+ if (children.length) {
361
+ props.children = children.length === 1 ? children[0] : children
362
+ }
363
+
364
+ const key = props.key
365
+ delete props['key']
366
+
367
+ const node = jsxFn(tag, props, children)
368
+ node.key = key
369
+ return node
370
+ }
371
+
372
+ let initDomRenderer = false
373
+ export const jsxFn = (tag: string | Function, props: Props, children: Child[]): JSXNode => {
374
+ if (!initDomRenderer) {
375
+ for (const k in domRenderers) {
376
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
377
+ ;(intrinsicElementTags[k as keyof typeof intrinsicElementTags] as any)[DOM_RENDERER] =
378
+ domRenderers[k]
379
+ }
380
+ initDomRenderer = true
381
+ }
382
+
383
+ if (typeof tag === 'function') {
384
+ return new JSXFunctionNode(tag, props, children)
385
+ } else if (intrinsicElementTags[tag as keyof typeof intrinsicElementTags]) {
386
+ return new JSXFunctionNode(
387
+ intrinsicElementTags[tag as keyof typeof intrinsicElementTags],
388
+ props,
389
+ children
390
+ )
391
+ } else if (tag === 'svg' || tag === 'head') {
392
+ nameSpaceContext ||= createContext('')
393
+ return new JSXNode(tag, props, [
394
+ new JSXFunctionNode(
395
+ nameSpaceContext,
396
+ {
397
+ value: tag,
398
+ },
399
+ children
400
+ ),
401
+ ])
402
+ } else {
403
+ return new JSXNode(tag, props, children)
404
+ }
405
+ }
406
+
407
+ export const shallowEqual = (a: Props, b: Props): boolean => {
408
+ if (a === b) {
409
+ return true
410
+ }
411
+
412
+ const aKeys = Object.keys(a).sort()
413
+ const bKeys = Object.keys(b).sort()
414
+ if (aKeys.length !== bKeys.length) {
415
+ return false
416
+ }
417
+
418
+ for (let i = 0, len = aKeys.length; i < len; i++) {
419
+ if (
420
+ aKeys[i] === 'children' &&
421
+ bKeys[i] === 'children' &&
422
+ !a.children?.length &&
423
+ !b.children?.length
424
+ ) {
425
+ continue
426
+ } else if (a[aKeys[i]] !== b[aKeys[i]]) {
427
+ return false
428
+ }
429
+ }
430
+
431
+ return true
432
+ }
433
+
434
+ export type MemorableFC<T> = FC<T> & {
435
+ [DOM_MEMO]: (prevProps: Readonly<T>, nextProps: Readonly<T>) => boolean
436
+ }
437
+ export const memo = <T>(
438
+ component: FC<T>,
439
+ propsAreEqual: (prevProps: Readonly<T>, nextProps: Readonly<T>) => boolean = shallowEqual
440
+ ): FC<T> => {
441
+ const wrapper: MemorableFC<T> = ((props: T) => component(props)) as MemorableFC<T>
442
+
443
+ // This function is for toString(), but it can also be used for DOM renderer.
444
+ // So, set DOM_MEMO and DOM_RENDERER for DOM renderer.
445
+ wrapper[DOM_MEMO] = propsAreEqual
446
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
447
+ ;(wrapper as any)[DOM_RENDERER] = component
448
+
449
+ return wrapper as FC<T>
450
+ }
451
+
452
+ export const Fragment = ({
453
+ children,
454
+ }: {
455
+ key?: string
456
+ children?: Child | HtmlEscapedString
457
+ }): HtmlEscapedString => {
458
+ return new JSXFragmentNode(
459
+ '',
460
+ {
461
+ children,
462
+ },
463
+ Array.isArray(children) ? children : children ? [children] : []
464
+ ) as never
465
+ }
466
+
467
+ export const isValidElement = (element: unknown): element is JSXNode => {
468
+ return !!(element && typeof element === 'object' && 'tag' in element && 'props' in element)
469
+ }
470
+
471
+ export const cloneElement = <T extends JSXNode | JSX.Element>(
472
+ element: T,
473
+ props: Partial<Props>,
474
+ ...children: Child[]
475
+ ): T => {
476
+ let childrenToClone
477
+ if (children.length > 0) {
478
+ childrenToClone = children
479
+ } else {
480
+ const c = (element as JSXNode).props.children
481
+ childrenToClone = c !== undefined ? (Array.isArray(c) ? c : [c]) : []
482
+ }
483
+ return jsx(
484
+ (element as JSXNode).tag,
485
+ { ...(element as JSXNode).props, ...props },
486
+ ...childrenToClone
487
+ ) as T
488
+ }
489
+
490
+ export const reactAPICompatVersion = '19.0.0-acryl-jsx'
@@ -0,0 +1,52 @@
1
+ import { createElement } from './index.ts'
2
+ import { Children } from './children.ts'
3
+
4
+ describe('map', () => {
5
+ it('should map children', () => {
6
+ const element = createElement('div', null, 1, 2, 3)
7
+ const result = Children.map(element.children, (child) => (child as number) * 2)
8
+ expect(result).toEqual([2, 4, 6])
9
+ })
10
+ })
11
+
12
+ describe('forEach', () => {
13
+ it('should iterate children', () => {
14
+ const element = createElement('div', null, 1, 2, 3)
15
+ const result: number[] = []
16
+ Children.forEach(element.children, (child) => {
17
+ result.push(child as number)
18
+ })
19
+ expect(result).toEqual([1, 2, 3])
20
+ })
21
+ })
22
+
23
+ describe('count', () => {
24
+ it('should count children', () => {
25
+ const element = createElement('div', null, 1, 2, 3)
26
+ const result = Children.count(element.children)
27
+ expect(result).toBe(3)
28
+ })
29
+ })
30
+
31
+ describe('only', () => {
32
+ it('should return the only child', () => {
33
+ const element = createElement('div', null, 1)
34
+ const result = Children.only(element.children)
35
+ expect(result).toBe(1)
36
+ })
37
+
38
+ it('should throw an error if there are multiple children', () => {
39
+ const element = createElement('div', null, 1, 2)
40
+ expect(() => Children.only(element.children)).toThrowError(
41
+ 'Children.only() expects only one child'
42
+ )
43
+ })
44
+ })
45
+
46
+ describe('toArray', () => {
47
+ it('should convert children to an array', () => {
48
+ const element = createElement('div', null, 1, 2, 3)
49
+ const result = Children.toArray(element.children)
50
+ expect(result).toEqual([1, 2, 3])
51
+ })
52
+ })
@@ -0,0 +1,20 @@
1
+ import type { Child } from './base.ts'
2
+
3
+ export const toArray = (children: Child): Child[] =>
4
+ Array.isArray(children) ? children : [children]
5
+ export const Children = {
6
+ map: (children: Child[], fn: (child: Child, index: number) => Child): Child[] =>
7
+ toArray(children).map(fn),
8
+ forEach: (children: Child[], fn: (child: Child, index: number) => void): void => {
9
+ toArray(children).forEach(fn)
10
+ },
11
+ count: (children: Child[]): number => toArray(children).length,
12
+ only: (_children: Child[]): Child => {
13
+ const children = toArray(_children)
14
+ if (children.length !== 1) {
15
+ throw new Error('Children.only() expects only one child')
16
+ }
17
+ return children[0]
18
+ },
19
+ toArray,
20
+ }