@typed/template 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Test.js.map +1 -1
- package/dist/cjs/internal/hydrate.js +9 -4
- package/dist/cjs/internal/hydrate.js.map +1 -1
- package/dist/cjs/internal/parser.js +19 -5
- package/dist/cjs/internal/parser.js.map +1 -1
- package/dist/cjs/internal/render.js +66 -34
- package/dist/cjs/internal/render.js.map +1 -1
- package/dist/dts/Test.d.ts +2 -2
- package/dist/dts/Test.d.ts.map +1 -1
- package/dist/dts/internal/hydrate.d.ts.map +1 -1
- package/dist/dts/internal/parser.d.ts.map +1 -1
- package/dist/dts/internal/render.d.ts.map +1 -1
- package/dist/esm/Test.js.map +1 -1
- package/dist/esm/internal/hydrate.js +10 -5
- package/dist/esm/internal/hydrate.js.map +1 -1
- package/dist/esm/internal/parser.js +23 -5
- package/dist/esm/internal/parser.js.map +1 -1
- package/dist/esm/internal/render.js +69 -37
- package/dist/esm/internal/render.js.map +1 -1
- package/package.json +9 -9
- package/src/Test.ts +7 -3
- package/src/internal/hydrate.ts +17 -11
- package/src/internal/parser.ts +26 -6
- package/src/internal/render.ts +102 -56
package/src/internal/render.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as Fx from "@typed/fx/Fx"
|
|
2
|
+
import * as Sink from "@typed/fx/Sink"
|
|
2
3
|
import { TypeId } from "@typed/fx/TypeId"
|
|
3
4
|
import type { Rendered } from "@typed/wire"
|
|
4
5
|
import { persistent } from "@typed/wire"
|
|
5
|
-
import { Effect } from "effect"
|
|
6
|
+
import { Effect, ExecutionStrategy, Exit } from "effect"
|
|
6
7
|
import type { Cause } from "effect/Cause"
|
|
7
8
|
import type { Chunk } from "effect/Chunk"
|
|
8
9
|
import * as Context from "effect/Context"
|
|
9
|
-
import
|
|
10
|
+
import * as Scope from "effect/Scope"
|
|
11
|
+
import { uncapitalize } from "effect/String"
|
|
10
12
|
import type { Directive } from "../Directive.js"
|
|
11
13
|
import { isDirective } from "../Directive.js"
|
|
12
14
|
import * as ElementRef from "../ElementRef.js"
|
|
@@ -49,7 +51,7 @@ import { findHoleComment, findPath } from "./utils.js"
|
|
|
49
51
|
* @internal
|
|
50
52
|
*/
|
|
51
53
|
export type RenderPartContext = {
|
|
52
|
-
readonly context: Context.Context<Scope>
|
|
54
|
+
readonly context: Context.Context<Scope.Scope>
|
|
53
55
|
readonly document: Document
|
|
54
56
|
readonly eventSource: EventSource
|
|
55
57
|
readonly refCounter: IndexRefCounter2
|
|
@@ -60,6 +62,7 @@ export type RenderPartContext = {
|
|
|
60
62
|
readonly makeHydrateContext?: (index: number) => HydrateContext
|
|
61
63
|
|
|
62
64
|
expected: number
|
|
65
|
+
spreadIndex: number
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
type RenderPartMap = {
|
|
@@ -101,15 +104,16 @@ const RenderPartMap: RenderPartMap = {
|
|
|
101
104
|
"boolean-part": (templatePart, node, ctx) => {
|
|
102
105
|
const { refCounter, renderContext, values } = ctx
|
|
103
106
|
const element = node as HTMLElement | SVGElement
|
|
107
|
+
const name = templatePart.name
|
|
104
108
|
const renderable = values[templatePart.index]
|
|
105
109
|
const setValue = (value: boolean | null | undefined) => {
|
|
106
|
-
element.toggleAttribute(
|
|
110
|
+
element.toggleAttribute(name, isNullOrUndefined(value) ? false : Boolean(value))
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
return matchSettablePart(
|
|
110
114
|
renderable,
|
|
111
115
|
setValue,
|
|
112
|
-
() => BooleanPartImpl.browser(templatePart.index, element,
|
|
116
|
+
() => BooleanPartImpl.browser(templatePart.index, element, name, renderContext),
|
|
113
117
|
(f) => Effect.zipRight(renderContext.queue.add(element, f), refCounter.release(templatePart.index)),
|
|
114
118
|
() => ctx.expected++
|
|
115
119
|
)
|
|
@@ -124,14 +128,13 @@ const RenderPartMap: RenderPartMap = {
|
|
|
124
128
|
element.classList.remove(...classNames)
|
|
125
129
|
classNames.clear()
|
|
126
130
|
} else {
|
|
127
|
-
const newClassNames = new Set(
|
|
131
|
+
const newClassNames = new Set(
|
|
132
|
+
Array.isArray(value) ? value.flatMap((x) => splitClassNames(String(x))) : splitClassNames(String(value))
|
|
133
|
+
)
|
|
128
134
|
const { added, removed } = diffClassNames(classNames, newClassNames)
|
|
129
135
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
if (added.length > 0) element.classList.add(...added)
|
|
134
|
-
|
|
136
|
+
element.classList.remove(...removed)
|
|
137
|
+
element.classList.add(...added)
|
|
135
138
|
classNames = newClassNames
|
|
136
139
|
}
|
|
137
140
|
}
|
|
@@ -219,7 +222,7 @@ const RenderPartMap: RenderPartMap = {
|
|
|
219
222
|
|
|
220
223
|
const handle = handlePart(
|
|
221
224
|
ctx.values[templatePart.index],
|
|
222
|
-
(value) => Effect.zipRight(part.update(value
|
|
225
|
+
Sink.make(ctx.onCause, (value) => Effect.zipRight(part.update(value), ctx.refCounter.release(templatePart.index)))
|
|
223
226
|
)
|
|
224
227
|
|
|
225
228
|
if (makeHydrateContext) {
|
|
@@ -273,18 +276,21 @@ const RenderPartMap: RenderPartMap = {
|
|
|
273
276
|
;(element as any)[key] = value
|
|
274
277
|
}
|
|
275
278
|
}
|
|
279
|
+
const setClassNames = (previous: Set<string>, updated: Set<string>) => {
|
|
280
|
+
const { added, removed } = diffClassNames(previous, updated)
|
|
276
281
|
|
|
277
|
-
|
|
282
|
+
element.classList.remove(...removed)
|
|
283
|
+
element.classList.add(...added)
|
|
284
|
+
removed.forEach((r) => previous.delete(r))
|
|
285
|
+
added.forEach((a) => previous.add(a))
|
|
286
|
+
}
|
|
278
287
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// As there should only ever be exactly 1 properties part.
|
|
282
|
-
let i = ctx.values.length
|
|
288
|
+
const effects: Array<Effect.Effect<any, any, void>> = []
|
|
289
|
+
const entries = Object.entries(renderable)
|
|
283
290
|
|
|
284
291
|
loop:
|
|
285
|
-
for (const [key, value] of
|
|
286
|
-
const index = ++
|
|
287
|
-
|
|
292
|
+
for (const [key, value] of entries) {
|
|
293
|
+
const index = ++ctx.spreadIndex
|
|
288
294
|
switch (key[0]) {
|
|
289
295
|
case "?": {
|
|
290
296
|
const name = key.slice(1)
|
|
@@ -315,7 +321,7 @@ const RenderPartMap: RenderPartMap = {
|
|
|
315
321
|
continue loop
|
|
316
322
|
}
|
|
317
323
|
case "@": {
|
|
318
|
-
const name = key.slice(1)
|
|
324
|
+
const name = uncapitalize(key.slice(1))
|
|
319
325
|
const handler = getEventHandler(value, ctx.context, ctx.onCause)
|
|
320
326
|
if (handler) {
|
|
321
327
|
ctx.eventSource.addEventListener(element, name, handler)
|
|
@@ -324,7 +330,7 @@ const RenderPartMap: RenderPartMap = {
|
|
|
324
330
|
}
|
|
325
331
|
case "o": {
|
|
326
332
|
if (key[1] === "n") {
|
|
327
|
-
const name = key.slice(2)
|
|
333
|
+
const name = uncapitalize(key.slice(2))
|
|
328
334
|
const handler = getEventHandler(value, ctx.context, ctx.onCause)
|
|
329
335
|
if (handler) {
|
|
330
336
|
ctx.eventSource.addEventListener(element, name, handler)
|
|
@@ -334,15 +340,40 @@ const RenderPartMap: RenderPartMap = {
|
|
|
334
340
|
}
|
|
335
341
|
}
|
|
336
342
|
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
()
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
343
|
+
const lowerCaseName = key.toLowerCase()
|
|
344
|
+
|
|
345
|
+
const isClass = lowerCaseName === "class" || lowerCaseName === "classname"
|
|
346
|
+
|
|
347
|
+
if (isClass) {
|
|
348
|
+
const classNames: Set<string> = new Set()
|
|
349
|
+
const eff = matchSettablePart(
|
|
350
|
+
value,
|
|
351
|
+
(value) => {
|
|
352
|
+
if (isNullOrUndefined(value)) {
|
|
353
|
+
element.classList.remove(...classNames)
|
|
354
|
+
classNames.clear()
|
|
355
|
+
} else {
|
|
356
|
+
setClassNames(classNames, new Set(splitClassNames(String(value))))
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
() => ClassNamePartImpl.browser(index, element, ctx.renderContext),
|
|
360
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
|
|
361
|
+
() => ctx.expected++
|
|
362
|
+
)
|
|
363
|
+
if (eff !== null) {
|
|
364
|
+
effects.push(eff)
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
const eff = matchSettablePart(
|
|
368
|
+
value,
|
|
369
|
+
(value) => setAttribute(key, value),
|
|
370
|
+
() => AttributePartImpl.browser(index, element, key, ctx.renderContext),
|
|
371
|
+
(f) => Effect.zipRight(ctx.renderContext.queue.add(element, f), ctx.refCounter.release(index)),
|
|
372
|
+
() => ctx.expected++
|
|
373
|
+
)
|
|
374
|
+
if (eff !== null) {
|
|
375
|
+
effects.push(eff)
|
|
376
|
+
}
|
|
346
377
|
}
|
|
347
378
|
}
|
|
348
379
|
|
|
@@ -474,7 +505,10 @@ const RenderPartMap: RenderPartMap = {
|
|
|
474
505
|
|
|
475
506
|
return handlePart(
|
|
476
507
|
ctx.values[templatePart.index],
|
|
477
|
-
|
|
508
|
+
Sink.make(
|
|
509
|
+
ctx.onCause,
|
|
510
|
+
(value) => Effect.zipRight(part.update(value as any), ctx.refCounter.release(templatePart.index))
|
|
511
|
+
)
|
|
478
512
|
)
|
|
479
513
|
}
|
|
480
514
|
}
|
|
@@ -538,13 +572,15 @@ export const renderTemplate: (document: Document, renderContext: RenderContext)
|
|
|
538
572
|
return Fx.sync(() => DomRenderEvent(persistent(document.importNode(entry.content, true))))
|
|
539
573
|
}
|
|
540
574
|
|
|
541
|
-
return Fx.make<Scope | Placeholder.Context<Values[number]>, Placeholder.Error<Values[number]>, RenderEvent>((
|
|
575
|
+
return Fx.make<Scope.Scope | Placeholder.Context<Values[number]>, Placeholder.Error<Values[number]>, RenderEvent>((
|
|
542
576
|
sink
|
|
543
577
|
) => {
|
|
544
578
|
return Effect.gen(function*(_) {
|
|
545
|
-
const
|
|
546
|
-
const
|
|
579
|
+
const context = yield* _(Effect.context<Scope.Scope | Placeholder.Context<Values[number]>>())
|
|
580
|
+
const parentScope = Context.get(context, Scope.Scope)
|
|
581
|
+
const scope = yield* _(Scope.fork(parentScope, ExecutionStrategy.sequential))
|
|
547
582
|
const refCounter = yield* _(indexRefCounter2())
|
|
583
|
+
const content = document.importNode(entry.content, true)
|
|
548
584
|
const ctx: RenderPartContext = {
|
|
549
585
|
context,
|
|
550
586
|
document,
|
|
@@ -553,17 +589,18 @@ export const renderTemplate: (document: Document, renderContext: RenderContext)
|
|
|
553
589
|
refCounter,
|
|
554
590
|
renderContext,
|
|
555
591
|
onCause: sink.onFailure as any,
|
|
592
|
+
spreadIndex: values.length,
|
|
556
593
|
values
|
|
557
594
|
}
|
|
558
595
|
|
|
559
596
|
// Connect our interpolated values to our template parts
|
|
560
|
-
const effects: Array<Effect.Effect<Scope | Placeholder.Context<Values[number]>, never, void>> = []
|
|
597
|
+
const effects: Array<Effect.Effect<Scope.Scope | Placeholder.Context<Values[number]>, never, void>> = []
|
|
561
598
|
for (const [part, path] of entry.template.parts) {
|
|
562
599
|
const eff = renderPart2(part, content, path, ctx)
|
|
563
600
|
if (eff !== null) {
|
|
564
601
|
effects.push(
|
|
565
602
|
...(Array.isArray(eff) ? eff : [eff]) as Array<
|
|
566
|
-
Effect.Effect<Scope | Placeholder.Context<Values[number]>, never, void>
|
|
603
|
+
Effect.Effect<Scope.Scope | Placeholder.Context<Values[number]>, never, void>
|
|
567
604
|
>
|
|
568
605
|
)
|
|
569
606
|
}
|
|
@@ -571,7 +608,11 @@ export const renderTemplate: (document: Document, renderContext: RenderContext)
|
|
|
571
608
|
|
|
572
609
|
// Fork any effects necessary
|
|
573
610
|
if (effects.length > 0) {
|
|
574
|
-
|
|
611
|
+
for (const eff of effects) {
|
|
612
|
+
yield* _(
|
|
613
|
+
Effect.forkIn(Effect.catchAllCause(eff, ctx.onCause), scope)
|
|
614
|
+
)
|
|
615
|
+
}
|
|
575
616
|
}
|
|
576
617
|
|
|
577
618
|
// If there's anything to wait on and it's not already done, wait for an initial value
|
|
@@ -584,14 +625,17 @@ export const renderTemplate: (document: Document, renderContext: RenderContext)
|
|
|
584
625
|
const wire = persistent(content) as T
|
|
585
626
|
|
|
586
627
|
// Set the element when it is ready
|
|
587
|
-
yield* _(ctx.eventSource.setup(wire,
|
|
628
|
+
yield* _(ctx.eventSource.setup(wire, scope))
|
|
588
629
|
|
|
589
630
|
// Emit our DomRenderEvent
|
|
590
|
-
yield* _(
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
631
|
+
yield* _(
|
|
632
|
+
sink.onSuccess(DomRenderEvent(wire)),
|
|
633
|
+
// Ensure our templates last forever in the DOM environment
|
|
634
|
+
// so event listeners are kept attached to the current Scope.
|
|
635
|
+
Effect.zipRight(Effect.never),
|
|
636
|
+
// Close our scope whenever the current Fiber is interrupted
|
|
637
|
+
Effect.ensuring(Scope.close(scope, Exit.unit))
|
|
638
|
+
)
|
|
595
639
|
})
|
|
596
640
|
})
|
|
597
641
|
}
|
|
@@ -619,26 +663,26 @@ function getEventHandler<R, E>(
|
|
|
619
663
|
return null
|
|
620
664
|
}
|
|
621
665
|
|
|
622
|
-
function handlePart<R, E>(
|
|
666
|
+
function handlePart<R, E, R2>(
|
|
623
667
|
renderable: unknown,
|
|
624
|
-
|
|
625
|
-
): Effect.Effect<R | Scope,
|
|
668
|
+
sink: Sink.Sink<R2, any, any>
|
|
669
|
+
): Effect.Effect<R | R2 | Scope.Scope, never, any> {
|
|
626
670
|
switch (typeof renderable) {
|
|
627
671
|
case "undefined":
|
|
628
672
|
case "object": {
|
|
629
|
-
if (renderable === null || renderable === undefined) return
|
|
673
|
+
if (renderable === null || renderable === undefined) return sink.onSuccess(null)
|
|
630
674
|
else if (Array.isArray(renderable)) {
|
|
631
675
|
return renderable.length === 0
|
|
632
|
-
?
|
|
633
|
-
:
|
|
676
|
+
? sink.onSuccess(null)
|
|
677
|
+
: Fx.tuple(renderable.map(unwrapRenderable)).run(sink) as any
|
|
634
678
|
} else if (TypeId in renderable) {
|
|
635
|
-
return
|
|
679
|
+
return (renderable as Fx.Fx<R | R2, any, any>).run(sink)
|
|
636
680
|
} else if (Effect.EffectTypeId in renderable) {
|
|
637
|
-
return Effect.
|
|
638
|
-
} else return
|
|
681
|
+
return Effect.matchCauseEffect(renderable as Effect.Effect<R, E, any>, sink)
|
|
682
|
+
} else return sink.onSuccess(renderable)
|
|
639
683
|
}
|
|
640
684
|
default:
|
|
641
|
-
return
|
|
685
|
+
return sink.onSuccess(renderable)
|
|
642
686
|
}
|
|
643
687
|
}
|
|
644
688
|
|
|
@@ -648,7 +692,9 @@ function unwrapRenderable<R, E>(renderable: unknown): Fx.Fx<R, E, any> {
|
|
|
648
692
|
case "object": {
|
|
649
693
|
if (renderable === null || renderable === undefined) return Fx.succeed(null)
|
|
650
694
|
else if (Array.isArray(renderable)) {
|
|
651
|
-
return renderable.length === 0
|
|
695
|
+
return renderable.length === 0
|
|
696
|
+
? Fx.succeed(null)
|
|
697
|
+
: Fx.map(Fx.tuple(renderable.map(unwrapRenderable)), (xs) => xs.flat()) as any
|
|
652
698
|
} else if (TypeId in renderable) {
|
|
653
699
|
return renderable as any
|
|
654
700
|
} else if (Effect.EffectTypeId in renderable) {
|
|
@@ -837,7 +883,7 @@ function matchSettablePart(
|
|
|
837
883
|
renderable: Renderable<any, any>,
|
|
838
884
|
setValue: (value: any) => void,
|
|
839
885
|
makePart: () => Part,
|
|
840
|
-
schedule: (f: () => void) => Effect.Effect<Scope, never, void>,
|
|
886
|
+
schedule: (f: () => void) => Effect.Effect<Scope.Scope, never, void>,
|
|
841
887
|
expect: () => void
|
|
842
888
|
) {
|
|
843
889
|
return matchRenderable(renderable, {
|