assign-gingerly 0.0.57 → 0.0.59
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 +243 -131
- package/assignFrom.js +124 -129
- package/assignFrom.ts +146 -233
- package/assignFromAsync.js +118 -0
- package/assignFromAsync.ts +240 -0
- package/getValues.js +223 -0
- package/getValues.ts +255 -0
- package/handlers/join.ts +1 -1
- package/handlers/lazyLoad.js +2 -2
- package/handlers/lazyLoad.ts +3 -3
- package/handlers/lazyLoadSwitch.ts +1 -1
- package/handlers/manageTemplateList.js +203 -0
- package/handlers/manageTemplateList.ts +240 -0
- package/handlers/microDataJoin.ts +1 -1
- package/index.js +1 -0
- package/index.ts +2 -0
- package/inferredAssignments.js +1 -1
- package/inferredAssignments.ts +2 -2
- package/markerUtils.js +136 -127
- package/package.json +18 -1
- package/processHandlerCommands.js +30 -3
- package/processHandlerCommands.ts +31 -7
- package/resolveValues.js +41 -125
- package/resolveValues.ts +131 -255
- package/transitionHelper.js +11 -5
- package/transitionHelper.ts +11 -5
- package/types/assign-gingerly/types.d.ts +51 -0
- package/waitForSettled.js +57 -0
- package/waitForSettled.ts +65 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builtIns.manageTemplateList handler for assignFrom.
|
|
3
|
+
*
|
|
4
|
+
* Clones a template once per item in an iterable, distributing each item's
|
|
5
|
+
* properties into its clone via assignFrom. Manages the list over time —
|
|
6
|
+
* reconciling by key to add, remove, and update-in-place.
|
|
7
|
+
*
|
|
8
|
+
* This handler is auto-loaded by processHandlerCommands when
|
|
9
|
+
* `do: 'builtIns.manageTemplateList'` is encountered.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* assignFrom(document.body, {
|
|
13
|
+
* '?.querySelector?.tbody =>': {
|
|
14
|
+
* do: 'builtIns.manageTemplateList',
|
|
15
|
+
* resolve: {
|
|
16
|
+
* forEach: '?.rankings',
|
|
17
|
+
* instantiate: 'globalThis://country-ranking',
|
|
18
|
+
* },
|
|
19
|
+
* fromEachItem: {
|
|
20
|
+
* assignToFragment: { '?.querySelector?.tr?.ish': '?.' },
|
|
21
|
+
* withOptions: { withMethods: ['querySelector'], inferredAssignments: true },
|
|
22
|
+
* resolve: { key: '?.rank' }
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* }, { from: vm, withMethods: ['querySelector'], protocols: { globalThis: k => globalThis[k] } });
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { AssignFromHandler } from '../assignFromAsync.js';
|
|
29
|
+
import type { ManageTemplateListResolvedParams } from '../types/assign-gingerly/types.js';
|
|
30
|
+
import { findMarkers, createMarkers, getNodesBetweenMarkers, MARKER_START_PREFIX, MARKER_END } from '../markerUtils.js';
|
|
31
|
+
import { resolveValue } from '../resolveValues.js';
|
|
32
|
+
import { assignFrom } from '../assignFrom.js';
|
|
33
|
+
import { processInferredAssignments } from '../inferredAssignments.js';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* State stored per list instance (keyed by start marker).
|
|
37
|
+
* Tracks the mapping of keys to their cloned DOM nodes.
|
|
38
|
+
*/
|
|
39
|
+
interface ListState {
|
|
40
|
+
/** Map of key value → array of DOM nodes for that item's clone */
|
|
41
|
+
keyToNodes: Map<any, Node[]>;
|
|
42
|
+
/** Ordered array of keys matching current DOM order */
|
|
43
|
+
keyOrder: any[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const listStateMap = new WeakMap<Node, ListState>();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* ManageTemplateListHandler — clones a template per iterable item with keyed reconciliation.
|
|
50
|
+
*/
|
|
51
|
+
export class ManageTemplateListHandler implements AssignFromHandler {
|
|
52
|
+
config: any;
|
|
53
|
+
|
|
54
|
+
constructor(config: any) {
|
|
55
|
+
this.config = config;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async assign(lhsTarget: any, resolvedParams: ManageTemplateListResolvedParams, options?: any): Promise<void> {
|
|
59
|
+
//return;
|
|
60
|
+
const {
|
|
61
|
+
forEach: items,
|
|
62
|
+
instantiate,
|
|
63
|
+
method = 'appendChild',
|
|
64
|
+
forget = false,
|
|
65
|
+
markerName,
|
|
66
|
+
yieldEvery,
|
|
67
|
+
} = resolvedParams;
|
|
68
|
+
|
|
69
|
+
if (!(lhsTarget instanceof Element)) {
|
|
70
|
+
throw new Error('builtIns.manageTemplateList: lhsTarget must be a DOM Element');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!items || typeof items[Symbol.iterator] !== 'function') {
|
|
74
|
+
return; // Nothing to iterate
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const fromEachItem = this.config.fromEachItem;
|
|
78
|
+
const assignToFragment = fromEachItem?.assignToFragment ?? {};
|
|
79
|
+
const withOptions = fromEachItem?.withOptions ?? {};
|
|
80
|
+
const perItemResolve = fromEachItem?.resolve ?? {};
|
|
81
|
+
const keyPath = perItemResolve.key; // e.g., '?.rank'
|
|
82
|
+
|
|
83
|
+
// fromSource config — assigns from the outer `from` (parent VM) to each clone
|
|
84
|
+
const fromSource = this.config.fromSource;
|
|
85
|
+
const sourceAssignToFragment = fromSource?.assignToFragment;
|
|
86
|
+
const sourceWithOptions = fromSource?.withOptions ?? {};
|
|
87
|
+
|
|
88
|
+
// Detect fast path: no assignToFragment patterns, just inferredAssignments
|
|
89
|
+
const hasAssignPatterns = Object.keys(assignToFragment).length > 0;
|
|
90
|
+
const inferredConfig = withOptions.inferredAssignments;
|
|
91
|
+
const useFastPath = !hasAssignPatterns && inferredConfig && !sourceAssignToFragment;
|
|
92
|
+
|
|
93
|
+
const name = markerName ?? getMarkerName(instantiate) ?? 'templateList';
|
|
94
|
+
|
|
95
|
+
// Find or create markers
|
|
96
|
+
let [startMarker, endMarker] = findMarkers(lhsTarget, name);
|
|
97
|
+
if (!startMarker || !endMarker) {
|
|
98
|
+
[startMarker, endMarker] = createMarkers(lhsTarget, name, method);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Get or create list state
|
|
102
|
+
let state = listStateMap.get(startMarker);
|
|
103
|
+
if (!state) {
|
|
104
|
+
state = { keyToNodes: new Map(), keyOrder: [] };
|
|
105
|
+
listStateMap.set(startMarker, state);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Convert iterable to array
|
|
109
|
+
const itemsArray = Array.from(items);
|
|
110
|
+
|
|
111
|
+
// Fast path: use processInferredAssignments directly when possible
|
|
112
|
+
const processInferred = useFastPath ? processInferredAssignments : null;
|
|
113
|
+
const newKeys: any[] = itemsArray.map((item, index) => {
|
|
114
|
+
if (keyPath) {
|
|
115
|
+
return resolveValue(keyPath, item);
|
|
116
|
+
}
|
|
117
|
+
return index; // Positional fallback when no key specified
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Determine what changed
|
|
121
|
+
const oldKeys = new Set(state.keyOrder);
|
|
122
|
+
const newKeySet = new Set(newKeys);
|
|
123
|
+
|
|
124
|
+
// Keys to remove
|
|
125
|
+
for (const oldKey of state.keyOrder) {
|
|
126
|
+
if (!newKeySet.has(oldKey)) {
|
|
127
|
+
const nodes = state.keyToNodes.get(oldKey);
|
|
128
|
+
if (nodes) {
|
|
129
|
+
if (forget) {
|
|
130
|
+
for (const node of nodes) node.parentNode?.removeChild(node);
|
|
131
|
+
} else {
|
|
132
|
+
for (const node of nodes) {
|
|
133
|
+
if (node instanceof Element) node.setAttribute('hidden', '');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
state.keyToNodes.delete(oldKey);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Process items — clone new ones, update existing
|
|
142
|
+
const fragment = document.createDocumentFragment();
|
|
143
|
+
const newKeyToNodes = new Map<any, Node[]>();
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < itemsArray.length; i++) {
|
|
146
|
+
const item = itemsArray[i];
|
|
147
|
+
const key = newKeys[i];
|
|
148
|
+
|
|
149
|
+
if (state.keyToNodes.has(key) && oldKeys.has(key)) {
|
|
150
|
+
// Existing item — update in place
|
|
151
|
+
const existingNodes = state.keyToNodes.get(key)!;
|
|
152
|
+
const rootEl = existingNodes.find(n => n instanceof Element) as Element | undefined;
|
|
153
|
+
if (rootEl) {
|
|
154
|
+
const shouldYield = yieldEvery && i > 0 && i % yieldEvery === 0;
|
|
155
|
+
if (shouldYield) await new Promise(r => setTimeout(r, 0));
|
|
156
|
+
if (processInferred) {
|
|
157
|
+
processInferred(rootEl, item, inferredConfig === true ? { byItemprop: true } : inferredConfig);
|
|
158
|
+
} else {
|
|
159
|
+
assignFrom(rootEl, assignToFragment, { from: item, ...withOptions });
|
|
160
|
+
}
|
|
161
|
+
if (sourceAssignToFragment && options?.from) {
|
|
162
|
+
assignFrom(rootEl, sourceAssignToFragment, { from: options.from, ...sourceWithOptions });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
newKeyToNodes.set(key, existingNodes);
|
|
166
|
+
for (const node of existingNodes) {
|
|
167
|
+
if (node instanceof Element) node.removeAttribute('hidden');
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
// New item — clone template
|
|
171
|
+
let content: DocumentFragment;
|
|
172
|
+
if (instantiate instanceof HTMLTemplateElement) {
|
|
173
|
+
content = instantiate.content.cloneNode(true) as DocumentFragment;
|
|
174
|
+
} else if (instantiate instanceof DocumentFragment) {
|
|
175
|
+
content = instantiate.cloneNode(true) as DocumentFragment;
|
|
176
|
+
} else {
|
|
177
|
+
throw new Error('builtIns.manageTemplateList: instantiate must be an HTMLTemplateElement or DocumentFragment');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const clonedNodes = Array.from(content.childNodes);
|
|
181
|
+
|
|
182
|
+
// Apply per-item assignments to the cloned fragment
|
|
183
|
+
const rootEl = clonedNodes.find(n => n instanceof Element) as Element | undefined;
|
|
184
|
+
if (rootEl) {
|
|
185
|
+
const tempContainer = document.createDocumentFragment();
|
|
186
|
+
tempContainer.appendChild(content);
|
|
187
|
+
|
|
188
|
+
const shouldYield = yieldEvery && i > 0 && i % yieldEvery === 0;
|
|
189
|
+
if (shouldYield) await new Promise(r => setTimeout(r, 0));
|
|
190
|
+
if (processInferred) {
|
|
191
|
+
processInferred(rootEl, item, inferredConfig === true ? { byItemprop: true } : inferredConfig);
|
|
192
|
+
} else {
|
|
193
|
+
assignFrom(rootEl, assignToFragment, { from: item, ...withOptions });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (sourceAssignToFragment && options?.from) {
|
|
197
|
+
assignFrom(rootEl, sourceAssignToFragment, { from: options.from, ...sourceWithOptions });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const node of clonedNodes) {
|
|
202
|
+
fragment.appendChild(node);
|
|
203
|
+
}
|
|
204
|
+
newKeyToNodes.set(key, clonedNodes);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Wait for async rendering in the fragment to settle before committing
|
|
209
|
+
if (fragment.childNodes.length > 0) {
|
|
210
|
+
const waitOpt = resolvedParams.waitForSettled;
|
|
211
|
+
if (waitOpt) {
|
|
212
|
+
const { waitForSettled } = await import('../waitForSettled.js');
|
|
213
|
+
const idleMs = typeof waitOpt === 'object' ? waitOpt.idleMs : 100;
|
|
214
|
+
const timeout = typeof waitOpt === 'object' ? waitOpt.timeout : undefined;
|
|
215
|
+
try {
|
|
216
|
+
await waitForSettled(fragment, idleMs, timeout);
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.warn('builtIns.manageTemplateList:', (e as Error).message, '— inserting fragment anyway');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Insert new fragment before end marker
|
|
223
|
+
endMarker.parentNode!.insertBefore(fragment, endMarker);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Update state
|
|
227
|
+
state.keyToNodes = newKeyToNodes;
|
|
228
|
+
state.keyOrder = newKeys;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get marker name from a template element.
|
|
234
|
+
*/
|
|
235
|
+
function getMarkerName(templateEl: any): string | undefined {
|
|
236
|
+
if (templateEl instanceof HTMLTemplateElement) {
|
|
237
|
+
return templateEl.id || undefined;
|
|
238
|
+
}
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* }, { from: vm, withMethods: ['querySelector'] });
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
import type { AssignFromHandler } from '../
|
|
36
|
+
import type { AssignFromHandler } from '../assignFromAsync.js';
|
|
37
37
|
|
|
38
38
|
const MARKER_START_PREFIX = '?start name="';
|
|
39
39
|
const MARKER_END = '?end';
|
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { buildCSSQuery } from './buildCSSQuery.js';
|
|
|
8
8
|
export { resolveTemplate } from './resolveTemplate.js';
|
|
9
9
|
export { getHost } from './getHost.js';
|
|
10
10
|
export { resolveValues, resolveValue } from './resolveValues.js';
|
|
11
|
+
export { assignFromAsync } from './assignFromAsync.js';
|
|
11
12
|
export { assignFrom } from './assignFrom.js';
|
|
12
13
|
export { assignFeatures, FeaturesRegistry, captureFeatureInitVals, PropertyBag, suggestFeatureInfo, getFeatureInfoSuggestions } from './assignFeatures.js';
|
|
13
14
|
export { installForwarding } from './installForwarding.js';
|
package/index.ts
CHANGED
|
@@ -8,9 +8,11 @@ export {buildCSSQuery} from './buildCSSQuery.js';
|
|
|
8
8
|
export {resolveTemplate} from './resolveTemplate.js';
|
|
9
9
|
export {getHost} from './getHost.js';
|
|
10
10
|
export {resolveValues, resolveValue} from './resolveValues.js';
|
|
11
|
+
export {assignFromAsync} from './assignFromAsync.js';
|
|
11
12
|
export {assignFrom} from './assignFrom.js';
|
|
12
13
|
export {assignFeatures, FeaturesRegistry, captureFeatureInitVals, PropertyBag, suggestFeatureInfo, getFeatureInfoSuggestions} from './assignFeatures.js';
|
|
13
14
|
export {installForwarding} from './installForwarding.js';
|
|
14
15
|
export {defineWithFeatures} from './defineWithFeatures.js';
|
|
15
16
|
export {resolveAndAssignFeatures} from './resolveAndAssignFeatures.js';
|
|
17
|
+
export {} from './handlers/manageTemplateList.js';
|
|
16
18
|
import './object-extension.js';
|
package/inferredAssignments.js
CHANGED
|
@@ -12,7 +12,7 @@ import { Infer } from './inferencer/inferencer.js';
|
|
|
12
12
|
* @param from - The source object containing values to distribute
|
|
13
13
|
* @param config - The inferredAssignments configuration
|
|
14
14
|
*/
|
|
15
|
-
export
|
|
15
|
+
export function processInferredAssignments(target, from, config) {
|
|
16
16
|
if (!(target instanceof Element))
|
|
17
17
|
return;
|
|
18
18
|
if (!from || typeof from !== 'object')
|
package/inferredAssignments.ts
CHANGED
|
@@ -31,11 +31,11 @@ export interface InferredAssignmentsConfig {
|
|
|
31
31
|
* @param from - The source object containing values to distribute
|
|
32
32
|
* @param config - The inferredAssignments configuration
|
|
33
33
|
*/
|
|
34
|
-
export
|
|
34
|
+
export function processInferredAssignments(
|
|
35
35
|
target: any,
|
|
36
36
|
from: any,
|
|
37
37
|
config: InferredAssignmentsConfig
|
|
38
|
-
):
|
|
38
|
+
): void {
|
|
39
39
|
if (!(target instanceof Element)) return;
|
|
40
40
|
if (!from || typeof from !== 'object') return;
|
|
41
41
|
|
package/markerUtils.js
CHANGED
|
@@ -1,127 +1,136 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* markerUtils.
|
|
3
|
-
*
|
|
4
|
-
* Used by lazyLoad, microDataJoin, and future template loop handlers.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
null,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
target.prepend(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Get all nodes between start and end markers.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
export const MARKER_START_PREFIX = '?start name="';
|
|
12
|
+
export const MARKER_END = '?end';
|
|
13
|
+
/**
|
|
14
|
+
* Find existing start/end comment markers in a target element (TreeWalker approach).
|
|
15
|
+
* Searches the subtree of `target` for matching comment nodes.
|
|
16
|
+
*
|
|
17
|
+
* @param target - The element to search within
|
|
18
|
+
* @param name - The marker name to find
|
|
19
|
+
* @returns [startMarker, endMarker] or [null, null] if not found
|
|
20
|
+
*/
|
|
21
|
+
export function findMarkers(target, name) {
|
|
22
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
23
|
+
let startMarker = null;
|
|
24
|
+
let endMarker = null;
|
|
25
|
+
const walker = document.createTreeWalker(target, NodeFilter.SHOW_COMMENT);
|
|
26
|
+
let node;
|
|
27
|
+
while ((node = walker.nextNode())) {
|
|
28
|
+
if (!startMarker && node.data === startText) {
|
|
29
|
+
startMarker = node;
|
|
30
|
+
}
|
|
31
|
+
else if (startMarker && !endMarker && node.data === MARKER_END) {
|
|
32
|
+
endMarker = node;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return [startMarker, endMarker];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Find existing start/end comment markers using XPath (alternative approach).
|
|
40
|
+
* May be faster in large DOMs due to engine-level indexing.
|
|
41
|
+
*
|
|
42
|
+
* @param target - The element to search within
|
|
43
|
+
* @param name - The marker name to find
|
|
44
|
+
* @returns [startMarker, endMarker] or [null, null] if not found
|
|
45
|
+
*/
|
|
46
|
+
export function findMarkersXPath(target, name) {
|
|
47
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
48
|
+
const startResult = document.evaluate(`.//comment()[. = "${startText}"]`, target, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
49
|
+
const startMarker = startResult.singleNodeValue;
|
|
50
|
+
if (!startMarker)
|
|
51
|
+
return [null, null];
|
|
52
|
+
// Find the next sibling comment that is the end marker
|
|
53
|
+
const endResult = document.evaluate(`following-sibling::comment()[. = "${MARKER_END}"][1]`, startMarker, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
54
|
+
const endMarker = endResult.singleNodeValue;
|
|
55
|
+
return [startMarker, endMarker];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create start/end markers and insert them into the target.
|
|
59
|
+
*
|
|
60
|
+
* @param target - The element to insert markers into
|
|
61
|
+
* @param name - The marker name
|
|
62
|
+
* @param method - 'appendChild' (default) or 'prepend'
|
|
63
|
+
* @returns [startMarker, endMarker]
|
|
64
|
+
*/
|
|
65
|
+
export function createMarkers(target, name, method = 'appendChild') {
|
|
66
|
+
const startMarker = document.createComment(`${MARKER_START_PREFIX}${name}"`);
|
|
67
|
+
const endMarker = document.createComment(MARKER_END);
|
|
68
|
+
if (method === 'prepend') {
|
|
69
|
+
target.prepend(endMarker);
|
|
70
|
+
target.prepend(startMarker);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
target.appendChild(startMarker);
|
|
74
|
+
target.appendChild(endMarker);
|
|
75
|
+
}
|
|
76
|
+
return [startMarker, endMarker];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get all nodes between start and end markers.
|
|
80
|
+
*
|
|
81
|
+
* @param start - The start comment marker
|
|
82
|
+
* @param end - The end comment marker
|
|
83
|
+
* @returns Array of nodes between the markers (exclusive of markers themselves)
|
|
84
|
+
*/
|
|
85
|
+
export function getNodesBetweenMarkers(start, end) {
|
|
86
|
+
const nodes = [];
|
|
87
|
+
let current = start.nextSibling;
|
|
88
|
+
while (current && current !== end) {
|
|
89
|
+
nodes.push(current);
|
|
90
|
+
current = current.nextSibling;
|
|
91
|
+
}
|
|
92
|
+
return nodes;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Find existing start/end comment markers among siblings of an anchor element.
|
|
96
|
+
* Used for 'after' insertion mode where markers are siblings, not children.
|
|
97
|
+
*
|
|
98
|
+
* @param anchor - The element after which markers were inserted
|
|
99
|
+
* @param name - The marker name to find
|
|
100
|
+
* @returns [startMarker, endMarker] or [null, null] if not found
|
|
101
|
+
*/
|
|
102
|
+
export function findMarkersSibling(anchor, name) {
|
|
103
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
104
|
+
let startMarker = null;
|
|
105
|
+
let endMarker = null;
|
|
106
|
+
let current = anchor.nextSibling;
|
|
107
|
+
while (current) {
|
|
108
|
+
if (current.nodeType === Node.COMMENT_NODE) {
|
|
109
|
+
const comment = current;
|
|
110
|
+
if (!startMarker && comment.data === startText) {
|
|
111
|
+
startMarker = comment;
|
|
112
|
+
}
|
|
113
|
+
else if (startMarker && !endMarker && comment.data === MARKER_END) {
|
|
114
|
+
endMarker = comment;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
current = current.nextSibling;
|
|
119
|
+
}
|
|
120
|
+
return [startMarker, endMarker];
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Create start/end markers as siblings after an anchor element.
|
|
124
|
+
* Used for 'after' insertion mode.
|
|
125
|
+
*
|
|
126
|
+
* @param anchor - The element to insert markers after
|
|
127
|
+
* @param name - The marker name
|
|
128
|
+
* @returns [startMarker, endMarker]
|
|
129
|
+
*/
|
|
130
|
+
export function createMarkersSibling(anchor, name) {
|
|
131
|
+
const startMarker = document.createComment(`${MARKER_START_PREFIX}${name}"`);
|
|
132
|
+
const endMarker = document.createComment(MARKER_END);
|
|
133
|
+
// Insert after the anchor: anchor → startMarker → endMarker
|
|
134
|
+
anchor.after(startMarker, endMarker);
|
|
135
|
+
return [startMarker, endMarker];
|
|
136
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.59",
|
|
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": {
|
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
"default": "./resolveValues.js",
|
|
63
63
|
"types": "./resolveValues.ts"
|
|
64
64
|
},
|
|
65
|
+
"./getValues.js": {
|
|
66
|
+
"default": "./getValues.js",
|
|
67
|
+
"types": "./getValues.ts"
|
|
68
|
+
},
|
|
65
69
|
"./resolveIdRef.js": {
|
|
66
70
|
"default": "./resolveIdRef.js",
|
|
67
71
|
"types": "./resolveIdRef.ts"
|
|
@@ -98,6 +102,10 @@
|
|
|
98
102
|
"default": "./handlers/microDataJoin.js",
|
|
99
103
|
"types": "./handlers/microDataJoin.ts"
|
|
100
104
|
},
|
|
105
|
+
"./handlers/manageTemplateList.js": {
|
|
106
|
+
"default": "./handlers/manageTemplateList.js",
|
|
107
|
+
"types": "./handlers/manageTemplateList.ts"
|
|
108
|
+
},
|
|
101
109
|
"./paths.js": {
|
|
102
110
|
"default": "./paths.js",
|
|
103
111
|
"types": "./paths.ts"
|
|
@@ -114,6 +122,10 @@
|
|
|
114
122
|
"default": "./markerUtils.js",
|
|
115
123
|
"types": "./markerUtils.ts"
|
|
116
124
|
},
|
|
125
|
+
"./waitForSettled.js": {
|
|
126
|
+
"default": "./waitForSettled.js",
|
|
127
|
+
"types": "./waitForSettled.ts"
|
|
128
|
+
},
|
|
117
129
|
"./inferredAssignments.js": {
|
|
118
130
|
"default": "./inferredAssignments.js",
|
|
119
131
|
"types": "./inferredAssignments.ts"
|
|
@@ -130,6 +142,10 @@
|
|
|
130
142
|
"default": "./inferencer/withScopePerimeter.js",
|
|
131
143
|
"types": "./inferencer/withScopePerimeter.ts"
|
|
132
144
|
},
|
|
145
|
+
"./assignFromAsync.js": {
|
|
146
|
+
"default": "./assignFromAsync.js",
|
|
147
|
+
"types": "./assignFromAsync.ts"
|
|
148
|
+
},
|
|
133
149
|
"./assignFrom.js": {
|
|
134
150
|
"default": "./assignFrom.js",
|
|
135
151
|
"types": "./assignFrom.ts"
|
|
@@ -148,6 +164,7 @@
|
|
|
148
164
|
"scripts": {
|
|
149
165
|
"serve": "node ./node_modules/spa-ssi/serve.js",
|
|
150
166
|
"test": "playwright test",
|
|
167
|
+
"benchmark": "playwright test tests/benchmark.spec.ts --reporter=list --project=chromium",
|
|
151
168
|
"update": "ncu -u && npm install",
|
|
152
169
|
"safari": "npx playwright wk http://localhost:8000",
|
|
153
170
|
"chrome": "npx playwright cr http://localhost:8000"
|