@typed/template 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/cjs/Directive.js +76 -0
- package/dist/cjs/Directive.js.map +1 -0
- package/dist/cjs/ElementRef.js +83 -0
- package/dist/cjs/ElementRef.js.map +1 -0
- package/dist/cjs/ElementSource.js +244 -0
- package/dist/cjs/ElementSource.js.map +1 -0
- package/dist/cjs/Entry.js +6 -0
- package/dist/cjs/Entry.js.map +1 -0
- package/dist/cjs/EventHandler.js +65 -0
- package/dist/cjs/EventHandler.js.map +1 -0
- package/dist/cjs/Html.js +169 -0
- package/dist/cjs/Html.js.map +1 -0
- package/dist/cjs/HtmlChunk.js +257 -0
- package/dist/cjs/HtmlChunk.js.map +1 -0
- package/dist/cjs/Hydrate.js +49 -0
- package/dist/cjs/Hydrate.js.map +1 -0
- package/dist/cjs/Many.js +45 -0
- package/dist/cjs/Many.js.map +1 -0
- package/dist/cjs/Meta.js +37 -0
- package/dist/cjs/Meta.js.map +1 -0
- package/dist/cjs/Parser.js +331 -0
- package/dist/cjs/Parser.js.map +1 -0
- package/dist/cjs/Part.js +6 -0
- package/dist/cjs/Part.js.map +1 -0
- package/dist/cjs/Placeholder.js +38 -0
- package/dist/cjs/Placeholder.js.map +1 -0
- package/dist/cjs/Platform.js +64 -0
- package/dist/cjs/Platform.js.map +1 -0
- package/dist/cjs/Render.js +39 -0
- package/dist/cjs/Render.js.map +1 -0
- package/dist/cjs/RenderContext.js +130 -0
- package/dist/cjs/RenderContext.js.map +1 -0
- package/dist/cjs/RenderEvent.js +44 -0
- package/dist/cjs/RenderEvent.js.map +1 -0
- package/dist/cjs/RenderTemplate.js +41 -0
- package/dist/cjs/RenderTemplate.js.map +1 -0
- package/dist/cjs/Renderable.js +6 -0
- package/dist/cjs/Renderable.js.map +1 -0
- package/dist/cjs/Template.js +266 -0
- package/dist/cjs/Template.js.map +1 -0
- package/dist/cjs/TemplateInstance.js +51 -0
- package/dist/cjs/TemplateInstance.js.map +1 -0
- package/dist/cjs/Test.js +90 -0
- package/dist/cjs/Test.js.map +1 -0
- package/dist/cjs/Token.js +270 -0
- package/dist/cjs/Token.js.map +1 -0
- package/dist/cjs/Tokenizer.js +18 -0
- package/dist/cjs/Tokenizer.js.map +1 -0
- package/dist/cjs/Vitest.js +44 -0
- package/dist/cjs/Vitest.js.map +1 -0
- package/dist/cjs/index.js +147 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/internal/HydrateContext.js +13 -0
- package/dist/cjs/internal/HydrateContext.js.map +1 -0
- package/dist/cjs/internal/browser.js +109 -0
- package/dist/cjs/internal/browser.js.map +1 -0
- package/dist/cjs/internal/chunks.js +54 -0
- package/dist/cjs/internal/chunks.js.map +1 -0
- package/dist/cjs/internal/errors.js +23 -0
- package/dist/cjs/internal/errors.js.map +1 -0
- package/dist/cjs/internal/hydrate.js +197 -0
- package/dist/cjs/internal/hydrate.js.map +1 -0
- package/dist/cjs/internal/indexRefCounter.js +32 -0
- package/dist/cjs/internal/indexRefCounter.js.map +1 -0
- package/dist/cjs/internal/module-augmentation.js +6 -0
- package/dist/cjs/internal/module-augmentation.js.map +1 -0
- package/dist/cjs/internal/parser.js +492 -0
- package/dist/cjs/internal/parser.js.map +1 -0
- package/dist/cjs/internal/parts.js +350 -0
- package/dist/cjs/internal/parts.js.map +1 -0
- package/dist/cjs/internal/readAttribute.js +34 -0
- package/dist/cjs/internal/readAttribute.js.map +1 -0
- package/dist/cjs/internal/render.js +332 -0
- package/dist/cjs/internal/render.js.map +1 -0
- package/dist/cjs/internal/server.js +219 -0
- package/dist/cjs/internal/server.js.map +1 -0
- package/dist/cjs/internal/tokenizer.js +264 -0
- package/dist/cjs/internal/tokenizer.js.map +1 -0
- package/dist/cjs/internal/utils.js +68 -0
- package/dist/cjs/internal/utils.js.map +1 -0
- package/dist/dts/Directive.d.ts +70 -0
- package/dist/dts/Directive.d.ts.map +1 -0
- package/dist/dts/ElementRef.d.ts +40 -0
- package/dist/dts/ElementRef.d.ts.map +1 -0
- package/dist/dts/ElementSource.d.ts +72 -0
- package/dist/dts/ElementSource.d.ts.map +1 -0
- package/dist/dts/Entry.d.ts +26 -0
- package/dist/dts/Entry.d.ts.map +1 -0
- package/dist/dts/EventHandler.d.ts +61 -0
- package/dist/dts/EventHandler.d.ts.map +1 -0
- package/dist/dts/Html.d.ts +17 -0
- package/dist/dts/Html.d.ts.map +1 -0
- package/dist/dts/HtmlChunk.d.ts +56 -0
- package/dist/dts/HtmlChunk.d.ts.map +1 -0
- package/dist/dts/Hydrate.d.ts +20 -0
- package/dist/dts/Hydrate.d.ts.map +1 -0
- package/dist/dts/Many.d.ts +32 -0
- package/dist/dts/Many.d.ts.map +1 -0
- package/dist/dts/Meta.d.ts +24 -0
- package/dist/dts/Meta.d.ts.map +1 -0
- package/dist/dts/Parser.d.ts +16 -0
- package/dist/dts/Parser.d.ts.map +1 -0
- package/dist/dts/Part.d.ts +147 -0
- package/dist/dts/Part.d.ts.map +1 -0
- package/dist/dts/Placeholder.d.ts +51 -0
- package/dist/dts/Placeholder.d.ts.map +1 -0
- package/dist/dts/Platform.d.ts +23 -0
- package/dist/dts/Platform.d.ts.map +1 -0
- package/dist/dts/Render.d.ts +23 -0
- package/dist/dts/Render.d.ts.map +1 -0
- package/dist/dts/RenderContext.d.ts +88 -0
- package/dist/dts/RenderContext.d.ts.map +1 -0
- package/dist/dts/RenderEvent.d.ts +37 -0
- package/dist/dts/RenderEvent.d.ts.map +1 -0
- package/dist/dts/RenderTemplate.d.ts +38 -0
- package/dist/dts/RenderTemplate.d.ts.map +1 -0
- package/dist/dts/Renderable.d.ts +28 -0
- package/dist/dts/Renderable.d.ts.map +1 -0
- package/dist/dts/Template.d.ts +218 -0
- package/dist/dts/Template.d.ts.map +1 -0
- package/dist/dts/TemplateInstance.d.ts +32 -0
- package/dist/dts/TemplateInstance.d.ts.map +1 -0
- package/dist/dts/Test.d.ts +58 -0
- package/dist/dts/Test.d.ts.map +1 -0
- package/dist/dts/Token.d.ts +202 -0
- package/dist/dts/Token.d.ts.map +1 -0
- package/dist/dts/Tokenizer.d.ts +6 -0
- package/dist/dts/Tokenizer.d.ts.map +1 -0
- package/dist/dts/Vitest.d.ts +28 -0
- package/dist/dts/Vitest.d.ts.map +1 -0
- package/dist/dts/index.d.ts +65 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/internal/HydrateContext.d.ts +2 -0
- package/dist/dts/internal/HydrateContext.d.ts.map +1 -0
- package/dist/dts/internal/browser.d.ts +8 -0
- package/dist/dts/internal/browser.d.ts.map +1 -0
- package/dist/dts/internal/chunks.d.ts +22 -0
- package/dist/dts/internal/chunks.d.ts.map +1 -0
- package/dist/dts/internal/errors.d.ts +9 -0
- package/dist/dts/internal/errors.d.ts.map +1 -0
- package/dist/dts/internal/hydrate.d.ts +37 -0
- package/dist/dts/internal/hydrate.d.ts.map +1 -0
- package/dist/dts/internal/indexRefCounter.d.ts +6 -0
- package/dist/dts/internal/indexRefCounter.d.ts.map +1 -0
- package/dist/dts/internal/module-augmentation.d.ts +36 -0
- package/dist/dts/internal/module-augmentation.d.ts.map +1 -0
- package/dist/dts/internal/parser.d.ts +12 -0
- package/dist/dts/internal/parser.d.ts.map +1 -0
- package/dist/dts/internal/parts.d.ts +304 -0
- package/dist/dts/internal/parts.d.ts.map +1 -0
- package/dist/dts/internal/readAttribute.d.ts +9 -0
- package/dist/dts/internal/readAttribute.d.ts.map +1 -0
- package/dist/dts/internal/render.d.ts +30 -0
- package/dist/dts/internal/render.d.ts.map +1 -0
- package/dist/dts/internal/server.d.ts +31 -0
- package/dist/dts/internal/server.d.ts.map +1 -0
- package/dist/dts/internal/tokenizer.d.ts +3 -0
- package/dist/dts/internal/tokenizer.d.ts.map +1 -0
- package/dist/dts/internal/utils.d.ts +15 -0
- package/dist/dts/internal/utils.d.ts.map +1 -0
- package/dist/esm/Directive.js +64 -0
- package/dist/esm/Directive.js.map +1 -0
- package/dist/esm/ElementRef.js +72 -0
- package/dist/esm/ElementRef.js.map +1 -0
- package/dist/esm/ElementSource.js +237 -0
- package/dist/esm/ElementSource.js.map +1 -0
- package/dist/esm/Entry.js +2 -0
- package/dist/esm/Entry.js.map +1 -0
- package/dist/esm/EventHandler.js +52 -0
- package/dist/esm/EventHandler.js.map +1 -0
- package/dist/esm/Html.js +167 -0
- package/dist/esm/Html.js.map +1 -0
- package/dist/esm/HtmlChunk.js +274 -0
- package/dist/esm/HtmlChunk.js.map +1 -0
- package/dist/esm/Hydrate.js +37 -0
- package/dist/esm/Hydrate.js.map +1 -0
- package/dist/esm/Many.js +33 -0
- package/dist/esm/Many.js.map +1 -0
- package/dist/esm/Meta.js +29 -0
- package/dist/esm/Meta.js.map +1 -0
- package/dist/esm/Parser.js +342 -0
- package/dist/esm/Parser.js.map +1 -0
- package/dist/esm/Part.js +5 -0
- package/dist/esm/Part.js.map +1 -0
- package/dist/esm/Placeholder.js +30 -0
- package/dist/esm/Placeholder.js.map +1 -0
- package/dist/esm/Platform.js +41 -0
- package/dist/esm/Platform.js.map +1 -0
- package/dist/esm/Render.js +27 -0
- package/dist/esm/Render.js.map +1 -0
- package/dist/esm/RenderContext.js +113 -0
- package/dist/esm/RenderContext.js.map +1 -0
- package/dist/esm/RenderEvent.js +36 -0
- package/dist/esm/RenderEvent.js.map +1 -0
- package/dist/esm/RenderTemplate.js +26 -0
- package/dist/esm/RenderTemplate.js.map +1 -0
- package/dist/esm/Renderable.js +2 -0
- package/dist/esm/Renderable.js.map +1 -0
- package/dist/esm/Template.js +239 -0
- package/dist/esm/Template.js.map +1 -0
- package/dist/esm/TemplateInstance.js +43 -0
- package/dist/esm/TemplateInstance.js.map +1 -0
- package/dist/esm/Test.js +68 -0
- package/dist/esm/Test.js.map +1 -0
- package/dist/esm/Token.js +264 -0
- package/dist/esm/Token.js.map +1 -0
- package/dist/esm/Tokenizer.js +9 -0
- package/dist/esm/Tokenizer.js.map +1 -0
- package/dist/esm/Vitest.js +29 -0
- package/dist/esm/Vitest.js.map +1 -0
- package/dist/esm/index.js +65 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/HydrateContext.js +7 -0
- package/dist/esm/internal/HydrateContext.js.map +1 -0
- package/dist/esm/internal/browser.js +102 -0
- package/dist/esm/internal/browser.js.map +1 -0
- package/dist/esm/internal/chunks.js +47 -0
- package/dist/esm/internal/chunks.js.map +1 -0
- package/dist/esm/internal/errors.js +15 -0
- package/dist/esm/internal/errors.js.map +1 -0
- package/dist/esm/internal/hydrate.js +165 -0
- package/dist/esm/internal/hydrate.js.map +1 -0
- package/dist/esm/internal/indexRefCounter.js +24 -0
- package/dist/esm/internal/indexRefCounter.js.map +1 -0
- package/dist/esm/internal/module-augmentation.js +2 -0
- package/dist/esm/internal/module-augmentation.js.map +1 -0
- package/dist/esm/internal/parser.js +493 -0
- package/dist/esm/internal/parser.js.map +1 -0
- package/dist/esm/internal/parts.js +291 -0
- package/dist/esm/internal/parts.js.map +1 -0
- package/dist/esm/internal/readAttribute.js +24 -0
- package/dist/esm/internal/readAttribute.js.map +1 -0
- package/dist/esm/internal/render.js +329 -0
- package/dist/esm/internal/render.js.map +1 -0
- package/dist/esm/internal/server.js +174 -0
- package/dist/esm/internal/server.js.map +1 -0
- package/dist/esm/internal/tokenizer.js +296 -0
- package/dist/esm/internal/tokenizer.js.map +1 -0
- package/dist/esm/internal/utils.js +52 -0
- package/dist/esm/internal/utils.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/package.json +242 -0
- package/src/Directive.ts +114 -0
- package/src/ElementRef.ts +123 -0
- package/src/ElementSource.ts +417 -0
- package/src/Entry.ts +28 -0
- package/src/EventHandler.ts +104 -0
- package/src/Html.ts +258 -0
- package/src/HtmlChunk.ts +346 -0
- package/src/Hydrate.ts +53 -0
- package/src/Many.ts +128 -0
- package/src/Meta.ts +32 -0
- package/src/Parser.ts +457 -0
- package/src/Part.ts +186 -0
- package/src/Placeholder.ts +70 -0
- package/src/Platform.ts +71 -0
- package/src/Render.ts +45 -0
- package/src/RenderContext.ts +221 -0
- package/src/RenderEvent.ts +67 -0
- package/src/RenderTemplate.ts +88 -0
- package/src/Renderable.ts +34 -0
- package/src/Template.ts +284 -0
- package/src/TemplateInstance.ts +83 -0
- package/src/Test.ts +151 -0
- package/src/Token.ts +269 -0
- package/src/Tokenizer.ts +10 -0
- package/src/Vitest.ts +61 -0
- package/src/index.ts +66 -0
- package/src/internal/HydrateContext.ts +23 -0
- package/src/internal/browser.ts +132 -0
- package/src/internal/chunks.ts +73 -0
- package/src/internal/errors.ts +11 -0
- package/src/internal/external.d.ts +11 -0
- package/src/internal/hydrate.ts +262 -0
- package/src/internal/indexRefCounter.ts +33 -0
- package/src/internal/module-augmentation.ts +48 -0
- package/src/internal/parser.ts +637 -0
- package/src/internal/parts.ts +527 -0
- package/src/internal/readAttribute.ts +28 -0
- package/src/internal/render.ts +529 -0
- package/src/internal/server.ts +293 -0
- package/src/internal/tokenizer.ts +338 -0
- package/src/internal/utils.ts +73 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import * as Fx from "@typed/fx/Fx"
|
|
2
|
+
import { makeSubject } from "@typed/fx/internal/core-subject"
|
|
3
|
+
import { TypeId } from "@typed/fx/TypeId"
|
|
4
|
+
import type { Rendered } from "@typed/wire"
|
|
5
|
+
import { persistent } from "@typed/wire"
|
|
6
|
+
import { Effect } from "effect"
|
|
7
|
+
import type { Cause } from "effect/Cause"
|
|
8
|
+
import { replace } from "effect/ReadonlyArray"
|
|
9
|
+
import type { Scope } from "effect/Scope"
|
|
10
|
+
import { isDirective } from "../Directive"
|
|
11
|
+
import * as ElementRef from "../ElementRef"
|
|
12
|
+
import type { BrowserEntry } from "../Entry"
|
|
13
|
+
import * as EventHandler from "../EventHandler"
|
|
14
|
+
import type { AttributePart, ClassNamePart, CommentPart, Part, Parts, SparsePart, StaticText } from "../Part"
|
|
15
|
+
import type { Placeholder } from "../Placeholder"
|
|
16
|
+
import type { ToRendered } from "../Render"
|
|
17
|
+
import type { Renderable } from "../Renderable"
|
|
18
|
+
import type { RenderContext } from "../RenderContext"
|
|
19
|
+
import type { RenderEvent } from "../RenderEvent"
|
|
20
|
+
import { DomRenderEvent } from "../RenderEvent"
|
|
21
|
+
import type { RenderTemplate } from "../RenderTemplate"
|
|
22
|
+
import type * as Template from "../Template"
|
|
23
|
+
import { TemplateInstance } from "../TemplateInstance"
|
|
24
|
+
import { makeRenderNodePart } from "./browser"
|
|
25
|
+
import { HydrateContext } from "./HydrateContext"
|
|
26
|
+
import type { IndexRefCounter } from "./indexRefCounter"
|
|
27
|
+
import { indexRefCounter } from "./indexRefCounter"
|
|
28
|
+
import { parse } from "./parser"
|
|
29
|
+
import {
|
|
30
|
+
AttributePartImpl,
|
|
31
|
+
BooleanPartImpl,
|
|
32
|
+
ClassNamePartImpl,
|
|
33
|
+
CommentPartImpl,
|
|
34
|
+
DataPartImpl,
|
|
35
|
+
EventPartImpl,
|
|
36
|
+
PropertyPartImpl,
|
|
37
|
+
RefPartImpl,
|
|
38
|
+
SparseAttributePartImpl,
|
|
39
|
+
SparseClassNamePartImpl,
|
|
40
|
+
SparseCommentPartImpl,
|
|
41
|
+
StaticTextImpl,
|
|
42
|
+
TextPartImpl
|
|
43
|
+
} from "./parts"
|
|
44
|
+
import type { ParentChildNodes } from "./utils"
|
|
45
|
+
import { findPath } from "./utils"
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Here for "standard" browser rendering, a TemplateInstance is effectively a live
|
|
49
|
+
* view into the contents rendered by the Template.
|
|
50
|
+
*/
|
|
51
|
+
export const renderTemplate: (document: Document, ctx: RenderContext) => RenderTemplate =
|
|
52
|
+
(document, ctx) =>
|
|
53
|
+
<Values extends ReadonlyArray<Renderable<any, any>>, T extends Rendered = Rendered>(
|
|
54
|
+
templateStrings: TemplateStringsArray,
|
|
55
|
+
values: Values,
|
|
56
|
+
providedRef?: ElementRef.ElementRef<T>
|
|
57
|
+
) =>
|
|
58
|
+
Effect.gen(function*(_) {
|
|
59
|
+
const elementRef = providedRef || (yield* _(ElementRef.make<T>()))
|
|
60
|
+
const events = Fx.map(elementRef, DomRenderEvent)
|
|
61
|
+
const errors = makeSubject<Placeholder.Error<Values[number]>, never>()
|
|
62
|
+
const entry = getBrowserEntry(document, ctx, templateStrings)
|
|
63
|
+
const content = document.importNode(entry.content, true) // Clone our template
|
|
64
|
+
|
|
65
|
+
const parts = yield* _(buildParts(document, ctx, entry.template, content, elementRef, errors.onFailure, false)) // Build runtime-variant of parts with our content.
|
|
66
|
+
|
|
67
|
+
// If there are parts we need to render them before constructing our Wire
|
|
68
|
+
if (parts.length > 0) {
|
|
69
|
+
const refCounter = yield* _(indexRefCounter(parts.length))
|
|
70
|
+
|
|
71
|
+
// Do the work
|
|
72
|
+
yield* _(renderValues(values, parts, refCounter, errors.onFailure))
|
|
73
|
+
|
|
74
|
+
// Wait for initial work to be completed
|
|
75
|
+
yield* _(refCounter.wait)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Set the element when it is ready
|
|
79
|
+
yield* _(ElementRef.set(elementRef, persistent(content) as T))
|
|
80
|
+
|
|
81
|
+
// Return the Template instance
|
|
82
|
+
return TemplateInstance(Fx.merge([events, errors]), elementRef)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
export function renderValues<Values extends ReadonlyArray<Renderable<any, any>>>(
|
|
86
|
+
values: Values,
|
|
87
|
+
parts: Parts,
|
|
88
|
+
refCounter: IndexRefCounter,
|
|
89
|
+
onCause: (cause: Cause<Placeholder.Error<Values[number]>>) => Effect.Effect<never, never, unknown>,
|
|
90
|
+
makeHydrateContext?: (index: number) => HydrateContext
|
|
91
|
+
): Effect.Effect<Placeholder.Context<Values[number]> | Scope, never, void> {
|
|
92
|
+
return Effect.all(parts.map((part, index) => {
|
|
93
|
+
switch (part._tag) {
|
|
94
|
+
case "sparse/attribute":
|
|
95
|
+
case "sparse/className":
|
|
96
|
+
case "sparse/comment": {
|
|
97
|
+
return renderSparsePart(values, part, refCounter)
|
|
98
|
+
}
|
|
99
|
+
default:
|
|
100
|
+
return renderPart(
|
|
101
|
+
values,
|
|
102
|
+
part,
|
|
103
|
+
refCounter,
|
|
104
|
+
onCause,
|
|
105
|
+
makeHydrateContext ? () => makeHydrateContext(index) : undefined
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
})) as any
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function renderSparsePart(
|
|
112
|
+
values: ReadonlyArray<Renderable<any, any>>,
|
|
113
|
+
part: SparsePart,
|
|
114
|
+
refCounter: IndexRefCounter
|
|
115
|
+
) {
|
|
116
|
+
const indexes = part.parts.flatMap((p) => p._tag === "static/text" ? [] : [p.index])
|
|
117
|
+
|
|
118
|
+
return Effect.forkScoped(
|
|
119
|
+
Fx.observe(
|
|
120
|
+
unwrapSparsePartRenderables(
|
|
121
|
+
part.parts.map((p) => p._tag === "static/text" ? Fx.succeed(p.value) : values[p.index]),
|
|
122
|
+
part
|
|
123
|
+
),
|
|
124
|
+
(value) => Effect.tap(part.update(value as any), () => Effect.forEach(indexes, (a) => refCounter.release(a)))
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function renderPart<Values extends ReadonlyArray<Renderable<any, any>>>(
|
|
130
|
+
values: Values,
|
|
131
|
+
part: Part,
|
|
132
|
+
refCounter: IndexRefCounter,
|
|
133
|
+
onCause: (cause: Cause<Placeholder.Error<Values[number]>>) => Effect.Effect<never, never, unknown>,
|
|
134
|
+
hydrateCtx?: () => HydrateContext
|
|
135
|
+
): Effect.Effect<any, never, void> {
|
|
136
|
+
const partIndex = part.index
|
|
137
|
+
const renderable = values[partIndex]
|
|
138
|
+
|
|
139
|
+
if (isDirective(renderable)) {
|
|
140
|
+
return renderable(part).pipe(
|
|
141
|
+
Effect.tap(() => refCounter.release(partIndex)),
|
|
142
|
+
Effect.forkScoped
|
|
143
|
+
)
|
|
144
|
+
} else if (part._tag === "ref") {
|
|
145
|
+
return refCounter.release(partIndex)
|
|
146
|
+
} else if (part._tag === "event") {
|
|
147
|
+
return Effect.tap(
|
|
148
|
+
part.update(
|
|
149
|
+
getEventHandler(values[partIndex], onCause) as EventHandler.EventHandler<
|
|
150
|
+
Placeholder.Context<Values[number]>,
|
|
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)
|
|
158
|
+
|
|
159
|
+
return handlePart(
|
|
160
|
+
values[partIndex],
|
|
161
|
+
(value) => Effect.tap(part.update(value), () => refCounter.release(partIndex))
|
|
162
|
+
).pipe(
|
|
163
|
+
HydrateContext.provide(hydrateCtx()),
|
|
164
|
+
Effect.forkScoped
|
|
165
|
+
)
|
|
166
|
+
} else {
|
|
167
|
+
const renderable = values[partIndex]
|
|
168
|
+
|
|
169
|
+
if (renderable === null || renderable === undefined) return refCounter.release(partIndex)
|
|
170
|
+
|
|
171
|
+
return handlePart(
|
|
172
|
+
values[partIndex],
|
|
173
|
+
(value) => Effect.tap(part.update(value as any), () => refCounter.release(partIndex))
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function getEventHandler<R, E>(
|
|
179
|
+
renderable: any,
|
|
180
|
+
onCause: (cause: Cause<E>) => Effect.Effect<never, never, unknown>
|
|
181
|
+
): EventHandler.EventHandler<R, never> | null {
|
|
182
|
+
if (renderable && typeof renderable === "object") {
|
|
183
|
+
if (EventHandler.EventHandlerTypeId in renderable) {
|
|
184
|
+
return EventHandler.make(
|
|
185
|
+
(ev) => Effect.catchAllCause((renderable as EventHandler.EventHandler<R, E>).handler(ev), onCause),
|
|
186
|
+
(renderable as EventHandler.EventHandler<R, E>).options
|
|
187
|
+
)
|
|
188
|
+
} else if (Effect.EffectTypeId in renderable) {
|
|
189
|
+
return EventHandler.make(() => Effect.catchAllCause(renderable, onCause))
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function handlePart<R, E>(
|
|
197
|
+
renderable: unknown,
|
|
198
|
+
update: (u: unknown) => Effect.Effect<Scope, never, unknown>
|
|
199
|
+
): Effect.Effect<R | Scope, E, any> {
|
|
200
|
+
switch (typeof renderable) {
|
|
201
|
+
case "undefined":
|
|
202
|
+
case "object": {
|
|
203
|
+
if (renderable === null || renderable === undefined) return update(null)
|
|
204
|
+
else if (Array.isArray(renderable)) {
|
|
205
|
+
return renderable.length === 0
|
|
206
|
+
? update(null)
|
|
207
|
+
: Effect.forkScoped(Fx.observe(Fx.combine(renderable.map(unwrapRenderable)) as any, update))
|
|
208
|
+
} else if (TypeId in renderable) {
|
|
209
|
+
return Effect.forkScoped(Fx.observe(renderable as any, update))
|
|
210
|
+
} else if (Effect.EffectTypeId in renderable) {
|
|
211
|
+
return Effect.flatMap(renderable as Effect.Effect<R, E, any>, update)
|
|
212
|
+
} else return update(renderable)
|
|
213
|
+
}
|
|
214
|
+
default:
|
|
215
|
+
return update(renderable)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function unwrapRenderable<R, E>(renderable: unknown): Fx.Fx<R, E, any> {
|
|
220
|
+
switch (typeof renderable) {
|
|
221
|
+
case "undefined":
|
|
222
|
+
case "object": {
|
|
223
|
+
if (renderable === null || renderable === undefined) return Fx.succeed(null)
|
|
224
|
+
else if (Array.isArray(renderable)) {
|
|
225
|
+
return renderable.length === 0 ? Fx.succeed(null) : Fx.combine(renderable.map(unwrapRenderable)) as any
|
|
226
|
+
} else if (TypeId in renderable) {
|
|
227
|
+
return renderable as any
|
|
228
|
+
} else if (Effect.EffectTypeId in renderable) {
|
|
229
|
+
return Fx.fromFxEffect(Effect.map(renderable as any, unwrapRenderable<R, E>))
|
|
230
|
+
} else return Fx.succeed(renderable as any)
|
|
231
|
+
}
|
|
232
|
+
default:
|
|
233
|
+
return Fx.succeed(renderable)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function unwrapSparsePartRenderables(
|
|
238
|
+
renderables: ReadonlyArray<Renderable<any, any>>,
|
|
239
|
+
part: SparsePart
|
|
240
|
+
) {
|
|
241
|
+
return Fx.combine(
|
|
242
|
+
// @ts-ignore type too deep
|
|
243
|
+
renderables.map((renderable, i) => {
|
|
244
|
+
const p = part.parts[i]
|
|
245
|
+
|
|
246
|
+
if (p._tag === "static/text") {
|
|
247
|
+
return Fx.succeed(p.value)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (isDirective(renderable)) {
|
|
251
|
+
return Fx.fromEffect(Effect.map(renderable(p), () => p.value))
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return Fx.mapEffect(
|
|
255
|
+
unwrapRenderable(renderable),
|
|
256
|
+
(u) => Effect.map(p.update(u), () => p.value)
|
|
257
|
+
)
|
|
258
|
+
})
|
|
259
|
+
) as any
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function attachRoot<T extends RenderEvent | null>(
|
|
263
|
+
cache: RenderContext["renderCache"],
|
|
264
|
+
where: HTMLElement,
|
|
265
|
+
what: RenderEvent | null // TODO: Should we support HTML RenderEvents here too?
|
|
266
|
+
): Effect.Effect<never, never, ToRendered<T>> {
|
|
267
|
+
return Effect.sync(() => {
|
|
268
|
+
const wire = what?.valueOf() as ToRendered<T>
|
|
269
|
+
const previous = cache.get(where)
|
|
270
|
+
|
|
271
|
+
if (wire !== previous) {
|
|
272
|
+
if (previous && !wire) removeChildren(where, previous)
|
|
273
|
+
|
|
274
|
+
cache.set(where, wire || null)
|
|
275
|
+
|
|
276
|
+
if (wire) replaceChildren(where, wire)
|
|
277
|
+
|
|
278
|
+
return wire as ToRendered<T>
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return previous as ToRendered<T>
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function removeChildren(where: HTMLElement, previous: Rendered) {
|
|
286
|
+
for (const node of getNodes(previous)) {
|
|
287
|
+
where.removeChild(node)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function replaceChildren(where: HTMLElement, wire: Rendered) {
|
|
292
|
+
where.replaceChildren(...getNodes(wire))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function getNodes(rendered: Rendered): Array<globalThis.Node> {
|
|
296
|
+
const value = rendered.valueOf() as globalThis.Node | Array<globalThis.Node>
|
|
297
|
+
return Array.isArray(value) ? value : [value]
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function getBrowserEntry(
|
|
301
|
+
document: Document,
|
|
302
|
+
ctx: RenderContext,
|
|
303
|
+
templateStrings: TemplateStringsArray
|
|
304
|
+
): BrowserEntry {
|
|
305
|
+
const cached = ctx.templateCache.get(templateStrings)
|
|
306
|
+
|
|
307
|
+
if (cached === undefined || cached._tag === "Server") {
|
|
308
|
+
const template = parse(templateStrings)
|
|
309
|
+
const content = buildTemplate(document, template)
|
|
310
|
+
const entry: BrowserEntry = {
|
|
311
|
+
_tag: "Browser",
|
|
312
|
+
template,
|
|
313
|
+
content
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
ctx.templateCache.set(templateStrings, entry)
|
|
317
|
+
|
|
318
|
+
return entry
|
|
319
|
+
} else {
|
|
320
|
+
return cached
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function buildParts<T extends Rendered, E>(
|
|
325
|
+
document: Document,
|
|
326
|
+
ctx: RenderContext,
|
|
327
|
+
template: Template.Template,
|
|
328
|
+
content: ParentChildNodes,
|
|
329
|
+
ref: ElementRef.ElementRef<T>,
|
|
330
|
+
onCause: (cause: Cause<E>) => Effect.Effect<never, never, void>,
|
|
331
|
+
isHydrating: boolean
|
|
332
|
+
): Effect.Effect<Scope, never, Parts> {
|
|
333
|
+
return Effect.all(
|
|
334
|
+
template.parts.map(([part, path]) =>
|
|
335
|
+
buildPartWithNode(document, ctx, part, findPath(content, path), ref, onCause, isHydrating)
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function buildPartWithNode<T extends Rendered, E>(
|
|
341
|
+
document: Document,
|
|
342
|
+
ctx: RenderContext,
|
|
343
|
+
part: Template.PartNode | Template.SparsePartNode,
|
|
344
|
+
node: Node,
|
|
345
|
+
ref: ElementRef.ElementRef<T>,
|
|
346
|
+
onCause: (cause: Cause<E>) => Effect.Effect<never, never, void>,
|
|
347
|
+
isHydrating: boolean
|
|
348
|
+
): Effect.Effect<Scope, never, Part | SparsePart> {
|
|
349
|
+
switch (part._tag) {
|
|
350
|
+
case "attr":
|
|
351
|
+
return Effect.succeed(AttributePartImpl.browser(part.index, node as Element, part.name, ctx))
|
|
352
|
+
case "boolean-part":
|
|
353
|
+
return Effect.succeed(BooleanPartImpl.browser(part.index, node as Element, part.name, ctx))
|
|
354
|
+
case "className-part":
|
|
355
|
+
return Effect.succeed(ClassNamePartImpl.browser(part.index, node as Element, ctx))
|
|
356
|
+
case "comment-part":
|
|
357
|
+
return Effect.succeed(CommentPartImpl.browser(part.index, node as Comment, ctx))
|
|
358
|
+
case "data":
|
|
359
|
+
return Effect.succeed(DataPartImpl.browser(part.index, node as HTMLElement | SVGElement, ctx))
|
|
360
|
+
case "event":
|
|
361
|
+
return EventPartImpl.browser(part.name, part.index, ref, node as HTMLElement | SVGElement, onCause) as any
|
|
362
|
+
case "node":
|
|
363
|
+
return Effect.succeed(
|
|
364
|
+
makeRenderNodePart(part.index, node as HTMLElement | SVGElement, ctx, document, isHydrating)
|
|
365
|
+
)
|
|
366
|
+
case "property":
|
|
367
|
+
return Effect.succeed(PropertyPartImpl.browser(part.index, node, part.name, ctx))
|
|
368
|
+
case "ref":
|
|
369
|
+
return Effect.succeed(new RefPartImpl(ref.query(node as HTMLElement | SVGElement), part.index)) as any
|
|
370
|
+
case "sparse-attr": {
|
|
371
|
+
const parts: Array<AttributePart | StaticText> = Array(part.nodes.length)
|
|
372
|
+
const sparse = SparseAttributePartImpl.browser(
|
|
373
|
+
part.name,
|
|
374
|
+
parts,
|
|
375
|
+
node as HTMLElement | SVGElement,
|
|
376
|
+
ctx
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
for (let i = 0; i < part.nodes.length; ++i) {
|
|
380
|
+
const node = part.nodes[i]
|
|
381
|
+
|
|
382
|
+
if (node._tag === "text") {
|
|
383
|
+
parts.push(new StaticTextImpl(node.value))
|
|
384
|
+
;(sparse as any).value[i] = node.value
|
|
385
|
+
} else {
|
|
386
|
+
parts.push(
|
|
387
|
+
new AttributePartImpl(
|
|
388
|
+
node.name,
|
|
389
|
+
node.index,
|
|
390
|
+
({ value }) => sparse.update(replace(sparse.value, i, value || "")),
|
|
391
|
+
sparse.value[i]
|
|
392
|
+
)
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return Effect.succeed(sparse)
|
|
398
|
+
}
|
|
399
|
+
case "sparse-class-name": {
|
|
400
|
+
const parts: Array<ClassNamePart | StaticText> = []
|
|
401
|
+
const values: Array<string | Array<string>> = [] // TODO: Do this for all other sparse attrs
|
|
402
|
+
const sparse = SparseClassNamePartImpl.browser(
|
|
403
|
+
parts,
|
|
404
|
+
node as HTMLElement | SVGElement,
|
|
405
|
+
ctx,
|
|
406
|
+
values
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
for (let i = 0; i < part.nodes.length; ++i) {
|
|
410
|
+
const node = part.nodes[i]
|
|
411
|
+
|
|
412
|
+
if (node._tag === "text") {
|
|
413
|
+
parts.push(new StaticTextImpl(node.value))
|
|
414
|
+
values.push(node.value)
|
|
415
|
+
} else {
|
|
416
|
+
values.push([])
|
|
417
|
+
parts.push(
|
|
418
|
+
new ClassNamePartImpl(
|
|
419
|
+
node.index,
|
|
420
|
+
({ value }) => sparse.update(replace(sparse.value, i, value || "")),
|
|
421
|
+
[]
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return Effect.succeed(sparse)
|
|
428
|
+
}
|
|
429
|
+
case "sparse-comment": {
|
|
430
|
+
const parts: Array<CommentPart | StaticText> = Array(part.nodes.length)
|
|
431
|
+
const sparse = SparseCommentPartImpl.browser(
|
|
432
|
+
node as Comment,
|
|
433
|
+
parts,
|
|
434
|
+
ctx
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
for (let i = 0; i < part.nodes.length; ++i) {
|
|
438
|
+
const node = part.nodes[i]
|
|
439
|
+
|
|
440
|
+
if (node._tag === "text") {
|
|
441
|
+
parts.push(new StaticTextImpl(node.value))
|
|
442
|
+
;(sparse as any).value[i] = node.value
|
|
443
|
+
} else {
|
|
444
|
+
parts.push(
|
|
445
|
+
new CommentPartImpl(
|
|
446
|
+
node.index,
|
|
447
|
+
({ value }) => sparse.update(replace(sparse.value, i, value || "")),
|
|
448
|
+
sparse.value[i]
|
|
449
|
+
)
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return Effect.succeed(sparse)
|
|
455
|
+
}
|
|
456
|
+
case "text-part":
|
|
457
|
+
return Effect.succeed(TextPartImpl.browser(document, part.index, node as Element, ctx))
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function buildTemplate(document: Document, { nodes }: Template.Template): DocumentFragment {
|
|
462
|
+
const fragment = document.createDocumentFragment()
|
|
463
|
+
|
|
464
|
+
for (let i = 0; i < nodes.length; ++i) {
|
|
465
|
+
fragment.append(buildNode(document, nodes[i], false))
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return fragment
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function buildNode(document: Document, node: Template.Node, isSvgContext: boolean): globalThis.Node {
|
|
472
|
+
switch (node._tag) {
|
|
473
|
+
case "element":
|
|
474
|
+
case "self-closing-element":
|
|
475
|
+
case "text-only-element":
|
|
476
|
+
return buildElement(document, node, isSvgContext)
|
|
477
|
+
case "text":
|
|
478
|
+
return document.createTextNode(node.value)
|
|
479
|
+
case "comment":
|
|
480
|
+
return document.createComment(node.value)
|
|
481
|
+
case "sparse-comment":
|
|
482
|
+
return document.createComment("")
|
|
483
|
+
// Create placeholders for these elements
|
|
484
|
+
case "comment-part":
|
|
485
|
+
case "node":
|
|
486
|
+
return document.createComment(`hole${node.index}`)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const SVG_NAMESPACE = "http://www.w3.org/2000/svg"
|
|
491
|
+
|
|
492
|
+
function buildElement(
|
|
493
|
+
document: Document,
|
|
494
|
+
node: Template.ElementNode | Template.SelfClosingElementNode | Template.TextOnlyElement,
|
|
495
|
+
isSvgContext: boolean
|
|
496
|
+
): Element {
|
|
497
|
+
const { _tag, attributes, tagName } = node
|
|
498
|
+
const isSvg = isSvgContext ? tagName !== "foreignObject" : tagName === "svg"
|
|
499
|
+
const element = isSvg
|
|
500
|
+
? document.createElementNS(SVG_NAMESPACE, tagName)
|
|
501
|
+
: document.createElement(tagName)
|
|
502
|
+
|
|
503
|
+
for (let i = 0; i < attributes.length; ++i) {
|
|
504
|
+
const attr = attributes[i]
|
|
505
|
+
|
|
506
|
+
// We only handle static attributes here, parts are handled elsewhere
|
|
507
|
+
if (attr._tag === "attribute") {
|
|
508
|
+
element.setAttribute(attr.name, attr.value)
|
|
509
|
+
} else if (attr._tag === "boolean") {
|
|
510
|
+
element.toggleAttribute(attr.name, true)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (_tag === "element") {
|
|
515
|
+
element.append(...node.children.map((child) => buildNode(document, child, isSvg)))
|
|
516
|
+
} else if (_tag === "text-only-element") {
|
|
517
|
+
element.append(...node.children.map((child) => buildTextChild(document, child)))
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return element
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function buildTextChild(document: Document, node: Template.Text): globalThis.Node {
|
|
524
|
+
if (node._tag === "text") {
|
|
525
|
+
return document.createTextNode(node.value)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return document.createComment(`hole${node.index}`)
|
|
529
|
+
}
|