@typed/template 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/hydrate.js +49 -50
- 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 +60 -17
- 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 +486 -53
- 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/hydrate.d.ts +5 -5
- 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 +7 -7
- 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/hydrate.js +45 -46
- 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 +84 -17
- 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 +476 -58
- 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/hydrate.ts +68 -61
- package/src/internal/indexRefCounter.ts +63 -2
- package/src/internal/module-augmentation.ts +0 -4
- package/src/internal/parser.ts +92 -19
- package/src/internal/parts.ts +158 -73
- package/src/internal/render.ts +701 -89
- 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,28 @@
|
|
|
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"
|
|
7
|
+
import * as Context from "effect/Context"
|
|
8
8
|
import { replace } from "effect/ReadonlyArray"
|
|
9
|
-
import
|
|
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 {
|
|
17
|
+
AttributePart,
|
|
18
|
+
ClassNamePart,
|
|
19
|
+
CommentPart,
|
|
20
|
+
Part,
|
|
21
|
+
Parts,
|
|
22
|
+
PropertiesPart,
|
|
23
|
+
SparsePart,
|
|
24
|
+
StaticText
|
|
25
|
+
} from "../Part.js"
|
|
15
26
|
import type { Placeholder } from "../Placeholder.js"
|
|
16
27
|
import type { ToRendered } from "../Render.js"
|
|
17
28
|
import type { Renderable } from "../Renderable.js"
|
|
@@ -20,11 +31,11 @@ import type { RenderEvent } from "../RenderEvent.js"
|
|
|
20
31
|
import { DomRenderEvent } from "../RenderEvent.js"
|
|
21
32
|
import type { RenderTemplate } from "../RenderTemplate.js"
|
|
22
33
|
import type * as Template from "../Template.js"
|
|
23
|
-
import { TemplateInstance } from "../TemplateInstance.js"
|
|
24
34
|
import { makeRenderNodePart } from "./browser.js"
|
|
35
|
+
import { type EventSource, makeEventSource } from "./EventSource.js"
|
|
25
36
|
import { HydrateContext } from "./HydrateContext.js"
|
|
26
|
-
import type { IndexRefCounter } from "./indexRefCounter.js"
|
|
27
|
-
import {
|
|
37
|
+
import type { IndexRefCounter, IndexRefCounter2 } from "./indexRefCounter.js"
|
|
38
|
+
import { indexRefCounter2 } from "./indexRefCounter.js"
|
|
28
39
|
import { parse } from "./parser.js"
|
|
29
40
|
import {
|
|
30
41
|
AttributePartImpl,
|
|
@@ -33,6 +44,7 @@ import {
|
|
|
33
44
|
CommentPartImpl,
|
|
34
45
|
DataPartImpl,
|
|
35
46
|
EventPartImpl,
|
|
47
|
+
PropertiesPartImpl,
|
|
36
48
|
PropertyPartImpl,
|
|
37
49
|
RefPartImpl,
|
|
38
50
|
SparseAttributePartImpl,
|
|
@@ -42,51 +54,547 @@ import {
|
|
|
42
54
|
TextPartImpl
|
|
43
55
|
} from "./parts.js"
|
|
44
56
|
import type { ParentChildNodes } from "./utils.js"
|
|
45
|
-
import { findPath } from "./utils.js"
|
|
57
|
+
import { findHoleComment, findPath } from "./utils.js"
|
|
58
|
+
|
|
59
|
+
// TODO: We need to add support for hydration of templates
|
|
60
|
+
// TODO: We need to re-think hydration for dynamic lists, probably just markers should be fine
|
|
61
|
+
// TODO: We need to make Parts synchronous
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
type RenderPartContext = {
|
|
67
|
+
readonly context: Context.Context<Scope>
|
|
68
|
+
readonly document: Document
|
|
69
|
+
readonly eventSource: EventSource
|
|
70
|
+
readonly refCounter: IndexRefCounter2
|
|
71
|
+
readonly renderContext: RenderContext
|
|
72
|
+
readonly values: ReadonlyArray<Renderable<any, any>>
|
|
73
|
+
readonly onCause: (cause: Cause<unknown>) => Effect.Effect<never, never, void>
|
|
74
|
+
|
|
75
|
+
expected: number
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
type RenderPartMap = {
|
|
79
|
+
readonly [K in Template.PartNode["_tag"] | Template.SparsePartNode["_tag"]]: (
|
|
80
|
+
part: Extract<Template.PartNode | Template.SparsePartNode, { _tag: K }>,
|
|
81
|
+
node: Node,
|
|
82
|
+
ctx: RenderPartContext
|
|
83
|
+
) => null | Effect.Effect<any, any, void> | Array<Effect.Effect<any, any, void>>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const RenderPartMap: RenderPartMap = {
|
|
87
|
+
"attr": (templatePart, node, ctx) => {
|
|
88
|
+
const { document, refCounter, renderContext, values } = ctx
|
|
89
|
+
const element = node as HTMLElement | SVGElement
|
|
90
|
+
const attr = createAttribute(document, element, templatePart.name)
|
|
91
|
+
const renderable = values[templatePart.index]
|
|
92
|
+
let isSet = true
|
|
93
|
+
const setValue = (value: string | null | undefined) => {
|
|
94
|
+
if (isNullOrUndefined(value)) {
|
|
95
|
+
element.removeAttribute(templatePart.name)
|
|
96
|
+
isSet = false
|
|
97
|
+
} else {
|
|
98
|
+
attr.value = String(value)
|
|
99
|
+
if (isSet === false) {
|
|
100
|
+
element.setAttributeNode(attr)
|
|
101
|
+
isSet = true
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return matchSettablePart(
|
|
107
|
+
renderable,
|
|
108
|
+
setValue,
|
|
109
|
+
() => AttributePartImpl.browser(templatePart.index, element, templatePart.name, renderContext),
|
|
110
|
+
(f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
|
|
111
|
+
() => ctx.expected++
|
|
112
|
+
)
|
|
113
|
+
},
|
|
114
|
+
"boolean-part": (templatePart, node, ctx) => {
|
|
115
|
+
const { refCounter, renderContext, values } = ctx
|
|
116
|
+
const element = node as HTMLElement | SVGElement
|
|
117
|
+
const renderable = values[templatePart.index]
|
|
118
|
+
const setValue = (value: boolean | null | undefined) => {
|
|
119
|
+
element.toggleAttribute(templatePart.name, isNullOrUndefined(value) ? false : Boolean(value))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return matchSettablePart(
|
|
123
|
+
renderable,
|
|
124
|
+
setValue,
|
|
125
|
+
() => BooleanPartImpl.browser(templatePart.index, element, templatePart.name, renderContext),
|
|
126
|
+
(f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
|
|
127
|
+
() => ctx.expected++
|
|
128
|
+
)
|
|
129
|
+
},
|
|
130
|
+
"className-part": (templatePart, node, ctx) => {
|
|
131
|
+
const { refCounter, renderContext, values } = ctx
|
|
132
|
+
const element = node as HTMLElement | SVGElement
|
|
133
|
+
const renderable = values[templatePart.index]
|
|
134
|
+
let classNames: Set<string> = new Set()
|
|
135
|
+
const setValue = (value: string | Array<string> | null | undefined) => {
|
|
136
|
+
if (isNullOrUndefined(value)) {
|
|
137
|
+
element.classList.remove(...classNames)
|
|
138
|
+
classNames.clear()
|
|
139
|
+
} else {
|
|
140
|
+
const newClassNames = new Set(Array.isArray(value) ? value : [String(value)])
|
|
141
|
+
const { added, removed } = diffClassNames(classNames, newClassNames)
|
|
142
|
+
|
|
143
|
+
if (removed.length > 0) {
|
|
144
|
+
element.classList.remove(...removed)
|
|
145
|
+
}
|
|
146
|
+
if (added.length > 0) element.classList.add(...added)
|
|
147
|
+
|
|
148
|
+
classNames = newClassNames
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return matchSettablePart(
|
|
153
|
+
renderable,
|
|
154
|
+
setValue,
|
|
155
|
+
() => ClassNamePartImpl.browser(templatePart.index, element, renderContext),
|
|
156
|
+
(f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
|
|
157
|
+
() => ctx.expected++
|
|
158
|
+
)
|
|
159
|
+
},
|
|
160
|
+
"comment-part": (templatePart, node, ctx) => {
|
|
161
|
+
const { refCounter, renderContext, values } = ctx
|
|
162
|
+
const comment = findHoleComment(node as Element, templatePart.index)
|
|
163
|
+
const renderable = values[templatePart.index]
|
|
164
|
+
const setValue = (value: string | null | undefined) => {
|
|
165
|
+
comment.textContent = isNullOrUndefined(value) ? "" : String(value)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return matchSettablePart(
|
|
169
|
+
renderable,
|
|
170
|
+
setValue,
|
|
171
|
+
() => CommentPartImpl.browser(templatePart.index, comment, renderContext),
|
|
172
|
+
(f) => Effect.zipRight(renderContext.queue.add(comment, f), refCounter.release(templatePart.index)),
|
|
173
|
+
() => ctx.expected++
|
|
174
|
+
)
|
|
175
|
+
},
|
|
176
|
+
"data": (templatePart, node, ctx) => {
|
|
177
|
+
const element = node as HTMLElement | SVGElement
|
|
178
|
+
const renderable = ctx.values[templatePart.index]
|
|
179
|
+
const previousKeys = new Set<string>(Object.keys(element.dataset))
|
|
180
|
+
const setValue = (value: Record<string, string | undefined> | null | undefined) => {
|
|
181
|
+
if (isNullOrUndefined(value)) {
|
|
182
|
+
for (const key of previousKeys) {
|
|
183
|
+
delete element.dataset[key]
|
|
184
|
+
}
|
|
185
|
+
previousKeys.clear()
|
|
186
|
+
} else {
|
|
187
|
+
for (const key of previousKeys) {
|
|
188
|
+
if (!(key in value)) {
|
|
189
|
+
delete element.dataset[key]
|
|
190
|
+
previousKeys.delete(key)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const key of Object.keys(value)) {
|
|
195
|
+
if (!previousKeys.has(key)) {
|
|
196
|
+
previousKeys.add(key)
|
|
197
|
+
}
|
|
198
|
+
element.dataset[key] = value[key] || ""
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return matchSettablePart(
|
|
204
|
+
renderable,
|
|
205
|
+
setValue,
|
|
206
|
+
() => DataPartImpl.browser(templatePart.index, element, ctx.renderContext),
|
|
207
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(templatePart.index)),
|
|
208
|
+
() => ctx.expected++
|
|
209
|
+
)
|
|
210
|
+
},
|
|
211
|
+
"event": (templatePart, node, ctx) => {
|
|
212
|
+
const element = node as HTMLElement | SVGElement
|
|
213
|
+
const renderable = ctx.values[templatePart.index]
|
|
214
|
+
const handler = getEventHandler(renderable, ctx.context, ctx.onCause)
|
|
215
|
+
if (handler) {
|
|
216
|
+
ctx.eventSource.addEventListener(element, templatePart.name, handler)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return null
|
|
220
|
+
},
|
|
221
|
+
"node": (templatePart, node, ctx) => {
|
|
222
|
+
const part = makeRenderNodePart(
|
|
223
|
+
templatePart.index,
|
|
224
|
+
node as HTMLElement | SVGElement,
|
|
225
|
+
ctx.renderContext,
|
|
226
|
+
ctx.document,
|
|
227
|
+
false
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
ctx.expected++
|
|
231
|
+
|
|
232
|
+
return handlePart(
|
|
233
|
+
ctx.values[templatePart.index],
|
|
234
|
+
(value) => Effect.zipRight(part.update(value as any), ctx.refCounter.release(templatePart.index))
|
|
235
|
+
)
|
|
236
|
+
},
|
|
237
|
+
"property": (templatePart, node, ctx) => {
|
|
238
|
+
const element = node as HTMLElement | SVGElement
|
|
239
|
+
const renderable = ctx.values[templatePart.index]
|
|
240
|
+
const setValue = (value: string | null | undefined) => {
|
|
241
|
+
if (isNullOrUndefined(value)) {
|
|
242
|
+
delete (element as any)[templatePart.name]
|
|
243
|
+
} else {
|
|
244
|
+
;(element as any)[templatePart.name] = value
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return matchSettablePart(
|
|
249
|
+
renderable,
|
|
250
|
+
setValue,
|
|
251
|
+
() => PropertyPartImpl.browser(templatePart.index, element, templatePart.name, ctx.renderContext),
|
|
252
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(templatePart.index)),
|
|
253
|
+
() => ctx.expected++
|
|
254
|
+
)
|
|
255
|
+
},
|
|
256
|
+
"properties": (templatePart, node, ctx) => {
|
|
257
|
+
const renderable = ctx.values[templatePart.index] as any as Record<string, any>
|
|
258
|
+
|
|
259
|
+
if (isNullOrUndefined(renderable)) return null
|
|
260
|
+
else if (Fx.isFx(renderable) || Effect.isEffect(renderable)) {
|
|
261
|
+
throw new Error(`Properties Part must utilize an Record of renderable values.`)
|
|
262
|
+
} else if (typeof renderable === "object" && !Array.isArray(renderable)) {
|
|
263
|
+
const element = node as HTMLElement | SVGElement
|
|
264
|
+
|
|
265
|
+
const toggleBoolean = (key: string, value: unknown) => {
|
|
266
|
+
element.toggleAttribute(key, isNullOrUndefined(value) ? false : Boolean(value))
|
|
267
|
+
}
|
|
268
|
+
const setAttribute = (key: string, value: unknown) => {
|
|
269
|
+
if (isNullOrUndefined(value)) {
|
|
270
|
+
element.removeAttribute(key)
|
|
271
|
+
} else {
|
|
272
|
+
element.setAttribute(key, String(value))
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const setProperty = (key: string, value: unknown) => {
|
|
276
|
+
if (isNullOrUndefined(value)) {
|
|
277
|
+
delete (element as any)[key]
|
|
278
|
+
} else {
|
|
279
|
+
;(element as any)[key] = value
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const effects: Array<Effect.Effect<any, any, void>> = []
|
|
284
|
+
|
|
285
|
+
// We need indexes to track async values that won't conflict
|
|
286
|
+
// with any other Parts, we can start end of the current values.length
|
|
287
|
+
// As there should only ever be exactly 1 properties part.
|
|
288
|
+
let i = ctx.values.length
|
|
289
|
+
|
|
290
|
+
loop:
|
|
291
|
+
for (const [key, value] of Object.entries(renderable)) {
|
|
292
|
+
const index = ++i
|
|
293
|
+
|
|
294
|
+
switch (key[0]) {
|
|
295
|
+
case "?": {
|
|
296
|
+
const name = key.slice(1)
|
|
297
|
+
const eff = matchSettablePart(
|
|
298
|
+
value,
|
|
299
|
+
(value) => toggleBoolean(name, value),
|
|
300
|
+
() => BooleanPartImpl.browser(index, element, name, ctx.renderContext),
|
|
301
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
|
|
302
|
+
() => ctx.expected++
|
|
303
|
+
)
|
|
304
|
+
if (eff !== null) {
|
|
305
|
+
effects.push(eff)
|
|
306
|
+
}
|
|
307
|
+
continue loop
|
|
308
|
+
}
|
|
309
|
+
case ".": {
|
|
310
|
+
const name = key.slice(1)
|
|
311
|
+
const eff = matchSettablePart(
|
|
312
|
+
value,
|
|
313
|
+
(value) => setProperty(name, value),
|
|
314
|
+
() => PropertyPartImpl.browser(index, element, name, ctx.renderContext),
|
|
315
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
|
|
316
|
+
() => ctx.expected++
|
|
317
|
+
)
|
|
318
|
+
if (eff !== null) {
|
|
319
|
+
effects.push(eff)
|
|
320
|
+
}
|
|
321
|
+
continue loop
|
|
322
|
+
}
|
|
323
|
+
case "@": {
|
|
324
|
+
const name = key.slice(1)
|
|
325
|
+
const handler = getEventHandler(value, ctx.context, ctx.onCause)
|
|
326
|
+
if (handler) {
|
|
327
|
+
ctx.eventSource.addEventListener(element, name, handler)
|
|
328
|
+
}
|
|
329
|
+
continue loop
|
|
330
|
+
}
|
|
331
|
+
case "o": {
|
|
332
|
+
if (key[1] === "n") {
|
|
333
|
+
const name = key.slice(2)
|
|
334
|
+
const handler = getEventHandler(value, ctx.context, ctx.onCause)
|
|
335
|
+
if (handler) {
|
|
336
|
+
ctx.eventSource.addEventListener(element, name, handler)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
continue loop
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const eff = matchSettablePart(
|
|
344
|
+
value,
|
|
345
|
+
(value) => setAttribute(key, value),
|
|
346
|
+
() => AttributePartImpl.browser(index, element, key, ctx.renderContext),
|
|
347
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
|
|
348
|
+
() => ctx.expected++
|
|
349
|
+
)
|
|
350
|
+
if (eff !== null) {
|
|
351
|
+
effects.push(eff)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return effects
|
|
356
|
+
} else {
|
|
357
|
+
return null
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
"ref": (templatePart, node, ctx) => {
|
|
361
|
+
const element = node as HTMLElement | SVGElement
|
|
362
|
+
const renderable = ctx.values[templatePart.index]
|
|
363
|
+
|
|
364
|
+
if (isDirective(renderable)) {
|
|
365
|
+
return renderable(new RefPartImpl(ElementSource.fromElement(element), templatePart.index))
|
|
366
|
+
} else if (ElementRef.isElementRef(renderable)) {
|
|
367
|
+
return ElementRef.set(renderable, element)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return null
|
|
371
|
+
},
|
|
372
|
+
"sparse-attr": (templatePart, node, ctx) => {
|
|
373
|
+
const values = Array.from({ length: templatePart.nodes.length }, (): string => "")
|
|
374
|
+
const element = node as HTMLElement | SVGElement
|
|
375
|
+
const attr = createAttribute(ctx.document, element, templatePart.name)
|
|
376
|
+
|
|
377
|
+
const setValue = (value: string | null | undefined, index: number) =>
|
|
378
|
+
Effect.suspend(() => {
|
|
379
|
+
values[index] = value || ""
|
|
380
|
+
return ctx.renderContext.queue.add(element, () => attr.value = values.join(""))
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
const effects: Array<Effect.Effect<any, any, void>> = []
|
|
384
|
+
|
|
385
|
+
for (let i = 0; i < templatePart.nodes.length; ++i) {
|
|
386
|
+
const node = templatePart.nodes[i]
|
|
387
|
+
if (node._tag === "text") {
|
|
388
|
+
values[i] = node.value
|
|
389
|
+
} else {
|
|
390
|
+
const renderable = ctx.values[node.index]
|
|
391
|
+
const index = i
|
|
392
|
+
const effect = matchSettablePart(
|
|
393
|
+
renderable,
|
|
394
|
+
(value) => setValue(value, index),
|
|
395
|
+
() =>
|
|
396
|
+
new AttributePartImpl(
|
|
397
|
+
templatePart.name,
|
|
398
|
+
node.index,
|
|
399
|
+
({ value }) => setValue(value, index),
|
|
400
|
+
null
|
|
401
|
+
),
|
|
402
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(node.index)),
|
|
403
|
+
() => ctx.expected++
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
if (effect !== null) {
|
|
407
|
+
effects.push(effect)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return effects
|
|
413
|
+
},
|
|
414
|
+
"sparse-class-name": (templatePart, node, ctx) => {
|
|
415
|
+
const element = node as HTMLElement | SVGElement
|
|
416
|
+
|
|
417
|
+
const effects = templatePart.nodes.flatMap((node) => {
|
|
418
|
+
if (node._tag === "text") {
|
|
419
|
+
const split = splitClassNames(node.value)
|
|
420
|
+
if (split.length > 0) element.classList.add(...split)
|
|
421
|
+
return []
|
|
422
|
+
} else {
|
|
423
|
+
const eff = RenderPartMap[node._tag](node, element, ctx)
|
|
424
|
+
if (eff === null) return []
|
|
425
|
+
return Array.isArray(eff) ? eff : [eff]
|
|
426
|
+
}
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
return effects
|
|
430
|
+
},
|
|
431
|
+
"sparse-comment": (templatePart, node, ctx) => {
|
|
432
|
+
const values = Array.from({ length: templatePart.nodes.length }, (): string => "")
|
|
433
|
+
const comment = node as Comment
|
|
434
|
+
|
|
435
|
+
const setValue = (value: string | null | undefined, index: number) =>
|
|
436
|
+
Effect.suspend(() => {
|
|
437
|
+
values[index] = value || ""
|
|
438
|
+
return ctx.renderContext.queue.add(comment, () => comment.textContent = values.join(""))
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
const effects: Array<Effect.Effect<any, any, void>> = []
|
|
442
|
+
|
|
443
|
+
for (let i = 0; i < templatePart.nodes.length; ++i) {
|
|
444
|
+
const node = templatePart.nodes[i]
|
|
445
|
+
if (node._tag === "text") {
|
|
446
|
+
values[i] = node.value
|
|
447
|
+
} else {
|
|
448
|
+
const renderable = ctx.values[node.index]
|
|
449
|
+
const index = i
|
|
450
|
+
const effect = matchSettablePart(
|
|
451
|
+
renderable,
|
|
452
|
+
(value) => setValue(value, index),
|
|
453
|
+
() =>
|
|
454
|
+
new CommentPartImpl(
|
|
455
|
+
node.index,
|
|
456
|
+
({ value }) => setValue(value, index),
|
|
457
|
+
null
|
|
458
|
+
),
|
|
459
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(comment, f), ctx.refCounter.release(node.index)),
|
|
460
|
+
() => ctx.expected++
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
if (effect !== null) {
|
|
464
|
+
effects.push(effect)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return effects
|
|
470
|
+
},
|
|
471
|
+
"text-part": (templatePart, node, ctx) => {
|
|
472
|
+
const part = TextPartImpl.browser(
|
|
473
|
+
ctx.document,
|
|
474
|
+
templatePart.index,
|
|
475
|
+
node as HTMLElement | SVGElement,
|
|
476
|
+
ctx.renderContext
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
ctx.expected++
|
|
480
|
+
|
|
481
|
+
return handlePart(
|
|
482
|
+
ctx.values[templatePart.index],
|
|
483
|
+
(value) => Effect.zipRight(part.update(value as any), ctx.refCounter.release(templatePart.index))
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const SPACE_REGEXP = /\s+/g
|
|
489
|
+
|
|
490
|
+
function splitClassNames(value: string) {
|
|
491
|
+
return value.split(SPACE_REGEXP).flatMap((a) => {
|
|
492
|
+
const trimmed = a.trim()
|
|
493
|
+
return trimmed.length > 0 ? [trimmed] : []
|
|
494
|
+
})
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function isNullOrUndefined<T>(value: T | null | undefined): value is null | undefined {
|
|
498
|
+
return value === null || value === undefined
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function diffClassNames(oldClassNames: Set<string>, newClassNames: Set<string>) {
|
|
502
|
+
const added: Array<string> = []
|
|
503
|
+
const removed: Array<string> = []
|
|
504
|
+
|
|
505
|
+
for (const className of oldClassNames) {
|
|
506
|
+
if (!newClassNames.has(className)) {
|
|
507
|
+
removed.push(className)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
for (const className of newClassNames) {
|
|
512
|
+
if (!oldClassNames.has(className) && className.trim()) {
|
|
513
|
+
added.push(className)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return { added, removed }
|
|
518
|
+
}
|
|
46
519
|
|
|
47
520
|
/**
|
|
48
521
|
* Here for "standard" browser rendering, a TemplateInstance is effectively a live
|
|
49
522
|
* view into the contents rendered by the Template.
|
|
50
523
|
*/
|
|
51
|
-
export const renderTemplate: (document: Document,
|
|
52
|
-
(document,
|
|
524
|
+
export const renderTemplate: (document: Document, renderContext: RenderContext) => RenderTemplate =
|
|
525
|
+
(document, renderContext) =>
|
|
53
526
|
<Values extends ReadonlyArray<Renderable<any, any>>, T extends Rendered = Rendered>(
|
|
54
527
|
templateStrings: TemplateStringsArray,
|
|
55
|
-
values: Values
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
528
|
+
values: Values
|
|
529
|
+
) => {
|
|
530
|
+
const entry = getBrowserEntry(document, renderContext, templateStrings)
|
|
531
|
+
if (values.length === 0) {
|
|
532
|
+
return Fx.sync(() => DomRenderEvent(persistent(document.importNode(entry.content, true))))
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return Fx.make<Scope | Placeholder.Context<Values[number]>, Placeholder.Error<Values[number]>, RenderEvent>((
|
|
536
|
+
sink
|
|
537
|
+
) => {
|
|
538
|
+
return Effect.gen(function*(_) {
|
|
539
|
+
const content = document.importNode(entry.content, true)
|
|
540
|
+
const context = yield* _(Effect.context<Scope>())
|
|
541
|
+
const refCounter = yield* _(indexRefCounter2())
|
|
542
|
+
const ctx: RenderPartContext = {
|
|
543
|
+
context,
|
|
544
|
+
document,
|
|
545
|
+
eventSource: makeEventSource(),
|
|
546
|
+
expected: 0,
|
|
547
|
+
refCounter,
|
|
548
|
+
renderContext,
|
|
549
|
+
onCause: sink.onFailure as any,
|
|
550
|
+
values
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Connect our interpolated values to our template parts
|
|
554
|
+
const effects: Array<Effect.Effect<Scope | Placeholder.Context<Values[number]>, never, void>> = []
|
|
555
|
+
for (const [part, path] of entry.template.parts) {
|
|
556
|
+
const eff = RenderPartMap[part._tag](part as never, findPath(content, path), ctx)
|
|
557
|
+
if (eff !== null) {
|
|
558
|
+
effects.push(
|
|
559
|
+
...(Array.isArray(eff) ? eff : [eff]) as Array<
|
|
560
|
+
Effect.Effect<Scope | Placeholder.Context<Values[number]>, never, void>
|
|
561
|
+
>
|
|
562
|
+
)
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Fork any effects necessary
|
|
567
|
+
if (effects.length > 0) {
|
|
568
|
+
yield* _(Effect.forkAll(effects))
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// If there's anything to wait on and it's not already done, wait for an initial value
|
|
572
|
+
// for all asynchronous sources.
|
|
573
|
+
if (ctx.expected > 0 && (yield* _(refCounter.expect(ctx.expected)))) {
|
|
574
|
+
yield* _(refCounter.wait)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Create a persistent wire from our content
|
|
578
|
+
const wire = persistent(content) as T
|
|
77
579
|
|
|
78
|
-
|
|
79
|
-
|
|
580
|
+
// Set the element when it is ready
|
|
581
|
+
yield* _(ctx.eventSource.setup(wire, Context.get(context, Scope)))
|
|
80
582
|
|
|
81
|
-
|
|
82
|
-
|
|
583
|
+
// Emity our DomRenderEvent
|
|
584
|
+
yield* _(sink.onSuccess(DomRenderEvent(wire)))
|
|
585
|
+
|
|
586
|
+
// Ensure our templates last forever in the DOM environment
|
|
587
|
+
// so event listeners are kept attached to the current Scope.
|
|
588
|
+
yield* _(Effect.never)
|
|
589
|
+
})
|
|
83
590
|
})
|
|
591
|
+
}
|
|
84
592
|
|
|
85
593
|
export function renderValues<Values extends ReadonlyArray<Renderable<any, any>>>(
|
|
86
594
|
values: Values,
|
|
87
595
|
parts: Parts,
|
|
88
596
|
refCounter: IndexRefCounter,
|
|
89
|
-
|
|
597
|
+
ctx: Context.Context<any> | Context.Context<never>,
|
|
90
598
|
makeHydrateContext?: (index: number) => HydrateContext
|
|
91
599
|
): Effect.Effect<Placeholder.Context<Values[number]> | Scope, never, void> {
|
|
92
600
|
return Effect.all(parts.map((part, index) => {
|
|
@@ -101,11 +609,11 @@ export function renderValues<Values extends ReadonlyArray<Renderable<any, any>>>
|
|
|
101
609
|
values,
|
|
102
610
|
part,
|
|
103
611
|
refCounter,
|
|
104
|
-
|
|
612
|
+
ctx,
|
|
105
613
|
makeHydrateContext ? () => makeHydrateContext(index) : undefined
|
|
106
614
|
)
|
|
107
615
|
}
|
|
108
|
-
}))
|
|
616
|
+
}))
|
|
109
617
|
}
|
|
110
618
|
|
|
111
619
|
export function renderSparsePart(
|
|
@@ -130,63 +638,78 @@ export function renderPart<Values extends ReadonlyArray<Renderable<any, any>>>(
|
|
|
130
638
|
values: Values,
|
|
131
639
|
part: Part,
|
|
132
640
|
refCounter: IndexRefCounter,
|
|
133
|
-
|
|
641
|
+
ctx: Context.Context<any> | Context.Context<never>,
|
|
134
642
|
hydrateCtx?: () => HydrateContext
|
|
135
643
|
): Effect.Effect<any, never, void> {
|
|
136
644
|
const partIndex = part.index
|
|
137
645
|
const renderable = values[partIndex]
|
|
138
646
|
|
|
647
|
+
if (renderable === null || renderable === undefined) return refCounter.release(partIndex)
|
|
648
|
+
|
|
139
649
|
if (isDirective(renderable)) {
|
|
140
650
|
return renderable(part).pipe(
|
|
141
|
-
Effect.
|
|
651
|
+
Effect.flatMap(() => refCounter.release(partIndex)),
|
|
142
652
|
Effect.forkScoped
|
|
143
653
|
)
|
|
144
654
|
} else if (part._tag === "ref") {
|
|
145
655
|
return refCounter.release(partIndex)
|
|
146
656
|
} else if (part._tag === "event") {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
never
|
|
152
|
-
>
|
|
153
|
-
),
|
|
154
|
-
() => refCounter.release(partIndex)
|
|
155
|
-
)
|
|
156
|
-
} else if (part._tag === "node" && hydrateCtx) {
|
|
157
|
-
if (renderable === null || renderable === undefined) return refCounter.release(partIndex)
|
|
657
|
+
const handler = getEventHandler(renderable, ctx, part.onCause)
|
|
658
|
+
if (handler) {
|
|
659
|
+
part.addEventListener(handler)
|
|
660
|
+
}
|
|
158
661
|
|
|
662
|
+
return refCounter.release(partIndex)
|
|
663
|
+
} else if (part._tag === "node" && hydrateCtx) {
|
|
159
664
|
return handlePart(
|
|
160
|
-
|
|
161
|
-
(value) => Effect.
|
|
665
|
+
renderable,
|
|
666
|
+
(value) => Effect.flatMap(part.update(value), () => refCounter.release(partIndex))
|
|
162
667
|
).pipe(
|
|
163
668
|
HydrateContext.provide(hydrateCtx()),
|
|
164
669
|
Effect.forkScoped
|
|
165
670
|
)
|
|
671
|
+
} else if (part._tag === "properties") {
|
|
672
|
+
return handlePropertiesPart(renderable, part, refCounter)
|
|
166
673
|
} else {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
674
|
+
return handlePart(
|
|
675
|
+
renderable,
|
|
676
|
+
(value) => Effect.flatMap(part.update(value as any), () => refCounter.release(partIndex))
|
|
677
|
+
)
|
|
678
|
+
}
|
|
679
|
+
}
|
|
170
680
|
|
|
681
|
+
function handlePropertiesPart<R, E>(
|
|
682
|
+
renderable: unknown,
|
|
683
|
+
part: PropertiesPart,
|
|
684
|
+
refCounter: IndexRefCounter
|
|
685
|
+
): Effect.Effect<R | Scope, E, void> {
|
|
686
|
+
if (renderable && typeof renderable === "object") {
|
|
171
687
|
return handlePart(
|
|
172
|
-
|
|
173
|
-
(value) => Effect.tap(part.update(value as any), () => refCounter.release(
|
|
688
|
+
Fx.struct(Object.fromEntries(Object.entries(renderable).map(([k, v]) => [k, unwrapRenderable(v)] as const))),
|
|
689
|
+
(value) => Effect.tap(part.update(value as any), () => refCounter.release(part.index))
|
|
174
690
|
)
|
|
175
691
|
}
|
|
692
|
+
|
|
693
|
+
return Effect.succeed(void 0)
|
|
176
694
|
}
|
|
177
695
|
|
|
178
696
|
function getEventHandler<R, E>(
|
|
179
697
|
renderable: any,
|
|
698
|
+
ctx: Context.Context<any> | Context.Context<never>,
|
|
180
699
|
onCause: (cause: Cause<E>) => Effect.Effect<never, never, unknown>
|
|
181
|
-
): EventHandler.EventHandler<
|
|
700
|
+
): EventHandler.EventHandler<never, never> | null {
|
|
182
701
|
if (renderable && typeof renderable === "object") {
|
|
183
702
|
if (EventHandler.EventHandlerTypeId in renderable) {
|
|
184
703
|
return EventHandler.make(
|
|
185
|
-
(ev) =>
|
|
704
|
+
(ev) =>
|
|
705
|
+
Effect.provide(
|
|
706
|
+
Effect.catchAllCause((renderable as EventHandler.EventHandler<R, E>).handler(ev), onCause),
|
|
707
|
+
ctx as any
|
|
708
|
+
),
|
|
186
709
|
(renderable as EventHandler.EventHandler<R, E>).options
|
|
187
710
|
)
|
|
188
711
|
} else if (Effect.EffectTypeId in renderable) {
|
|
189
|
-
return EventHandler.make(() => Effect.catchAllCause(renderable, onCause))
|
|
712
|
+
return EventHandler.make(() => Effect.provide(Effect.catchAllCause(renderable, onCause), ctx))
|
|
190
713
|
}
|
|
191
714
|
}
|
|
192
715
|
|
|
@@ -204,7 +727,7 @@ function handlePart<R, E>(
|
|
|
204
727
|
else if (Array.isArray(renderable)) {
|
|
205
728
|
return renderable.length === 0
|
|
206
729
|
? update(null)
|
|
207
|
-
: Effect.forkScoped(Fx.observe(Fx.
|
|
730
|
+
: Effect.forkScoped(Fx.observe(Fx.tuple(renderable.map(unwrapRenderable)) as any, update))
|
|
208
731
|
} else if (TypeId in renderable) {
|
|
209
732
|
return Effect.forkScoped(Fx.observe(renderable as any, update))
|
|
210
733
|
} else if (Effect.EffectTypeId in renderable) {
|
|
@@ -222,7 +745,7 @@ function unwrapRenderable<R, E>(renderable: unknown): Fx.Fx<R, E, any> {
|
|
|
222
745
|
case "object": {
|
|
223
746
|
if (renderable === null || renderable === undefined) return Fx.succeed(null)
|
|
224
747
|
else if (Array.isArray(renderable)) {
|
|
225
|
-
return renderable.length === 0 ? Fx.succeed(null) : Fx.
|
|
748
|
+
return renderable.length === 0 ? Fx.succeed(null) : Fx.tuple(renderable.map(unwrapRenderable)) as any
|
|
226
749
|
} else if (TypeId in renderable) {
|
|
227
750
|
return renderable as any
|
|
228
751
|
} else if (Effect.EffectTypeId in renderable) {
|
|
@@ -238,7 +761,7 @@ function unwrapSparsePartRenderables(
|
|
|
238
761
|
renderables: ReadonlyArray<Renderable<any, any>>,
|
|
239
762
|
part: SparsePart
|
|
240
763
|
) {
|
|
241
|
-
return Fx.
|
|
764
|
+
return Fx.tuple(
|
|
242
765
|
// @ts-ignore type too deep
|
|
243
766
|
renderables.map((renderable, i) => {
|
|
244
767
|
const p = part.parts[i]
|
|
@@ -321,52 +844,56 @@ export function getBrowserEntry(
|
|
|
321
844
|
}
|
|
322
845
|
}
|
|
323
846
|
|
|
324
|
-
export function buildParts<
|
|
847
|
+
export function buildParts<E>(
|
|
325
848
|
document: Document,
|
|
326
849
|
ctx: RenderContext,
|
|
327
850
|
template: Template.Template,
|
|
328
851
|
content: ParentChildNodes,
|
|
329
|
-
|
|
852
|
+
eventSource: EventSource,
|
|
330
853
|
onCause: (cause: Cause<E>) => Effect.Effect<never, never, void>,
|
|
331
854
|
isHydrating: boolean
|
|
332
|
-
):
|
|
333
|
-
return
|
|
334
|
-
|
|
335
|
-
buildPartWithNode(document, ctx, part, findPath(content, path), ref, onCause, isHydrating)
|
|
336
|
-
)
|
|
855
|
+
): Parts {
|
|
856
|
+
return template.parts.map(([part, path]) =>
|
|
857
|
+
buildPartWithNode(document, ctx, part, findPath(content, path), eventSource, onCause, isHydrating)
|
|
337
858
|
)
|
|
338
859
|
}
|
|
339
860
|
|
|
340
|
-
function buildPartWithNode<
|
|
861
|
+
function buildPartWithNode<E>(
|
|
341
862
|
document: Document,
|
|
342
863
|
ctx: RenderContext,
|
|
343
864
|
part: Template.PartNode | Template.SparsePartNode,
|
|
344
865
|
node: Node,
|
|
345
|
-
|
|
866
|
+
eventSource: EventSource,
|
|
346
867
|
onCause: (cause: Cause<E>) => Effect.Effect<never, never, void>,
|
|
347
868
|
isHydrating: boolean
|
|
348
|
-
):
|
|
869
|
+
): Part | SparsePart {
|
|
349
870
|
switch (part._tag) {
|
|
350
871
|
case "attr":
|
|
351
|
-
return
|
|
872
|
+
return AttributePartImpl.browser(part.index, node as Element, part.name, ctx)
|
|
352
873
|
case "boolean-part":
|
|
353
|
-
return
|
|
874
|
+
return BooleanPartImpl.browser(part.index, node as Element, part.name, ctx)
|
|
354
875
|
case "className-part":
|
|
355
|
-
return
|
|
876
|
+
return ClassNamePartImpl.browser(part.index, node as Element, ctx)
|
|
356
877
|
case "comment-part":
|
|
357
|
-
return
|
|
878
|
+
return CommentPartImpl.browser(part.index, node as Comment, ctx)
|
|
358
879
|
case "data":
|
|
359
|
-
return
|
|
880
|
+
return DataPartImpl.browser(part.index, node as HTMLElement | SVGElement, ctx)
|
|
360
881
|
case "event":
|
|
361
|
-
return EventPartImpl
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
882
|
+
return new EventPartImpl(
|
|
883
|
+
part.name,
|
|
884
|
+
part.index,
|
|
885
|
+
ElementSource.fromElement(node as Element),
|
|
886
|
+
onCause as any,
|
|
887
|
+
(handler) => eventSource.addEventListener(node as Element, part.name, handler)
|
|
365
888
|
)
|
|
889
|
+
case "node":
|
|
890
|
+
return makeRenderNodePart(part.index, node as HTMLElement | SVGElement, ctx, document, isHydrating)
|
|
366
891
|
case "property":
|
|
367
|
-
return
|
|
892
|
+
return PropertyPartImpl.browser(part.index, node, part.name, ctx)
|
|
893
|
+
case "properties":
|
|
894
|
+
return PropertiesPartImpl.browser(part.index, node as HTMLElement | SVGElement, ctx)
|
|
368
895
|
case "ref":
|
|
369
|
-
return
|
|
896
|
+
return new RefPartImpl(ElementSource.fromElement(node as Element), part.index) as any
|
|
370
897
|
case "sparse-attr": {
|
|
371
898
|
const parts: Array<AttributePart | StaticText> = Array(part.nodes.length)
|
|
372
899
|
const sparse = SparseAttributePartImpl.browser(
|
|
@@ -394,7 +921,7 @@ function buildPartWithNode<T extends Rendered, E>(
|
|
|
394
921
|
}
|
|
395
922
|
}
|
|
396
923
|
|
|
397
|
-
return
|
|
924
|
+
return sparse
|
|
398
925
|
}
|
|
399
926
|
case "sparse-class-name": {
|
|
400
927
|
const parts: Array<ClassNamePart | StaticText> = []
|
|
@@ -424,7 +951,7 @@ function buildPartWithNode<T extends Rendered, E>(
|
|
|
424
951
|
}
|
|
425
952
|
}
|
|
426
953
|
|
|
427
|
-
return
|
|
954
|
+
return sparse
|
|
428
955
|
}
|
|
429
956
|
case "sparse-comment": {
|
|
430
957
|
const parts: Array<CommentPart | StaticText> = Array(part.nodes.length)
|
|
@@ -451,10 +978,10 @@ function buildPartWithNode<T extends Rendered, E>(
|
|
|
451
978
|
}
|
|
452
979
|
}
|
|
453
980
|
|
|
454
|
-
return
|
|
981
|
+
return sparse
|
|
455
982
|
}
|
|
456
983
|
case "text-part":
|
|
457
|
-
return
|
|
984
|
+
return TextPartImpl.browser(document, part.index, node as Element, ctx)
|
|
458
985
|
}
|
|
459
986
|
}
|
|
460
987
|
|
|
@@ -484,6 +1011,39 @@ function buildNode(document: Document, node: Template.Node, isSvgContext: boolea
|
|
|
484
1011
|
case "comment-part":
|
|
485
1012
|
case "node":
|
|
486
1013
|
return document.createComment(`hole${node.index}`)
|
|
1014
|
+
case "doctype":
|
|
1015
|
+
return document.implementation.createDocumentType(
|
|
1016
|
+
node.name,
|
|
1017
|
+
docTypeNameToPublicId(node.name),
|
|
1018
|
+
docTypeNameToSystemId(node.name)
|
|
1019
|
+
)
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function docTypeNameToPublicId(name: string): string {
|
|
1024
|
+
switch (name) {
|
|
1025
|
+
case "html":
|
|
1026
|
+
return "-//W3C//DTD HTML 4.01//EN"
|
|
1027
|
+
case "svg":
|
|
1028
|
+
return "-//W3C//DTD SVG 1.1//EN"
|
|
1029
|
+
case "math":
|
|
1030
|
+
return "-//W3C//DTD MathML 2.0//EN"
|
|
1031
|
+
default:
|
|
1032
|
+
return ""
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function docTypeNameToSystemId(name: string): string {
|
|
1037
|
+
switch (name) {
|
|
1038
|
+
// HTML5
|
|
1039
|
+
case "html":
|
|
1040
|
+
return "http://www.w3.org/TR/html4/strict.dtd"
|
|
1041
|
+
case "svg":
|
|
1042
|
+
return "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
|
|
1043
|
+
case "math":
|
|
1044
|
+
return "http://www.w3.org/Math/DTD/mathml2/mathml2.dtd"
|
|
1045
|
+
default:
|
|
1046
|
+
return ""
|
|
487
1047
|
}
|
|
488
1048
|
}
|
|
489
1049
|
|
|
@@ -527,3 +1087,55 @@ function buildTextChild(document: Document, node: Template.Text): globalThis.Nod
|
|
|
527
1087
|
|
|
528
1088
|
return document.createComment(`hole${node.index}`)
|
|
529
1089
|
}
|
|
1090
|
+
|
|
1091
|
+
function createAttribute(
|
|
1092
|
+
document: Document,
|
|
1093
|
+
element: HTMLElement | SVGElement,
|
|
1094
|
+
name: string
|
|
1095
|
+
): Attr {
|
|
1096
|
+
return element.getAttributeNode(name) ?? document.createAttribute(name)
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function matchSettablePart(
|
|
1100
|
+
renderable: Renderable<any, any>,
|
|
1101
|
+
setValue: (value: any) => void,
|
|
1102
|
+
makePart: () => Part,
|
|
1103
|
+
schedule: (f: () => void) => Effect.Effect<Scope, never, void>,
|
|
1104
|
+
expect: () => void
|
|
1105
|
+
) {
|
|
1106
|
+
return matchRenderable(renderable, {
|
|
1107
|
+
Fx: (fx) => {
|
|
1108
|
+
expect()
|
|
1109
|
+
return Fx.observe(fx, (a) => schedule(() => setValue(a)))
|
|
1110
|
+
},
|
|
1111
|
+
Effect: (effect) => {
|
|
1112
|
+
expect()
|
|
1113
|
+
return Effect.flatMap(effect, (a) => schedule(() => setValue(a)))
|
|
1114
|
+
},
|
|
1115
|
+
Directive: (directive) => {
|
|
1116
|
+
expect()
|
|
1117
|
+
return directive(makePart())
|
|
1118
|
+
},
|
|
1119
|
+
Otherwise: (otherwise) => {
|
|
1120
|
+
setValue(otherwise)
|
|
1121
|
+
return null
|
|
1122
|
+
}
|
|
1123
|
+
})
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function matchRenderable(renderable: Renderable<any, any>, matches: {
|
|
1127
|
+
Fx: (fx: Fx.Fx<any, any, any>) => Effect.Effect<any, any, void> | null
|
|
1128
|
+
Effect: (effect: Effect.Effect<any, any, any>) => Effect.Effect<any, any, void> | null
|
|
1129
|
+
Directive: (directive: Directive<any, any>) => Effect.Effect<any, any, void> | null
|
|
1130
|
+
Otherwise: (_: Renderable<any, any>) => Effect.Effect<any, any, void> | null
|
|
1131
|
+
}): Effect.Effect<any, any, void> | null {
|
|
1132
|
+
if (Fx.isFx(renderable)) {
|
|
1133
|
+
return matches.Fx(renderable)
|
|
1134
|
+
} else if (Effect.isEffect(renderable)) {
|
|
1135
|
+
return matches.Effect(renderable)
|
|
1136
|
+
} else if (isDirective<any, any>(renderable)) {
|
|
1137
|
+
return matches.Directive(renderable)
|
|
1138
|
+
} else {
|
|
1139
|
+
return matches.Otherwise(renderable)
|
|
1140
|
+
}
|
|
1141
|
+
}
|