@typed/template 0.1.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Directive/package.json +6 -0
- package/ElementRef/package.json +6 -0
- package/ElementSource/package.json +6 -0
- package/Entry/package.json +6 -0
- package/EventHandler/package.json +6 -0
- package/Html/package.json +6 -0
- package/HtmlChunk/package.json +6 -0
- package/Hydrate/package.json +6 -0
- package/Many/package.json +6 -0
- package/Meta/package.json +6 -0
- package/Parser/package.json +6 -0
- package/Part/package.json +6 -0
- package/Placeholder/package.json +6 -0
- package/Platform/package.json +6 -0
- package/Render/package.json +6 -0
- package/RenderContext/package.json +6 -0
- package/RenderEvent/package.json +6 -0
- package/RenderTemplate/package.json +6 -0
- package/Renderable/package.json +6 -0
- package/Template/package.json +6 -0
- package/TemplateInstance/package.json +6 -0
- package/Test/package.json +6 -0
- package/Vitest/package.json +6 -0
- package/dist/cjs/Directive.js +1 -1
- package/dist/cjs/Directive.js.map +1 -1
- package/dist/cjs/ElementRef.js +23 -13
- package/dist/cjs/ElementRef.js.map +1 -1
- package/dist/cjs/ElementSource.js +16 -18
- package/dist/cjs/ElementSource.js.map +1 -1
- package/dist/cjs/EventHandler.js +1 -1
- package/dist/cjs/EventHandler.js.map +1 -1
- package/dist/cjs/Html.js +31 -32
- package/dist/cjs/Html.js.map +1 -1
- package/dist/cjs/HtmlChunk.js +4 -1
- package/dist/cjs/HtmlChunk.js.map +1 -1
- package/dist/cjs/Hydrate.js +1 -1
- package/dist/cjs/Hydrate.js.map +1 -1
- package/dist/cjs/Many.js +14 -13
- package/dist/cjs/Many.js.map +1 -1
- package/dist/cjs/Parser.js +11 -323
- package/dist/cjs/Parser.js.map +1 -1
- package/dist/cjs/Placeholder.js +3 -3
- package/dist/cjs/Placeholder.js.map +1 -1
- package/dist/cjs/Platform.js +4 -4
- package/dist/cjs/Platform.js.map +1 -1
- package/dist/cjs/Render.js +1 -1
- package/dist/cjs/Render.js.map +1 -1
- package/dist/cjs/RenderContext.js +48 -27
- package/dist/cjs/RenderContext.js.map +1 -1
- package/dist/cjs/RenderTemplate.js +2 -17
- package/dist/cjs/RenderTemplate.js.map +1 -1
- package/dist/cjs/Template.js +27 -1
- package/dist/cjs/Template.js.map +1 -1
- package/dist/cjs/TemplateInstance.js +2 -2
- package/dist/cjs/TemplateInstance.js.map +1 -1
- package/dist/cjs/Test.js +20 -7
- package/dist/cjs/Test.js.map +1 -1
- package/dist/cjs/index.js +0 -12
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/EventSource.js +95 -0
- package/dist/cjs/internal/EventSource.js.map +1 -0
- package/dist/cjs/internal/browser.js +11 -11
- package/dist/cjs/internal/browser.js.map +1 -1
- package/dist/cjs/internal/hydrate.js +49 -50
- package/dist/cjs/internal/hydrate.js.map +1 -1
- package/dist/cjs/internal/indexRefCounter.js +49 -2
- package/dist/cjs/internal/indexRefCounter.js.map +1 -1
- package/dist/cjs/internal/parser.js +60 -17
- package/dist/cjs/internal/parser.js.map +1 -1
- package/dist/cjs/internal/parts.js +128 -28
- package/dist/cjs/internal/parts.js.map +1 -1
- package/dist/cjs/internal/render.js +486 -53
- package/dist/cjs/internal/render.js.map +1 -1
- package/dist/cjs/internal/server.js +5 -2
- package/dist/cjs/internal/server.js.map +1 -1
- package/dist/dts/Directive.d.ts.map +1 -1
- package/dist/dts/ElementRef.d.ts +4 -2
- package/dist/dts/ElementRef.d.ts.map +1 -1
- package/dist/dts/ElementSource.d.ts +10 -5
- package/dist/dts/ElementSource.d.ts.map +1 -1
- package/dist/dts/EventHandler.d.ts.map +1 -1
- package/dist/dts/Html.d.ts +1 -1
- package/dist/dts/Html.d.ts.map +1 -1
- package/dist/dts/HtmlChunk.d.ts.map +1 -1
- package/dist/dts/Many.d.ts +13 -11
- package/dist/dts/Many.d.ts.map +1 -1
- package/dist/dts/Parser.d.ts +3 -6
- package/dist/dts/Parser.d.ts.map +1 -1
- package/dist/dts/Part.d.ts +13 -3
- package/dist/dts/Part.d.ts.map +1 -1
- package/dist/dts/Placeholder.d.ts +2 -2
- package/dist/dts/Placeholder.d.ts.map +1 -1
- package/dist/dts/Render.d.ts +2 -1
- package/dist/dts/Render.d.ts.map +1 -1
- package/dist/dts/RenderContext.d.ts +5 -4
- package/dist/dts/RenderContext.d.ts.map +1 -1
- package/dist/dts/RenderTemplate.d.ts +2 -16
- package/dist/dts/RenderTemplate.d.ts.map +1 -1
- package/dist/dts/Renderable.d.ts +2 -2
- package/dist/dts/Renderable.d.ts.map +1 -1
- package/dist/dts/Template.d.ts +21 -3
- package/dist/dts/Template.d.ts.map +1 -1
- package/dist/dts/TemplateInstance.d.ts +3 -2
- package/dist/dts/TemplateInstance.d.ts.map +1 -1
- package/dist/dts/Test.d.ts +4 -6
- package/dist/dts/Test.d.ts.map +1 -1
- package/dist/dts/index.d.ts +0 -4
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/EventSource.d.ts +12 -0
- package/dist/dts/internal/EventSource.d.ts.map +1 -0
- package/dist/dts/internal/browser.d.ts.map +1 -1
- package/dist/dts/internal/hydrate.d.ts +5 -5
- package/dist/dts/internal/hydrate.d.ts.map +1 -1
- package/dist/dts/internal/indexRefCounter.d.ts +5 -0
- package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
- package/dist/dts/internal/module-augmentation.d.ts +0 -4
- package/dist/dts/internal/module-augmentation.d.ts.map +1 -1
- package/dist/dts/internal/parser.d.ts +8 -0
- package/dist/dts/internal/parser.d.ts.map +1 -1
- package/dist/dts/internal/parts.d.ts +66 -56
- package/dist/dts/internal/parts.d.ts.map +1 -1
- package/dist/dts/internal/render.d.ts +7 -7
- package/dist/dts/internal/render.d.ts.map +1 -1
- package/dist/dts/internal/server.d.ts.map +1 -1
- package/dist/esm/Directive.js +1 -1
- package/dist/esm/Directive.js.map +1 -1
- package/dist/esm/ElementRef.js +12 -7
- package/dist/esm/ElementRef.js.map +1 -1
- package/dist/esm/ElementSource.js +16 -13
- package/dist/esm/ElementSource.js.map +1 -1
- package/dist/esm/EventHandler.js +1 -1
- package/dist/esm/EventHandler.js.map +1 -1
- package/dist/esm/Html.js +29 -24
- package/dist/esm/Html.js.map +1 -1
- package/dist/esm/HtmlChunk.js +6 -1
- package/dist/esm/HtmlChunk.js.map +1 -1
- package/dist/esm/Hydrate.js +1 -1
- package/dist/esm/Hydrate.js.map +1 -1
- package/dist/esm/Many.js +14 -10
- package/dist/esm/Many.js.map +1 -1
- package/dist/esm/Parser.js +6 -335
- package/dist/esm/Parser.js.map +1 -1
- package/dist/esm/Placeholder.js +2 -2
- package/dist/esm/Placeholder.js.map +1 -1
- package/dist/esm/Platform.js +2 -2
- package/dist/esm/Platform.js.map +1 -1
- package/dist/esm/Render.js +1 -1
- package/dist/esm/Render.js.map +1 -1
- package/dist/esm/RenderContext.js +38 -17
- package/dist/esm/RenderContext.js.map +1 -1
- package/dist/esm/RenderTemplate.js +2 -12
- package/dist/esm/RenderTemplate.js.map +1 -1
- package/dist/esm/Template.js +24 -0
- package/dist/esm/Template.js.map +1 -1
- package/dist/esm/TemplateInstance.js +2 -2
- package/dist/esm/TemplateInstance.js.map +1 -1
- package/dist/esm/Test.js +20 -7
- package/dist/esm/Test.js.map +1 -1
- package/dist/esm/index.js +0 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/EventSource.js +91 -0
- package/dist/esm/internal/EventSource.js.map +1 -0
- package/dist/esm/internal/browser.js +12 -12
- package/dist/esm/internal/browser.js.map +1 -1
- package/dist/esm/internal/hydrate.js +45 -46
- package/dist/esm/internal/hydrate.js.map +1 -1
- package/dist/esm/internal/indexRefCounter.js +50 -2
- package/dist/esm/internal/indexRefCounter.js.map +1 -1
- package/dist/esm/internal/parser.js +84 -17
- package/dist/esm/internal/parser.js.map +1 -1
- package/dist/esm/internal/parts.js +110 -27
- package/dist/esm/internal/parts.js.map +1 -1
- package/dist/esm/internal/render.js +476 -58
- package/dist/esm/internal/render.js.map +1 -1
- package/dist/esm/internal/server.js +5 -4
- package/dist/esm/internal/server.js.map +1 -1
- package/package.json +10 -26
- package/src/Directive.ts +1 -1
- package/src/ElementRef.ts +18 -14
- package/src/ElementSource.ts +62 -47
- package/src/EventHandler.ts +1 -1
- package/src/Html.ts +58 -57
- package/src/HtmlChunk.ts +15 -1
- package/src/Hydrate.ts +1 -1
- package/src/Many.ts +53 -43
- package/src/Parser.ts +10 -453
- package/src/Part.ts +15 -3
- package/src/Placeholder.ts +4 -4
- package/src/Platform.ts +2 -2
- package/src/Render.ts +7 -2
- package/src/RenderContext.ts +49 -21
- package/src/RenderTemplate.ts +9 -54
- package/src/Renderable.ts +2 -1
- package/src/Template.ts +26 -0
- package/src/TemplateInstance.ts +9 -9
- package/src/Test.ts +40 -21
- package/src/index.ts +0 -4
- package/src/internal/EventSource.ts +153 -0
- package/src/internal/browser.ts +26 -25
- package/src/internal/hydrate.ts +68 -61
- package/src/internal/indexRefCounter.ts +63 -2
- package/src/internal/module-augmentation.ts +0 -4
- package/src/internal/parser.ts +92 -19
- package/src/internal/parts.ts +158 -73
- package/src/internal/render.ts +701 -89
- package/src/internal/server.ts +5 -3
- package/dist/cjs/Token.js +0 -270
- package/dist/cjs/Token.js.map +0 -1
- package/dist/cjs/Tokenizer.js +0 -18
- package/dist/cjs/Tokenizer.js.map +0 -1
- package/dist/cjs/internal/readAttribute.js +0 -34
- package/dist/cjs/internal/readAttribute.js.map +0 -1
- package/dist/cjs/internal/tokenizer.js +0 -264
- package/dist/cjs/internal/tokenizer.js.map +0 -1
- package/dist/dts/Token.d.ts +0 -202
- package/dist/dts/Token.d.ts.map +0 -1
- package/dist/dts/Tokenizer.d.ts +0 -6
- package/dist/dts/Tokenizer.d.ts.map +0 -1
- package/dist/dts/internal/readAttribute.d.ts +0 -9
- package/dist/dts/internal/readAttribute.d.ts.map +0 -1
- package/dist/dts/internal/tokenizer.d.ts +0 -3
- package/dist/dts/internal/tokenizer.d.ts.map +0 -1
- package/dist/esm/Token.js +0 -264
- package/dist/esm/Token.js.map +0 -1
- package/dist/esm/Tokenizer.js +0 -9
- package/dist/esm/Tokenizer.js.map +0 -1
- package/dist/esm/internal/readAttribute.js +0 -24
- package/dist/esm/internal/readAttribute.js.map +0 -1
- package/dist/esm/internal/tokenizer.js +0 -296
- package/dist/esm/internal/tokenizer.js.map +0 -1
- package/src/Token.ts +0 -269
- package/src/Tokenizer.ts +0 -10
- package/src/internal/readAttribute.ts +0 -28
- package/src/internal/tokenizer.ts +0 -338
package/src/RenderContext.ts
CHANGED
|
@@ -11,10 +11,13 @@ import type { Environment } from "@typed/environment"
|
|
|
11
11
|
import { CurrentEnvironment } from "@typed/environment"
|
|
12
12
|
import * as Idle from "@typed/fx/Idle"
|
|
13
13
|
import type { Rendered } from "@typed/wire"
|
|
14
|
-
import
|
|
14
|
+
import * as Effect from "effect/Effect"
|
|
15
|
+
import * as Layer from "effect/Layer"
|
|
16
|
+
import * as Option from "effect/Option"
|
|
15
17
|
import * as Scope from "effect/Scope"
|
|
16
18
|
import type { Entry } from "./Entry.js"
|
|
17
|
-
|
|
19
|
+
|
|
20
|
+
// TODO: We should probably have a more explicit environment type between DOM/HTML rendering
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* The context in which templates are rendered within
|
|
@@ -54,7 +57,7 @@ export const RenderContext: Context.Tagged<RenderContext, RenderContext> = Conte
|
|
|
54
57
|
* @since 1.0.0
|
|
55
58
|
*/
|
|
56
59
|
export interface RenderQueue {
|
|
57
|
-
readonly add: (part:
|
|
60
|
+
readonly add: (part: unknown, task: () => void) => Effect.Effect<Scope.Scope, never, void>
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
/**
|
|
@@ -114,19 +117,19 @@ const buildWithCurrentEnvironment = (environment: Environment, skipRenderSchedul
|
|
|
114
117
|
/**
|
|
115
118
|
* @since 1.0.0
|
|
116
119
|
*/
|
|
117
|
-
export const
|
|
120
|
+
export const dom: (
|
|
118
121
|
window: Window & GlobalThis,
|
|
119
122
|
options?: DomServicesElementParams & { readonly skipRenderScheduling?: boolean }
|
|
120
123
|
) => Layer.Layer<never, never, RenderContext | CurrentEnvironment | DomServices> = (window, options) =>
|
|
121
124
|
Layer.provideMerge(
|
|
122
|
-
Layer.mergeAll(Window.layer(window), GlobalThis.layer(window)),
|
|
123
125
|
Layer.mergeAll(
|
|
124
126
|
buildWithCurrentEnvironment(
|
|
125
|
-
"
|
|
127
|
+
"dom",
|
|
126
128
|
options?.skipRenderScheduling
|
|
127
129
|
),
|
|
128
130
|
domServices(options)
|
|
129
|
-
)
|
|
131
|
+
),
|
|
132
|
+
Layer.mergeAll(Window.layer(window), GlobalThis.layer(window))
|
|
130
133
|
)
|
|
131
134
|
|
|
132
135
|
/**
|
|
@@ -146,8 +149,9 @@ export {
|
|
|
146
149
|
}
|
|
147
150
|
|
|
148
151
|
class RenderQueueImpl implements RenderQueue {
|
|
149
|
-
queue = new Map<
|
|
152
|
+
queue = new Map<unknown, () => void>()
|
|
150
153
|
scheduled = false
|
|
154
|
+
run: Effect.Effect<Scope.Scope, never, void>
|
|
151
155
|
|
|
152
156
|
constructor(
|
|
153
157
|
readonly scope: Scope.Scope,
|
|
@@ -155,9 +159,11 @@ class RenderQueueImpl implements RenderQueue {
|
|
|
155
159
|
readonly skipRenderScheduling: boolean = false
|
|
156
160
|
) {
|
|
157
161
|
this.add.bind(this)
|
|
162
|
+
|
|
163
|
+
this.run = typeof requestAnimationFrame === "undefined" ? this.runIdle : this.runAnimationFrame
|
|
158
164
|
}
|
|
159
165
|
|
|
160
|
-
add(part:
|
|
166
|
+
add(part: unknown, task: () => void) {
|
|
161
167
|
if (this.skipRenderScheduling) return Effect.sync(task)
|
|
162
168
|
|
|
163
169
|
return Effect.suspend(() => {
|
|
@@ -190,26 +196,20 @@ class RenderQueueImpl implements RenderQueue {
|
|
|
190
196
|
)
|
|
191
197
|
})
|
|
192
198
|
|
|
193
|
-
|
|
194
|
-
Effect.flatMap(
|
|
199
|
+
runIdle: Effect.Effect<Scope.Scope, never, void> = Effect.suspend(() => {
|
|
200
|
+
return Effect.flatMap(
|
|
195
201
|
Idle.whenIdle(this.options),
|
|
196
202
|
(deadline) =>
|
|
197
203
|
Effect.suspend(() => {
|
|
198
204
|
const iterator = this.queue.entries()
|
|
199
205
|
|
|
200
|
-
while (Idle.shouldContinue(deadline)) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (result.done) break
|
|
204
|
-
else {
|
|
205
|
-
const [part, task] = result.value
|
|
206
|
-
this.queue.delete(part)
|
|
207
|
-
task()
|
|
208
|
-
}
|
|
206
|
+
while (Idle.shouldContinue(deadline) && this.runTask(iterator)) {
|
|
207
|
+
// Continue
|
|
209
208
|
}
|
|
210
209
|
|
|
210
|
+
// If we have more work to do, schedule another run
|
|
211
211
|
if (this.queue.size > 0) {
|
|
212
|
-
return this.
|
|
212
|
+
return this.runIdle
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
this.scheduled = false
|
|
@@ -217,5 +217,33 @@ class RenderQueueImpl implements RenderQueue {
|
|
|
217
217
|
return Effect.unit
|
|
218
218
|
})
|
|
219
219
|
)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
runAnimationFrame: Effect.Effect<Scope.Scope, never, void> = Effect.zipRight(
|
|
223
|
+
Effect.asyncOption<never, never, void>((cb) => {
|
|
224
|
+
const id = requestAnimationFrame(() => cb(Effect.unit))
|
|
225
|
+
return Option.some(Effect.sync(() => cancelAnimationFrame(id)))
|
|
226
|
+
}),
|
|
227
|
+
Effect.sync(() => {
|
|
228
|
+
const iterator = this.queue.entries()
|
|
229
|
+
|
|
230
|
+
while (this.runTask(iterator)) {
|
|
231
|
+
// Continue
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
this.scheduled = false
|
|
235
|
+
})
|
|
220
236
|
)
|
|
237
|
+
|
|
238
|
+
private runTask = (iterator: Iterator<[unknown, () => void]>) => {
|
|
239
|
+
const result = iterator.next()
|
|
240
|
+
|
|
241
|
+
if (result.done) return false
|
|
242
|
+
else {
|
|
243
|
+
const [part, task] = result.value
|
|
244
|
+
this.queue.delete(part)
|
|
245
|
+
task()
|
|
246
|
+
return true
|
|
247
|
+
}
|
|
248
|
+
}
|
|
221
249
|
}
|
package/src/RenderTemplate.ts
CHANGED
|
@@ -4,30 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
import * as Context from "@typed/context"
|
|
6
6
|
import * as Fx from "@typed/fx/Fx"
|
|
7
|
-
import type { Rendered } from "@typed/wire"
|
|
8
|
-
import type * as Effect from "effect/Effect"
|
|
9
7
|
import type { Scope } from "effect/Scope"
|
|
10
|
-
import type { ElementRef } from "./ElementRef.js"
|
|
11
8
|
import type { Placeholder } from "./Placeholder.js"
|
|
12
9
|
import type { Renderable } from "./Renderable.js"
|
|
13
10
|
import type { RenderEvent } from "./RenderEvent.js"
|
|
14
|
-
import type { TemplateInstance } from "./TemplateInstance.js"
|
|
15
11
|
|
|
16
12
|
/**
|
|
17
13
|
* @since 1.0.0
|
|
18
14
|
*/
|
|
19
15
|
export interface RenderTemplate {
|
|
20
|
-
<Values extends ReadonlyArray<Renderable<any, any
|
|
16
|
+
<Values extends ReadonlyArray<Renderable<any, any>>>(
|
|
21
17
|
templateStrings: TemplateStringsArray,
|
|
22
|
-
values: Values
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
TemplateInstance<
|
|
28
|
-
Placeholder.Error<Values[number]>,
|
|
29
|
-
T
|
|
30
|
-
>
|
|
18
|
+
values: Values
|
|
19
|
+
): Fx.Fx<
|
|
20
|
+
Scope | Placeholder.Context<Values[number]>,
|
|
21
|
+
Placeholder.Error<Values[number]>,
|
|
22
|
+
RenderEvent
|
|
31
23
|
>
|
|
32
24
|
}
|
|
33
25
|
|
|
@@ -38,51 +30,14 @@ export const RenderTemplate: Context.Tagged<RenderTemplate, RenderTemplate> = Co
|
|
|
38
30
|
RenderTemplate,
|
|
39
31
|
RenderTemplate
|
|
40
32
|
>(
|
|
41
|
-
"
|
|
33
|
+
"@typed/template/RenderTemplate"
|
|
42
34
|
)
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @since 1.0.0
|
|
46
|
-
*/
|
|
47
|
-
export interface TemplateFx<R, E, T extends Rendered = Rendered> extends
|
|
48
|
-
Fx.Fx<
|
|
49
|
-
RenderTemplate | Scope | R,
|
|
50
|
-
E,
|
|
51
|
-
RenderEvent
|
|
52
|
-
>
|
|
53
|
-
{
|
|
54
|
-
readonly instance: Effect.Effect<
|
|
55
|
-
RenderTemplate | Scope | R,
|
|
56
|
-
never,
|
|
57
|
-
TemplateInstance<
|
|
58
|
-
E,
|
|
59
|
-
T
|
|
60
|
-
>
|
|
61
|
-
>
|
|
62
|
-
}
|
|
63
|
-
|
|
64
35
|
/**
|
|
65
36
|
* @since 1.0.0
|
|
66
37
|
*/
|
|
67
38
|
export function html<const Values extends ReadonlyArray<Renderable<any, any>>>(
|
|
68
39
|
template: TemplateStringsArray,
|
|
69
40
|
...values: Values
|
|
70
|
-
):
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return Object.assign(Fx.fromFxEffect(instance), { instance })
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* @since 1.0.0
|
|
78
|
-
*/
|
|
79
|
-
export function as<T extends Rendered = Rendered>(ref: ElementRef<T>) {
|
|
80
|
-
return function html<const Values extends ReadonlyArray<Renderable<any, any>>>(
|
|
81
|
-
template: TemplateStringsArray,
|
|
82
|
-
...values: Values
|
|
83
|
-
): TemplateFx<Placeholder.Context<Values[number]>, Placeholder.Error<Values[number]>, T> {
|
|
84
|
-
const instance = RenderTemplate.withEffect((render) => render(template, values, ref))
|
|
85
|
-
|
|
86
|
-
return Object.assign(Fx.fromFxEffect(instance), { instance })
|
|
87
|
-
}
|
|
41
|
+
): Fx.Fx<RenderTemplate | Scope | Placeholder.Context<Values[number]>, Placeholder.Error<Values[number]>, RenderEvent> {
|
|
42
|
+
return Fx.fromFxEffect(RenderTemplate.with((render) => render(template, values)))
|
|
88
43
|
}
|
package/src/Renderable.ts
CHANGED
|
@@ -12,7 +12,8 @@ import type { RenderEvent } from "./RenderEvent.js"
|
|
|
12
12
|
*/
|
|
13
13
|
export type Renderable<R = never, E = never> =
|
|
14
14
|
| Renderable.Value
|
|
15
|
-
|
|
|
15
|
+
| Placeholder<R, E, any>
|
|
16
|
+
| { readonly [key: string]: Renderable<R, E> | Placeholder<R, E, any> | unknown } // TODO: Should we manage data attributes this way?
|
|
16
17
|
| Placeholder<R, E, any>
|
|
17
18
|
| Effect<R, E, any>
|
|
18
19
|
| Fx<R, E, any>
|
package/src/Template.ts
CHANGED
|
@@ -32,6 +32,7 @@ export type Node =
|
|
|
32
32
|
| TextNode
|
|
33
33
|
| NodePart
|
|
34
34
|
| Comment
|
|
35
|
+
| DocType
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* @since 1.0.0
|
|
@@ -44,6 +45,7 @@ export type PartNode =
|
|
|
44
45
|
| EventPartNode
|
|
45
46
|
| NodePart
|
|
46
47
|
| PropertyPartNode
|
|
48
|
+
| PropertiesPartNode
|
|
47
49
|
| RefPartNode
|
|
48
50
|
| TextPartNode
|
|
49
51
|
| CommentPartNode
|
|
@@ -96,6 +98,18 @@ export class TextOnlyElement {
|
|
|
96
98
|
) {}
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
/**
|
|
102
|
+
* @since 1.0.0
|
|
103
|
+
*/
|
|
104
|
+
export class DocType {
|
|
105
|
+
readonly _tag = "doctype"
|
|
106
|
+
constructor(
|
|
107
|
+
readonly name: string,
|
|
108
|
+
readonly publicType?: string,
|
|
109
|
+
readonly systemId?: string
|
|
110
|
+
) {}
|
|
111
|
+
}
|
|
112
|
+
|
|
99
113
|
/**
|
|
100
114
|
* @since 1.0.0
|
|
101
115
|
*/
|
|
@@ -110,6 +124,7 @@ export type Attribute =
|
|
|
110
124
|
| DataPartNode
|
|
111
125
|
| EventPartNode
|
|
112
126
|
| PropertyPartNode
|
|
127
|
+
| PropertiesPartNode
|
|
113
128
|
| RefPartNode
|
|
114
129
|
|
|
115
130
|
/**
|
|
@@ -219,6 +234,17 @@ export class PropertyPartNode {
|
|
|
219
234
|
) {}
|
|
220
235
|
}
|
|
221
236
|
|
|
237
|
+
/**
|
|
238
|
+
* @since 1.0.0
|
|
239
|
+
*/
|
|
240
|
+
export class PropertiesPartNode {
|
|
241
|
+
readonly _tag = "properties" as const
|
|
242
|
+
|
|
243
|
+
constructor(
|
|
244
|
+
readonly index: number
|
|
245
|
+
) {}
|
|
246
|
+
}
|
|
247
|
+
|
|
222
248
|
/**
|
|
223
249
|
* @since 1.0.0
|
|
224
250
|
*/
|
package/src/TemplateInstance.ts
CHANGED
|
@@ -8,14 +8,14 @@ import type * as Versioned from "@typed/fx/Versioned"
|
|
|
8
8
|
import type { Rendered } from "@typed/wire"
|
|
9
9
|
import type { NoSuchElementException } from "effect/Cause"
|
|
10
10
|
import type * as Effect from "effect/Effect"
|
|
11
|
+
import type { Scope } from "effect/Scope"
|
|
11
12
|
import { type ElementRef, ElementRefTypeId } from "./ElementRef.js"
|
|
12
|
-
import type { Placeholder } from "./Placeholder.js"
|
|
13
13
|
import type { RenderEvent } from "./RenderEvent.js"
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @since 1.0.0
|
|
17
17
|
*/
|
|
18
|
-
export const TemplateInstanceTypeId = Symbol.for("
|
|
18
|
+
export const TemplateInstanceTypeId = Symbol.for("@typed/template/TemplateInstance")
|
|
19
19
|
/**
|
|
20
20
|
* @since 1.0.0
|
|
21
21
|
*/
|
|
@@ -25,7 +25,7 @@ export type TemplateInstanceTypeId = typeof TemplateInstanceTypeId
|
|
|
25
25
|
* @since 1.0.0
|
|
26
26
|
*/
|
|
27
27
|
export interface TemplateInstance<E, T extends Rendered = Rendered>
|
|
28
|
-
extends Versioned.Versioned<never, never,
|
|
28
|
+
extends Versioned.Versioned<never, never, Scope, E, RenderEvent, never, E | NoSuchElementException, T>
|
|
29
29
|
{
|
|
30
30
|
readonly [TemplateInstanceTypeId]: TemplateInstanceTypeId
|
|
31
31
|
|
|
@@ -42,16 +42,16 @@ export interface TemplateInstance<E, T extends Rendered = Rendered>
|
|
|
42
42
|
* @since 1.0.0
|
|
43
43
|
*/
|
|
44
44
|
export function TemplateInstance<T extends Rendered = Rendered, E = never>(
|
|
45
|
-
events: Fx.Fx<
|
|
45
|
+
events: Fx.Fx<Scope, E, RenderEvent>,
|
|
46
46
|
ref: ElementRef<T>
|
|
47
47
|
): TemplateInstance<E, T> {
|
|
48
48
|
return new TemplateInstanceImpl(events, ref) as any
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// @ts-expect-error
|
|
51
|
+
// @ts-expect-error does not implement Placeholder
|
|
52
52
|
class TemplateInstanceImpl<E, T extends Rendered>
|
|
53
|
-
extends FxEffectBase<
|
|
54
|
-
implements
|
|
53
|
+
extends FxEffectBase<Scope, E, RenderEvent, never, E | NoSuchElementException, T>
|
|
54
|
+
implements TemplateInstance<E, T>
|
|
55
55
|
{
|
|
56
56
|
readonly [TemplateInstanceTypeId]: TemplateInstanceTypeId = TemplateInstanceTypeId
|
|
57
57
|
query: TemplateInstance<E, T>["query"]
|
|
@@ -61,7 +61,7 @@ class TemplateInstanceImpl<E, T extends Rendered>
|
|
|
61
61
|
version: Effect.Effect<never, never, number>
|
|
62
62
|
|
|
63
63
|
constructor(
|
|
64
|
-
readonly i0: Fx.Fx<
|
|
64
|
+
readonly i0: Fx.Fx<Scope, E, RenderEvent>,
|
|
65
65
|
readonly i1: ElementRef<T>
|
|
66
66
|
) {
|
|
67
67
|
super()
|
|
@@ -73,7 +73,7 @@ class TemplateInstanceImpl<E, T extends Rendered>
|
|
|
73
73
|
this.version = this.i1[ElementRefTypeId].version
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
toFx(): Fx.Fx<
|
|
76
|
+
toFx(): Fx.Fx<Scope, E, RenderEvent> {
|
|
77
77
|
return this.i0
|
|
78
78
|
}
|
|
79
79
|
|
package/src/Test.ts
CHANGED
|
@@ -6,17 +6,15 @@ import type { DomServices, DomServicesElementParams } from "@typed/dom/DomServic
|
|
|
6
6
|
import type { GlobalThis } from "@typed/dom/GlobalThis"
|
|
7
7
|
import type { Window } from "@typed/dom/Window"
|
|
8
8
|
import type { CurrentEnvironment } from "@typed/environment"
|
|
9
|
-
import type { Computed } from "@typed/fx/Computed"
|
|
10
|
-
import type { Filtered } from "@typed/fx/Filtered"
|
|
11
9
|
import * as Fx from "@typed/fx/Fx"
|
|
12
10
|
import * as RefArray from "@typed/fx/RefArray"
|
|
11
|
+
import * as RefSubject from "@typed/fx/RefSubject"
|
|
13
12
|
import * as Sink from "@typed/fx/Sink"
|
|
14
13
|
import * as Cause from "effect/Cause"
|
|
15
14
|
import * as Effect from "effect/Effect"
|
|
16
15
|
import * as Either from "effect/Either"
|
|
17
16
|
import * as Fiber from "effect/Fiber"
|
|
18
17
|
import type * as Scope from "effect/Scope"
|
|
19
|
-
import * as happyDOM from "happy-dom"
|
|
20
18
|
import type IHappyDOMOptions from "happy-dom/lib/window/IHappyDOMOptions.js"
|
|
21
19
|
import * as ElementRef from "./ElementRef.js"
|
|
22
20
|
import { ROOT_CSS_SELECTOR } from "./ElementSource.js"
|
|
@@ -36,11 +34,11 @@ import type { RenderTemplate } from "./RenderTemplate.js"
|
|
|
36
34
|
* @since 1.0.0
|
|
37
35
|
*/
|
|
38
36
|
export interface TestRender<E> {
|
|
39
|
-
readonly window: Window & GlobalThis
|
|
37
|
+
readonly window: Window & GlobalThis
|
|
40
38
|
readonly document: Document
|
|
41
39
|
readonly elementRef: ElementRef.ElementRef
|
|
42
|
-
readonly errors: Computed<never, never, ReadonlyArray<E>>
|
|
43
|
-
readonly lastError: Filtered<never, never, E>
|
|
40
|
+
readonly errors: RefSubject.Computed<never, never, ReadonlyArray<E>>
|
|
41
|
+
readonly lastError: RefSubject.Filtered<never, never, E>
|
|
44
42
|
readonly interrupt: Effect.Effect<never, never, void>
|
|
45
43
|
readonly makeEvent: (type: string, eventInitDict?: EventInit) => Event
|
|
46
44
|
readonly makeCustomEvent: <A>(type: string, eventInitDict?: CustomEventInit<A>) => CustomEvent<A>
|
|
@@ -62,24 +60,25 @@ export function testRender<R, E>(
|
|
|
62
60
|
TestRender<E>
|
|
63
61
|
> {
|
|
64
62
|
return Effect.gen(function*(_) {
|
|
65
|
-
const window =
|
|
63
|
+
const window = yield* _(getOrMakeWindow(options))
|
|
66
64
|
const elementRef = yield* _(ElementRef.make())
|
|
67
|
-
const errors = yield* _(
|
|
65
|
+
const errors = yield* _(RefSubject.make<never, never, ReadonlyArray<E>>(Effect.succeed([])))
|
|
68
66
|
const fiber = yield* _(
|
|
69
67
|
fx,
|
|
70
68
|
render,
|
|
71
|
-
|
|
72
|
-
(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
69
|
+
(x) =>
|
|
70
|
+
x.run(Sink.make(
|
|
71
|
+
(cause) =>
|
|
72
|
+
Cause.failureOrCause(cause).pipe(
|
|
73
|
+
Either.match({
|
|
74
|
+
onLeft: (error) => RefArray.append(errors, error),
|
|
75
|
+
onRight: (cause) => errors.onFailure(cause)
|
|
76
|
+
})
|
|
77
|
+
),
|
|
78
|
+
(rendered) => ElementRef.set(elementRef, rendered)
|
|
79
|
+
)),
|
|
81
80
|
Effect.forkScoped,
|
|
82
|
-
Effect.provide(RenderContext.
|
|
81
|
+
Effect.provide(RenderContext.dom(window, { skipRenderScheduling: true }))
|
|
83
82
|
)
|
|
84
83
|
|
|
85
84
|
const test: TestRender<E> = {
|
|
@@ -99,6 +98,9 @@ export function testRender<R, E>(
|
|
|
99
98
|
yield* _(adjustTime(1))
|
|
100
99
|
yield* _(adjustTime(1))
|
|
101
100
|
|
|
101
|
+
// Await the first render
|
|
102
|
+
yield* _(Fx.first(elementRef), Effect.race(Effect.delay(Effect.dieMessage(`Rendering taking too long`), 1000)))
|
|
103
|
+
|
|
102
104
|
return test
|
|
103
105
|
})
|
|
104
106
|
}
|
|
@@ -146,6 +148,23 @@ export function click<E>(
|
|
|
146
148
|
|
|
147
149
|
// internals
|
|
148
150
|
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
+
function getOrMakeWindow(options?: IHappyDOMOptions) {
|
|
152
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
153
|
+
return Effect.gen(function*(_) {
|
|
154
|
+
window.document.head.innerHTML = ""
|
|
155
|
+
window.document.body.innerHTML = ""
|
|
156
|
+
yield* _(Effect.addFinalizer(() =>
|
|
157
|
+
Effect.sync(() => {
|
|
158
|
+
window.document.head.innerHTML = ""
|
|
159
|
+
window.document.body.innerHTML = ""
|
|
160
|
+
})
|
|
161
|
+
))
|
|
162
|
+
|
|
163
|
+
return window
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return Effect.promise(() =>
|
|
168
|
+
import("happy-dom").then((happyDOM) => new happyDOM.Window(options) as any as Window & GlobalThis)
|
|
169
|
+
)
|
|
151
170
|
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { Rendered } from "@typed/wire"
|
|
2
|
+
import { Effect, Scope } from "effect"
|
|
3
|
+
import type * as Fiber from "effect/Fiber"
|
|
4
|
+
import * as Runtime from "effect/Runtime"
|
|
5
|
+
import { getElements } from "../ElementSource"
|
|
6
|
+
import type { EventHandler } from "../EventHandler"
|
|
7
|
+
|
|
8
|
+
type EventName = string
|
|
9
|
+
|
|
10
|
+
type Handler<Ev extends Event> = EventHandler<never, never, Ev>
|
|
11
|
+
|
|
12
|
+
export interface EventSource {
|
|
13
|
+
readonly addEventListener: <Ev extends Event>(
|
|
14
|
+
element: Element,
|
|
15
|
+
event: EventName,
|
|
16
|
+
handler: Handler<Ev>
|
|
17
|
+
) => void
|
|
18
|
+
|
|
19
|
+
readonly setup: (rendered: Rendered, scope: Scope.Scope) => Effect.Effect<never, never, void>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Entry = readonly [Element, Handler<any>]
|
|
23
|
+
type Run = <E, A>(effect: Effect.Effect<never, E, A>) => Fiber.RuntimeFiber<E, A>
|
|
24
|
+
|
|
25
|
+
const disposable = (f: () => void): Disposable => ({
|
|
26
|
+
[Symbol.dispose]: f
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export function makeEventSource(): EventSource {
|
|
30
|
+
const bubbleListeners = new Map<
|
|
31
|
+
EventName,
|
|
32
|
+
Set<Entry>
|
|
33
|
+
>()
|
|
34
|
+
const captureListeners = new Map<
|
|
35
|
+
EventName,
|
|
36
|
+
Set<Entry>
|
|
37
|
+
>()
|
|
38
|
+
|
|
39
|
+
function addListener(
|
|
40
|
+
listeners: Map<
|
|
41
|
+
EventName,
|
|
42
|
+
Set<Entry>
|
|
43
|
+
>,
|
|
44
|
+
event: EventName,
|
|
45
|
+
entry: Entry
|
|
46
|
+
): void {
|
|
47
|
+
const set = listeners.get(event)
|
|
48
|
+
if (set === undefined) {
|
|
49
|
+
const set = new Set<Entry>()
|
|
50
|
+
set.add(entry)
|
|
51
|
+
listeners.set(event, set)
|
|
52
|
+
} else {
|
|
53
|
+
set.add(entry)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function addEventListener<Ev extends Event>(
|
|
58
|
+
element: Element,
|
|
59
|
+
event: EventName,
|
|
60
|
+
handler: Handler<Ev>
|
|
61
|
+
): void {
|
|
62
|
+
if (handler.options?.capture === true) {
|
|
63
|
+
return addListener(captureListeners, event, [element, handler])
|
|
64
|
+
} else {
|
|
65
|
+
return addListener(bubbleListeners, event, [element, handler])
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function setupBubbleListeners(element: Element, run: Run) {
|
|
70
|
+
const disposables: Array<Disposable> = []
|
|
71
|
+
|
|
72
|
+
for (const [event, handlers] of bubbleListeners) {
|
|
73
|
+
const listener = (ev: Event) =>
|
|
74
|
+
run(
|
|
75
|
+
Effect.forEach(handlers, ([el, handler]) =>
|
|
76
|
+
ev.target === el || el.contains(ev.target as Node) ? handler.handler(ev) : Effect.unit)
|
|
77
|
+
)
|
|
78
|
+
element.addEventListener(event, listener, getDerivedAddEventListenerOptions(handlers))
|
|
79
|
+
disposables.push(disposable(() => element.removeEventListener(event, listener)))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return disposables
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function setupCaptureListeners(element: Element, run: Run) {
|
|
86
|
+
const disposables: Array<Disposable> = []
|
|
87
|
+
|
|
88
|
+
for (const [event, handlers] of captureListeners) {
|
|
89
|
+
const listener = (ev: Event) =>
|
|
90
|
+
run(
|
|
91
|
+
Effect.forEach(handlers, ([el, handler]) =>
|
|
92
|
+
ev.target === el || el.contains(ev.target as Node)
|
|
93
|
+
? handler.handler(proxyCurrentTargetForCaptureEvents(ev, el))
|
|
94
|
+
: Effect.unit)
|
|
95
|
+
)
|
|
96
|
+
element.addEventListener(event, listener, getDerivedAddEventListenerOptions(handlers))
|
|
97
|
+
disposables.push(disposable(() => element.removeEventListener(event, listener)))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return disposables
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function setup(rendered: Rendered, scope: Scope.Scope) {
|
|
104
|
+
const hasBubbleListeners = bubbleListeners.size > 0
|
|
105
|
+
const hasCaptureListeners = captureListeners.size > 0
|
|
106
|
+
|
|
107
|
+
if (!hasBubbleListeners && !hasCaptureListeners) {
|
|
108
|
+
return Effect.unit
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Effect.flatMap(Effect.runtime<never>(), (runtime) => {
|
|
112
|
+
const elements = getElements(rendered)
|
|
113
|
+
const disposables: Array<Disposable> = []
|
|
114
|
+
const runFork = Runtime.runFork(runtime)
|
|
115
|
+
const run: Run = <E, A>(effect: Effect.Effect<never, E, A>) =>
|
|
116
|
+
runFork(Effect.fromFiberEffect(Effect.forkIn(effect, scope)))
|
|
117
|
+
|
|
118
|
+
for (const element of elements) {
|
|
119
|
+
if (hasBubbleListeners) {
|
|
120
|
+
disposables.push(...setupBubbleListeners(element, run))
|
|
121
|
+
}
|
|
122
|
+
if (hasCaptureListeners) {
|
|
123
|
+
disposables.push(...setupCaptureListeners(element, run))
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return Scope.addFinalizer(scope, Effect.sync(() => disposables.forEach((d) => d[Symbol.dispose]())))
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
addEventListener,
|
|
133
|
+
setup
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const EVENT_PROPERTY_TO_REPLACE = "currentTarget"
|
|
138
|
+
|
|
139
|
+
function proxyCurrentTargetForCaptureEvents<E extends Event>(event: E, currentTarget: Element): E {
|
|
140
|
+
return new Proxy(event, {
|
|
141
|
+
get(target: E, property: string | symbol) {
|
|
142
|
+
return property === EVENT_PROPERTY_TO_REPLACE ? currentTarget : target[property as keyof E]
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getDerivedAddEventListenerOptions(entries: Set<Entry>): AddEventListenerOptions {
|
|
148
|
+
const hs = Array.from(entries)
|
|
149
|
+
return {
|
|
150
|
+
once: hs.some((h) => h[1].options?.once === true),
|
|
151
|
+
passive: hs.every((h) => h[1].options?.passive === true)
|
|
152
|
+
}
|
|
153
|
+
}
|