@typed/template 0.1.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 (285) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/dist/cjs/Directive.js +76 -0
  4. package/dist/cjs/Directive.js.map +1 -0
  5. package/dist/cjs/ElementRef.js +83 -0
  6. package/dist/cjs/ElementRef.js.map +1 -0
  7. package/dist/cjs/ElementSource.js +244 -0
  8. package/dist/cjs/ElementSource.js.map +1 -0
  9. package/dist/cjs/Entry.js +6 -0
  10. package/dist/cjs/Entry.js.map +1 -0
  11. package/dist/cjs/EventHandler.js +65 -0
  12. package/dist/cjs/EventHandler.js.map +1 -0
  13. package/dist/cjs/Html.js +169 -0
  14. package/dist/cjs/Html.js.map +1 -0
  15. package/dist/cjs/HtmlChunk.js +257 -0
  16. package/dist/cjs/HtmlChunk.js.map +1 -0
  17. package/dist/cjs/Hydrate.js +49 -0
  18. package/dist/cjs/Hydrate.js.map +1 -0
  19. package/dist/cjs/Many.js +45 -0
  20. package/dist/cjs/Many.js.map +1 -0
  21. package/dist/cjs/Meta.js +37 -0
  22. package/dist/cjs/Meta.js.map +1 -0
  23. package/dist/cjs/Parser.js +331 -0
  24. package/dist/cjs/Parser.js.map +1 -0
  25. package/dist/cjs/Part.js +6 -0
  26. package/dist/cjs/Part.js.map +1 -0
  27. package/dist/cjs/Placeholder.js +38 -0
  28. package/dist/cjs/Placeholder.js.map +1 -0
  29. package/dist/cjs/Platform.js +64 -0
  30. package/dist/cjs/Platform.js.map +1 -0
  31. package/dist/cjs/Render.js +39 -0
  32. package/dist/cjs/Render.js.map +1 -0
  33. package/dist/cjs/RenderContext.js +130 -0
  34. package/dist/cjs/RenderContext.js.map +1 -0
  35. package/dist/cjs/RenderEvent.js +44 -0
  36. package/dist/cjs/RenderEvent.js.map +1 -0
  37. package/dist/cjs/RenderTemplate.js +41 -0
  38. package/dist/cjs/RenderTemplate.js.map +1 -0
  39. package/dist/cjs/Renderable.js +6 -0
  40. package/dist/cjs/Renderable.js.map +1 -0
  41. package/dist/cjs/Template.js +266 -0
  42. package/dist/cjs/Template.js.map +1 -0
  43. package/dist/cjs/TemplateInstance.js +51 -0
  44. package/dist/cjs/TemplateInstance.js.map +1 -0
  45. package/dist/cjs/Test.js +90 -0
  46. package/dist/cjs/Test.js.map +1 -0
  47. package/dist/cjs/Token.js +270 -0
  48. package/dist/cjs/Token.js.map +1 -0
  49. package/dist/cjs/Tokenizer.js +18 -0
  50. package/dist/cjs/Tokenizer.js.map +1 -0
  51. package/dist/cjs/Vitest.js +44 -0
  52. package/dist/cjs/Vitest.js.map +1 -0
  53. package/dist/cjs/index.js +147 -0
  54. package/dist/cjs/index.js.map +1 -0
  55. package/dist/cjs/internal/HydrateContext.js +13 -0
  56. package/dist/cjs/internal/HydrateContext.js.map +1 -0
  57. package/dist/cjs/internal/browser.js +109 -0
  58. package/dist/cjs/internal/browser.js.map +1 -0
  59. package/dist/cjs/internal/chunks.js +54 -0
  60. package/dist/cjs/internal/chunks.js.map +1 -0
  61. package/dist/cjs/internal/errors.js +23 -0
  62. package/dist/cjs/internal/errors.js.map +1 -0
  63. package/dist/cjs/internal/hydrate.js +197 -0
  64. package/dist/cjs/internal/hydrate.js.map +1 -0
  65. package/dist/cjs/internal/indexRefCounter.js +32 -0
  66. package/dist/cjs/internal/indexRefCounter.js.map +1 -0
  67. package/dist/cjs/internal/module-augmentation.js +6 -0
  68. package/dist/cjs/internal/module-augmentation.js.map +1 -0
  69. package/dist/cjs/internal/parser.js +492 -0
  70. package/dist/cjs/internal/parser.js.map +1 -0
  71. package/dist/cjs/internal/parts.js +350 -0
  72. package/dist/cjs/internal/parts.js.map +1 -0
  73. package/dist/cjs/internal/readAttribute.js +34 -0
  74. package/dist/cjs/internal/readAttribute.js.map +1 -0
  75. package/dist/cjs/internal/render.js +332 -0
  76. package/dist/cjs/internal/render.js.map +1 -0
  77. package/dist/cjs/internal/server.js +219 -0
  78. package/dist/cjs/internal/server.js.map +1 -0
  79. package/dist/cjs/internal/tokenizer.js +264 -0
  80. package/dist/cjs/internal/tokenizer.js.map +1 -0
  81. package/dist/cjs/internal/utils.js +68 -0
  82. package/dist/cjs/internal/utils.js.map +1 -0
  83. package/dist/dts/Directive.d.ts +70 -0
  84. package/dist/dts/Directive.d.ts.map +1 -0
  85. package/dist/dts/ElementRef.d.ts +40 -0
  86. package/dist/dts/ElementRef.d.ts.map +1 -0
  87. package/dist/dts/ElementSource.d.ts +72 -0
  88. package/dist/dts/ElementSource.d.ts.map +1 -0
  89. package/dist/dts/Entry.d.ts +26 -0
  90. package/dist/dts/Entry.d.ts.map +1 -0
  91. package/dist/dts/EventHandler.d.ts +61 -0
  92. package/dist/dts/EventHandler.d.ts.map +1 -0
  93. package/dist/dts/Html.d.ts +17 -0
  94. package/dist/dts/Html.d.ts.map +1 -0
  95. package/dist/dts/HtmlChunk.d.ts +56 -0
  96. package/dist/dts/HtmlChunk.d.ts.map +1 -0
  97. package/dist/dts/Hydrate.d.ts +20 -0
  98. package/dist/dts/Hydrate.d.ts.map +1 -0
  99. package/dist/dts/Many.d.ts +32 -0
  100. package/dist/dts/Many.d.ts.map +1 -0
  101. package/dist/dts/Meta.d.ts +24 -0
  102. package/dist/dts/Meta.d.ts.map +1 -0
  103. package/dist/dts/Parser.d.ts +16 -0
  104. package/dist/dts/Parser.d.ts.map +1 -0
  105. package/dist/dts/Part.d.ts +147 -0
  106. package/dist/dts/Part.d.ts.map +1 -0
  107. package/dist/dts/Placeholder.d.ts +51 -0
  108. package/dist/dts/Placeholder.d.ts.map +1 -0
  109. package/dist/dts/Platform.d.ts +23 -0
  110. package/dist/dts/Platform.d.ts.map +1 -0
  111. package/dist/dts/Render.d.ts +23 -0
  112. package/dist/dts/Render.d.ts.map +1 -0
  113. package/dist/dts/RenderContext.d.ts +88 -0
  114. package/dist/dts/RenderContext.d.ts.map +1 -0
  115. package/dist/dts/RenderEvent.d.ts +37 -0
  116. package/dist/dts/RenderEvent.d.ts.map +1 -0
  117. package/dist/dts/RenderTemplate.d.ts +38 -0
  118. package/dist/dts/RenderTemplate.d.ts.map +1 -0
  119. package/dist/dts/Renderable.d.ts +28 -0
  120. package/dist/dts/Renderable.d.ts.map +1 -0
  121. package/dist/dts/Template.d.ts +218 -0
  122. package/dist/dts/Template.d.ts.map +1 -0
  123. package/dist/dts/TemplateInstance.d.ts +32 -0
  124. package/dist/dts/TemplateInstance.d.ts.map +1 -0
  125. package/dist/dts/Test.d.ts +58 -0
  126. package/dist/dts/Test.d.ts.map +1 -0
  127. package/dist/dts/Token.d.ts +202 -0
  128. package/dist/dts/Token.d.ts.map +1 -0
  129. package/dist/dts/Tokenizer.d.ts +6 -0
  130. package/dist/dts/Tokenizer.d.ts.map +1 -0
  131. package/dist/dts/Vitest.d.ts +28 -0
  132. package/dist/dts/Vitest.d.ts.map +1 -0
  133. package/dist/dts/index.d.ts +65 -0
  134. package/dist/dts/index.d.ts.map +1 -0
  135. package/dist/dts/internal/HydrateContext.d.ts +2 -0
  136. package/dist/dts/internal/HydrateContext.d.ts.map +1 -0
  137. package/dist/dts/internal/browser.d.ts +8 -0
  138. package/dist/dts/internal/browser.d.ts.map +1 -0
  139. package/dist/dts/internal/chunks.d.ts +22 -0
  140. package/dist/dts/internal/chunks.d.ts.map +1 -0
  141. package/dist/dts/internal/errors.d.ts +9 -0
  142. package/dist/dts/internal/errors.d.ts.map +1 -0
  143. package/dist/dts/internal/hydrate.d.ts +37 -0
  144. package/dist/dts/internal/hydrate.d.ts.map +1 -0
  145. package/dist/dts/internal/indexRefCounter.d.ts +6 -0
  146. package/dist/dts/internal/indexRefCounter.d.ts.map +1 -0
  147. package/dist/dts/internal/module-augmentation.d.ts +36 -0
  148. package/dist/dts/internal/module-augmentation.d.ts.map +1 -0
  149. package/dist/dts/internal/parser.d.ts +12 -0
  150. package/dist/dts/internal/parser.d.ts.map +1 -0
  151. package/dist/dts/internal/parts.d.ts +304 -0
  152. package/dist/dts/internal/parts.d.ts.map +1 -0
  153. package/dist/dts/internal/readAttribute.d.ts +9 -0
  154. package/dist/dts/internal/readAttribute.d.ts.map +1 -0
  155. package/dist/dts/internal/render.d.ts +30 -0
  156. package/dist/dts/internal/render.d.ts.map +1 -0
  157. package/dist/dts/internal/server.d.ts +31 -0
  158. package/dist/dts/internal/server.d.ts.map +1 -0
  159. package/dist/dts/internal/tokenizer.d.ts +3 -0
  160. package/dist/dts/internal/tokenizer.d.ts.map +1 -0
  161. package/dist/dts/internal/utils.d.ts +15 -0
  162. package/dist/dts/internal/utils.d.ts.map +1 -0
  163. package/dist/esm/Directive.js +64 -0
  164. package/dist/esm/Directive.js.map +1 -0
  165. package/dist/esm/ElementRef.js +72 -0
  166. package/dist/esm/ElementRef.js.map +1 -0
  167. package/dist/esm/ElementSource.js +237 -0
  168. package/dist/esm/ElementSource.js.map +1 -0
  169. package/dist/esm/Entry.js +2 -0
  170. package/dist/esm/Entry.js.map +1 -0
  171. package/dist/esm/EventHandler.js +52 -0
  172. package/dist/esm/EventHandler.js.map +1 -0
  173. package/dist/esm/Html.js +167 -0
  174. package/dist/esm/Html.js.map +1 -0
  175. package/dist/esm/HtmlChunk.js +274 -0
  176. package/dist/esm/HtmlChunk.js.map +1 -0
  177. package/dist/esm/Hydrate.js +37 -0
  178. package/dist/esm/Hydrate.js.map +1 -0
  179. package/dist/esm/Many.js +33 -0
  180. package/dist/esm/Many.js.map +1 -0
  181. package/dist/esm/Meta.js +29 -0
  182. package/dist/esm/Meta.js.map +1 -0
  183. package/dist/esm/Parser.js +342 -0
  184. package/dist/esm/Parser.js.map +1 -0
  185. package/dist/esm/Part.js +5 -0
  186. package/dist/esm/Part.js.map +1 -0
  187. package/dist/esm/Placeholder.js +30 -0
  188. package/dist/esm/Placeholder.js.map +1 -0
  189. package/dist/esm/Platform.js +41 -0
  190. package/dist/esm/Platform.js.map +1 -0
  191. package/dist/esm/Render.js +27 -0
  192. package/dist/esm/Render.js.map +1 -0
  193. package/dist/esm/RenderContext.js +113 -0
  194. package/dist/esm/RenderContext.js.map +1 -0
  195. package/dist/esm/RenderEvent.js +36 -0
  196. package/dist/esm/RenderEvent.js.map +1 -0
  197. package/dist/esm/RenderTemplate.js +26 -0
  198. package/dist/esm/RenderTemplate.js.map +1 -0
  199. package/dist/esm/Renderable.js +2 -0
  200. package/dist/esm/Renderable.js.map +1 -0
  201. package/dist/esm/Template.js +239 -0
  202. package/dist/esm/Template.js.map +1 -0
  203. package/dist/esm/TemplateInstance.js +43 -0
  204. package/dist/esm/TemplateInstance.js.map +1 -0
  205. package/dist/esm/Test.js +68 -0
  206. package/dist/esm/Test.js.map +1 -0
  207. package/dist/esm/Token.js +264 -0
  208. package/dist/esm/Token.js.map +1 -0
  209. package/dist/esm/Tokenizer.js +9 -0
  210. package/dist/esm/Tokenizer.js.map +1 -0
  211. package/dist/esm/Vitest.js +29 -0
  212. package/dist/esm/Vitest.js.map +1 -0
  213. package/dist/esm/index.js +65 -0
  214. package/dist/esm/index.js.map +1 -0
  215. package/dist/esm/internal/HydrateContext.js +7 -0
  216. package/dist/esm/internal/HydrateContext.js.map +1 -0
  217. package/dist/esm/internal/browser.js +102 -0
  218. package/dist/esm/internal/browser.js.map +1 -0
  219. package/dist/esm/internal/chunks.js +47 -0
  220. package/dist/esm/internal/chunks.js.map +1 -0
  221. package/dist/esm/internal/errors.js +15 -0
  222. package/dist/esm/internal/errors.js.map +1 -0
  223. package/dist/esm/internal/hydrate.js +165 -0
  224. package/dist/esm/internal/hydrate.js.map +1 -0
  225. package/dist/esm/internal/indexRefCounter.js +24 -0
  226. package/dist/esm/internal/indexRefCounter.js.map +1 -0
  227. package/dist/esm/internal/module-augmentation.js +2 -0
  228. package/dist/esm/internal/module-augmentation.js.map +1 -0
  229. package/dist/esm/internal/parser.js +493 -0
  230. package/dist/esm/internal/parser.js.map +1 -0
  231. package/dist/esm/internal/parts.js +291 -0
  232. package/dist/esm/internal/parts.js.map +1 -0
  233. package/dist/esm/internal/readAttribute.js +24 -0
  234. package/dist/esm/internal/readAttribute.js.map +1 -0
  235. package/dist/esm/internal/render.js +329 -0
  236. package/dist/esm/internal/render.js.map +1 -0
  237. package/dist/esm/internal/server.js +174 -0
  238. package/dist/esm/internal/server.js.map +1 -0
  239. package/dist/esm/internal/tokenizer.js +296 -0
  240. package/dist/esm/internal/tokenizer.js.map +1 -0
  241. package/dist/esm/internal/utils.js +52 -0
  242. package/dist/esm/internal/utils.js.map +1 -0
  243. package/dist/esm/package.json +4 -0
  244. package/package.json +242 -0
  245. package/src/Directive.ts +114 -0
  246. package/src/ElementRef.ts +123 -0
  247. package/src/ElementSource.ts +417 -0
  248. package/src/Entry.ts +28 -0
  249. package/src/EventHandler.ts +104 -0
  250. package/src/Html.ts +258 -0
  251. package/src/HtmlChunk.ts +346 -0
  252. package/src/Hydrate.ts +53 -0
  253. package/src/Many.ts +128 -0
  254. package/src/Meta.ts +32 -0
  255. package/src/Parser.ts +457 -0
  256. package/src/Part.ts +186 -0
  257. package/src/Placeholder.ts +70 -0
  258. package/src/Platform.ts +71 -0
  259. package/src/Render.ts +45 -0
  260. package/src/RenderContext.ts +221 -0
  261. package/src/RenderEvent.ts +67 -0
  262. package/src/RenderTemplate.ts +88 -0
  263. package/src/Renderable.ts +34 -0
  264. package/src/Template.ts +284 -0
  265. package/src/TemplateInstance.ts +83 -0
  266. package/src/Test.ts +151 -0
  267. package/src/Token.ts +269 -0
  268. package/src/Tokenizer.ts +10 -0
  269. package/src/Vitest.ts +61 -0
  270. package/src/index.ts +66 -0
  271. package/src/internal/HydrateContext.ts +23 -0
  272. package/src/internal/browser.ts +132 -0
  273. package/src/internal/chunks.ts +73 -0
  274. package/src/internal/errors.ts +11 -0
  275. package/src/internal/external.d.ts +11 -0
  276. package/src/internal/hydrate.ts +262 -0
  277. package/src/internal/indexRefCounter.ts +33 -0
  278. package/src/internal/module-augmentation.ts +48 -0
  279. package/src/internal/parser.ts +637 -0
  280. package/src/internal/parts.ts +527 -0
  281. package/src/internal/readAttribute.ts +28 -0
  282. package/src/internal/render.ts +529 -0
  283. package/src/internal/server.ts +293 -0
  284. package/src/internal/tokenizer.ts +338 -0
  285. package/src/internal/utils.ts +73 -0
