@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,527 @@
|
|
|
1
|
+
import type { Context } from "@typed/context"
|
|
2
|
+
import * as Fx from "@typed/fx/Fx"
|
|
3
|
+
import { WithContext } from "@typed/fx/Sink"
|
|
4
|
+
import { isText, type Rendered } from "@typed/wire"
|
|
5
|
+
import type { Cause } from "effect/Cause"
|
|
6
|
+
import * as Effect from "effect/Effect"
|
|
7
|
+
import { equals } from "effect/Equal"
|
|
8
|
+
import { strict } from "effect/Equivalence"
|
|
9
|
+
import type { Equivalence } from "effect/Equivalence"
|
|
10
|
+
import * as Fiber from "effect/Fiber"
|
|
11
|
+
import * as ReadonlyArray from "effect/ReadonlyArray"
|
|
12
|
+
import type { Scope } from "effect/Scope"
|
|
13
|
+
import * as SynchronizedRef from "effect/SynchronizedRef"
|
|
14
|
+
import type { ElementRef } from "../ElementRef"
|
|
15
|
+
import type { ElementSource } from "../ElementSource"
|
|
16
|
+
import { unescape } from "../HtmlChunk"
|
|
17
|
+
import type {
|
|
18
|
+
AttributePart,
|
|
19
|
+
BooleanPart,
|
|
20
|
+
ClassNamePart,
|
|
21
|
+
CommentPart,
|
|
22
|
+
DataPart,
|
|
23
|
+
EventPart,
|
|
24
|
+
NodePart,
|
|
25
|
+
Part,
|
|
26
|
+
PropertyPart,
|
|
27
|
+
RefPart,
|
|
28
|
+
SparseAttributePart,
|
|
29
|
+
SparseClassNamePart,
|
|
30
|
+
SparseCommentPart,
|
|
31
|
+
SparsePart,
|
|
32
|
+
StaticText,
|
|
33
|
+
TextPart
|
|
34
|
+
} from "../Part"
|
|
35
|
+
import type { RenderContext } from "../RenderContext"
|
|
36
|
+
import { findHoleComment } from "./utils"
|
|
37
|
+
|
|
38
|
+
const strictEq = strict<any>()
|
|
39
|
+
|
|
40
|
+
const base = <T extends Part["_tag"]>(tag: T) =>
|
|
41
|
+
class Base {
|
|
42
|
+
readonly _tag: T = tag
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
readonly index: number,
|
|
46
|
+
readonly commit: (
|
|
47
|
+
params: {
|
|
48
|
+
previous: Extract<Part, { readonly _tag: T }>["value"]
|
|
49
|
+
value: Extract<Part, { readonly _tag: T }>["value"]
|
|
50
|
+
part: Extract<Part, { readonly _tag: T }>
|
|
51
|
+
}
|
|
52
|
+
) => Effect.Effect<Scope, never, void>,
|
|
53
|
+
public value: Extract<Part, { readonly _tag: T }>["value"],
|
|
54
|
+
readonly eq: Equivalence<Extract<Part, { readonly _tag: T }>["value"]> = equals
|
|
55
|
+
) {
|
|
56
|
+
this.update = this.update.bind(this)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
update(input: this["value"]) {
|
|
60
|
+
const previous = this.value as any
|
|
61
|
+
const value = this.getValue(input) as any
|
|
62
|
+
|
|
63
|
+
if (this.eq(previous as any, value as any)) {
|
|
64
|
+
return Effect.unit
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return Effect.tap(
|
|
68
|
+
this.commit.call(this, {
|
|
69
|
+
previous,
|
|
70
|
+
value,
|
|
71
|
+
part: this as any
|
|
72
|
+
}),
|
|
73
|
+
() => Effect.sync(() => this.value = value)
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getValue(value: unknown) {
|
|
78
|
+
return value
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class AttributePartImpl extends base("attribute") implements AttributePart {
|
|
83
|
+
constructor(
|
|
84
|
+
readonly name: string,
|
|
85
|
+
index: number,
|
|
86
|
+
commit: AttributePartImpl["commit"],
|
|
87
|
+
value: AttributePart["value"]
|
|
88
|
+
) {
|
|
89
|
+
super(index, commit, value, strictEq)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static browser(index: number, element: Element, name: string, context: RenderContext): AttributePartImpl {
|
|
93
|
+
return new AttributePartImpl(
|
|
94
|
+
name,
|
|
95
|
+
index,
|
|
96
|
+
({ part, value }) =>
|
|
97
|
+
context.queue.add(
|
|
98
|
+
part,
|
|
99
|
+
() => value == null ? element.removeAttribute(name) : element.setAttribute(name, value)
|
|
100
|
+
),
|
|
101
|
+
element.getAttribute(name)
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static server(
|
|
106
|
+
name: string,
|
|
107
|
+
index: number,
|
|
108
|
+
commit: AttributePartImpl["commit"]
|
|
109
|
+
) {
|
|
110
|
+
return new AttributePartImpl(name, index, commit, null)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export class BooleanPartImpl extends base("boolean") implements BooleanPart {
|
|
115
|
+
constructor(
|
|
116
|
+
readonly name: string,
|
|
117
|
+
index: number,
|
|
118
|
+
commit: BooleanPartImpl["commit"],
|
|
119
|
+
value: BooleanPart["value"]
|
|
120
|
+
) {
|
|
121
|
+
super(index, commit, value, strictEq)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static browser(index: number, element: Element, name: string, context: RenderContext): BooleanPartImpl {
|
|
125
|
+
return new BooleanPartImpl(
|
|
126
|
+
name,
|
|
127
|
+
index,
|
|
128
|
+
({ part, value }) =>
|
|
129
|
+
context.queue.add(
|
|
130
|
+
part,
|
|
131
|
+
() => element.toggleAttribute(name, value === true)
|
|
132
|
+
),
|
|
133
|
+
element.hasAttribute(name)
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
static server(
|
|
138
|
+
name: string,
|
|
139
|
+
index: number,
|
|
140
|
+
commit: BooleanPartImpl["commit"]
|
|
141
|
+
) {
|
|
142
|
+
return new BooleanPartImpl(name, index, commit, null)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const isString = (x: unknown): x is string => typeof x === "string"
|
|
147
|
+
|
|
148
|
+
export class ClassNamePartImpl extends base("className") implements ClassNamePart {
|
|
149
|
+
constructor(
|
|
150
|
+
index: number,
|
|
151
|
+
commit: ClassNamePartImpl["commit"],
|
|
152
|
+
value: ClassNamePart["value"]
|
|
153
|
+
) {
|
|
154
|
+
super(index, commit, value, strictEq)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
getValue(value: unknown): ReadonlyArray<string> {
|
|
158
|
+
if (isString(value)) {
|
|
159
|
+
return value.split(" ").filter((x) => isString(x) && x.trim() !== "")
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (Array.isArray(value)) {
|
|
163
|
+
return value.filter((x) => isString(x) && x.trim() !== "")
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return []
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static browser(index: number, element: Element, context: RenderContext): ClassNamePartImpl {
|
|
170
|
+
return new ClassNamePartImpl(
|
|
171
|
+
index,
|
|
172
|
+
({ part, previous, value }) =>
|
|
173
|
+
context.queue.add(
|
|
174
|
+
part,
|
|
175
|
+
() => {
|
|
176
|
+
const { added, removed } = diffStrings(
|
|
177
|
+
previous,
|
|
178
|
+
value
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
element.classList.add(...added)
|
|
182
|
+
element.classList.remove(...removed)
|
|
183
|
+
}
|
|
184
|
+
),
|
|
185
|
+
Array.from(element.classList)
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static server(
|
|
190
|
+
index: number,
|
|
191
|
+
commit: ClassNamePartImpl["commit"]
|
|
192
|
+
) {
|
|
193
|
+
return new ClassNamePartImpl(index, commit, null)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function diffStrings(
|
|
198
|
+
previous: ReadonlyArray<string> | null | undefined,
|
|
199
|
+
current: ReadonlyArray<string> | null | undefined
|
|
200
|
+
): { added: ReadonlyArray<string>; removed: ReadonlyArray<string>; unchanged: ReadonlyArray<string> } {
|
|
201
|
+
if (previous == null || previous.length === 0) {
|
|
202
|
+
return {
|
|
203
|
+
added: current || [],
|
|
204
|
+
removed: [],
|
|
205
|
+
unchanged: []
|
|
206
|
+
}
|
|
207
|
+
} else if (current == null || current.length === 0) {
|
|
208
|
+
return {
|
|
209
|
+
added: [],
|
|
210
|
+
removed: previous,
|
|
211
|
+
unchanged: []
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
const added = current.filter((c) => !previous.includes(c))
|
|
215
|
+
const removed: Array<string> = []
|
|
216
|
+
const unchanged: Array<string> = []
|
|
217
|
+
|
|
218
|
+
for (let i = 0; i < previous.length; ++i) {
|
|
219
|
+
if (current.includes(previous[i])) {
|
|
220
|
+
unchanged.push(previous[i])
|
|
221
|
+
} else {
|
|
222
|
+
removed.push(previous[i])
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
added,
|
|
228
|
+
removed,
|
|
229
|
+
unchanged
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export class CommentPartImpl extends base("comment") implements CommentPart {
|
|
235
|
+
static browser(index: number, comment: globalThis.Comment, ctx: RenderContext) {
|
|
236
|
+
return new CommentPartImpl(
|
|
237
|
+
index,
|
|
238
|
+
({ part, value }) => ctx.queue.add(part, () => comment.data = value || ""),
|
|
239
|
+
comment.data,
|
|
240
|
+
strictEq
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static server(index: number, commit: CommentPartImpl["commit"]) {
|
|
245
|
+
return new CommentPartImpl(index, commit, null)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export class DataPartImpl extends base("data") implements DataPart {
|
|
250
|
+
static browser(index: number, element: HTMLElement | SVGElement, ctx: RenderContext) {
|
|
251
|
+
return new DataPartImpl(
|
|
252
|
+
index,
|
|
253
|
+
({ part, previous, value }) =>
|
|
254
|
+
ctx.queue.add(
|
|
255
|
+
part,
|
|
256
|
+
() => {
|
|
257
|
+
const diff = diffDataSet(previous, value)
|
|
258
|
+
|
|
259
|
+
if (diff) {
|
|
260
|
+
const { added, removed } = diff
|
|
261
|
+
|
|
262
|
+
removed.forEach((k) => delete element.dataset[k])
|
|
263
|
+
added.forEach(([k, v]) => element.dataset[k] = v)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
),
|
|
267
|
+
element.dataset
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
static server(index: number, commit: DataPartImpl["commit"]) {
|
|
272
|
+
return new DataPartImpl(index, commit, null)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function diffDataSet(
|
|
277
|
+
a: Record<string, string | undefined> | null | undefined,
|
|
278
|
+
b: Record<string, string | undefined> | null | undefined
|
|
279
|
+
):
|
|
280
|
+
| { added: Array<readonly [string, string | undefined]>; removed: ReadonlyArray<string> }
|
|
281
|
+
| null
|
|
282
|
+
{
|
|
283
|
+
if (!a) return b ? { added: Object.entries(b), removed: [] } : null
|
|
284
|
+
if (!b) return { added: [], removed: Object.keys(a) }
|
|
285
|
+
|
|
286
|
+
const { added, removed, unchanged } = diffStrings(Object.keys(a), Object.keys(b))
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
added: added.concat(unchanged).map((k) => [k, b[k]] as const),
|
|
290
|
+
removed
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export class EventPartImpl extends base("event") implements EventPart {
|
|
295
|
+
constructor(
|
|
296
|
+
readonly name: string,
|
|
297
|
+
readonly onCause: <E>(cause: Cause<E>) => Effect.Effect<never, never, unknown>,
|
|
298
|
+
index: number,
|
|
299
|
+
commit: EventPartImpl["commit"],
|
|
300
|
+
value: EventPart["value"]
|
|
301
|
+
) {
|
|
302
|
+
super(index, commit, value, strictEq)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
static browser<T extends Rendered, E>(
|
|
306
|
+
name: string,
|
|
307
|
+
index: number,
|
|
308
|
+
ref: ElementRef<T>,
|
|
309
|
+
element: HTMLElement | SVGElement,
|
|
310
|
+
onCause: (cause: Cause<E>) => Effect.Effect<never, never, unknown>
|
|
311
|
+
): Effect.Effect<unknown, never, void> {
|
|
312
|
+
return withSwitchFork((fork, ctx) => {
|
|
313
|
+
const source = ref.query(element)
|
|
314
|
+
|
|
315
|
+
return Effect.succeed(
|
|
316
|
+
new EventPartImpl(
|
|
317
|
+
name,
|
|
318
|
+
onCause as any,
|
|
319
|
+
index,
|
|
320
|
+
({ value }) => {
|
|
321
|
+
return value
|
|
322
|
+
? Fx.run(
|
|
323
|
+
source.events(name as keyof HTMLElementEventMap | keyof SVGElementEventMap, value.options),
|
|
324
|
+
WithContext(onCause, value.handler)
|
|
325
|
+
).pipe(
|
|
326
|
+
Effect.provide(ctx),
|
|
327
|
+
fork
|
|
328
|
+
)
|
|
329
|
+
: fork(Effect.unit)
|
|
330
|
+
},
|
|
331
|
+
null
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function withScopedFork<R, E, A>(f: (fork: Fx.ScopedFork) => Effect.Effect<R, E, A>): Effect.Effect<R | Scope, E, A> {
|
|
339
|
+
return Effect.scopeWith((scope) => f(Effect.forkIn(scope)))
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Ensures only a single fiber is executing
|
|
343
|
+
function withSwitchFork<R, E, A>(
|
|
344
|
+
f: (fork: Fx.FxFork, ctx: Context<R | Scope>) => Effect.Effect<R, E, A>
|
|
345
|
+
): Effect.Effect<R | Scope, E, A> {
|
|
346
|
+
return Effect.contextWithEffect((ctx) =>
|
|
347
|
+
withScopedFork((fork) =>
|
|
348
|
+
Effect.flatMap(
|
|
349
|
+
SynchronizedRef.make<Fiber.Fiber<never, void>>(Fiber.unit),
|
|
350
|
+
(ref) =>
|
|
351
|
+
f((effect) =>
|
|
352
|
+
SynchronizedRef.updateAndGetEffect(
|
|
353
|
+
ref,
|
|
354
|
+
(fiber) => Effect.flatMap(Fiber.interrupt(fiber), () => fork(effect))
|
|
355
|
+
), ctx)
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export class NodePartImpl extends base("node") implements NodePart {}
|
|
362
|
+
|
|
363
|
+
export class PropertyPartImpl extends base("property") implements PropertyPart {
|
|
364
|
+
constructor(
|
|
365
|
+
readonly name: string,
|
|
366
|
+
index: number,
|
|
367
|
+
commit: PropertyPartImpl["commit"],
|
|
368
|
+
value: PropertyPartImpl["value"]
|
|
369
|
+
) {
|
|
370
|
+
super(index, commit, value, strictEq)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
static browser(index: number, node: Node, name: string, ctx: RenderContext) {
|
|
374
|
+
const existing = (node as Element).getAttribute(name)
|
|
375
|
+
|
|
376
|
+
return new PropertyPartImpl(
|
|
377
|
+
name,
|
|
378
|
+
index,
|
|
379
|
+
({ part, value }) => ctx.queue.add(part, () => (node as any)[name] = value),
|
|
380
|
+
existing ? unescape(existing) : null
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export class RefPartImpl implements RefPart {
|
|
386
|
+
readonly _tag = "ref"
|
|
387
|
+
|
|
388
|
+
constructor(readonly value: ElementSource<any>, readonly index: number) {}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export class TextPartImpl extends base("text") implements TextPart {
|
|
392
|
+
// TODO: Make this properly
|
|
393
|
+
static browser(document: Document, index: number, element: Element, ctx: RenderContext) {
|
|
394
|
+
const comment = findHoleComment(element, index)
|
|
395
|
+
const text = comment.previousSibling && isText(comment.previousSibling)
|
|
396
|
+
? comment.previousSibling
|
|
397
|
+
: document.createTextNode("")
|
|
398
|
+
|
|
399
|
+
return new TextPartImpl(
|
|
400
|
+
index,
|
|
401
|
+
({ part, value }) => ctx.queue.add(part, () => text.nodeValue = value ?? null),
|
|
402
|
+
text.nodeValue,
|
|
403
|
+
strictEq
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const sparse = <T extends SparsePart["_tag"]>(tag: T) =>
|
|
409
|
+
class Base {
|
|
410
|
+
readonly _tag: T = tag
|
|
411
|
+
|
|
412
|
+
constructor(
|
|
413
|
+
readonly commit: (
|
|
414
|
+
params: {
|
|
415
|
+
previous: SparseAttributeValues<Extract<SparsePart, { readonly _tag: T }>["parts"]>
|
|
416
|
+
value: SparseAttributeValues<Extract<SparsePart, { readonly _tag: T }>["parts"]>
|
|
417
|
+
part: Extract<SparsePart, { readonly _tag: T }>
|
|
418
|
+
}
|
|
419
|
+
) => Effect.Effect<Scope, never, void>,
|
|
420
|
+
public value: SparseAttributeValues<Extract<SparsePart, { readonly _tag: T }>["parts"]>,
|
|
421
|
+
readonly eq: Equivalence<SparseAttributeValues<Extract<SparsePart, { readonly _tag: T }>["parts"]>> = equals
|
|
422
|
+
) {}
|
|
423
|
+
|
|
424
|
+
update = (value: this["value"]) => {
|
|
425
|
+
if (this.eq(this.value as any, value as any)) {
|
|
426
|
+
return Effect.unit
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return this.commit({
|
|
430
|
+
previous: this.value,
|
|
431
|
+
value: this.value = value as any,
|
|
432
|
+
part: this
|
|
433
|
+
} as any)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
type SparseAttributeValues<T extends ReadonlyArray<AttributePart | ClassNamePart | CommentPart | StaticText>> =
|
|
438
|
+
ReadonlyArray<
|
|
439
|
+
SparseAttributeValue<T[number]>
|
|
440
|
+
>
|
|
441
|
+
type SparseAttributeValue<T extends AttributePart | ClassNamePart | CommentPart | StaticText> = T["value"]
|
|
442
|
+
|
|
443
|
+
export class SparseAttributePartImpl extends sparse("sparse/attribute") implements SparseAttributePart {
|
|
444
|
+
constructor(
|
|
445
|
+
readonly name: string,
|
|
446
|
+
readonly parts: ReadonlyArray<AttributePart | StaticText>,
|
|
447
|
+
commit: SparseAttributePartImpl["commit"]
|
|
448
|
+
) {
|
|
449
|
+
super(commit, [], ReadonlyArray.getEquivalence(strictEq))
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
static browser(
|
|
453
|
+
name: string,
|
|
454
|
+
parts: ReadonlyArray<AttributePart | StaticText>,
|
|
455
|
+
element: HTMLElement | SVGElement,
|
|
456
|
+
ctx: RenderContext
|
|
457
|
+
) {
|
|
458
|
+
return new SparseAttributePartImpl(
|
|
459
|
+
name,
|
|
460
|
+
parts,
|
|
461
|
+
({ part, value }) =>
|
|
462
|
+
ctx.queue.add(part, () => element.setAttribute(name, value.flatMap((s) => isNonEmptyString(s, true)).join("")))
|
|
463
|
+
)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export class SparseClassNamePartImpl extends sparse("sparse/className") implements SparseClassNamePart {
|
|
468
|
+
constructor(
|
|
469
|
+
readonly parts: ReadonlyArray<ClassNamePart | StaticText>,
|
|
470
|
+
commit: SparseClassNamePartImpl["commit"],
|
|
471
|
+
values: Array<string | Array<string>>
|
|
472
|
+
) {
|
|
473
|
+
super(commit, values, ReadonlyArray.getEquivalence(strictEq))
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
static browser(
|
|
477
|
+
parts: ReadonlyArray<ClassNamePart | StaticText>,
|
|
478
|
+
element: HTMLElement | SVGElement,
|
|
479
|
+
ctx: RenderContext,
|
|
480
|
+
values: Array<string | Array<string>> = []
|
|
481
|
+
) {
|
|
482
|
+
return new SparseClassNamePartImpl(
|
|
483
|
+
parts,
|
|
484
|
+
({ part, value }) =>
|
|
485
|
+
ctx.queue.add(part, () => {
|
|
486
|
+
return element.setAttribute("class", value.flatMap((s) => isNonEmptyString(s, true)).join(" "))
|
|
487
|
+
}),
|
|
488
|
+
values
|
|
489
|
+
)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export class SparseCommentPartImpl extends sparse("sparse/comment") implements SparseCommentPart {
|
|
494
|
+
constructor(
|
|
495
|
+
readonly parts: ReadonlyArray<CommentPart | StaticText>,
|
|
496
|
+
commit: SparseCommentPartImpl["commit"],
|
|
497
|
+
value: SparseCommentPartImpl["value"]
|
|
498
|
+
) {
|
|
499
|
+
super(commit, value, ReadonlyArray.getEquivalence(strictEq))
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
static browser(comment: Comment, parts: ReadonlyArray<CommentPart | StaticText>, ctx: RenderContext) {
|
|
503
|
+
return new SparseCommentPartImpl(
|
|
504
|
+
parts,
|
|
505
|
+
({ part, value }) =>
|
|
506
|
+
ctx.queue.add(part, () => comment.nodeValue = value.flatMap((s) => isNonEmptyString(s, false)).join("")),
|
|
507
|
+
[]
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export class StaticTextImpl implements StaticText {
|
|
513
|
+
readonly _tag = "static/text"
|
|
514
|
+
|
|
515
|
+
constructor(readonly value: string) {}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function isNonEmptyString(s: string | ReadonlyArray<string> | null | undefined, trim: boolean): Array<string> {
|
|
519
|
+
if (s == null) return []
|
|
520
|
+
if (Array.isArray(s)) return s.flatMap((s) => isNonEmptyString(s, trim))
|
|
521
|
+
|
|
522
|
+
const trimmed = trim ? (s as string).trim() : s
|
|
523
|
+
|
|
524
|
+
if (trimmed.length === 0) return []
|
|
525
|
+
|
|
526
|
+
return [trimmed as string]
|
|
527
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const PATTERN = /(\s*([^>\s]*))/g
|
|
2
|
+
const QUOTES = new Set(`"'`)
|
|
3
|
+
|
|
4
|
+
export type AttrChunk = {
|
|
5
|
+
readonly length: number
|
|
6
|
+
readonly value: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extract an attribute from a chunk of text.
|
|
11
|
+
*/
|
|
12
|
+
export default function readAttribute(text: string, pos: number): AttrChunk | null {
|
|
13
|
+
const quote = text.charAt(pos)
|
|
14
|
+
const pos1 = pos + 1
|
|
15
|
+
|
|
16
|
+
if (QUOTES.has(quote)) {
|
|
17
|
+
const nextQuote = text.indexOf(quote, pos1)
|
|
18
|
+
if (nextQuote === -1) {
|
|
19
|
+
return null
|
|
20
|
+
} else {
|
|
21
|
+
return { length: nextQuote - pos + 1, value: text.substring(pos1, nextQuote) }
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
PATTERN.lastIndex = pos
|
|
25
|
+
const match = PATTERN.exec(text) || []
|
|
26
|
+
return { length: match[1].length, value: match[2] }
|
|
27
|
+
}
|
|
28
|
+
}
|