@repobit/dex-store-elements 1.7.3 → 2.0.1
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/CHANGELOG.md +6 -906
- package/README.md +308 -283
- package/dist/src/actions/action.adapter.d.ts +36 -0
- package/dist/src/actions/action.adapter.js +183 -0
- package/dist/src/actions/action.adapter.js.map +1 -0
- package/dist/src/actions/action.attributes.d.ts +15 -0
- package/dist/src/actions/action.attributes.js +137 -0
- package/dist/src/actions/action.attributes.js.map +1 -0
- package/dist/src/actions/action.registry.d.ts +4 -0
- package/dist/src/actions/action.registry.js +119 -0
- package/dist/src/actions/action.registry.js.map +1 -0
- package/dist/src/actions/index.d.ts +3 -2
- package/dist/src/actions/index.js +3 -80
- package/dist/src/actions/index.js.map +1 -1
- package/dist/src/compute/compute.expand.d.ts +70 -0
- package/dist/src/compute/compute.expand.js +319 -0
- package/dist/src/compute/compute.expand.js.map +1 -0
- package/dist/src/compute/compute.scan.d.ts +54 -0
- package/dist/src/compute/compute.scan.js +160 -0
- package/dist/src/compute/compute.scan.js.map +1 -0
- package/dist/src/compute/option-resolver.d.ts +31 -0
- package/dist/src/compute/option-resolver.js +147 -0
- package/dist/src/compute/option-resolver.js.map +1 -0
- package/dist/src/contexts/context.compute.d.ts +12 -0
- package/dist/src/contexts/context.compute.js +4 -0
- package/dist/src/contexts/context.compute.js.map +1 -0
- package/dist/src/contexts/context.scope.d.ts +139 -0
- package/dist/src/contexts/context.scope.js +48 -0
- package/dist/src/contexts/context.scope.js.map +1 -0
- package/dist/src/controllers/controller.action-resolve.d.ts +110 -0
- package/dist/src/controllers/controller.action-resolve.js +203 -0
- package/dist/src/controllers/controller.action-resolve.js.map +1 -0
- package/dist/src/controllers/controller.action-topology.d.ts +59 -0
- package/dist/src/controllers/controller.action-topology.js +130 -0
- package/dist/src/controllers/controller.action-topology.js.map +1 -0
- package/dist/src/controllers/controller.compute.d.ts +72 -0
- package/dist/src/controllers/controller.compute.js +110 -0
- package/dist/src/controllers/controller.compute.js.map +1 -0
- package/dist/src/controllers/controller.resolve.d.ts +78 -0
- package/dist/src/controllers/controller.resolve.js +205 -0
- package/dist/src/controllers/controller.resolve.js.map +1 -0
- package/dist/src/core/action-topology.d.ts +62 -0
- package/dist/src/core/action-topology.js +67 -0
- package/dist/src/core/action-topology.js.map +1 -0
- package/dist/src/core/actions.d.ts +64 -0
- package/dist/src/core/actions.js +47 -0
- package/dist/src/core/actions.js.map +1 -0
- package/dist/src/core/node.element.d.ts +179 -0
- package/dist/src/core/node.element.js +573 -0
- package/dist/src/core/node.element.js.map +1 -0
- package/dist/src/core/node.scope.d.ts +374 -0
- package/dist/src/core/node.scope.js +558 -0
- package/dist/src/core/node.scope.js.map +1 -0
- package/dist/src/core/option-update.d.ts +11 -0
- package/dist/src/core/option-update.js +59 -0
- package/dist/src/core/option-update.js.map +1 -0
- package/dist/src/dsl/compilers/array/compiler.js +13 -14
- package/dist/src/dsl/compilers/array/compiler.js.map +1 -1
- package/dist/src/dsl/compilers/boolean/compiler.d.ts +1 -1
- package/dist/src/dsl/compilers/boolean/compiler.js +14 -27
- package/dist/src/dsl/compilers/boolean/compiler.js.map +1 -1
- package/dist/src/dsl/compilers/enum/compiler.js +7 -10
- package/dist/src/dsl/compilers/enum/compiler.js.map +1 -1
- package/dist/src/dsl/compilers/index.d.ts +1 -1
- package/dist/src/dsl/compilers/index.js +5 -5
- package/dist/src/dsl/compilers/index.js.map +1 -1
- package/dist/src/dsl/utilty.d.ts +0 -4
- package/dist/src/dsl/utilty.js +3 -30
- package/dist/src/dsl/utilty.js.map +1 -1
- package/dist/src/eta/batch.d.ts +123 -0
- package/dist/src/eta/batch.js +162 -0
- package/dist/src/eta/batch.js.map +1 -0
- package/dist/src/eta/batcher.d.ts +184 -0
- package/dist/src/eta/batcher.js +335 -0
- package/dist/src/eta/batcher.js.map +1 -0
- package/dist/src/eta/cache.d.ts +36 -0
- package/dist/src/eta/cache.js +56 -0
- package/dist/src/eta/cache.js.map +1 -0
- package/dist/src/eta/controller.d.ts +608 -0
- package/dist/src/eta/controller.js +1036 -0
- package/dist/src/eta/controller.js.map +1 -0
- package/dist/src/eta/factory.d.ts +74 -0
- package/dist/src/eta/factory.js +121 -0
- package/dist/src/eta/factory.js.map +1 -0
- package/dist/src/eta/idle.d.ts +39 -0
- package/dist/src/eta/idle.js +52 -0
- package/dist/src/eta/idle.js.map +1 -0
- package/dist/src/eta/index.d.ts +52 -0
- package/dist/src/eta/index.js +13 -0
- package/dist/src/eta/index.js.map +1 -0
- package/dist/src/eta/observer.d.ts +263 -0
- package/dist/src/eta/observer.js +263 -0
- package/dist/src/eta/observer.js.map +1 -0
- package/dist/src/eta/operations.d.ts +41 -0
- package/dist/src/eta/operations.js +59 -0
- package/dist/src/eta/operations.js.map +1 -0
- package/dist/src/eta/source-registry.d.ts +198 -0
- package/dist/src/eta/source-registry.js +267 -0
- package/dist/src/eta/source-registry.js.map +1 -0
- package/dist/src/eta/template-index.d.ts +285 -0
- package/dist/src/eta/template-index.js +642 -0
- package/dist/src/eta/template-index.js.map +1 -0
- package/dist/src/eta/types.d.ts +529 -0
- package/dist/src/eta/types.js +2 -0
- package/dist/src/eta/types.js.map +1 -0
- package/dist/src/index.d.ts +21 -9
- package/dist/src/index.js +22 -11
- package/dist/src/index.js.map +1 -1
- package/dist/src/nodes/node.context.d.ts +5 -3
- package/dist/src/nodes/node.context.js +48 -3
- package/dist/src/nodes/node.context.js.map +1 -1
- package/dist/src/nodes/node.option.d.ts +11 -31
- package/dist/src/nodes/node.option.js +158 -222
- package/dist/src/nodes/node.option.js.map +1 -1
- package/dist/src/nodes/node.product.d.ts +7 -11
- package/dist/src/nodes/node.product.js +85 -64
- package/dist/src/nodes/node.product.js.map +1 -1
- package/dist/src/renders/attributes/buyLink.d.ts +2 -2
- package/dist/src/renders/attributes/buyLink.js +14 -11
- package/dist/src/renders/attributes/buyLink.js.map +1 -1
- package/dist/src/renders/attributes/devices.d.ts +2 -2
- package/dist/src/renders/attributes/devices.js +9 -19
- package/dist/src/renders/attributes/devices.js.map +1 -1
- package/dist/src/renders/attributes/discount.d.ts +2 -2
- package/dist/src/renders/attributes/discount.js +56 -44
- package/dist/src/renders/attributes/discount.js.map +1 -1
- package/dist/src/renders/attributes/hide.d.ts +2 -2
- package/dist/src/renders/attributes/hide.js +49 -14
- package/dist/src/renders/attributes/hide.js.map +1 -1
- package/dist/src/renders/attributes/index.d.ts +3 -3
- package/dist/src/renders/attributes/index.js +2 -4
- package/dist/src/renders/attributes/index.js.map +1 -1
- package/dist/src/renders/attributes/price.d.ts +2 -2
- package/dist/src/renders/attributes/price.js +30 -44
- package/dist/src/renders/attributes/price.js.map +1 -1
- package/dist/src/renders/attributes/subscription.d.ts +2 -2
- package/dist/src/renders/attributes/subscription.js +14 -20
- package/dist/src/renders/attributes/subscription.js.map +1 -1
- package/dist/src/renders/attributes/utilty.d.ts +2 -0
- package/dist/src/renders/attributes/utilty.js +17 -4
- package/dist/src/renders/attributes/utilty.js.map +1 -1
- package/dist/src/renders/context.d.ts +122 -10
- package/dist/src/renders/context.js +140 -80
- package/dist/src/renders/context.js.map +1 -1
- package/dist/src/renders/format.js +2 -2
- package/dist/src/renders/format.js.map +1 -1
- package/dist/src/renders/index.d.ts +6 -2
- package/dist/src/renders/index.js +67 -29
- package/dist/src/renders/index.js.map +1 -1
- package/dist/src/renders/observe.d.ts +1 -4
- package/dist/src/renders/observe.js +10 -24
- package/dist/src/renders/observe.js.map +1 -1
- package/dist/src/renders/render.wrapper.d.ts +37 -0
- package/dist/src/renders/render.wrapper.js +147 -0
- package/dist/src/renders/render.wrapper.js.map +1 -0
- package/dist/src/templating/eta.js +1 -4
- package/dist/src/templating/eta.js.map +1 -1
- package/package.json +4 -5
- package/dist/src/actions/action.button.d.ts +0 -2
- package/dist/src/actions/action.button.js +0 -8
- package/dist/src/actions/action.button.js.map +0 -1
- package/dist/src/actions/action.input.d.ts +0 -2
- package/dist/src/actions/action.input.js +0 -43
- package/dist/src/actions/action.input.js.map +0 -1
- package/dist/src/actions/action.select.d.ts +0 -2
- package/dist/src/actions/action.select.js +0 -14
- package/dist/src/actions/action.select.js.map +0 -1
- package/dist/src/actions/utilty.d.ts +0 -16
- package/dist/src/actions/utilty.js +0 -149
- package/dist/src/actions/utilty.js.map +0 -1
- package/dist/src/contexts/context.datalayer.d.ts +0 -9
- package/dist/src/contexts/context.datalayer.js +0 -3
- package/dist/src/contexts/context.datalayer.js.map +0 -1
- package/dist/src/contexts/context.derived.d.ts +0 -12
- package/dist/src/contexts/context.derived.js +0 -3
- package/dist/src/contexts/context.derived.js.map +0 -1
- package/dist/src/contexts/context.option.d.ts +0 -5
- package/dist/src/contexts/context.option.js +0 -3
- package/dist/src/contexts/context.option.js.map +0 -1
- package/dist/src/contexts/context.product.d.ts +0 -5
- package/dist/src/contexts/context.product.js +0 -3
- package/dist/src/contexts/context.product.js.map +0 -1
- package/dist/src/contexts/context.state.d.ts +0 -28
- package/dist/src/contexts/context.state.js +0 -3
- package/dist/src/contexts/context.state.js.map +0 -1
- package/dist/src/contexts/context.store.d.ts +0 -5
- package/dist/src/contexts/context.store.js +0 -3
- package/dist/src/contexts/context.store.js.map +0 -1
- package/dist/src/controllers/collect-controller.d.ts +0 -39
- package/dist/src/controllers/collect-controller.js +0 -161
- package/dist/src/controllers/collect-controller.js.map +0 -1
- package/dist/src/controllers/compute-controller.d.ts +0 -55
- package/dist/src/controllers/compute-controller.js +0 -350
- package/dist/src/controllers/compute-controller.js.map +0 -1
- package/dist/src/controllers/eta-renderer/eta-dom-renderer.d.ts +0 -25
- package/dist/src/controllers/eta-renderer/eta-dom-renderer.js +0 -192
- package/dist/src/controllers/eta-renderer/eta-dom-renderer.js.map +0 -1
- package/dist/src/controllers/eta-renderer/eta-mutation-observer.d.ts +0 -28
- package/dist/src/controllers/eta-renderer/eta-mutation-observer.js +0 -206
- package/dist/src/controllers/eta-renderer/eta-mutation-observer.js.map +0 -1
- package/dist/src/controllers/eta-renderer/eta-renderer.d.ts +0 -21
- package/dist/src/controllers/eta-renderer/eta-renderer.js +0 -77
- package/dist/src/controllers/eta-renderer/eta-renderer.js.map +0 -1
- package/dist/src/controllers/eta-renderer/eta-template-cache.d.ts +0 -24
- package/dist/src/controllers/eta-renderer/eta-template-cache.js +0 -61
- package/dist/src/controllers/eta-renderer/eta-template-cache.js.map +0 -1
- package/dist/src/controllers/eta-renderer/eta-types.d.ts +0 -11
- package/dist/src/controllers/eta-renderer/eta-types.js +0 -1
- package/dist/src/controllers/eta-renderer/eta-types.js.map +0 -1
- package/dist/src/controllers/eta-renderer/index.d.ts +0 -2
- package/dist/src/controllers/eta-renderer/index.js +0 -2
- package/dist/src/controllers/eta-renderer/index.js.map +0 -1
- package/dist/src/controllers/event-pipeline-controller.d.ts +0 -28
- package/dist/src/controllers/event-pipeline-controller.js +0 -72
- package/dist/src/controllers/event-pipeline-controller.js.map +0 -1
- package/dist/src/events/events.d.ts +0 -75
- package/dist/src/events/events.js +0 -78
- package/dist/src/events/events.js.map +0 -1
- package/dist/src/nodes/node.root.d.ts +0 -16
- package/dist/src/nodes/node.root.js +0 -49
- package/dist/src/nodes/node.root.js.map +0 -1
- package/dist/src/nodes/node.state.d.ts +0 -88
- package/dist/src/nodes/node.state.js +0 -330
- package/dist/src/nodes/node.state.js.map +0 -1
- package/dist/src/renders/utility.d.ts +0 -32
- package/dist/src/renders/utility.js +0 -191
- package/dist/src/renders/utility.js.map +0 -1
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidecar registry that preserves original template sources across renders.
|
|
3
|
+
*
|
|
4
|
+
* The Eta template subsystem overwrites DOM values during render:
|
|
5
|
+
*
|
|
6
|
+
* <span>{{=it.name}}</span>
|
|
7
|
+
* becomes
|
|
8
|
+
* <span>iPhone</span>
|
|
9
|
+
*
|
|
10
|
+
* Once a render has happened, the original `{{=it.name}}` source is gone
|
|
11
|
+
* from the DOM. If the index is later cleared and re-initialized — or if a
|
|
12
|
+
* mutation observer forwards an external edit — the factory can only see
|
|
13
|
+
* the rendered string, not the template source.
|
|
14
|
+
*
|
|
15
|
+
* The registry stores the original sources keyed by node identity. The
|
|
16
|
+
* factory consults the registry first and only falls back to the DOM
|
|
17
|
+
* value when the registry has no entry.
|
|
18
|
+
*
|
|
19
|
+
* The registry is intentionally generic: it does not know about Eta, scope,
|
|
20
|
+
* products, options, or pricing. It is a small, GC-friendly cache.
|
|
21
|
+
*
|
|
22
|
+
* Lifetime:
|
|
23
|
+
*
|
|
24
|
+
* - Uses `WeakMap` / `Map` keyed by DOM nodes so disconnected nodes can be
|
|
25
|
+
* reclaimed by the GC.
|
|
26
|
+
* - `clear()` resets the registry to empty.
|
|
27
|
+
*
|
|
28
|
+
* **Cross-scope limitation.** A source registry is owned by the
|
|
29
|
+
* index that created it (one registry per `EtaTemplateIndex`
|
|
30
|
+
* instance by default). Moving a rendered template node from one
|
|
31
|
+
* scope to another loses the original template source — the
|
|
32
|
+
* destination index's registry has no record of it, so the
|
|
33
|
+
* factory falls back to the live DOM value, which now holds the
|
|
34
|
+
* rendered string rather than the template literal. The
|
|
35
|
+
* destination index cannot recreate the operation without
|
|
36
|
+
* discovering the new template content externally.
|
|
37
|
+
*
|
|
38
|
+
* If an application reparents rendered template nodes across
|
|
39
|
+
* scopes, the safe options are:
|
|
40
|
+
*
|
|
41
|
+
* 1. Share a single `EtaTemplateSourceRegistry` across the
|
|
42
|
+
* affected indices and have each index pass that shared
|
|
43
|
+
* registry through the `TemplateOperationContext` on every
|
|
44
|
+
* factory call — the factory itself is stateless at the call
|
|
45
|
+
* site, so a single registry can back multiple indices.
|
|
46
|
+
* 2. Accept that moved nodes must be re-discovered by the
|
|
47
|
+
* destination index — for example, by re-emitting the original
|
|
48
|
+
* template literal into the moved node, or by relying on the
|
|
49
|
+
* index's normal `MutationObserver` flow to pick up the new
|
|
50
|
+
* template content.
|
|
51
|
+
*
|
|
52
|
+
* Today the controller does not expose a `sourceRegistry`
|
|
53
|
+
* cross-index option; sharing requires reaching into the index
|
|
54
|
+
* constructor. The v4 review recommends keeping this limitation
|
|
55
|
+
* documented unless an application actually needs cross-scope
|
|
56
|
+
* DOM moves — at which point the index/controller can grow a
|
|
57
|
+
* `sourceRegistry` option that overrides the per-index
|
|
58
|
+
* `createEtaTemplateSourceRegistry()` default.
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* Default predicate used by `EtaTemplateSourceRegistry` to recognise
|
|
62
|
+
* template text.
|
|
63
|
+
*
|
|
64
|
+
* Requires BOTH an opening `{{` AND a closing `}}` so the registry
|
|
65
|
+
* can safely inspect arbitrary DOM values without false positives on
|
|
66
|
+
* stray `{{` markers in plain string literals.
|
|
67
|
+
*
|
|
68
|
+
/**
|
|
69
|
+
* Default predicate used to decide whether a string contains an
|
|
70
|
+
* Eta template token.
|
|
71
|
+
*
|
|
72
|
+
* Requires BOTH an opening `{{` AND a closing `}}` so the
|
|
73
|
+
* predicate does not pay the cost of compiling partial template
|
|
74
|
+
* fragments (e.g. `Hello {{ user` with a typo) or arbitrary
|
|
75
|
+
* DOM values that happen to contain `{{` inside a plain string
|
|
76
|
+
* literal. The empty-string guard short-circuits the
|
|
77
|
+
* `indexOf` calls on falsy input.
|
|
78
|
+
*
|
|
79
|
+
* **Single source of truth.** The factory (`factory.ts`) re-exports
|
|
80
|
+
* this predicate as `defaultIsTemplateText` for backward
|
|
81
|
+
* compatibility; the two names point at the same function so the
|
|
82
|
+
* registry and the factory can never drift apart.
|
|
83
|
+
*/
|
|
84
|
+
export declare function defaultContainsEtaTemplate(text: string): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Predicate used to decide whether a string contains a template token.
|
|
87
|
+
*
|
|
88
|
+
* The default (`defaultContainsEtaTemplate`) requires BOTH an opening
|
|
89
|
+
* `{{` AND a closing `}}` so the registry can safely inspect arbitrary
|
|
90
|
+
* DOM values. Callers can plug in a stricter predicate when they want
|
|
91
|
+
* to be even more conservative.
|
|
92
|
+
*/
|
|
93
|
+
export type EtaTemplatePredicate = (text: string) => boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Options accepted by `EtaTemplateSourceRegistry`.
|
|
96
|
+
*/
|
|
97
|
+
export interface EtaTemplateSourceRegistryOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Predicate used to recognise template text. Defaults to
|
|
100
|
+
* `defaultContainsEtaTemplate`, which requires both `{{` AND `}}`.
|
|
101
|
+
*/
|
|
102
|
+
readonly isTemplateText?: EtaTemplatePredicate;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Sidecar storage for original template sources.
|
|
106
|
+
*
|
|
107
|
+
* Reads are tolerant: if no source is preserved for a node, the registry
|
|
108
|
+
* consults the DOM value, and if that contains a template it persists the
|
|
109
|
+
* value so the next read returns the preserved source rather than the
|
|
110
|
+
* (possibly already-rendered) DOM value.
|
|
111
|
+
*
|
|
112
|
+
* **Destructive operations out of scope.** The registry only preserves
|
|
113
|
+
* sources; it does not enumerate attribute names that have been removed
|
|
114
|
+
* from the DOM. Removing an attribute at runtime (e.g. via
|
|
115
|
+
* `element.removeAttribute`) is a destructive operation: the registry
|
|
116
|
+
* has no way to discover a removed attribute name from a live element
|
|
117
|
+
* alone. Once `removeWhenEmpty` was dropped from the renderer, the
|
|
118
|
+
* registry only ever needs to handle attributes that are still present
|
|
119
|
+
* in the DOM, so this limitation is no longer a concern in practice.
|
|
120
|
+
*/
|
|
121
|
+
export declare class EtaTemplateSourceRegistry {
|
|
122
|
+
private textSources;
|
|
123
|
+
private attrSources;
|
|
124
|
+
private readonly isTemplateText;
|
|
125
|
+
constructor(options?: EtaTemplateSourceRegistryOptions);
|
|
126
|
+
/**
|
|
127
|
+
* Returns the preserved source for a text node, falling back to the
|
|
128
|
+
* current DOM value.
|
|
129
|
+
*
|
|
130
|
+
* If the DOM value contains a template and no preserved source exists,
|
|
131
|
+
* the value is persisted so future reads stay stable even after the
|
|
132
|
+
* DOM value changes.
|
|
133
|
+
*/
|
|
134
|
+
readTextSource(node: Text): string | undefined;
|
|
135
|
+
/**
|
|
136
|
+
* Sets or clears the preserved source for a text node.
|
|
137
|
+
*
|
|
138
|
+
* Pass `undefined` to remove any preserved source; the next call to
|
|
139
|
+
* `readTextSource` will then fall back to the current DOM value.
|
|
140
|
+
*/
|
|
141
|
+
replaceTextSource(node: Text, source: string | undefined): void;
|
|
142
|
+
/**
|
|
143
|
+
* Returns the preserved source for a single attribute on an element.
|
|
144
|
+
*
|
|
145
|
+
* If no preserved source exists, the registry consults the current
|
|
146
|
+
* DOM value; when the value contains a template, the registry persists
|
|
147
|
+
* it so future reads stay stable.
|
|
148
|
+
*/
|
|
149
|
+
readAttributeSource(element: Element, name: string): string | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Stores a preserved source for an attribute. Used both internally
|
|
152
|
+
* (after reading a fresh DOM value) and externally (after parsing a
|
|
153
|
+
* template literal that the DOM no longer exposes).
|
|
154
|
+
*/
|
|
155
|
+
setAttributeSource(element: Element, name: string, source: string): void;
|
|
156
|
+
/**
|
|
157
|
+
* Removes a preserved attribute source. No-op if no source exists.
|
|
158
|
+
*/
|
|
159
|
+
removeAttributeSource(element: Element, name: string): void;
|
|
160
|
+
/**
|
|
161
|
+
* Reads the current DOM value for a text node and updates the
|
|
162
|
+
* registry accordingly.
|
|
163
|
+
*
|
|
164
|
+
* Persists the value when it contains a template; clears any
|
|
165
|
+
* previously preserved source otherwise. This is the right call for
|
|
166
|
+
* external mutations where the DOM has been edited and the registry
|
|
167
|
+
* should reflect the new state.
|
|
168
|
+
*/
|
|
169
|
+
syncTextSource(node: Text): void;
|
|
170
|
+
/**
|
|
171
|
+
* Reads the current DOM value for an attribute and updates the
|
|
172
|
+
* registry accordingly.
|
|
173
|
+
*
|
|
174
|
+
* Persists the value when it contains a template; clears any
|
|
175
|
+
* previously preserved source otherwise. The right call for external
|
|
176
|
+
* attribute mutations.
|
|
177
|
+
*/
|
|
178
|
+
syncAttributeSource(element: Element, name: string): void;
|
|
179
|
+
/**
|
|
180
|
+
* Wipes every preserved source.
|
|
181
|
+
*
|
|
182
|
+
* Mostly useful for tests. Production code should let the GC reclaim
|
|
183
|
+
* entries as nodes become unreachable.
|
|
184
|
+
*/
|
|
185
|
+
clear(): void;
|
|
186
|
+
/**
|
|
187
|
+
* Returns `true` when `text` looks like an Eta template.
|
|
188
|
+
*
|
|
189
|
+
* Delegates to the configured predicate; the default
|
|
190
|
+
* (`defaultContainsEtaTemplate`) requires both an opening `{{` AND a
|
|
191
|
+
* closing `}}`.
|
|
192
|
+
*/
|
|
193
|
+
private containsTemplate;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Convenience constructor used by the index and the default factory.
|
|
197
|
+
*/
|
|
198
|
+
export declare function createEtaTemplateSourceRegistry(options?: EtaTemplateSourceRegistryOptions): EtaTemplateSourceRegistry;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// src/eta/source-registry.ts
|
|
2
|
+
/**
|
|
3
|
+
* Sidecar registry that preserves original template sources across renders.
|
|
4
|
+
*
|
|
5
|
+
* The Eta template subsystem overwrites DOM values during render:
|
|
6
|
+
*
|
|
7
|
+
* <span>{{=it.name}}</span>
|
|
8
|
+
* becomes
|
|
9
|
+
* <span>iPhone</span>
|
|
10
|
+
*
|
|
11
|
+
* Once a render has happened, the original `{{=it.name}}` source is gone
|
|
12
|
+
* from the DOM. If the index is later cleared and re-initialized — or if a
|
|
13
|
+
* mutation observer forwards an external edit — the factory can only see
|
|
14
|
+
* the rendered string, not the template source.
|
|
15
|
+
*
|
|
16
|
+
* The registry stores the original sources keyed by node identity. The
|
|
17
|
+
* factory consults the registry first and only falls back to the DOM
|
|
18
|
+
* value when the registry has no entry.
|
|
19
|
+
*
|
|
20
|
+
* The registry is intentionally generic: it does not know about Eta, scope,
|
|
21
|
+
* products, options, or pricing. It is a small, GC-friendly cache.
|
|
22
|
+
*
|
|
23
|
+
* Lifetime:
|
|
24
|
+
*
|
|
25
|
+
* - Uses `WeakMap` / `Map` keyed by DOM nodes so disconnected nodes can be
|
|
26
|
+
* reclaimed by the GC.
|
|
27
|
+
* - `clear()` resets the registry to empty.
|
|
28
|
+
*
|
|
29
|
+
* **Cross-scope limitation.** A source registry is owned by the
|
|
30
|
+
* index that created it (one registry per `EtaTemplateIndex`
|
|
31
|
+
* instance by default). Moving a rendered template node from one
|
|
32
|
+
* scope to another loses the original template source — the
|
|
33
|
+
* destination index's registry has no record of it, so the
|
|
34
|
+
* factory falls back to the live DOM value, which now holds the
|
|
35
|
+
* rendered string rather than the template literal. The
|
|
36
|
+
* destination index cannot recreate the operation without
|
|
37
|
+
* discovering the new template content externally.
|
|
38
|
+
*
|
|
39
|
+
* If an application reparents rendered template nodes across
|
|
40
|
+
* scopes, the safe options are:
|
|
41
|
+
*
|
|
42
|
+
* 1. Share a single `EtaTemplateSourceRegistry` across the
|
|
43
|
+
* affected indices and have each index pass that shared
|
|
44
|
+
* registry through the `TemplateOperationContext` on every
|
|
45
|
+
* factory call — the factory itself is stateless at the call
|
|
46
|
+
* site, so a single registry can back multiple indices.
|
|
47
|
+
* 2. Accept that moved nodes must be re-discovered by the
|
|
48
|
+
* destination index — for example, by re-emitting the original
|
|
49
|
+
* template literal into the moved node, or by relying on the
|
|
50
|
+
* index's normal `MutationObserver` flow to pick up the new
|
|
51
|
+
* template content.
|
|
52
|
+
*
|
|
53
|
+
* Today the controller does not expose a `sourceRegistry`
|
|
54
|
+
* cross-index option; sharing requires reaching into the index
|
|
55
|
+
* constructor. The v4 review recommends keeping this limitation
|
|
56
|
+
* documented unless an application actually needs cross-scope
|
|
57
|
+
* DOM moves — at which point the index/controller can grow a
|
|
58
|
+
* `sourceRegistry` option that overrides the per-index
|
|
59
|
+
* `createEtaTemplateSourceRegistry()` default.
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Default predicate used by `EtaTemplateSourceRegistry` to recognise
|
|
63
|
+
* template text.
|
|
64
|
+
*
|
|
65
|
+
* Requires BOTH an opening `{{` AND a closing `}}` so the registry
|
|
66
|
+
* can safely inspect arbitrary DOM values without false positives on
|
|
67
|
+
* stray `{{` markers in plain string literals.
|
|
68
|
+
*
|
|
69
|
+
/**
|
|
70
|
+
* Default predicate used to decide whether a string contains an
|
|
71
|
+
* Eta template token.
|
|
72
|
+
*
|
|
73
|
+
* Requires BOTH an opening `{{` AND a closing `}}` so the
|
|
74
|
+
* predicate does not pay the cost of compiling partial template
|
|
75
|
+
* fragments (e.g. `Hello {{ user` with a typo) or arbitrary
|
|
76
|
+
* DOM values that happen to contain `{{` inside a plain string
|
|
77
|
+
* literal. The empty-string guard short-circuits the
|
|
78
|
+
* `indexOf` calls on falsy input.
|
|
79
|
+
*
|
|
80
|
+
* **Single source of truth.** The factory (`factory.ts`) re-exports
|
|
81
|
+
* this predicate as `defaultIsTemplateText` for backward
|
|
82
|
+
* compatibility; the two names point at the same function so the
|
|
83
|
+
* registry and the factory can never drift apart.
|
|
84
|
+
*/
|
|
85
|
+
export function defaultContainsEtaTemplate(text) {
|
|
86
|
+
if (!text) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return (text.indexOf('{{') !== -1 &&
|
|
90
|
+
text.indexOf('}}') !== -1);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Sidecar storage for original template sources.
|
|
94
|
+
*
|
|
95
|
+
* Reads are tolerant: if no source is preserved for a node, the registry
|
|
96
|
+
* consults the DOM value, and if that contains a template it persists the
|
|
97
|
+
* value so the next read returns the preserved source rather than the
|
|
98
|
+
* (possibly already-rendered) DOM value.
|
|
99
|
+
*
|
|
100
|
+
* **Destructive operations out of scope.** The registry only preserves
|
|
101
|
+
* sources; it does not enumerate attribute names that have been removed
|
|
102
|
+
* from the DOM. Removing an attribute at runtime (e.g. via
|
|
103
|
+
* `element.removeAttribute`) is a destructive operation: the registry
|
|
104
|
+
* has no way to discover a removed attribute name from a live element
|
|
105
|
+
* alone. Once `removeWhenEmpty` was dropped from the renderer, the
|
|
106
|
+
* registry only ever needs to handle attributes that are still present
|
|
107
|
+
* in the DOM, so this limitation is no longer a concern in practice.
|
|
108
|
+
*/
|
|
109
|
+
export class EtaTemplateSourceRegistry {
|
|
110
|
+
constructor(options = {}) {
|
|
111
|
+
this.textSources = new WeakMap();
|
|
112
|
+
this.attrSources = new WeakMap();
|
|
113
|
+
this.isTemplateText =
|
|
114
|
+
options.isTemplateText ??
|
|
115
|
+
defaultContainsEtaTemplate;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Returns the preserved source for a text node, falling back to the
|
|
119
|
+
* current DOM value.
|
|
120
|
+
*
|
|
121
|
+
* If the DOM value contains a template and no preserved source exists,
|
|
122
|
+
* the value is persisted so future reads stay stable even after the
|
|
123
|
+
* DOM value changes.
|
|
124
|
+
*/
|
|
125
|
+
readTextSource(node) {
|
|
126
|
+
const existing = this.textSources.get(node);
|
|
127
|
+
if (existing !== undefined) {
|
|
128
|
+
return existing;
|
|
129
|
+
}
|
|
130
|
+
const current = node.nodeValue ?? '';
|
|
131
|
+
if (!this.containsTemplate(current)) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
this.textSources.set(node, current);
|
|
135
|
+
return current;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Sets or clears the preserved source for a text node.
|
|
139
|
+
*
|
|
140
|
+
* Pass `undefined` to remove any preserved source; the next call to
|
|
141
|
+
* `readTextSource` will then fall back to the current DOM value.
|
|
142
|
+
*/
|
|
143
|
+
replaceTextSource(node, source) {
|
|
144
|
+
if (source === undefined) {
|
|
145
|
+
this.textSources.delete(node);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
this.textSources.set(node, source);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Returns the preserved source for a single attribute on an element.
|
|
152
|
+
*
|
|
153
|
+
* If no preserved source exists, the registry consults the current
|
|
154
|
+
* DOM value; when the value contains a template, the registry persists
|
|
155
|
+
* it so future reads stay stable.
|
|
156
|
+
*/
|
|
157
|
+
readAttributeSource(element, name) {
|
|
158
|
+
const existing = this.attrSources.get(element)?.get(name);
|
|
159
|
+
if (existing !== undefined) {
|
|
160
|
+
return existing;
|
|
161
|
+
}
|
|
162
|
+
const current = element.getAttribute(name);
|
|
163
|
+
if (current === null ||
|
|
164
|
+
current === undefined ||
|
|
165
|
+
!this.containsTemplate(current)) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
this.setAttributeSource(element, name, current);
|
|
169
|
+
return current;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Stores a preserved source for an attribute. Used both internally
|
|
173
|
+
* (after reading a fresh DOM value) and externally (after parsing a
|
|
174
|
+
* template literal that the DOM no longer exposes).
|
|
175
|
+
*/
|
|
176
|
+
setAttributeSource(element, name, source) {
|
|
177
|
+
let byName = this.attrSources.get(element);
|
|
178
|
+
if (!byName) {
|
|
179
|
+
byName = new Map();
|
|
180
|
+
this.attrSources.set(element, byName);
|
|
181
|
+
}
|
|
182
|
+
byName.set(name, source);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Removes a preserved attribute source. No-op if no source exists.
|
|
186
|
+
*/
|
|
187
|
+
removeAttributeSource(element, name) {
|
|
188
|
+
const byName = this.attrSources.get(element);
|
|
189
|
+
if (!byName) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
byName.delete(name);
|
|
193
|
+
if (byName.size === 0) {
|
|
194
|
+
this.attrSources.delete(element);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Reads the current DOM value for a text node and updates the
|
|
199
|
+
* registry accordingly.
|
|
200
|
+
*
|
|
201
|
+
* Persists the value when it contains a template; clears any
|
|
202
|
+
* previously preserved source otherwise. This is the right call for
|
|
203
|
+
* external mutations where the DOM has been edited and the registry
|
|
204
|
+
* should reflect the new state.
|
|
205
|
+
*/
|
|
206
|
+
syncTextSource(node) {
|
|
207
|
+
const current = node.nodeValue ?? '';
|
|
208
|
+
if (this.containsTemplate(current)) {
|
|
209
|
+
this.textSources.set(node, current);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
this.textSources.delete(node);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Reads the current DOM value for an attribute and updates the
|
|
217
|
+
* registry accordingly.
|
|
218
|
+
*
|
|
219
|
+
* Persists the value when it contains a template; clears any
|
|
220
|
+
* previously preserved source otherwise. The right call for external
|
|
221
|
+
* attribute mutations.
|
|
222
|
+
*/
|
|
223
|
+
syncAttributeSource(element, name) {
|
|
224
|
+
const current = element.getAttribute(name);
|
|
225
|
+
if (current === null ||
|
|
226
|
+
current === undefined) {
|
|
227
|
+
this.removeAttributeSource(element, name);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (this.containsTemplate(current)) {
|
|
231
|
+
this.setAttributeSource(element, name, current);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
this.removeAttributeSource(element, name);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Wipes every preserved source.
|
|
239
|
+
*
|
|
240
|
+
* Mostly useful for tests. Production code should let the GC reclaim
|
|
241
|
+
* entries as nodes become unreachable.
|
|
242
|
+
*/
|
|
243
|
+
clear() {
|
|
244
|
+
// WeakMap has no `clear` in older targets, so we replace the
|
|
245
|
+
// internal references with fresh maps. The old maps become
|
|
246
|
+
// unreachable as soon as no caller holds them.
|
|
247
|
+
this.textSources = new WeakMap();
|
|
248
|
+
this.attrSources = new WeakMap();
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Returns `true` when `text` looks like an Eta template.
|
|
252
|
+
*
|
|
253
|
+
* Delegates to the configured predicate; the default
|
|
254
|
+
* (`defaultContainsEtaTemplate`) requires both an opening `{{` AND a
|
|
255
|
+
* closing `}}`.
|
|
256
|
+
*/
|
|
257
|
+
containsTemplate(text) {
|
|
258
|
+
return this.isTemplateText(text);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Convenience constructor used by the index and the default factory.
|
|
263
|
+
*/
|
|
264
|
+
export function createEtaTemplateSourceRegistry(options) {
|
|
265
|
+
return new EtaTemplateSourceRegistry(options);
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=source-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-registry.js","sourceRoot":"","sources":["../../../src/eta/source-registry.ts"],"names":[],"mappings":"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","sourcesContent":["// src/eta/source-registry.ts\n\n/**\n * Sidecar registry that preserves original template sources across renders.\n *\n * The Eta template subsystem overwrites DOM values during render:\n *\n * <span>{{=it.name}}</span>\n * becomes\n * <span>iPhone</span>\n *\n * Once a render has happened, the original `{{=it.name}}` source is gone\n * from the DOM. If the index is later cleared and re-initialized — or if a\n * mutation observer forwards an external edit — the factory can only see\n * the rendered string, not the template source.\n *\n * The registry stores the original sources keyed by node identity. The\n * factory consults the registry first and only falls back to the DOM\n * value when the registry has no entry.\n *\n * The registry is intentionally generic: it does not know about Eta, scope,\n * products, options, or pricing. It is a small, GC-friendly cache.\n *\n * Lifetime:\n *\n * - Uses `WeakMap` / `Map` keyed by DOM nodes so disconnected nodes can be\n * reclaimed by the GC.\n * - `clear()` resets the registry to empty.\n *\n * **Cross-scope limitation.** A source registry is owned by the\n * index that created it (one registry per `EtaTemplateIndex`\n * instance by default). Moving a rendered template node from one\n * scope to another loses the original template source — the\n * destination index's registry has no record of it, so the\n * factory falls back to the live DOM value, which now holds the\n * rendered string rather than the template literal. The\n * destination index cannot recreate the operation without\n * discovering the new template content externally.\n *\n * If an application reparents rendered template nodes across\n * scopes, the safe options are:\n *\n * 1. Share a single `EtaTemplateSourceRegistry` across the\n * affected indices and have each index pass that shared\n * registry through the `TemplateOperationContext` on every\n * factory call — the factory itself is stateless at the call\n * site, so a single registry can back multiple indices.\n * 2. Accept that moved nodes must be re-discovered by the\n * destination index — for example, by re-emitting the original\n * template literal into the moved node, or by relying on the\n * index's normal `MutationObserver` flow to pick up the new\n * template content.\n *\n * Today the controller does not expose a `sourceRegistry`\n * cross-index option; sharing requires reaching into the index\n * constructor. The v4 review recommends keeping this limitation\n * documented unless an application actually needs cross-scope\n * DOM moves — at which point the index/controller can grow a\n * `sourceRegistry` option that overrides the per-index\n * `createEtaTemplateSourceRegistry()` default.\n */\n\n/**\n * Default predicate used by `EtaTemplateSourceRegistry` to recognise\n * template text.\n *\n * Requires BOTH an opening `{{` AND a closing `}}` so the registry\n * can safely inspect arbitrary DOM values without false positives on\n * stray `{{` markers in plain string literals.\n *\n/**\n * Default predicate used to decide whether a string contains an\n * Eta template token.\n *\n * Requires BOTH an opening `{{` AND a closing `}}` so the\n * predicate does not pay the cost of compiling partial template\n * fragments (e.g. `Hello {{ user` with a typo) or arbitrary\n * DOM values that happen to contain `{{` inside a plain string\n * literal. The empty-string guard short-circuits the\n * `indexOf` calls on falsy input.\n *\n * **Single source of truth.** The factory (`factory.ts`) re-exports\n * this predicate as `defaultIsTemplateText` for backward\n * compatibility; the two names point at the same function so the\n * registry and the factory can never drift apart.\n */\nexport function defaultContainsEtaTemplate(\n text: string\n): boolean {\n if (!text) {\n return false;\n }\n\n return (\n text.indexOf('{{') !== -1 &&\n text.indexOf('}}') !== -1\n );\n}\n\n/**\n * Predicate used to decide whether a string contains a template token.\n *\n * The default (`defaultContainsEtaTemplate`) requires BOTH an opening\n * `{{` AND a closing `}}` so the registry can safely inspect arbitrary\n * DOM values. Callers can plug in a stricter predicate when they want\n * to be even more conservative.\n */\nexport type EtaTemplatePredicate = (text: string) => boolean;\n\n/**\n * Options accepted by `EtaTemplateSourceRegistry`.\n */\nexport interface EtaTemplateSourceRegistryOptions {\n /**\n * Predicate used to recognise template text. Defaults to\n * `defaultContainsEtaTemplate`, which requires both `{{` AND `}}`.\n */\n readonly isTemplateText?: EtaTemplatePredicate;\n}\n\n/**\n * Sidecar storage for original template sources.\n *\n * Reads are tolerant: if no source is preserved for a node, the registry\n * consults the DOM value, and if that contains a template it persists the\n * value so the next read returns the preserved source rather than the\n * (possibly already-rendered) DOM value.\n *\n * **Destructive operations out of scope.** The registry only preserves\n * sources; it does not enumerate attribute names that have been removed\n * from the DOM. Removing an attribute at runtime (e.g. via\n * `element.removeAttribute`) is a destructive operation: the registry\n * has no way to discover a removed attribute name from a live element\n * alone. Once `removeWhenEmpty` was dropped from the renderer, the\n * registry only ever needs to handle attributes that are still present\n * in the DOM, so this limitation is no longer a concern in practice.\n */\nexport class EtaTemplateSourceRegistry {\n private textSources: WeakMap<Text, string> =\n new WeakMap();\n\n private attrSources: WeakMap<\n Element,\n Map<string, string>\n > = new WeakMap();\n\n private readonly isTemplateText: EtaTemplatePredicate;\n\n constructor(\n options: EtaTemplateSourceRegistryOptions = {}\n ) {\n this.isTemplateText =\n options.isTemplateText ??\n defaultContainsEtaTemplate;\n }\n\n /**\n * Returns the preserved source for a text node, falling back to the\n * current DOM value.\n *\n * If the DOM value contains a template and no preserved source exists,\n * the value is persisted so future reads stay stable even after the\n * DOM value changes.\n */\n readTextSource(node: Text): string | undefined {\n const existing = this.textSources.get(node);\n\n if (existing !== undefined) {\n return existing;\n }\n\n const current = node.nodeValue ?? '';\n\n if (!this.containsTemplate(current)) {\n return undefined;\n }\n\n this.textSources.set(node, current);\n\n return current;\n }\n\n /**\n * Sets or clears the preserved source for a text node.\n *\n * Pass `undefined` to remove any preserved source; the next call to\n * `readTextSource` will then fall back to the current DOM value.\n */\n replaceTextSource(\n node: Text,\n source: string | undefined\n ): void {\n if (source === undefined) {\n this.textSources.delete(node);\n return;\n }\n\n this.textSources.set(node, source);\n }\n\n /**\n * Returns the preserved source for a single attribute on an element.\n *\n * If no preserved source exists, the registry consults the current\n * DOM value; when the value contains a template, the registry persists\n * it so future reads stay stable.\n */\n readAttributeSource(\n element: Element,\n name: string\n ): string | undefined {\n const existing =\n this.attrSources.get(element)?.get(name);\n\n if (existing !== undefined) {\n return existing;\n }\n\n const current = element.getAttribute(name);\n\n if (\n current === null ||\n current === undefined ||\n !this.containsTemplate(current)\n ) {\n return undefined;\n }\n\n this.setAttributeSource(element, name, current);\n\n return current;\n }\n\n /**\n * Stores a preserved source for an attribute. Used both internally\n * (after reading a fresh DOM value) and externally (after parsing a\n * template literal that the DOM no longer exposes).\n */\n setAttributeSource(\n element: Element,\n name: string,\n source: string\n ): void {\n let byName = this.attrSources.get(element);\n\n if (!byName) {\n byName = new Map<string, string>();\n this.attrSources.set(element, byName);\n }\n\n byName.set(name, source);\n }\n\n /**\n * Removes a preserved attribute source. No-op if no source exists.\n */\n removeAttributeSource(\n element: Element,\n name: string\n ): void {\n const byName = this.attrSources.get(element);\n\n if (!byName) {\n return;\n }\n\n byName.delete(name);\n\n if (byName.size === 0) {\n this.attrSources.delete(element);\n }\n }\n\n /**\n * Reads the current DOM value for a text node and updates the\n * registry accordingly.\n *\n * Persists the value when it contains a template; clears any\n * previously preserved source otherwise. This is the right call for\n * external mutations where the DOM has been edited and the registry\n * should reflect the new state.\n */\n syncTextSource(node: Text): void {\n const current = node.nodeValue ?? '';\n\n if (this.containsTemplate(current)) {\n this.textSources.set(node, current);\n } else {\n this.textSources.delete(node);\n }\n }\n\n /**\n * Reads the current DOM value for an attribute and updates the\n * registry accordingly.\n *\n * Persists the value when it contains a template; clears any\n * previously preserved source otherwise. The right call for external\n * attribute mutations.\n */\n syncAttributeSource(\n element: Element,\n name: string\n ): void {\n const current = element.getAttribute(name);\n\n if (\n current === null ||\n current === undefined\n ) {\n this.removeAttributeSource(element, name);\n return;\n }\n\n if (this.containsTemplate(current)) {\n this.setAttributeSource(element, name, current);\n } else {\n this.removeAttributeSource(element, name);\n }\n }\n\n /**\n * Wipes every preserved source.\n *\n * Mostly useful for tests. Production code should let the GC reclaim\n * entries as nodes become unreachable.\n */\n clear(): void {\n // WeakMap has no `clear` in older targets, so we replace the\n // internal references with fresh maps. The old maps become\n // unreachable as soon as no caller holds them.\n this.textSources = new WeakMap();\n this.attrSources = new WeakMap();\n }\n\n /**\n * Returns `true` when `text` looks like an Eta template.\n *\n * Delegates to the configured predicate; the default\n * (`defaultContainsEtaTemplate`) requires both an opening `{{` AND a\n * closing `}}`.\n */\n private containsTemplate(text: string): boolean {\n return this.isTemplateText(text);\n }\n}\n\n/**\n * Convenience constructor used by the index and the default factory.\n */\nexport function createEtaTemplateSourceRegistry(\n options?: EtaTemplateSourceRegistryOptions\n): EtaTemplateSourceRegistry {\n return new EtaTemplateSourceRegistry(options);\n}\n"]}
|