assign-gingerly 0.0.53 → 0.0.55
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/README.md +847 -5
- package/assignFrom.js +229 -9
- package/assignFrom.ts +338 -9
- package/assignGingerly.js +34 -1
- package/assignGingerly.ts +54 -1
- package/beVigilant.js +73 -0
- package/beVigilant.ts +85 -0
- package/enhanceAll.js +106 -0
- package/enhanceAll.ts +138 -0
- package/handlers/join.js +74 -0
- package/handlers/join.ts +80 -0
- package/handlers/lazyLoad.js +212 -0
- package/handlers/lazyLoad.ts +307 -0
- package/handlers/lazyLoadSwitch.js +58 -0
- package/handlers/lazyLoadSwitch.ts +63 -0
- package/handlers/microDataJoin.js +184 -0
- package/handlers/microDataJoin.ts +270 -0
- package/inferencer/.gitmodules +3 -0
- package/inferencer/.vscode/settings.json +2 -0
- package/inferencer/InferencedPropagator.js +230 -0
- package/inferencer/InferencedPropagator.ts +269 -0
- package/inferencer/LICENSE +21 -0
- package/inferencer/README.md +524 -0
- package/inferencer/Requirements/SupportForPropagator.md +368 -0
- package/inferencer/imports.html +7 -0
- package/inferencer/inferencer.js +254 -0
- package/inferencer/inferencer.ts +292 -0
- package/inferencer/package-lock.json +129 -0
- package/inferencer/package.json +60 -0
- package/inferencer/playwright-report/data/507ad515125e13390ea07de92f22331c913fa068.md +55 -0
- package/inferencer/playwright-report/index.html +90 -0
- package/inferencer/playwright.config.ts +54 -0
- package/inferencer/test-results/.last-run.json +6 -0
- package/inferencer/test-results/inferencer-Inferencer-Enha-535bc-inferencer-tests-in-browser-chromium/error-context.md +55 -0
- package/inferencer/tests/inferencedPropagator.html +428 -0
- package/inferencer/tests/inferencedPropagator.spec.ts +18 -0
- package/inferencer/tests/inferencer.html +355 -0
- package/inferencer/tests/inferencer.spec.ts +19 -0
- package/inferencer/tsconfig.json +19 -0
- package/inferencer/types/.kiro/specs/conversion-template/README.md +128 -0
- package/inferencer/types/.kiro/specs/conversion-template/design.md +360 -0
- package/inferencer/types/.kiro/specs/conversion-template/requirements.md +191 -0
- package/inferencer/types/.kiro/specs/conversion-template/tasks.md +174 -0
- package/inferencer/types/.kiro/steering/coding-standards.md +53 -0
- package/inferencer/types/.kiro/steering/conversion-guide.md +108 -0
- package/inferencer/types/.kiro/steering/declarative-configuration.md +108 -0
- package/inferencer/types/.kiro/steering/emc-json-serializability.md +306 -0
- package/inferencer/types/EnhancementConversionInstructions.md +1854 -0
- package/inferencer/types/LICENSE +21 -0
- package/inferencer/types/NewCustomElement.md +388 -0
- package/inferencer/types/NewCustomElementFeature.md +683 -0
- package/inferencer/types/NewEnhancementInstructions.md +705 -0
- package/inferencer/types/README.md +2 -0
- package/inferencer/types/agrace/types.d.ts +11 -0
- package/inferencer/types/assign-gingerly/types.d.ts +572 -0
- package/inferencer/types/be-a-beacon/types.d.ts +17 -0
- package/inferencer/types/be-bound/types.d.ts +66 -0
- package/inferencer/types/be-buttoned-up/types.d.ts +19 -0
- package/inferencer/types/be-calculating/types.d.ts +54 -0
- package/inferencer/types/be-clonable/types.d.ts +38 -0
- package/inferencer/types/be-committed/types.d.ts +22 -0
- package/inferencer/types/be-consoling/types.d.ts +24 -0
- package/inferencer/types/be-decked-with/types.d.ts +26 -0
- package/inferencer/types/be-delible/types.d.ts +27 -0
- package/inferencer/types/be-dispatching/types.d.ts +34 -0
- package/inferencer/types/be-evanescent/types.d.ts +20 -0
- package/inferencer/types/be-flashy/types.d.ts +21 -0
- package/inferencer/types/be-gone/types.d.ts +25 -0
- package/inferencer/types/be-observing/types.d.ts +55 -0
- package/inferencer/types/be-reflective/types.d.ts +78 -0
- package/inferencer/types/be-reformable/types.d.ts +49 -0
- package/inferencer/types/be-render-neutral/types.d.ts +32 -0
- package/inferencer/types/be-switched/types.d.ts +146 -0
- package/inferencer/types/be-typed/types.d.ts +32 -0
- package/inferencer/types/be-valued/types.d.ts +22 -0
- package/inferencer/types/data-props/types.d.ts +34 -0
- package/inferencer/types/do-inc/types.d.ts +56 -0
- package/inferencer/types/do-invoke/types.d.ts +38 -0
- package/inferencer/types/do-merge/types.d.ts +28 -0
- package/inferencer/types/do-toggle/types.d.ts +31 -0
- package/inferencer/types/face-up/types.d.ts +100 -0
- package/inferencer/types/fetch-for/types.d.ts +36 -0
- package/inferencer/types/folder-picker/types.d.ts +21 -0
- package/inferencer/types/global.d.ts +29 -0
- package/inferencer/types/id-generation/types.d.ts +26 -0
- package/inferencer/types/inferencer/types.d.ts +46 -0
- package/inferencer/types/mount-observer/types.d.ts +363 -0
- package/inferencer/types/nested-regex-groups/types.d.ts +107 -0
- package/inferencer/types/pipe-in/types.d.ts +52 -0
- package/inferencer/types/roundabout/types.d.ts +268 -0
- package/inferencer/types/soak-up/types.d.ts +40 -0
- package/inferencer/types/templ-maker/types.d.ts +43 -0
- package/inferencer/types/time-ticker/types.d.ts +62 -0
- package/inferencer/types/truth-sourcer/types.d.ts +44 -0
- package/inferencer/upSearch.js +27 -0
- package/inferencer/upSearch.ts +26 -0
- package/inferencer/withScopePerimeter.js +27 -0
- package/inferencer/withScopePerimeter.ts +33 -0
- package/inferredAssignments.js +38 -0
- package/inferredAssignments.ts +65 -0
- package/isAllowedImportPath.js +42 -0
- package/isAllowedImportPath.ts +53 -0
- package/package.json +57 -3
- package/paths.js +231 -0
- package/paths.ts +413 -0
- package/processHandlerCommands.js +188 -0
- package/processHandlerCommands.ts +217 -0
- package/resolveIdRef.js +144 -0
- package/resolveIdRef.ts +170 -0
- package/resolveValues.js +41 -2
- package/resolveValues.ts +41 -1
- package/transitionHelper.js +109 -0
- package/transitionHelper.ts +132 -0
- package/types/assign-gingerly/types.d.ts +89 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builtIns.lazyLoad handler for assignFrom.
|
|
3
|
+
*
|
|
4
|
+
* Conditionally clones a template into a target element, using comment markers
|
|
5
|
+
* to track the inserted content. Supports show/hide and show/remove modes.
|
|
6
|
+
*
|
|
7
|
+
* This handler is auto-loaded by processHandlerCommands when `do: 'builtIns.lazyLoad'`
|
|
8
|
+
* is encountered — no explicit import is needed.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* assignFrom(document.body, {
|
|
12
|
+
* '?.querySelector?..mainView =>': {
|
|
13
|
+
* do: 'builtIns.lazyLoad',
|
|
14
|
+
* resolve: {
|
|
15
|
+
* if: '?.isHappy',
|
|
16
|
+
* instantiate: 'globalThis://happyMood',
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* }, { withMethods: ['querySelector'], from: myVM });
|
|
20
|
+
*/
|
|
21
|
+
import { withTransition, ensureHideStyle, DEFAULT_HIDE_CLASS } from '../transitionHelper.js';
|
|
22
|
+
const MARKER_START_PREFIX = '?start name="';
|
|
23
|
+
const MARKER_END = '?end';
|
|
24
|
+
/**
|
|
25
|
+
* Get the marker name from a template element (uses its id).
|
|
26
|
+
*/
|
|
27
|
+
function getMarkerName(templateEl) {
|
|
28
|
+
if (templateEl instanceof HTMLTemplateElement) {
|
|
29
|
+
return templateEl.id || 'anonymous';
|
|
30
|
+
}
|
|
31
|
+
if (typeof templateEl === 'string') {
|
|
32
|
+
return templateEl;
|
|
33
|
+
}
|
|
34
|
+
return 'anonymous';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Find existing start/end comment markers in a target element.
|
|
38
|
+
* Returns [startMarker, endMarker] or [null, null] if not found.
|
|
39
|
+
*/
|
|
40
|
+
function findMarkers(target, name) {
|
|
41
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
42
|
+
let startMarker = null;
|
|
43
|
+
let endMarker = null;
|
|
44
|
+
const walker = document.createTreeWalker(target, NodeFilter.SHOW_COMMENT);
|
|
45
|
+
let node;
|
|
46
|
+
while ((node = walker.nextNode())) {
|
|
47
|
+
if (!startMarker && node.data === startText) {
|
|
48
|
+
startMarker = node;
|
|
49
|
+
}
|
|
50
|
+
else if (startMarker && !endMarker && node.data === MARKER_END) {
|
|
51
|
+
endMarker = node;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return [startMarker, endMarker];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create start/end markers and insert them into the target.
|
|
59
|
+
*/
|
|
60
|
+
function createMarkers(target, name, method) {
|
|
61
|
+
const startMarker = document.createComment(`${MARKER_START_PREFIX}${name}"`);
|
|
62
|
+
const endMarker = document.createComment(MARKER_END);
|
|
63
|
+
if (method === 'prepend') {
|
|
64
|
+
target.prepend(endMarker);
|
|
65
|
+
target.prepend(startMarker);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
target.appendChild(startMarker);
|
|
69
|
+
target.appendChild(endMarker);
|
|
70
|
+
}
|
|
71
|
+
return [startMarker, endMarker];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get all nodes between start and end markers.
|
|
75
|
+
*/
|
|
76
|
+
function getNodesBetweenMarkers(start, end) {
|
|
77
|
+
const nodes = [];
|
|
78
|
+
let current = start.nextSibling;
|
|
79
|
+
while (current && current !== end) {
|
|
80
|
+
nodes.push(current);
|
|
81
|
+
current = current.nextSibling;
|
|
82
|
+
}
|
|
83
|
+
return nodes;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* LazyLoadHandler — built-in handler for conditional template instantiation.
|
|
87
|
+
*
|
|
88
|
+
* Exported so it can be subclassed for custom behavior.
|
|
89
|
+
*/
|
|
90
|
+
export class LazyLoadHandler {
|
|
91
|
+
config;
|
|
92
|
+
constructor(config) {
|
|
93
|
+
this.config = config;
|
|
94
|
+
}
|
|
95
|
+
async assign(lhsTarget, resolvedParams) {
|
|
96
|
+
const { if: condition, instantiate, method = 'appendChild', forget = false, transitional = false, hideClass = DEFAULT_HIDE_CLASS } = resolvedParams;
|
|
97
|
+
if (!(lhsTarget instanceof Element)) {
|
|
98
|
+
throw new Error('builtIns.lazyLoad: lhsTarget must be a DOM Element');
|
|
99
|
+
}
|
|
100
|
+
const name = getMarkerName(instantiate);
|
|
101
|
+
let [startMarker, endMarker] = findMarkers(lhsTarget, name);
|
|
102
|
+
if (condition) {
|
|
103
|
+
if (startMarker && endMarker) {
|
|
104
|
+
const nodes = getNodesBetweenMarkers(startMarker, endMarker);
|
|
105
|
+
if (nodes.length > 0) {
|
|
106
|
+
withTransition(startMarker, 'show', transitional, () => {
|
|
107
|
+
for (const node of nodes) {
|
|
108
|
+
if (node instanceof Element) {
|
|
109
|
+
if (transitional) {
|
|
110
|
+
node.classList.remove(hideClass);
|
|
111
|
+
} else {
|
|
112
|
+
node.removeAttribute('hidden');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
if (transitional) {
|
|
120
|
+
ensureHideStyle(lhsTarget.getRootNode());
|
|
121
|
+
withTransition(startMarker, 'show', true, () => {
|
|
122
|
+
this.cloneAndInsertSync(instantiate, startMarker, endMarker, lhsTarget, resolvedParams);
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
await this.cloneAndInsert(instantiate, startMarker, endMarker, lhsTarget, resolvedParams);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
[startMarker, endMarker] = createMarkers(lhsTarget, name, method);
|
|
131
|
+
if (transitional) {
|
|
132
|
+
ensureHideStyle(lhsTarget.getRootNode());
|
|
133
|
+
withTransition(startMarker, 'show', true, () => {
|
|
134
|
+
this.cloneAndInsertSync(instantiate, startMarker, endMarker, lhsTarget, resolvedParams);
|
|
135
|
+
});
|
|
136
|
+
} else {
|
|
137
|
+
await this.cloneAndInsert(instantiate, startMarker, endMarker, lhsTarget, resolvedParams);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
if (startMarker && endMarker) {
|
|
143
|
+
const nodes = getNodesBetweenMarkers(startMarker, endMarker);
|
|
144
|
+
if (nodes.length === 0) return;
|
|
145
|
+
if (forget) {
|
|
146
|
+
withTransition(startMarker, 'hide', transitional, () => {
|
|
147
|
+
for (const node of nodes) {
|
|
148
|
+
node.parentNode?.removeChild(node);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
withTransition(startMarker, 'hide', transitional, () => {
|
|
154
|
+
for (const node of nodes) {
|
|
155
|
+
if (node instanceof Element) {
|
|
156
|
+
if (transitional) {
|
|
157
|
+
ensureHideStyle(lhsTarget.getRootNode(), hideClass);
|
|
158
|
+
node.classList.add(hideClass);
|
|
159
|
+
} else {
|
|
160
|
+
node.setAttribute('hidden', '');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
cloneAndInsertSync(templateEl, startMarker, endMarker, lhsTarget, resolvedParams) {
|
|
170
|
+
let content;
|
|
171
|
+
if (templateEl instanceof HTMLTemplateElement) {
|
|
172
|
+
content = templateEl.content.cloneNode(true);
|
|
173
|
+
}
|
|
174
|
+
else if (templateEl instanceof DocumentFragment) {
|
|
175
|
+
content = templateEl.cloneNode(true);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
throw new Error(`builtIns.lazyLoad: instantiate must resolve to an HTMLTemplateElement or DocumentFragment`);
|
|
179
|
+
}
|
|
180
|
+
const nodes = Array.from(content.childNodes);
|
|
181
|
+
endMarker.parentNode.insertBefore(content, endMarker);
|
|
182
|
+
return nodes;
|
|
183
|
+
}
|
|
184
|
+
async cloneAndInsert(templateEl, startMarker, endMarker, lhsTarget, resolvedParams) {
|
|
185
|
+
let content;
|
|
186
|
+
if (templateEl instanceof HTMLTemplateElement) {
|
|
187
|
+
content = templateEl.content.cloneNode(true);
|
|
188
|
+
}
|
|
189
|
+
else if (templateEl instanceof DocumentFragment) {
|
|
190
|
+
content = templateEl.cloneNode(true);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
throw new Error(`builtIns.lazyLoad: instantiate must resolve to an HTMLTemplateElement or DocumentFragment`);
|
|
194
|
+
}
|
|
195
|
+
const nodes = Array.from(content.childNodes);
|
|
196
|
+
endMarker.parentNode.insertBefore(content, endMarker);
|
|
197
|
+
await this.onCloneInserted(nodes, lhsTarget, resolvedParams);
|
|
198
|
+
if (resolvedParams.onInstantiated && typeof resolvedParams.onInstantiated === 'function') {
|
|
199
|
+
const ctx = {
|
|
200
|
+
nodes,
|
|
201
|
+
target: lhsTarget,
|
|
202
|
+
config: this.config,
|
|
203
|
+
resolvedParams
|
|
204
|
+
};
|
|
205
|
+
await resolvedParams.onInstantiated(ctx);
|
|
206
|
+
}
|
|
207
|
+
return nodes;
|
|
208
|
+
}
|
|
209
|
+
async onCloneInserted(nodes, lhsTarget, resolvedParams) {
|
|
210
|
+
// No-op by default. Subclasses override.
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builtIns.lazyLoad handler for assignFrom.
|
|
3
|
+
*
|
|
4
|
+
* Conditionally clones a template into a target element, using comment markers
|
|
5
|
+
* to track the inserted content. Supports show/hide and show/remove modes.
|
|
6
|
+
*
|
|
7
|
+
* This handler is auto-loaded by processHandlerCommands when `do: 'builtIns.lazyLoad'`
|
|
8
|
+
* is encountered — no explicit import is needed.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* assignFrom(document.body, {
|
|
12
|
+
* '?.querySelector?..mainView =>': {
|
|
13
|
+
* do: 'builtIns.lazyLoad',
|
|
14
|
+
* resolve: {
|
|
15
|
+
* if: '?.isHappy',
|
|
16
|
+
* instantiate: 'globalThis://happyMood',
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* }, { withMethods: ['querySelector'], from: myVM });
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { AssignFromHandler } from '../assignFrom.js';
|
|
23
|
+
import { withTransition, ensureHideStyle, DEFAULT_HIDE_CLASS } from '../transitionHelper.js';
|
|
24
|
+
|
|
25
|
+
const MARKER_START_PREFIX = '?start name="';
|
|
26
|
+
const MARKER_END = '?end';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Context passed to onInstantiated callbacks.
|
|
30
|
+
*/
|
|
31
|
+
export interface LazyLoadInstantiatedContext {
|
|
32
|
+
/** The inserted child nodes */
|
|
33
|
+
nodes: Node[];
|
|
34
|
+
/** The target element containing the markers */
|
|
35
|
+
target: Element;
|
|
36
|
+
/** The full handler config */
|
|
37
|
+
config: any;
|
|
38
|
+
/** The resolved parameters */
|
|
39
|
+
resolvedParams: Record<string, any>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get the marker name from a template element (uses its id).
|
|
44
|
+
*/
|
|
45
|
+
function getMarkerName(templateEl: any): string {
|
|
46
|
+
if (templateEl instanceof HTMLTemplateElement) {
|
|
47
|
+
return templateEl.id || 'anonymous';
|
|
48
|
+
}
|
|
49
|
+
if (typeof templateEl === 'string') {
|
|
50
|
+
return templateEl;
|
|
51
|
+
}
|
|
52
|
+
return 'anonymous';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Find existing start/end comment markers in a target element.
|
|
57
|
+
* Returns [startMarker, endMarker] or [null, null] if not found.
|
|
58
|
+
*/
|
|
59
|
+
function findMarkers(target: Element, name: string): [Comment | null, Comment | null] {
|
|
60
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
61
|
+
let startMarker: Comment | null = null;
|
|
62
|
+
let endMarker: Comment | null = null;
|
|
63
|
+
|
|
64
|
+
const walker = document.createTreeWalker(target, NodeFilter.SHOW_COMMENT);
|
|
65
|
+
let node: Comment | null;
|
|
66
|
+
while ((node = walker.nextNode() as Comment | null)) {
|
|
67
|
+
if (!startMarker && node.data === startText) {
|
|
68
|
+
startMarker = node;
|
|
69
|
+
} else if (startMarker && !endMarker && node.data === MARKER_END) {
|
|
70
|
+
endMarker = node;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return [startMarker, endMarker];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create start/end markers and insert them into the target.
|
|
80
|
+
*/
|
|
81
|
+
function createMarkers(target: Element, name: string, method: string): [Comment, Comment] {
|
|
82
|
+
const startMarker = document.createComment(`${MARKER_START_PREFIX}${name}"`);
|
|
83
|
+
const endMarker = document.createComment(MARKER_END);
|
|
84
|
+
|
|
85
|
+
if (method === 'prepend') {
|
|
86
|
+
target.prepend(endMarker);
|
|
87
|
+
target.prepend(startMarker);
|
|
88
|
+
} else {
|
|
89
|
+
// Default: appendChild
|
|
90
|
+
target.appendChild(startMarker);
|
|
91
|
+
target.appendChild(endMarker);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return [startMarker, endMarker];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get all nodes between start and end markers.
|
|
99
|
+
*/
|
|
100
|
+
function getNodesBetweenMarkers(start: Comment, end: Comment): Node[] {
|
|
101
|
+
const nodes: Node[] = [];
|
|
102
|
+
let current: Node | null = start.nextSibling;
|
|
103
|
+
while (current && current !== end) {
|
|
104
|
+
nodes.push(current);
|
|
105
|
+
current = current.nextSibling;
|
|
106
|
+
}
|
|
107
|
+
return nodes;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* LazyLoadHandler — built-in handler for conditional template instantiation.
|
|
112
|
+
*
|
|
113
|
+
* Exported so it can be subclassed for custom behavior.
|
|
114
|
+
*/
|
|
115
|
+
export class LazyLoadHandler implements AssignFromHandler {
|
|
116
|
+
config: any;
|
|
117
|
+
|
|
118
|
+
constructor(config: any) {
|
|
119
|
+
this.config = config;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async assign(lhsTarget: any, resolvedParams: Record<string, any>): Promise<void> {
|
|
123
|
+
const {
|
|
124
|
+
if: condition,
|
|
125
|
+
instantiate,
|
|
126
|
+
method = 'appendChild',
|
|
127
|
+
forget = false,
|
|
128
|
+
transitional = false,
|
|
129
|
+
hideClass = DEFAULT_HIDE_CLASS,
|
|
130
|
+
} = resolvedParams;
|
|
131
|
+
|
|
132
|
+
if (!(lhsTarget instanceof Element)) {
|
|
133
|
+
throw new Error('builtIns.lazyLoad: lhsTarget must be a DOM Element');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const name = getMarkerName(instantiate);
|
|
137
|
+
|
|
138
|
+
// Find or create markers
|
|
139
|
+
let [startMarker, endMarker] = findMarkers(lhsTarget, name);
|
|
140
|
+
|
|
141
|
+
if (condition) {
|
|
142
|
+
// SHOW
|
|
143
|
+
if (startMarker && endMarker) {
|
|
144
|
+
// Markers exist — check if content is hidden or removed
|
|
145
|
+
const nodes = getNodesBetweenMarkers(startMarker, endMarker);
|
|
146
|
+
if (nodes.length > 0) {
|
|
147
|
+
// Content exists — show it
|
|
148
|
+
withTransition(startMarker, 'show', transitional, () => {
|
|
149
|
+
for (const node of nodes) {
|
|
150
|
+
if (node instanceof Element) {
|
|
151
|
+
if (transitional) {
|
|
152
|
+
node.classList.remove(hideClass);
|
|
153
|
+
} else {
|
|
154
|
+
node.removeAttribute('hidden');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
// Content was removed (forget mode) — re-clone
|
|
161
|
+
if (transitional) {
|
|
162
|
+
ensureHideStyle(lhsTarget.getRootNode());
|
|
163
|
+
withTransition(startMarker, 'show', true, () => {
|
|
164
|
+
// cloneAndInsert is async but the transition callback is sync
|
|
165
|
+
// For transitions with clone, we insert synchronously
|
|
166
|
+
this.cloneAndInsertSync(instantiate, startMarker!, endMarker!, lhsTarget, resolvedParams);
|
|
167
|
+
});
|
|
168
|
+
} else {
|
|
169
|
+
await this.cloneAndInsert(instantiate, startMarker, endMarker, lhsTarget, resolvedParams);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
// No markers — first time. Create markers and clone template.
|
|
174
|
+
[startMarker, endMarker] = createMarkers(lhsTarget, name, method);
|
|
175
|
+
if (transitional) {
|
|
176
|
+
ensureHideStyle(lhsTarget.getRootNode());
|
|
177
|
+
withTransition(startMarker, 'show', true, () => {
|
|
178
|
+
this.cloneAndInsertSync(instantiate, startMarker!, endMarker!, lhsTarget, resolvedParams);
|
|
179
|
+
});
|
|
180
|
+
} else {
|
|
181
|
+
await this.cloneAndInsert(instantiate, startMarker, endMarker, lhsTarget, resolvedParams);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
// HIDE or REMOVE
|
|
186
|
+
if (startMarker && endMarker) {
|
|
187
|
+
const nodes = getNodesBetweenMarkers(startMarker, endMarker);
|
|
188
|
+
if (nodes.length === 0) return;
|
|
189
|
+
|
|
190
|
+
if (forget) {
|
|
191
|
+
// Remove nodes entirely (markers persist for re-insertion)
|
|
192
|
+
withTransition(startMarker, 'hide', transitional, () => {
|
|
193
|
+
for (const node of nodes) {
|
|
194
|
+
node.parentNode?.removeChild(node);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
} else {
|
|
198
|
+
// Hide nodes
|
|
199
|
+
withTransition(startMarker, 'hide', transitional, () => {
|
|
200
|
+
for (const node of nodes) {
|
|
201
|
+
if (node instanceof Element) {
|
|
202
|
+
if (transitional) {
|
|
203
|
+
ensureHideStyle(lhsTarget.getRootNode(), hideClass);
|
|
204
|
+
node.classList.add(hideClass);
|
|
205
|
+
} else {
|
|
206
|
+
node.setAttribute('hidden', '');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// If no markers exist and condition is false, do nothing (never loaded)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Clone a template and insert its content between the markers (synchronous version).
|
|
219
|
+
* Used inside view transition callbacks which must be synchronous.
|
|
220
|
+
* Does not call async hooks (onCloneInserted, onInstantiated).
|
|
221
|
+
*/
|
|
222
|
+
protected cloneAndInsertSync(
|
|
223
|
+
templateEl: any,
|
|
224
|
+
startMarker: Comment,
|
|
225
|
+
endMarker: Comment,
|
|
226
|
+
lhsTarget: Element,
|
|
227
|
+
resolvedParams: Record<string, any>
|
|
228
|
+
): Node[] {
|
|
229
|
+
let content: DocumentFragment;
|
|
230
|
+
|
|
231
|
+
if (templateEl instanceof HTMLTemplateElement) {
|
|
232
|
+
content = templateEl.content.cloneNode(true) as DocumentFragment;
|
|
233
|
+
} else if (templateEl instanceof DocumentFragment) {
|
|
234
|
+
content = templateEl.cloneNode(true) as DocumentFragment;
|
|
235
|
+
} else {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`builtIns.lazyLoad: instantiate must resolve to an HTMLTemplateElement or DocumentFragment`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const nodes = Array.from(content.childNodes);
|
|
242
|
+
endMarker.parentNode!.insertBefore(content, endMarker);
|
|
243
|
+
return nodes;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Clone a template and insert its content between the markers.
|
|
248
|
+
* Calls onCloneInserted hook and onInstantiated callback after insertion.
|
|
249
|
+
*/
|
|
250
|
+
protected async cloneAndInsert(
|
|
251
|
+
templateEl: any,
|
|
252
|
+
startMarker: Comment,
|
|
253
|
+
endMarker: Comment,
|
|
254
|
+
lhsTarget: Element,
|
|
255
|
+
resolvedParams: Record<string, any>
|
|
256
|
+
): Promise<Node[]> {
|
|
257
|
+
let content: DocumentFragment;
|
|
258
|
+
|
|
259
|
+
if (templateEl instanceof HTMLTemplateElement) {
|
|
260
|
+
content = templateEl.content.cloneNode(true) as DocumentFragment;
|
|
261
|
+
} else if (templateEl instanceof DocumentFragment) {
|
|
262
|
+
content = templateEl.cloneNode(true) as DocumentFragment;
|
|
263
|
+
} else {
|
|
264
|
+
throw new Error(
|
|
265
|
+
`builtIns.lazyLoad: instantiate must resolve to an HTMLTemplateElement or DocumentFragment`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Capture nodes before insertion (childNodes empties after insertBefore)
|
|
270
|
+
const nodes = Array.from(content.childNodes);
|
|
271
|
+
|
|
272
|
+
// Insert before endMarker
|
|
273
|
+
endMarker.parentNode!.insertBefore(content, endMarker);
|
|
274
|
+
|
|
275
|
+
// Call protected hook (for subclass overrides)
|
|
276
|
+
await this.onCloneInserted(nodes, lhsTarget, resolvedParams);
|
|
277
|
+
|
|
278
|
+
// Call onInstantiated callback if provided (resolved from VM)
|
|
279
|
+
if (resolvedParams.onInstantiated && typeof resolvedParams.onInstantiated === 'function') {
|
|
280
|
+
const ctx: LazyLoadInstantiatedContext = {
|
|
281
|
+
nodes,
|
|
282
|
+
target: lhsTarget,
|
|
283
|
+
config: this.config,
|
|
284
|
+
resolvedParams
|
|
285
|
+
};
|
|
286
|
+
await resolvedParams.onInstantiated(ctx);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return nodes;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Hook called after template content is cloned and inserted.
|
|
294
|
+
* Override in subclasses for custom post-clone logic.
|
|
295
|
+
*
|
|
296
|
+
* @param nodes - The inserted child nodes
|
|
297
|
+
* @param lhsTarget - The target element
|
|
298
|
+
* @param resolvedParams - The resolved parameters
|
|
299
|
+
*/
|
|
300
|
+
protected async onCloneInserted(
|
|
301
|
+
nodes: Node[],
|
|
302
|
+
lhsTarget: Element,
|
|
303
|
+
resolvedParams: Record<string, any>
|
|
304
|
+
): Promise<void> {
|
|
305
|
+
// No-op by default. Subclasses override.
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builtIns.lazyLoadSwitch handler for assignFrom.
|
|
3
|
+
*
|
|
4
|
+
* Extends LazyLoadHandler with a comparison-based condition instead of a
|
|
5
|
+
* pre-computed boolean. Evaluates `lhs op rhs` to determine whether to
|
|
6
|
+
* show or hide the template content.
|
|
7
|
+
*
|
|
8
|
+
* This handler is auto-loaded by processHandlerCommands when
|
|
9
|
+
* `do: 'builtIns.lazyLoadSwitch'` is encountered — no explicit import is needed.
|
|
10
|
+
*
|
|
11
|
+
* Useful for routing and multi-way conditional rendering where multiple
|
|
12
|
+
* handlers in an array represent different "cases."
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* assignFrom(el, {
|
|
16
|
+
* '?.querySelector?..routerOutlet =>': [
|
|
17
|
+
* { do: 'builtIns.lazyLoadSwitch', resolve: { lhs: '?.route', rhs: 'home', instantiate: 'globalThis://homeView' } },
|
|
18
|
+
* { do: 'builtIns.lazyLoadSwitch', resolve: { lhs: '?.route', rhs: 'settings', instantiate: 'globalThis://settingsView' } },
|
|
19
|
+
* ]
|
|
20
|
+
* }, { withMethods: ['querySelector'], from: vm, protocols: { globalThis: k => globalThis[k] } });
|
|
21
|
+
*/
|
|
22
|
+
import { LazyLoadHandler } from './lazyLoad.js';
|
|
23
|
+
/**
|
|
24
|
+
* Evaluate a comparison operation.
|
|
25
|
+
*
|
|
26
|
+
* @param lhs - Left-hand side value
|
|
27
|
+
* @param op - Operator string (default: '===')
|
|
28
|
+
* @param rhs - Right-hand side value
|
|
29
|
+
* @returns Boolean result of the comparison
|
|
30
|
+
*/
|
|
31
|
+
function evaluateOp(lhs, op, rhs) {
|
|
32
|
+
switch (op) {
|
|
33
|
+
case '===': return lhs === rhs;
|
|
34
|
+
case '!==': return lhs !== rhs;
|
|
35
|
+
case '==': return lhs == rhs;
|
|
36
|
+
case '!=': return lhs != rhs;
|
|
37
|
+
case '<': return lhs < rhs;
|
|
38
|
+
case '>': return lhs > rhs;
|
|
39
|
+
case '<=': return lhs <= rhs;
|
|
40
|
+
case '>=': return lhs >= rhs;
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`builtIns.lazyLoadSwitch: unsupported operator "${op}"`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* LazyLoadSwitchHandler — comparison-based conditional template instantiation.
|
|
47
|
+
*
|
|
48
|
+
* Extends LazyLoadHandler by evaluating `lhs op rhs` to compute the condition,
|
|
49
|
+
* then delegating all DOM logic to the parent class.
|
|
50
|
+
*/
|
|
51
|
+
export class LazyLoadSwitchHandler extends LazyLoadHandler {
|
|
52
|
+
async assign(lhsTarget, resolvedParams) {
|
|
53
|
+
const { lhs, op = '===', rhs, ...rest } = resolvedParams;
|
|
54
|
+
const condition = evaluateOp(lhs, op, rhs);
|
|
55
|
+
// Delegate to parent with computed condition
|
|
56
|
+
return super.assign(lhsTarget, { ...rest, if: condition });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builtIns.lazyLoadSwitch handler for assignFrom.
|
|
3
|
+
*
|
|
4
|
+
* Extends LazyLoadHandler with a comparison-based condition instead of a
|
|
5
|
+
* pre-computed boolean. Evaluates `lhs op rhs` to determine whether to
|
|
6
|
+
* show or hide the template content.
|
|
7
|
+
*
|
|
8
|
+
* This handler is auto-loaded by processHandlerCommands when
|
|
9
|
+
* `do: 'builtIns.lazyLoadSwitch'` is encountered — no explicit import is needed.
|
|
10
|
+
*
|
|
11
|
+
* Useful for routing and multi-way conditional rendering where multiple
|
|
12
|
+
* handlers in an array represent different "cases."
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* assignFrom(el, {
|
|
16
|
+
* '?.querySelector?..routerOutlet =>': [
|
|
17
|
+
* { do: 'builtIns.lazyLoadSwitch', resolve: { lhs: '?.route', rhs: 'home', instantiate: 'globalThis://homeView' } },
|
|
18
|
+
* { do: 'builtIns.lazyLoadSwitch', resolve: { lhs: '?.route', rhs: 'settings', instantiate: 'globalThis://settingsView' } },
|
|
19
|
+
* ]
|
|
20
|
+
* }, { withMethods: ['querySelector'], from: vm, protocols: { globalThis: k => globalThis[k] } });
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { LazyLoadHandler } from './lazyLoad.js';
|
|
24
|
+
import type { AssignFromHandler } from '../assignFrom.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Evaluate a comparison operation.
|
|
28
|
+
*
|
|
29
|
+
* @param lhs - Left-hand side value
|
|
30
|
+
* @param op - Operator string (default: '===')
|
|
31
|
+
* @param rhs - Right-hand side value
|
|
32
|
+
* @returns Boolean result of the comparison
|
|
33
|
+
*/
|
|
34
|
+
function evaluateOp(lhs: any, op: string, rhs: any): boolean {
|
|
35
|
+
switch (op) {
|
|
36
|
+
case '===': return lhs === rhs;
|
|
37
|
+
case '!==': return lhs !== rhs;
|
|
38
|
+
case '==': return lhs == rhs;
|
|
39
|
+
case '!=': return lhs != rhs;
|
|
40
|
+
case '<': return lhs < rhs;
|
|
41
|
+
case '>': return lhs > rhs;
|
|
42
|
+
case '<=': return lhs <= rhs;
|
|
43
|
+
case '>=': return lhs >= rhs;
|
|
44
|
+
default:
|
|
45
|
+
throw new Error(`builtIns.lazyLoadSwitch: unsupported operator "${op}"`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* LazyLoadSwitchHandler — comparison-based conditional template instantiation.
|
|
51
|
+
*
|
|
52
|
+
* Extends LazyLoadHandler by evaluating `lhs op rhs` to compute the condition,
|
|
53
|
+
* then delegating all DOM logic to the parent class.
|
|
54
|
+
*/
|
|
55
|
+
export class LazyLoadSwitchHandler extends LazyLoadHandler implements AssignFromHandler {
|
|
56
|
+
|
|
57
|
+
async assign(lhsTarget: any, resolvedParams: Record<string, any>): Promise<void> {
|
|
58
|
+
const { lhs, op = '===', rhs, ...rest } = resolvedParams;
|
|
59
|
+
const condition = evaluateOp(lhs, op, rhs);
|
|
60
|
+
// Delegate to parent with computed condition
|
|
61
|
+
return super.assign(lhsTarget, { ...rest, if: condition });
|
|
62
|
+
}
|
|
63
|
+
}
|