@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.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/cjs/Directive.js +76 -0
- package/dist/cjs/Directive.js.map +1 -0
- package/dist/cjs/ElementRef.js +83 -0
- package/dist/cjs/ElementRef.js.map +1 -0
- package/dist/cjs/ElementSource.js +244 -0
- package/dist/cjs/ElementSource.js.map +1 -0
- package/dist/cjs/Entry.js +6 -0
- package/dist/cjs/Entry.js.map +1 -0
- package/dist/cjs/EventHandler.js +65 -0
- package/dist/cjs/EventHandler.js.map +1 -0
- package/dist/cjs/Html.js +169 -0
- package/dist/cjs/Html.js.map +1 -0
- package/dist/cjs/HtmlChunk.js +257 -0
- package/dist/cjs/HtmlChunk.js.map +1 -0
- package/dist/cjs/Hydrate.js +49 -0
- package/dist/cjs/Hydrate.js.map +1 -0
- package/dist/cjs/Many.js +45 -0
- package/dist/cjs/Many.js.map +1 -0
- package/dist/cjs/Meta.js +37 -0
- package/dist/cjs/Meta.js.map +1 -0
- package/dist/cjs/Parser.js +331 -0
- package/dist/cjs/Parser.js.map +1 -0
- package/dist/cjs/Part.js +6 -0
- package/dist/cjs/Part.js.map +1 -0
- package/dist/cjs/Placeholder.js +38 -0
- package/dist/cjs/Placeholder.js.map +1 -0
- package/dist/cjs/Platform.js +64 -0
- package/dist/cjs/Platform.js.map +1 -0
- package/dist/cjs/Render.js +39 -0
- package/dist/cjs/Render.js.map +1 -0
- package/dist/cjs/RenderContext.js +130 -0
- package/dist/cjs/RenderContext.js.map +1 -0
- package/dist/cjs/RenderEvent.js +44 -0
- package/dist/cjs/RenderEvent.js.map +1 -0
- package/dist/cjs/RenderTemplate.js +41 -0
- package/dist/cjs/RenderTemplate.js.map +1 -0
- package/dist/cjs/Renderable.js +6 -0
- package/dist/cjs/Renderable.js.map +1 -0
- package/dist/cjs/Template.js +266 -0
- package/dist/cjs/Template.js.map +1 -0
- package/dist/cjs/TemplateInstance.js +51 -0
- package/dist/cjs/TemplateInstance.js.map +1 -0
- package/dist/cjs/Test.js +90 -0
- package/dist/cjs/Test.js.map +1 -0
- package/dist/cjs/Token.js +270 -0
- package/dist/cjs/Token.js.map +1 -0
- package/dist/cjs/Tokenizer.js +18 -0
- package/dist/cjs/Tokenizer.js.map +1 -0
- package/dist/cjs/Vitest.js +44 -0
- package/dist/cjs/Vitest.js.map +1 -0
- package/dist/cjs/index.js +147 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/internal/HydrateContext.js +13 -0
- package/dist/cjs/internal/HydrateContext.js.map +1 -0
- package/dist/cjs/internal/browser.js +109 -0
- package/dist/cjs/internal/browser.js.map +1 -0
- package/dist/cjs/internal/chunks.js +54 -0
- package/dist/cjs/internal/chunks.js.map +1 -0
- package/dist/cjs/internal/errors.js +23 -0
- package/dist/cjs/internal/errors.js.map +1 -0
- package/dist/cjs/internal/hydrate.js +197 -0
- package/dist/cjs/internal/hydrate.js.map +1 -0
- package/dist/cjs/internal/indexRefCounter.js +32 -0
- package/dist/cjs/internal/indexRefCounter.js.map +1 -0
- package/dist/cjs/internal/module-augmentation.js +6 -0
- package/dist/cjs/internal/module-augmentation.js.map +1 -0
- package/dist/cjs/internal/parser.js +492 -0
- package/dist/cjs/internal/parser.js.map +1 -0
- package/dist/cjs/internal/parts.js +350 -0
- package/dist/cjs/internal/parts.js.map +1 -0
- package/dist/cjs/internal/readAttribute.js +34 -0
- package/dist/cjs/internal/readAttribute.js.map +1 -0
- package/dist/cjs/internal/render.js +332 -0
- package/dist/cjs/internal/render.js.map +1 -0
- package/dist/cjs/internal/server.js +219 -0
- package/dist/cjs/internal/server.js.map +1 -0
- package/dist/cjs/internal/tokenizer.js +264 -0
- package/dist/cjs/internal/tokenizer.js.map +1 -0
- package/dist/cjs/internal/utils.js +68 -0
- package/dist/cjs/internal/utils.js.map +1 -0
- package/dist/dts/Directive.d.ts +70 -0
- package/dist/dts/Directive.d.ts.map +1 -0
- package/dist/dts/ElementRef.d.ts +40 -0
- package/dist/dts/ElementRef.d.ts.map +1 -0
- package/dist/dts/ElementSource.d.ts +72 -0
- package/dist/dts/ElementSource.d.ts.map +1 -0
- package/dist/dts/Entry.d.ts +26 -0
- package/dist/dts/Entry.d.ts.map +1 -0
- package/dist/dts/EventHandler.d.ts +61 -0
- package/dist/dts/EventHandler.d.ts.map +1 -0
- package/dist/dts/Html.d.ts +17 -0
- package/dist/dts/Html.d.ts.map +1 -0
- package/dist/dts/HtmlChunk.d.ts +56 -0
- package/dist/dts/HtmlChunk.d.ts.map +1 -0
- package/dist/dts/Hydrate.d.ts +20 -0
- package/dist/dts/Hydrate.d.ts.map +1 -0
- package/dist/dts/Many.d.ts +32 -0
- package/dist/dts/Many.d.ts.map +1 -0
- package/dist/dts/Meta.d.ts +24 -0
- package/dist/dts/Meta.d.ts.map +1 -0
- package/dist/dts/Parser.d.ts +16 -0
- package/dist/dts/Parser.d.ts.map +1 -0
- package/dist/dts/Part.d.ts +147 -0
- package/dist/dts/Part.d.ts.map +1 -0
- package/dist/dts/Placeholder.d.ts +51 -0
- package/dist/dts/Placeholder.d.ts.map +1 -0
- package/dist/dts/Platform.d.ts +23 -0
- package/dist/dts/Platform.d.ts.map +1 -0
- package/dist/dts/Render.d.ts +23 -0
- package/dist/dts/Render.d.ts.map +1 -0
- package/dist/dts/RenderContext.d.ts +88 -0
- package/dist/dts/RenderContext.d.ts.map +1 -0
- package/dist/dts/RenderEvent.d.ts +37 -0
- package/dist/dts/RenderEvent.d.ts.map +1 -0
- package/dist/dts/RenderTemplate.d.ts +38 -0
- package/dist/dts/RenderTemplate.d.ts.map +1 -0
- package/dist/dts/Renderable.d.ts +28 -0
- package/dist/dts/Renderable.d.ts.map +1 -0
- package/dist/dts/Template.d.ts +218 -0
- package/dist/dts/Template.d.ts.map +1 -0
- package/dist/dts/TemplateInstance.d.ts +32 -0
- package/dist/dts/TemplateInstance.d.ts.map +1 -0
- package/dist/dts/Test.d.ts +58 -0
- package/dist/dts/Test.d.ts.map +1 -0
- package/dist/dts/Token.d.ts +202 -0
- package/dist/dts/Token.d.ts.map +1 -0
- package/dist/dts/Tokenizer.d.ts +6 -0
- package/dist/dts/Tokenizer.d.ts.map +1 -0
- package/dist/dts/Vitest.d.ts +28 -0
- package/dist/dts/Vitest.d.ts.map +1 -0
- package/dist/dts/index.d.ts +65 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/internal/HydrateContext.d.ts +2 -0
- package/dist/dts/internal/HydrateContext.d.ts.map +1 -0
- package/dist/dts/internal/browser.d.ts +8 -0
- package/dist/dts/internal/browser.d.ts.map +1 -0
- package/dist/dts/internal/chunks.d.ts +22 -0
- package/dist/dts/internal/chunks.d.ts.map +1 -0
- package/dist/dts/internal/errors.d.ts +9 -0
- package/dist/dts/internal/errors.d.ts.map +1 -0
- package/dist/dts/internal/hydrate.d.ts +37 -0
- package/dist/dts/internal/hydrate.d.ts.map +1 -0
- package/dist/dts/internal/indexRefCounter.d.ts +6 -0
- package/dist/dts/internal/indexRefCounter.d.ts.map +1 -0
- package/dist/dts/internal/module-augmentation.d.ts +36 -0
- package/dist/dts/internal/module-augmentation.d.ts.map +1 -0
- package/dist/dts/internal/parser.d.ts +12 -0
- package/dist/dts/internal/parser.d.ts.map +1 -0
- package/dist/dts/internal/parts.d.ts +304 -0
- package/dist/dts/internal/parts.d.ts.map +1 -0
- package/dist/dts/internal/readAttribute.d.ts +9 -0
- package/dist/dts/internal/readAttribute.d.ts.map +1 -0
- package/dist/dts/internal/render.d.ts +30 -0
- package/dist/dts/internal/render.d.ts.map +1 -0
- package/dist/dts/internal/server.d.ts +31 -0
- package/dist/dts/internal/server.d.ts.map +1 -0
- package/dist/dts/internal/tokenizer.d.ts +3 -0
- package/dist/dts/internal/tokenizer.d.ts.map +1 -0
- package/dist/dts/internal/utils.d.ts +15 -0
- package/dist/dts/internal/utils.d.ts.map +1 -0
- package/dist/esm/Directive.js +64 -0
- package/dist/esm/Directive.js.map +1 -0
- package/dist/esm/ElementRef.js +72 -0
- package/dist/esm/ElementRef.js.map +1 -0
- package/dist/esm/ElementSource.js +237 -0
- package/dist/esm/ElementSource.js.map +1 -0
- package/dist/esm/Entry.js +2 -0
- package/dist/esm/Entry.js.map +1 -0
- package/dist/esm/EventHandler.js +52 -0
- package/dist/esm/EventHandler.js.map +1 -0
- package/dist/esm/Html.js +167 -0
- package/dist/esm/Html.js.map +1 -0
- package/dist/esm/HtmlChunk.js +274 -0
- package/dist/esm/HtmlChunk.js.map +1 -0
- package/dist/esm/Hydrate.js +37 -0
- package/dist/esm/Hydrate.js.map +1 -0
- package/dist/esm/Many.js +33 -0
- package/dist/esm/Many.js.map +1 -0
- package/dist/esm/Meta.js +29 -0
- package/dist/esm/Meta.js.map +1 -0
- package/dist/esm/Parser.js +342 -0
- package/dist/esm/Parser.js.map +1 -0
- package/dist/esm/Part.js +5 -0
- package/dist/esm/Part.js.map +1 -0
- package/dist/esm/Placeholder.js +30 -0
- package/dist/esm/Placeholder.js.map +1 -0
- package/dist/esm/Platform.js +41 -0
- package/dist/esm/Platform.js.map +1 -0
- package/dist/esm/Render.js +27 -0
- package/dist/esm/Render.js.map +1 -0
- package/dist/esm/RenderContext.js +113 -0
- package/dist/esm/RenderContext.js.map +1 -0
- package/dist/esm/RenderEvent.js +36 -0
- package/dist/esm/RenderEvent.js.map +1 -0
- package/dist/esm/RenderTemplate.js +26 -0
- package/dist/esm/RenderTemplate.js.map +1 -0
- package/dist/esm/Renderable.js +2 -0
- package/dist/esm/Renderable.js.map +1 -0
- package/dist/esm/Template.js +239 -0
- package/dist/esm/Template.js.map +1 -0
- package/dist/esm/TemplateInstance.js +43 -0
- package/dist/esm/TemplateInstance.js.map +1 -0
- package/dist/esm/Test.js +68 -0
- package/dist/esm/Test.js.map +1 -0
- package/dist/esm/Token.js +264 -0
- package/dist/esm/Token.js.map +1 -0
- package/dist/esm/Tokenizer.js +9 -0
- package/dist/esm/Tokenizer.js.map +1 -0
- package/dist/esm/Vitest.js +29 -0
- package/dist/esm/Vitest.js.map +1 -0
- package/dist/esm/index.js +65 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/HydrateContext.js +7 -0
- package/dist/esm/internal/HydrateContext.js.map +1 -0
- package/dist/esm/internal/browser.js +102 -0
- package/dist/esm/internal/browser.js.map +1 -0
- package/dist/esm/internal/chunks.js +47 -0
- package/dist/esm/internal/chunks.js.map +1 -0
- package/dist/esm/internal/errors.js +15 -0
- package/dist/esm/internal/errors.js.map +1 -0
- package/dist/esm/internal/hydrate.js +165 -0
- package/dist/esm/internal/hydrate.js.map +1 -0
- package/dist/esm/internal/indexRefCounter.js +24 -0
- package/dist/esm/internal/indexRefCounter.js.map +1 -0
- package/dist/esm/internal/module-augmentation.js +2 -0
- package/dist/esm/internal/module-augmentation.js.map +1 -0
- package/dist/esm/internal/parser.js +493 -0
- package/dist/esm/internal/parser.js.map +1 -0
- package/dist/esm/internal/parts.js +291 -0
- package/dist/esm/internal/parts.js.map +1 -0
- package/dist/esm/internal/readAttribute.js +24 -0
- package/dist/esm/internal/readAttribute.js.map +1 -0
- package/dist/esm/internal/render.js +329 -0
- package/dist/esm/internal/render.js.map +1 -0
- package/dist/esm/internal/server.js +174 -0
- package/dist/esm/internal/server.js.map +1 -0
- package/dist/esm/internal/tokenizer.js +296 -0
- package/dist/esm/internal/tokenizer.js.map +1 -0
- package/dist/esm/internal/utils.js +52 -0
- package/dist/esm/internal/utils.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/package.json +242 -0
- package/src/Directive.ts +114 -0
- package/src/ElementRef.ts +123 -0
- package/src/ElementSource.ts +417 -0
- package/src/Entry.ts +28 -0
- package/src/EventHandler.ts +104 -0
- package/src/Html.ts +258 -0
- package/src/HtmlChunk.ts +346 -0
- package/src/Hydrate.ts +53 -0
- package/src/Many.ts +128 -0
- package/src/Meta.ts +32 -0
- package/src/Parser.ts +457 -0
- package/src/Part.ts +186 -0
- package/src/Placeholder.ts +70 -0
- package/src/Platform.ts +71 -0
- package/src/Render.ts +45 -0
- package/src/RenderContext.ts +221 -0
- package/src/RenderEvent.ts +67 -0
- package/src/RenderTemplate.ts +88 -0
- package/src/Renderable.ts +34 -0
- package/src/Template.ts +284 -0
- package/src/TemplateInstance.ts +83 -0
- package/src/Test.ts +151 -0
- package/src/Token.ts +269 -0
- package/src/Tokenizer.ts +10 -0
- package/src/Vitest.ts +61 -0
- package/src/index.ts +66 -0
- package/src/internal/HydrateContext.ts +23 -0
- package/src/internal/browser.ts +132 -0
- package/src/internal/chunks.ts +73 -0
- package/src/internal/errors.ts +11 -0
- package/src/internal/external.d.ts +11 -0
- package/src/internal/hydrate.ts +262 -0
- package/src/internal/indexRefCounter.ts +33 -0
- package/src/internal/module-augmentation.ts +48 -0
- package/src/internal/parser.ts +637 -0
- package/src/internal/parts.ts +527 -0
- package/src/internal/readAttribute.ts +28 -0
- package/src/internal/render.ts +529 -0
- package/src/internal/server.ts +293 -0
- package/src/internal/tokenizer.ts +338 -0
- 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
|
+
}
|