@@ -0,0 +1,529 @@
1
+ import * as Fx from "@typed/fx/Fx"
2
+ import { makeSubject } from "@typed/fx/internal/core-subject"
3
+ import { TypeId } from "@typed/fx/TypeId"
4
+ import type { Rendered } from "@typed/wire"
5
+ import { persistent } from "@typed/wire"
6
+ import { Effect } from "effect"
7
+ import type { Cause } from "effect/Cause"
8
+ import { replace } from "effect/ReadonlyArray"
9
+ import type { Scope } from "effect/Scope"
10
+ import { isDirective } from "../Directive"
11
+ import * as ElementRef from "../ElementRef"
12
+ import type { BrowserEntry } from "../Entry"
13
+ import * as EventHandler from "../EventHandler"
14
+ import type { AttributePart, ClassNamePart, CommentPart, Part, Parts, SparsePart, StaticText } from "../Part"
15
+ import type { Placeholder } from "../Placeholder"
16
+ import type { ToRendered } from "../Render"
17
+ import type { Renderable } from "../Renderable"
18
+ import type { RenderContext } from "../RenderContext"
19
+ import type { RenderEvent } from "../RenderEvent"
20
+ import { DomRenderEvent } from "../RenderEvent"
21
+ import type { RenderTemplate } from "../RenderTemplate"
22
+ import type * as Template from "../Template"
23
+ import { TemplateInstance } from "../TemplateInstance"
24
+ import { makeRenderNodePart } from "./browser"
25
+ import { HydrateContext } from "./HydrateContext"
26
+ import type { IndexRefCounter } from "./indexRefCounter"
27
+ import { indexRefCounter } from "./indexRefCounter"
28
+ import { parse } from "./parser"
29
+ import {
30
+ AttributePartImpl,
31
+ BooleanPartImpl,
32
+ ClassNamePartImpl,
33
+ CommentPartImpl,
34
+ DataPartImpl,
35
+ EventPartImpl,
36
+ PropertyPartImpl,
37
+ RefPartImpl,
38
+ SparseAttributePartImpl,
39
+ SparseClassNamePartImpl,
40
+ SparseCommentPartImpl,
41
+ StaticTextImpl,
42
+ TextPartImpl
43
+ } from "./parts"
44
+ import type { ParentChildNodes } from "./utils"
45
+ import { findPath } from "./utils"
46
+
47
+ /**
48
+ * Here for "standard" browser rendering, a TemplateInstance is effectively a live
49
+ * view into the contents rendered by the Template.
50
+ */
51
+ export const renderTemplate: (document: Document, ctx: RenderContext) => RenderTemplate =
52
+ (document, ctx) =>
53
+ <Values extends ReadonlyArray<Renderable<any, any>>, T extends Rendered = Rendered>(
54
+ templateStrings: TemplateStringsArray,
55
+ values: Values,
56
+ providedRef?: ElementRef.ElementRef<T>
57
+ ) =>
58
+ Effect.gen(function*(_) {
59
+ const elementRef = providedRef || (yield* _(ElementRef.make<T>()))
60
+ const events = Fx.map(elementRef, DomRenderEvent)
61
+ const errors = makeSubject<Placeholder.Error<Values[number]>, never>()
62
+ const entry = getBrowserEntry(document, ctx, templateStrings)
63
+ const content = document.importNode(entry.content, true) // Clone our template
64
+
65
+ const parts = yield* _(buildParts(document, ctx, entry.template, content, elementRef, errors.onFailure, false)) // Build runtime-variant of parts with our content.
66
+
67
+ // If there are parts we need to render them before constructing our Wire
68
+ if (parts.length > 0) {
69
+ const refCounter = yield* _(indexRefCounter(parts.length))
70
+
71
+ // Do the work
72
+ yield* _(renderValues(values, parts, refCounter, errors.onFailure))
73
+
74
+ // Wait for initial work to be completed
75
+ yield* _(refCounter.wait)
76
+ }
77
+
78
+ // Set the element when it is ready
79
+ yield* _(ElementRef.set(elementRef, persistent(content) as T))
80
+
81
+ // Return the Template instance
82
+ return TemplateInstance(Fx.merge([events, errors]), elementRef)
83
+ })
84
+
85
+ export function renderValues<Values extends ReadonlyArray<Renderable<any, any>>>(
86
+ values: Values,
87
+ parts: Parts,
88
+ refCounter: IndexRefCounter,
89
+ onCause: (cause: Cause<Placeholder.Error<Values[number]>>) => Effect.Effect<never, never, unknown>,
90
+ makeHydrateContext?: (index: number) => HydrateContext
91
+ ): Effect.Effect<Placeholder.Context<Values[number]> | Scope, never, void> {
92
+ return Effect.all(parts.map((part, index) => {
93
+ switch (part._tag) {
94
+ case "sparse/attribute":
95
+ case "sparse/className":
96
+ case "sparse/comment": {
97
+ return renderSparsePart(values, part, refCounter)
98
+ }
99
+ default:
100
+ return renderPart(
101
+ values,
102
+ part,
103
+ refCounter,
104
+ onCause,
105
+ makeHydrateContext ? () => makeHydrateContext(index) : undefined
106
+ )
107
+ }
108
+ })) as any
109
+ }
110
+
111
+ export function renderSparsePart(
112
+ values: ReadonlyArray<Renderable<any, any>>,
113
+ part: SparsePart,
114
+ refCounter: IndexRefCounter
115
+ ) {
116
+ const indexes = part.parts.flatMap((p) => p._tag === "static/text" ? [] : [p.index])
117
+
118
+ return Effect.forkScoped(
119
+ Fx.observe(
120
+ unwrapSparsePartRenderables(
121
+ part.parts.map((p) => p._tag === "static/text" ? Fx.succeed(p.value) : values[p.index]),
122
+ part
123
+ ),
124
+ (value) => Effect.tap(part.update(value as any), () => Effect.forEach(indexes, (a) => refCounter.release(a)))
125
+ )
126
+ )
127
+ }
128
+
129
+ export function renderPart<Values extends ReadonlyArray<Renderable<any, any>>>(
130
+ values: Values,
131
+ part: Part,
132
+ refCounter: IndexRefCounter,
133
+ onCause: (cause: Cause<Placeholder.Error<Values[number]>>) => Effect.Effect<never, never, unknown>,
134
+ hydrateCtx?: () => HydrateContext
135
+ ): Effect.Effect<any, never, void> {
136
+ const partIndex = part.index
137
+ const renderable = values[partIndex]
138
+
139
+ if (isDirective(renderable)) {
140
+ return renderable(part).pipe(
141
+ Effect.tap(() => refCounter.release(partIndex)),
142
+ Effect.forkScoped
143
+ )
144
+ } else if (part._tag === "ref") {
145
+ return refCounter.release(partIndex)
146
+ } else if (part._tag === "event") {
147
+ return Effect.tap(
148
+ part.update(
149
+ getEventHandler(values[partIndex], onCause) as EventHandler.EventHandler<
150
+ Placeholder.Context<Values[number]>,
151
+ never
152
+ >
153
+ ),
154
+ () => refCounter.release(partIndex)
155
+ )
156
+ } else if (part._tag === "node" && hydrateCtx) {
157
+ if (renderable === null || renderable === undefined) return refCounter.release(partIndex)
158
+
159
+ return handlePart(
160
+ values[partIndex],
161
+ (value) => Effect.tap(part.update(value), () => refCounter.release(partIndex))
162
+ ).pipe(
163
+ HydrateContext.provide(hydrateCtx()),
164
+ Effect.forkScoped
165
+ )
166
+ } else {
167
+ const renderable = values[partIndex]
168
+
169
+ if (renderable === null || renderable === undefined) return refCounter.release(partIndex)
170
+
171
+ return handlePart(
172
+ values[partIndex],
173
+ (value) => Effect.tap(part.update(value as any), () => refCounter.release(partIndex))
174
+ )
175
+ }
176
+ }
177
+
178
+ function getEventHandler<R, E>(
179
+ renderable: any,
180
+ onCause: (cause: Cause<E>) => Effect.Effect<never, never, unknown>
181
+ ): EventHandler.EventHandler<R, never> | null {
182
+ if (renderable && typeof renderable === "object") {
183
+ if (EventHandler.EventHandlerTypeId in renderable) {
184
+ return EventHandler.make(
185
+ (ev) => Effect.catchAllCause((renderable as EventHandler.EventHandler<R, E>).handler(ev), onCause),
186
+ (renderable as EventHandler.EventHandler<R, E>).options
187
+ )
188
+ } else if (Effect.EffectTypeId in renderable) {
189
+ return EventHandler.make(() => Effect.catchAllCause(renderable, onCause))
190
+ }
191
+ }
192
+
193
+ return null
194
+ }
195
+
196
+ function handlePart<R, E>(
197
+ renderable: unknown,
198
+ update: (u: unknown) => Effect.Effect<Scope, never, unknown>
199
+ ): Effect.Effect<R | Scope, E, any> {
200
+ switch (typeof renderable) {
201
+ case "undefined":
202
+ case "object": {
203
+ if (renderable === null || renderable === undefined) return update(null)
204
+ else if (Array.isArray(renderable)) {
205
+ return renderable.length === 0
206
+ ? update(null)
207
+ : Effect.forkScoped(Fx.observe(Fx.combine(renderable.map(unwrapRenderable)) as any, update))
208
+ } else if (TypeId in renderable) {
209
+ return Effect.forkScoped(Fx.observe(renderable as any, update))
210
+ } else if (Effect.EffectTypeId in renderable) {
211
+ return Effect.flatMap(renderable as Effect.Effect<R, E, any>, update)
212
+ } else return update(renderable)
213
+ }
214
+ default:
215
+ return update(renderable)
216
+ }
217
+ }
218
+
219
+ function unwrapRenderable<R, E>(renderable: unknown): Fx.Fx<R, E, any> {
220
+ switch (typeof renderable) {
221
+ case "undefined":
222
+ case "object": {
223
+ if (renderable === null || renderable === undefined) return Fx.succeed(null)
224
+ else if (Array.isArray(renderable)) {
225
+ return renderable.length === 0 ? Fx.succeed(null) : Fx.combine(renderable.map(unwrapRenderable)) as any
226
+ } else if (TypeId in renderable) {
227
+ return renderable as any
228
+ } else if (Effect.EffectTypeId in renderable) {
229
+ return Fx.fromFxEffect(Effect.map(renderable as any, unwrapRenderable<R, E>))
230
+ } else return Fx.succeed(renderable as any)
231
+ }
232
+ default:
233
+ return Fx.succeed(renderable)
234
+ }
235
+ }
236
+
237
+ function unwrapSparsePartRenderables(
238
+ renderables: ReadonlyArray<Renderable<any, any>>,
239
+ part: SparsePart
240
+ ) {
241
+ return Fx.combine(
242
+ // @ts-ignore type too deep
243
+ renderables.map((renderable, i) => {
244
+ const p = part.parts[i]
245
+
246
+ if (p._tag === "static/text") {
247
+ return Fx.succeed(p.value)
248
+ }
249
+
250
+ if (isDirective(renderable)) {
251
+ return Fx.fromEffect(Effect.map(renderable(p), () => p.value))
252
+ }
253
+
254
+ return Fx.mapEffect(
255
+ unwrapRenderable(renderable),
256
+ (u) => Effect.map(p.update(u), () => p.value)
257
+ )
258
+ })
259
+ ) as any
260
+ }
261
+
262
+ export function attachRoot<T extends RenderEvent | null>(
263
+ cache: RenderContext["renderCache"],
264
+ where: HTMLElement,
265
+ what: RenderEvent | null // TODO: Should we support HTML RenderEvents here too?
266
+ ): Effect.Effect<never, never, ToRendered<T>> {
267
+ return Effect.sync(() => {
268
+ const wire = what?.valueOf() as ToRendered<T>
269
+ const previous = cache.get(where)
270
+
271
+ if (wire !== previous) {
272
+ if (previous && !wire) removeChildren(where, previous)
273
+
274
+ cache.set(where, wire || null)
275
+
276
+ if (wire) replaceChildren(where, wire)
277
+
278
+ return wire as ToRendered<T>
279
+ }
280
+
281
+ return previous as ToRendered<T>
282
+ })
283
+ }
284
+
285
+ function removeChildren(where: HTMLElement, previous: Rendered) {
286
+ for (const node of getNodes(previous)) {
287
+ where.removeChild(node)
288
+ }
289
+ }
290
+
291
+ function replaceChildren(where: HTMLElement, wire: Rendered) {
292
+ where.replaceChildren(...getNodes(wire))
293
+ }
294
+
295
+ function getNodes(rendered: Rendered): Array<globalThis.Node> {
296
+ const value = rendered.valueOf() as globalThis.Node | Array<globalThis.Node>
297
+ return Array.isArray(value) ? value : [value]
298
+ }
299
+
300
+ export function getBrowserEntry(
301
+ document: Document,
302
+ ctx: RenderContext,
303
+ templateStrings: TemplateStringsArray
304
+ ): BrowserEntry {
305
+ const cached = ctx.templateCache.get(templateStrings)
306
+
307
+ if (cached === undefined || cached._tag === "Server") {
308
+ const template = parse(templateStrings)
309
+ const content = buildTemplate(document, template)
310
+ const entry: BrowserEntry = {
311
+ _tag: "Browser",
312
+ template,
313
+ content
314
+ }
315
+
316
+ ctx.templateCache.set(templateStrings, entry)
317
+
318
+ return entry
319
+ } else {
320
+ return cached
321
+ }
322
+ }
323
+
324
+ export function buildParts<T extends Rendered, E>(
325
+ document: Document,
326
+ ctx: RenderContext,
327
+ template: Template.Template,
328
+ content: ParentChildNodes,
329
+ ref: ElementRef.ElementRef<T>,
330
+ onCause: (cause: Cause<E>) => Effect.Effect<never, never, void>,
331
+ isHydrating: boolean
332
+ ): Effect.Effect<Scope, never, Parts> {
333
+ return Effect.all(
334
+ template.parts.map(([part, path]) =>
335
+ buildPartWithNode(document, ctx, part, findPath(content, path), ref, onCause, isHydrating)
336
+ )
337
+ )
338
+ }
339
+
340
+ function buildPartWithNode<T extends Rendered, E>(
341
+ document: Document,
342
+ ctx: RenderContext,
343
+ part: Template.PartNode | Template.SparsePartNode,
344
+ node: Node,
345
+ ref: ElementRef.ElementRef<T>,
346
+ onCause: (cause: Cause<E>) => Effect.Effect<never, never, void>,
347
+ isHydrating: boolean
348
+ ): Effect.Effect<Scope, never, Part | SparsePart> {
349
+ switch (part._tag) {
350
+ case "attr":
351
+ return Effect.succeed(AttributePartImpl.browser(part.index, node as Element, part.name, ctx))
352
+ case "boolean-part":
353
+ return Effect.succeed(BooleanPartImpl.browser(part.index, node as Element, part.name, ctx))
354
+ case "className-part":
355
+ return Effect.succeed(ClassNamePartImpl.browser(part.index, node as Element, ctx))
356
+ case "comment-part":
357
+ return Effect.succeed(CommentPartImpl.browser(part.index, node as Comment, ctx))
358
+ case "data":
359
+ return Effect.succeed(DataPartImpl.browser(part.index, node as HTMLElement | SVGElement, ctx))
360
+ case "event":
361
+ return EventPartImpl.browser(part.name, part.index, ref, node as HTMLElement | SVGElement, onCause) as any
362
+ case "node":
363
+ return Effect.succeed(
364
+ makeRenderNodePart(part.index, node as HTMLElement | SVGElement, ctx, document, isHydrating)
365
+ )
366
+ case "property":
367
+ return Effect.succeed(PropertyPartImpl.browser(part.index, node, part.name, ctx))
368
+ case "ref":
369
+ return Effect.succeed(new RefPartImpl(ref.query(node as HTMLElement | SVGElement), part.index)) as any
370
+ case "sparse-attr": {
371
+ const parts: Array<AttributePart | StaticText> = Array(part.nodes.length)
372
+ const sparse = SparseAttributePartImpl.browser(
373
+ part.name,
374
+ parts,
375
+ node as HTMLElement | SVGElement,
376
+ ctx
377
+ )
378
+
379
+ for (let i = 0; i < part.nodes.length; ++i) {
380
+ const node = part.nodes[i]
381
+
382
+ if (node._tag === "text") {
383
+ parts.push(new StaticTextImpl(node.value))
384
+ ;(sparse as any).value[i] = node.value
385
+ } else {
386
+ parts.push(
387
+ new AttributePartImpl(
388
+ node.name,
389
+ node.index,
390
+ ({ value }) => sparse.update(replace(sparse.value, i, value || "")),
391
+ sparse.value[i]
392
+ )
393
+ )
394
+ }
395
+ }
396
+
397
+ return Effect.succeed(sparse)
398
+ }
399
+ case "sparse-class-name": {
400
+ const parts: Array<ClassNamePart | StaticText> = []
401
+ const values: Array<string | Array<string>> = [] // TODO: Do this for all other sparse attrs
402
+ const sparse = SparseClassNamePartImpl.browser(
403
+ parts,
404
+ node as HTMLElement | SVGElement,
405
+ ctx,
406
+ values
407
+ )
408
+
409
+ for (let i = 0; i < part.nodes.length; ++i) {
410
+ const node = part.nodes[i]
411
+
412
+ if (node._tag === "text") {
413
+ parts.push(new StaticTextImpl(node.value))
414
+ values.push(node.value)
415
+ } else {
416
+ values.push([])
417
+ parts.push(
418
+ new ClassNamePartImpl(
419
+ node.index,
420
+ ({ value }) => sparse.update(replace(sparse.value, i, value || "")),
421
+ []
422
+ )
423
+ )
424
+ }
425
+ }
426
+
427
+ return Effect.succeed(sparse)
428
+ }
429
+ case "sparse-comment": {
430
+ const parts: Array<CommentPart | StaticText> = Array(part.nodes.length)
431
+ const sparse = SparseCommentPartImpl.browser(
432
+ node as Comment,
433
+ parts,
434
+ ctx
435
+ )
436
+
437
+ for (let i = 0; i < part.nodes.length; ++i) {
438
+ const node = part.nodes[i]
439
+
440
+ if (node._tag === "text") {
441
+ parts.push(new StaticTextImpl(node.value))
442
+ ;(sparse as any).value[i] = node.value
443
+ } else {
444
+ parts.push(
445
+ new CommentPartImpl(
446
+ node.index,
447
+ ({ value }) => sparse.update(replace(sparse.value, i, value || "")),
448
+ sparse.value[i]
449
+ )
450
+ )
451
+ }
452
+ }
453
+
454
+ return Effect.succeed(sparse)
455
+ }
456
+ case "text-part":
457
+ return Effect.succeed(TextPartImpl.browser(document, part.index, node as Element, ctx))
458
+ }
459
+ }
460
+
461
+ export function buildTemplate(document: Document, { nodes }: Template.Template): DocumentFragment {
462
+ const fragment = document.createDocumentFragment()
463
+
464
+ for (let i = 0; i < nodes.length; ++i) {
465
+ fragment.append(buildNode(document, nodes[i], false))
466
+ }
467
+
468
+ return fragment
469
+ }
470
+
471
+ function buildNode(document: Document, node: Template.Node, isSvgContext: boolean): globalThis.Node {
472
+ switch (node._tag) {
473
+ case "element":
474
+ case "self-closing-element":
475
+ case "text-only-element":
476
+ return buildElement(document, node, isSvgContext)
477
+ case "text":
478
+ return document.createTextNode(node.value)
479
+ case "comment":
480
+ return document.createComment(node.value)
481
+ case "sparse-comment":
482
+ return document.createComment("")
483
+ // Create placeholders for these elements
484
+ case "comment-part":
485
+ case "node":
486
+ return document.createComment(`hole${node.index}`)
487
+ }
488
+ }
489
+
490
+ const SVG_NAMESPACE = "http://www.w3.org/2000/svg"
491
+
492
+ function buildElement(
493
+ document: Document,
494
+ node: Template.ElementNode | Template.SelfClosingElementNode | Template.TextOnlyElement,
495
+ isSvgContext: boolean
496
+ ): Element {
497
+ const { _tag, attributes, tagName } = node
498
+ const isSvg = isSvgContext ? tagName !== "foreignObject" : tagName === "svg"
499
+ const element = isSvg
500
+ ? document.createElementNS(SVG_NAMESPACE, tagName)
501
+ : document.createElement(tagName)
502
+
503
+ for (let i = 0; i < attributes.length; ++i) {
504
+ const attr = attributes[i]
505
+
506
+ // We only handle static attributes here, parts are handled elsewhere
507
+ if (attr._tag === "attribute") {
508
+ element.setAttribute(attr.name, attr.value)
509
+ } else if (attr._tag === "boolean") {
510
+ element.toggleAttribute(attr.name, true)
511
+ }
512
+ }
513
+
514
+ if (_tag === "element") {
515
+ element.append(...node.children.map((child) => buildNode(document, child, isSvg)))
516
+ } else if (_tag === "text-only-element") {
517
+ element.append(...node.children.map((child) => buildTextChild(document, child)))
518
+ }
519
+
520
+ return element
521
+ }
522
+
523
+ function buildTextChild(document: Document, node: Template.Text): globalThis.Node {
524
+ if (node._tag === "text") {
525
+ return document.createTextNode(node.value)
526
+ }
527
+
528
+ return document.createComment(`hole${node.index}`)
529
+ }