@typed/template 0.2.0 → 0.3.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 (213) hide show
  1. package/dist/cjs/Directive.js +1 -1
  2. package/dist/cjs/Directive.js.map +1 -1
  3. package/dist/cjs/ElementRef.js +23 -13
  4. package/dist/cjs/ElementRef.js.map +1 -1
  5. package/dist/cjs/ElementSource.js +16 -18
  6. package/dist/cjs/ElementSource.js.map +1 -1
  7. package/dist/cjs/EventHandler.js +1 -1
  8. package/dist/cjs/EventHandler.js.map +1 -1
  9. package/dist/cjs/Html.js +31 -32
  10. package/dist/cjs/Html.js.map +1 -1
  11. package/dist/cjs/HtmlChunk.js +4 -1
  12. package/dist/cjs/HtmlChunk.js.map +1 -1
  13. package/dist/cjs/Hydrate.js +1 -1
  14. package/dist/cjs/Hydrate.js.map +1 -1
  15. package/dist/cjs/Many.js +14 -13
  16. package/dist/cjs/Many.js.map +1 -1
  17. package/dist/cjs/Parser.js +11 -323
  18. package/dist/cjs/Parser.js.map +1 -1
  19. package/dist/cjs/Placeholder.js +3 -3
  20. package/dist/cjs/Placeholder.js.map +1 -1
  21. package/dist/cjs/Platform.js +4 -4
  22. package/dist/cjs/Platform.js.map +1 -1
  23. package/dist/cjs/Render.js +1 -1
  24. package/dist/cjs/Render.js.map +1 -1
  25. package/dist/cjs/RenderContext.js +48 -27
  26. package/dist/cjs/RenderContext.js.map +1 -1
  27. package/dist/cjs/RenderTemplate.js +2 -17
  28. package/dist/cjs/RenderTemplate.js.map +1 -1
  29. package/dist/cjs/Template.js +27 -1
  30. package/dist/cjs/Template.js.map +1 -1
  31. package/dist/cjs/TemplateInstance.js +2 -2
  32. package/dist/cjs/TemplateInstance.js.map +1 -1
  33. package/dist/cjs/Test.js +20 -7
  34. package/dist/cjs/Test.js.map +1 -1
  35. package/dist/cjs/index.js +0 -12
  36. package/dist/cjs/index.js.map +1 -1
  37. package/dist/cjs/internal/EventSource.js +95 -0
  38. package/dist/cjs/internal/EventSource.js.map +1 -0
  39. package/dist/cjs/internal/browser.js +11 -11
  40. package/dist/cjs/internal/browser.js.map +1 -1
  41. package/dist/cjs/internal/hydrate.js +49 -50
  42. package/dist/cjs/internal/hydrate.js.map +1 -1
  43. package/dist/cjs/internal/indexRefCounter.js +49 -2
  44. package/dist/cjs/internal/indexRefCounter.js.map +1 -1
  45. package/dist/cjs/internal/parser.js +60 -17
  46. package/dist/cjs/internal/parser.js.map +1 -1
  47. package/dist/cjs/internal/parts.js +128 -28
  48. package/dist/cjs/internal/parts.js.map +1 -1
  49. package/dist/cjs/internal/render.js +486 -53
  50. package/dist/cjs/internal/render.js.map +1 -1
  51. package/dist/cjs/internal/server.js +5 -2
  52. package/dist/cjs/internal/server.js.map +1 -1
  53. package/dist/dts/Directive.d.ts.map +1 -1
  54. package/dist/dts/ElementRef.d.ts +4 -2
  55. package/dist/dts/ElementRef.d.ts.map +1 -1
  56. package/dist/dts/ElementSource.d.ts +10 -5
  57. package/dist/dts/ElementSource.d.ts.map +1 -1
  58. package/dist/dts/EventHandler.d.ts.map +1 -1
  59. package/dist/dts/Html.d.ts +1 -1
  60. package/dist/dts/Html.d.ts.map +1 -1
  61. package/dist/dts/HtmlChunk.d.ts.map +1 -1
  62. package/dist/dts/Many.d.ts +13 -11
  63. package/dist/dts/Many.d.ts.map +1 -1
  64. package/dist/dts/Parser.d.ts +3 -6
  65. package/dist/dts/Parser.d.ts.map +1 -1
  66. package/dist/dts/Part.d.ts +13 -3
  67. package/dist/dts/Part.d.ts.map +1 -1
  68. package/dist/dts/Placeholder.d.ts +2 -2
  69. package/dist/dts/Placeholder.d.ts.map +1 -1
  70. package/dist/dts/Render.d.ts +2 -1
  71. package/dist/dts/Render.d.ts.map +1 -1
  72. package/dist/dts/RenderContext.d.ts +5 -4
  73. package/dist/dts/RenderContext.d.ts.map +1 -1
  74. package/dist/dts/RenderTemplate.d.ts +2 -16
  75. package/dist/dts/RenderTemplate.d.ts.map +1 -1
  76. package/dist/dts/Renderable.d.ts +2 -2
  77. package/dist/dts/Renderable.d.ts.map +1 -1
  78. package/dist/dts/Template.d.ts +21 -3
  79. package/dist/dts/Template.d.ts.map +1 -1
  80. package/dist/dts/TemplateInstance.d.ts +3 -2
  81. package/dist/dts/TemplateInstance.d.ts.map +1 -1
  82. package/dist/dts/Test.d.ts +4 -6
  83. package/dist/dts/Test.d.ts.map +1 -1
  84. package/dist/dts/index.d.ts +0 -4
  85. package/dist/dts/index.d.ts.map +1 -1
  86. package/dist/dts/internal/EventSource.d.ts +12 -0
  87. package/dist/dts/internal/EventSource.d.ts.map +1 -0
  88. package/dist/dts/internal/browser.d.ts.map +1 -1
  89. package/dist/dts/internal/hydrate.d.ts +5 -5
  90. package/dist/dts/internal/hydrate.d.ts.map +1 -1
  91. package/dist/dts/internal/indexRefCounter.d.ts +5 -0
  92. package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
  93. package/dist/dts/internal/module-augmentation.d.ts +0 -4
  94. package/dist/dts/internal/module-augmentation.d.ts.map +1 -1
  95. package/dist/dts/internal/parser.d.ts +8 -0
  96. package/dist/dts/internal/parser.d.ts.map +1 -1
  97. package/dist/dts/internal/parts.d.ts +66 -56
  98. package/dist/dts/internal/parts.d.ts.map +1 -1
  99. package/dist/dts/internal/render.d.ts +7 -7
  100. package/dist/dts/internal/render.d.ts.map +1 -1
  101. package/dist/dts/internal/server.d.ts.map +1 -1
  102. package/dist/esm/Directive.js +1 -1
  103. package/dist/esm/Directive.js.map +1 -1
  104. package/dist/esm/ElementRef.js +12 -7
  105. package/dist/esm/ElementRef.js.map +1 -1
  106. package/dist/esm/ElementSource.js +16 -13
  107. package/dist/esm/ElementSource.js.map +1 -1
  108. package/dist/esm/EventHandler.js +1 -1
  109. package/dist/esm/EventHandler.js.map +1 -1
  110. package/dist/esm/Html.js +29 -24
  111. package/dist/esm/Html.js.map +1 -1
  112. package/dist/esm/HtmlChunk.js +6 -1
  113. package/dist/esm/HtmlChunk.js.map +1 -1
  114. package/dist/esm/Hydrate.js +1 -1
  115. package/dist/esm/Hydrate.js.map +1 -1
  116. package/dist/esm/Many.js +14 -10
  117. package/dist/esm/Many.js.map +1 -1
  118. package/dist/esm/Parser.js +6 -335
  119. package/dist/esm/Parser.js.map +1 -1
  120. package/dist/esm/Placeholder.js +2 -2
  121. package/dist/esm/Placeholder.js.map +1 -1
  122. package/dist/esm/Platform.js +2 -2
  123. package/dist/esm/Platform.js.map +1 -1
  124. package/dist/esm/Render.js +1 -1
  125. package/dist/esm/Render.js.map +1 -1
  126. package/dist/esm/RenderContext.js +38 -17
  127. package/dist/esm/RenderContext.js.map +1 -1
  128. package/dist/esm/RenderTemplate.js +2 -12
  129. package/dist/esm/RenderTemplate.js.map +1 -1
  130. package/dist/esm/Template.js +24 -0
  131. package/dist/esm/Template.js.map +1 -1
  132. package/dist/esm/TemplateInstance.js +2 -2
  133. package/dist/esm/TemplateInstance.js.map +1 -1
  134. package/dist/esm/Test.js +20 -7
  135. package/dist/esm/Test.js.map +1 -1
  136. package/dist/esm/index.js +0 -4
  137. package/dist/esm/index.js.map +1 -1
  138. package/dist/esm/internal/EventSource.js +91 -0
  139. package/dist/esm/internal/EventSource.js.map +1 -0
  140. package/dist/esm/internal/browser.js +12 -12
  141. package/dist/esm/internal/browser.js.map +1 -1
  142. package/dist/esm/internal/hydrate.js +45 -46
  143. package/dist/esm/internal/hydrate.js.map +1 -1
  144. package/dist/esm/internal/indexRefCounter.js +50 -2
  145. package/dist/esm/internal/indexRefCounter.js.map +1 -1
  146. package/dist/esm/internal/parser.js +84 -17
  147. package/dist/esm/internal/parser.js.map +1 -1
  148. package/dist/esm/internal/parts.js +110 -27
  149. package/dist/esm/internal/parts.js.map +1 -1
  150. package/dist/esm/internal/render.js +476 -58
  151. package/dist/esm/internal/render.js.map +1 -1
  152. package/dist/esm/internal/server.js +5 -4
  153. package/dist/esm/internal/server.js.map +1 -1
  154. package/package.json +10 -26
  155. package/src/Directive.ts +1 -1
  156. package/src/ElementRef.ts +18 -14
  157. package/src/ElementSource.ts +62 -47
  158. package/src/EventHandler.ts +1 -1
  159. package/src/Html.ts +58 -57
  160. package/src/HtmlChunk.ts +15 -1
  161. package/src/Hydrate.ts +1 -1
  162. package/src/Many.ts +53 -43
  163. package/src/Parser.ts +10 -453
  164. package/src/Part.ts +15 -3
  165. package/src/Placeholder.ts +4 -4
  166. package/src/Platform.ts +2 -2
  167. package/src/Render.ts +7 -2
  168. package/src/RenderContext.ts +47 -19
  169. package/src/RenderTemplate.ts +9 -54
  170. package/src/Renderable.ts +2 -1
  171. package/src/Template.ts +26 -0
  172. package/src/TemplateInstance.ts +9 -9
  173. package/src/Test.ts +40 -21
  174. package/src/index.ts +0 -4
  175. package/src/internal/EventSource.ts +153 -0
  176. package/src/internal/browser.ts +26 -25
  177. package/src/internal/hydrate.ts +68 -61
  178. package/src/internal/indexRefCounter.ts +63 -2
  179. package/src/internal/module-augmentation.ts +0 -4
  180. package/src/internal/parser.ts +92 -19
  181. package/src/internal/parts.ts +158 -73
  182. package/src/internal/render.ts +701 -89
  183. package/src/internal/server.ts +5 -3
  184. package/Token/package.json +0 -6
  185. package/Tokenizer/package.json +0 -6
  186. package/dist/cjs/Token.js +0 -270
  187. package/dist/cjs/Token.js.map +0 -1
  188. package/dist/cjs/Tokenizer.js +0 -18
  189. package/dist/cjs/Tokenizer.js.map +0 -1
  190. package/dist/cjs/internal/readAttribute.js +0 -34
  191. package/dist/cjs/internal/readAttribute.js.map +0 -1
  192. package/dist/cjs/internal/tokenizer.js +0 -264
  193. package/dist/cjs/internal/tokenizer.js.map +0 -1
  194. package/dist/dts/Token.d.ts +0 -202
  195. package/dist/dts/Token.d.ts.map +0 -1
  196. package/dist/dts/Tokenizer.d.ts +0 -6
  197. package/dist/dts/Tokenizer.d.ts.map +0 -1
  198. package/dist/dts/internal/readAttribute.d.ts +0 -9
  199. package/dist/dts/internal/readAttribute.d.ts.map +0 -1
  200. package/dist/dts/internal/tokenizer.d.ts +0 -3
  201. package/dist/dts/internal/tokenizer.d.ts.map +0 -1
  202. package/dist/esm/Token.js +0 -264
  203. package/dist/esm/Token.js.map +0 -1
  204. package/dist/esm/Tokenizer.js +0 -9
  205. package/dist/esm/Tokenizer.js.map +0 -1
  206. package/dist/esm/internal/readAttribute.js +0 -24
  207. package/dist/esm/internal/readAttribute.js.map +0 -1
  208. package/dist/esm/internal/tokenizer.js +0 -296
  209. package/dist/esm/internal/tokenizer.js.map +0 -1
  210. package/src/Token.ts +0 -269
  211. package/src/Tokenizer.ts +0 -10
  212. package/src/internal/readAttribute.ts +0 -28
  213. package/src/internal/tokenizer.ts +0 -338
