@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,417 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+
5
+ import type { EventWithCurrentTarget } from "@typed/dom/EventTarget"
6
+ import { addEventListener } from "@typed/dom/EventTarget"
7
+ import { Filtered } from "@typed/fx/Filtered"
8
+ import * as Fx from "@typed/fx/Fx"
9
+ import { FxEffectBase } from "@typed/fx/internal/protos"
10
+ import * as Versioned from "@typed/fx/Versioned"
11
+ import type { Rendered } from "@typed/wire"
12
+ import { isWire } from "@typed/wire"
13
+ import type { NoSuchElementException } from "effect/Cause"
14
+ import type { DurationInput } from "effect/Duration"
15
+ import * as Effect from "effect/Effect"
16
+ import { pipe } from "effect/Function"
17
+ import * as Scope from "effect/Scope"
18
+ import { adjustTime } from "./internal/utils"
19
+ import { PlaceholderTypeId } from "./Placeholder"
20
+
21
+ import type * as TQS from "typed-query-selector/parser"
22
+
23
+ /**
24
+ * @since 1.0.0
25
+ */
26
+ export interface ElementSource<
27
+ T extends Rendered = Element,
28
+ EventMap extends {} = DefaultEventMap<Rendered.Elements<T>[number]>
29
+ > extends Versioned.Versioned<never, never, never, never, Rendered.Elements<T>, never, never, Rendered.Elements<T>> {
30
+ readonly selector: Selector
31
+
32
+ readonly query: {
33
+ <S extends string, Ev extends {} = DefaultEventMap<ParseSelector<S, Element>>>(
34
+ selector: S
35
+ ): ElementSource<ParseSelector<S, Element>, Ev>
36
+
37
+ <Target extends Rendered, EventMap extends {} = DefaultEventMap<Target>>(
38
+ rendered: Target
39
+ ): ElementSource<Target, EventMap>
40
+ }
41
+
42
+ readonly elements: Filtered<never, never, Rendered.Elements<T>>
43
+
44
+ readonly events: <Type extends keyof EventMap>(
45
+ type: Type,
46
+ options?: AddEventListenerOptions
47
+ ) => Fx.Fx<never, never, EventWithCurrentTarget<Rendered.Elements<T>[number], EventMap[Type]>>
48
+
49
+ readonly dispatchEvent: (event: Event, wait?: DurationInput) => Effect.Effect<never, NoSuchElementException, void>
50
+ }
51
+
52
+ /**
53
+ * @since 1.0.0
54
+ */
55
+ export function ElementSource<T extends Rendered, EventMap extends {} = DefaultEventMap<T>>(
56
+ rootElement: Filtered<never, never, T>
57
+ ): ElementSource<T, EventMap> {
58
+ return new ElementSourceImpl<T, EventMap>(rootElement) as any
59
+ }
60
+
61
+ /**
62
+ * @since 1.0.0
63
+ */
64
+ export type ParseSelector<T extends string, Fallback> = [T] extends [typeof ROOT_CSS_SELECTOR] ? Fallback
65
+ : Fallback extends globalThis.Element ? TQS.ParseSelector<T, Fallback>
66
+ : Fallback
67
+
68
+ /**
69
+ * @since 1.0.0
70
+ */
71
+ export type DefaultEventMap<T> = T extends Window ? WindowEventMap
72
+ : T extends Document ? DocumentEventMap
73
+ : T extends HTMLVideoElement ? HTMLVideoElementEventMap
74
+ : T extends HTMLMediaElement ? HTMLMediaElementEventMap
75
+ : T extends HTMLElement ? HTMLElementEventMap
76
+ : T extends SVGElement ? SVGElementEventMap
77
+ : T extends Element ? ElementEventMap & Readonly<Record<string, Event>>
78
+ : Readonly<Record<string, Event>>
79
+
80
+ /**
81
+ * @since 1.0.0
82
+ */
83
+ export const ROOT_CSS_SELECTOR = `:root` as const
84
+
85
+ type RenderedWithoutArray = Exclude<Rendered, ReadonlyArray<Rendered>>
86
+
87
+ /**
88
+ * @since 1.0.0
89
+ */
90
+ export function getElements<T extends Rendered>(element: T): ReadonlyArray<Element> {
91
+ if (Array.isArray(element)) return element.flatMap(getElements)
92
+ if (isWire(element as RenderedWithoutArray)) {
93
+ return Array.from((element.valueOf() as DocumentFragment).children)
94
+ }
95
+ if (isElement(element as RenderedWithoutArray)) return [element as Element]
96
+ if (isDocumentFragment(element as RenderedWithoutArray)) {
97
+ return Array.from((element as DocumentFragment).children)
98
+ }
99
+
100
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
101
+ if ((element as Node).parentElement) return [(element as Node).parentElement!]
102
+
103
+ return []
104
+ }
105
+
106
+ function findMostSpecificElement<T extends Element>(cssSelectors: ReadonlyArray<string>) {
107
+ return function(element: Rendered): T {
108
+ const elements = getElements(element)
109
+
110
+ for (let i = 0; i < cssSelectors.length; ++i) {
111
+ const cssSelector = dropLast(i, cssSelectors).join(" ")
112
+
113
+ for (let j = 0; j < elements.length; ++j) {
114
+ const node = elements[j].querySelector(cssSelector)
115
+
116
+ if (node) return node as T
117
+ }
118
+ }
119
+
120
+ return element as T
121
+ }
122
+ }
123
+
124
+ function findMatchingElements<El extends Element = Element>(cssSelectors: ReadonlyArray<string>) {
125
+ if (cssSelectors.length === 0) return getElements
126
+
127
+ const cssSelector = cssSelectors.join(" ")
128
+ return function(element: Rendered): ReadonlyArray<El> {
129
+ const elements = getElements(element)
130
+ const nodes = elements.flatMap((element) => Array.from(element.querySelectorAll<El>(cssSelector)))
131
+
132
+ const matchedElements = elements.filter((element) => element.matches(cssSelector)) as Array<El>
133
+
134
+ if (matchedElements.length > 0) return [...matchedElements, ...nodes]
135
+
136
+ return nodes
137
+ }
138
+ }
139
+
140
+ function dropLast(i: number, cssSelectors: ReadonlyArray<string>) {
141
+ return cssSelectors.slice(0, cssSelectors.length - i)
142
+ }
143
+
144
+ function makeEventStream<Ev extends Event>(
145
+ cssSelectors: ReadonlyArray<string>,
146
+ eventName: string,
147
+ options: EventListenerOptions = {}
148
+ ) {
149
+ return function(element: Rendered): Fx.Fx<never, never, Ev> {
150
+ const { capture } = options
151
+ const cssSelector = cssSelectors.join(" ")
152
+ const lastTwoCssSelectors = cssSelectors.slice(-2).join("")
153
+ const elements = getElements(element)
154
+
155
+ const event$ = Fx.merge(
156
+ elements.map((element) =>
157
+ Fx.filter(
158
+ Fx.withScopedFork<never, never, Ev>(({ scope, sink }) =>
159
+ Effect.zipRight(
160
+ Effect.provideService(
161
+ addEventListener(element, {
162
+ eventName,
163
+ handler: (ev) => sink.onSuccess(ev as any as Ev)
164
+ }),
165
+ Scope.Scope,
166
+ scope
167
+ ),
168
+ Effect.never
169
+ )
170
+ ),
171
+ (event: Ev) =>
172
+ ensureMatches(cssSelector, element, event, capture) ||
173
+ ensureMatches(lastTwoCssSelectors, element, event, capture)
174
+ )
175
+ )
176
+ )
177
+
178
+ if (capture) {
179
+ return pipe(event$, Fx.map(findCurrentTarget(cssSelector, element)))
180
+ }
181
+
182
+ return event$
183
+ }
184
+ }
185
+
186
+ function makeElementEventStream<Ev extends Event>(
187
+ currentTarget: Element,
188
+ eventName: string,
189
+ options: EventListenerOptions = {}
190
+ ) {
191
+ return function(rendered: Rendered): Fx.Fx<never, never, Ev> {
192
+ const { capture } = options
193
+ const elements = getElements(rendered)
194
+
195
+ const event$ = Fx.merge(
196
+ elements.map((element) =>
197
+ Fx.filter(
198
+ Fx.withScopedFork<never, never, Ev>(({ scope, sink }) =>
199
+ Effect.zipRight(
200
+ Effect.provideService(
201
+ addEventListener(element, {
202
+ eventName,
203
+ handler: (ev) => sink.onSuccess(ev as any as Ev)
204
+ }),
205
+ Scope.Scope,
206
+ scope
207
+ ),
208
+ Effect.never
209
+ )
210
+ ),
211
+ (event: Ev) => event.target ? currentTarget.contains(event.target as Element) : false
212
+ )
213
+ )
214
+ )
215
+
216
+ if (capture) {
217
+ return event$.pipe(Fx.map((ev) => cloneEvent(ev, currentTarget)))
218
+ }
219
+
220
+ return event$
221
+ }
222
+ }
223
+
224
+ function findCurrentTarget(cssSelector: string, element: Rendered) {
225
+ const elements = getElements(element)
226
+ const length = elements.length
227
+
228
+ return function<E extends Event>(event: E): E {
229
+ for (let i = 0; i < length; ++i) {
230
+ const element = elements[i]
231
+ const isCurrentTarget = !cssSelector || element.matches(cssSelector)
232
+
233
+ if (isCurrentTarget) return cloneEvent(event, element) as E
234
+
235
+ const nodes = element.querySelectorAll(cssSelector)
236
+
237
+ for (let i = 0; i < nodes.length; ++i) {
238
+ const node = nodes[i]
239
+ const containsEventTarget = node.contains(event.target as Element)
240
+
241
+ if (containsEventTarget) return cloneEvent(event, node)
242
+ }
243
+ }
244
+
245
+ return event
246
+ }
247
+ }
248
+
249
+ const EVENT_PROPERTY_TO_REPLACE = "currentTarget"
250
+
251
+ function cloneEvent<E extends Event>(event: E, currentTarget: Element): E {
252
+ return new Proxy(event, {
253
+ get(target: E, property: string | symbol) {
254
+ return property === EVENT_PROPERTY_TO_REPLACE ? currentTarget : target[property as keyof E]
255
+ }
256
+ })
257
+ }
258
+
259
+ function ensureMatches(cssSelector: string, element: Element, ev: Event, capture = false): boolean {
260
+ let target = ev.target as Element
261
+
262
+ if (!cssSelector) return (capture && element.contains(target)) || target === element
263
+
264
+ for (; target && target !== element; target = target.parentElement as Element) {
265
+ if (target.matches(cssSelector)) return true
266
+ }
267
+
268
+ return element.matches(cssSelector)
269
+ }
270
+
271
+ function isDocumentFragment(element: RenderedWithoutArray): element is DocumentFragment {
272
+ return element.nodeType === element.DOCUMENT_FRAGMENT_NODE
273
+ }
274
+
275
+ function isElement(element: RenderedWithoutArray): element is Element {
276
+ return element.nodeType === element.ELEMENT_NODE
277
+ }
278
+
279
+ /**
280
+ * @internal
281
+ * @since 1.0.0
282
+ */
283
+ // @ts-expect-error
284
+ export class ElementSourceImpl<
285
+ T extends Rendered,
286
+ EventMap extends {} = DefaultEventMap<Rendered.Elements<T>[number]>
287
+ > extends FxEffectBase<never, never, Rendered.Elements<T>, never, NoSuchElementException, Rendered.Elements<T>>
288
+ implements Omit<ElementSource<T, EventMap>, PlaceholderTypeId>
289
+ {
290
+ readonly [PlaceholderTypeId]!: any
291
+
292
+ private bubbleMap = new Map<any, Fx.Fx<never, never, any>>()
293
+ private captureMap = new Map<any, Fx.Fx<never, never, any>>()
294
+
295
+ readonly elements: ElementSource<T, EventMap>["elements"]
296
+ readonly version: ElementSource<T, EventMap>["version"]
297
+
298
+ constructor(readonly rootElement: Filtered<never, never, T>, readonly selector: Selector = CssSelectors([])) {
299
+ super()
300
+ this.query = this.query.bind(this)
301
+ this.events = this.events.bind(this)
302
+
303
+ this.elements = this.selector._tag === "css" ?
304
+ this.rootElement.map(findMatchingElements<any>(this.selector.selectors)) :
305
+ Filtered(Versioned.of(this.selector.element), (x) => Effect.succeedSome([x])) as any
306
+
307
+ this.version = this.elements.version
308
+ }
309
+
310
+ static fromElement<T extends Rendered>(rootElement: T): ElementSource<T> {
311
+ return new ElementSourceImpl(Filtered(Versioned.of(rootElement), Effect.succeedSome)) as any
312
+ }
313
+
314
+ protected toEffect(): Effect.Effect<never, NoSuchElementException, Rendered.Elements<T>> {
315
+ return this.elements
316
+ }
317
+
318
+ protected toFx() {
319
+ return this.elements
320
+ }
321
+
322
+ query<S extends string, Ev extends {} = DefaultEventMap<ParseSelector<S, Element>>>(
323
+ selector: S
324
+ ): ElementSource<ParseSelector<S, Element>, Ev> {
325
+ if (selector === ROOT_CSS_SELECTOR) {
326
+ return this as any
327
+ } else if (typeof selector === "string") {
328
+ if (this.selector._tag === "css") {
329
+ return new ElementSourceImpl(this.rootElement, CssSelectors([...this.selector.selectors, selector])) as any
330
+ } else {
331
+ return ElementSourceImpl.fromElement(this.selector.element).query(selector) as any
332
+ }
333
+ } else {
334
+ return new ElementSourceImpl(this.rootElement, ElementSelector(selector)) as any
335
+ }
336
+ }
337
+
338
+ events<Type extends keyof EventMap>(
339
+ type: Type,
340
+ options?: AddEventListenerOptions
341
+ ) {
342
+ const capture = options?.capture === true
343
+ const map = capture ? this.captureMap : this.bubbleMap
344
+
345
+ let current = map.get(type)
346
+
347
+ if (current === undefined) {
348
+ if (this.selector._tag === "css") {
349
+ current = this.rootElement.map(findMostSpecificElement(this.selector.selectors)).pipe(
350
+ Fx.switchMap(makeEventStream(this.selector.selectors, type as any, options)),
351
+ Fx.multicast
352
+ )
353
+ } else {
354
+ current = this.rootElement.pipe(
355
+ Fx.switchMap(makeElementEventStream(this.selector.element, type as string, options)),
356
+ Fx.multicast
357
+ )
358
+ }
359
+
360
+ map.set(type, current)
361
+ }
362
+
363
+ return current
364
+ }
365
+
366
+ dispatchEvent(event: Event, wait?: DurationInput) {
367
+ return Effect.zipRight(
368
+ Effect.flatMap(
369
+ this.elements,
370
+ (elements) => Effect.sync(() => elements.length > 0 ? elements[0].dispatchEvent(event) : null)
371
+ ),
372
+ // Allow time to move forward
373
+ adjustTime(wait)
374
+ )
375
+ }
376
+ }
377
+
378
+ /**
379
+ * @since 1.0.0
380
+ */
381
+ export type Selector = CssSelectors | ElementSelector
382
+
383
+ /**
384
+ * @since 1.0.0
385
+ */
386
+ export interface CssSelectors {
387
+ readonly _tag: "css"
388
+ readonly selectors: ReadonlyArray<string>
389
+ }
390
+
391
+ /**
392
+ * @since 1.0.0
393
+ */
394
+ export function CssSelectors(selectors: ReadonlyArray<string>): CssSelectors {
395
+ return {
396
+ _tag: "css",
397
+ selectors
398
+ }
399
+ }
400
+
401
+ /**
402
+ * @since 1.0.0
403
+ */
404
+ export interface ElementSelector {
405
+ readonly _tag: "element"
406
+ readonly element: Element
407
+ }
408
+
409
+ /**
410
+ * @since 1.0.0
411
+ */
412
+ export function ElementSelector(element: Element): ElementSelector {
413
+ return {
414
+ _tag: "element",
415
+ element
416
+ }
417
+ }
package/src/Entry.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import type { HtmlChunk } from "./HtmlChunk"
5
+ import type { Template } from "./Template"
6
+
7
+ /**
8
+ * @since 1.0.0
9
+ */
10
+ export type Entry = BrowserEntry | ServerEntry
11
+
12
+ /**
13
+ * @since 1.0.0
14
+ */
15
+ export interface BrowserEntry {
16
+ readonly _tag: "Browser"
17
+ readonly template: Template
18
+ readonly content: DocumentFragment
19
+ }
20
+
21
+ /**
22
+ * @since 1.0.0
23
+ */
24
+ export interface ServerEntry {
25
+ readonly _tag: "Server"
26
+ readonly template: Template
27
+ readonly chunks: ReadonlyArray<HtmlChunk>
28
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import { type EventWithTarget, isUsingKeyModifier } from "@typed/dom/EventTarget"
5
+ import { type Effect, unit } from "effect/Effect"
6
+ import type { Placeholder } from "./Placeholder"
7
+
8
+ /**
9
+ * @since 1.0.0
10
+ */
11
+ export const EventHandlerTypeId = Symbol.for("./EventHandler")
12
+ /**
13
+ * @since 1.0.0
14
+ */
15
+ export type EventHandlerTypeId = typeof EventHandlerTypeId
16
+
17
+ /**
18
+ * @since 1.0.0
19
+ */
20
+ export interface EventHandler<R, E, Ev extends Event = Event> extends Placeholder<R, E, null> {
21
+ readonly [EventHandlerTypeId]: EventHandlerTypeId
22
+ readonly handler: (event: Ev) => Effect<R, E, unknown>
23
+ readonly options: AddEventListenerOptions | undefined
24
+ }
25
+
26
+ /**
27
+ * @since 1.0.0
28
+ */
29
+ export type Context<T> = T extends EventHandler<infer R, infer _E, infer _Ev> ? R : never
30
+ /**
31
+ * @since 1.0.0
32
+ */
33
+ export type Error<T> = T extends EventHandler<infer _R, infer E, infer _Ev> ? E : never
34
+ /**
35
+ * @since 1.0.0
36
+ */
37
+ export type EventOf<T> = T extends EventHandler<infer _R, infer _E, infer Ev> ? Ev : never
38
+
39
+ /**
40
+ * @since 1.0.0
41
+ */
42
+ export function make<R, E, Ev extends Event>(
43
+ handler: (event: Ev) => Effect<R, E, unknown>,
44
+ options?: AddEventListenerOptions
45
+ ): EventHandler<R, E, Ev> {
46
+ return {
47
+ [EventHandlerTypeId]: EventHandlerTypeId,
48
+ handler,
49
+ options
50
+ } as any
51
+ }
52
+
53
+ /**
54
+ * @since 1.0.0
55
+ */
56
+ export function preventDefault<R, E, Ev extends Event>(
57
+ handler: (event: Ev) => Effect<R, E, unknown>,
58
+ options?: AddEventListenerOptions
59
+ ): EventHandler<R, E, Ev> {
60
+ return make((ev) => (ev.preventDefault(), handler(ev)), options)
61
+ }
62
+
63
+ /**
64
+ * @since 1.0.0
65
+ */
66
+ export function stopPropagation<R, E, Ev extends Event>(
67
+ handler: (event: Ev) => Effect<R, E, unknown>,
68
+ options?: AddEventListenerOptions
69
+ ): EventHandler<R, E, Ev> {
70
+ return make((ev) => (ev.stopPropagation(), handler(ev)), options)
71
+ }
72
+
73
+ /**
74
+ * @since 1.0.0
75
+ */
76
+ export function stopImmediatePropagation<R, E, Ev extends Event>(
77
+ handler: (event: Ev) => Effect<R, E, unknown>,
78
+ options?: AddEventListenerOptions
79
+ ): EventHandler<R, E, Ev> {
80
+ return make((ev) => (ev.stopImmediatePropagation(), handler(ev)), options)
81
+ }
82
+
83
+ /**
84
+ * @since 1.0.0
85
+ */
86
+ export function target<T extends HTMLElement>() {
87
+ return <R, E, Ev extends Event>(
88
+ handler: (event: EventWithTarget<T, Ev>) => Effect<R, E, unknown>,
89
+ options?: AddEventListenerOptions
90
+ ): EventHandler<R, E, EventWithTarget<T, Ev>> => {
91
+ return make(handler, options)
92
+ }
93
+ }
94
+
95
+ /**
96
+ * @since 1.0.0
97
+ */
98
+ export function keys<Keys extends ReadonlyArray<string>>(...keys: Keys) {
99
+ return <R, E>(
100
+ handler: (event: KeyboardEvent & { key: Keys[number] }) => Effect<R, E, unknown>,
101
+ options?: AddEventListenerOptions
102
+ ): EventHandler<R, E, KeyboardEvent> =>
103
+ make((ev: KeyboardEvent) => !isUsingKeyModifier(ev) && keys.includes(ev.key) ? handler(ev as any) : unit, options)
104
+ }