assign-gingerly 0.0.55 → 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/handlers/lazyLoad.js +48 -74
- package/handlers/lazyLoad.ts +78 -104
- package/handlers/lazyLoadSwitch.ts +2 -1
- package/markerUtils.js +127 -0
- package/markerUtils.ts +162 -0
- package/package.json +5 -1
- package/types/assign-gingerly/types.d.ts +75 -0
package/handlers/lazyLoad.js
CHANGED
|
@@ -19,8 +19,7 @@
|
|
|
19
19
|
* }, { withMethods: ['querySelector'], from: myVM });
|
|
20
20
|
*/
|
|
21
21
|
import { withTransition, ensureHideStyle, DEFAULT_HIDE_CLASS } from '../transitionHelper.js';
|
|
22
|
-
|
|
23
|
-
const MARKER_END = '?end';
|
|
22
|
+
import { findMarkers, createMarkers, getNodesBetweenMarkers, findMarkersSibling, createMarkersSibling } from '../markerUtils.js';
|
|
24
23
|
/**
|
|
25
24
|
* Get the marker name from a template element (uses its id).
|
|
26
25
|
*/
|
|
@@ -33,55 +32,6 @@ function getMarkerName(templateEl) {
|
|
|
33
32
|
}
|
|
34
33
|
return 'anonymous';
|
|
35
34
|
}
|
|
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
35
|
/**
|
|
86
36
|
* LazyLoadHandler — built-in handler for conditional template instantiation.
|
|
87
37
|
*
|
|
@@ -93,26 +43,20 @@ export class LazyLoadHandler {
|
|
|
93
43
|
this.config = config;
|
|
94
44
|
}
|
|
95
45
|
async assign(lhsTarget, resolvedParams) {
|
|
96
|
-
const { if: condition, instantiate, method = 'appendChild', forget = false, transitional = false, hideClass = DEFAULT_HIDE_CLASS } = resolvedParams;
|
|
46
|
+
const { if: condition, instantiate, method = 'appendChild', forget = false, transitional = false, hideClass = DEFAULT_HIDE_CLASS, markerName, toggleInert = false, toggleDisabled = false } = resolvedParams;
|
|
97
47
|
if (!(lhsTarget instanceof Element)) {
|
|
98
48
|
throw new Error('builtIns.lazyLoad: lhsTarget must be a DOM Element');
|
|
99
49
|
}
|
|
100
|
-
const name = getMarkerName(instantiate);
|
|
101
|
-
let [startMarker, endMarker] =
|
|
50
|
+
const name = markerName ?? getMarkerName(instantiate) ?? (lhsTarget.id || 'anonymous');
|
|
51
|
+
let [startMarker, endMarker] = method === 'after'
|
|
52
|
+
? findMarkersSibling(lhsTarget, name)
|
|
53
|
+
: findMarkers(lhsTarget, name);
|
|
102
54
|
if (condition) {
|
|
103
55
|
if (startMarker && endMarker) {
|
|
104
56
|
const nodes = getNodesBetweenMarkers(startMarker, endMarker);
|
|
105
57
|
if (nodes.length > 0) {
|
|
106
58
|
withTransition(startMarker, 'show', transitional, () => {
|
|
107
|
-
|
|
108
|
-
if (node instanceof Element) {
|
|
109
|
-
if (transitional) {
|
|
110
|
-
node.classList.remove(hideClass);
|
|
111
|
-
} else {
|
|
112
|
-
node.removeAttribute('hidden');
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
59
|
+
this.showNodes(nodes, transitional, hideClass, toggleInert, toggleDisabled);
|
|
116
60
|
});
|
|
117
61
|
}
|
|
118
62
|
else {
|
|
@@ -127,7 +71,11 @@ export class LazyLoadHandler {
|
|
|
127
71
|
}
|
|
128
72
|
}
|
|
129
73
|
else {
|
|
130
|
-
|
|
74
|
+
if (method === 'after') {
|
|
75
|
+
[startMarker, endMarker] = createMarkersSibling(lhsTarget, name);
|
|
76
|
+
} else {
|
|
77
|
+
[startMarker, endMarker] = createMarkers(lhsTarget, name, method);
|
|
78
|
+
}
|
|
131
79
|
if (transitional) {
|
|
132
80
|
ensureHideStyle(lhsTarget.getRootNode());
|
|
133
81
|
withTransition(startMarker, 'show', true, () => {
|
|
@@ -151,21 +99,47 @@ export class LazyLoadHandler {
|
|
|
151
99
|
}
|
|
152
100
|
else {
|
|
153
101
|
withTransition(startMarker, 'hide', transitional, () => {
|
|
154
|
-
|
|
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
|
-
}
|
|
102
|
+
this.hideNodes(nodes, lhsTarget, transitional, hideClass, toggleInert, toggleDisabled);
|
|
164
103
|
});
|
|
165
104
|
}
|
|
166
105
|
}
|
|
167
106
|
}
|
|
168
107
|
}
|
|
108
|
+
showNodes(nodes, transitional, hideClass, toggleInert, toggleDisabled) {
|
|
109
|
+
for (const node of nodes) {
|
|
110
|
+
if (node instanceof Element) {
|
|
111
|
+
if (transitional) {
|
|
112
|
+
node.classList.remove(hideClass);
|
|
113
|
+
} else {
|
|
114
|
+
node.removeAttribute('hidden');
|
|
115
|
+
}
|
|
116
|
+
if (toggleInert) {
|
|
117
|
+
node.removeAttribute('inert');
|
|
118
|
+
}
|
|
119
|
+
if (toggleDisabled && 'disabled' in node) {
|
|
120
|
+
node.disabled = false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
hideNodes(nodes, lhsTarget, transitional, hideClass, toggleInert, toggleDisabled) {
|
|
126
|
+
for (const node of nodes) {
|
|
127
|
+
if (node instanceof Element) {
|
|
128
|
+
if (transitional) {
|
|
129
|
+
ensureHideStyle(lhsTarget.getRootNode(), hideClass);
|
|
130
|
+
node.classList.add(hideClass);
|
|
131
|
+
} else {
|
|
132
|
+
node.setAttribute('hidden', '');
|
|
133
|
+
}
|
|
134
|
+
if (toggleInert) {
|
|
135
|
+
node.setAttribute('inert', '');
|
|
136
|
+
}
|
|
137
|
+
if (toggleDisabled && 'disabled' in node) {
|
|
138
|
+
node.disabled = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
169
143
|
cloneAndInsertSync(templateEl, startMarker, endMarker, lhsTarget, resolvedParams) {
|
|
170
144
|
let content;
|
|
171
145
|
if (templateEl instanceof HTMLTemplateElement) {
|
package/handlers/lazyLoad.ts
CHANGED
|
@@ -20,24 +20,11 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import type { AssignFromHandler } from '../assignFrom.js';
|
|
23
|
+
import type { LazyLoadResolvedParams, LazyLoadInstantiatedContext } from '../types/assign-gingerly/types.js';
|
|
23
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';
|
|
24
26
|
|
|
25
|
-
|
|
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
|
-
}
|
|
27
|
+
export type { LazyLoadResolvedParams, LazyLoadInstantiatedContext };
|
|
41
28
|
|
|
42
29
|
/**
|
|
43
30
|
* Get the marker name from a template element (uses its id).
|
|
@@ -52,61 +39,6 @@ function getMarkerName(templateEl: any): string {
|
|
|
52
39
|
return 'anonymous';
|
|
53
40
|
}
|
|
54
41
|
|
|
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
42
|
/**
|
|
111
43
|
* LazyLoadHandler — built-in handler for conditional template instantiation.
|
|
112
44
|
*
|
|
@@ -119,7 +51,7 @@ export class LazyLoadHandler implements AssignFromHandler {
|
|
|
119
51
|
this.config = config;
|
|
120
52
|
}
|
|
121
53
|
|
|
122
|
-
async assign(lhsTarget: any, resolvedParams:
|
|
54
|
+
async assign(lhsTarget: any, resolvedParams: LazyLoadResolvedParams): Promise<void> {
|
|
123
55
|
const {
|
|
124
56
|
if: condition,
|
|
125
57
|
instantiate,
|
|
@@ -127,42 +59,36 @@ export class LazyLoadHandler implements AssignFromHandler {
|
|
|
127
59
|
forget = false,
|
|
128
60
|
transitional = false,
|
|
129
61
|
hideClass = DEFAULT_HIDE_CLASS,
|
|
62
|
+
markerName,
|
|
63
|
+
toggleInert = false,
|
|
64
|
+
toggleDisabled = false,
|
|
130
65
|
} = resolvedParams;
|
|
131
66
|
|
|
132
67
|
if (!(lhsTarget instanceof Element)) {
|
|
133
68
|
throw new Error('builtIns.lazyLoad: lhsTarget must be a DOM Element');
|
|
134
69
|
}
|
|
135
70
|
|
|
136
|
-
const name = getMarkerName(instantiate);
|
|
71
|
+
const name = markerName ?? getMarkerName(instantiate) ?? (lhsTarget.id || 'anonymous');
|
|
137
72
|
|
|
138
|
-
// Find or create markers
|
|
139
|
-
let [startMarker, endMarker] =
|
|
73
|
+
// Find or create markers based on method
|
|
74
|
+
let [startMarker, endMarker] = method === 'after'
|
|
75
|
+
? findMarkersSibling(lhsTarget, name)
|
|
76
|
+
: findMarkers(lhsTarget, name);
|
|
140
77
|
|
|
141
78
|
if (condition) {
|
|
142
79
|
// SHOW
|
|
143
80
|
if (startMarker && endMarker) {
|
|
144
|
-
// Markers exist — check if content is hidden or removed
|
|
145
81
|
const nodes = getNodesBetweenMarkers(startMarker, endMarker);
|
|
146
82
|
if (nodes.length > 0) {
|
|
147
83
|
// Content exists — show it
|
|
148
84
|
withTransition(startMarker, 'show', transitional, () => {
|
|
149
|
-
|
|
150
|
-
if (node instanceof Element) {
|
|
151
|
-
if (transitional) {
|
|
152
|
-
node.classList.remove(hideClass);
|
|
153
|
-
} else {
|
|
154
|
-
node.removeAttribute('hidden');
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
85
|
+
this.showNodes(nodes, transitional, hideClass, toggleInert, toggleDisabled);
|
|
158
86
|
});
|
|
159
87
|
} else {
|
|
160
88
|
// Content was removed (forget mode) — re-clone
|
|
161
89
|
if (transitional) {
|
|
162
90
|
ensureHideStyle(lhsTarget.getRootNode());
|
|
163
91
|
withTransition(startMarker, 'show', true, () => {
|
|
164
|
-
// cloneAndInsert is async but the transition callback is sync
|
|
165
|
-
// For transitions with clone, we insert synchronously
|
|
166
92
|
this.cloneAndInsertSync(instantiate, startMarker!, endMarker!, lhsTarget, resolvedParams);
|
|
167
93
|
});
|
|
168
94
|
} else {
|
|
@@ -171,7 +97,11 @@ export class LazyLoadHandler implements AssignFromHandler {
|
|
|
171
97
|
}
|
|
172
98
|
} else {
|
|
173
99
|
// No markers — first time. Create markers and clone template.
|
|
174
|
-
|
|
100
|
+
if (method === 'after') {
|
|
101
|
+
[startMarker, endMarker] = createMarkersSibling(lhsTarget, name);
|
|
102
|
+
} else {
|
|
103
|
+
[startMarker, endMarker] = createMarkers(lhsTarget, name, method);
|
|
104
|
+
}
|
|
175
105
|
if (transitional) {
|
|
176
106
|
ensureHideStyle(lhsTarget.getRootNode());
|
|
177
107
|
withTransition(startMarker, 'show', true, () => {
|
|
@@ -188,29 +118,73 @@ export class LazyLoadHandler implements AssignFromHandler {
|
|
|
188
118
|
if (nodes.length === 0) return;
|
|
189
119
|
|
|
190
120
|
if (forget) {
|
|
191
|
-
// Remove nodes entirely (markers persist for re-insertion)
|
|
192
121
|
withTransition(startMarker, 'hide', transitional, () => {
|
|
193
122
|
for (const node of nodes) {
|
|
194
123
|
node.parentNode?.removeChild(node);
|
|
195
124
|
}
|
|
196
125
|
});
|
|
197
126
|
} else {
|
|
198
|
-
// Hide nodes
|
|
199
127
|
withTransition(startMarker, 'hide', transitional, () => {
|
|
200
|
-
|
|
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
|
-
}
|
|
128
|
+
this.hideNodes(nodes, lhsTarget, transitional, hideClass, toggleInert, toggleDisabled);
|
|
210
129
|
});
|
|
211
130
|
}
|
|
212
131
|
}
|
|
213
|
-
|
|
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
|
+
}
|
|
214
188
|
}
|
|
215
189
|
}
|
|
216
190
|
|
|
@@ -224,7 +198,7 @@ export class LazyLoadHandler implements AssignFromHandler {
|
|
|
224
198
|
startMarker: Comment,
|
|
225
199
|
endMarker: Comment,
|
|
226
200
|
lhsTarget: Element,
|
|
227
|
-
resolvedParams:
|
|
201
|
+
resolvedParams: LazyLoadResolvedParams
|
|
228
202
|
): Node[] {
|
|
229
203
|
let content: DocumentFragment;
|
|
230
204
|
|
|
@@ -252,7 +226,7 @@ export class LazyLoadHandler implements AssignFromHandler {
|
|
|
252
226
|
startMarker: Comment,
|
|
253
227
|
endMarker: Comment,
|
|
254
228
|
lhsTarget: Element,
|
|
255
|
-
resolvedParams:
|
|
229
|
+
resolvedParams: LazyLoadResolvedParams
|
|
256
230
|
): Promise<Node[]> {
|
|
257
231
|
let content: DocumentFragment;
|
|
258
232
|
|
|
@@ -300,7 +274,7 @@ export class LazyLoadHandler implements AssignFromHandler {
|
|
|
300
274
|
protected async onCloneInserted(
|
|
301
275
|
nodes: Node[],
|
|
302
276
|
lhsTarget: Element,
|
|
303
|
-
resolvedParams:
|
|
277
|
+
resolvedParams: LazyLoadResolvedParams
|
|
304
278
|
): Promise<void> {
|
|
305
279
|
// No-op by default. Subclasses override.
|
|
306
280
|
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { LazyLoadHandler } from './lazyLoad.js';
|
|
24
24
|
import type { AssignFromHandler } from '../assignFrom.js';
|
|
25
|
+
import type { LazyLoadSwitchResolvedParams } from '../types/assign-gingerly/types.js';
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Evaluate a comparison operation.
|
|
@@ -54,7 +55,7 @@ function evaluateOp(lhs: any, op: string, rhs: any): boolean {
|
|
|
54
55
|
*/
|
|
55
56
|
export class LazyLoadSwitchHandler extends LazyLoadHandler implements AssignFromHandler {
|
|
56
57
|
|
|
57
|
-
async assign(lhsTarget: any, resolvedParams:
|
|
58
|
+
async assign(lhsTarget: any, resolvedParams: LazyLoadSwitchResolvedParams): Promise<void> {
|
|
58
59
|
const { lhs, op = '===', rhs, ...rest } = resolvedParams;
|
|
59
60
|
const condition = evaluateOp(lhs, op, rhs);
|
|
60
61
|
// Delegate to parent with computed condition
|
package/markerUtils.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* markerUtils.js — Shared utilities for comment marker management.
|
|
3
|
+
*
|
|
4
|
+
* Used by lazyLoad, microDataJoin, and future template loop handlers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const MARKER_START_PREFIX = '?start name="';
|
|
8
|
+
export const MARKER_END = '?end';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Find existing start/end comment markers (TreeWalker approach).
|
|
12
|
+
*/
|
|
13
|
+
export function findMarkers(target, name) {
|
|
14
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
15
|
+
let startMarker = null;
|
|
16
|
+
let endMarker = null;
|
|
17
|
+
|
|
18
|
+
const walker = document.createTreeWalker(target, NodeFilter.SHOW_COMMENT);
|
|
19
|
+
let node;
|
|
20
|
+
while ((node = walker.nextNode())) {
|
|
21
|
+
if (!startMarker && node.data === startText) {
|
|
22
|
+
startMarker = node;
|
|
23
|
+
} else if (startMarker && !endMarker && node.data === MARKER_END) {
|
|
24
|
+
endMarker = node;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return [startMarker, endMarker];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Find existing start/end comment markers (XPath approach).
|
|
34
|
+
*/
|
|
35
|
+
export function findMarkersXPath(target, name) {
|
|
36
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
37
|
+
|
|
38
|
+
const startResult = document.evaluate(
|
|
39
|
+
`.//comment()[. = "${startText}"]`,
|
|
40
|
+
target,
|
|
41
|
+
null,
|
|
42
|
+
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
43
|
+
null
|
|
44
|
+
);
|
|
45
|
+
const startMarker = startResult.singleNodeValue;
|
|
46
|
+
if (!startMarker) return [null, null];
|
|
47
|
+
|
|
48
|
+
const endResult = document.evaluate(
|
|
49
|
+
`following-sibling::comment()[. = "${MARKER_END}"][1]`,
|
|
50
|
+
startMarker,
|
|
51
|
+
null,
|
|
52
|
+
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
53
|
+
null
|
|
54
|
+
);
|
|
55
|
+
const endMarker = endResult.singleNodeValue;
|
|
56
|
+
|
|
57
|
+
return [startMarker, endMarker];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create start/end markers and insert them into the target.
|
|
62
|
+
*/
|
|
63
|
+
export function createMarkers(target, name, method = 'appendChild') {
|
|
64
|
+
const startMarker = document.createComment(`${MARKER_START_PREFIX}${name}"`);
|
|
65
|
+
const endMarker = document.createComment(MARKER_END);
|
|
66
|
+
|
|
67
|
+
if (method === 'prepend') {
|
|
68
|
+
target.prepend(endMarker);
|
|
69
|
+
target.prepend(startMarker);
|
|
70
|
+
} else {
|
|
71
|
+
target.appendChild(startMarker);
|
|
72
|
+
target.appendChild(endMarker);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return [startMarker, endMarker];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get all nodes between start and end markers.
|
|
80
|
+
*/
|
|
81
|
+
export function getNodesBetweenMarkers(start, end) {
|
|
82
|
+
const nodes = [];
|
|
83
|
+
let current = start.nextSibling;
|
|
84
|
+
while (current && current !== end) {
|
|
85
|
+
nodes.push(current);
|
|
86
|
+
current = current.nextSibling;
|
|
87
|
+
}
|
|
88
|
+
return nodes;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Find existing start/end comment markers among siblings of an anchor element.
|
|
93
|
+
* Used for 'after' insertion mode.
|
|
94
|
+
*/
|
|
95
|
+
export function findMarkersSibling(anchor, name) {
|
|
96
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
97
|
+
let startMarker = null;
|
|
98
|
+
let endMarker = null;
|
|
99
|
+
|
|
100
|
+
let current = anchor.nextSibling;
|
|
101
|
+
while (current) {
|
|
102
|
+
if (current.nodeType === Node.COMMENT_NODE) {
|
|
103
|
+
if (!startMarker && current.data === startText) {
|
|
104
|
+
startMarker = current;
|
|
105
|
+
} else if (startMarker && !endMarker && current.data === MARKER_END) {
|
|
106
|
+
endMarker = current;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
current = current.nextSibling;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return [startMarker, endMarker];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create start/end markers as siblings after an anchor element.
|
|
118
|
+
* Used for 'after' insertion mode.
|
|
119
|
+
*/
|
|
120
|
+
export function createMarkersSibling(anchor, name) {
|
|
121
|
+
const startMarker = document.createComment(`${MARKER_START_PREFIX}${name}"`);
|
|
122
|
+
const endMarker = document.createComment(MARKER_END);
|
|
123
|
+
|
|
124
|
+
anchor.after(startMarker, endMarker);
|
|
125
|
+
|
|
126
|
+
return [startMarker, endMarker];
|
|
127
|
+
}
|
package/markerUtils.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* markerUtils.ts — Shared utilities for comment marker management.
|
|
3
|
+
*
|
|
4
|
+
* Used by lazyLoad, microDataJoin, and future template loop handlers.
|
|
5
|
+
* Provides finding, creating, and traversing comment marker pairs.
|
|
6
|
+
*
|
|
7
|
+
* Markers are HTML comment nodes with specific content:
|
|
8
|
+
* - Start: <!--?start name="markerName"-->
|
|
9
|
+
* - End: <!--?end-->
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const MARKER_START_PREFIX = '?start name="';
|
|
13
|
+
export const MARKER_END = '?end';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Find existing start/end comment markers in a target element (TreeWalker approach).
|
|
17
|
+
* Searches the subtree of `target` for matching comment nodes.
|
|
18
|
+
*
|
|
19
|
+
* @param target - The element to search within
|
|
20
|
+
* @param name - The marker name to find
|
|
21
|
+
* @returns [startMarker, endMarker] or [null, null] if not found
|
|
22
|
+
*/
|
|
23
|
+
export function findMarkers(target: Element | Node, name: string): [Comment | null, Comment | null] {
|
|
24
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
25
|
+
let startMarker: Comment | null = null;
|
|
26
|
+
let endMarker: Comment | null = null;
|
|
27
|
+
|
|
28
|
+
const walker = document.createTreeWalker(target as Node, NodeFilter.SHOW_COMMENT);
|
|
29
|
+
let node: Comment | null;
|
|
30
|
+
while ((node = walker.nextNode() as Comment | null)) {
|
|
31
|
+
if (!startMarker && node.data === startText) {
|
|
32
|
+
startMarker = node;
|
|
33
|
+
} else if (startMarker && !endMarker && node.data === MARKER_END) {
|
|
34
|
+
endMarker = node;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return [startMarker, endMarker];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Find existing start/end comment markers using XPath (alternative approach).
|
|
44
|
+
* May be faster in large DOMs due to engine-level indexing.
|
|
45
|
+
*
|
|
46
|
+
* @param target - The element to search within
|
|
47
|
+
* @param name - The marker name to find
|
|
48
|
+
* @returns [startMarker, endMarker] or [null, null] if not found
|
|
49
|
+
*/
|
|
50
|
+
export function findMarkersXPath(target: Element | Node, name: string): [Comment | null, Comment | null] {
|
|
51
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
52
|
+
|
|
53
|
+
const startResult = document.evaluate(
|
|
54
|
+
`.//comment()[. = "${startText}"]`,
|
|
55
|
+
target,
|
|
56
|
+
null,
|
|
57
|
+
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
58
|
+
null
|
|
59
|
+
);
|
|
60
|
+
const startMarker = startResult.singleNodeValue as Comment | null;
|
|
61
|
+
if (!startMarker) return [null, null];
|
|
62
|
+
|
|
63
|
+
// Find the next sibling comment that is the end marker
|
|
64
|
+
const endResult = document.evaluate(
|
|
65
|
+
`following-sibling::comment()[. = "${MARKER_END}"][1]`,
|
|
66
|
+
startMarker,
|
|
67
|
+
null,
|
|
68
|
+
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
|
69
|
+
null
|
|
70
|
+
);
|
|
71
|
+
const endMarker = endResult.singleNodeValue as Comment | null;
|
|
72
|
+
|
|
73
|
+
return [startMarker, endMarker];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create start/end markers and insert them into the target.
|
|
78
|
+
*
|
|
79
|
+
* @param target - The element to insert markers into
|
|
80
|
+
* @param name - The marker name
|
|
81
|
+
* @param method - 'appendChild' (default) or 'prepend'
|
|
82
|
+
* @returns [startMarker, endMarker]
|
|
83
|
+
*/
|
|
84
|
+
export function createMarkers(target: Element, name: string, method: string = 'appendChild'): [Comment, Comment] {
|
|
85
|
+
const startMarker = document.createComment(`${MARKER_START_PREFIX}${name}"`);
|
|
86
|
+
const endMarker = document.createComment(MARKER_END);
|
|
87
|
+
|
|
88
|
+
if (method === 'prepend') {
|
|
89
|
+
target.prepend(endMarker);
|
|
90
|
+
target.prepend(startMarker);
|
|
91
|
+
} else {
|
|
92
|
+
target.appendChild(startMarker);
|
|
93
|
+
target.appendChild(endMarker);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return [startMarker, endMarker];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get all nodes between start and end markers.
|
|
101
|
+
*
|
|
102
|
+
* @param start - The start comment marker
|
|
103
|
+
* @param end - The end comment marker
|
|
104
|
+
* @returns Array of nodes between the markers (exclusive of markers themselves)
|
|
105
|
+
*/
|
|
106
|
+
export function getNodesBetweenMarkers(start: Comment, end: Comment): Node[] {
|
|
107
|
+
const nodes: Node[] = [];
|
|
108
|
+
let current: Node | null = start.nextSibling;
|
|
109
|
+
while (current && current !== end) {
|
|
110
|
+
nodes.push(current);
|
|
111
|
+
current = current.nextSibling;
|
|
112
|
+
}
|
|
113
|
+
return nodes;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Find existing start/end comment markers among siblings of an anchor element.
|
|
118
|
+
* Used for 'after' insertion mode where markers are siblings, not children.
|
|
119
|
+
*
|
|
120
|
+
* @param anchor - The element after which markers were inserted
|
|
121
|
+
* @param name - The marker name to find
|
|
122
|
+
* @returns [startMarker, endMarker] or [null, null] if not found
|
|
123
|
+
*/
|
|
124
|
+
export function findMarkersSibling(anchor: Element | Node, name: string): [Comment | null, Comment | null] {
|
|
125
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
126
|
+
let startMarker: Comment | null = null;
|
|
127
|
+
let endMarker: Comment | null = null;
|
|
128
|
+
|
|
129
|
+
let current: Node | null = anchor.nextSibling;
|
|
130
|
+
while (current) {
|
|
131
|
+
if (current.nodeType === Node.COMMENT_NODE) {
|
|
132
|
+
const comment = current as Comment;
|
|
133
|
+
if (!startMarker && comment.data === startText) {
|
|
134
|
+
startMarker = comment;
|
|
135
|
+
} else if (startMarker && !endMarker && comment.data === MARKER_END) {
|
|
136
|
+
endMarker = comment;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
current = current.nextSibling;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return [startMarker, endMarker];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Create start/end markers as siblings after an anchor element.
|
|
148
|
+
* Used for 'after' insertion mode.
|
|
149
|
+
*
|
|
150
|
+
* @param anchor - The element to insert markers after
|
|
151
|
+
* @param name - The marker name
|
|
152
|
+
* @returns [startMarker, endMarker]
|
|
153
|
+
*/
|
|
154
|
+
export function createMarkersSibling(anchor: Element | Node, name: string): [Comment, Comment] {
|
|
155
|
+
const startMarker = document.createComment(`${MARKER_START_PREFIX}${name}"`);
|
|
156
|
+
const endMarker = document.createComment(MARKER_END);
|
|
157
|
+
|
|
158
|
+
// Insert after the anchor: anchor → startMarker → endMarker
|
|
159
|
+
(anchor as ChildNode).after(startMarker, endMarker);
|
|
160
|
+
|
|
161
|
+
return [startMarker, endMarker];
|
|
162
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.56",
|
|
4
4
|
"description": "This package provides a utility function for carefully merging one object into another.",
|
|
5
5
|
"homepage": "https://github.com/bahrus/assign-gingerly#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -110,6 +110,10 @@
|
|
|
110
110
|
"default": "./isAllowedImportPath.js",
|
|
111
111
|
"types": "./isAllowedImportPath.ts"
|
|
112
112
|
},
|
|
113
|
+
"./markerUtils.js": {
|
|
114
|
+
"default": "./markerUtils.js",
|
|
115
|
+
"types": "./markerUtils.ts"
|
|
116
|
+
},
|
|
113
117
|
"./inferredAssignments.js": {
|
|
114
118
|
"default": "./inferredAssignments.js",
|
|
115
119
|
"types": "./inferredAssignments.ts"
|
|
@@ -538,15 +538,90 @@ export interface LazyLoadConfig extends HandlerConfig {
|
|
|
538
538
|
if: string;
|
|
539
539
|
/** Template element to clone (resolved via protocol or path) */
|
|
540
540
|
instantiate: string;
|
|
541
|
+
/** Insert method: 'appendChild' (default), 'prepend', or 'after' (sibling after target) */
|
|
542
|
+
method?: string;
|
|
543
|
+
/** If true, removes nodes when hiding instead of adding hidden attribute */
|
|
544
|
+
forget?: boolean | string;
|
|
545
|
+
/** Enable view transitions */
|
|
546
|
+
transitional?: boolean | string;
|
|
547
|
+
/** CSS class for hiding (default: 'ag-hide', only used when transitional: true) */
|
|
548
|
+
hideClass?: string;
|
|
549
|
+
/** Optional async callback invoked after cloning, resolved from the VM */
|
|
550
|
+
onInstantiated?: string;
|
|
551
|
+
/** Override auto-derived marker name */
|
|
552
|
+
markerName?: string;
|
|
553
|
+
/** Set inert attribute on hidden elements */
|
|
554
|
+
toggleInert?: boolean | string;
|
|
555
|
+
/** Set disabled property on hidden form elements */
|
|
556
|
+
toggleDisabled?: boolean | string;
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Resolved parameters received by LazyLoadHandler.assign() after resolveValues processing.
|
|
562
|
+
*/
|
|
563
|
+
export interface LazyLoadResolvedParams {
|
|
564
|
+
/** Condition — resolved to actual truthy/falsy value */
|
|
565
|
+
if: any;
|
|
566
|
+
/** Template element — resolved to HTMLTemplateElement or DocumentFragment */
|
|
567
|
+
instantiate: HTMLTemplateElement | DocumentFragment;
|
|
568
|
+
/** Insertion method (default: 'appendChild') */
|
|
569
|
+
method?: 'appendChild' | 'prepend' | 'after';
|
|
570
|
+
/** Remove nodes on hide instead of using hidden attribute */
|
|
571
|
+
forget?: boolean;
|
|
572
|
+
/** Enable view transitions */
|
|
573
|
+
transitional?: boolean;
|
|
574
|
+
/** CSS class for hiding (default: 'ag-hide', only used when transitional: true) */
|
|
575
|
+
hideClass?: string;
|
|
576
|
+
/** Callback after clone+insert */
|
|
577
|
+
onInstantiated?: (ctx: LazyLoadInstantiatedContext) => void | Promise<void>;
|
|
578
|
+
/** Override auto-derived marker name */
|
|
579
|
+
markerName?: string;
|
|
580
|
+
/** Set inert attribute on hidden elements (removes from a11y tree + interaction) */
|
|
581
|
+
toggleInert?: boolean;
|
|
582
|
+
/** Set disabled property on hidden form elements */
|
|
583
|
+
toggleDisabled?: boolean;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Configuration for the builtIns.lazyLoadSwitch handler.
|
|
588
|
+
*/
|
|
589
|
+
export interface LazyLoadSwitchConfig extends HandlerConfig {
|
|
590
|
+
do: 'builtIns.lazyLoadSwitch';
|
|
591
|
+
resolve: {
|
|
592
|
+
/** Left-hand side of comparison (resolved from VM) */
|
|
593
|
+
lhs: string;
|
|
594
|
+
/** Comparison operator (default: '===') */
|
|
595
|
+
op?: '===' | '!==' | '==' | '!=' | '<' | '>' | '<=' | '>=';
|
|
596
|
+
/** Right-hand side of comparison (resolved from VM or literal) */
|
|
597
|
+
rhs: string;
|
|
598
|
+
/** Template element to clone (resolved via protocol or path) */
|
|
599
|
+
instantiate: string;
|
|
541
600
|
/** Insert method: 'appendChild' (default) or 'prepend' */
|
|
542
601
|
method?: string;
|
|
543
602
|
/** If true, removes nodes when hiding instead of adding hidden attribute */
|
|
544
603
|
forget?: boolean | string;
|
|
604
|
+
/** Enable view transitions */
|
|
605
|
+
transitional?: boolean | string;
|
|
606
|
+
/** CSS class for hiding (default: 'ag-hide', only used when transitional: true) */
|
|
607
|
+
hideClass?: string;
|
|
545
608
|
/** Optional async callback invoked after cloning, resolved from the VM */
|
|
546
609
|
onInstantiated?: string;
|
|
547
610
|
};
|
|
548
611
|
}
|
|
549
612
|
|
|
613
|
+
/**
|
|
614
|
+
* Resolved parameters received by LazyLoadSwitchHandler.assign() after resolveValues processing.
|
|
615
|
+
*/
|
|
616
|
+
export interface LazyLoadSwitchResolvedParams extends Omit<LazyLoadResolvedParams, 'if'> {
|
|
617
|
+
/** Left-hand side — resolved to actual value */
|
|
618
|
+
lhs: any;
|
|
619
|
+
/** Comparison operator (default: '===') */
|
|
620
|
+
op?: '===' | '!==' | '==' | '!=' | '<' | '>' | '<=' | '>=';
|
|
621
|
+
/** Right-hand side — resolved to actual value */
|
|
622
|
+
rhs: any;
|
|
623
|
+
}
|
|
624
|
+
|
|
550
625
|
/**
|
|
551
626
|
* Context passed to onInstantiated callbacks after template cloning.
|
|
552
627
|
*/
|