@typed/template 0.9.6 → 0.11.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 +15 -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 +20 -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 +20 -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 +50 -12
  241. package/src/Html.ts +207 -98
  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
@@ -0,0 +1,588 @@
1
+ import * as Context from "@typed/context"
2
+ import * as Fx from "@typed/fx"
3
+ import type { Rendered } from "@typed/wire"
4
+ import { persistent } from "@typed/wire"
5
+ import type * as Cause from "effect/Cause"
6
+ import type { Chunk } from "effect/Chunk"
7
+ import * as Effect from "effect/Effect"
8
+ import * as ExecutionStrategy from "effect/ExecutionStrategy"
9
+ import { flow } from "effect/Function"
10
+ import * as Scope from "effect/Scope"
11
+ import { type Directive, isDirective } from "../../Directive.js"
12
+ import * as ElementRef from "../../ElementRef.js"
13
+ import * as ElementSource from "../../ElementSource.js"
14
+ import * as EventHandler from "../../EventHandler.js"
15
+ import type { Placeholder } from "../../Placeholder.js"
16
+ import type { ToRendered } from "../../Render.js"
17
+ import type { Renderable } from "../../Renderable.js"
18
+ import type { RenderContext } from "../../RenderContext.js"
19
+ import { DomRenderEvent, type RenderEvent } from "../../RenderEvent.js"
20
+ import { DEFAULT_PRIORITY, RenderQueue } from "../../RenderQueue.js"
21
+ import type { RenderTemplate } from "../../RenderTemplate.js"
22
+ import type * as Template from "../../Template.js"
23
+ import type { EventSource } from "../EventSource.js"
24
+ import { makeEventSource } from "../EventSource.js"
25
+ import type { IndexRefCounter } from "../indexRefCounter.js"
26
+ import { makeRefCounter } from "../indexRefCounter.js"
27
+ import { findHoleComment, findPath, keyToPartType } from "../utils.js"
28
+ import { isNullOrUndefined } from "./helpers.js"
29
+ import { EventPartImpl, RefPartImpl, syncPartToPart } from "./parts.js"
30
+ import { getBrowserEntry } from "./render-entry.js"
31
+ import * as SyncPartsInternal from "./render-sync-parts.js"
32
+ import type { SyncPart } from "./SyncPart.js"
33
+
34
+ export type TemplateContext = {
35
+ /**
36
+ * @internal
37
+ */
38
+ expected: number
39
+ /**
40
+ * @internal
41
+ */
42
+ spreadIndex: number
43
+
44
+ readonly content: DocumentFragment
45
+ readonly context: Context.Context<Scope.Scope>
46
+ readonly document: Document
47
+ readonly eventSource: EventSource
48
+ readonly parentScope: Scope.Scope
49
+ readonly queue: RenderQueue
50
+ readonly refCounter: IndexRefCounter
51
+ readonly renderContext: RenderContext
52
+ readonly scope: Scope.CloseableScope
53
+ readonly values: ReadonlyArray<Renderable<any, any>>
54
+ readonly onCause: (cause: Cause.Cause<any>) => Effect.Effect<unknown>
55
+ }
56
+
57
+ export const renderTemplate: (
58
+ document: Document,
59
+ renderContext: RenderContext
60
+ ) => RenderTemplate = (document, renderContext) =>
61
+ <Values extends ReadonlyArray<Renderable<any, any>>>(
62
+ templateStrings: TemplateStringsArray,
63
+ values: Values
64
+ ) => {
65
+ const entry = getBrowserEntry(document, renderContext, templateStrings)
66
+ if (entry.template.parts.length === 0) {
67
+ return Fx.succeed(DomRenderEvent(persistent(document, document.importNode(entry.content, true))))
68
+ }
69
+
70
+ return Fx.make<
71
+ RenderEvent,
72
+ Placeholder.Error<Values[number]>,
73
+ Scope.Scope | RenderQueue | Placeholder.Context<Values[number]>
74
+ >((sink) =>
75
+ Effect.catchAllCause(
76
+ Effect.gen(function*() {
77
+ // Create a context for rendering our template
78
+ const ctx = yield* makeTemplateContext<Values>(
79
+ entry.content,
80
+ document,
81
+ renderContext,
82
+ values,
83
+ sink.onFailure
84
+ )
85
+
86
+ // Setup all parts
87
+ const effects = setupParts(entry.template.parts, ctx)
88
+ if (effects.length > 0) {
89
+ yield* Effect.forEach(effects, flow(Effect.catchAllCause(ctx.onCause), Effect.forkIn(ctx.scope)))
90
+ }
91
+
92
+ // If there's anything to wait on and it's not already done, wait for an initial value
93
+ // for all asynchronous sources.
94
+ if (ctx.expected > 0 && (yield* ctx.refCounter.expect(ctx.expected))) {
95
+ yield* ctx.refCounter.wait
96
+ }
97
+
98
+ // Create a persistent wire from our content
99
+ const wire = persistent(document, ctx.content)
100
+
101
+ // Setup our event listeners for our wire.
102
+ // We use the parentScope to allow event listeners to exist
103
+ // beyond the lifetime of the current Fiber, but no further than its parent template.
104
+ yield* ctx.eventSource.setup(wire, ctx.parentScope)
105
+
106
+ // Emit our DomRenderEvent
107
+ yield* sink.onSuccess(DomRenderEvent(wire)).pipe(
108
+ // Ensure our templates last forever in the DOM environment
109
+ // so event listeners are kept attached to the current Scope.
110
+ Effect.zipRight(Effect.never),
111
+ // Close our scope whenever the current Fiber is interrupted
112
+ Effect.onExit((exit) => Scope.close(ctx.scope, exit))
113
+ )
114
+ }),
115
+ sink.onFailure
116
+ )
117
+ )
118
+ }
119
+
120
+ export function makeTemplateContext<Values extends ReadonlyArray<Renderable<any, any>>>(
121
+ entry: DocumentFragment,
122
+ document: Document,
123
+ renderContext: RenderContext,
124
+ values: ReadonlyArray<Renderable<any, any>>,
125
+ onCause: (cause: Cause.Cause<Placeholder.Error<Values[number]>>) => Effect.Effect<unknown>
126
+ ): Effect.Effect<TemplateContext, never, Scope.Scope | RenderQueue | Placeholder.Context<Values[number]>> {
127
+ return Effect.gen(function*() {
128
+ const refCounter = yield* makeRefCounter
129
+ const context = yield* Effect.context<Placeholder.Context<Values[number]> | Scope.Scope | RenderQueue>()
130
+ const queue = Context.get(context, RenderQueue)
131
+ const parentScope = Context.get(context, Scope.Scope)
132
+ const eventSource = makeEventSource()
133
+ const content = document.importNode(entry, true)
134
+ const scope = yield* Scope.fork(parentScope, ExecutionStrategy.sequential)
135
+ const templateContext: TemplateContext = {
136
+ context: Context.add(context, Scope.Scope, scope),
137
+ expected: 0,
138
+ content,
139
+ document,
140
+ eventSource,
141
+ parentScope,
142
+ queue,
143
+ refCounter,
144
+ renderContext,
145
+ scope,
146
+ values,
147
+ spreadIndex: values.length,
148
+ onCause
149
+ }
150
+
151
+ return templateContext
152
+ })
153
+ }
154
+
155
+ function setupParts(parts: Template.Template["parts"], ctx: TemplateContext) {
156
+ const effects: Array<Effect.Effect<void, any, any>> = []
157
+
158
+ for (const [part, path] of parts) {
159
+ const effect = setupPart(part, path, ctx)
160
+ if (effect) {
161
+ effects.push(effect)
162
+ }
163
+ }
164
+
165
+ return effects
166
+ }
167
+
168
+ function setupPart(
169
+ part: Template.PartNode | Template.SparsePartNode,
170
+ path: Chunk<number>,
171
+ ctx: TemplateContext
172
+ ) {
173
+ switch (part._tag) {
174
+ case "attr":
175
+ return setupAttrPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
176
+ case "boolean-part":
177
+ return setupBooleanPart(
178
+ part,
179
+ findPath(ctx.content, path) as HTMLElement | SVGElement,
180
+ ctx,
181
+ ctx.values[part.index]
182
+ )
183
+ case "className-part":
184
+ return setupClassNamePart(
185
+ part,
186
+ findPath(ctx.content, path) as HTMLElement | SVGElement,
187
+ ctx,
188
+ ctx.values[part.index]
189
+ )
190
+ case "comment-part":
191
+ return setupCommentPart(part, findPath(ctx.content, path) as Comment, ctx)
192
+ case "data":
193
+ return setupDataPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
194
+ case "event":
195
+ return setupEventPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
196
+ case "node": {
197
+ const parent = findPath(ctx.content, path) as Element
198
+ const comment = findHoleComment(parent, part.index)
199
+ return setupNodePart(part, comment, ctx, null, [])
200
+ }
201
+ case "properties":
202
+ return setupPropertiesPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx)
203
+ case "property":
204
+ return setupPropertyPart(
205
+ part,
206
+ findPath(ctx.content, path) as HTMLElement | SVGElement,
207
+ ctx,
208
+ ctx.values[part.index]
209
+ )
210
+ case "ref":
211
+ return setupRefPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx)
212
+ case "sparse-attr":
213
+ return setupSparseAttrPart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx)
214
+ case "sparse-class-name":
215
+ return setupSparseClassNamePart(part, findPath(ctx.content, path) as HTMLElement | SVGElement, ctx)
216
+ case "sparse-comment":
217
+ return setupSparseCommentPart(part, findPath(ctx.content, path) as Comment, ctx)
218
+ case "text-part": {
219
+ const parent = findPath(ctx.content, path) as Element
220
+ const comment = findHoleComment(parent, part.index)
221
+ return setupTextPart(part, comment, ctx)
222
+ }
223
+ }
224
+ }
225
+
226
+ export function setupAttrPart(
227
+ { index, name }: Pick<Template.AttrPartNode, "index" | "name">,
228
+ element: HTMLElement | SVGElement,
229
+ ctx: TemplateContext,
230
+ renderable: Renderable<any, any>
231
+ ) {
232
+ const attr = element.getAttributeNode(name) ?? ctx.document.createAttribute(name)
233
+ const part = SyncPartsInternal.makeAttributePart(index, element, attr)
234
+ return matchSyncPart(renderable, ctx, part)
235
+ }
236
+
237
+ export function setupBooleanPart(
238
+ { index, name }: Pick<Template.BooleanPartNode, "index" | "name">,
239
+ element: HTMLElement | SVGElement,
240
+ ctx: TemplateContext,
241
+ renderable: Renderable<any, any>
242
+ ) {
243
+ const part = SyncPartsInternal.makeBooleanAttributePart(name, index, element)
244
+ return matchSyncPart(renderable, ctx, part)
245
+ }
246
+
247
+ export function setupClassNamePart(
248
+ { index }: Pick<Template.ClassNamePartNode, "index">,
249
+ element: HTMLElement | SVGElement,
250
+ ctx: TemplateContext,
251
+ renderable: Renderable<any, any>
252
+ ) {
253
+ const part = SyncPartsInternal.makeClassNamePart(index, element)
254
+ return matchSyncPart(renderable, ctx, part)
255
+ }
256
+
257
+ export function setupCommentPart(
258
+ { index }: Pick<Template.CommentPartNode, "index">,
259
+ comment: Comment,
260
+ ctx: TemplateContext
261
+ ) {
262
+ const part = SyncPartsInternal.makeCommentPart(index, comment)
263
+ const renderable = ctx.values[index]
264
+ return matchSyncPart(renderable, ctx, part)
265
+ }
266
+
267
+ export function setupDataPart(
268
+ { index }: Pick<Template.DataPartNode, "index">,
269
+ element: HTMLElement | SVGElement,
270
+ ctx: TemplateContext,
271
+ renderable: Renderable<any, any>
272
+ ) {
273
+ const part = SyncPartsInternal.makeDataPart(index, element)
274
+ return matchSyncPart(renderable, ctx, part)
275
+ }
276
+
277
+ export function setupEventPart(
278
+ { index, name }: Pick<Template.EventPartNode, "index" | "name">,
279
+ element: HTMLElement | SVGElement,
280
+ ctx: TemplateContext,
281
+ renderable: Renderable<any, any>
282
+ ) {
283
+ if (isNullOrUndefined(renderable)) return null
284
+
285
+ if (isDirective(renderable)) {
286
+ return renderable(
287
+ new EventPartImpl(
288
+ name,
289
+ index,
290
+ ElementSource.fromElement(element),
291
+ ctx.onCause,
292
+ (handler) => ctx.eventSource.addEventListener(element, name, handler)
293
+ )
294
+ )
295
+ } else {
296
+ const handler = getEventHandler(renderable, ctx.context, ctx.onCause)
297
+ if (handler === null) return null
298
+ ctx.eventSource.addEventListener(element, name, handler)
299
+ return null
300
+ }
301
+ }
302
+
303
+ export function getEventHandler<E, R>(
304
+ renderable: any,
305
+ ctx: Context.Context<any> | Context.Context<never>,
306
+ onCause: (cause: Cause.Cause<E>) => Effect.Effect<unknown>
307
+ ): EventHandler.EventHandler<never, never> | null {
308
+ if (renderable && typeof renderable === "object") {
309
+ if (EventHandler.EventHandlerTypeId in renderable) {
310
+ return EventHandler.make(
311
+ (ev) =>
312
+ Effect.provide(
313
+ Effect.catchAllCause((renderable as EventHandler.EventHandler<Event, E, R>).handler(ev), onCause),
314
+ ctx as any
315
+ ),
316
+ (renderable as EventHandler.EventHandler<Event, E, R>).options
317
+ )
318
+ } else if (Effect.EffectTypeId in renderable) {
319
+ return EventHandler.make(() => Effect.provide(Effect.catchAllCause(renderable, onCause), ctx))
320
+ }
321
+ }
322
+
323
+ return null
324
+ }
325
+
326
+ export function setupNodePart(
327
+ { index }: Template.NodePart,
328
+ comment: Comment,
329
+ ctx: TemplateContext,
330
+ text: Text | null,
331
+ nodes: Array<Node>
332
+ ) {
333
+ const part = SyncPartsInternal.makeNodePart(index, comment, ctx.document, text, nodes)
334
+ const renderable = ctx.values[index]
335
+ return matchSyncPart(renderable, ctx, part)
336
+ }
337
+
338
+ export function setupPropertyPart(
339
+ { index, name }: Pick<Template.PropertyPartNode, "index" | "name">,
340
+ element: HTMLElement | SVGElement,
341
+ ctx: TemplateContext,
342
+ renderable: Renderable<any, any>
343
+ ) {
344
+ const part = SyncPartsInternal.makePropertyPart(name, index, element)
345
+ return matchSyncPart(renderable, ctx, part)
346
+ }
347
+
348
+ export function setupRefPart(
349
+ { index }: Pick<Template.RefPartNode, "index">,
350
+ element: HTMLElement | SVGElement,
351
+ ctx: TemplateContext
352
+ ) {
353
+ const renderable = ctx.values[index]
354
+
355
+ if (isNullOrUndefined(renderable)) return null
356
+ else if (isDirective(renderable)) {
357
+ return renderable(
358
+ new RefPartImpl(ElementSource.fromElement(element), index)
359
+ )
360
+ } else if (ElementRef.isElementRef(renderable)) {
361
+ // TODO: We need to enable only setting these values once the Template has been rendered into the DOM
362
+ return ElementRef.set(renderable, element)
363
+ } else {
364
+ return null
365
+ }
366
+ }
367
+
368
+ export function setupPropertiesPart(
369
+ { index }: Pick<Template.PropertiesPartNode, "index">,
370
+ element: HTMLElement | SVGElement,
371
+ ctx: TemplateContext
372
+ ) {
373
+ const renderable = ctx.values[index]
374
+ if (renderable && typeof renderable === "object") {
375
+ const effects: Array<Effect.Effect<void, any, any>> = []
376
+ const addEffect = (effect: Effect.Effect<void, any, any> | null | undefined) => {
377
+ if (isNullOrUndefined(effect)) return
378
+ effects.push(effect)
379
+ }
380
+
381
+ for (const [key, value] of Object.entries(renderable as Record<string, any>)) {
382
+ const [type, name] = keyToPartType(key)
383
+ const index = ++ctx.spreadIndex
384
+ switch (type) {
385
+ case "attr":
386
+ addEffect(setupAttrPart({ index, name }, element, ctx, value))
387
+ break
388
+ case "boolean":
389
+ addEffect(setupBooleanPart({ index, name }, element, ctx, value))
390
+ break
391
+ case "class":
392
+ addEffect(setupClassNamePart({ index }, element, ctx, value))
393
+ break
394
+ case "data":
395
+ addEffect(setupDataPart({ index }, element, ctx, value))
396
+ break
397
+ case "event":
398
+ addEffect(setupEventPart({ index, name }, element, ctx, value))
399
+ break
400
+ case "property":
401
+ addEffect(setupPropertyPart({ index, name }, element, ctx, value))
402
+ break
403
+ }
404
+ }
405
+
406
+ return Effect.all(effects, { concurrency: "unbounded" })
407
+ }
408
+
409
+ return null
410
+ }
411
+
412
+ export function setupSparseAttrPart(
413
+ { name, nodes }: Template.SparseAttrNode,
414
+ element: HTMLElement | SVGElement,
415
+ ctx: TemplateContext
416
+ ) {
417
+ ctx.expected++
418
+ const attr = element.getAttributeNode(name) ?? ctx.document.createAttribute(name)
419
+ const index = nodes.find((n): n is Template.AttrPartNode => n._tag === "attr")!.index
420
+ return SyncPartsInternal.handleSparseAttribute(
421
+ element,
422
+ attr,
423
+ nodes,
424
+ ctx.values,
425
+ (f) => Effect.zipRight(ctx.queue.add(attr, f, DEFAULT_PRIORITY), ctx.refCounter.release(index))
426
+ )
427
+ }
428
+
429
+ export function setupSparseClassNamePart(
430
+ { nodes }: Template.SparseClassNameNode,
431
+ element: HTMLElement | SVGElement,
432
+ ctx: TemplateContext
433
+ ) {
434
+ ctx.expected++
435
+ const index = nodes.find((n): n is Template.ClassNamePartNode => n._tag === "className-part")!.index
436
+ return SyncPartsInternal.handleSparseClassName(
437
+ element,
438
+ nodes,
439
+ ctx.values,
440
+ (f) => Effect.zipRight(ctx.queue.add(element.classList, f, DEFAULT_PRIORITY), ctx.refCounter.release(index))
441
+ )
442
+ }
443
+
444
+ export function setupSparseCommentPart(
445
+ { nodes }: Template.SparseCommentNode,
446
+ comment: Comment,
447
+ ctx: TemplateContext
448
+ ) {
449
+ ctx.expected++
450
+ const index = nodes.find((n): n is Template.CommentPartNode => n._tag === "comment-part")!.index
451
+ return SyncPartsInternal.handleSparseComment(
452
+ comment,
453
+ nodes,
454
+ ctx.values,
455
+ (f) => Effect.zipRight(ctx.queue.add(comment, f, DEFAULT_PRIORITY), ctx.refCounter.release(index))
456
+ )
457
+ }
458
+
459
+ export function setupTextPart({ index }: Template.TextPartNode, comment: Comment, ctx: TemplateContext) {
460
+ const text = comment.previousSibling
461
+ ? SyncPartsInternal.getPreviousTextSibling(comment.previousSibling) ?? createText(ctx.document, comment)
462
+ : createText(ctx.document, comment)
463
+ const part = SyncPartsInternal.makeTextPart(index, text)
464
+ const renderable = ctx.values[index]
465
+ return matchSyncPart(renderable, ctx, part)
466
+ }
467
+
468
+ function createText(document: Document, comment: Comment) {
469
+ const text = document.createTextNode("")
470
+ comment.parentNode!.insertBefore(text, comment)
471
+ return text
472
+ }
473
+
474
+ export function matchSyncPart(
475
+ renderable: Renderable<any, any>,
476
+ ctx: TemplateContext,
477
+ syncPart: SyncPart
478
+ ) {
479
+ return matchRenderable(renderable, ctx, {
480
+ Fx: (fx) =>
481
+ fx.run(Fx.Sink.make(
482
+ ctx.onCause,
483
+ (value) => runSyncUpdate(syncPart, value, ctx)
484
+ )),
485
+ Effect: (effect) => Effect.flatMap(effect, (value) => runSyncUpdate(syncPart, value, ctx)),
486
+ Directive: (directive) => directive(syncPartToPart(syncPart, ({ value }) => runSyncUpdate(syncPart, value, ctx))),
487
+ Otherwise: (value) => {
488
+ syncPart.update(value as never)
489
+ return null
490
+ }
491
+ })
492
+ }
493
+
494
+ function unwrapRenderable<E, R>(renderable: unknown): Fx.Fx<any, E, R> {
495
+ switch (typeof renderable) {
496
+ case "undefined":
497
+ case "object": {
498
+ if (renderable === null || renderable === undefined) return Fx.succeed(null)
499
+ else if (Array.isArray(renderable)) {
500
+ return renderable.length === 0
501
+ ? Fx.succeed(null)
502
+ // TODO: We need to ensure the ordering of these values in server environments
503
+ : Fx.map(Fx.tuple(renderable.map(unwrapRenderable)), (xs) => xs.flat()) as any
504
+ } else if (Fx.TypeId in renderable) {
505
+ return renderable as any
506
+ } else if (Effect.EffectTypeId in renderable) {
507
+ return Fx.fromFxEffect(Effect.map(renderable as any, unwrapRenderable<E, R>))
508
+ } else return Fx.succeed(renderable as any)
509
+ }
510
+ default:
511
+ return Fx.succeed(renderable)
512
+ }
513
+ }
514
+
515
+ export function runSyncUpdate(
516
+ syncPart: SyncPart,
517
+ value: any,
518
+ ctx: TemplateContext
519
+ ) {
520
+ return Effect.zipRight(
521
+ ctx.queue.add(syncPart, () => syncPart.update(value as never), DEFAULT_PRIORITY),
522
+ ctx.refCounter.release(syncPart.index)
523
+ )
524
+ }
525
+
526
+ export function matchRenderable(
527
+ renderable: Renderable<any, any>,
528
+ ctx: TemplateContext,
529
+ matches: {
530
+ Fx: (fx: Fx.Fx<any, any, any>) => Effect.Effect<void, any, any>
531
+ Effect: (effect: Effect.Effect<any, any, any>) => Effect.Effect<void, any, any>
532
+ Directive: (directive: Directive<any, any>) => Effect.Effect<void, any, any>
533
+ Otherwise: (_: any) => Effect.Effect<void, any, any> | null
534
+ }
535
+ ): Effect.Effect<void, any, any> | null {
536
+ if (Fx.isFx(renderable)) {
537
+ ctx.expected++
538
+ return matches.Fx(renderable)
539
+ } else if (Effect.isEffect(renderable)) {
540
+ ctx.expected++
541
+ return matches.Effect(renderable)
542
+ } else if (isDirective<any, any>(renderable)) {
543
+ ctx.expected++
544
+ return matches.Directive(renderable)
545
+ } else if (Array.isArray(renderable)) {
546
+ return matches.Fx(unwrapRenderable(renderable))
547
+ } else {
548
+ return matches.Otherwise(renderable)
549
+ }
550
+ }
551
+
552
+ export function attachRoot<T extends RenderEvent | null>(
553
+ cache: RenderContext["renderCache"],
554
+ where: HTMLElement,
555
+ what: RenderEvent | null // TODO: Should we support HTML RenderEvents here too?,
556
+ ): Effect.Effect<ToRendered<T>> {
557
+ return Effect.sync(() => {
558
+ const wire = what?.valueOf() as ToRendered<T>
559
+ const previous = cache.get(where)
560
+
561
+ if (wire !== previous) {
562
+ if (previous && !wire) removeChildren(where, previous)
563
+
564
+ cache.set(where, wire || null)
565
+
566
+ if (wire) replaceChildren(where, wire)
567
+
568
+ return wire as ToRendered<T>
569
+ }
570
+
571
+ return previous as ToRendered<T>
572
+ })
573
+ }
574
+
575
+ export function removeChildren(where: HTMLElement, previous: Rendered) {
576
+ for (const node of getNodes(previous)) {
577
+ where.removeChild(node)
578
+ }
579
+ }
580
+
581
+ export function replaceChildren(where: HTMLElement, wire: Rendered) {
582
+ where.replaceChildren(...getNodes(wire))
583
+ }
584
+
585
+ export function getNodes(rendered: Rendered): Array<globalThis.Node> {
586
+ const value = rendered.valueOf() as globalThis.Node | Array<globalThis.Node>
587
+ return Array.isArray(value) ? value : [value]
588
+ }