@typed/template 0.2.0 → 0.3.1
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/dist/cjs/Directive.js +1 -1
- package/dist/cjs/Directive.js.map +1 -1
- package/dist/cjs/ElementRef.js +23 -13
- package/dist/cjs/ElementRef.js.map +1 -1
- package/dist/cjs/ElementSource.js +16 -18
- package/dist/cjs/ElementSource.js.map +1 -1
- package/dist/cjs/EventHandler.js +1 -1
- package/dist/cjs/EventHandler.js.map +1 -1
- package/dist/cjs/Html.js +31 -32
- package/dist/cjs/Html.js.map +1 -1
- package/dist/cjs/HtmlChunk.js +4 -1
- package/dist/cjs/HtmlChunk.js.map +1 -1
- package/dist/cjs/Hydrate.js +1 -1
- package/dist/cjs/Hydrate.js.map +1 -1
- package/dist/cjs/Many.js +14 -13
- package/dist/cjs/Many.js.map +1 -1
- package/dist/cjs/Parser.js +11 -323
- package/dist/cjs/Parser.js.map +1 -1
- package/dist/cjs/Placeholder.js +3 -3
- package/dist/cjs/Placeholder.js.map +1 -1
- package/dist/cjs/Platform.js +4 -4
- package/dist/cjs/Platform.js.map +1 -1
- package/dist/cjs/Render.js +1 -1
- package/dist/cjs/Render.js.map +1 -1
- package/dist/cjs/RenderContext.js +48 -27
- package/dist/cjs/RenderContext.js.map +1 -1
- package/dist/cjs/RenderTemplate.js +2 -17
- package/dist/cjs/RenderTemplate.js.map +1 -1
- package/dist/cjs/Template.js +27 -1
- package/dist/cjs/Template.js.map +1 -1
- package/dist/cjs/TemplateInstance.js +2 -2
- package/dist/cjs/TemplateInstance.js.map +1 -1
- package/dist/cjs/Test.js +20 -7
- package/dist/cjs/Test.js.map +1 -1
- package/dist/cjs/index.js +0 -12
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/EventSource.js +95 -0
- package/dist/cjs/internal/EventSource.js.map +1 -0
- package/dist/cjs/internal/browser.js +11 -11
- package/dist/cjs/internal/browser.js.map +1 -1
- package/dist/cjs/internal/chunks.js +4 -1
- package/dist/cjs/internal/chunks.js.map +1 -1
- package/dist/cjs/internal/errors.js +4 -0
- package/dist/cjs/internal/errors.js.map +1 -1
- package/dist/cjs/internal/hydrate.js +113 -80
- package/dist/cjs/internal/hydrate.js.map +1 -1
- package/dist/cjs/internal/indexRefCounter.js +49 -2
- package/dist/cjs/internal/indexRefCounter.js.map +1 -1
- package/dist/cjs/internal/parser.js +72 -21
- package/dist/cjs/internal/parser.js.map +1 -1
- package/dist/cjs/internal/parts.js +128 -28
- package/dist/cjs/internal/parts.js.map +1 -1
- package/dist/cjs/internal/render.js +460 -161
- package/dist/cjs/internal/render.js.map +1 -1
- package/dist/cjs/internal/server.js +5 -2
- package/dist/cjs/internal/server.js.map +1 -1
- package/dist/dts/Directive.d.ts.map +1 -1
- package/dist/dts/ElementRef.d.ts +4 -2
- package/dist/dts/ElementRef.d.ts.map +1 -1
- package/dist/dts/ElementSource.d.ts +10 -5
- package/dist/dts/ElementSource.d.ts.map +1 -1
- package/dist/dts/EventHandler.d.ts.map +1 -1
- package/dist/dts/Html.d.ts +1 -1
- package/dist/dts/Html.d.ts.map +1 -1
- package/dist/dts/HtmlChunk.d.ts.map +1 -1
- package/dist/dts/Many.d.ts +13 -11
- package/dist/dts/Many.d.ts.map +1 -1
- package/dist/dts/Parser.d.ts +3 -6
- package/dist/dts/Parser.d.ts.map +1 -1
- package/dist/dts/Part.d.ts +13 -3
- package/dist/dts/Part.d.ts.map +1 -1
- package/dist/dts/Placeholder.d.ts +2 -2
- package/dist/dts/Placeholder.d.ts.map +1 -1
- package/dist/dts/Render.d.ts +2 -1
- package/dist/dts/Render.d.ts.map +1 -1
- package/dist/dts/RenderContext.d.ts +5 -4
- package/dist/dts/RenderContext.d.ts.map +1 -1
- package/dist/dts/RenderTemplate.d.ts +2 -16
- package/dist/dts/RenderTemplate.d.ts.map +1 -1
- package/dist/dts/Renderable.d.ts +2 -2
- package/dist/dts/Renderable.d.ts.map +1 -1
- package/dist/dts/Template.d.ts +21 -3
- package/dist/dts/Template.d.ts.map +1 -1
- package/dist/dts/TemplateInstance.d.ts +3 -2
- package/dist/dts/TemplateInstance.d.ts.map +1 -1
- package/dist/dts/Test.d.ts +4 -6
- package/dist/dts/Test.d.ts.map +1 -1
- package/dist/dts/index.d.ts +0 -4
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/EventSource.d.ts +12 -0
- package/dist/dts/internal/EventSource.d.ts.map +1 -0
- package/dist/dts/internal/browser.d.ts.map +1 -1
- package/dist/dts/internal/chunks.d.ts +1 -0
- package/dist/dts/internal/chunks.d.ts.map +1 -1
- package/dist/dts/internal/errors.d.ts +1 -0
- package/dist/dts/internal/errors.d.ts.map +1 -1
- package/dist/dts/internal/hydrate.d.ts +9 -16
- package/dist/dts/internal/hydrate.d.ts.map +1 -1
- package/dist/dts/internal/indexRefCounter.d.ts +5 -0
- package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
- package/dist/dts/internal/module-augmentation.d.ts +0 -4
- package/dist/dts/internal/module-augmentation.d.ts.map +1 -1
- package/dist/dts/internal/parser.d.ts +8 -0
- package/dist/dts/internal/parser.d.ts.map +1 -1
- package/dist/dts/internal/parts.d.ts +66 -56
- package/dist/dts/internal/parts.d.ts.map +1 -1
- package/dist/dts/internal/render.d.ts +1 -15
- package/dist/dts/internal/render.d.ts.map +1 -1
- package/dist/dts/internal/server.d.ts.map +1 -1
- package/dist/esm/Directive.js +1 -1
- package/dist/esm/Directive.js.map +1 -1
- package/dist/esm/ElementRef.js +12 -7
- package/dist/esm/ElementRef.js.map +1 -1
- package/dist/esm/ElementSource.js +16 -13
- package/dist/esm/ElementSource.js.map +1 -1
- package/dist/esm/EventHandler.js +1 -1
- package/dist/esm/EventHandler.js.map +1 -1
- package/dist/esm/Html.js +29 -24
- package/dist/esm/Html.js.map +1 -1
- package/dist/esm/HtmlChunk.js +6 -1
- package/dist/esm/HtmlChunk.js.map +1 -1
- package/dist/esm/Hydrate.js +1 -1
- package/dist/esm/Hydrate.js.map +1 -1
- package/dist/esm/Many.js +14 -10
- package/dist/esm/Many.js.map +1 -1
- package/dist/esm/Parser.js +6 -335
- package/dist/esm/Parser.js.map +1 -1
- package/dist/esm/Placeholder.js +2 -2
- package/dist/esm/Placeholder.js.map +1 -1
- package/dist/esm/Platform.js +2 -2
- package/dist/esm/Platform.js.map +1 -1
- package/dist/esm/Render.js +1 -1
- package/dist/esm/Render.js.map +1 -1
- package/dist/esm/RenderContext.js +38 -17
- package/dist/esm/RenderContext.js.map +1 -1
- package/dist/esm/RenderTemplate.js +2 -12
- package/dist/esm/RenderTemplate.js.map +1 -1
- package/dist/esm/Template.js +24 -0
- package/dist/esm/Template.js.map +1 -1
- package/dist/esm/TemplateInstance.js +2 -2
- package/dist/esm/TemplateInstance.js.map +1 -1
- package/dist/esm/Test.js +20 -7
- package/dist/esm/Test.js.map +1 -1
- package/dist/esm/index.js +0 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/EventSource.js +91 -0
- package/dist/esm/internal/EventSource.js.map +1 -0
- package/dist/esm/internal/browser.js +12 -12
- package/dist/esm/internal/browser.js.map +1 -1
- package/dist/esm/internal/chunks.js +2 -0
- package/dist/esm/internal/chunks.js.map +1 -1
- package/dist/esm/internal/errors.js +3 -0
- package/dist/esm/internal/errors.js.map +1 -1
- package/dist/esm/internal/hydrate.js +113 -76
- package/dist/esm/internal/hydrate.js.map +1 -1
- package/dist/esm/internal/indexRefCounter.js +50 -2
- package/dist/esm/internal/indexRefCounter.js.map +1 -1
- package/dist/esm/internal/parser.js +98 -22
- package/dist/esm/internal/parser.js.map +1 -1
- package/dist/esm/internal/parts.js +110 -27
- package/dist/esm/internal/parts.js.map +1 -1
- package/dist/esm/internal/render.js +446 -157
- package/dist/esm/internal/render.js.map +1 -1
- package/dist/esm/internal/server.js +5 -4
- package/dist/esm/internal/server.js.map +1 -1
- package/package.json +10 -26
- package/src/Directive.ts +1 -1
- package/src/ElementRef.ts +18 -14
- package/src/ElementSource.ts +62 -47
- package/src/EventHandler.ts +1 -1
- package/src/Html.ts +58 -57
- package/src/HtmlChunk.ts +15 -1
- package/src/Hydrate.ts +1 -1
- package/src/Many.ts +53 -43
- package/src/Parser.ts +10 -453
- package/src/Part.ts +15 -3
- package/src/Placeholder.ts +4 -4
- package/src/Platform.ts +2 -2
- package/src/Render.ts +7 -2
- package/src/RenderContext.ts +47 -19
- package/src/RenderTemplate.ts +9 -54
- package/src/Renderable.ts +2 -1
- package/src/Template.ts +26 -0
- package/src/TemplateInstance.ts +9 -9
- package/src/Test.ts +40 -21
- package/src/index.ts +0 -4
- package/src/internal/EventSource.ts +153 -0
- package/src/internal/browser.ts +26 -25
- package/src/internal/chunks.ts +4 -0
- package/src/internal/errors.ts +4 -0
- package/src/internal/hydrate.ts +161 -107
- package/src/internal/indexRefCounter.ts +63 -2
- package/src/internal/module-augmentation.ts +0 -4
- package/src/internal/parser.ts +107 -25
- package/src/internal/parts.ts +158 -73
- package/src/internal/render.ts +638 -289
- package/src/internal/server.ts +5 -3
- package/Token/package.json +0 -6
- package/Tokenizer/package.json +0 -6
- package/dist/cjs/Token.js +0 -270
- package/dist/cjs/Token.js.map +0 -1
- package/dist/cjs/Tokenizer.js +0 -18
- package/dist/cjs/Tokenizer.js.map +0 -1
- package/dist/cjs/internal/readAttribute.js +0 -34
- package/dist/cjs/internal/readAttribute.js.map +0 -1
- package/dist/cjs/internal/tokenizer.js +0 -264
- package/dist/cjs/internal/tokenizer.js.map +0 -1
- package/dist/dts/Token.d.ts +0 -202
- package/dist/dts/Token.d.ts.map +0 -1
- package/dist/dts/Tokenizer.d.ts +0 -6
- package/dist/dts/Tokenizer.d.ts.map +0 -1
- package/dist/dts/internal/readAttribute.d.ts +0 -9
- package/dist/dts/internal/readAttribute.d.ts.map +0 -1
- package/dist/dts/internal/tokenizer.d.ts +0 -3
- package/dist/dts/internal/tokenizer.d.ts.map +0 -1
- package/dist/esm/Token.js +0 -264
- package/dist/esm/Token.js.map +0 -1
- package/dist/esm/Tokenizer.js +0 -9
- package/dist/esm/Tokenizer.js.map +0 -1
- package/dist/esm/internal/readAttribute.js +0 -24
- package/dist/esm/internal/readAttribute.js.map +0 -1
- package/dist/esm/internal/tokenizer.js +0 -296
- package/dist/esm/internal/tokenizer.js.map +0 -1
- package/src/Token.ts +0 -269
- package/src/Tokenizer.ts +0 -10
- package/src/internal/readAttribute.ts +0 -28
- package/src/internal/tokenizer.ts +0 -338
package/src/internal/render.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import * as Fx from "@typed/fx/Fx"
|
|
2
|
-
import * as Subject from "@typed/fx/Subject"
|
|
3
2
|
import { TypeId } from "@typed/fx/TypeId"
|
|
4
3
|
import type { Rendered } from "@typed/wire"
|
|
5
4
|
import { persistent } from "@typed/wire"
|
|
6
5
|
import { Effect } from "effect"
|
|
7
6
|
import type { Cause } from "effect/Cause"
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
7
|
+
import type { Chunk } from "effect/Chunk"
|
|
8
|
+
import * as Context from "effect/Context"
|
|
9
|
+
import { Scope } from "effect/Scope"
|
|
10
|
+
import type { Directive } from "../Directive.js"
|
|
10
11
|
import { isDirective } from "../Directive.js"
|
|
11
12
|
import * as ElementRef from "../ElementRef.js"
|
|
13
|
+
import * as ElementSource from "../ElementSource.js"
|
|
12
14
|
import type { BrowserEntry } from "../Entry.js"
|
|
13
15
|
import * as EventHandler from "../EventHandler.js"
|
|
14
|
-
import type {
|
|
16
|
+
import type { Part } from "../Part.js"
|
|
15
17
|
import type { Placeholder } from "../Placeholder.js"
|
|
16
18
|
import type { ToRendered } from "../Render.js"
|
|
17
19
|
import type { Renderable } from "../Renderable.js"
|
|
@@ -20,11 +22,11 @@ import type { RenderEvent } from "../RenderEvent.js"
|
|
|
20
22
|
import { DomRenderEvent } from "../RenderEvent.js"
|
|
21
23
|
import type { RenderTemplate } from "../RenderTemplate.js"
|
|
22
24
|
import type * as Template from "../Template.js"
|
|
23
|
-
import { TemplateInstance } from "../TemplateInstance.js"
|
|
24
25
|
import { makeRenderNodePart } from "./browser.js"
|
|
26
|
+
import { type EventSource, makeEventSource } from "./EventSource.js"
|
|
25
27
|
import { HydrateContext } from "./HydrateContext.js"
|
|
26
|
-
import type {
|
|
27
|
-
import {
|
|
28
|
+
import type { IndexRefCounter2 } from "./indexRefCounter.js"
|
|
29
|
+
import { indexRefCounter2 } from "./indexRefCounter.js"
|
|
28
30
|
import { parse } from "./parser.js"
|
|
29
31
|
import {
|
|
30
32
|
AttributePartImpl,
|
|
@@ -32,161 +34,585 @@ import {
|
|
|
32
34
|
ClassNamePartImpl,
|
|
33
35
|
CommentPartImpl,
|
|
34
36
|
DataPartImpl,
|
|
35
|
-
EventPartImpl,
|
|
36
37
|
PropertyPartImpl,
|
|
37
38
|
RefPartImpl,
|
|
38
|
-
SparseAttributePartImpl,
|
|
39
|
-
SparseClassNamePartImpl,
|
|
40
|
-
SparseCommentPartImpl,
|
|
41
|
-
StaticTextImpl,
|
|
42
39
|
TextPartImpl
|
|
43
40
|
} from "./parts.js"
|
|
44
41
|
import type { ParentChildNodes } from "./utils.js"
|
|
45
|
-
import { findPath } from "./utils.js"
|
|
42
|
+
import { findHoleComment, findPath } from "./utils.js"
|
|
43
|
+
|
|
44
|
+
// TODO: We need to add support for hydration of templates
|
|
45
|
+
// TODO: We need to re-think hydration for dynamic lists, probably just markers should be fine
|
|
46
|
+
// TODO: We need to make Parts synchronous
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
|
-
*
|
|
49
|
-
* view into the contents rendered by the Template.
|
|
49
|
+
* @internal
|
|
50
50
|
*/
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
51
|
+
export type RenderPartContext = {
|
|
52
|
+
readonly context: Context.Context<Scope>
|
|
53
|
+
readonly document: Document
|
|
54
|
+
readonly eventSource: EventSource
|
|
55
|
+
readonly refCounter: IndexRefCounter2
|
|
56
|
+
readonly renderContext: RenderContext
|
|
57
|
+
readonly values: ReadonlyArray<Renderable<any, any>>
|
|
58
|
+
readonly onCause: (cause: Cause<any>) => Effect.Effect<never, never, void>
|
|
59
|
+
|
|
60
|
+
readonly makeHydrateContext?: (index: number) => HydrateContext
|
|
61
|
+
|
|
62
|
+
expected: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type RenderPartMap = {
|
|
66
|
+
readonly [K in Template.PartNode["_tag"] | Template.SparsePartNode["_tag"]]: (
|
|
67
|
+
part: Extract<Template.PartNode | Template.SparsePartNode, { _tag: K }>,
|
|
68
|
+
node: Node,
|
|
69
|
+
ctx: RenderPartContext
|
|
70
|
+
) => null | Effect.Effect<any, any, void> | Array<Effect.Effect<any, any, void>>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const RenderPartMap: RenderPartMap = {
|
|
74
|
+
"attr": (templatePart, node, ctx) => {
|
|
75
|
+
const { document, refCounter, renderContext, values } = ctx
|
|
76
|
+
const element = node as HTMLElement | SVGElement
|
|
77
|
+
const attr = createAttribute(document, element, templatePart.name)
|
|
78
|
+
const renderable = values[templatePart.index]
|
|
79
|
+
let isSet = true
|
|
80
|
+
const setValue = (value: string | null | undefined) => {
|
|
81
|
+
if (isNullOrUndefined(value)) {
|
|
82
|
+
element.removeAttribute(templatePart.name)
|
|
83
|
+
isSet = false
|
|
84
|
+
} else {
|
|
85
|
+
attr.value = String(value)
|
|
86
|
+
if (isSet === false) {
|
|
87
|
+
element.setAttributeNode(attr)
|
|
88
|
+
isSet = true
|
|
89
|
+
}
|
|
76
90
|
}
|
|
91
|
+
}
|
|
77
92
|
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
return matchSettablePart(
|
|
94
|
+
renderable,
|
|
95
|
+
setValue,
|
|
96
|
+
() => AttributePartImpl.browser(templatePart.index, element, templatePart.name, renderContext),
|
|
97
|
+
(f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
|
|
98
|
+
() => ctx.expected++
|
|
99
|
+
)
|
|
100
|
+
},
|
|
101
|
+
"boolean-part": (templatePart, node, ctx) => {
|
|
102
|
+
const { refCounter, renderContext, values } = ctx
|
|
103
|
+
const element = node as HTMLElement | SVGElement
|
|
104
|
+
const renderable = values[templatePart.index]
|
|
105
|
+
const setValue = (value: boolean | null | undefined) => {
|
|
106
|
+
element.toggleAttribute(templatePart.name, isNullOrUndefined(value) ? false : Boolean(value))
|
|
107
|
+
}
|
|
80
108
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
109
|
+
return matchSettablePart(
|
|
110
|
+
renderable,
|
|
111
|
+
setValue,
|
|
112
|
+
() => BooleanPartImpl.browser(templatePart.index, element, templatePart.name, renderContext),
|
|
113
|
+
(f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
|
|
114
|
+
() => ctx.expected++
|
|
115
|
+
)
|
|
116
|
+
},
|
|
117
|
+
"className-part": (templatePart, node, ctx) => {
|
|
118
|
+
const { refCounter, renderContext, values } = ctx
|
|
119
|
+
const element = node as HTMLElement | SVGElement
|
|
120
|
+
const renderable = values[templatePart.index]
|
|
121
|
+
let classNames: Set<string> = new Set()
|
|
122
|
+
const setValue = (value: string | Array<string> | null | undefined) => {
|
|
123
|
+
if (isNullOrUndefined(value)) {
|
|
124
|
+
element.classList.remove(...classNames)
|
|
125
|
+
classNames.clear()
|
|
126
|
+
} else {
|
|
127
|
+
const newClassNames = new Set(Array.isArray(value) ? value : [String(value)])
|
|
128
|
+
const { added, removed } = diffClassNames(classNames, newClassNames)
|
|
129
|
+
|
|
130
|
+
if (removed.length > 0) {
|
|
131
|
+
element.classList.remove(...removed)
|
|
132
|
+
}
|
|
133
|
+
if (added.length > 0) element.classList.add(...added)
|
|
84
134
|
|
|
85
|
-
|
|
86
|
-
values: Values,
|
|
87
|
-
parts: Parts,
|
|
88
|
-
refCounter: IndexRefCounter,
|
|
89
|
-
onCause: (cause: Cause<Placeholder.Error<Values[number]>>) => Effect.Effect<never, never, unknown>,
|
|
90
|
-
makeHydrateContext?: (index: number) => HydrateContext
|
|
91
|
-
): Effect.Effect<Placeholder.Context<Values[number]> | Scope, never, void> {
|
|
92
|
-
return Effect.all(parts.map((part, index) => {
|
|
93
|
-
switch (part._tag) {
|
|
94
|
-
case "sparse/attribute":
|
|
95
|
-
case "sparse/className":
|
|
96
|
-
case "sparse/comment": {
|
|
97
|
-
return renderSparsePart(values, part, refCounter)
|
|
135
|
+
classNames = newClassNames
|
|
98
136
|
}
|
|
99
|
-
default:
|
|
100
|
-
return renderPart(
|
|
101
|
-
values,
|
|
102
|
-
part,
|
|
103
|
-
refCounter,
|
|
104
|
-
onCause,
|
|
105
|
-
makeHydrateContext ? () => makeHydrateContext(index) : undefined
|
|
106
|
-
)
|
|
107
137
|
}
|
|
108
|
-
})) as any
|
|
109
|
-
}
|
|
110
138
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return Effect.forkScoped(
|
|
119
|
-
Fx.observe(
|
|
120
|
-
unwrapSparsePartRenderables(
|
|
121
|
-
part.parts.map((p) => p._tag === "static/text" ? Fx.succeed(p.value) : values[p.index]),
|
|
122
|
-
part
|
|
123
|
-
),
|
|
124
|
-
(value) => Effect.tap(part.update(value as any), () => Effect.forEach(indexes, (a) => refCounter.release(a)))
|
|
139
|
+
return matchSettablePart(
|
|
140
|
+
renderable,
|
|
141
|
+
setValue,
|
|
142
|
+
() => ClassNamePartImpl.browser(templatePart.index, element, renderContext),
|
|
143
|
+
(f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
|
|
144
|
+
() => ctx.expected++
|
|
125
145
|
)
|
|
126
|
-
|
|
127
|
-
|
|
146
|
+
},
|
|
147
|
+
"comment-part": (templatePart, node, ctx) => {
|
|
148
|
+
const { refCounter, renderContext, values } = ctx
|
|
149
|
+
const comment = findHoleComment(node as Element, templatePart.index)
|
|
150
|
+
const renderable = values[templatePart.index]
|
|
151
|
+
const setValue = (value: string | null | undefined) => {
|
|
152
|
+
comment.textContent = isNullOrUndefined(value) ? "" : String(value)
|
|
153
|
+
}
|
|
128
154
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
): Effect.Effect<any, never, void> {
|
|
136
|
-
const partIndex = part.index
|
|
137
|
-
const renderable = values[partIndex]
|
|
138
|
-
|
|
139
|
-
if (isDirective(renderable)) {
|
|
140
|
-
return renderable(part).pipe(
|
|
141
|
-
Effect.tap(() => refCounter.release(partIndex)),
|
|
142
|
-
Effect.forkScoped
|
|
155
|
+
return matchSettablePart(
|
|
156
|
+
renderable,
|
|
157
|
+
setValue,
|
|
158
|
+
() => CommentPartImpl.browser(templatePart.index, comment, renderContext),
|
|
159
|
+
(f) => Effect.zipRight(renderContext.queue.add(comment, f), refCounter.release(templatePart.index)),
|
|
160
|
+
() => ctx.expected++
|
|
143
161
|
)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
162
|
+
},
|
|
163
|
+
"data": (templatePart, node, ctx) => {
|
|
164
|
+
const element = node as HTMLElement | SVGElement
|
|
165
|
+
const renderable = ctx.values[templatePart.index]
|
|
166
|
+
const previousKeys = new Set<string>(Object.keys(element.dataset))
|
|
167
|
+
const setValue = (value: Record<string, string | undefined> | null | undefined) => {
|
|
168
|
+
if (isNullOrUndefined(value)) {
|
|
169
|
+
for (const key of previousKeys) {
|
|
170
|
+
delete element.dataset[key]
|
|
171
|
+
}
|
|
172
|
+
previousKeys.clear()
|
|
173
|
+
} else {
|
|
174
|
+
for (const key of previousKeys) {
|
|
175
|
+
if (!(key in value)) {
|
|
176
|
+
delete element.dataset[key]
|
|
177
|
+
previousKeys.delete(key)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const key of Object.keys(value)) {
|
|
182
|
+
if (!previousKeys.has(key)) {
|
|
183
|
+
previousKeys.add(key)
|
|
184
|
+
}
|
|
185
|
+
element.dataset[key] = value[key] || ""
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return matchSettablePart(
|
|
191
|
+
renderable,
|
|
192
|
+
setValue,
|
|
193
|
+
() => DataPartImpl.browser(templatePart.index, element, ctx.renderContext),
|
|
194
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(templatePart.index)),
|
|
195
|
+
() => ctx.expected++
|
|
155
196
|
)
|
|
156
|
-
}
|
|
157
|
-
|
|
197
|
+
},
|
|
198
|
+
"event": (templatePart, node, ctx) => {
|
|
199
|
+
const element = node as HTMLElement | SVGElement
|
|
200
|
+
const renderable = ctx.values[templatePart.index]
|
|
201
|
+
const handler = getEventHandler(renderable, ctx.context, ctx.onCause)
|
|
202
|
+
if (handler) {
|
|
203
|
+
ctx.eventSource.addEventListener(element, templatePart.name, handler)
|
|
204
|
+
}
|
|
158
205
|
|
|
159
|
-
return
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
206
|
+
return null
|
|
207
|
+
},
|
|
208
|
+
"node": (templatePart, node, ctx) => {
|
|
209
|
+
const makeHydrateContext = ctx.makeHydrateContext
|
|
210
|
+
const part = makeRenderNodePart(
|
|
211
|
+
templatePart.index,
|
|
212
|
+
node as HTMLElement | SVGElement,
|
|
213
|
+
ctx.renderContext,
|
|
214
|
+
ctx.document,
|
|
215
|
+
!!makeHydrateContext
|
|
165
216
|
)
|
|
166
|
-
} else {
|
|
167
|
-
const renderable = values[partIndex]
|
|
168
217
|
|
|
169
|
-
|
|
218
|
+
ctx.expected++
|
|
219
|
+
|
|
220
|
+
const handle = handlePart(
|
|
221
|
+
ctx.values[templatePart.index],
|
|
222
|
+
(value) => Effect.zipRight(part.update(value as any), ctx.refCounter.release(templatePart.index))
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if (makeHydrateContext) {
|
|
226
|
+
return Effect.provideService(handle, HydrateContext, makeHydrateContext(templatePart.index))
|
|
227
|
+
} else {
|
|
228
|
+
return handle
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
"property": (templatePart, node, ctx) => {
|
|
232
|
+
const element = node as HTMLElement | SVGElement
|
|
233
|
+
const renderable = ctx.values[templatePart.index]
|
|
234
|
+
const setValue = (value: string | null | undefined) => {
|
|
235
|
+
if (isNullOrUndefined(value)) {
|
|
236
|
+
delete (element as any)[templatePart.name]
|
|
237
|
+
} else {
|
|
238
|
+
;(element as any)[templatePart.name] = value
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return matchSettablePart(
|
|
243
|
+
renderable,
|
|
244
|
+
setValue,
|
|
245
|
+
() => PropertyPartImpl.browser(templatePart.index, element, templatePart.name, ctx.renderContext),
|
|
246
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(templatePart.index)),
|
|
247
|
+
() => ctx.expected++
|
|
248
|
+
)
|
|
249
|
+
},
|
|
250
|
+
"properties": (templatePart, node, ctx) => {
|
|
251
|
+
const renderable = ctx.values[templatePart.index] as any as Record<string, any>
|
|
252
|
+
|
|
253
|
+
if (isNullOrUndefined(renderable)) return null
|
|
254
|
+
else if (Fx.isFx(renderable) || Effect.isEffect(renderable)) {
|
|
255
|
+
throw new Error(`Properties Part must utilize an Record of renderable values.`)
|
|
256
|
+
} else if (typeof renderable === "object" && !Array.isArray(renderable)) {
|
|
257
|
+
const element = node as HTMLElement | SVGElement
|
|
258
|
+
|
|
259
|
+
const toggleBoolean = (key: string, value: unknown) => {
|
|
260
|
+
element.toggleAttribute(key, isNullOrUndefined(value) ? false : Boolean(value))
|
|
261
|
+
}
|
|
262
|
+
const setAttribute = (key: string, value: unknown) => {
|
|
263
|
+
if (isNullOrUndefined(value)) {
|
|
264
|
+
element.removeAttribute(key)
|
|
265
|
+
} else {
|
|
266
|
+
element.setAttribute(key, String(value))
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const setProperty = (key: string, value: unknown) => {
|
|
270
|
+
if (isNullOrUndefined(value)) {
|
|
271
|
+
delete (element as any)[key]
|
|
272
|
+
} else {
|
|
273
|
+
;(element as any)[key] = value
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const effects: Array<Effect.Effect<any, any, void>> = []
|
|
278
|
+
|
|
279
|
+
// We need indexes to track async values that won't conflict
|
|
280
|
+
// with any other Parts, we can start end of the current values.length
|
|
281
|
+
// As there should only ever be exactly 1 properties part.
|
|
282
|
+
let i = ctx.values.length
|
|
283
|
+
|
|
284
|
+
loop:
|
|
285
|
+
for (const [key, value] of Object.entries(renderable)) {
|
|
286
|
+
const index = ++i
|
|
287
|
+
|
|
288
|
+
switch (key[0]) {
|
|
289
|
+
case "?": {
|
|
290
|
+
const name = key.slice(1)
|
|
291
|
+
const eff = matchSettablePart(
|
|
292
|
+
value,
|
|
293
|
+
(value) => toggleBoolean(name, value),
|
|
294
|
+
() => BooleanPartImpl.browser(index, element, name, ctx.renderContext),
|
|
295
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
|
|
296
|
+
() => ctx.expected++
|
|
297
|
+
)
|
|
298
|
+
if (eff !== null) {
|
|
299
|
+
effects.push(eff)
|
|
300
|
+
}
|
|
301
|
+
continue loop
|
|
302
|
+
}
|
|
303
|
+
case ".": {
|
|
304
|
+
const name = key.slice(1)
|
|
305
|
+
const eff = matchSettablePart(
|
|
306
|
+
value,
|
|
307
|
+
(value) => setProperty(name, value),
|
|
308
|
+
() => PropertyPartImpl.browser(index, element, name, ctx.renderContext),
|
|
309
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
|
|
310
|
+
() => ctx.expected++
|
|
311
|
+
)
|
|
312
|
+
if (eff !== null) {
|
|
313
|
+
effects.push(eff)
|
|
314
|
+
}
|
|
315
|
+
continue loop
|
|
316
|
+
}
|
|
317
|
+
case "@": {
|
|
318
|
+
const name = key.slice(1)
|
|
319
|
+
const handler = getEventHandler(value, ctx.context, ctx.onCause)
|
|
320
|
+
if (handler) {
|
|
321
|
+
ctx.eventSource.addEventListener(element, name, handler)
|
|
322
|
+
}
|
|
323
|
+
continue loop
|
|
324
|
+
}
|
|
325
|
+
case "o": {
|
|
326
|
+
if (key[1] === "n") {
|
|
327
|
+
const name = key.slice(2)
|
|
328
|
+
const handler = getEventHandler(value, ctx.context, ctx.onCause)
|
|
329
|
+
if (handler) {
|
|
330
|
+
ctx.eventSource.addEventListener(element, name, handler)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
continue loop
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const eff = matchSettablePart(
|
|
338
|
+
value,
|
|
339
|
+
(value) => setAttribute(key, value),
|
|
340
|
+
() => AttributePartImpl.browser(index, element, key, ctx.renderContext),
|
|
341
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
|
|
342
|
+
() => ctx.expected++
|
|
343
|
+
)
|
|
344
|
+
if (eff !== null) {
|
|
345
|
+
effects.push(eff)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return effects
|
|
350
|
+
} else {
|
|
351
|
+
return null
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
"ref": (templatePart, node, ctx) => {
|
|
355
|
+
const element = node as HTMLElement | SVGElement
|
|
356
|
+
const renderable = ctx.values[templatePart.index]
|
|
357
|
+
|
|
358
|
+
if (isDirective(renderable)) {
|
|
359
|
+
return renderable(new RefPartImpl(ElementSource.fromElement(element), templatePart.index))
|
|
360
|
+
} else if (ElementRef.isElementRef(renderable)) {
|
|
361
|
+
return ElementRef.set(renderable, element)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return null
|
|
365
|
+
},
|
|
366
|
+
"sparse-attr": (templatePart, node, ctx) => {
|
|
367
|
+
const values = Array.from({ length: templatePart.nodes.length }, (): string => "")
|
|
368
|
+
const element = node as HTMLElement | SVGElement
|
|
369
|
+
const attr = createAttribute(ctx.document, element, templatePart.name)
|
|
370
|
+
|
|
371
|
+
const setValue = (value: string | null | undefined, index: number) =>
|
|
372
|
+
Effect.suspend(() => {
|
|
373
|
+
values[index] = value || ""
|
|
374
|
+
return ctx.renderContext.queue.add(element, () => attr.value = values.join(""))
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
const effects: Array<Effect.Effect<any, any, void>> = []
|
|
378
|
+
|
|
379
|
+
for (let i = 0; i < templatePart.nodes.length; ++i) {
|
|
380
|
+
const node = templatePart.nodes[i]
|
|
381
|
+
if (node._tag === "text") {
|
|
382
|
+
values[i] = node.value
|
|
383
|
+
} else {
|
|
384
|
+
const renderable = ctx.values[node.index]
|
|
385
|
+
const index = i
|
|
386
|
+
const effect = matchSettablePart(
|
|
387
|
+
renderable,
|
|
388
|
+
(value) => setValue(value, index),
|
|
389
|
+
() =>
|
|
390
|
+
new AttributePartImpl(
|
|
391
|
+
templatePart.name,
|
|
392
|
+
node.index,
|
|
393
|
+
({ value }) => setValue(value, index),
|
|
394
|
+
null
|
|
395
|
+
),
|
|
396
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(node.index)),
|
|
397
|
+
() => ctx.expected++
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
if (effect !== null) {
|
|
401
|
+
effects.push(effect)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return effects
|
|
407
|
+
},
|
|
408
|
+
"sparse-class-name": (templatePart, node, ctx) => {
|
|
409
|
+
const element = node as HTMLElement | SVGElement
|
|
410
|
+
|
|
411
|
+
const effects = templatePart.nodes.flatMap((node) => {
|
|
412
|
+
if (node._tag === "text") {
|
|
413
|
+
const split = splitClassNames(node.value)
|
|
414
|
+
if (split.length > 0) element.classList.add(...split)
|
|
415
|
+
return []
|
|
416
|
+
} else {
|
|
417
|
+
const eff = RenderPartMap[node._tag](node, element, ctx)
|
|
418
|
+
if (eff === null) return []
|
|
419
|
+
return Array.isArray(eff) ? eff : [eff]
|
|
420
|
+
}
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
return effects
|
|
424
|
+
},
|
|
425
|
+
"sparse-comment": (templatePart, node, ctx) => {
|
|
426
|
+
const values = Array.from({ length: templatePart.nodes.length }, (): string => "")
|
|
427
|
+
const comment = node as Comment
|
|
428
|
+
|
|
429
|
+
const setValue = (value: string | null | undefined, index: number) =>
|
|
430
|
+
Effect.suspend(() => {
|
|
431
|
+
values[index] = value || ""
|
|
432
|
+
return ctx.renderContext.queue.add(comment, () => comment.textContent = values.join(""))
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
const effects: Array<Effect.Effect<any, any, void>> = []
|
|
436
|
+
|
|
437
|
+
for (let i = 0; i < templatePart.nodes.length; ++i) {
|
|
438
|
+
const node = templatePart.nodes[i]
|
|
439
|
+
if (node._tag === "text") {
|
|
440
|
+
values[i] = node.value
|
|
441
|
+
} else {
|
|
442
|
+
const renderable = ctx.values[node.index]
|
|
443
|
+
const index = i
|
|
444
|
+
const effect = matchSettablePart(
|
|
445
|
+
renderable,
|
|
446
|
+
(value) => setValue(value, index),
|
|
447
|
+
() =>
|
|
448
|
+
new CommentPartImpl(
|
|
449
|
+
node.index,
|
|
450
|
+
({ value }) => setValue(value, index),
|
|
451
|
+
null
|
|
452
|
+
),
|
|
453
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(comment, f), ctx.refCounter.release(node.index)),
|
|
454
|
+
() => ctx.expected++
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
if (effect !== null) {
|
|
458
|
+
effects.push(effect)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return effects
|
|
464
|
+
},
|
|
465
|
+
"text-part": (templatePart, node, ctx) => {
|
|
466
|
+
const part = TextPartImpl.browser(
|
|
467
|
+
ctx.document,
|
|
468
|
+
templatePart.index,
|
|
469
|
+
node as HTMLElement | SVGElement,
|
|
470
|
+
ctx.renderContext
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
ctx.expected++
|
|
170
474
|
|
|
171
475
|
return handlePart(
|
|
172
|
-
values[
|
|
173
|
-
(value) => Effect.
|
|
476
|
+
ctx.values[templatePart.index],
|
|
477
|
+
(value) => Effect.zipRight(part.update(value as any), ctx.refCounter.release(templatePart.index))
|
|
174
478
|
)
|
|
175
479
|
}
|
|
176
480
|
}
|
|
177
481
|
|
|
482
|
+
const SPACE_REGEXP = /\s+/g
|
|
483
|
+
|
|
484
|
+
function splitClassNames(value: string) {
|
|
485
|
+
return value.split(SPACE_REGEXP).flatMap((a) => {
|
|
486
|
+
const trimmed = a.trim()
|
|
487
|
+
return trimmed.length > 0 ? [trimmed] : []
|
|
488
|
+
})
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function isNullOrUndefined<T>(value: T | null | undefined): value is null | undefined {
|
|
492
|
+
return value === null || value === undefined
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function diffClassNames(oldClassNames: Set<string>, newClassNames: Set<string>) {
|
|
496
|
+
const added: Array<string> = []
|
|
497
|
+
const removed: Array<string> = []
|
|
498
|
+
|
|
499
|
+
for (const className of oldClassNames) {
|
|
500
|
+
if (!newClassNames.has(className)) {
|
|
501
|
+
removed.push(className)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
for (const className of newClassNames) {
|
|
506
|
+
if (!oldClassNames.has(className) && className.trim()) {
|
|
507
|
+
added.push(className)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return { added, removed }
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* @internal
|
|
516
|
+
*/
|
|
517
|
+
export function renderPart2(
|
|
518
|
+
part: Template.PartNode | Template.SparsePartNode,
|
|
519
|
+
content: ParentChildNodes,
|
|
520
|
+
path: Chunk<number>,
|
|
521
|
+
ctx: RenderPartContext
|
|
522
|
+
): Effect.Effect<any, any, void> | Array<Effect.Effect<any, any, void>> | null {
|
|
523
|
+
return RenderPartMap[part._tag](part as any, findPath(content, path), ctx)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Here for "standard" browser rendering, a TemplateInstance is effectively a live
|
|
528
|
+
* view into the contents rendered by the Template.
|
|
529
|
+
*/
|
|
530
|
+
export const renderTemplate: (document: Document, renderContext: RenderContext) => RenderTemplate =
|
|
531
|
+
(document, renderContext) =>
|
|
532
|
+
<Values extends ReadonlyArray<Renderable<any, any>>, T extends Rendered = Rendered>(
|
|
533
|
+
templateStrings: TemplateStringsArray,
|
|
534
|
+
values: Values
|
|
535
|
+
) => {
|
|
536
|
+
const entry = getBrowserEntry(document, renderContext, templateStrings)
|
|
537
|
+
if (values.length === 0) {
|
|
538
|
+
return Fx.sync(() => DomRenderEvent(persistent(document.importNode(entry.content, true))))
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return Fx.make<Scope | Placeholder.Context<Values[number]>, Placeholder.Error<Values[number]>, RenderEvent>((
|
|
542
|
+
sink
|
|
543
|
+
) => {
|
|
544
|
+
return Effect.gen(function*(_) {
|
|
545
|
+
const content = document.importNode(entry.content, true)
|
|
546
|
+
const context = yield* _(Effect.context<Scope>())
|
|
547
|
+
const refCounter = yield* _(indexRefCounter2())
|
|
548
|
+
const ctx: RenderPartContext = {
|
|
549
|
+
context,
|
|
550
|
+
document,
|
|
551
|
+
eventSource: makeEventSource(),
|
|
552
|
+
expected: 0,
|
|
553
|
+
refCounter,
|
|
554
|
+
renderContext,
|
|
555
|
+
onCause: sink.onFailure as any,
|
|
556
|
+
values
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Connect our interpolated values to our template parts
|
|
560
|
+
const effects: Array<Effect.Effect<Scope | Placeholder.Context<Values[number]>, never, void>> = []
|
|
561
|
+
for (const [part, path] of entry.template.parts) {
|
|
562
|
+
const eff = renderPart2(part, content, path, ctx)
|
|
563
|
+
if (eff !== null) {
|
|
564
|
+
effects.push(
|
|
565
|
+
...(Array.isArray(eff) ? eff : [eff]) as Array<
|
|
566
|
+
Effect.Effect<Scope | Placeholder.Context<Values[number]>, never, void>
|
|
567
|
+
>
|
|
568
|
+
)
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Fork any effects necessary
|
|
573
|
+
if (effects.length > 0) {
|
|
574
|
+
yield* _(Effect.forkAll(effects))
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// If there's anything to wait on and it's not already done, wait for an initial value
|
|
578
|
+
// for all asynchronous sources.
|
|
579
|
+
if (ctx.expected > 0 && (yield* _(refCounter.expect(ctx.expected)))) {
|
|
580
|
+
yield* _(refCounter.wait)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Create a persistent wire from our content
|
|
584
|
+
const wire = persistent(content) as T
|
|
585
|
+
|
|
586
|
+
// Set the element when it is ready
|
|
587
|
+
yield* _(ctx.eventSource.setup(wire, Context.get(context, Scope)))
|
|
588
|
+
|
|
589
|
+
// Emit our DomRenderEvent
|
|
590
|
+
yield* _(sink.onSuccess(DomRenderEvent(wire)))
|
|
591
|
+
|
|
592
|
+
// Ensure our templates last forever in the DOM environment
|
|
593
|
+
// so event listeners are kept attached to the current Scope.
|
|
594
|
+
yield* _(Effect.never)
|
|
595
|
+
})
|
|
596
|
+
})
|
|
597
|
+
}
|
|
598
|
+
|
|
178
599
|
function getEventHandler<R, E>(
|
|
179
600
|
renderable: any,
|
|
601
|
+
ctx: Context.Context<any> | Context.Context<never>,
|
|
180
602
|
onCause: (cause: Cause<E>) => Effect.Effect<never, never, unknown>
|
|
181
|
-
): EventHandler.EventHandler<
|
|
603
|
+
): EventHandler.EventHandler<never, never> | null {
|
|
182
604
|
if (renderable && typeof renderable === "object") {
|
|
183
605
|
if (EventHandler.EventHandlerTypeId in renderable) {
|
|
184
606
|
return EventHandler.make(
|
|
185
|
-
(ev) =>
|
|
607
|
+
(ev) =>
|
|
608
|
+
Effect.provide(
|
|
609
|
+
Effect.catchAllCause((renderable as EventHandler.EventHandler<R, E>).handler(ev), onCause),
|
|
610
|
+
ctx as any
|
|
611
|
+
),
|
|
186
612
|
(renderable as EventHandler.EventHandler<R, E>).options
|
|
187
613
|
)
|
|
188
614
|
} else if (Effect.EffectTypeId in renderable) {
|
|
189
|
-
return EventHandler.make(() => Effect.catchAllCause(renderable, onCause))
|
|
615
|
+
return EventHandler.make(() => Effect.provide(Effect.catchAllCause(renderable, onCause), ctx))
|
|
190
616
|
}
|
|
191
617
|
}
|
|
192
618
|
|
|
@@ -204,7 +630,7 @@ function handlePart<R, E>(
|
|
|
204
630
|
else if (Array.isArray(renderable)) {
|
|
205
631
|
return renderable.length === 0
|
|
206
632
|
? update(null)
|
|
207
|
-
: Effect.forkScoped(Fx.observe(Fx.
|
|
633
|
+
: Effect.forkScoped(Fx.observe(Fx.tuple(renderable.map(unwrapRenderable)) as any, update))
|
|
208
634
|
} else if (TypeId in renderable) {
|
|
209
635
|
return Effect.forkScoped(Fx.observe(renderable as any, update))
|
|
210
636
|
} else if (Effect.EffectTypeId in renderable) {
|
|
@@ -222,7 +648,7 @@ function unwrapRenderable<R, E>(renderable: unknown): Fx.Fx<R, E, any> {
|
|
|
222
648
|
case "object": {
|
|
223
649
|
if (renderable === null || renderable === undefined) return Fx.succeed(null)
|
|
224
650
|
else if (Array.isArray(renderable)) {
|
|
225
|
-
return renderable.length === 0 ? Fx.succeed(null) : Fx.
|
|
651
|
+
return renderable.length === 0 ? Fx.succeed(null) : Fx.tuple(renderable.map(unwrapRenderable)) as any
|
|
226
652
|
} else if (TypeId in renderable) {
|
|
227
653
|
return renderable as any
|
|
228
654
|
} else if (Effect.EffectTypeId in renderable) {
|
|
@@ -234,31 +660,6 @@ function unwrapRenderable<R, E>(renderable: unknown): Fx.Fx<R, E, any> {
|
|
|
234
660
|
}
|
|
235
661
|
}
|
|
236
662
|
|
|
237
|
-
function unwrapSparsePartRenderables(
|
|
238
|
-
renderables: ReadonlyArray<Renderable<any, any>>,
|
|
239
|
-
part: SparsePart
|
|
240
|
-
) {
|
|
241
|
-
return Fx.combine(
|
|
242
|
-
// @ts-ignore type too deep
|
|
243
|
-
renderables.map((renderable, i) => {
|
|
244
|
-
const p = part.parts[i]
|
|
245
|
-
|
|
246
|
-
if (p._tag === "static/text") {
|
|
247
|
-
return Fx.succeed(p.value)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (isDirective(renderable)) {
|
|
251
|
-
return Fx.fromEffect(Effect.map(renderable(p), () => p.value))
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return Fx.mapEffect(
|
|
255
|
-
unwrapRenderable(renderable),
|
|
256
|
-
(u) => Effect.map(p.update(u), () => p.value)
|
|
257
|
-
)
|
|
258
|
-
})
|
|
259
|
-
) as any
|
|
260
|
-
}
|
|
261
|
-
|
|
262
663
|
export function attachRoot<T extends RenderEvent | null>(
|
|
263
664
|
cache: RenderContext["renderCache"],
|
|
264
665
|
where: HTMLElement,
|
|
@@ -321,143 +722,6 @@ export function getBrowserEntry(
|
|
|
321
722
|
}
|
|
322
723
|
}
|
|
323
724
|
|
|
324
|
-
export function buildParts<T extends Rendered, E>(
|
|
325
|
-
document: Document,
|
|
326
|
-
ctx: RenderContext,
|
|
327
|
-
template: Template.Template,
|
|
328
|
-
content: ParentChildNodes,
|
|
329
|
-
ref: ElementRef.ElementRef<T>,
|
|
330
|
-
onCause: (cause: Cause<E>) => Effect.Effect<never, never, void>,
|
|
331
|
-
isHydrating: boolean
|
|
332
|
-
): Effect.Effect<Scope, never, Parts> {
|
|
333
|
-
return Effect.all(
|
|
334
|
-
template.parts.map(([part, path]) =>
|
|
335
|
-
buildPartWithNode(document, ctx, part, findPath(content, path), ref, onCause, isHydrating)
|
|
336
|
-
)
|
|
337
|
-
)
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function buildPartWithNode<T extends Rendered, E>(
|
|
341
|
-
document: Document,
|
|
342
|
-
ctx: RenderContext,
|
|
343
|
-
part: Template.PartNode | Template.SparsePartNode,
|
|
344
|
-
node: Node,
|
|
345
|
-
ref: ElementRef.ElementRef<T>,
|
|
346
|
-
onCause: (cause: Cause<E>) => Effect.Effect<never, never, void>,
|
|
347
|
-
isHydrating: boolean
|
|
348
|
-
): Effect.Effect<Scope, never, Part | SparsePart> {
|
|
349
|
-
switch (part._tag) {
|
|
350
|
-
case "attr":
|
|
351
|
-
return Effect.succeed(AttributePartImpl.browser(part.index, node as Element, part.name, ctx))
|
|
352
|
-
case "boolean-part":
|
|
353
|
-
return Effect.succeed(BooleanPartImpl.browser(part.index, node as Element, part.name, ctx))
|
|
354
|
-
case "className-part":
|
|
355
|
-
return Effect.succeed(ClassNamePartImpl.browser(part.index, node as Element, ctx))
|
|
356
|
-
case "comment-part":
|
|
357
|
-
return Effect.succeed(CommentPartImpl.browser(part.index, node as Comment, ctx))
|
|
358
|
-
case "data":
|
|
359
|
-
return Effect.succeed(DataPartImpl.browser(part.index, node as HTMLElement | SVGElement, ctx))
|
|
360
|
-
case "event":
|
|
361
|
-
return EventPartImpl.browser(part.name, part.index, ref, node as HTMLElement | SVGElement, onCause) as any
|
|
362
|
-
case "node":
|
|
363
|
-
return Effect.succeed(
|
|
364
|
-
makeRenderNodePart(part.index, node as HTMLElement | SVGElement, ctx, document, isHydrating)
|
|
365
|
-
)
|
|
366
|
-
case "property":
|
|
367
|
-
return Effect.succeed(PropertyPartImpl.browser(part.index, node, part.name, ctx))
|
|
368
|
-
case "ref":
|
|
369
|
-
return Effect.succeed(new RefPartImpl(ref.query(node as HTMLElement | SVGElement), part.index)) as any
|
|
370
|
-
case "sparse-attr": {
|
|
371
|
-
const parts: Array<AttributePart | StaticText> = Array(part.nodes.length)
|
|
372
|
-
const sparse = SparseAttributePartImpl.browser(
|
|
373
|
-
part.name,
|
|
374
|
-
parts,
|
|
375
|
-
node as HTMLElement | SVGElement,
|
|
376
|
-
ctx
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
for (let i = 0; i < part.nodes.length; ++i) {
|
|
380
|
-
const node = part.nodes[i]
|
|
381
|
-
|
|
382
|
-
if (node._tag === "text") {
|
|
383
|
-
parts.push(new StaticTextImpl(node.value))
|
|
384
|
-
;(sparse as any).value[i] = node.value
|
|
385
|
-
} else {
|
|
386
|
-
parts.push(
|
|
387
|
-
new AttributePartImpl(
|
|
388
|
-
node.name,
|
|
389
|
-
node.index,
|
|
390
|
-
({ value }) => sparse.update(replace(sparse.value, i, value || "")),
|
|
391
|
-
sparse.value[i]
|
|
392
|
-
)
|
|
393
|
-
)
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return Effect.succeed(sparse)
|
|
398
|
-
}
|
|
399
|
-
case "sparse-class-name": {
|
|
400
|
-
const parts: Array<ClassNamePart | StaticText> = []
|
|
401
|
-
const values: Array<string | Array<string>> = [] // TODO: Do this for all other sparse attrs
|
|
402
|
-
const sparse = SparseClassNamePartImpl.browser(
|
|
403
|
-
parts,
|
|
404
|
-
node as HTMLElement | SVGElement,
|
|
405
|
-
ctx,
|
|
406
|
-
values
|
|
407
|
-
)
|
|
408
|
-
|
|
409
|
-
for (let i = 0; i < part.nodes.length; ++i) {
|
|
410
|
-
const node = part.nodes[i]
|
|
411
|
-
|
|
412
|
-
if (node._tag === "text") {
|
|
413
|
-
parts.push(new StaticTextImpl(node.value))
|
|
414
|
-
values.push(node.value)
|
|
415
|
-
} else {
|
|
416
|
-
values.push([])
|
|
417
|
-
parts.push(
|
|
418
|
-
new ClassNamePartImpl(
|
|
419
|
-
node.index,
|
|
420
|
-
({ value }) => sparse.update(replace(sparse.value, i, value || "")),
|
|
421
|
-
[]
|
|
422
|
-
)
|
|
423
|
-
)
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
return Effect.succeed(sparse)
|
|
428
|
-
}
|
|
429
|
-
case "sparse-comment": {
|
|
430
|
-
const parts: Array<CommentPart | StaticText> = Array(part.nodes.length)
|
|
431
|
-
const sparse = SparseCommentPartImpl.browser(
|
|
432
|
-
node as Comment,
|
|
433
|
-
parts,
|
|
434
|
-
ctx
|
|
435
|
-
)
|
|
436
|
-
|
|
437
|
-
for (let i = 0; i < part.nodes.length; ++i) {
|
|
438
|
-
const node = part.nodes[i]
|
|
439
|
-
|
|
440
|
-
if (node._tag === "text") {
|
|
441
|
-
parts.push(new StaticTextImpl(node.value))
|
|
442
|
-
;(sparse as any).value[i] = node.value
|
|
443
|
-
} else {
|
|
444
|
-
parts.push(
|
|
445
|
-
new CommentPartImpl(
|
|
446
|
-
node.index,
|
|
447
|
-
({ value }) => sparse.update(replace(sparse.value, i, value || "")),
|
|
448
|
-
sparse.value[i]
|
|
449
|
-
)
|
|
450
|
-
)
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
return Effect.succeed(sparse)
|
|
455
|
-
}
|
|
456
|
-
case "text-part":
|
|
457
|
-
return Effect.succeed(TextPartImpl.browser(document, part.index, node as Element, ctx))
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
725
|
export function buildTemplate(document: Document, { nodes }: Template.Template): DocumentFragment {
|
|
462
726
|
const fragment = document.createDocumentFragment()
|
|
463
727
|
|
|
@@ -484,6 +748,39 @@ function buildNode(document: Document, node: Template.Node, isSvgContext: boolea
|
|
|
484
748
|
case "comment-part":
|
|
485
749
|
case "node":
|
|
486
750
|
return document.createComment(`hole${node.index}`)
|
|
751
|
+
case "doctype":
|
|
752
|
+
return document.implementation.createDocumentType(
|
|
753
|
+
node.name,
|
|
754
|
+
docTypeNameToPublicId(node.name),
|
|
755
|
+
docTypeNameToSystemId(node.name)
|
|
756
|
+
)
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function docTypeNameToPublicId(name: string): string {
|
|
761
|
+
switch (name) {
|
|
762
|
+
case "html":
|
|
763
|
+
return "-//W3C//DTD HTML 4.01//EN"
|
|
764
|
+
case "svg":
|
|
765
|
+
return "-//W3C//DTD SVG 1.1//EN"
|
|
766
|
+
case "math":
|
|
767
|
+
return "-//W3C//DTD MathML 2.0//EN"
|
|
768
|
+
default:
|
|
769
|
+
return ""
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function docTypeNameToSystemId(name: string): string {
|
|
774
|
+
switch (name) {
|
|
775
|
+
// HTML5
|
|
776
|
+
case "html":
|
|
777
|
+
return "http://www.w3.org/TR/html4/strict.dtd"
|
|
778
|
+
case "svg":
|
|
779
|
+
return "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
|
|
780
|
+
case "math":
|
|
781
|
+
return "http://www.w3.org/Math/DTD/mathml2/mathml2.dtd"
|
|
782
|
+
default:
|
|
783
|
+
return ""
|
|
487
784
|
}
|
|
488
785
|
}
|
|
489
786
|
|
|
@@ -527,3 +824,55 @@ function buildTextChild(document: Document, node: Template.Text): globalThis.Nod
|
|
|
527
824
|
|
|
528
825
|
return document.createComment(`hole${node.index}`)
|
|
529
826
|
}
|
|
827
|
+
|
|
828
|
+
function createAttribute(
|
|
829
|
+
document: Document,
|
|
830
|
+
element: HTMLElement | SVGElement,
|
|
831
|
+
name: string
|
|
832
|
+
): Attr {
|
|
833
|
+
return element.getAttributeNode(name) ?? document.createAttribute(name)
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function matchSettablePart(
|
|
837
|
+
renderable: Renderable<any, any>,
|
|
838
|
+
setValue: (value: any) => void,
|
|
839
|
+
makePart: () => Part,
|
|
840
|
+
schedule: (f: () => void) => Effect.Effect<Scope, never, void>,
|
|
841
|
+
expect: () => void
|
|
842
|
+
) {
|
|
843
|
+
return matchRenderable(renderable, {
|
|
844
|
+
Fx: (fx) => {
|
|
845
|
+
expect()
|
|
846
|
+
return Fx.observe(fx, (a) => schedule(() => setValue(a)))
|
|
847
|
+
},
|
|
848
|
+
Effect: (effect) => {
|
|
849
|
+
expect()
|
|
850
|
+
return Effect.flatMap(effect, (a) => schedule(() => setValue(a)))
|
|
851
|
+
},
|
|
852
|
+
Directive: (directive) => {
|
|
853
|
+
expect()
|
|
854
|
+
return directive(makePart())
|
|
855
|
+
},
|
|
856
|
+
Otherwise: (otherwise) => {
|
|
857
|
+
setValue(otherwise)
|
|
858
|
+
return null
|
|
859
|
+
}
|
|
860
|
+
})
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function matchRenderable(renderable: Renderable<any, any>, matches: {
|
|
864
|
+
Fx: (fx: Fx.Fx<any, any, any>) => Effect.Effect<any, any, void> | null
|
|
865
|
+
Effect: (effect: Effect.Effect<any, any, any>) => Effect.Effect<any, any, void> | null
|
|
866
|
+
Directive: (directive: Directive<any, any>) => Effect.Effect<any, any, void> | null
|
|
867
|
+
Otherwise: (_: Renderable<any, any>) => Effect.Effect<any, any, void> | null
|
|
868
|
+
}): Effect.Effect<any, any, void> | null {
|
|
869
|
+
if (Fx.isFx(renderable)) {
|
|
870
|
+
return matches.Fx(renderable)
|
|
871
|
+
} else if (Effect.isEffect(renderable)) {
|
|
872
|
+
return matches.Effect(renderable)
|
|
873
|
+
} else if (isDirective<any, any>(renderable)) {
|
|
874
|
+
return matches.Directive(renderable)
|
|
875
|
+
} else {
|
|
876
|
+
return matches.Otherwise(renderable)
|
|
877
|
+
}
|
|
878
|
+
}
|