@@ -4,30 +4,22 @@
4
4
 
5
5
  import * as Context from "@typed/context"
6
6
  import * as Fx from "@typed/fx/Fx"
7
- import type { Rendered } from "@typed/wire"
8
- import type * as Effect from "effect/Effect"
9
7
  import type { Scope } from "effect/Scope"
10
- import type { ElementRef } from "./ElementRef.js"
11
8
  import type { Placeholder } from "./Placeholder.js"
12
9
  import type { Renderable } from "./Renderable.js"
13
10
  import type { RenderEvent } from "./RenderEvent.js"
14
- import type { TemplateInstance } from "./TemplateInstance.js"
15
11
 
16
12
  /**
17
13
  * @since 1.0.0
18
14
  */
19
15
  export interface RenderTemplate {
20
- <Values extends ReadonlyArray<Renderable<any, any>>, T extends Rendered = Rendered>(
16
+ <Values extends ReadonlyArray<Renderable<any, any>>>(
21
17
  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
- >
18
+ values: Values
19
+ ): Fx.Fx<
20
+ Scope | Placeholder.Context<Values[number]>,
21
+ Placeholder.Error<Values[number]>,
22
+ RenderEvent
31
23
  >
32
24
  }
33
25
 
@@ -38,51 +30,14 @@ export const RenderTemplate: Context.Tagged<RenderTemplate, RenderTemplate> = Co
38
30
  RenderTemplate,
39
31
  RenderTemplate
40
32
  >(
41
- "./RenderTemplate.js"
33
+ "@typed/template/RenderTemplate"
42
34
  )
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
35
  /**
65
36
  * @since 1.0.0
66
37
  */
67
38
  export function html<const Values extends ReadonlyArray<Renderable<any, any>>>(
68
39
  template: TemplateStringsArray,
69
40
  ...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
- }
41
+ ): Fx.Fx<RenderTemplate | Scope | Placeholder.Context<Values[number]>, Placeholder.Error<Values[number]>, RenderEvent> {
42
+ return Fx.fromFxEffect(RenderTemplate.with((render) => render(template, values)))
88
43
  }
