@typed/template 0.3.0 → 0.3.2

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.
Files changed (41) hide show
  1. package/dist/cjs/internal/EventSource.js +46 -19
  2. package/dist/cjs/internal/EventSource.js.map +1 -1
  3. package/dist/cjs/internal/chunks.js +15 -1
  4. package/dist/cjs/internal/chunks.js.map +1 -1
  5. package/dist/cjs/internal/errors.js +4 -0
  6. package/dist/cjs/internal/errors.js.map +1 -1
  7. package/dist/cjs/internal/hydrate.js +92 -58
  8. package/dist/cjs/internal/hydrate.js.map +1 -1
  9. package/dist/cjs/internal/parser.js +14 -6
  10. package/dist/cjs/internal/parser.js.map +1 -1
  11. package/dist/cjs/internal/render.js +17 -151
  12. package/dist/cjs/internal/render.js.map +1 -1
  13. package/dist/dts/internal/EventSource.d.ts.map +1 -1
  14. package/dist/dts/internal/chunks.d.ts +1 -0
  15. package/dist/dts/internal/chunks.d.ts.map +1 -1
  16. package/dist/dts/internal/errors.d.ts +1 -0
  17. package/dist/dts/internal/errors.d.ts.map +1 -1
  18. package/dist/dts/internal/hydrate.d.ts +9 -16
  19. package/dist/dts/internal/hydrate.d.ts.map +1 -1
  20. package/dist/dts/internal/parser.d.ts.map +1 -1
  21. package/dist/dts/internal/render.d.ts +0 -14
  22. package/dist/dts/internal/render.d.ts.map +1 -1
  23. package/dist/esm/internal/EventSource.js +51 -21
  24. package/dist/esm/internal/EventSource.js.map +1 -1
  25. package/dist/esm/internal/chunks.js +13 -0
  26. package/dist/esm/internal/chunks.js.map +1 -1
  27. package/dist/esm/internal/errors.js +3 -0
  28. package/dist/esm/internal/errors.js.map +1 -1
  29. package/dist/esm/internal/hydrate.js +94 -56
  30. package/dist/esm/internal/hydrate.js.map +1 -1
  31. package/dist/esm/internal/parser.js +16 -7
  32. package/dist/esm/internal/parser.js.map +1 -1
  33. package/dist/esm/internal/render.js +18 -147
  34. package/dist/esm/internal/render.js.map +1 -1
  35. package/package.json +2 -2
  36. package/src/internal/EventSource.ts +63 -34
  37. package/src/internal/chunks.ts +16 -0
  38. package/src/internal/errors.ts +4 -0
  39. package/src/internal/hydrate.ts +124 -77
  40. package/src/internal/parser.ts +17 -8
  41. package/src/internal/render.ts +30 -293
@@ -4,8 +4,8 @@ import type { Rendered } from "@typed/wire"
4
4
  import { persistent } from "@typed/wire"
5
5
  import { Effect } from "effect"
6
6
  import type { Cause } from "effect/Cause"
7
+ import type { Chunk } from "effect/Chunk"
7
8
  import * as Context from "effect/Context"
8
- import { replace } from "effect/ReadonlyArray"
9
9
  import { Scope } from "effect/Scope"
10
10
  import type { Directive } from "../Directive.js"
11
11
  import { isDirective } from "../Directive.js"
@@ -13,16 +13,7 @@ import * as ElementRef from "../ElementRef.js"
13
13
  import * as ElementSource from "../ElementSource.js"
14
14
  import type { BrowserEntry } from "../Entry.js"
15
15
  import * as EventHandler from "../EventHandler.js"
16
- import type {
17
- AttributePart,
18
- ClassNamePart,
19
- CommentPart,
20
- Part,
21
- Parts,
22
- PropertiesPart,
23
- SparsePart,
24
- StaticText
25
- } from "../Part.js"
16
+ import type { Part } from "../Part.js"
26
17
  import type { Placeholder } from "../Placeholder.js"
27
18
  import type { ToRendered } from "../Render.js"
