@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,88 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+
5
+ import * as Context from "@typed/context"
6
+ import * as Fx from "@typed/fx/Fx"
7
+ import type { Rendered } from "@typed/wire"
8
+ import type * as Effect from "effect/Effect"
9
+ import type { Scope } from "effect/Scope"
10
+ import type { ElementRef } from "./ElementRef"
11
+ import type { Placeholder } from "./Placeholder"
12
+ import type { Renderable } from "./Renderable"
13
+ import type { RenderEvent } from "./RenderEvent"
14
+ import type { TemplateInstance } from "./TemplateInstance"
15
+
16
+ /**
17
+ * @since 1.0.0
18
+ */
19
+ export interface RenderTemplate {
20
+ <Values extends ReadonlyArray<Renderable<any, any>>, T extends Rendered = Rendered>(
21
+ templateStrings: TemplateStringsArray,
22
+ values: Values,
23
+ ref?: ElementRef<T>
24
+ ): Effect.Effect<
25
+ Scope | Placeholder.Context<readonly [] extends Values ? never : Values[number]>,
26
+ never,
27
+ TemplateInstance<
28
+ Placeholder.Error<Values[number]>,
29
+ T
30
+ >
31
+ >
32
+ }
33
+
34
+ /**
35
+ * @since 1.0.0
36
+ */
37
+ export const RenderTemplate: Context.Tagged<RenderTemplate, RenderTemplate> = Context.Tagged<
38
+ RenderTemplate,
39
+ RenderTemplate
40
+ >(
41
+ "./RenderTemplate"
42
+ )
43
+
44
+ /**
45
+ * @since 1.0.0
46
+ */
47
+ export interface TemplateFx<R, E, T extends Rendered = Rendered> extends
48
+ Fx.Fx<
49
+ RenderTemplate | Scope | R,
50
+ E,
51
+ RenderEvent
52
+ >
53
+ {
54
+ readonly instance: Effect.Effect<
55
+ RenderTemplate | Scope | R,
56
+ never,
57
+ TemplateInstance<
58
+ E,
59
+ T
60
+ >
61
+ >
62
+ }
63
+
64
+ /**
65
+ * @since 1.0.0
66
+ */
67
+ export function html<const Values extends ReadonlyArray<Renderable<any, any>>>(
68
+ template: TemplateStringsArray,
69
+ ...values: Values
70
+ ): TemplateFx<Placeholder.Context<Values[number]>, Placeholder.Error<Values[number]>> {
71
+ const instance = RenderTemplate.withEffect((render) => render(template, values))
72
+
73
+ return Object.assign(Fx.fromFxEffect(instance), { instance })
74
+ }
75
+
76
+ /**
77
+ * @since 1.0.0
78
+ */
79
+ export function as<T extends Rendered = Rendered>(ref: ElementRef<T>) {
80
+ return function html<const Values extends ReadonlyArray<Renderable<any, any>>>(
81
+ template: TemplateStringsArray,
82
+ ...values: Values
83
+ ): TemplateFx<Placeholder.Context<Values[number]>, Placeholder.Error<Values[number]>, T> {
84
+ const instance = RenderTemplate.withEffect((render) => render(template, values, ref))
85
+
86
+ return Object.assign(Fx.fromFxEffect(instance), { instance })
87
+ }
88
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import type { Fx } from "@typed/fx/Fx"
5
+ import type { Rendered } from "@typed/wire"
6
+ import type { Effect } from "effect/Effect"
7
+ import type { Placeholder } from "./Placeholder"
8
+ import type { RenderEvent } from "./RenderEvent"
9
+
10
+ /**
11
+ * @since 1.0.0
12
+ */
13
+ export type Renderable<R = never, E = never> =
14
+ | Renderable.Value
15
+ | { readonly [key: string]: Renderable.Value } // TODO: Should we manage data attributes this way?
16
+ | Placeholder<R, E, any>
17
+ | Effect<R, E, any>
18
+ | Fx<R, E, any>
19
+ | ReadonlyArray<Renderable<R, E>>
20
+
21
+ /**
22
+ * @since 1.0.0
23
+ */
24
+ export namespace Renderable {
25
+ /**
26
+ * @since 1.0.0
27
+ */
28
+ export type Primitive = string | number | boolean | null | undefined | void | ReadonlyArray<Primitive>
29
+
30
+ /**
31
+ * @since 1.0.0
32
+ */
33
+ export type Value = Primitive | Rendered | RenderEvent
34
+ }
@@ -0,0 +1,284 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import type { Chunk } from "effect/Chunk"
5
+
6
+ /**
7
+ * @since 1.0.0
8
+ */
9
+ export class Template {
10
+ readonly _tag = "template"
11
+
12
+ constructor(
13
+ readonly nodes: ReadonlyArray<Node>,
14
+ readonly hash: string,
15
+ // Parts are a array of Parts to the respective path from the root node to access it during
16
+ readonly parts: ReadonlyArray<readonly [part: PartNode | SparsePartNode, path: Chunk<number>]>
17
+ ) {}
18
+ }
19
+
20
+ /**
21
+ * @since 1.0.0
22
+ */
23
+ export type ParentNode = ElementNode | SelfClosingElementNode | TextOnlyElement
24
+
25
+ /**
26
+ * @since 1.0.0
27
+ */
28
+ export type Node =
29
+ | ElementNode
30
+ | SelfClosingElementNode
31
+ | TextOnlyElement
32
+ | TextNode
33
+ | NodePart
34
+ | Comment
35
+
36
+ /**
37
+ * @since 1.0.0
38
+ */
39
+ export type PartNode =
40
+ | AttrPartNode
41
+ | BooleanPartNode
42
+ | ClassNamePartNode
43
+ | DataPartNode
44
+ | EventPartNode
45
+ | NodePart
46
+ | PropertyPartNode
47
+ | RefPartNode
48
+ | TextPartNode
49
+ | CommentPartNode
50
+
51
+ /**
52
+ * @since 1.0.0
53
+ */
54
+ export type SparsePartNode = SparseAttrNode | SparseClassNameNode | SparseCommentNode
55
+
56
+ /**
57
+ * @since 1.0.0
58
+ */
59
+ export class ElementNode {
60
+ readonly _tag = "element"
61
+ constructor(
62
+ readonly tagName: string,
63
+ readonly attributes: Array<Attribute>,
64
+ readonly children: Array<Node>
65
+ ) {}
66
+ }
67
+
68
+ /**
69
+ * @since 1.0.0
70
+ */
71
+ export class NodePart {
72
+ readonly _tag = "node"
73
+ constructor(readonly index: number) {}
74
+ }
75
+
76
+ /**
77
+ * @since 1.0.0
78
+ */
79
+ export class SelfClosingElementNode {
80
+ readonly _tag = "self-closing-element"
81
+ constructor(
82
+ readonly tagName: string,
83
+ readonly attributes: Array<Attribute>
84
+ ) {}
85
+ }
86
+
87
+ /**
88
+ * @since 1.0.0
89
+ */
90
+ export class TextOnlyElement {
91
+ readonly _tag = "text-only-element"
92
+ constructor(
93
+ readonly tagName: string,
94
+ readonly attributes: Array<Attribute>,
95
+ readonly children: Array<Text>
96
+ ) {}
97
+ }
98
+
99
+ /**
100
+ * @since 1.0.0
101
+ */
102
+ export type Attribute =
103
+ | AttributeNode
104
+ | AttrPartNode
105
+ | SparseAttrNode
106
+ | BooleanNode
107
+ | BooleanPartNode
108
+ | ClassNameNode
109
+ | SparseClassNameNode
110
+ | DataPartNode
111
+ | EventPartNode
112
+ | PropertyPartNode
113
+ | RefPartNode
114
+
115
+ /**
116
+ * @since 1.0.0
117
+ */
118
+ export class AttributeNode {
119
+ readonly _tag = "attribute" as const
120
+ constructor(
121
+ readonly name: string,
122
+ readonly value: string
123
+ ) {}
124
+ }
125
+
126
+ /**
127
+ * @since 1.0.0
128
+ */
129
+ export class AttrPartNode {
130
+ readonly _tag = "attr" as const
131
+ constructor(
132
+ readonly name: string,
133
+ readonly index: number
134
+ ) {}
135
+ }
136
+
137
+ /**
138
+ * @since 1.0.0
139
+ */
140
+ export class SparseAttrNode {
141
+ readonly _tag = "sparse-attr" as const
142
+ constructor(
143
+ readonly name: string,
144
+ readonly nodes: Array<AttrPartNode | TextNode>
145
+ ) {}
146
+ }
147
+
148
+ /**
149
+ * @since 1.0.0
150
+ */
151
+ export class BooleanNode {
152
+ readonly _tag = "boolean" as const
153
+ constructor(readonly name: string) {}
154
+ }
155
+
156
+ /**
157
+ * @since 1.0.0
158
+ */
159
+ export class BooleanPartNode {
160
+ readonly _tag = "boolean-part" as const
161
+ constructor(
162
+ readonly name: string,
163
+ readonly index: number
164
+ ) {}
165
+ }
166
+
167
+ /**
168
+ * @since 1.0.0
169
+ */
170
+ export type ClassNameNode = TextNode | ClassNamePartNode
171
+
172
+ /**
173
+ * @since 1.0.0
174
+ */
175
+ export class ClassNamePartNode {
176
+ readonly _tag = "className-part" as const
177
+ constructor(readonly index: number) {}
178
+ }
179
+
180
+ /**
181
+ * @since 1.0.0
182
+ */
183
+ export class SparseClassNameNode {
184
+ readonly _tag = "sparse-class-name" as const
185
+
186
+ constructor(readonly nodes: Array<ClassNameNode>) {}
187
+ }
188
+
189
+ /**
190
+ * @since 1.0.0
191
+ */
192
+ export class DataPartNode {
193
+ readonly _tag = "data" as const
194
+
195
+ constructor(readonly index: number) {}
196
+ }
197
+
198
+ /**
199
+ * @since 1.0.0
200
+ */
201
+ export class EventPartNode {
202
+ readonly _tag = "event" as const
203
+
204
+ constructor(
205
+ readonly name: string,
206
+ readonly index: number
207
+ ) {}
208
+ }
209
+
210
+ /**
211
+ * @since 1.0.0
212
+ */
213
+ export class PropertyPartNode {
214
+ readonly _tag = "property" as const
215
+
216
+ constructor(
217
+ readonly name: string,
218
+ readonly index: number
219
+ ) {}
220
+ }
221
+
222
+ /**
223
+ * @since 1.0.0
224
+ */
225
+ export class RefPartNode {
226
+ readonly _tag = "ref" as const
227
+
228
+ constructor(readonly index: number) {}
229
+ }
230
+
231
+ /**
232
+ * @since 1.0.0
233
+ */
234
+ export type Text = TextNode | TextPartNode
235
+
236
+ /**
237
+ * @since 1.0.0
238
+ */
239
+ export class TextNode {
240
+ readonly _tag = "text" as const
241
+
242
+ constructor(readonly value: string) {}
243
+ }
244
+
245
+ /**
246
+ * @since 1.0.0
247
+ */
248
+ export class TextPartNode {
249
+ readonly _tag = "text-part" as const
250
+
251
+ constructor(readonly index: number) {}
252
+ }
253
+
254
+ /**
255
+ * @since 1.0.0
256
+ */
257
+ export type Comment = CommentNode | CommentPartNode | SparseCommentNode
258
+
259
+ /**
260
+ * @since 1.0.0
261
+ */
262
+ export class CommentNode {
263
+ readonly _tag = "comment" as const
264
+
265
+ constructor(readonly value: string) {}
266
+ }
267
+
268
+ /**
269
+ * @since 1.0.0
270
+ */
271
+ export class CommentPartNode {
272
+ readonly _tag = "comment-part" as const
273
+
274
+ constructor(readonly index: number) {}
275
+ }
276
+
277
+ /**
278
+ * @since 1.0.0
279
+ */
280
+ export class SparseCommentNode {
281
+ readonly _tag = "sparse-comment" as const
282
+
283
+ constructor(readonly nodes: Array<TextNode | CommentPartNode>) {}
284
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+
5
+ import type * as Fx from "@typed/fx/Fx"
6
+ import { FxEffectBase } from "@typed/fx/internal/protos"
7
+ import type * as Versioned from "@typed/fx/Versioned"
8
+ import type { Rendered } from "@typed/wire"
9
+ import type { NoSuchElementException } from "effect/Cause"
10
+ import type * as Effect from "effect/Effect"
11
+ import { type ElementRef, ElementRefTypeId } from "./ElementRef"
12
+ import type { Placeholder } from "./Placeholder"
13
+ import type { RenderEvent } from "./RenderEvent"
14
+
15
+ /**
16
+ * @since 1.0.0
17
+ */
18
+ export const TemplateInstanceTypeId = Symbol.for("./TemplateInstance")
19
+ /**
20
+ * @since 1.0.0
21
+ */
22
+ export type TemplateInstanceTypeId = typeof TemplateInstanceTypeId
23
+
24
+ /**
25
+ * @since 1.0.0
26
+ */
27
+ export interface TemplateInstance<E, T extends Rendered = Rendered>
28
+ extends Versioned.Versioned<never, never, never, E, RenderEvent, never, E | NoSuchElementException, T>
29
+ {
30
+ readonly [TemplateInstanceTypeId]: TemplateInstanceTypeId
31
+
32
+ readonly query: ElementRef<T>["query"]
33
+
34
+ readonly events: ElementRef<T>["events"]
35
+
36
+ readonly elements: ElementRef<T>["elements"]
37
+
38
+ readonly dispatchEvent: ElementRef<T>["dispatchEvent"]
39
+ }
40
+
41
+ /**
42
+ * @since 1.0.0
43
+ */
44
+ export function TemplateInstance<T extends Rendered = Rendered, E = never>(
45
+ events: Fx.Fx<never, E, RenderEvent>,
46
+ ref: ElementRef<T>
47
+ ): TemplateInstance<E, T> {
48
+ return new TemplateInstanceImpl(events, ref) as any
49
+ }
50
+
51
+ // @ts-expect-error placeholder issues
52
+ class TemplateInstanceImpl<E, T extends Rendered>
53
+ extends FxEffectBase<never, E, RenderEvent, never, E | NoSuchElementException, T>
54
+ implements Omit<TemplateInstance<E, T>, keyof Placeholder<never, E, RenderEvent>>
55
+ {
56
+ readonly [TemplateInstanceTypeId]: TemplateInstanceTypeId = TemplateInstanceTypeId
57
+ query: TemplateInstance<E, T>["query"]
58
+ events: TemplateInstance<E, T>["events"]
59
+ elements: TemplateInstance<E, T>["elements"]
60
+ dispatchEvent: TemplateInstance<E, T>["dispatchEvent"]
61
+ version: Effect.Effect<never, never, number>
62
+
63
+ constructor(
64
+ readonly i0: Fx.Fx<never, E, RenderEvent>,
65
+ readonly i1: ElementRef<T>
66
+ ) {
67
+ super()
68
+
69
+ this.query = this.i1.query
70
+ this.events = this.i1.events
71
+ this.elements = this.i1.elements
72
+ this.dispatchEvent = this.i1.dispatchEvent
73
+ this.version = this.i1[ElementRefTypeId].version
74
+ }
75
+
76
+ toFx(): Fx.Fx<never, E, RenderEvent> {
77
+ return this.i0
78
+ }
79
+
80
+ toEffect(): Effect.Effect<never, E | NoSuchElementException, T> {
81
+ return this.i1
82
+ }
83
+ }
package/src/Test.ts ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import type { Document } from "@typed/dom/Document"
5
+ import type { DomServices, DomServicesElementParams } from "@typed/dom/DomServices"
6
+ import type { GlobalThis } from "@typed/dom/GlobalThis"
7
+ import type { Window } from "@typed/dom/Window"
8
+ import type { CurrentEnvironment } from "@typed/environment"
9
+ import type { Computed } from "@typed/fx/Computed"
10
+ import type { Filtered } from "@typed/fx/Filtered"
11
+ import * as Fx from "@typed/fx/Fx"
12
+ import * as RefArray from "@typed/fx/RefArray"
13
+ import * as Sink from "@typed/fx/Sink"
14
+ import * as Cause from "effect/Cause"
15
+ import * as Effect from "effect/Effect"
16
+ import * as Either from "effect/Either"
17
+ import * as Fiber from "effect/Fiber"
18
+ import type * as Scope from "effect/Scope"
19
+ import * as happyDOM from "happy-dom"
20
+ import type IHappyDOMOptions from "happy-dom/lib/window/IHappyDOMOptions.js"
21
+ import * as ElementRef from "./ElementRef"
22
+ import { ROOT_CSS_SELECTOR } from "./ElementSource"
23
+ import { adjustTime } from "./internal/utils"
24
+ import { render } from "./Render"
25
+ import * as RenderContext from "./RenderContext"
26
+ import type { RenderEvent } from "./RenderEvent"
27
+ import type { RenderTemplate } from "./RenderTemplate"
28
+
29
+ // TODO: Instrument RenderTemplate to log info about when specific values are hanging for too long
30
+ // TODO: Input events
31
+ // TODO: Form events
32
+ // TODO: keyboard events
33
+ // TODO: drag and drop events
34
+
35
+ /**
36
+ * @since 1.0.0
37
+ */
38
+ export interface TestRender<E> {
39
+ readonly window: Window & GlobalThis & Pick<happyDOM.Window, "happyDOM">
40
+ readonly document: Document
41
+ readonly elementRef: ElementRef.ElementRef
42
+ readonly errors: Computed<never, never, ReadonlyArray<E>>
43
+ readonly lastError: Filtered<never, never, E>
44
+ readonly interrupt: Effect.Effect<never, never, void>
45
+ readonly makeEvent: (type: string, eventInitDict?: EventInit) => Event
46
+ readonly makeCustomEvent: <A>(type: string, eventInitDict?: CustomEventInit<A>) => CustomEvent<A>
47
+ readonly dispatchEvent: (options: EventOptions) => Effect.Effect<never, Cause.NoSuchElementException, void>
48
+ readonly click: (options?: Omit<EventOptions, "event">) => Effect.Effect<never, Cause.NoSuchElementException, void>
49
+ }
50
+
51
+ /**
52
+ * @since 1.0.0
53
+ */
54
+ export function testRender<R, E>(
55
+ fx: Fx.Fx<R, E, RenderEvent>,
56
+ options?:
57
+ & IHappyDOMOptions
58
+ & { readonly [K in keyof DomServicesElementParams]?: (document: Document) => DomServicesElementParams[K] }
59
+ ): Effect.Effect<
60
+ Scope.Scope | Exclude<Exclude<R, RenderTemplate>, RenderContext.RenderContext | CurrentEnvironment | DomServices>,
61
+ never,
62
+ TestRender<E>
63
+ > {
64
+ return Effect.gen(function*(_) {
65
+ const window = makeWindow(options)
66
+ const elementRef = yield* _(ElementRef.make())
67
+ const errors = yield* _(RefArray.make<never, never, E>(Effect.succeed([])))
68
+ const fiber = yield* _(
69
+ fx,
70
+ render,
71
+ Fx.run(Sink.Sink(
72
+ (cause) =>
73
+ Cause.failureOrCause(cause).pipe(
74
+ Either.match({
75
+ onLeft: (error) => RefArray.append(errors, error),
76
+ onRight: (cause) => errors.onFailure(cause)
77
+ })
78
+ ),
79
+ (rendered) => ElementRef.set(elementRef, rendered)
80
+ )),
81
+ Effect.forkScoped,
82
+ Effect.provide(RenderContext.browser(window, { skipRenderScheduling: true }))
83
+ )
84
+
85
+ const test: TestRender<E> = {
86
+ window,
87
+ document: window.document,
88
+ elementRef,
89
+ errors,
90
+ lastError: RefArray.last(errors),
91
+ interrupt: Fiber.interrupt(fiber),
92
+ makeEvent: (type, init) => new window.Event(type, init),
93
+ makeCustomEvent: (type, init) => new window.CustomEvent(type, init),
94
+ dispatchEvent: (options) => dispatchEvent(test, options),
95
+ click: (options) => click(test, options)
96
+ }
97
+
98
+ // Allow our fibers to start
99
+ yield* _(adjustTime(1))
100
+ yield* _(adjustTime(1))
101
+
102
+ return test
103
+ })
104
+ }
105
+
106
+ /**
107
+ * @since 1.0.0
108
+ */
109
+ export type EventOptions = {
110
+ readonly event: string
111
+ readonly selector?: string
112
+ readonly eventInit?: EventInit
113
+ }
114
+
115
+ // TODO: Find more events to add here
116
+ const NON_BUBBLING_EVENTS = new Set(["focus", "blur"])
117
+
118
+ /**
119
+ * @since 1.0.0
120
+ */
121
+ export function dispatchEvent<E>(
122
+ { elementRef, makeEvent }: Pick<TestRender<E>, "elementRef" | "makeEvent">,
123
+ options: EventOptions
124
+ ) {
125
+ const selector = options.selector ?? ROOT_CSS_SELECTOR
126
+
127
+ return elementRef.query(selector).dispatchEvent(
128
+ makeEvent(options.event, {
129
+ bubbles: !NON_BUBBLING_EVENTS.has(selector),
130
+ cancelable: true,
131
+ composed: false,
132
+ ...options?.eventInit
133
+ })
134
+ )
135
+ }
136
+
137
+ /**
138
+ * @since 1.0.0
139
+ */
140
+ export function click<E>(
141
+ rendered: Pick<TestRender<E>, "elementRef" | "makeEvent">,
142
+ options?: Omit<EventOptions, "event">
143
+ ) {
144
+ return dispatchEvent(rendered, { event: "click", ...options, eventInit: { bubbles: true, ...options?.eventInit } })
145
+ }
146
+
147
+ // internals
148
+
149
+ function makeWindow(options?: IHappyDOMOptions) {
150
+ return new happyDOM.Window(options) as any as Window & GlobalThis & Pick<happyDOM.Window, "happyDOM">
151
+ }