@typed/template 0.9.6 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/RenderQueue/package.json +6 -0
  2. package/dist/cjs/Directive.js +1 -1
  3. package/dist/cjs/Directive.js.map +1 -1
  4. package/dist/cjs/ElementRef.js +1 -1
  5. package/dist/cjs/ElementRef.js.map +1 -1
  6. package/dist/cjs/ElementSource.js +1 -1
  7. package/dist/cjs/ElementSource.js.map +1 -1
  8. package/dist/cjs/EventHandler.js +11 -4
  9. package/dist/cjs/EventHandler.js.map +1 -1
  10. package/dist/cjs/Html.js +84 -44
  11. package/dist/cjs/Html.js.map +1 -1
  12. package/dist/cjs/HtmlChunk.js +67 -21
  13. package/dist/cjs/HtmlChunk.js.map +1 -1
  14. package/dist/cjs/Hydrate.js +6 -6
  15. package/dist/cjs/Hydrate.js.map +1 -1
  16. package/dist/cjs/Many.js +4 -4
  17. package/dist/cjs/Many.js.map +1 -1
  18. package/dist/cjs/Meta.js +10 -3
  19. package/dist/cjs/Meta.js.map +1 -1
  20. package/dist/cjs/Parser.js +1 -1
  21. package/dist/cjs/Placeholder.js +5 -9
  22. package/dist/cjs/Placeholder.js.map +1 -1
  23. package/dist/cjs/Platform.js +7 -5
  24. package/dist/cjs/Platform.js.map +1 -1
  25. package/dist/cjs/Render.js +8 -7
  26. package/dist/cjs/Render.js.map +1 -1
  27. package/dist/cjs/RenderContext.js +8 -92
  28. package/dist/cjs/RenderContext.js.map +1 -1
  29. package/dist/cjs/RenderEvent.js +9 -1
  30. package/dist/cjs/RenderEvent.js.map +1 -1
  31. package/dist/cjs/RenderQueue.js +341 -0
  32. package/dist/cjs/RenderQueue.js.map +1 -0
  33. package/dist/cjs/RenderTemplate.js +1 -1
  34. package/dist/cjs/RenderTemplate.js.map +1 -1
  35. package/dist/cjs/Template.js +12 -0
  36. package/dist/cjs/Template.js.map +1 -1
  37. package/dist/cjs/Test.js +64 -33
  38. package/dist/cjs/Test.js.map +1 -1
  39. package/dist/cjs/Vitest.js +12 -20
  40. package/dist/cjs/Vitest.js.map +1 -1
  41. package/dist/cjs/index.js +6 -3
  42. package/dist/cjs/index.js.map +1 -1
  43. package/dist/cjs/internal/EventSource.js +16 -9
  44. package/dist/cjs/internal/EventSource.js.map +1 -1
  45. package/dist/cjs/internal/HydrateContext.js.map +1 -1
  46. package/dist/cjs/internal/browser.js +11 -10
  47. package/dist/cjs/internal/browser.js.map +1 -1
  48. package/dist/cjs/internal/character-entities.js +2141 -0
  49. package/dist/cjs/internal/character-entities.js.map +1 -0
  50. package/dist/cjs/internal/errors.js +19 -2
  51. package/dist/cjs/internal/errors.js.map +1 -1
  52. package/dist/cjs/internal/indexRefCounter.js +36 -63
  53. package/dist/cjs/internal/indexRefCounter.js.map +1 -1
  54. package/dist/cjs/internal/parser.js +18 -17
  55. package/dist/cjs/internal/parser.js.map +1 -1
  56. package/dist/cjs/internal/parser2.js +382 -0
  57. package/dist/cjs/internal/parser2.js.map +1 -0
  58. package/dist/cjs/internal/server-parts.js +124 -0
  59. package/dist/cjs/internal/server-parts.js.map +1 -0
  60. package/dist/cjs/internal/server.js +15 -185
  61. package/dist/cjs/internal/server.js.map +1 -1
  62. package/dist/cjs/internal/utils.js +73 -9
  63. package/dist/cjs/internal/utils.js.map +1 -1
  64. package/dist/cjs/internal/v2/SyncPart.js +6 -0
  65. package/dist/cjs/internal/v2/SyncPart.js.map +1 -0
  66. package/dist/cjs/internal/v2/helpers.js +15 -0
  67. package/dist/cjs/internal/v2/helpers.js.map +1 -0
  68. package/dist/cjs/internal/v2/hydrate.js +202 -0
  69. package/dist/cjs/internal/v2/hydrate.js.map +1 -0
  70. package/dist/cjs/internal/v2/hydration-template.js +269 -0
  71. package/dist/cjs/internal/v2/hydration-template.js.map +1 -0
  72. package/dist/cjs/internal/v2/parts.js +169 -0
  73. package/dist/cjs/internal/v2/parts.js.map +1 -0
  74. package/dist/cjs/internal/v2/render-entry.js +110 -0
  75. package/dist/cjs/internal/v2/render-entry.js.map +1 -0
  76. package/dist/cjs/internal/v2/render-sync-parts.js +318 -0
  77. package/dist/cjs/internal/v2/render-sync-parts.js.map +1 -0
  78. package/dist/cjs/internal/v2/render.js +417 -0
  79. package/dist/cjs/internal/v2/render.js.map +1 -0
  80. package/dist/cjs/internal/v2/sync-parts.js +115 -0
  81. package/dist/cjs/internal/v2/sync-parts.js.map +1 -0
  82. package/dist/dts/ElementRef.d.ts +1 -1
  83. package/dist/dts/ElementRef.d.ts.map +1 -1
  84. package/dist/dts/ElementSource.d.ts +1 -1
  85. package/dist/dts/ElementSource.d.ts.map +1 -1
  86. package/dist/dts/EventHandler.d.ts +12 -8
  87. package/dist/dts/EventHandler.d.ts.map +1 -1
  88. package/dist/dts/Html.d.ts +6 -5
  89. package/dist/dts/Html.d.ts.map +1 -1
  90. package/dist/dts/HtmlChunk.d.ts.map +1 -1
  91. package/dist/dts/Hydrate.d.ts +1 -3
  92. package/dist/dts/Hydrate.d.ts.map +1 -1
  93. package/dist/dts/Many.d.ts +9 -11
  94. package/dist/dts/Many.d.ts.map +1 -1
  95. package/dist/dts/Meta.d.ts +5 -1
  96. package/dist/dts/Meta.d.ts.map +1 -1
  97. package/dist/dts/Parser.d.ts +1 -1
  98. package/dist/dts/Parser.d.ts.map +1 -1
  99. package/dist/dts/Part.d.ts +20 -56
  100. package/dist/dts/Part.d.ts.map +1 -1
  101. package/dist/dts/Placeholder.d.ts +6 -10
  102. package/dist/dts/Placeholder.d.ts.map +1 -1
  103. package/dist/dts/Platform.d.ts +2 -4
  104. package/dist/dts/Platform.d.ts.map +1 -1
  105. package/dist/dts/Render.d.ts +4 -8
  106. package/dist/dts/Render.d.ts.map +1 -1
  107. package/dist/dts/RenderContext.d.ts +3 -22
  108. package/dist/dts/RenderContext.d.ts.map +1 -1
  109. package/dist/dts/RenderEvent.d.ts +6 -1
  110. package/dist/dts/RenderEvent.d.ts.map +1 -1
  111. package/dist/dts/RenderQueue.d.ts +103 -0
  112. package/dist/dts/RenderQueue.d.ts.map +1 -0
  113. package/dist/dts/RenderTemplate.d.ts +3 -2
  114. package/dist/dts/RenderTemplate.d.ts.map +1 -1
  115. package/dist/dts/Renderable.d.ts +1 -1
  116. package/dist/dts/Template.d.ts +14 -1
  117. package/dist/dts/Template.d.ts.map +1 -1
  118. package/dist/dts/Test.d.ts +14 -1
  119. package/dist/dts/Test.d.ts.map +1 -1
  120. package/dist/dts/Vitest.d.ts +11 -8
  121. package/dist/dts/Vitest.d.ts.map +1 -1
  122. package/dist/dts/index.d.ts +4 -0
  123. package/dist/dts/index.d.ts.map +1 -1
  124. package/dist/dts/internal/EventSource.d.ts +2 -1
  125. package/dist/dts/internal/EventSource.d.ts.map +1 -1
  126. package/dist/dts/internal/browser.d.ts +3 -3
  127. package/dist/dts/internal/browser.d.ts.map +1 -1
  128. package/dist/dts/internal/character-entities.d.ts +2133 -0
  129. package/dist/dts/internal/character-entities.d.ts.map +1 -0
  130. package/dist/dts/internal/errors.d.ts +9 -1
  131. package/dist/dts/internal/errors.d.ts.map +1 -1
  132. package/dist/dts/internal/indexRefCounter.d.ts +0 -4
  133. package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
  134. package/dist/dts/internal/parser.d.ts +13 -0
  135. package/dist/dts/internal/parser.d.ts.map +1 -1
  136. package/dist/dts/internal/parser2.d.ts +12 -0
  137. package/dist/dts/internal/parser2.d.ts.map +1 -0
  138. package/dist/dts/internal/server-parts.d.ts +223 -0
  139. package/dist/dts/internal/server-parts.d.ts.map +1 -0
  140. package/dist/dts/internal/server.d.ts +2 -28
  141. package/dist/dts/internal/server.d.ts.map +1 -1
  142. package/dist/dts/internal/utils.d.ts +4 -1
  143. package/dist/dts/internal/utils.d.ts.map +1 -1
  144. package/dist/dts/internal/v2/SyncPart.d.ts +87 -0
  145. package/dist/dts/internal/v2/SyncPart.d.ts.map +1 -0
  146. package/dist/dts/internal/v2/helpers.d.ts +3 -0
  147. package/dist/dts/internal/v2/helpers.d.ts.map +1 -0
  148. package/dist/dts/internal/v2/hydrate.d.ts +7 -0
  149. package/dist/dts/internal/v2/hydrate.d.ts.map +1 -0
  150. package/dist/dts/internal/v2/hydration-template.d.ts +54 -0
  151. package/dist/dts/internal/v2/hydration-template.d.ts.map +1 -0
  152. package/dist/dts/internal/v2/parts.d.ts +245 -0
  153. package/dist/dts/internal/v2/parts.d.ts.map +1 -0
  154. package/dist/dts/internal/v2/render-entry.d.ts +6 -0
  155. package/dist/dts/internal/v2/render-entry.d.ts.map +1 -0
  156. package/dist/dts/internal/v2/render-sync-parts.d.ts +22 -0
  157. package/dist/dts/internal/v2/render-sync-parts.d.ts.map +1 -0
  158. package/dist/dts/internal/v2/render.d.ts +62 -0
  159. package/dist/dts/internal/v2/render.d.ts.map +1 -0
  160. package/dist/dts/internal/v2/sync-parts.d.ts +129 -0
  161. package/dist/dts/internal/v2/sync-parts.d.ts.map +1 -0
  162. package/dist/esm/ElementRef.js.map +1 -1
  163. package/dist/esm/EventHandler.js +14 -4
  164. package/dist/esm/EventHandler.js.map +1 -1
  165. package/dist/esm/Html.js +91 -50
  166. package/dist/esm/Html.js.map +1 -1
  167. package/dist/esm/HtmlChunk.js +75 -24
  168. package/dist/esm/HtmlChunk.js.map +1 -1
  169. package/dist/esm/Hydrate.js +5 -5
  170. package/dist/esm/Hydrate.js.map +1 -1
  171. package/dist/esm/Many.js +3 -3
  172. package/dist/esm/Many.js.map +1 -1
  173. package/dist/esm/Meta.js +7 -1
  174. package/dist/esm/Meta.js.map +1 -1
  175. package/dist/esm/Parser.js +1 -1
  176. package/dist/esm/Parser.js.map +1 -1
  177. package/dist/esm/Placeholder.js +4 -8
  178. package/dist/esm/Placeholder.js.map +1 -1
  179. package/dist/esm/Platform.js +3 -1
  180. package/dist/esm/Platform.js.map +1 -1
  181. package/dist/esm/Render.js +6 -5
  182. package/dist/esm/Render.js.map +1 -1
  183. package/dist/esm/RenderContext.js +5 -85
  184. package/dist/esm/RenderContext.js.map +1 -1
  185. package/dist/esm/RenderEvent.js +8 -1
  186. package/dist/esm/RenderEvent.js.map +1 -1
  187. package/dist/esm/RenderQueue.js +336 -0
  188. package/dist/esm/RenderQueue.js.map +1 -0
  189. package/dist/esm/RenderTemplate.js.map +1 -1
  190. package/dist/esm/Template.js +12 -0
  191. package/dist/esm/Template.js.map +1 -1
  192. package/dist/esm/Test.js +71 -30
  193. package/dist/esm/Test.js.map +1 -1
  194. package/dist/esm/Vitest.js +11 -8
  195. package/dist/esm/Vitest.js.map +1 -1
  196. package/dist/esm/index.js +4 -0
  197. package/dist/esm/index.js.map +1 -1
  198. package/dist/esm/internal/EventSource.js +12 -7
  199. package/dist/esm/internal/EventSource.js.map +1 -1
  200. package/dist/esm/internal/HydrateContext.js.map +1 -1
  201. package/dist/esm/internal/browser.js +10 -9
  202. package/dist/esm/internal/browser.js.map +1 -1
  203. package/dist/esm/internal/character-entities.js +2134 -0
  204. package/dist/esm/internal/character-entities.js.map +1 -0
  205. package/dist/esm/internal/errors.js +22 -2
  206. package/dist/esm/internal/errors.js.map +1 -1
  207. package/dist/esm/internal/indexRefCounter.js +36 -61
  208. package/dist/esm/internal/indexRefCounter.js.map +1 -1
  209. package/dist/esm/internal/parser.js +18 -18
  210. package/dist/esm/internal/parser.js.map +1 -1
  211. package/dist/esm/internal/parser2.js +393 -0
  212. package/dist/esm/internal/parser2.js.map +1 -0
  213. package/dist/esm/internal/server-parts.js +109 -0
  214. package/dist/esm/internal/server-parts.js.map +1 -0
  215. package/dist/esm/internal/server.js +12 -161
  216. package/dist/esm/internal/server.js.map +1 -1
  217. package/dist/esm/internal/utils.js +71 -7
  218. package/dist/esm/internal/utils.js.map +1 -1
  219. package/dist/esm/internal/v2/SyncPart.js +5 -0
  220. package/dist/esm/internal/v2/SyncPart.js.map +1 -0
  221. package/dist/esm/internal/v2/helpers.js +12 -0
  222. package/dist/esm/internal/v2/helpers.js.map +1 -0
  223. package/dist/esm/internal/v2/hydrate.js +195 -0
  224. package/dist/esm/internal/v2/hydrate.js.map +1 -0
  225. package/dist/esm/internal/v2/hydration-template.js +265 -0
  226. package/dist/esm/internal/v2/hydration-template.js.map +1 -0
  227. package/dist/esm/internal/v2/parts.js +150 -0
  228. package/dist/esm/internal/v2/parts.js.map +1 -0
  229. package/dist/esm/internal/v2/render-entry.js +102 -0
  230. package/dist/esm/internal/v2/render-entry.js.map +1 -0
  231. package/dist/esm/internal/v2/render-sync-parts.js +265 -0
  232. package/dist/esm/internal/v2/render-sync-parts.js.map +1 -0
  233. package/dist/esm/internal/v2/render.js +353 -0
  234. package/dist/esm/internal/v2/render.js.map +1 -0
  235. package/dist/esm/internal/v2/sync-parts.js +102 -0
  236. package/dist/esm/internal/v2/sync-parts.js.map +1 -0
  237. package/package.json +20 -13
  238. package/src/ElementRef.ts +1 -1
  239. package/src/ElementSource.ts +1 -1
  240. package/src/EventHandler.ts +29 -11
  241. package/src/Html.ts +199 -90
  242. package/src/HtmlChunk.ts +77 -30
  243. package/src/Hydrate.ts +20 -14
  244. package/src/Many.ts +17 -14
  245. package/src/Meta.ts +8 -1
  246. package/src/Parser.ts +1 -1
  247. package/src/Part.ts +22 -66
  248. package/src/Placeholder.ts +17 -15
  249. package/src/Platform.ts +5 -5
  250. package/src/Render.ts +23 -26
  251. package/src/RenderContext.ts +14 -142
  252. package/src/RenderEvent.ts +10 -1
  253. package/src/RenderQueue.ts +445 -0
  254. package/src/RenderTemplate.ts +7 -2
  255. package/src/Renderable.ts +1 -1
  256. package/src/Template.ts +15 -1
  257. package/src/Test.ts +122 -38
  258. package/src/Vitest.ts +20 -10
  259. package/src/index.ts +4 -0
  260. package/src/internal/EventSource.ts +14 -8
  261. package/src/internal/HydrateContext.ts +3 -4
  262. package/src/internal/browser.ts +26 -21
  263. package/src/internal/character-entities.ts +2136 -0
  264. package/src/internal/errors.ts +30 -3
  265. package/src/internal/indexRefCounter.ts +38 -70
  266. package/src/internal/parser.ts +19 -19
  267. package/src/internal/parser2.ts +468 -0
  268. package/src/internal/server-parts.ts +161 -0
  269. package/src/internal/server.ts +16 -272
  270. package/src/internal/utils.ts +83 -7
  271. package/src/internal/v2/SyncPart.ts +112 -0
  272. package/src/internal/v2/helpers.ts +13 -0
  273. package/src/internal/v2/hydrate.ts +289 -0
  274. package/src/internal/v2/hydration-template.ts +308 -0
  275. package/src/internal/v2/parts.ts +254 -0
  276. package/src/internal/v2/render-entry.ts +131 -0
  277. package/src/internal/v2/render-sync-parts.ts +440 -0
  278. package/src/internal/v2/render.ts +588 -0
  279. package/src/internal/v2/sync-parts.ts +133 -0
  280. package/dist/cjs/internal/hydrate.js +0 -274
  281. package/dist/cjs/internal/hydrate.js.map +0 -1
  282. package/dist/cjs/internal/parts.js +0 -451
  283. package/dist/cjs/internal/parts.js.map +0 -1
  284. package/dist/cjs/internal/render.js +0 -704
  285. package/dist/cjs/internal/render.js.map +0 -1
  286. package/dist/dts/internal/hydrate.d.ts +0 -33
  287. package/dist/dts/internal/hydrate.d.ts.map +0 -1
  288. package/dist/dts/internal/parts.d.ts +0 -314
  289. package/dist/dts/internal/parts.d.ts.map +0 -1
  290. package/dist/dts/internal/render.d.ts +0 -16
  291. package/dist/dts/internal/render.d.ts.map +0 -1
  292. package/dist/esm/internal/hydrate.js +0 -239
  293. package/dist/esm/internal/hydrate.js.map +0 -1
  294. package/dist/esm/internal/parts.js +0 -373
  295. package/dist/esm/internal/parts.js.map +0 -1
  296. package/dist/esm/internal/render.js +0 -689
  297. package/dist/esm/internal/render.js.map +0 -1
  298. package/src/internal/hydrate.ts +0 -366
  299. package/src/internal/parts.ts +0 -609
  300. package/src/internal/render.ts +0 -971