package/src/Renderable.ts CHANGED
@@ -12,7 +12,8 @@ import type { RenderEvent } from "./RenderEvent.js"
12
12
  */
13
13
  export type Renderable<R = never, E = never> =
14
14
  | Renderable.Value
15
- | { readonly [key: string]: Renderable.Value } // TODO: Should we manage data attributes this way?
15
+ | Placeholder<R, E, any>
16
+ | { readonly [key: string]: Renderable<R, E> | Placeholder<R, E, any> | unknown } // TODO: Should we manage data attributes this way?
16
17
  | Placeholder<R, E, any>
17
18
  | Effect<R, E, any>
18
19
  | Fx<R, E, any>
package/src/Template.ts CHANGED
@@ -32,6 +32,7 @@ export type Node =
32
32
  | TextNode
33
33
  | NodePart
34
34
  | Comment
35
+ | DocType
35
36
 
36
37
  /**
37
38
  * @since 1.0.0
@@ -44,6 +45,7 @@ export type PartNode =
44
45
  | EventPartNode
45
46
  | NodePart
46
47
  | PropertyPartNode
48
+ | PropertiesPartNode
47
49
  | RefPartNode
48
50
  | TextPartNode
49
51
  | CommentPartNode
@@ -96,6 +98,18 @@ export class TextOnlyElement {
96
98
  ) {}
97
99
  }
98
100
 
101
+ /**
102
+ * @since 1.0.0
103
+ */
104
+ export class DocType {
105
+ readonly _tag = "doctype"
106
+ constructor(
107
+ readonly name: string,
108
+ readonly publicType?: string,
109
+ readonly systemId?: string
110
+ ) {}
111
+ }
112
+
99
113
  /**
100
114
  * @since 1.0.0
101
115
  */