28
19
  import type { Renderable } from "../Renderable.js"
@@ -34,7 +25,7 @@ import type * as Template from "../Template.js"
34
25
  import { makeRenderNodePart } from "./browser.js"
35
26
  import { type EventSource, makeEventSource } from "./EventSource.js"
36
27
  import { HydrateContext } from "./HydrateContext.js"
37
- import type { IndexRefCounter, IndexRefCounter2 } from "./indexRefCounter.js"
28
+ import type { IndexRefCounter2 } from "./indexRefCounter.js"
38
29
  import { indexRefCounter2 } from "./indexRefCounter.js"
39
30
  import { parse } from "./parser.js"
40
31
  import {
@@ -43,14 +34,8 @@ import {
43
34
  ClassNamePartImpl,
44
35
  CommentPartImpl,
45
36
  DataPartImpl,
46
- EventPartImpl,
47
- PropertiesPartImpl,
48
37
  PropertyPartImpl,
49
38
  RefPartImpl,
50
- SparseAttributePartImpl,
51
- SparseClassNamePartImpl,
52
- SparseCommentPartImpl,
53
- StaticTextImpl,
54
39
  TextPartImpl
55
40
  } from "./parts.js"
56
41
  import type { ParentChildNodes } from "./utils.js"
@@ -63,14 +48,16 @@ import { findHoleComment, findPath } from "./utils.js"
63
48
  /**
64
49
  * @internal
65
50
  */
66
- type RenderPartContext = {
51
+ export type RenderPartContext = {
67
52
  readonly context: Context.Context<Scope>
68
53
  readonly document: Document
69
54
  readonly eventSource: EventSource
70
55
  readonly refCounter: IndexRefCounter2
71
56
  readonly renderContext: RenderContext
72
57
  readonly values: ReadonlyArray<Renderable<any, any>>
73
- readonly onCause: (cause: Cause<unknown>) => Effect.Effect<never, never, void>
58
+ readonly onCause: (cause: Cause<any>) => Effect.Effect<never, never, void>
59
+
60
+ readonly makeHydrateContext?: (index: number) => HydrateContext
74
61
 
75
62
  expected: number
76
63
  }
@@ -219,20 +206,27 @@ const RenderPartMap: RenderPartMap = {
219
206
  return null
220
207
  },
221
208
  "node": (templatePart, node, ctx) => {
209
+ const makeHydrateContext = ctx.makeHydrateContext
222
210
  const part = makeRenderNodePart(
223
211
  templatePart.index,
224
212
  node as HTMLElement | SVGElement,
225
213
  ctx.renderContext,
226
214
  ctx.document,
227
- false
215
+ !!makeHydrateContext
228
216
  )
229
217
 
230
218
  ctx.expected++
231
219
 
232
- return handlePart(
220
+ const handle = handlePart(
233
221
  ctx.values[templatePart.index],
234
222
  (value) => Effect.zipRight(part.update(value as any), ctx.refCounter.release(templatePart.index))
235
223
  )
224
+
225
+ if (makeHydrateContext) {
226
+ return Effect.provideService(handle, HydrateContext, makeHydrateContext(templatePart.index))
227
+ } else {
228
+ return handle
229
+ }
236
230
  },
237
231
  "property": (templatePart, node, ctx) => {
238
232
  const element = node as HTMLElement | SVGElement
@@ -517,6 +511,18 @@ function diffClassNames(oldClassNames: Set<string>, newClassNames: Set<string>)
517
511
  return { added, removed }
518
512
  }
519
513
 
514
+ /**
515
+ * @internal
516
+ */
517
+ export function renderPart2(
518
+ part: Template.PartNode | Template.SparsePartNode,
519
+ content: ParentChildNodes,
520
+ path: Chunk<number>,
521
+ ctx: RenderPartContext
522
+ ): Effect.Effect<any, any, void> | Array<Effect.Effect<any, any, void>> | null {
523
+ return RenderPartMap[part._tag](part as any, findPath(content, path), ctx)
524
+ }
525
+
520
526
  /**
521
527
  * Here for "standard" browser rendering, a TemplateInstance is effectively a live
522
528
  * view into the contents rendered by the Template.
@@ -553,7 +559,7 @@ export const renderTemplate: (document: Document, renderContext: RenderContext)
553
559
  // Connect our interpolated values to our template parts
554
560
  const effects: Array<Effect.Effect<Scope | Placeholder.Context<Values[number]>, never, void>> = []
555
561
  for (const [part, path] of entry.template.parts) {
556
- const eff = RenderPartMap[part._tag](part as never, findPath(content, path), ctx)
562
+ const eff = renderPart2(part, content, path, ctx)
557
563
  if (eff !== null) {
558
564
  effects.push(
559
565
  ...(Array.isArray(eff) ? eff : [eff]) as Array<
@@ -580,7 +586,7 @@ export const renderTemplate: (document: Document, renderContext: RenderContext)
580
586
  // Set the element when it is ready
581
587
  yield* _(ctx.eventSource.setup(wire, Context.get(context, Scope)))
582
588
 
583
- // Emity our DomRenderEvent
589
+ // Emit our DomRenderEvent
584
590
  yield* _(sink.onSuccess(DomRenderEvent(wire)))
585
591
 
586
592
  // Ensure our templates last forever in the DOM environment
@@ -590,109 +596,6 @@ export const renderTemplate: (document: Document, renderContext: RenderContext)
590
596
  })
591
597
  }
592
598
 
593
- export function renderValues<Values extends ReadonlyArray<Renderable<any, any>>>(
594
- values: Values,
595
- parts: Parts,
596
- refCounter: IndexRefCounter,
597
- ctx: Context.Context<any> | Context.Context<never>,
598
- makeHydrateContext?: (index: number) => HydrateContext
599
- ): Effect.Effect<Placeholder.Context<Values[number]> | Scope, never, void> {
600
- return Effect.all(parts.map((part, index) => {
601
- switch (part._tag) {
602
- case "sparse/attribute":
603
- case "sparse/className":
604
- case "sparse/comment": {
605
- return renderSparsePart(values, part, refCounter)
606
- }
607
- default:
608
- return renderPart(
609
- values,
610
- part,
611
- refCounter,
612
- ctx,
613
- makeHydrateContext ? () => makeHydrateContext(index) : undefined
614
- )
615
- }
616
- }))
617
- }
618
-
619
- export function renderSparsePart(
620
- values: ReadonlyArray<Renderable<any, any>>,
621
- part: SparsePart,
622
- refCounter: IndexRefCounter
623
- ) {
624
- const indexes = part.parts.flatMap((p) => p._tag === "static/text" ? [] : [p.index])
625
-
626
- return Effect.forkScoped(
627
- Fx.observe(
628
- unwrapSparsePartRenderables(
629
- part.parts.map((p) => p._tag === "static/text" ? Fx.succeed(p.value) : values[p.index]),
630
- part
631
- ),
632
- (value) => Effect.tap(part.update(value as any), () => Effect.forEach(indexes, (a) => refCounter.release(a)))
633
- )
634
- )
635
- }
636
-
637
- export function renderPart<Values extends ReadonlyArray<Renderable<any, any>>>(
638
- values: Values,
639
- part: Part,
640
- refCounter: IndexRefCounter,
641
- ctx: Context.Context<any> | Context.Context<never>,
642
- hydrateCtx?: () => HydrateContext
643
- ): Effect.Effect<any, never, void> {
644
- const partIndex = part.index
645
- const renderable = values[partIndex]
646
-
647
- if (renderable === null || renderable === undefined) return refCounter.release(partIndex)
648
-
649
- if (isDirective(renderable)) {
650
- return renderable(part).pipe(
651
- Effect.flatMap(() => refCounter.release(partIndex)),
652
- Effect.forkScoped
653
- )
654
- } else if (part._tag === "ref") {
655
- return refCounter.release(partIndex)
656
- } else if (part._tag === "event") {
657
- const handler = getEventHandler(renderable, ctx, part.onCause)
658
- if (handler) {
659
- part.addEventListener(handler)
660
- }
661
-
662
- return refCounter.release(partIndex)
663
- } else if (part._tag === "node" && hydrateCtx) {
664
- return handlePart(
665
- renderable,
666
- (value) => Effect.flatMap(part.update(value), () => refCounter.release(partIndex))
667
- ).pipe(
668
- HydrateContext.provide(hydrateCtx()),
669
- Effect.forkScoped
670
- )
671
- } else if (part._tag === "properties") {
672
- return handlePropertiesPart(renderable, part, refCounter)
673
- } else {
674
- return handlePart(
675
- renderable,
676
- (value) => Effect.flatMap(part.update(value as any), () => refCounter.release(partIndex))
677
- )
678
- }
679
- }
680
-
681
- function handlePropertiesPart<R, E>(
682
- renderable: unknown,
683
- part: PropertiesPart,
684
- refCounter: IndexRefCounter
685
- ): Effect.Effect<R | Scope, E, void> {
686
- if (renderable && typeof renderable === "object") {
687
- return handlePart(
688
- Fx.struct(Object.fromEntries(Object.entries(renderable).map(([k, v]) => [k, unwrapRenderable(v)] as const))),
689
- (value) => Effect.tap(part.update(value as any), () => refCounter.release(part.index))
690
- )
691
- }
692
-
693
- return Effect.succeed(void 0)
694
- }
695
-
696
599
  function getEventHandler<R, E>(
697
600
  renderable: any,
698
601
  ctx: Context.Context<any> | Context.Context<never>,
@@ -757,31 +660,6 @@ function unwrapRenderable<R, E>(renderable: unknown): Fx.Fx<R, E, any> {
757
660
  }
758
661
  }
759
662
 
760
- function unwrapSparsePartRenderables(
761
- renderables: ReadonlyArray<Renderable<any, any>>,
762
- part: SparsePart
763
- ) {
764
- return Fx.tuple(
765
- // @ts-ignore type too deep
766
- renderables.map((renderable, i) => {
767
- const p = part.parts[i]
768
-
769
- if (p._tag === "static/text") {
770
- return Fx.succeed(p.value)
771
- }
772
-
773
- if (isDirective(renderable)) {
774
- return Fx.fromEffect(Effect.map(renderable(p), () => p.value))
775
- }
776
-
777
- return Fx.mapEffect(
778
- unwrapRenderable(renderable),
779
- (u) => Effect.map(p.update(u), () => p.value)
780
- )
781
- })
782
- ) as any
783
- }
784
-
785
663
  export function attachRoot<T extends RenderEvent | null>(
786
664
  cache: RenderContext["renderCache"],
787
665
  where: HTMLElement,
@@ -844,147 +722,6 @@ export function getBrowserEntry(
844
722
  }
845
723
  }
846
724
 
847
- export function buildParts<E>(
848
- document: Document,
849
- ctx: RenderContext,
850
- template: Template.Template,
851
- content: ParentChildNodes,
852
- eventSource: EventSource,
853
- onCause: (cause: Cause<E>) => Effect.Effect<never, never, void>,
854
- isHydrating: boolean
855
- ): Parts {
856
- return template.parts.map(([part, path]) =>
857
- buildPartWithNode(document, ctx, part, findPath(content, path), eventSource, onCause, isHydrating)
858
- )
859
- }
860
-
861
- function buildPartWithNode<E>(
862
- document: Document,
863
- ctx: RenderContext,
864
- part: Template.PartNode | Template.SparsePartNode,
865
- node: Node,
866
- eventSource: EventSource,
867
- onCause: (cause: Cause<E>) => Effect.Effect<never, never, void>,
868
- isHydrating: boolean
869
- ): Part | SparsePart {
870
- switch (part._tag) {
871
- case "attr":
872
- return AttributePartImpl.browser(part.index, node as Element, part.name, ctx)
873
- case "boolean-part":
874
- return BooleanPartImpl.browser(part.index, node as Element, part.name, ctx)
875
- case "className-part":
876
- return ClassNamePartImpl.browser(part.index, node as Element, ctx)
877
- case "comment-part":
878
- return CommentPartImpl.browser(part.index, node as Comment, ctx)
879
- case "data":
880
- return DataPartImpl.browser(part.index, node as HTMLElement | SVGElement, ctx)
881
- case "event":
882
- return new EventPartImpl(
883
- part.name,
884
- part.index,
885
- ElementSource.fromElement(node as Element),
886
- onCause as any,
887
- (handler) => eventSource.addEventListener(node as Element, part.name, handler)
888
- )
889
- case "node":
890
- return makeRenderNodePart(part.index, node as HTMLElement | SVGElement, ctx, document, isHydrating)
891
- case "property":
892
- return PropertyPartImpl.browser(part.index, node, part.name, ctx)
893
- case "properties":
894
- return PropertiesPartImpl.browser(part.index, node as HTMLElement | SVGElement, ctx)
895
- case "ref":
896
- return new RefPartImpl(ElementSource.fromElement(node as Element), part.index) as any
897
- case "sparse-attr": {
898
- const parts: Array<AttributePart | StaticText> = Array(part.nodes.length)
899
- const sparse = SparseAttributePartImpl.browser(
900
- part.name,
901
- parts,
902
- node as HTMLElement | SVGElement,
903
- ctx
904
- )
905
-
906
- for (let i = 0; i < part.nodes.length; ++i) {
907
- const node = part.nodes[i]
908
-
909
- if (node._tag === "text") {
910
- parts.push(new StaticTextImpl(node.value))
911
- ;(sparse as any).value[i] = node.value
912
- } else {
913
- parts.push(
914
- new AttributePartImpl(
915
- node.name,
916
- node.index,
917
- ({ value }) => sparse.update(replace(sparse.value, i, value || "")),
918
- sparse.value[i]
919
- )
920
- )
921
- }
922
- }
923
-
924
- return sparse
925
- }
926
- case "sparse-class-name": {
927
- const parts: Array<ClassNamePart | StaticText> = []
928
- const values: Array<string | Array<string>> = [] // TODO: Do this for all other sparse attrs
929
- const sparse = SparseClassNamePartImpl.browser(
930
- parts,
931
- node as HTMLElement | SVGElement,
932
- ctx,
933
- values
934
- )
935
-
936
- for (let i = 0; i < part.nodes.length; ++i) {
937
- const node = part.nodes[i]
938
-
939
- if (node._tag === "text") {
940
- parts.push(new StaticTextImpl(node.value))
941
- values.push(node.value)
942
- } else {
943
- values.push([])
944
- parts.push(
945
- new ClassNamePartImpl(
946
- node.index,
947
- ({ value }) => sparse.update(replace(sparse.value, i, value || "")),
948
- []
949
- )
950
- )
951
- }
952
- }
953
-
954
- return sparse
955
- }
956
- case "sparse-comment": {
957
- const parts: Array<CommentPart | StaticText> = Array(part.nodes.length)
958
- const sparse = SparseCommentPartImpl.browser(
959
- node as Comment,
960
- parts,
961
- ctx
962
- )
963
-
964
- for (let i = 0; i < part.nodes.length; ++i) {
965
- const node = part.nodes[i]
966
-
967
- if (node._tag === "text") {
968
- parts.push(new StaticTextImpl(node.value))
969
- ;(sparse as any).value[i] = node.value
970
- } else {
971
- parts.push(
972
- new CommentPartImpl(
973
- node.index,
974
- ({ value }) => sparse.update(replace(sparse.value, i, value || "")),
975
- sparse.value[i]
976
- )
977
- )
978
- }
979
- }
980
-
981
- return sparse
982
- }
983
- case "text-part":
984
- return TextPartImpl.browser(document, part.index, node as Element, ctx)
985
- }
986
- }
987
-
988
725
  export function buildTemplate(document: Document, { nodes }: Template.Template): DocumentFragment {
989
726
  const fragment = document.createDocumentFragment()
990
727