@@ -1,971 +0,0 @@
1
- import * as Fx from "@typed/fx/Fx"
2
- import * as Sink from "@typed/fx/Sink"
3
- import { TypeId } from "@typed/fx/TypeId"
4
- import type { Rendered } from "@typed/wire"
5
- import { persistent } from "@typed/wire"
6
- import { Effect, ExecutionStrategy, Exit, Runtime } from "effect"
7
- import type { Cause } from "effect/Cause"
8
- import type { Chunk } from "effect/Chunk"
9
- import * as Context from "effect/Context"
10
- import { hasProperty } from "effect/Predicate"
11
- import * as Scope from "effect/Scope"
12
- import { uncapitalize } from "effect/String"
13
- import type { Directive } from "../Directive.js"
14
- import { isDirective } from "../Directive.js"
15
- import * as ElementRef from "../ElementRef.js"
16
- import * as ElementSource from "../ElementSource.js"
17
- import type { BrowserEntry } from "../Entry.js"
18
- import * as EventHandler from "../EventHandler.js"
19
- import type { Part } from "../Part.js"
20
- import type { Placeholder } from "../Placeholder.js"
21
- import type { ToRendered } from "../Render.js"
22
- import type { Renderable } from "../Renderable.js"
23
- import type { RenderContext } from "../RenderContext.js"
24
- import type { RenderEvent } from "../RenderEvent.js"
25
- import { DomRenderEvent } from "../RenderEvent.js"
26
- import type { RenderTemplate } from "../RenderTemplate.js"
27
- import type * as Template from "../Template.js"
28
- import { makeRenderNodePart } from "./browser.js"
29
- import { type EventSource, makeEventSource } from "./EventSource.js"
30
- import { HydrateContext } from "./HydrateContext.js"
31
- import type { IndexRefCounter2 } from "./indexRefCounter.js"
32
- import { indexRefCounter2 } from "./indexRefCounter.js"
33
- import { parse } from "./parser.js"
34
- import {
35
- AttributePartImpl,
36
- BooleanPartImpl,
37
- ClassNamePartImpl,
38
- CommentPartImpl,
39
- DataPartImpl,
40
- PropertyPartImpl,
41
- RefPartImpl,
42
- TextPartImpl
43
- } from "./parts.js"
44
- import type { ParentChildNodes } from "./utils.js"
45
- import { findPath } from "./utils.js"
46
-
47
- // TODO: We need to re-think hydration for dynamic lists, probably just markers should be fine
48
-
49
- /**
50
- * @internal
51
- */
52
- export type RenderPartContext = {
53
- readonly context: Context.Context<Scope.Scope>
54
- readonly document: Document
55
- readonly eventSource: EventSource
56
- readonly refCounter: IndexRefCounter2
57
- readonly renderContext: RenderContext
58
- readonly values: ReadonlyArray<Renderable<any, any>>
59
- readonly onCause: (cause: Cause<any>) => Effect.Effect<void>
60
-
61
- readonly makeHydrateContext?: (index: number) => HydrateContext
62
-
63
- expected: number
64
- spreadIndex: number
65
- }
66
-
67
- type RenderPartMap = {
68
- readonly [K in Template.PartNode["_tag"] | Template.SparsePartNode["_tag"]]: (
69
- part: Extract<Template.PartNode | Template.SparsePartNode, { _tag: K }>,
70
- node: Node,
71
- ctx: RenderPartContext
72
- ) => null | Effect.Effect<void, any, any> | Array<Effect.Effect<void, any, any>>
73
- }
74
-
75
- const RenderPartMap: RenderPartMap = {
76
- "attr": (templatePart, node, ctx) => {
77
- const { document, refCounter, renderContext, values } = ctx
78
- const element = node as HTMLElement | SVGElement
79
- const attr = createAttribute(document, element, templatePart.name)
80
- const renderable = values[templatePart.index]
81
- let isSet = false
82
- const setValue = (value: string | null | undefined) => {
83
- if (isNullOrUndefined(value)) {
84
- element.removeAttribute(templatePart.name)
85
- isSet = false
86
- } else {
87
- attr.value = String(value)
88
- if (isSet === false) {
89
- element.setAttributeNode(attr)
90
- isSet = true
91
- }
92
- }
93
- }
94
-
95
- return matchSettablePart(
96
- renderable,
97
- setValue,
98
- () => AttributePartImpl.browser(templatePart.index, element, templatePart.name, renderContext),
99
- (f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
100
- () => ctx.expected++
101
- )
102
- },
103
- "boolean-part": (templatePart, node, ctx) => {
104
- const { refCounter, renderContext, values } = ctx
105
- const element = node as HTMLElement | SVGElement
106
- const name = templatePart.name
107
- const renderable = values[templatePart.index]
108
- const setValue = (value: boolean | null | undefined) => {
109
- element.toggleAttribute(name, isNullOrUndefined(value) ? false : Boolean(value))
110
- }
111
-
112
- return matchSettablePart(
113
- renderable,
114
- setValue,
115
- () => BooleanPartImpl.browser(templatePart.index, element, name, renderContext),
116
- (f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
117
- () => ctx.expected++
118
- )
119
- },
120
- "className-part": (templatePart, node, ctx) => {
121
- const { refCounter, renderContext, values } = ctx
122
- const element = node as HTMLElement | SVGElement
123
- const renderable = values[templatePart.index]
124
- let classNames: Set<string> = new Set()
125
- const setValue = (value: string | Array<string> | null | undefined) => {
126
- if (isNullOrUndefined(value)) {
127
- element.classList.remove(...classNames)
128
- classNames.clear()
129
- } else {
130
- const newClassNames = new Set(
131
- Array.isArray(value) ? value.flatMap((x) => splitClassNames(String(x))) : splitClassNames(String(value))
132
- )
133
- const { added, removed } = diffClassNames(classNames, newClassNames)
134
-
135
- element.classList.remove(...removed)
136
- element.classList.add(...added)
137
- classNames = newClassNames
138
- }
139
- }
140
-
141
- return matchSettablePart(
142
- renderable,
143
- setValue,
144
- () => ClassNamePartImpl.browser(templatePart.index, element, renderContext),
145
- (f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
146
- () => ctx.expected++
147
- )
148
- },
149
- "comment-part": (templatePart, node, ctx) => {
150
- const { refCounter, renderContext, values } = ctx
151
- const comment = node as Comment
152
- const renderable = values[templatePart.index]
153
- const setValue = (value: string | null | undefined) => {
154
- comment.nodeValue = isNullOrUndefined(value) ? "" : String(value)
155
- }
156
-
157
- return matchSettablePart(
158
- renderable,
159
- setValue,
160
- () => CommentPartImpl.browser(templatePart.index, comment, renderContext),
161
- (f) => Effect.zipRight(renderContext.queue.add(comment, f), refCounter.release(templatePart.index)),
162
- () => ctx.expected++
163
- )
164
- },
165
- "data": (templatePart, node, ctx) => {
166
- const element = node as HTMLElement | SVGElement
167
- const renderable = ctx.values[templatePart.index]
168
- const previousKeys = new Set<string>(Object.keys(element.dataset))
169
- const setValue = (value: Record<string, string | undefined> | null | undefined) => {
170
- if (isNullOrUndefined(value)) {
171
- for (const key of previousKeys) {
172
- delete element.dataset[key]
173
- }
174
- previousKeys.clear()
175
- } else {
176
- for (const key of previousKeys) {
177
- if (!(key in value)) {
178
- delete element.dataset[key]
179
- previousKeys.delete(key)
180
- }
181
- }
182
-
183
- for (const key of Object.keys(value)) {
184
- if (!previousKeys.has(key)) {
185
- previousKeys.add(key)
186
- }
187
- element.dataset[key] = value[key] || ""
188
- }
189
- }
190
- }
191
-
192
- return matchSettablePart(
193
- renderable,
194
- setValue,
195
- () => DataPartImpl.browser(templatePart.index, element, ctx.renderContext),
196
- (f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(templatePart.index)),
197
- () => ctx.expected++
198
- )
199
- },
200
- "event": (templatePart, node, ctx) => {
201
- const element = node as HTMLElement | SVGElement
202
- const renderable = ctx.values[templatePart.index]
203
- const handler = getEventHandler(renderable, ctx.context, ctx.onCause)
204
- if (handler) {
205
- ctx.eventSource.addEventListener(element, templatePart.name, handler)
206
- }
207
-
208
- return null
209
- },
210
- "node": (templatePart, node, ctx) => {
211
- const makeHydrateContext = ctx.makeHydrateContext
212
- const renderable = ctx.values[templatePart.index]
213
- const part = makeRenderNodePart(
214
- templatePart.index,
215
- node as HTMLElement | SVGElement,
216
- ctx.renderContext,
217
- ctx.document,
218
- !!makeHydrateContext
219
- )
220
-
221
- if (isDirective(renderable)) {
222
- const effect = Effect.zipRight(renderable(part), ctx.refCounter.release(templatePart.index))
223
- if (makeHydrateContext) {
224
- return Effect.provideService(effect, HydrateContext, makeHydrateContext(templatePart.index))
225
- } else {
226
- return effect
227
- }
228
- }
229
-
230
- ctx.expected++
231
-
232
- const handle = handlePart(
233
- renderable,
234
- Sink.make(ctx.onCause, (value) => Effect.zipRight(part.update(value), ctx.refCounter.release(templatePart.index)))
235
- )
236
-
237
- if (makeHydrateContext) {
238
- return Effect.provideService(handle, HydrateContext, makeHydrateContext(templatePart.index))
239
- } else {
240
- return handle
241
- }
242
- },
243
- "property": (templatePart, node, ctx) => {
244
- const element = node as HTMLElement | SVGElement
245
- const renderable = ctx.values[templatePart.index]
246
- const setValue = (value: unknown) => {
247
- if (isNullOrUndefined(value)) {
248
- delete (element as any)[templatePart.name]
249
- } else {
250
- ;(element as any)[templatePart.name] = value
251
- }
252
- }
253
-
254
- return matchSettablePart(
255
- renderable,
256
- setValue,
257
- () => PropertyPartImpl.browser(templatePart.index, element, templatePart.name, ctx.renderContext),
258
- (f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(templatePart.index)),
259
- () => ctx.expected++
260
- )
261
- },
262
- "properties": (templatePart, node, ctx) => {
263
- const renderable = ctx.values[templatePart.index] as any as Record<string, any>
264
- if (isNullOrUndefined(renderable)) return null
265
- else if (Fx.isFx(renderable) || Effect.isEffect(renderable)) {
266
- throw new Error(`Properties Part must utilize an Record of renderable values.`)
267
- } else if (typeof renderable === "object" && !Array.isArray(renderable)) {
268
- const element = node as HTMLElement | SVGElement
269
-
270
- const toggleBoolean = (key: string, value: unknown) => {
271
- element.toggleAttribute(key, isNullOrUndefined(value) ? false : Boolean(value))
272
- }
273
- const setAttribute = (key: string, value: unknown) => {
274
- if (isNullOrUndefined(value)) {
275
- element.removeAttribute(key)
276
- } else {
277
- element.setAttribute(key, String(value))
278
- }
279
- }
280
- const setProperty = (key: string, value: unknown) => {
281
- if (isNullOrUndefined(value)) {
282
- delete (element as any)[key]
283
- } else {
284
- ;(element as any)[key] = value
285
- }
286
- }
287
- const setClassNames = (previous: Set<string>, updated: Set<string>) => {
288
- const { added, removed } = diffClassNames(previous, updated)
289
-
290
- element.classList.remove(...removed)
291
- element.classList.add(...added)
292
- removed.forEach((r) => previous.delete(r))
293
- added.forEach((a) => previous.add(a))
294
- }
295
-
296
- const effects: Array<Effect.Effect<void, any, any>> = []
297
- const entries = Object.entries(renderable)
298
-
299
- loop:
300
- for (const [key, value] of entries) {
301
- const index = ++ctx.spreadIndex
302
- switch (key[0]) {
303
- case "?": {
304
- const name = key.slice(1)
305
- const eff = matchSettablePart(
306
- value,
307
- (value) => toggleBoolean(name, value),
308
- () => BooleanPartImpl.browser(index, element, name, ctx.renderContext),
309
- (f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
310
- () => ctx.expected++
311
- )
312
- if (eff !== null) {
313
- effects.push(eff)
314
- }
315
- continue loop
316
- }
317
- case ".": {
318
- const name = key.slice(1)
319
- const eff = matchSettablePart(
320
- value,
321
- (value) => setProperty(name, value),
322
- () => PropertyPartImpl.browser(index, element, name, ctx.renderContext),
323
- (f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
324
- () => ctx.expected++
325
- )
326
- if (eff !== null) {
327
- effects.push(eff)
328
- }
329
- continue loop
330
- }
331
- case "@": {
332
- const name = uncapitalize(key.slice(1))
333
- const handler = getEventHandler(value, ctx.context, ctx.onCause)
334
- if (handler) {
335
- ctx.eventSource.addEventListener(element, name, handler)
336
- }
337
- continue loop
338
- }
339
- case "o": {
340
- if (key[1] === "n") {
341
- const name = uncapitalize(key.slice(2))
342
- const handler = getEventHandler(value, ctx.context, ctx.onCause)
343
- if (handler) {
344
- ctx.eventSource.addEventListener(element, name, handler)
345
- }
346
- }
347
- continue loop
348
- }
349
- }
350
-
351
- const lowerCaseName = key.toLowerCase()
352
-
353
- const isClass = lowerCaseName === "class" || lowerCaseName === "classname"
354
-
355
- if (isClass) {
356
- const classNames: Set<string> = new Set()
357
- const eff = matchSettablePart(
358
- value,
359
- (value) => {
360
- if (isNullOrUndefined(value)) {
361
- element.classList.remove(...classNames)
362
- classNames.clear()
363
- } else {
364
- setClassNames(classNames, new Set(splitClassNames(String(value))))
365
- }
366
- },
367
- () => ClassNamePartImpl.browser(index, element, ctx.renderContext),
368
- (f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
369
- () => ctx.expected++
370
- )
371
- if (eff !== null) {
372
- effects.push(eff)
373
- }
374
- } else {
375
- const eff = matchSettablePart(
376
- value,
377
- (value) => setAttribute(key, value),
378
- () => AttributePartImpl.browser(index, element, key, ctx.renderContext),
379
- (f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
380
- () => ctx.expected++
381
- )
382
- if (eff !== null) {
383
- effects.push(eff)
384
- }
385
- }
386
- }
387
-
388
- return effects
389
- } else {
390
- return null
391
- }
392
- },
393
- "ref": (templatePart, node, ctx) => {
394
- const element = node as HTMLElement | SVGElement
395
- const renderable = ctx.values[templatePart.index]
396
-
397
- if (isDirective(renderable)) {
398
- return renderable(new RefPartImpl(ElementSource.fromElement(element), templatePart.index))
399
- } else if (ElementRef.isElementRef(renderable)) {
400
- return ElementRef.set(renderable, element)
401
- }
402
-
403
- return null
404
- },
405
- "sparse-attr": (templatePart, node, ctx) => {
406
- const values = Array.from({ length: templatePart.nodes.length }, (): string => "")
407
- const element = node as HTMLElement | SVGElement
408
- const attr = createAttribute(ctx.document, element, templatePart.name)
409
-
410
- const setValue = (value: string | null | undefined, index: number) => {
411
- values[index] = value ?? ""
412
- }
413
-
414
- const effects: Array<Effect.Effect<void, any, any>> = []
415
-
416
- for (let i = 0; i < templatePart.nodes.length; ++i) {
417
- const node = templatePart.nodes[i]
418
- if (node._tag === "text") {
419
- values[i] = node.value
420
- } else {
421
- const renderable = ctx.values[node.index]
422
- const index = i
423
- const effect = matchSettablePart(
424
- renderable,
425
- (value) => setValue(value, index),
426
- () =>
427
- new AttributePartImpl(
428
- templatePart.name,
429
- node.index,
430
- ({ value }) =>
431
- Effect.zipRight(
432
- ctx.renderContext.queue.add(element, () => setValue(value, index)),
433
- ctx.refCounter.release(node.index)
434
- ),
435
- attr.value
436
- ),
437
- (f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(node.index)),
438
- () => ctx.expected++
439
- )
440
-
441
- if (effect !== null) {
442
- effects.push(effect)
443
- }
444
- }
445
- }
446
-
447
- if (effects.length === 0) {
448
- attr.value = values.join("")
449
- element.setAttributeNode(attr)
450
- }
451
-
452
- return effects
453
- },
454
- "sparse-class-name": (templatePart, node, ctx) => {
455
- const element = node as HTMLElement | SVGElement
456
-
457
- const effects = templatePart.nodes.flatMap((node) => {
458
- if (node._tag === "text") {
459
- const split = splitClassNames(node.value)
460
- if (split.length > 0) element.classList.add(...split)
461
- return []
462
- } else {
463
- const eff = RenderPartMap[node._tag](node, element, ctx)
464
- if (eff === null) return []
465
- return Array.isArray(eff) ? eff : [eff]
466
- }
467
- })
468
-
469
- return effects
470
- },
471
- "sparse-comment": (templatePart, node, ctx) => {
472
- const values = Array.from({ length: templatePart.nodes.length }, (): string => "")
473
- const comment = node as Comment
474
-
475
- const setValue = (value: string | null | undefined, index: number) => {
476
- values[index] = value ?? ""
477
- }
478
- const flushValue = () => {
479
- comment.data = values.join("")
480
- }
481
-
482
- const effects: Array<Effect.Effect<void, any, any>> = []
483
-
484
- for (let i = 0; i < templatePart.nodes.length; ++i) {
485
- const node = templatePart.nodes[i]
486
- if (node._tag === "text") {
487
- values[i] = node.value
488
- } else {
489
- const renderable = ctx.values[node.index]
490
- const index = i
491
- const effect = matchSettablePart(
492
- renderable,
493
- (value) => {
494
- setValue(value, index)
495
- flushValue()
496
- },
497
- () =>
498
- new CommentPartImpl(
499
- node.index,
500
- ({ value }) => (setValue(value, index),
501
- Effect.zipRight(
502
- ctx.renderContext.queue.add(comment, () => flushValue()),
503
- ctx.refCounter.release(node.index)
504
- )),
505
- null
506
- ),
507
- (f) => Effect.zipRight(ctx.renderContext.queue.add(comment, f), ctx.refCounter.release(node.index)),
508
- () => ctx.expected++
509
- )
510
-
511
- if (effect !== null) {
512
- effects.push(effect)
513
- }
514
- }
515
- }
516
-
517
- if (effects.length === 0) {
518
- flushValue()
519
- }
520
-
521
- return effects
522
- },
523
- "text-part": (templatePart, node, ctx) => {
524
- const renderable = ctx.values[templatePart.index]
525
- const part = TextPartImpl.browser(
526
- ctx.document,
527
- templatePart.index,
528
- node as HTMLElement | SVGElement,
529
- ctx.renderContext
530
- )
531
-
532
- if (isDirective(renderable)) {
533
- return Effect.zipRight(renderable(part), ctx.refCounter.release(templatePart.index))
534
- }
535
-
536
- ctx.expected++
537
-
538
- return handlePart(
539
- renderable,
540
- Sink.make(
541
- ctx.onCause,
542
- (value) => Effect.zipRight(part.update(value as any), ctx.refCounter.release(templatePart.index))
543
- )
544
- )
545
- }
546
- }
547
-
548
- const SPACE_REGEXP = /\s+/g
549
-
550
- function splitClassNames(value: string) {
551
- return value.split(SPACE_REGEXP).flatMap((a) => {
552
- const trimmed = a.trim()
553
- return trimmed.length > 0 ? [trimmed] : []
554
- })
555
- }
556
-
557
- function isNullOrUndefined<T>(value: T | null | undefined): value is null | undefined {
558
- return value === null || value === undefined
559
- }
560
-
561
- function diffClassNames(oldClassNames: Set<string>, newClassNames: Set<string>) {
562
- const added: Array<string> = []
563
- const removed: Array<string> = []
564
-
565
- for (const className of oldClassNames) {
566
- if (!newClassNames.has(className)) {
567
- removed.push(className)
568
- }
569
- }
570
-
571
- for (const className of newClassNames) {
572
- if (!oldClassNames.has(className) && className.trim()) {
573
- added.push(className)
574
- }
575
- }
576
-
577
- return { added, removed }
578
- }
579
-
580
- /**
581
- * @internal
582
- */
583
- export function renderPart2(
584
- part: Template.PartNode | Template.SparsePartNode,
585
- content: ParentChildNodes,
586
- path: Chunk<number>,
587
- ctx: RenderPartContext
588
- ): Effect.Effect<void, any, any> | Array<Effect.Effect<void, any, any>> | null {
589
- return RenderPartMap[part._tag](part as any, findPath(content, path), ctx)
590
- }
591
-
592
- /**
593
- * Here for "standard" browser rendering, a TemplateInstance is effectively a live
594
- * view into the contents rendered by the Template.
595
- */
596
- export const renderTemplate: (document: Document, renderContext: RenderContext) => RenderTemplate =
597
- (document, renderContext) =>
598
- <Values extends ReadonlyArray<Renderable<any, any>>, T extends Rendered = Rendered>(
599
- templateStrings: TemplateStringsArray,
600
- values: Values
601
- ) => {
602
- const entry = getBrowserEntry(document, renderContext, templateStrings)
603
- if (values.length === 0) {
604
- return Fx.sync(() => DomRenderEvent(persistent(document.importNode(entry.content, true))))
605
- }
606
-
607
- return Fx.make<RenderEvent, Placeholder.Error<Values[number]>, Scope.Scope | Placeholder.Context<Values[number]>>((
608
- sink
609
- ) => {
610
- return Effect.gen(function*(_) {
611
- const runtime = yield* _(Effect.runtime<Scope.Scope | Placeholder.Context<Values[number]>>())
612
- const runFork = Runtime.runFork(runtime)
613
- const parentScope = Context.get(runtime.context, Scope.Scope)
614
- const scope = yield* _(Scope.fork(parentScope, ExecutionStrategy.sequential))
615
- const refCounter = yield* _(indexRefCounter2())
616
- const content = document.importNode(entry.content, true)
617
- const ctx: RenderPartContext = {
618
- context: runtime.context,
619
- document,
620
- eventSource: makeEventSource(),
621
- expected: 0,
622
- refCounter,
623
- renderContext,
624
- onCause: sink.onFailure as any,
625
- spreadIndex: values.length,
626
- values
627
- }
628
-
629
- // Connect our interpolated values to our template parts
630
- const effects: Array<Effect.Effect<void, never, Scope.Scope | Placeholder.Context<Values[number]>>> = []
631
- for (const [part, path] of entry.template.parts) {
632
- const eff = renderPart2(part, content, path, ctx)
633
- if (eff !== null) {
634
- effects.push(
635
- ...(Array.isArray(eff) ? eff : [eff]) as Array<
636
- Effect.Effect<void, never, Scope.Scope | Placeholder.Context<Values[number]>>
637
- >
638
- )
639
- }
640
- }
641
-
642
- // Fork any effects necessary
643
- if (effects.length > 0) {
644
- for (let i = 0; i < effects.length; ++i) {
645
- runFork(effects[i], { scope })
646
- }
647
- }
648
-
649
- // If there's anything to wait on and it's not already done, wait for an initial value
650
- // for all asynchronous sources.
651
- if (ctx.expected > 0 && (yield* _(refCounter.expect(ctx.expected)))) {
652
- yield* _(refCounter.wait)
653
- }
654
-
655
- // Create a persistent wire from our content
656
- const wire = persistent(content) as T
657
-
658
- // Setup our event listeners for our wire.
659
- // We use the parentScope to allow event listeners to exist
660
- // beyond the lifetime of the current Fiber, but no further than its parent template.
661
- yield* _(ctx.eventSource.setup(wire, parentScope))
662
-
663
- // Emit our DomRenderEvent
664
- yield* _(
665
- sink.onSuccess(DomRenderEvent(wire)),
666
- // Ensure our templates last forever in the DOM environment
667
- // so event listeners are kept attached to the current Scope.
668
- Effect.zipRight(Effect.never),
669
- // Close our scope whenever the current Fiber is interrupted
670
- Effect.ensuring(Scope.close(scope, Exit.unit))
671
- )
672
- })
673
- })
674
- }
675
-
676
- function getEventHandler<E, R>(
677
- renderable: any,
678
- ctx: Context.Context<any> | Context.Context<never>,
679
- onCause: (cause: Cause<E>) => Effect.Effect<unknown>
680
- ): EventHandler.EventHandler<never, never> | null {
681
- if (renderable && typeof renderable === "object") {
682
- if (EventHandler.EventHandlerTypeId in renderable) {
683
- return EventHandler.make(
684
- (ev) =>
685
- Effect.provide(
686
- Effect.catchAllCause((renderable as EventHandler.EventHandler<Event, E, R>).handler(ev), onCause),
687
- ctx as any
688
- ),
689
- (renderable as EventHandler.EventHandler<Event, E, R>).options
690
- )
691
- } else if (Effect.EffectTypeId in renderable) {
692
- return EventHandler.make(() => Effect.provide(Effect.catchAllCause(renderable, onCause), ctx))
693
- }
694
- }
695
-
696
- return null
697
- }
698
-
699
- function handlePart<R, E, R2>(
700
- renderable: unknown,
701
- sink: Sink.Sink<any, any, R2>
702
- ): Effect.Effect<any, never, R | R2 | Scope.Scope> {
703
- switch (typeof renderable) {
704
- case "undefined":
705
- case "object": {
706
- if (renderable === null || renderable === undefined) return sink.onSuccess(null)
707
- else if (Array.isArray(renderable)) {
708
- return renderable.length === 0
709
- ? sink.onSuccess(null)
710
- : Fx.tuple(renderable.map(unwrapRenderable)).run(sink) as any
711
- } else if (TypeId in renderable) {
712
- return (renderable as Fx.Fx<any, any, R | R2>).run(sink)
713
- } else if (Effect.EffectTypeId in renderable) {
714
- return Effect.matchCauseEffect(renderable as Effect.Effect<any, E, R>, sink)
715
- } else return sink.onSuccess(renderable)
716
- }
717
- default:
718
- return sink.onSuccess(renderable)
719
- }
720
- }
721
-
722
- function unwrapRenderable<E, R>(renderable: unknown): Fx.Fx<any, E, R> {
723
- switch (typeof renderable) {
724
- case "undefined":
725
- case "object": {
726
- if (renderable === null || renderable === undefined) return Fx.succeed(null)
727
- else if (Array.isArray(renderable)) {
728
- return renderable.length === 0
729
- ? Fx.succeed(null)
730
- : Fx.map(Fx.tuple(renderable.map(unwrapRenderable)), (xs) => xs.flat()) as any
731
- } else if (TypeId in renderable) {
732
- return renderable as any
733
- } else if (Effect.EffectTypeId in renderable) {
734
- return Fx.fromFxEffect(Effect.map(renderable as any, unwrapRenderable<E, R>))
735
- } else return Fx.succeed(renderable as any)
736
- }
737
- default:
738
- return Fx.succeed(renderable)
739
- }
740
- }
741
-
742
- export function attachRoot<T extends RenderEvent | null>(
743
- cache: RenderContext["renderCache"],
744
- where: HTMLElement,
745
- what: RenderEvent | null // TODO: Should we support HTML RenderEvents here too?
746
- ): Effect.Effect<ToRendered<T>> {
747
- return Effect.sync(() => {
748
- const wire = what?.valueOf() as ToRendered<T>
749
- const previous = cache.get(where)
750
-
751
- if (wire !== previous) {
752
- if (previous && !wire) removeChildren(where, previous)
753
-
754
- cache.set(where, wire || null)
755
-
756
- if (wire) replaceChildren(where, wire)
757
-
758
- return wire as ToRendered<T>
759
- }
760
-
761
- return previous as ToRendered<T>
762
- })
763
- }
764
-
765
- function removeChildren(where: HTMLElement, previous: Rendered) {
766
- for (const node of getNodes(previous)) {
767
- where.removeChild(node)
768
- }
769
- }
770
-
771
- function replaceChildren(where: HTMLElement, wire: Rendered) {
772
- where.replaceChildren(...getNodes(wire))
773
- }
774
-
775
- function getNodes(rendered: Rendered): Array<globalThis.Node> {
776
- const value = rendered.valueOf() as globalThis.Node | Array<globalThis.Node>
777
- return Array.isArray(value) ? value : [value]
778
- }
779
-
780
- export function getBrowserEntry(
781
- document: Document,
782
- ctx: RenderContext,
783
- templateStrings: TemplateStringsArray
784
- ): BrowserEntry {
785
- const cached = ctx.templateCache.get(templateStrings)
786
-
787
- if (cached === undefined || cached._tag === "Server") {
788
- const template = parse(templateStrings)
789
- const content = buildTemplate(document, template)
790
- const entry: BrowserEntry = {
791
- _tag: "Browser",
792
- template,
793
- content
794
- }
795
-
796
- ctx.templateCache.set(templateStrings, entry)
797
-
798
- return entry
799
- } else {
800
- return cached
801
- }
802
- }
803
-
804
- export function buildTemplate(document: Document, { nodes }: Template.Template): DocumentFragment {
805
- const fragment = document.createDocumentFragment()
806
-
807
- for (let i = 0; i < nodes.length; ++i) {
808
- fragment.append(buildNode(document, nodes[i], false))
809
- }
810
-
811
- return fragment
812
- }
813
-
814
- function buildNode(document: Document, node: Template.Node, isSvgContext: boolean): globalThis.Node {
815
- switch (node._tag) {
816
- case "element":
817
- case "self-closing-element":
818
- case "text-only-element":
819
- return buildElement(document, node, isSvgContext)
820
- case "text":
821
- return document.createTextNode(node.value)
822
- case "comment":
823
- return document.createComment(node.value)
824
- case "sparse-comment":
825
- return document.createComment(`hole${node.nodes.map((n) => n._tag === "text" ? "" : n.index).join("")}`)
826
- // Create placeholders for these elements
827
- case "comment-part":
828
- case "node":
829
- return document.createComment(`hole${node.index}`)
830
- case "doctype":
831
- return document.implementation.createDocumentType(
832
- node.name,
833
- docTypeNameToPublicId(node.name),
834
- docTypeNameToSystemId(node.name)
835
- )
836
- }
837
- }
838
-
839
- function docTypeNameToPublicId(name: string): string {
840
- switch (name) {
841
- case "html":
842
- return "-//W3C//DTD HTML 4.01//EN"
843
- case "svg":
844
- return "-//W3C//DTD SVG 1.1//EN"
845
- case "math":
846
- return "-//W3C//DTD MathML 2.0//EN"
847
- default:
848
- return ""
849
- }
850
- }
851
-
852
- function docTypeNameToSystemId(name: string): string {
853
- switch (name) {
854
- // HTML5
855
- case "html":
856
- return "http://www.w3.org/TR/html4/strict.dtd"
857
- case "svg":
858
- return "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
859
- case "math":
860
- return "http://www.w3.org/Math/DTD/mathml2/mathml2.dtd"
861
- default:
862
- return ""
863
- }
864
- }
865
-
866
- const SVG_NAMESPACE = "http://www.w3.org/2000/svg"
867
-
868
- function buildElement(
869
- document: Document,
870
- node: Template.ElementNode | Template.SelfClosingElementNode | Template.TextOnlyElement,
871
- isSvgContext: boolean
872
- ): Element {
873
- const { _tag, attributes, tagName } = node
874
- const isSvg = isSvgContext ? tagName !== "foreignObject" : tagName === "svg"
875
- const element = isSvg
876
- ? document.createElementNS(SVG_NAMESPACE, tagName)
877
- : document.createElement(tagName)
878
-
879
- for (let i = 0; i < attributes.length; ++i) {
880
- const attr = attributes[i]
881
-
882
- // We only handle static attributes here, parts are handled elsewhere
883
- if (attr._tag === "attribute") {
884
- element.setAttribute(attr.name, attr.value)
885
- } else if (attr._tag === "boolean") {
886
- element.toggleAttribute(attr.name, true)
887
- }
888
- }
889
-
890
- if (_tag === "element") {
891
- element.append(...node.children.map((child) => buildNode(document, child, isSvg)))
892
- } else if (_tag === "text-only-element") {
893
- element.append(...node.children.map((child) => buildTextChild(document, child)))
894
- }
895
-
896
- return element
897
- }
898
-
899
- function buildTextChild(document: Document, node: Template.Text): globalThis.Node {
900
- if (node._tag === "text") {
901
- return document.createTextNode(node.value)
902
- }
903
-
904
- return document.createComment(`hole${node.index}`)
905
- }
906
-
907
- function createAttribute(
908
- document: Document,
909
- element: HTMLElement | SVGElement,
910
- name: string
911
- ): Attr {
912
- return element.getAttributeNode(name) ?? document.createAttribute(name)
913
- }
914
-
915
- function matchSettablePart(
916
- renderable: Renderable<any, any>,
917
- setValue: (value: any) => void,
918
- makePart: () => Part,
919
- schedule: (f: () => void) => Effect.Effect<void, never, Scope.Scope>,
920
- expect: () => void
921
- ) {
922
- return matchRenderable(renderable, {
923
- Fx: (fx) => {
924
- expect()
925
- return Fx.observe(fx, (a) => schedule(() => setValue(a)))
926
- },
927
- Effect: (effect) => {
928
- expect()
929
- return Effect.flatMap(effect, (a) => schedule(() => setValue(a)))
930
- },
931
- Directive: (directive) => {
932
- expect()
933
- const part = makePart()
934
- return runDirective(directive, part, setValue, schedule)
935
- },
936
- Otherwise: (otherwise) => {
937
- setValue(otherwise)
938
- return null
939
- }
940
- })
941
- }
942
-
943
- function matchRenderable(renderable: Renderable<any, any>, matches: {
944
- Fx: (fx: Fx.Fx<any, any, any>) => Effect.Effect<void, any, any> | null
945
- Effect: (effect: Effect.Effect<any, any, any>) => Effect.Effect<void, any, any> | null
946
- Directive: (directive: Directive<any, any>) => Effect.Effect<void, any, any> | null
947
- Otherwise: (_: Renderable<any, any>) => Effect.Effect<void, any, any> | null
948
- }): Effect.Effect<void, any, any> | null {
949
- if (Fx.isFx(renderable)) {
950
- return matches.Fx(renderable)
951
- } else if (Effect.isEffect(renderable)) {
952
- return matches.Effect(renderable)
953
- } else if (isDirective<any, any>(renderable)) {
954
- return matches.Directive(renderable)
955
- } else {
956
- return matches.Otherwise(renderable)
957
- }
958
- }
959
-
960
- function runDirective(
961
- directive: Directive<any, any>,
962
- part: Part,
963
- setValue: (value: any) => void,
964
- schedule: (f: () => void) => Effect.Effect<void, never, Scope.Scope>
965
- ): Effect.Effect<void, any, any> {
966
- if (hasProperty(part, "update")) {
967
- return directive({ ...part, update: (value: any) => schedule(() => setValue(value)) })
968
- } else {
969
- return Effect.flatMap(directive(part), () => schedule(() => setValue(part.value)))
970
- }
971
- }