@typed/template 0.9.6 → 0.10.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/RenderQueue/package.json +6 -0
- package/dist/cjs/Directive.js +1 -1
- package/dist/cjs/Directive.js.map +1 -1
- package/dist/cjs/ElementRef.js +1 -1
- package/dist/cjs/ElementRef.js.map +1 -1
- package/dist/cjs/ElementSource.js +1 -1
- package/dist/cjs/ElementSource.js.map +1 -1
- package/dist/cjs/EventHandler.js +11 -4
- package/dist/cjs/EventHandler.js.map +1 -1
- package/dist/cjs/Html.js +84 -44
- package/dist/cjs/Html.js.map +1 -1
- package/dist/cjs/HtmlChunk.js +67 -21
- package/dist/cjs/HtmlChunk.js.map +1 -1
- package/dist/cjs/Hydrate.js +6 -6
- package/dist/cjs/Hydrate.js.map +1 -1
- package/dist/cjs/Many.js +4 -4
- package/dist/cjs/Many.js.map +1 -1
- package/dist/cjs/Meta.js +10 -3
- package/dist/cjs/Meta.js.map +1 -1
- package/dist/cjs/Parser.js +1 -1
- package/dist/cjs/Placeholder.js +5 -9
- package/dist/cjs/Placeholder.js.map +1 -1
- package/dist/cjs/Platform.js +7 -5
- package/dist/cjs/Platform.js.map +1 -1
- package/dist/cjs/Render.js +8 -7
- package/dist/cjs/Render.js.map +1 -1
- package/dist/cjs/RenderContext.js +8 -92
- package/dist/cjs/RenderContext.js.map +1 -1
- package/dist/cjs/RenderEvent.js +9 -1
- package/dist/cjs/RenderEvent.js.map +1 -1
- package/dist/cjs/RenderQueue.js +341 -0
- package/dist/cjs/RenderQueue.js.map +1 -0
- package/dist/cjs/RenderTemplate.js +1 -1
- package/dist/cjs/RenderTemplate.js.map +1 -1
- package/dist/cjs/Template.js +12 -0
- package/dist/cjs/Template.js.map +1 -1
- package/dist/cjs/Test.js +64 -33
- package/dist/cjs/Test.js.map +1 -1
- package/dist/cjs/Vitest.js +12 -20
- package/dist/cjs/Vitest.js.map +1 -1
- package/dist/cjs/index.js +6 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/EventSource.js +16 -9
- package/dist/cjs/internal/EventSource.js.map +1 -1
- package/dist/cjs/internal/HydrateContext.js.map +1 -1
- package/dist/cjs/internal/browser.js +11 -10
- package/dist/cjs/internal/browser.js.map +1 -1
- package/dist/cjs/internal/character-entities.js +2141 -0
- package/dist/cjs/internal/character-entities.js.map +1 -0
- package/dist/cjs/internal/errors.js +19 -2
- package/dist/cjs/internal/errors.js.map +1 -1
- package/dist/cjs/internal/indexRefCounter.js +36 -63
- package/dist/cjs/internal/indexRefCounter.js.map +1 -1
- package/dist/cjs/internal/parser.js +18 -17
- package/dist/cjs/internal/parser.js.map +1 -1
- package/dist/cjs/internal/parser2.js +382 -0
- package/dist/cjs/internal/parser2.js.map +1 -0
- package/dist/cjs/internal/server-parts.js +124 -0
- package/dist/cjs/internal/server-parts.js.map +1 -0
- package/dist/cjs/internal/server.js +15 -185
- package/dist/cjs/internal/server.js.map +1 -1
- package/dist/cjs/internal/utils.js +73 -9
- package/dist/cjs/internal/utils.js.map +1 -1
- package/dist/cjs/internal/v2/SyncPart.js +6 -0
- package/dist/cjs/internal/v2/SyncPart.js.map +1 -0
- package/dist/cjs/internal/v2/helpers.js +15 -0
- package/dist/cjs/internal/v2/helpers.js.map +1 -0
- package/dist/cjs/internal/v2/hydrate.js +202 -0
- package/dist/cjs/internal/v2/hydrate.js.map +1 -0
- package/dist/cjs/internal/v2/hydration-template.js +269 -0
- package/dist/cjs/internal/v2/hydration-template.js.map +1 -0
- package/dist/cjs/internal/v2/parts.js +169 -0
- package/dist/cjs/internal/v2/parts.js.map +1 -0
- package/dist/cjs/internal/v2/render-entry.js +110 -0
- package/dist/cjs/internal/v2/render-entry.js.map +1 -0
- package/dist/cjs/internal/v2/render-sync-parts.js +318 -0
- package/dist/cjs/internal/v2/render-sync-parts.js.map +1 -0
- package/dist/cjs/internal/v2/render.js +417 -0
- package/dist/cjs/internal/v2/render.js.map +1 -0
- package/dist/cjs/internal/v2/sync-parts.js +115 -0
- package/dist/cjs/internal/v2/sync-parts.js.map +1 -0
- package/dist/dts/ElementRef.d.ts +1 -1
- package/dist/dts/ElementRef.d.ts.map +1 -1
- package/dist/dts/ElementSource.d.ts +1 -1
- package/dist/dts/ElementSource.d.ts.map +1 -1
- package/dist/dts/EventHandler.d.ts +12 -8
- package/dist/dts/EventHandler.d.ts.map +1 -1
- package/dist/dts/Html.d.ts +6 -5
- package/dist/dts/Html.d.ts.map +1 -1
- package/dist/dts/HtmlChunk.d.ts.map +1 -1
- package/dist/dts/Hydrate.d.ts +1 -3
- package/dist/dts/Hydrate.d.ts.map +1 -1
- package/dist/dts/Many.d.ts +9 -11
- package/dist/dts/Many.d.ts.map +1 -1
- package/dist/dts/Meta.d.ts +5 -1
- package/dist/dts/Meta.d.ts.map +1 -1
- package/dist/dts/Parser.d.ts +1 -1
- package/dist/dts/Parser.d.ts.map +1 -1
- package/dist/dts/Part.d.ts +20 -56
- package/dist/dts/Part.d.ts.map +1 -1
- package/dist/dts/Placeholder.d.ts +6 -10
- package/dist/dts/Placeholder.d.ts.map +1 -1
- package/dist/dts/Platform.d.ts +2 -4
- package/dist/dts/Platform.d.ts.map +1 -1
- package/dist/dts/Render.d.ts +4 -8
- package/dist/dts/Render.d.ts.map +1 -1
- package/dist/dts/RenderContext.d.ts +3 -22
- package/dist/dts/RenderContext.d.ts.map +1 -1
- package/dist/dts/RenderEvent.d.ts +6 -1
- package/dist/dts/RenderEvent.d.ts.map +1 -1
- package/dist/dts/RenderQueue.d.ts +103 -0
- package/dist/dts/RenderQueue.d.ts.map +1 -0
- package/dist/dts/RenderTemplate.d.ts +3 -2
- package/dist/dts/RenderTemplate.d.ts.map +1 -1
- package/dist/dts/Renderable.d.ts +1 -1
- package/dist/dts/Template.d.ts +14 -1
- package/dist/dts/Template.d.ts.map +1 -1
- package/dist/dts/Test.d.ts +14 -1
- package/dist/dts/Test.d.ts.map +1 -1
- package/dist/dts/Vitest.d.ts +11 -8
- package/dist/dts/Vitest.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/EventSource.d.ts +2 -1
- package/dist/dts/internal/EventSource.d.ts.map +1 -1
- package/dist/dts/internal/browser.d.ts +3 -3
- package/dist/dts/internal/browser.d.ts.map +1 -1
- package/dist/dts/internal/character-entities.d.ts +2133 -0
- package/dist/dts/internal/character-entities.d.ts.map +1 -0
- package/dist/dts/internal/errors.d.ts +9 -1
- package/dist/dts/internal/errors.d.ts.map +1 -1
- package/dist/dts/internal/indexRefCounter.d.ts +0 -4
- package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
- package/dist/dts/internal/parser.d.ts +13 -0
- package/dist/dts/internal/parser.d.ts.map +1 -1
- package/dist/dts/internal/parser2.d.ts +12 -0
- package/dist/dts/internal/parser2.d.ts.map +1 -0
- package/dist/dts/internal/server-parts.d.ts +223 -0
- package/dist/dts/internal/server-parts.d.ts.map +1 -0
- package/dist/dts/internal/server.d.ts +2 -28
- package/dist/dts/internal/server.d.ts.map +1 -1
- package/dist/dts/internal/utils.d.ts +4 -1
- package/dist/dts/internal/utils.d.ts.map +1 -1
- package/dist/dts/internal/v2/SyncPart.d.ts +87 -0
- package/dist/dts/internal/v2/SyncPart.d.ts.map +1 -0
- package/dist/dts/internal/v2/helpers.d.ts +3 -0
- package/dist/dts/internal/v2/helpers.d.ts.map +1 -0
- package/dist/dts/internal/v2/hydrate.d.ts +7 -0
- package/dist/dts/internal/v2/hydrate.d.ts.map +1 -0
- package/dist/dts/internal/v2/hydration-template.d.ts +54 -0
- package/dist/dts/internal/v2/hydration-template.d.ts.map +1 -0
- package/dist/dts/internal/v2/parts.d.ts +245 -0
- package/dist/dts/internal/v2/parts.d.ts.map +1 -0
- package/dist/dts/internal/v2/render-entry.d.ts +6 -0
- package/dist/dts/internal/v2/render-entry.d.ts.map +1 -0
- package/dist/dts/internal/v2/render-sync-parts.d.ts +22 -0
- package/dist/dts/internal/v2/render-sync-parts.d.ts.map +1 -0
- package/dist/dts/internal/v2/render.d.ts +62 -0
- package/dist/dts/internal/v2/render.d.ts.map +1 -0
- package/dist/dts/internal/v2/sync-parts.d.ts +129 -0
- package/dist/dts/internal/v2/sync-parts.d.ts.map +1 -0
- package/dist/esm/ElementRef.js.map +1 -1
- package/dist/esm/EventHandler.js +14 -4
- package/dist/esm/EventHandler.js.map +1 -1
- package/dist/esm/Html.js +91 -50
- package/dist/esm/Html.js.map +1 -1
- package/dist/esm/HtmlChunk.js +75 -24
- package/dist/esm/HtmlChunk.js.map +1 -1
- package/dist/esm/Hydrate.js +5 -5
- package/dist/esm/Hydrate.js.map +1 -1
- package/dist/esm/Many.js +3 -3
- package/dist/esm/Many.js.map +1 -1
- package/dist/esm/Meta.js +7 -1
- package/dist/esm/Meta.js.map +1 -1
- package/dist/esm/Parser.js +1 -1
- package/dist/esm/Parser.js.map +1 -1
- package/dist/esm/Placeholder.js +4 -8
- package/dist/esm/Placeholder.js.map +1 -1
- package/dist/esm/Platform.js +3 -1
- package/dist/esm/Platform.js.map +1 -1
- package/dist/esm/Render.js +6 -5
- package/dist/esm/Render.js.map +1 -1
- package/dist/esm/RenderContext.js +5 -85
- package/dist/esm/RenderContext.js.map +1 -1
- package/dist/esm/RenderEvent.js +8 -1
- package/dist/esm/RenderEvent.js.map +1 -1
- package/dist/esm/RenderQueue.js +336 -0
- package/dist/esm/RenderQueue.js.map +1 -0
- package/dist/esm/RenderTemplate.js.map +1 -1
- package/dist/esm/Template.js +12 -0
- package/dist/esm/Template.js.map +1 -1
- package/dist/esm/Test.js +71 -30
- package/dist/esm/Test.js.map +1 -1
- package/dist/esm/Vitest.js +11 -8
- package/dist/esm/Vitest.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/EventSource.js +12 -7
- package/dist/esm/internal/EventSource.js.map +1 -1
- package/dist/esm/internal/HydrateContext.js.map +1 -1
- package/dist/esm/internal/browser.js +10 -9
- package/dist/esm/internal/browser.js.map +1 -1
- package/dist/esm/internal/character-entities.js +2134 -0
- package/dist/esm/internal/character-entities.js.map +1 -0
- package/dist/esm/internal/errors.js +22 -2
- package/dist/esm/internal/errors.js.map +1 -1
- package/dist/esm/internal/indexRefCounter.js +36 -61
- package/dist/esm/internal/indexRefCounter.js.map +1 -1
- package/dist/esm/internal/parser.js +18 -18
- package/dist/esm/internal/parser.js.map +1 -1
- package/dist/esm/internal/parser2.js +393 -0
- package/dist/esm/internal/parser2.js.map +1 -0
- package/dist/esm/internal/server-parts.js +109 -0
- package/dist/esm/internal/server-parts.js.map +1 -0
- package/dist/esm/internal/server.js +12 -161
- package/dist/esm/internal/server.js.map +1 -1
- package/dist/esm/internal/utils.js +71 -7
- package/dist/esm/internal/utils.js.map +1 -1
- package/dist/esm/internal/v2/SyncPart.js +5 -0
- package/dist/esm/internal/v2/SyncPart.js.map +1 -0
- package/dist/esm/internal/v2/helpers.js +12 -0
- package/dist/esm/internal/v2/helpers.js.map +1 -0
- package/dist/esm/internal/v2/hydrate.js +195 -0
- package/dist/esm/internal/v2/hydrate.js.map +1 -0
- package/dist/esm/internal/v2/hydration-template.js +265 -0
- package/dist/esm/internal/v2/hydration-template.js.map +1 -0
- package/dist/esm/internal/v2/parts.js +150 -0
- package/dist/esm/internal/v2/parts.js.map +1 -0
- package/dist/esm/internal/v2/render-entry.js +102 -0
- package/dist/esm/internal/v2/render-entry.js.map +1 -0
- package/dist/esm/internal/v2/render-sync-parts.js +265 -0
- package/dist/esm/internal/v2/render-sync-parts.js.map +1 -0
- package/dist/esm/internal/v2/render.js +353 -0
- package/dist/esm/internal/v2/render.js.map +1 -0
- package/dist/esm/internal/v2/sync-parts.js +102 -0
- package/dist/esm/internal/v2/sync-parts.js.map +1 -0
- package/package.json +20 -13
- package/src/ElementRef.ts +1 -1
- package/src/ElementSource.ts +1 -1
- package/src/EventHandler.ts +29 -11
- package/src/Html.ts +199 -90
- package/src/HtmlChunk.ts +77 -30
- package/src/Hydrate.ts +20 -14
- package/src/Many.ts +17 -14
- package/src/Meta.ts +8 -1
- package/src/Parser.ts +1 -1
- package/src/Part.ts +22 -66
- package/src/Placeholder.ts +17 -15
- package/src/Platform.ts +5 -5
- package/src/Render.ts +23 -26
- package/src/RenderContext.ts +14 -142
- package/src/RenderEvent.ts +10 -1
- package/src/RenderQueue.ts +445 -0
- package/src/RenderTemplate.ts +7 -2
- package/src/Renderable.ts +1 -1
- package/src/Template.ts +15 -1
- package/src/Test.ts +122 -38
- package/src/Vitest.ts +20 -10
- package/src/index.ts +4 -0
- package/src/internal/EventSource.ts +14 -8
- package/src/internal/HydrateContext.ts +3 -4
- package/src/internal/browser.ts +26 -21
- package/src/internal/character-entities.ts +2136 -0
- package/src/internal/errors.ts +30 -3
- package/src/internal/indexRefCounter.ts +38 -70
- package/src/internal/parser.ts +19 -19
- package/src/internal/parser2.ts +468 -0
- package/src/internal/server-parts.ts +161 -0
- package/src/internal/server.ts +16 -272
- package/src/internal/utils.ts +83 -7
- package/src/internal/v2/SyncPart.ts +112 -0
- package/src/internal/v2/helpers.ts +13 -0
- package/src/internal/v2/hydrate.ts +289 -0
- package/src/internal/v2/hydration-template.ts +308 -0
- package/src/internal/v2/parts.ts +254 -0
- package/src/internal/v2/render-entry.ts +131 -0
- package/src/internal/v2/render-sync-parts.ts +440 -0
- package/src/internal/v2/render.ts +588 -0
- package/src/internal/v2/sync-parts.ts +133 -0
- package/dist/cjs/internal/hydrate.js +0 -274
- package/dist/cjs/internal/hydrate.js.map +0 -1
- package/dist/cjs/internal/parts.js +0 -451
- package/dist/cjs/internal/parts.js.map +0 -1
- package/dist/cjs/internal/render.js +0 -704
- package/dist/cjs/internal/render.js.map +0 -1
- package/dist/dts/internal/hydrate.d.ts +0 -33
- package/dist/dts/internal/hydrate.d.ts.map +0 -1
- package/dist/dts/internal/parts.d.ts +0 -314
- package/dist/dts/internal/parts.d.ts.map +0 -1
- package/dist/dts/internal/render.d.ts +0 -16
- package/dist/dts/internal/render.d.ts.map +0 -1
- package/dist/esm/internal/hydrate.js +0 -239
- package/dist/esm/internal/hydrate.js.map +0 -1
- package/dist/esm/internal/parts.js +0 -373
- package/dist/esm/internal/parts.js.map +0 -1
- package/dist/esm/internal/render.js +0 -689
- package/dist/esm/internal/render.js.map +0 -1
- package/src/internal/hydrate.ts +0 -366
- package/src/internal/parts.ts +0 -609
- package/src/internal/render.ts +0 -971
package/src/internal/render.ts
DELETED
|
@@ -1,971 +0,0 @@
|
|
|
1
|
-
import * as Fx from "@typed/fx/Fx"
|
|
2
|
-
import * as Sink from "@typed/fx/Sink"
|
|
3
|
-
import { TypeId } from "@typed/fx/TypeId"
|
|
4
|
-
import type { Rendered } from "@typed/wire"
|
|
5
|
-
import { persistent } from "@typed/wire"
|
|
6
|
-
import { Effect, ExecutionStrategy, Exit, Runtime } from "effect"
|
|
7
|
-
import type { Cause } from "effect/Cause"
|
|
8
|
-
import type { Chunk } from "effect/Chunk"
|
|
9
|
-
import * as Context from "effect/Context"
|
|
10
|
-
import { hasProperty } from "effect/Predicate"
|
|
11
|
-
import * as Scope from "effect/Scope"
|
|
12
|
-
import { uncapitalize } from "effect/String"
|
|
13
|
-
import type { Directive } from "../Directive.js"
|
|
14
|
-
import { isDirective } from "../Directive.js"
|
|
15
|
-
import * as ElementRef from "../ElementRef.js"
|
|
16
|
-
import * as ElementSource from "../ElementSource.js"
|
|
17
|
-
import type { BrowserEntry } from "../Entry.js"
|
|
18
|
-
import * as EventHandler from "../EventHandler.js"
|
|
19
|
-
import type { Part } from "../Part.js"
|
|
20
|
-
import type { Placeholder } from "../Placeholder.js"
|
|
21
|
-
import type { ToRendered } from "../Render.js"
|
|
22
|
-
import type { Renderable } from "../Renderable.js"
|
|
23
|
-
import type { RenderContext } from "../RenderContext.js"
|
|
24
|
-
import type { RenderEvent } from "../RenderEvent.js"
|
|
25
|
-
import { DomRenderEvent } from "../RenderEvent.js"
|
|
26
|
-
import type { RenderTemplate } from "../RenderTemplate.js"
|
|
27
|
-
import type * as Template from "../Template.js"
|
|
28
|
-
import { makeRenderNodePart } from "./browser.js"
|
|
29
|
-
import { type EventSource, makeEventSource } from "./EventSource.js"
|
|
30
|
-
import { HydrateContext } from "./HydrateContext.js"
|
|
31
|
-
import type { IndexRefCounter2 } from "./indexRefCounter.js"
|
|
32
|
-
import { indexRefCounter2 } from "./indexRefCounter.js"
|
|
33
|
-
import { parse } from "./parser.js"
|
|
34
|
-
import {
|
|
35
|
-
AttributePartImpl,
|
|
36
|
-
BooleanPartImpl,
|
|
37
|
-
ClassNamePartImpl,
|
|
38
|
-
CommentPartImpl,
|
|
39
|
-
DataPartImpl,
|
|
40
|
-
PropertyPartImpl,
|
|
41
|
-
RefPartImpl,
|
|
42
|
-
TextPartImpl
|
|
43
|
-
} from "./parts.js"
|
|
44
|
-
import type { ParentChildNodes } from "./utils.js"
|
|
45
|
-
import { findPath } from "./utils.js"
|
|
46
|
-
|
|
47
|
-
// TODO: We need to re-think hydration for dynamic lists, probably just markers should be fine
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* @internal
|
|
51
|
-
*/
|
|
52
|
-
export type RenderPartContext = {
|
|
53
|
-
readonly context: Context.Context<Scope.Scope>
|
|
54
|
-
readonly document: Document
|
|
55
|
-
readonly eventSource: EventSource
|
|
56
|
-
readonly refCounter: IndexRefCounter2
|
|
57
|
-
readonly renderContext: RenderContext
|
|
58
|
-
readonly values: ReadonlyArray<Renderable<any, any>>
|
|
59
|
-
readonly onCause: (cause: Cause<any>) => Effect.Effect<void>
|
|
60
|
-
|
|
61
|
-
readonly makeHydrateContext?: (index: number) => HydrateContext
|
|
62
|
-
|
|
63
|
-
expected: number
|
|
64
|
-
spreadIndex: number
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
type RenderPartMap = {
|
|
68
|
-
readonly [K in Template.PartNode["_tag"] | Template.SparsePartNode["_tag"]]: (
|
|
69
|
-
part: Extract<Template.PartNode | Template.SparsePartNode, { _tag: K }>,
|
|
70
|
-
node: Node,
|
|
71
|
-
ctx: RenderPartContext
|
|
72
|
-
) => null | Effect.Effect<void, any, any> | Array<Effect.Effect<void, any, any>>
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const RenderPartMap: RenderPartMap = {
|
|
76
|
-
"attr": (templatePart, node, ctx) => {
|
|
77
|
-
const { document, refCounter, renderContext, values } = ctx
|
|
78
|
-
const element = node as HTMLElement | SVGElement
|
|
79
|
-
const attr = createAttribute(document, element, templatePart.name)
|
|
80
|
-
const renderable = values[templatePart.index]
|
|
81
|
-
let isSet = false
|
|
82
|
-
const setValue = (value: string | null | undefined) => {
|
|
83
|
-
if (isNullOrUndefined(value)) {
|
|
84
|
-
element.removeAttribute(templatePart.name)
|
|
85
|
-
isSet = false
|
|
86
|
-
} else {
|
|
87
|
-
attr.value = String(value)
|
|
88
|
-
if (isSet === false) {
|
|
89
|
-
element.setAttributeNode(attr)
|
|
90
|
-
isSet = true
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return matchSettablePart(
|
|
96
|
-
renderable,
|
|
97
|
-
setValue,
|
|
98
|
-
() => AttributePartImpl.browser(templatePart.index, element, templatePart.name, renderContext),
|
|
99
|
-
(f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
|
|
100
|
-
() => ctx.expected++
|
|
101
|
-
)
|
|
102
|
-
},
|
|
103
|
-
"boolean-part": (templatePart, node, ctx) => {
|
|
104
|
-
const { refCounter, renderContext, values } = ctx
|
|
105
|
-
const element = node as HTMLElement | SVGElement
|
|
106
|
-
const name = templatePart.name
|
|
107
|
-
const renderable = values[templatePart.index]
|
|
108
|
-
const setValue = (value: boolean | null | undefined) => {
|
|
109
|
-
element.toggleAttribute(name, isNullOrUndefined(value) ? false : Boolean(value))
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return matchSettablePart(
|
|
113
|
-
renderable,
|
|
114
|
-
setValue,
|
|
115
|
-
() => BooleanPartImpl.browser(templatePart.index, element, name, renderContext),
|
|
116
|
-
(f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
|
|
117
|
-
() => ctx.expected++
|
|
118
|
-
)
|
|
119
|
-
},
|
|
120
|
-
"className-part": (templatePart, node, ctx) => {
|
|
121
|
-
const { refCounter, renderContext, values } = ctx
|
|
122
|
-
const element = node as HTMLElement | SVGElement
|
|
123
|
-
const renderable = values[templatePart.index]
|
|
124
|
-
let classNames: Set<string> = new Set()
|
|
125
|
-
const setValue = (value: string | Array<string> | null | undefined) => {
|
|
126
|
-
if (isNullOrUndefined(value)) {
|
|
127
|
-
element.classList.remove(...classNames)
|
|
128
|
-
classNames.clear()
|
|
129
|
-
} else {
|
|
130
|
-
const newClassNames = new Set(
|
|
131
|
-
Array.isArray(value) ? value.flatMap((x) => splitClassNames(String(x))) : splitClassNames(String(value))
|
|
132
|
-
)
|
|
133
|
-
const { added, removed } = diffClassNames(classNames, newClassNames)
|
|
134
|
-
|
|
135
|
-
element.classList.remove(...removed)
|
|
136
|
-
element.classList.add(...added)
|
|
137
|
-
classNames = newClassNames
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return matchSettablePart(
|
|
142
|
-
renderable,
|
|
143
|
-
setValue,
|
|
144
|
-
() => ClassNamePartImpl.browser(templatePart.index, element, renderContext),
|
|
145
|
-
(f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
|
|
146
|
-
() => ctx.expected++
|
|
147
|
-
)
|
|
148
|
-
},
|
|
149
|
-
"comment-part": (templatePart, node, ctx) => {
|
|
150
|
-
const { refCounter, renderContext, values } = ctx
|
|
151
|
-
const comment = node as Comment
|
|
152
|
-
const renderable = values[templatePart.index]
|
|
153
|
-
const setValue = (value: string | null | undefined) => {
|
|
154
|
-
comment.nodeValue = isNullOrUndefined(value) ? "" : String(value)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return matchSettablePart(
|
|
158
|
-
renderable,
|
|
159
|
-
setValue,
|
|
160
|
-
() => CommentPartImpl.browser(templatePart.index, comment, renderContext),
|
|
161
|
-
(f) => Effect.zipRight(renderContext.queue.add(comment, f), refCounter.release(templatePart.index)),
|
|
162
|
-
() => ctx.expected++
|
|
163
|
-
)
|
|
164
|
-
},
|
|
165
|
-
"data": (templatePart, node, ctx) => {
|
|
166
|
-
const element = node as HTMLElement | SVGElement
|
|
167
|
-
const renderable = ctx.values[templatePart.index]
|
|
168
|
-
const previousKeys = new Set<string>(Object.keys(element.dataset))
|
|
169
|
-
const setValue = (value: Record<string, string | undefined> | null | undefined) => {
|
|
170
|
-
if (isNullOrUndefined(value)) {
|
|
171
|
-
for (const key of previousKeys) {
|
|
172
|
-
delete element.dataset[key]
|
|
173
|
-
}
|
|
174
|
-
previousKeys.clear()
|
|
175
|
-
} else {
|
|
176
|
-
for (const key of previousKeys) {
|
|
177
|
-
if (!(key in value)) {
|
|
178
|
-
delete element.dataset[key]
|
|
179
|
-
previousKeys.delete(key)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
for (const key of Object.keys(value)) {
|
|
184
|
-
if (!previousKeys.has(key)) {
|
|
185
|
-
previousKeys.add(key)
|
|
186
|
-
}
|
|
187
|
-
element.dataset[key] = value[key] || ""
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return matchSettablePart(
|
|
193
|
-
renderable,
|
|
194
|
-
setValue,
|
|
195
|
-
() => DataPartImpl.browser(templatePart.index, element, ctx.renderContext),
|
|
196
|
-
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(templatePart.index)),
|
|
197
|
-
() => ctx.expected++
|
|
198
|
-
)
|
|
199
|
-
},
|
|
200
|
-
"event": (templatePart, node, ctx) => {
|
|
201
|
-
const element = node as HTMLElement | SVGElement
|
|
202
|
-
const renderable = ctx.values[templatePart.index]
|
|
203
|
-
const handler = getEventHandler(renderable, ctx.context, ctx.onCause)
|
|
204
|
-
if (handler) {
|
|
205
|
-
ctx.eventSource.addEventListener(element, templatePart.name, handler)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return null
|
|
209
|
-
},
|
|
210
|
-
"node": (templatePart, node, ctx) => {
|
|
211
|
-
const makeHydrateContext = ctx.makeHydrateContext
|
|
212
|
-
const renderable = ctx.values[templatePart.index]
|
|
213
|
-
const part = makeRenderNodePart(
|
|
214
|
-
templatePart.index,
|
|
215
|
-
node as HTMLElement | SVGElement,
|
|
216
|
-
ctx.renderContext,
|
|
217
|
-
ctx.document,
|
|
218
|
-
!!makeHydrateContext
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
if (isDirective(renderable)) {
|
|
222
|
-
const effect = Effect.zipRight(renderable(part), ctx.refCounter.release(templatePart.index))
|
|
223
|
-
if (makeHydrateContext) {
|
|
224
|
-
return Effect.provideService(effect, HydrateContext, makeHydrateContext(templatePart.index))
|
|
225
|
-
} else {
|
|
226
|
-
return effect
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
ctx.expected++
|
|
231
|
-
|
|
232
|
-
const handle = handlePart(
|
|
233
|
-
renderable,
|
|
234
|
-
Sink.make(ctx.onCause, (value) => Effect.zipRight(part.update(value), ctx.refCounter.release(templatePart.index)))
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
if (makeHydrateContext) {
|
|
238
|
-
return Effect.provideService(handle, HydrateContext, makeHydrateContext(templatePart.index))
|
|
239
|
-
} else {
|
|
240
|
-
return handle
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
"property": (templatePart, node, ctx) => {
|
|
244
|
-
const element = node as HTMLElement | SVGElement
|
|
245
|
-
const renderable = ctx.values[templatePart.index]
|
|
246
|
-
const setValue = (value: unknown) => {
|
|
247
|
-
if (isNullOrUndefined(value)) {
|
|
248
|
-
delete (element as any)[templatePart.name]
|
|
249
|
-
} else {
|
|
250
|
-
;(element as any)[templatePart.name] = value
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return matchSettablePart(
|
|
255
|
-
renderable,
|
|
256
|
-
setValue,
|
|
257
|
-
() => PropertyPartImpl.browser(templatePart.index, element, templatePart.name, ctx.renderContext),
|
|
258
|
-
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(templatePart.index)),
|
|
259
|
-
() => ctx.expected++
|
|
260
|
-
)
|
|
261
|
-
},
|
|
262
|
-
"properties": (templatePart, node, ctx) => {
|
|
263
|
-
const renderable = ctx.values[templatePart.index] as any as Record<string, any>
|
|
264
|
-
if (isNullOrUndefined(renderable)) return null
|
|
265
|
-
else if (Fx.isFx(renderable) || Effect.isEffect(renderable)) {
|
|
266
|
-
throw new Error(`Properties Part must utilize an Record of renderable values.`)
|
|
267
|
-
} else if (typeof renderable === "object" && !Array.isArray(renderable)) {
|
|
268
|
-
const element = node as HTMLElement | SVGElement
|
|
269
|
-
|
|
270
|
-
const toggleBoolean = (key: string, value: unknown) => {
|
|
271
|
-
element.toggleAttribute(key, isNullOrUndefined(value) ? false : Boolean(value))
|
|
272
|
-
}
|
|
273
|
-
const setAttribute = (key: string, value: unknown) => {
|
|
274
|
-
if (isNullOrUndefined(value)) {
|
|
275
|
-
element.removeAttribute(key)
|
|
276
|
-
} else {
|
|
277
|
-
element.setAttribute(key, String(value))
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const setProperty = (key: string, value: unknown) => {
|
|
281
|
-
if (isNullOrUndefined(value)) {
|
|
282
|
-
delete (element as any)[key]
|
|
283
|
-
} else {
|
|
284
|
-
;(element as any)[key] = value
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
const setClassNames = (previous: Set<string>, updated: Set<string>) => {
|
|
288
|
-
const { added, removed } = diffClassNames(previous, updated)
|
|
289
|
-
|
|
290
|
-
element.classList.remove(...removed)
|
|
291
|
-
element.classList.add(...added)
|
|
292
|
-
removed.forEach((r) => previous.delete(r))
|
|
293
|
-
added.forEach((a) => previous.add(a))
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const effects: Array<Effect.Effect<void, any, any>> = []
|
|
297
|
-
const entries = Object.entries(renderable)
|
|
298
|
-
|
|
299
|
-
loop:
|
|
300
|
-
for (const [key, value] of entries) {
|
|
301
|
-
const index = ++ctx.spreadIndex
|
|
302
|
-
switch (key[0]) {
|
|
303
|
-
case "?": {
|
|
304
|
-
const name = key.slice(1)
|
|
305
|
-
const eff = matchSettablePart(
|
|
306
|
-
value,
|
|
307
|
-
(value) => toggleBoolean(name, value),
|
|
308
|
-
() => BooleanPartImpl.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 eff = matchSettablePart(
|
|
320
|
-
value,
|
|
321
|
-
(value) => setProperty(name, value),
|
|
322
|
-
() => PropertyPartImpl.browser(index, element, name, ctx.renderContext),
|
|
323
|
-
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
|
|
324
|
-
() => ctx.expected++
|
|
325
|
-
)
|
|
326
|
-
if (eff !== null) {
|
|
327
|
-
effects.push(eff)
|
|
328
|
-
}
|
|
329
|
-
continue loop
|
|
330
|
-
}
|
|
331
|
-
case "@": {
|
|
332
|
-
const name = uncapitalize(key.slice(1))
|
|
333
|
-
const handler = getEventHandler(value, ctx.context, ctx.onCause)
|
|
334
|
-
if (handler) {
|
|
335
|
-
ctx.eventSource.addEventListener(element, name, handler)
|
|
336
|
-
}
|
|
337
|
-
continue loop
|
|
338
|
-
}
|
|
339
|
-
case "o": {
|
|
340
|
-
if (key[1] === "n") {
|
|
341
|
-
const name = uncapitalize(key.slice(2))
|
|
342
|
-
const handler = getEventHandler(value, ctx.context, ctx.onCause)
|
|
343
|
-
if (handler) {
|
|
344
|
-
ctx.eventSource.addEventListener(element, name, handler)
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
continue loop
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const lowerCaseName = key.toLowerCase()
|
|
352
|
-
|
|
353
|
-
const isClass = lowerCaseName === "class" || lowerCaseName === "classname"
|
|
354
|
-
|
|
355
|
-
if (isClass) {
|
|
356
|
-
const classNames: Set<string> = new Set()
|
|
357
|
-
const eff = matchSettablePart(
|
|
358
|
-
value,
|
|
359
|
-
(value) => {
|
|
360
|
-
if (isNullOrUndefined(value)) {
|
|
361
|
-
element.classList.remove(...classNames)
|
|
362
|
-
classNames.clear()
|
|
363
|
-
} else {
|
|
364
|
-
setClassNames(classNames, new Set(splitClassNames(String(value))))
|
|
365
|
-
}
|
|
366
|
-
},
|
|
367
|
-
() => ClassNamePartImpl.browser(index, element, ctx.renderContext),
|
|
368
|
-
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
|
|
369
|
-
() => ctx.expected++
|
|
370
|
-
)
|
|
371
|
-
if (eff !== null) {
|
|
372
|
-
effects.push(eff)
|
|
373
|
-
}
|
|
374
|
-
} else {
|
|
375
|
-
const eff = matchSettablePart(
|
|
376
|
-
value,
|
|
377
|
-
(value) => setAttribute(key, value),
|
|
378
|
-
() => AttributePartImpl.browser(index, element, key, ctx.renderContext),
|
|
379
|
-
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
|
|
380
|
-
() => ctx.expected++
|
|
381
|
-
)
|
|
382
|
-
if (eff !== null) {
|
|
383
|
-
effects.push(eff)
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return effects
|
|
389
|
-
} else {
|
|
390
|
-
return null
|
|
391
|
-
}
|
|
392
|
-
},
|
|
393
|
-
"ref": (templatePart, node, ctx) => {
|
|
394
|
-
const element = node as HTMLElement | SVGElement
|
|
395
|
-
const renderable = ctx.values[templatePart.index]
|
|
396
|
-
|
|
397
|
-
if (isDirective(renderable)) {
|
|
398
|
-
return renderable(new RefPartImpl(ElementSource.fromElement(element), templatePart.index))
|
|
399
|
-
} else if (ElementRef.isElementRef(renderable)) {
|
|
400
|
-
return ElementRef.set(renderable, element)
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
return null
|
|
404
|
-
},
|
|
405
|
-
"sparse-attr": (templatePart, node, ctx) => {
|
|
406
|
-
const values = Array.from({ length: templatePart.nodes.length }, (): string => "")
|
|
407
|
-
const element = node as HTMLElement | SVGElement
|
|
408
|
-
const attr = createAttribute(ctx.document, element, templatePart.name)
|
|
409
|
-
|
|
410
|
-
const setValue = (value: string | null | undefined, index: number) => {
|
|
411
|
-
values[index] = value ?? ""
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
const effects: Array<Effect.Effect<void, any, any>> = []
|
|
415
|
-
|
|
416
|
-
for (let i = 0; i < templatePart.nodes.length; ++i) {
|
|
417
|
-
const node = templatePart.nodes[i]
|
|
418
|
-
if (node._tag === "text") {
|
|
419
|
-
values[i] = node.value
|
|
420
|
-
} else {
|
|
421
|
-
const renderable = ctx.values[node.index]
|
|
422
|
-
const index = i
|
|
423
|
-
const effect = matchSettablePart(
|
|
424
|
-
renderable,
|
|
425
|
-
(value) => setValue(value, index),
|
|
426
|
-
() =>
|
|
427
|
-
new AttributePartImpl(
|
|
428
|
-
templatePart.name,
|
|
429
|
-
node.index,
|
|
430
|
-
({ value }) =>
|
|
431
|
-
Effect.zipRight(
|
|
432
|
-
ctx.renderContext.queue.add(element, () => setValue(value, index)),
|
|
433
|
-
ctx.refCounter.release(node.index)
|
|
434
|
-
),
|
|
435
|
-
attr.value
|
|
436
|
-
),
|
|
437
|
-
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(node.index)),
|
|
438
|
-
() => ctx.expected++
|
|
439
|
-
)
|
|
440
|
-
|
|
441
|
-
if (effect !== null) {
|
|
442
|
-
effects.push(effect)
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
if (effects.length === 0) {
|
|
448
|
-
attr.value = values.join("")
|
|
449
|
-
element.setAttributeNode(attr)
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
return effects
|
|
453
|
-
},
|
|
454
|
-
"sparse-class-name": (templatePart, node, ctx) => {
|
|
455
|
-
const element = node as HTMLElement | SVGElement
|
|
456
|
-
|
|
457
|
-
const effects = templatePart.nodes.flatMap((node) => {
|
|
458
|
-
if (node._tag === "text") {
|
|
459
|
-
const split = splitClassNames(node.value)
|
|
460
|
-
if (split.length > 0) element.classList.add(...split)
|
|
461
|
-
return []
|
|
462
|
-
} else {
|
|
463
|
-
const eff = RenderPartMap[node._tag](node, element, ctx)
|
|
464
|
-
if (eff === null) return []
|
|
465
|
-
return Array.isArray(eff) ? eff : [eff]
|
|
466
|
-
}
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
return effects
|
|
470
|
-
},
|
|
471
|
-
"sparse-comment": (templatePart, node, ctx) => {
|
|
472
|
-
const values = Array.from({ length: templatePart.nodes.length }, (): string => "")
|
|
473
|
-
const comment = node as Comment
|
|
474
|
-
|
|
475
|
-
const setValue = (value: string | null | undefined, index: number) => {
|
|
476
|
-
values[index] = value ?? ""
|
|
477
|
-
}
|
|
478
|
-
const flushValue = () => {
|
|
479
|
-
comment.data = values.join("")
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const effects: Array<Effect.Effect<void, any, any>> = []
|
|
483
|
-
|
|
484
|
-
for (let i = 0; i < templatePart.nodes.length; ++i) {
|
|
485
|
-
const node = templatePart.nodes[i]
|
|
486
|
-
if (node._tag === "text") {
|
|
487
|
-
values[i] = node.value
|
|
488
|
-
} else {
|
|
489
|
-
const renderable = ctx.values[node.index]
|
|
490
|
-
const index = i
|
|
491
|
-
const effect = matchSettablePart(
|
|
492
|
-
renderable,
|
|
493
|
-
(value) => {
|
|
494
|
-
setValue(value, index)
|
|
495
|
-
flushValue()
|
|
496
|
-
},
|
|
497
|
-
() =>
|
|
498
|
-
new CommentPartImpl(
|
|
499
|
-
node.index,
|
|
500
|
-
({ value }) => (setValue(value, index),
|
|
501
|
-
Effect.zipRight(
|
|
502
|
-
ctx.renderContext.queue.add(comment, () => flushValue()),
|
|
503
|
-
ctx.refCounter.release(node.index)
|
|
504
|
-
)),
|
|
505
|
-
null
|
|
506
|
-
),
|
|
507
|
-
(f) => Effect.zipRight(ctx.renderContext.queue.add(comment, f), ctx.refCounter.release(node.index)),
|
|
508
|
-
() => ctx.expected++
|
|
509
|
-
)
|
|
510
|
-
|
|
511
|
-
if (effect !== null) {
|
|
512
|
-
effects.push(effect)
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (effects.length === 0) {
|
|
518
|
-
flushValue()
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
return effects
|
|
522
|
-
},
|
|
523
|
-
"text-part": (templatePart, node, ctx) => {
|
|
524
|
-
const renderable = ctx.values[templatePart.index]
|
|
525
|
-
const part = TextPartImpl.browser(
|
|
526
|
-
ctx.document,
|
|
527
|
-
templatePart.index,
|
|
528
|
-
node as HTMLElement | SVGElement,
|
|
529
|
-
ctx.renderContext
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
if (isDirective(renderable)) {
|
|
533
|
-
return Effect.zipRight(renderable(part), ctx.refCounter.release(templatePart.index))
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
ctx.expected++
|
|
537
|
-
|
|
538
|
-
return handlePart(
|
|
539
|
-
renderable,
|
|
540
|
-
Sink.make(
|
|
541
|
-
ctx.onCause,
|
|
542
|
-
(value) => Effect.zipRight(part.update(value as any), ctx.refCounter.release(templatePart.index))
|
|
543
|
-
)
|
|
544
|
-
)
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const SPACE_REGEXP = /\s+/g
|
|
549
|
-
|
|
550
|
-
function splitClassNames(value: string) {
|
|
551
|
-
return value.split(SPACE_REGEXP).flatMap((a) => {
|
|
552
|
-
const trimmed = a.trim()
|
|
553
|
-
return trimmed.length > 0 ? [trimmed] : []
|
|
554
|
-
})
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
function isNullOrUndefined<T>(value: T | null | undefined): value is null | undefined {
|
|
558
|
-
return value === null || value === undefined
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function diffClassNames(oldClassNames: Set<string>, newClassNames: Set<string>) {
|
|
562
|
-
const added: Array<string> = []
|
|
563
|
-
const removed: Array<string> = []
|
|
564
|
-
|
|
565
|
-
for (const className of oldClassNames) {
|
|
566
|
-
if (!newClassNames.has(className)) {
|
|
567
|
-
removed.push(className)
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
for (const className of newClassNames) {
|
|
572
|
-
if (!oldClassNames.has(className) && className.trim()) {
|
|
573
|
-
added.push(className)
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
return { added, removed }
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* @internal
|
|
582
|
-
*/
|
|
583
|
-
export function renderPart2(
|
|
584
|
-
part: Template.PartNode | Template.SparsePartNode,
|
|
585
|
-
content: ParentChildNodes,
|
|
586
|
-
path: Chunk<number>,
|
|
587
|
-
ctx: RenderPartContext
|
|
588
|
-
): Effect.Effect<void, any, any> | Array<Effect.Effect<void, any, any>> | null {
|
|
589
|
-
return RenderPartMap[part._tag](part as any, findPath(content, path), ctx)
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* Here for "standard" browser rendering, a TemplateInstance is effectively a live
|
|
594
|
-
* view into the contents rendered by the Template.
|
|
595
|
-
*/
|
|
596
|
-
export const renderTemplate: (document: Document, renderContext: RenderContext) => RenderTemplate =
|
|
597
|
-
(document, renderContext) =>
|
|
598
|
-
<Values extends ReadonlyArray<Renderable<any, any>>, T extends Rendered = Rendered>(
|
|
599
|
-
templateStrings: TemplateStringsArray,
|
|
600
|
-
values: Values
|
|
601
|
-
) => {
|
|
602
|
-
const entry = getBrowserEntry(document, renderContext, templateStrings)
|
|
603
|
-
if (values.length === 0) {
|
|
604
|
-
return Fx.sync(() => DomRenderEvent(persistent(document.importNode(entry.content, true))))
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
return Fx.make<RenderEvent, Placeholder.Error<Values[number]>, Scope.Scope | Placeholder.Context<Values[number]>>((
|
|
608
|
-
sink
|
|
609
|
-
) => {
|
|
610
|
-
return Effect.gen(function*(_) {
|
|
611
|
-
const runtime = yield* _(Effect.runtime<Scope.Scope | Placeholder.Context<Values[number]>>())
|
|
612
|
-
const runFork = Runtime.runFork(runtime)
|
|
613
|
-
const parentScope = Context.get(runtime.context, Scope.Scope)
|
|
614
|
-
const scope = yield* _(Scope.fork(parentScope, ExecutionStrategy.sequential))
|
|
615
|
-
const refCounter = yield* _(indexRefCounter2())
|
|
616
|
-
const content = document.importNode(entry.content, true)
|
|
617
|
-
const ctx: RenderPartContext = {
|
|
618
|
-
context: runtime.context,
|
|
619
|
-
document,
|
|
620
|
-
eventSource: makeEventSource(),
|
|
621
|
-
expected: 0,
|
|
622
|
-
refCounter,
|
|
623
|
-
renderContext,
|
|
624
|
-
onCause: sink.onFailure as any,
|
|
625
|
-
spreadIndex: values.length,
|
|
626
|
-
values
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// Connect our interpolated values to our template parts
|
|
630
|
-
const effects: Array<Effect.Effect<void, never, Scope.Scope | Placeholder.Context<Values[number]>>> = []
|
|
631
|
-
for (const [part, path] of entry.template.parts) {
|
|
632
|
-
const eff = renderPart2(part, content, path, ctx)
|
|
633
|
-
if (eff !== null) {
|
|
634
|
-
effects.push(
|
|
635
|
-
...(Array.isArray(eff) ? eff : [eff]) as Array<
|
|
636
|
-
Effect.Effect<void, never, Scope.Scope | Placeholder.Context<Values[number]>>
|
|
637
|
-
>
|
|
638
|
-
)
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// Fork any effects necessary
|
|
643
|
-
if (effects.length > 0) {
|
|
644
|
-
for (let i = 0; i < effects.length; ++i) {
|
|
645
|
-
runFork(effects[i], { scope })
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// If there's anything to wait on and it's not already done, wait for an initial value
|
|
650
|
-
// for all asynchronous sources.
|
|
651
|
-
if (ctx.expected > 0 && (yield* _(refCounter.expect(ctx.expected)))) {
|
|
652
|
-
yield* _(refCounter.wait)
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// Create a persistent wire from our content
|
|
656
|
-
const wire = persistent(content) as T
|
|
657
|
-
|
|
658
|
-
// Setup our event listeners for our wire.
|
|
659
|
-
// We use the parentScope to allow event listeners to exist
|
|
660
|
-
// beyond the lifetime of the current Fiber, but no further than its parent template.
|
|
661
|
-
yield* _(ctx.eventSource.setup(wire, parentScope))
|
|
662
|
-
|
|
663
|
-
// Emit our DomRenderEvent
|
|
664
|
-
yield* _(
|
|
665
|
-
sink.onSuccess(DomRenderEvent(wire)),
|
|
666
|
-
// Ensure our templates last forever in the DOM environment
|
|
667
|
-
// so event listeners are kept attached to the current Scope.
|
|
668
|
-
Effect.zipRight(Effect.never),
|
|
669
|
-
// Close our scope whenever the current Fiber is interrupted
|
|
670
|
-
Effect.ensuring(Scope.close(scope, Exit.unit))
|
|
671
|
-
)
|
|
672
|
-
})
|
|
673
|
-
})
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
function getEventHandler<E, R>(
|
|
677
|
-
renderable: any,
|
|
678
|
-
ctx: Context.Context<any> | Context.Context<never>,
|
|
679
|
-
onCause: (cause: Cause<E>) => Effect.Effect<unknown>
|
|
680
|
-
): EventHandler.EventHandler<never, never> | null {
|
|
681
|
-
if (renderable && typeof renderable === "object") {
|
|
682
|
-
if (EventHandler.EventHandlerTypeId in renderable) {
|
|
683
|
-
return EventHandler.make(
|
|
684
|
-
(ev) =>
|
|
685
|
-
Effect.provide(
|
|
686
|
-
Effect.catchAllCause((renderable as EventHandler.EventHandler<Event, E, R>).handler(ev), onCause),
|
|
687
|
-
ctx as any
|
|
688
|
-
),
|
|
689
|
-
(renderable as EventHandler.EventHandler<Event, E, R>).options
|
|
690
|
-
)
|
|
691
|
-
} else if (Effect.EffectTypeId in renderable) {
|
|
692
|
-
return EventHandler.make(() => Effect.provide(Effect.catchAllCause(renderable, onCause), ctx))
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
return null
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
function handlePart<R, E, R2>(
|
|
700
|
-
renderable: unknown,
|
|
701
|
-
sink: Sink.Sink<any, any, R2>
|
|
702
|
-
): Effect.Effect<any, never, R | R2 | Scope.Scope> {
|
|
703
|
-
switch (typeof renderable) {
|
|
704
|
-
case "undefined":
|
|
705
|
-
case "object": {
|
|
706
|
-
if (renderable === null || renderable === undefined) return sink.onSuccess(null)
|
|
707
|
-
else if (Array.isArray(renderable)) {
|
|
708
|
-
return renderable.length === 0
|
|
709
|
-
? sink.onSuccess(null)
|
|
710
|
-
: Fx.tuple(renderable.map(unwrapRenderable)).run(sink) as any
|
|
711
|
-
} else if (TypeId in renderable) {
|
|
712
|
-
return (renderable as Fx.Fx<any, any, R | R2>).run(sink)
|
|
713
|
-
} else if (Effect.EffectTypeId in renderable) {
|
|
714
|
-
return Effect.matchCauseEffect(renderable as Effect.Effect<any, E, R>, sink)
|
|
715
|
-
} else return sink.onSuccess(renderable)
|
|
716
|
-
}
|
|
717
|
-
default:
|
|
718
|
-
return sink.onSuccess(renderable)
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
function unwrapRenderable<E, R>(renderable: unknown): Fx.Fx<any, E, R> {
|
|
723
|
-
switch (typeof renderable) {
|
|
724
|
-
case "undefined":
|
|
725
|
-
case "object": {
|
|
726
|
-
if (renderable === null || renderable === undefined) return Fx.succeed(null)
|
|
727
|
-
else if (Array.isArray(renderable)) {
|
|
728
|
-
return renderable.length === 0
|
|
729
|
-
? Fx.succeed(null)
|
|
730
|
-
: Fx.map(Fx.tuple(renderable.map(unwrapRenderable)), (xs) => xs.flat()) as any
|
|
731
|
-
} else if (TypeId in renderable) {
|
|
732
|
-
return renderable as any
|
|
733
|
-
} else if (Effect.EffectTypeId in renderable) {
|
|
734
|
-
return Fx.fromFxEffect(Effect.map(renderable as any, unwrapRenderable<E, R>))
|
|
735
|
-
} else return Fx.succeed(renderable as any)
|
|
736
|
-
}
|
|
737
|
-
default:
|
|
738
|
-
return Fx.succeed(renderable)
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
export function attachRoot<T extends RenderEvent | null>(
|
|
743
|
-
cache: RenderContext["renderCache"],
|
|
744
|
-
where: HTMLElement,
|
|
745
|
-
what: RenderEvent | null // TODO: Should we support HTML RenderEvents here too?
|
|
746
|
-
): Effect.Effect<ToRendered<T>> {
|
|
747
|
-
return Effect.sync(() => {
|
|
748
|
-
const wire = what?.valueOf() as ToRendered<T>
|
|
749
|
-
const previous = cache.get(where)
|
|
750
|
-
|
|
751
|
-
if (wire !== previous) {
|
|
752
|
-
if (previous && !wire) removeChildren(where, previous)
|
|
753
|
-
|
|
754
|
-
cache.set(where, wire || null)
|
|
755
|
-
|
|
756
|
-
if (wire) replaceChildren(where, wire)
|
|
757
|
-
|
|
758
|
-
return wire as ToRendered<T>
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
return previous as ToRendered<T>
|
|
762
|
-
})
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
function removeChildren(where: HTMLElement, previous: Rendered) {
|
|
766
|
-
for (const node of getNodes(previous)) {
|
|
767
|
-
where.removeChild(node)
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
function replaceChildren(where: HTMLElement, wire: Rendered) {
|
|
772
|
-
where.replaceChildren(...getNodes(wire))
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
function getNodes(rendered: Rendered): Array<globalThis.Node> {
|
|
776
|
-
const value = rendered.valueOf() as globalThis.Node | Array<globalThis.Node>
|
|
777
|
-
return Array.isArray(value) ? value : [value]
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
export function getBrowserEntry(
|
|
781
|
-
document: Document,
|
|
782
|
-
ctx: RenderContext,
|
|
783
|
-
templateStrings: TemplateStringsArray
|
|
784
|
-
): BrowserEntry {
|
|
785
|
-
const cached = ctx.templateCache.get(templateStrings)
|
|
786
|
-
|
|
787
|
-
if (cached === undefined || cached._tag === "Server") {
|
|
788
|
-
const template = parse(templateStrings)
|
|
789
|
-
const content = buildTemplate(document, template)
|
|
790
|
-
const entry: BrowserEntry = {
|
|
791
|
-
_tag: "Browser",
|
|
792
|
-
template,
|
|
793
|
-
content
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
ctx.templateCache.set(templateStrings, entry)
|
|
797
|
-
|
|
798
|
-
return entry
|
|
799
|
-
} else {
|
|
800
|
-
return cached
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
export function buildTemplate(document: Document, { nodes }: Template.Template): DocumentFragment {
|
|
805
|
-
const fragment = document.createDocumentFragment()
|
|
806
|
-
|
|
807
|
-
for (let i = 0; i < nodes.length; ++i) {
|
|
808
|
-
fragment.append(buildNode(document, nodes[i], false))
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
return fragment
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
function buildNode(document: Document, node: Template.Node, isSvgContext: boolean): globalThis.Node {
|
|
815
|
-
switch (node._tag) {
|
|
816
|
-
case "element":
|
|
817
|
-
case "self-closing-element":
|
|
818
|
-
case "text-only-element":
|
|
819
|
-
return buildElement(document, node, isSvgContext)
|
|
820
|
-
case "text":
|
|
821
|
-
return document.createTextNode(node.value)
|
|
822
|
-
case "comment":
|
|
823
|
-
return document.createComment(node.value)
|
|
824
|
-
case "sparse-comment":
|
|
825
|
-
return document.createComment(`hole${node.nodes.map((n) => n._tag === "text" ? "" : n.index).join("")}`)
|
|
826
|
-
// Create placeholders for these elements
|
|
827
|
-
case "comment-part":
|
|
828
|
-
case "node":
|
|
829
|
-
return document.createComment(`hole${node.index}`)
|
|
830
|
-
case "doctype":
|
|
831
|
-
return document.implementation.createDocumentType(
|
|
832
|
-
node.name,
|
|
833
|
-
docTypeNameToPublicId(node.name),
|
|
834
|
-
docTypeNameToSystemId(node.name)
|
|
835
|
-
)
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
function docTypeNameToPublicId(name: string): string {
|
|
840
|
-
switch (name) {
|
|
841
|
-
case "html":
|
|
842
|
-
return "-//W3C//DTD HTML 4.01//EN"
|
|
843
|
-
case "svg":
|
|
844
|
-
return "-//W3C//DTD SVG 1.1//EN"
|
|
845
|
-
case "math":
|
|
846
|
-
return "-//W3C//DTD MathML 2.0//EN"
|
|
847
|
-
default:
|
|
848
|
-
return ""
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
function docTypeNameToSystemId(name: string): string {
|
|
853
|
-
switch (name) {
|
|
854
|
-
// HTML5
|
|
855
|
-
case "html":
|
|
856
|
-
return "http://www.w3.org/TR/html4/strict.dtd"
|
|
857
|
-
case "svg":
|
|
858
|
-
return "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
|
|
859
|
-
case "math":
|
|
860
|
-
return "http://www.w3.org/Math/DTD/mathml2/mathml2.dtd"
|
|
861
|
-
default:
|
|
862
|
-
return ""
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
const SVG_NAMESPACE = "http://www.w3.org/2000/svg"
|
|
867
|
-
|
|
868
|
-
function buildElement(
|
|
869
|
-
document: Document,
|
|
870
|
-
node: Template.ElementNode | Template.SelfClosingElementNode | Template.TextOnlyElement,
|
|
871
|
-
isSvgContext: boolean
|
|
872
|
-
): Element {
|
|
873
|
-
const { _tag, attributes, tagName } = node
|
|
874
|
-
const isSvg = isSvgContext ? tagName !== "foreignObject" : tagName === "svg"
|
|
875
|
-
const element = isSvg
|
|
876
|
-
? document.createElementNS(SVG_NAMESPACE, tagName)
|
|
877
|
-
: document.createElement(tagName)
|
|
878
|
-
|
|
879
|
-
for (let i = 0; i < attributes.length; ++i) {
|
|
880
|
-
const attr = attributes[i]
|
|
881
|
-
|
|
882
|
-
// We only handle static attributes here, parts are handled elsewhere
|
|
883
|
-
if (attr._tag === "attribute") {
|
|
884
|
-
element.setAttribute(attr.name, attr.value)
|
|
885
|
-
} else if (attr._tag === "boolean") {
|
|
886
|
-
element.toggleAttribute(attr.name, true)
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
if (_tag === "element") {
|
|
891
|
-
element.append(...node.children.map((child) => buildNode(document, child, isSvg)))
|
|
892
|
-
} else if (_tag === "text-only-element") {
|
|
893
|
-
element.append(...node.children.map((child) => buildTextChild(document, child)))
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
return element
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
function buildTextChild(document: Document, node: Template.Text): globalThis.Node {
|
|
900
|
-
if (node._tag === "text") {
|
|
901
|
-
return document.createTextNode(node.value)
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
return document.createComment(`hole${node.index}`)
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
function createAttribute(
|
|
908
|
-
document: Document,
|
|
909
|
-
element: HTMLElement | SVGElement,
|
|
910
|
-
name: string
|
|
911
|
-
): Attr {
|
|
912
|
-
return element.getAttributeNode(name) ?? document.createAttribute(name)
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
function matchSettablePart(
|
|
916
|
-
renderable: Renderable<any, any>,
|
|
917
|
-
setValue: (value: any) => void,
|
|
918
|
-
makePart: () => Part,
|
|
919
|
-
schedule: (f: () => void) => Effect.Effect<void, never, Scope.Scope>,
|
|
920
|
-
expect: () => void
|
|
921
|
-
) {
|
|
922
|
-
return matchRenderable(renderable, {
|
|
923
|
-
Fx: (fx) => {
|
|
924
|
-
expect()
|
|
925
|
-
return Fx.observe(fx, (a) => schedule(() => setValue(a)))
|
|
926
|
-
},
|
|
927
|
-
Effect: (effect) => {
|
|
928
|
-
expect()
|
|
929
|
-
return Effect.flatMap(effect, (a) => schedule(() => setValue(a)))
|
|
930
|
-
},
|
|
931
|
-
Directive: (directive) => {
|
|
932
|
-
expect()
|
|
933
|
-
const part = makePart()
|
|
934
|
-
return runDirective(directive, part, setValue, schedule)
|
|
935
|
-
},
|
|
936
|
-
Otherwise: (otherwise) => {
|
|
937
|
-
setValue(otherwise)
|
|
938
|
-
return null
|
|
939
|
-
}
|
|
940
|
-
})
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
function matchRenderable(renderable: Renderable<any, any>, matches: {
|
|
944
|
-
Fx: (fx: Fx.Fx<any, any, any>) => Effect.Effect<void, any, any> | null
|
|
945
|
-
Effect: (effect: Effect.Effect<any, any, any>) => Effect.Effect<void, any, any> | null
|
|
946
|
-
Directive: (directive: Directive<any, any>) => Effect.Effect<void, any, any> | null
|
|
947
|
-
Otherwise: (_: Renderable<any, any>) => Effect.Effect<void, any, any> | null
|
|
948
|
-
}): Effect.Effect<void, any, any> | null {
|
|
949
|
-
if (Fx.isFx(renderable)) {
|
|
950
|
-
return matches.Fx(renderable)
|
|
951
|
-
} else if (Effect.isEffect(renderable)) {
|
|
952
|
-
return matches.Effect(renderable)
|
|
953
|
-
} else if (isDirective<any, any>(renderable)) {
|
|
954
|
-
return matches.Directive(renderable)
|
|
955
|
-
} else {
|
|
956
|
-
return matches.Otherwise(renderable)
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
function runDirective(
|
|
961
|
-
directive: Directive<any, any>,
|
|
962
|
-
part: Part,
|
|
963
|
-
setValue: (value: any) => void,
|
|
964
|
-
schedule: (f: () => void) => Effect.Effect<void, never, Scope.Scope>
|
|
965
|
-
): Effect.Effect<void, any, any> {
|
|
966
|
-
if (hasProperty(part, "update")) {
|
|
967
|
-
return directive({ ...part, update: (value: any) => schedule(() => setValue(value)) })
|
|
968
|
-
} else {
|
|
969
|
-
return Effect.flatMap(directive(part), () => schedule(() => setValue(part.value)))
|
|
970
|
-
}
|
|
971
|
-
}
|