@@ -110,6 +124,7 @@ export type Attribute =
110
124
  | DataPartNode
111
125
  | EventPartNode
112
126
  | PropertyPartNode
127
+ | PropertiesPartNode
113
128
  | RefPartNode
114
129
 
115
130
  /**
@@ -219,6 +234,17 @@ export class PropertyPartNode {
219
234
  ) {}
220
235
  }
221
236
 
237
+ /**
238
+ * @since 1.0.0
239
+ */
240
+ export class PropertiesPartNode {
241
+ readonly _tag = "properties" as const
242
+
243
+ constructor(
244
+ readonly index: number
245
+ ) {}
246
+ }
247
+
222
248
  /**
223
249
  * @since 1.0.0
224
250
  */
@@ -8,14 +8,14 @@ import type * as Versioned from "@typed/fx/Versioned"
8
8
  import type { Rendered } from "@typed/wire"
9
9
  import type { NoSuchElementException } from "effect/Cause"
10
10
  import type * as Effect from "effect/Effect"
11
+ import type { Scope } from "effect/Scope"
11
12
  import { type ElementRef, ElementRefTypeId } from "./ElementRef.js"
12
- import type { Placeholder } from "./Placeholder.js"
13
13
  import type { RenderEvent } from "./RenderEvent.js"
