assign-gingerly 0.0.54 → 0.0.56
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 +143 -2
- package/assignFrom.js +37 -18
- package/assignFrom.ts +62 -3
- 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 +186 -0
- package/handlers/lazyLoad.ts +281 -0
- package/handlers/lazyLoadSwitch.js +58 -0
- package/handlers/lazyLoadSwitch.ts +64 -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/markerUtils.js +127 -0
- package/markerUtils.ts +162 -0
- package/package.json +31 -1
- package/paths.js +49 -1
- package/paths.ts +80 -1
- package/processHandlerCommands.js +19 -19
- package/processHandlerCommands.ts +10 -13
- package/resolveIdRef.js +144 -125
- package/resolveIdRef.ts +30 -0
- package/types/assign-gingerly/types.d.ts +87 -0
|
@@ -0,0 +1,281 @@
|
|
|
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 type { LazyLoadResolvedParams, LazyLoadInstantiatedContext } from '../types/assign-gingerly/types.js';
|
|
24
|
+
import { withTransition, ensureHideStyle, DEFAULT_HIDE_CLASS } from '../transitionHelper.js';
|
|
25
|
+
import { findMarkers, createMarkers, getNodesBetweenMarkers, findMarkersSibling, createMarkersSibling, MARKER_START_PREFIX, MARKER_END } from '../markerUtils.js';
|
|
26
|
+
|
|
27
|
+
export type { LazyLoadResolvedParams, LazyLoadInstantiatedContext };
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get the marker name from a template element (uses its id).
|
|
31
|
+
*/
|
|
32
|
+
function getMarkerName(templateEl: any): string {
|
|
33
|
+
if (templateEl instanceof HTMLTemplateElement) {
|
|
34
|
+
return templateEl.id || 'anonymous';
|
|
35
|
+
}
|
|
36
|
+
if (typeof templateEl === 'string') {
|
|
37
|
+
return templateEl;
|
|
38
|
+
}
|
|
39
|
+
return 'anonymous';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* LazyLoadHandler — built-in handler for conditional template instantiation.
|
|
44
|
+
*
|
|
45
|
+
* Exported so it can be subclassed for custom behavior.
|
|
46
|
+
*/
|
|
47
|
+
export class LazyLoadHandler implements AssignFromHandler {
|
|
48
|
+
config: any;
|
|
49
|
+
|
|
50
|
+
constructor(config: any) {
|
|
51
|
+
this.config = config;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async assign(lhsTarget: any, resolvedParams: LazyLoadResolvedParams): Promise<void> {
|
|
55
|
+
const {
|
|
56
|
+
if: condition,
|
|
57
|
+
instantiate,
|
|
58
|
+
method = 'appendChild',
|
|
59
|
+
forget = false,
|
|
60
|
+
transitional = false,
|
|
61
|
+
hideClass = DEFAULT_HIDE_CLASS,
|
|
62
|
+
markerName,
|
|
63
|
+
toggleInert = false,
|
|
64
|
+
toggleDisabled = false,
|
|
65
|
+
} = resolvedParams;
|
|
66
|
+
|
|
67
|
+
if (!(lhsTarget instanceof Element)) {
|
|
68
|
+
throw new Error('builtIns.lazyLoad: lhsTarget must be a DOM Element');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const name = markerName ?? getMarkerName(instantiate) ?? (lhsTarget.id || 'anonymous');
|
|
72
|
+
|
|
73
|
+
// Find or create markers based on method
|
|
74
|
+
let [startMarker, endMarker] = method === 'after'
|
|
75
|
+
? findMarkersSibling(lhsTarget, name)
|
|
76
|
+
: findMarkers(lhsTarget, name);
|
|
77
|
+
|
|
78
|
+
if (condition) {
|
|
79
|
+
// SHOW
|
|
80
|
+
if (startMarker && endMarker) {
|
|
81
|
+
const nodes = getNodesBetweenMarkers(startMarker, endMarker);
|
|
82
|
+
if (nodes.length > 0) {
|
|
83
|
+
// Content exists — show it
|
|
84
|
+
withTransition(startMarker, 'show', transitional, () => {
|
|
85
|
+
this.showNodes(nodes, transitional, hideClass, toggleInert, toggleDisabled);
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
// Content was removed (forget mode) — re-clone
|
|
89
|
+
if (transitional) {
|
|
90
|
+
ensureHideStyle(lhsTarget.getRootNode());
|
|
91
|
+
withTransition(startMarker, 'show', true, () => {
|
|
92
|
+
this.cloneAndInsertSync(instantiate, startMarker!, endMarker!, lhsTarget, resolvedParams);
|
|
93
|
+
});
|
|
94
|
+
} else {
|
|
95
|
+
await this.cloneAndInsert(instantiate, startMarker, endMarker, lhsTarget, resolvedParams);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// No markers — first time. Create markers and clone template.
|
|
100
|
+
if (method === 'after') {
|
|
101
|
+
[startMarker, endMarker] = createMarkersSibling(lhsTarget, name);
|
|
102
|
+
} else {
|
|
103
|
+
[startMarker, endMarker] = createMarkers(lhsTarget, name, method);
|
|
104
|
+
}
|
|
105
|
+
if (transitional) {
|
|
106
|
+
ensureHideStyle(lhsTarget.getRootNode());
|
|
107
|
+
withTransition(startMarker, 'show', true, () => {
|
|
108
|
+
this.cloneAndInsertSync(instantiate, startMarker!, endMarker!, lhsTarget, resolvedParams);
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
await this.cloneAndInsert(instantiate, startMarker, endMarker, lhsTarget, resolvedParams);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
// HIDE or REMOVE
|
|
116
|
+
if (startMarker && endMarker) {
|
|
117
|
+
const nodes = getNodesBetweenMarkers(startMarker, endMarker);
|
|
118
|
+
if (nodes.length === 0) return;
|
|
119
|
+
|
|
120
|
+
if (forget) {
|
|
121
|
+
withTransition(startMarker, 'hide', transitional, () => {
|
|
122
|
+
for (const node of nodes) {
|
|
123
|
+
node.parentNode?.removeChild(node);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
withTransition(startMarker, 'hide', transitional, () => {
|
|
128
|
+
this.hideNodes(nodes, lhsTarget, transitional, hideClass, toggleInert, toggleDisabled);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Show nodes — remove hidden/class and restore inert/disabled state.
|
|
137
|
+
*/
|
|
138
|
+
protected showNodes(
|
|
139
|
+
nodes: Node[],
|
|
140
|
+
transitional: boolean,
|
|
141
|
+
hideClass: string,
|
|
142
|
+
toggleInert: boolean,
|
|
143
|
+
toggleDisabled: boolean
|
|
144
|
+
): void {
|
|
145
|
+
for (const node of nodes) {
|
|
146
|
+
if (node instanceof Element) {
|
|
147
|
+
if (transitional) {
|
|
148
|
+
node.classList.remove(hideClass);
|
|
149
|
+
} else {
|
|
150
|
+
node.removeAttribute('hidden');
|
|
151
|
+
}
|
|
152
|
+
if (toggleInert) {
|
|
153
|
+
node.removeAttribute('inert');
|
|
154
|
+
}
|
|
155
|
+
if (toggleDisabled && 'disabled' in node) {
|
|
156
|
+
(node as any).disabled = false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Hide nodes — add hidden/class and set inert/disabled state.
|
|
164
|
+
*/
|
|
165
|
+
protected hideNodes(
|
|
166
|
+
nodes: Node[],
|
|
167
|
+
lhsTarget: Element,
|
|
168
|
+
transitional: boolean,
|
|
169
|
+
hideClass: string,
|
|
170
|
+
toggleInert: boolean,
|
|
171
|
+
toggleDisabled: boolean
|
|
172
|
+
): void {
|
|
173
|
+
for (const node of nodes) {
|
|
174
|
+
if (node instanceof Element) {
|
|
175
|
+
if (transitional) {
|
|
176
|
+
ensureHideStyle(lhsTarget.getRootNode(), hideClass);
|
|
177
|
+
node.classList.add(hideClass);
|
|
178
|
+
} else {
|
|
179
|
+
node.setAttribute('hidden', '');
|
|
180
|
+
}
|
|
181
|
+
if (toggleInert) {
|
|
182
|
+
node.setAttribute('inert', '');
|
|
183
|
+
}
|
|
184
|
+
if (toggleDisabled && 'disabled' in node) {
|
|
185
|
+
(node as any).disabled = true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Clone a template and insert its content between the markers (synchronous version).
|
|
193
|
+
* Used inside view transition callbacks which must be synchronous.
|
|
194
|
+
* Does not call async hooks (onCloneInserted, onInstantiated).
|
|
195
|
+
*/
|
|
196
|
+
protected cloneAndInsertSync(
|
|
197
|
+
templateEl: any,
|
|
198
|
+
startMarker: Comment,
|
|
199
|
+
endMarker: Comment,
|
|
200
|
+
lhsTarget: Element,
|
|
201
|
+
resolvedParams: LazyLoadResolvedParams
|
|
202
|
+
): Node[] {
|
|
203
|
+
let content: DocumentFragment;
|
|
204
|
+
|
|
205
|
+
if (templateEl instanceof HTMLTemplateElement) {
|
|
206
|
+
content = templateEl.content.cloneNode(true) as DocumentFragment;
|
|
207
|
+
} else if (templateEl instanceof DocumentFragment) {
|
|
208
|
+
content = templateEl.cloneNode(true) as DocumentFragment;
|
|
209
|
+
} else {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`builtIns.lazyLoad: instantiate must resolve to an HTMLTemplateElement or DocumentFragment`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const nodes = Array.from(content.childNodes);
|
|
216
|
+
endMarker.parentNode!.insertBefore(content, endMarker);
|
|
217
|
+
return nodes;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Clone a template and insert its content between the markers.
|
|
222
|
+
* Calls onCloneInserted hook and onInstantiated callback after insertion.
|
|
223
|
+
*/
|
|
224
|
+
protected async cloneAndInsert(
|
|
225
|
+
templateEl: any,
|
|
226
|
+
startMarker: Comment,
|
|
227
|
+
endMarker: Comment,
|
|
228
|
+
lhsTarget: Element,
|
|
229
|
+
resolvedParams: LazyLoadResolvedParams
|
|
230
|
+
): Promise<Node[]> {
|
|
231
|
+
let content: DocumentFragment;
|
|
232
|
+
|
|
233
|
+
if (templateEl instanceof HTMLTemplateElement) {
|
|
234
|
+
content = templateEl.content.cloneNode(true) as DocumentFragment;
|
|
235
|
+
} else if (templateEl instanceof DocumentFragment) {
|
|
236
|
+
content = templateEl.cloneNode(true) as DocumentFragment;
|
|
237
|
+
} else {
|
|
238
|
+
throw new Error(
|
|
239
|
+
`builtIns.lazyLoad: instantiate must resolve to an HTMLTemplateElement or DocumentFragment`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Capture nodes before insertion (childNodes empties after insertBefore)
|
|
244
|
+
const nodes = Array.from(content.childNodes);
|
|
245
|
+
|
|
246
|
+
// Insert before endMarker
|
|
247
|
+
endMarker.parentNode!.insertBefore(content, endMarker);
|
|
248
|
+
|
|
249
|
+
// Call protected hook (for subclass overrides)
|
|
250
|
+
await this.onCloneInserted(nodes, lhsTarget, resolvedParams);
|
|
251
|
+
|
|
252
|
+
// Call onInstantiated callback if provided (resolved from VM)
|
|
253
|
+
if (resolvedParams.onInstantiated && typeof resolvedParams.onInstantiated === 'function') {
|
|
254
|
+
const ctx: LazyLoadInstantiatedContext = {
|
|
255
|
+
nodes,
|
|
256
|
+
target: lhsTarget,
|
|
257
|
+
config: this.config,
|
|
258
|
+
resolvedParams
|
|
259
|
+
};
|
|
260
|
+
await resolvedParams.onInstantiated(ctx);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return nodes;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Hook called after template content is cloned and inserted.
|
|
268
|
+
* Override in subclasses for custom post-clone logic.
|
|
269
|
+
*
|
|
270
|
+
* @param nodes - The inserted child nodes
|
|
271
|
+
* @param lhsTarget - The target element
|
|
272
|
+
* @param resolvedParams - The resolved parameters
|
|
273
|
+
*/
|
|
274
|
+
protected async onCloneInserted(
|
|
275
|
+
nodes: Node[],
|
|
276
|
+
lhsTarget: Element,
|
|
277
|
+
resolvedParams: LazyLoadResolvedParams
|
|
278
|
+
): Promise<void> {
|
|
279
|
+
// No-op by default. Subclasses override.
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -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,64 @@
|
|
|
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
|
+
import type { LazyLoadSwitchResolvedParams } from '../types/assign-gingerly/types.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Evaluate a comparison operation.
|
|
29
|
+
*
|
|
30
|
+
* @param lhs - Left-hand side value
|
|
31
|
+
* @param op - Operator string (default: '===')
|
|
32
|
+
* @param rhs - Right-hand side value
|
|
33
|
+
* @returns Boolean result of the comparison
|
|
34
|
+
*/
|
|
35
|
+
function evaluateOp(lhs: any, op: string, rhs: any): boolean {
|
|
36
|
+
switch (op) {
|
|
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
|
+
case '>=': return lhs >= rhs;
|
|
45
|
+
default:
|
|
46
|
+
throw new Error(`builtIns.lazyLoadSwitch: unsupported operator "${op}"`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* LazyLoadSwitchHandler — comparison-based conditional template instantiation.
|
|
52
|
+
*
|
|
53
|
+
* Extends LazyLoadHandler by evaluating `lhs op rhs` to compute the condition,
|
|
54
|
+
* then delegating all DOM logic to the parent class.
|
|
55
|
+
*/
|
|
56
|
+
export class LazyLoadSwitchHandler extends LazyLoadHandler implements AssignFromHandler {
|
|
57
|
+
|
|
58
|
+
async assign(lhsTarget: any, resolvedParams: LazyLoadSwitchResolvedParams): Promise<void> {
|
|
59
|
+
const { lhs, op = '===', rhs, ...rest } = resolvedParams;
|
|
60
|
+
const condition = evaluateOp(lhs, op, rhs);
|
|
61
|
+
// Delegate to parent with computed condition
|
|
62
|
+
return super.assign(lhsTarget, { ...rest, if: condition });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builtIns.microDataJoin handler for assignFrom.
|
|
3
|
+
*
|
|
4
|
+
* Renders a template array as semantic microdata-annotated DOM elements.
|
|
5
|
+
* Each dynamic value gets an HTML element based on its resolved type,
|
|
6
|
+
* with itemprop set to the property name.
|
|
7
|
+
*
|
|
8
|
+
* This handler is auto-loaded by processHandlerCommands when
|
|
9
|
+
* `do: 'builtIns.microDataJoin'` is encountered.
|
|
10
|
+
*/
|
|
11
|
+
const MARKER_START_PREFIX = '?start name="';
|
|
12
|
+
const MARKER_END = '?end';
|
|
13
|
+
const MARKER_NAME = 'microDataJoin';
|
|
14
|
+
|
|
15
|
+
function findMarkers(target) {
|
|
16
|
+
const startText = `${MARKER_START_PREFIX}${MARKER_NAME}"`;
|
|
17
|
+
let startMarker = null;
|
|
18
|
+
let endMarker = null;
|
|
19
|
+
const walker = document.createTreeWalker(target, NodeFilter.SHOW_COMMENT);
|
|
20
|
+
let node;
|
|
21
|
+
while ((node = walker.nextNode())) {
|
|
22
|
+
if (!startMarker && node.data === startText) {
|
|
23
|
+
startMarker = node;
|
|
24
|
+
} else if (startMarker && !endMarker && node.data === MARKER_END) {
|
|
25
|
+
endMarker = node;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return [startMarker, endMarker];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createMarkers(target) {
|
|
33
|
+
const startMarker = document.createComment(`${MARKER_START_PREFIX}${MARKER_NAME}"`);
|
|
34
|
+
const endMarker = document.createComment(MARKER_END);
|
|
35
|
+
target.appendChild(startMarker);
|
|
36
|
+
target.appendChild(endMarker);
|
|
37
|
+
return [startMarker, endMarker];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getNodesBetweenMarkers(start, end) {
|
|
41
|
+
const nodes = [];
|
|
42
|
+
let current = start.nextSibling;
|
|
43
|
+
while (current && current !== end) {
|
|
44
|
+
nodes.push(current);
|
|
45
|
+
current = current.nextSibling;
|
|
46
|
+
}
|
|
47
|
+
return nodes;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatForDisplay(value) {
|
|
51
|
+
if (value instanceof Date) {
|
|
52
|
+
return value.toLocaleDateString();
|
|
53
|
+
}
|
|
54
|
+
if (typeof value === 'number') {
|
|
55
|
+
return value.toLocaleString();
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === 'boolean') {
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
return String(value ?? '');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatForAttribute(value) {
|
|
64
|
+
if (value instanceof Date) {
|
|
65
|
+
return value.toISOString();
|
|
66
|
+
}
|
|
67
|
+
return String(value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createElementForValue(prop, value) {
|
|
71
|
+
if (value instanceof Date) {
|
|
72
|
+
const el = document.createElement('time');
|
|
73
|
+
el.setAttribute('itemprop', prop);
|
|
74
|
+
el.setAttribute('datetime', formatForAttribute(value));
|
|
75
|
+
el.textContent = formatForDisplay(value);
|
|
76
|
+
return el;
|
|
77
|
+
}
|
|
78
|
+
if (typeof value === 'number') {
|
|
79
|
+
const el = document.createElement('data');
|
|
80
|
+
el.setAttribute('itemprop', prop);
|
|
81
|
+
el.setAttribute('value', formatForAttribute(value));
|
|
82
|
+
el.textContent = formatForDisplay(value);
|
|
83
|
+
return el;
|
|
84
|
+
}
|
|
85
|
+
if (typeof value === 'boolean') {
|
|
86
|
+
const el = document.createElement('data');
|
|
87
|
+
el.setAttribute('itemprop', prop);
|
|
88
|
+
el.setAttribute('value', String(value));
|
|
89
|
+
return el;
|
|
90
|
+
}
|
|
91
|
+
const el = document.createElement('span');
|
|
92
|
+
el.setAttribute('itemprop', prop);
|
|
93
|
+
el.textContent = String(value ?? '');
|
|
94
|
+
return el;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function updateElement(el, prop, value) {
|
|
98
|
+
if (value instanceof Date) {
|
|
99
|
+
el.setAttribute('datetime', formatForAttribute(value));
|
|
100
|
+
el.textContent = formatForDisplay(value);
|
|
101
|
+
} else if (typeof value === 'number') {
|
|
102
|
+
el.setAttribute('value', formatForAttribute(value));
|
|
103
|
+
el.textContent = formatForDisplay(value);
|
|
104
|
+
} else if (typeof value === 'boolean') {
|
|
105
|
+
el.setAttribute('value', String(value));
|
|
106
|
+
} else {
|
|
107
|
+
el.textContent = String(value ?? '');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function processTemplate(template) {
|
|
112
|
+
const result = [];
|
|
113
|
+
for (const item of template) {
|
|
114
|
+
if (Array.isArray(item)) {
|
|
115
|
+
const hasNull = item.some(el =>
|
|
116
|
+
el && typeof el === 'object' && 'prop' in el && el.val == null
|
|
117
|
+
);
|
|
118
|
+
if (hasNull) continue;
|
|
119
|
+
result.push(...processTemplate(item));
|
|
120
|
+
} else if (item == null) {
|
|
121
|
+
continue;
|
|
122
|
+
} else {
|
|
123
|
+
result.push(item);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export class MicroDataJoinHandler {
|
|
130
|
+
config;
|
|
131
|
+
constructor(config) {
|
|
132
|
+
this.config = config;
|
|
133
|
+
}
|
|
134
|
+
async assign(lhsTarget, resolvedParams) {
|
|
135
|
+
const { template } = resolvedParams;
|
|
136
|
+
if (!template || !Array.isArray(template)) {
|
|
137
|
+
throw new Error('builtIns.microDataJoin: resolve.template must be an array');
|
|
138
|
+
}
|
|
139
|
+
if (!(lhsTarget instanceof Element)) {
|
|
140
|
+
throw new Error('builtIns.microDataJoin: lhsTarget must be a DOM Element');
|
|
141
|
+
}
|
|
142
|
+
if (!lhsTarget.hasAttribute('itemscope')) {
|
|
143
|
+
lhsTarget.setAttribute('itemscope', '');
|
|
144
|
+
}
|
|
145
|
+
const processed = processTemplate(template);
|
|
146
|
+
let [startMarker, endMarker] = findMarkers(lhsTarget);
|
|
147
|
+
const isUpdate = startMarker !== null && endMarker !== null;
|
|
148
|
+
if (!isUpdate) {
|
|
149
|
+
[startMarker, endMarker] = createMarkers(lhsTarget);
|
|
150
|
+
}
|
|
151
|
+
if (isUpdate) {
|
|
152
|
+
const existingNodes = getNodesBetweenMarkers(startMarker, endMarker);
|
|
153
|
+
let nodeIdx = 0;
|
|
154
|
+
for (const segment of processed) {
|
|
155
|
+
if (typeof segment === 'string') {
|
|
156
|
+
const node = existingNodes[nodeIdx];
|
|
157
|
+
if (node && node.nodeType === Node.TEXT_NODE) {
|
|
158
|
+
if (node.textContent !== segment) {
|
|
159
|
+
node.textContent = segment;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
nodeIdx++;
|
|
163
|
+
} else {
|
|
164
|
+
const node = existingNodes[nodeIdx];
|
|
165
|
+
if (node && node instanceof Element && node.getAttribute('itemprop') === segment.prop) {
|
|
166
|
+
updateElement(node, segment.prop, segment.val);
|
|
167
|
+
}
|
|
168
|
+
nodeIdx++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
const fragment = document.createDocumentFragment();
|
|
173
|
+
for (const segment of processed) {
|
|
174
|
+
if (typeof segment === 'string') {
|
|
175
|
+
fragment.appendChild(document.createTextNode(segment));
|
|
176
|
+
} else {
|
|
177
|
+
const el = createElementForValue(segment.prop, segment.val);
|
|
178
|
+
fragment.appendChild(el);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
endMarker.parentNode.insertBefore(fragment, endMarker);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|