14
14
 
15
15
  /**
16
16
  * @since 1.0.0
17
17
  */
18
- export const TemplateInstanceTypeId = Symbol.for("./TemplateInstance.js")
18
+ export const TemplateInstanceTypeId = Symbol.for("@typed/template/TemplateInstance")
19
19
  /**
20
20
  * @since 1.0.0
21
21
  */
@@ -25,7 +25,7 @@ export type TemplateInstanceTypeId = typeof TemplateInstanceTypeId
25
25
  * @since 1.0.0
26
26
  */
27
27
  export interface TemplateInstance<E, T extends Rendered = Rendered>
28
- extends Versioned.Versioned<never, never, never, E, RenderEvent, never, E | NoSuchElementException, T>
28
+ extends Versioned.Versioned<never, never, Scope, E, RenderEvent, never, E | NoSuchElementException, T>
29
29
  {
30
30
  readonly [TemplateInstanceTypeId]: TemplateInstanceTypeId
31
31
 
@@ -42,16 +42,16 @@ export interface TemplateInstance<E, T extends Rendered = Rendered>
42
42
  * @since 1.0.0
43
43
  */
44
44
  export function TemplateInstance<T extends Rendered = Rendered, E = never>(
45
- events: Fx.Fx<never, E, RenderEvent>,
45
+ events: Fx.Fx<Scope, E, RenderEvent>,
46
46
  ref: ElementRef<T>
47
47
  ): TemplateInstance<E, T> {
48
48
  return new TemplateInstanceImpl(events, ref) as any
49
49
  }
50
50
 
51
- // @ts-expect-error placeholder issues
51
+ // @ts-expect-error does not implement Placeholder
52
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>>
53
+ extends FxEffectBase<Scope, E, RenderEvent, never, E | NoSuchElementException, T>
54
+ implements TemplateInstance<E, T>
55
55
  {
56
56
  readonly [TemplateInstanceTypeId]: TemplateInstanceTypeId = TemplateInstanceTypeId
57
57
  query: TemplateInstance<E, T>["query"]
@@ -61,7 +61,7 @@ class TemplateInstanceImpl<E, T extends Rendered>
61
61
  version: Effect.Effect<never, never, number>
62
62
 
63
63
  constructor(
64
- readonly i0: Fx.Fx<never, E, RenderEvent>,
64
+ readonly i0: Fx.Fx<Scope, E, RenderEvent>,
65
65
  readonly i1: ElementRef<T>
66
66
  ) {
67
67
  super()
@@ -73,7 +73,7 @@ class TemplateInstanceImpl<E, T extends Rendered>
73
73
  this.version = this.i1[ElementRefTypeId].version
74
74
  }
75
75
 
76
- toFx(): Fx.Fx<never, E, RenderEvent> {
76
+ toFx(): Fx.Fx<Scope, E, RenderEvent> {
77
77
  return this.i0
78
78
  }
79
79
 
package/src/Test.ts CHANGED
@@ -6,17 +6,15 @@ import type { DomServices, DomServicesElementParams } from "@typed/dom/DomServic
6
6
  import type { GlobalThis } from "@typed/dom/GlobalThis"
7
7
  import type { Window } from "@typed/dom/Window"
8
8
  import type { CurrentEnvironment } from "@typed/environment"
9
- import type { Computed } from "@typed/fx/Computed"
10
- import type { Filtered } from "@typed/fx/Filtered"
11
9
  import * as Fx from "@typed/fx/Fx"
12
10
  import * as RefArray from "@typed/fx/RefArray"
11
+ import * as RefSubject from "@typed/fx/RefSubject"
13
12
  import * as Sink from "@typed/fx/Sink"
14
13
  import * as Cause from "effect/Cause"
15
14
  import * as Effect from "effect/Effect"
16
15
  import * as Either from "effect/Either"
17
16
  import * as Fiber from "effect/Fiber"
18
17
  import type * as Scope from "effect/Scope"
19
- import * as happyDOM from "happy-dom"
20
18
  import type IHappyDOMOptions from "happy-dom/lib/window/IHappyDOMOptions.js"
21
19
  import * as ElementRef from "./ElementRef.js"
22
20
  import { ROOT_CSS_SELECTOR } from "./ElementSource.js"
@@ -36,11 +34,11 @@ import type { RenderTemplate } from "./RenderTemplate.js"
36
34
  * @since 1.0.0
37
35
  */
38
36
  export interface TestRender<E> {
39
- readonly window: Window & GlobalThis & Pick<happyDOM.Window, "happyDOM">
37
+ readonly window: Window & GlobalThis
40
38
  readonly document: Document
41
39
  readonly elementRef: ElementRef.ElementRef
42
- readonly errors: Computed<never, never, ReadonlyArray<E>>
43
- readonly lastError: Filtered<never, never, E>
40
+ readonly errors: RefSubject.Computed<never, never, ReadonlyArray<E>>
41
+ readonly lastError: RefSubject.Filtered<never, never, E>
44
42
  readonly interrupt: Effect.Effect<never, never, void>
45
43
  readonly makeEvent: (type: string, eventInitDict?: EventInit) => Event
46
44
  readonly makeCustomEvent: <A>(type: string, eventInitDict?: CustomEventInit<A>) => CustomEvent<A>
@@ -62,24 +60,25 @@ export function testRender<R, E>(
62
60
  TestRender<E>
63
61
  > {
64
62
  return Effect.gen(function*(_) {
65
- const window = makeWindow(options)
63
+ const window = yield* _(getOrMakeWindow(options))
66
64
  const elementRef = yield* _(ElementRef.make())
67
- const errors = yield* _(RefArray.make<never, never, E>(Effect.succeed([])))
65
+ const errors = yield* _(RefSubject.make<never, never, ReadonlyArray<E>>(Effect.succeed([])))
68
66
  const fiber = yield* _(
69
67
  fx,
70
68
  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
- )),
69
+ (x) =>
70
+ x.run(Sink.make(
71
+ (cause) =>
72
+ Cause.failureOrCause(cause).pipe(
73
+ Either.match({
74
+ onLeft: (error) => RefArray.append(errors, error),
75
+ onRight: (cause) => errors.onFailure(cause)
76
+ })
77
+ ),
78
+ (rendered) => ElementRef.set(elementRef, rendered)
79
+ )),
81
80
  Effect.forkScoped,
82
- Effect.provide(RenderContext.browser(window, { skipRenderScheduling: true }))
81
+ Effect.provide(RenderContext.dom(window, { skipRenderScheduling: true }))
83
82
  )
84
83
 
85
84
  const test: TestRender<E> = {
@@ -99,6 +98,9 @@ export function testRender<R, E>(
99
98
  yield* _(adjustTime(1))
100
99
  yield* _(adjustTime(1))
101
100
 
101
+ // Await the first render
102
+ yield* _(Fx.first(elementRef), Effect.race(Effect.delay(Effect.dieMessage(`Rendering taking too long`), 1000)))
103
+
102
104
  return test
103
105
  })
104
106
  }
@@ -146,6 +148,23 @@ export function click<E>(
146
148
 
147
149
  // internals
148
150
 
149
- function makeWindow(options?: IHappyDOMOptions) {
150
- return new happyDOM.Window(options) as any as Window & GlobalThis & Pick<happyDOM.Window, "happyDOM">
151
+ function getOrMakeWindow(options?: IHappyDOMOptions) {
152
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
153
+ return Effect.gen(function*(_) {
154
+ window.document.head.innerHTML = ""
155
+ window.document.body.innerHTML = ""
156
+ yield* _(Effect.addFinalizer(() =>
157
+ Effect.sync(() => {
158
+ window.document.head.innerHTML = ""
159
+ window.document.body.innerHTML = ""
160
+ })
161
+ ))
162
+
163
+ return window
164
+ })
165
+ }
166
+
167
+ return Effect.promise(() =>
168
+ import("happy-dom").then((happyDOM) => new happyDOM.Window(options) as any as Window & GlobalThis)
169
+ )
151
170
  }
package/src/index.ts CHANGED
@@ -60,7 +60,3 @@ export * from "./RenderEvent.js"
60
60
  * @since 1.0.0
61
61
  */
62
62
  export * from "./RenderTemplate.js"
63
- /**
64
- * @since 1.0.0
65
- */
66
- export * from "./TemplateInstance.js"
@@ -0,0 +1,153 @@
1
+ import type { Rendered } from "@typed/wire"
2
+ import { Effect, Scope } from "effect"
3
+ import type * as Fiber from "effect/Fiber"
4
+ import * as Runtime from "effect/Runtime"
5
+ import { getElements } from "../ElementSource"
6
+ import type { EventHandler } from "../EventHandler"
7
+
8
+ type EventName = string
9
+
10
+ type Handler<Ev extends Event> = EventHandler<never, never, Ev>
11
+
12
+ export interface EventSource {
13
+ readonly addEventListener: <Ev extends Event>(
14
+ element: Element,
15
+ event: EventName,
16
+ handler: Handler<Ev>
17
+ ) => void
18
+
19
+ readonly setup: (rendered: Rendered, scope: Scope.Scope) => Effect.Effect<never, never, void>
20
+ }
21
+
22
+ type Entry = readonly [Element, Handler<any>]
23
+ type Run = <E, A>(effect: Effect.Effect<never, E, A>) => Fiber.RuntimeFiber<E, A>
24
+
25
+ const disposable = (f: () => void): Disposable => ({
26
+ [Symbol.dispose]: f
27
+ })
28
+
29
+ export function makeEventSource(): EventSource {
30
+ const bubbleListeners = new Map<
31
+ EventName,
32
+ Set<Entry>
33
+ >()
34
+ const captureListeners = new Map<
35
+ EventName,
36
+ Set<Entry>
37
+ >()
38
+
39
+ function addListener(
40
+ listeners: Map<
41
+ EventName,
42
+ Set<Entry>
43
+ >,
44
+ event: EventName,
45
+ entry: Entry
46
+ ): void {
47
+ const set = listeners.get(event)
48
+ if (set === undefined) {
49
+ const set = new Set<Entry>()
50
+ set.add(entry)
51
+ listeners.set(event, set)
52
+ } else {
53
+ set.add(entry)
54
+ }
55
+ }
56
+
57
+ function addEventListener<Ev extends Event>(
58
+ element: Element,
59
+ event: EventName,
60
+ handler: Handler<Ev>
61
+ ): void {
62
+ if (handler.options?.capture === true) {
63
+ return addListener(captureListeners, event, [element, handler])
64
+ } else {
65
+ return addListener(bubbleListeners, event, [element, handler])
66
+ }
67
+ }
68
+
69
+ function setupBubbleListeners(element: Element, run: Run) {
70
+ const disposables: Array<Disposable> = []
71
+
72
+ for (const [event, handlers] of bubbleListeners) {
73
+ const listener = (ev: Event) =>
74
+ run(
75
+ Effect.forEach(handlers, ([el, handler]) =>
76
+ ev.target === el || el.contains(ev.target as Node) ? handler.handler(ev) : Effect.unit)
77
+ )
78
+ element.addEventListener(event, listener, getDerivedAddEventListenerOptions(handlers))
79
+ disposables.push(disposable(() => element.removeEventListener(event, listener)))
80
+ }
81
+
82
+ return disposables
83
+ }
84
+
85
+ function setupCaptureListeners(element: Element, run: Run) {
86
+ const disposables: Array<Disposable> = []
87
+
88
+ for (const [event, handlers] of captureListeners) {
89
+ const listener = (ev: Event) =>
90
+ run(
91
+ Effect.forEach(handlers, ([el, handler]) =>
92
+ ev.target === el || el.contains(ev.target as Node)
93
+ ? handler.handler(proxyCurrentTargetForCaptureEvents(ev, el))
94
+ : Effect.unit)
95
+ )
96
+ element.addEventListener(event, listener, getDerivedAddEventListenerOptions(handlers))
97
+ disposables.push(disposable(() => element.removeEventListener(event, listener)))
98
+ }
99
+
100
+ return disposables
101
+ }
102
+
103
+ function setup(rendered: Rendered, scope: Scope.Scope) {
104
+ const hasBubbleListeners = bubbleListeners.size > 0
105
+ const hasCaptureListeners = captureListeners.size > 0
106
+
107
+ if (!hasBubbleListeners && !hasCaptureListeners) {
108
+ return Effect.unit
109
+ }
110
+
111
+ return Effect.flatMap(Effect.runtime<never>(), (runtime) => {
112
+ const elements = getElements(rendered)
113
+ const disposables: Array<Disposable> = []
114
+ const runFork = Runtime.runFork(runtime)
115
+ const run: Run = <E, A>(effect: Effect.Effect<never, E, A>) =>
116
+ runFork(Effect.fromFiberEffect(Effect.forkIn(effect, scope)))
117
+
118
+ for (const element of elements) {
119
+ if (hasBubbleListeners) {
120
+ disposables.push(...setupBubbleListeners(element, run))
121
+ }
122
+ if (hasCaptureListeners) {
123
+ disposables.push(...setupCaptureListeners(element, run))
124
+ }
125
+ }
126
+
127
+ return Scope.addFinalizer(scope, Effect.sync(() => disposables.forEach((d) => d[Symbol.dispose]())))
128
+ })
129
+ }
130
+
131
+ return {
132
+ addEventListener,
133
+ setup
134
+ }
135
+ }
136
+
137
+ const EVENT_PROPERTY_TO_REPLACE = "currentTarget"
138
+
139
+ function proxyCurrentTargetForCaptureEvents<E extends Event>(event: E, currentTarget: Element): E {
140
+ return new Proxy(event, {
141
+ get(target: E, property: string | symbol) {
142
+ return property === EVENT_PROPERTY_TO_REPLACE ? currentTarget : target[property as keyof E]
143
+ }
144
+ })
145
+ }
146
+
147
+ function getDerivedAddEventListenerOptions(entries: Set<Entry>): AddEventListenerOptions {
148
+ const hs = Array.from(entries)
149
+ return {
150
+ once: hs.some((h) => h[1].options?.once === true),
151
+ passive: hs.every((h) => h[1].options?.passive === true)
152
+ }
153
+ }
@@ -1,7 +1,6 @@
1
1
  import { diffable, isComment } from "@typed/wire"
2
2
  import udomdiff from "udomdiff"
3
3
  import type { RenderContext } from "../RenderContext.js"
4
- import type { RenderEvent } from "../RenderEvent.js"
5
4
  import { isRenderEvent } from "../RenderEvent.js"
6
5
  import { NodePartImpl } from "./parts.js"
7
6
  import { findHoleComment, isCommentWithValue } from "./utils.js"
@@ -19,16 +18,20 @@ export function makeRenderNodePart(
19
18
 
20
19
  return new NodePartImpl(index, ({ part, value }) => {
21
20
  return ctx.queue.add(part, () => {
22
- matchValue(value, (content) => {
23
- if (text === undefined) {
24
- text = document.createTextNode("")
21
+ matchValue(
22
+ value,
23
+ (content) => {
24
+ if (text === undefined) {
25
+ text = document.createTextNode("")
26
+ }
27
+ text.textContent = content
28
+
29
+ nodes = diffChildren(comment, nodes, [text], document)
30
+ },
31
+ (updatedNodes) => {
32
+ nodes = diffChildren(comment, nodes, updatedNodes, document)
25
33
  }
26
- text.textContent = content
27
-
28
- nodes = diffChildren(comment, nodes, [text], document)
29
- }, (updatedNodes) => {
30
- nodes = diffChildren(comment, nodes, updatedNodes, document)
31
- })
34
+ )
32
35
  })
33
36
  }, nodes)
34
37
  }
@@ -83,22 +86,12 @@ export function diffChildren(
83
86
  comment.parentNode!,
84
87
  // Document Fragments cannot be removed, so we filter them out
85
88
  currentNodes.filter((x) => x.nodeType !== x.DOCUMENT_FRAGMENT_NODE),
86
- nextNodes.flatMap(flattenRenderEvent),
89
+ nextNodes,
87
90
  diffable(document),
88
91
  comment
89
92
  )
90
93
  }
91
94
 
92
- function flattenRenderEvent(x: Node | RenderEvent): Array<Node> {
93
- if (isRenderEvent(x)) {
94
- const value = x.valueOf()
95
-
96
- return Array.isArray(value) ? value : [value]
97
- } else {
98
- return [x]
99
- }
100
- }
101
-
102
95
  function matchValue<A, B>(value: unknown, onText: (text: string) => A, onNodes: (nodes: Array<Node>) => B): A | B {
103
96
  switch (typeof value) {
104
97
  // primitives are handled as text content
@@ -117,16 +110,24 @@ function matchValue<A, B>(value: unknown, onText: (text: string) => A, onNodes:
117
110
  if (value.length === 0) return onNodes([])
118
111
  // or diffed, if these contains nodes or "wires"
119
112
  else if (value.some((x) => typeof x === "object")) {
120
- return onNodes(
121
- value.flatMap((x) => (x === null ? [] : [isRenderEvent(x) ? x.valueOf() : x]))
122
- )
113
+ return onNodes(value.flatMap(renderEventToArray))
123
114
  } // in all other cases the content is stringified as is
124
115
  else return onText(String(value))
125
116
  } else {
126
- return onNodes([isRenderEvent(value) ? (value.valueOf() as Node) : (value as Node)])
117
+ return onNodes(renderEventToArray(value))
127
118
  }
128
119
  }
129
120
  case "function":
130
121
  return onNodes([])
131
122
  }
132
123
  }
124
+
125
+ function renderEventToArray(x: unknown): Array<Node> {
126
+ if (x === null || x === undefined) return []
127
+ if (isRenderEvent(x)) {
128
+ const value = x.valueOf()
129
+ return Array.isArray(value) ? value : [value]
130
+ }
131
+
132
+ return [x as Node]
133
+ }