assign-gingerly 0.0.65 → 0.0.66
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 +26 -8
- package/assignFrom.js +3 -3
- package/assignFrom.ts +6 -5
- package/assignFromAsync.js +3 -3
- package/assignFromAsync.ts +5 -5
- package/assignGingerly.js +52 -10
- package/assignGingerly.ts +53 -10
- package/assignTentatively.js +8 -0
- package/assignTentatively.ts +6 -0
- package/{builtInEmoji.ts → emojis.js} +46 -34
- package/emojis.ts +52 -0
- package/getValues.js +0 -1
- package/getValues.ts +1 -1
- package/handlers/addEventListener.js +153 -0
- package/handlers/addEventListener.ts +178 -0
- package/handlers/lazyLoad.js +3 -3
- package/handlers/lazyLoad.ts +3 -3
- package/handlers/manageTemplateList.js +47 -20
- package/handlers/manageTemplateList.ts +50 -20
- package/inferencer/types/assign-gingerly/types.d.ts +58 -0
- package/package.json +4 -4
- package/types/assign-gingerly/types.d.ts +39 -23
- package/builtInEmoji.js +0 -26
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* handlers/addEventListener.ts — Event binding handler for += operator.
|
|
3
|
+
*
|
|
4
|
+
* Dynamically imported when assignGingerly/assignFrom detects an += with
|
|
5
|
+
* an object RHS containing an `on` property on a DOM Element LHS.
|
|
6
|
+
*
|
|
7
|
+
* Attaches an event listener that executes assign vectors on event fire.
|
|
8
|
+
*/
|
|
9
|
+
import assignGingerly from '../assignGingerly.js';
|
|
10
|
+
/**
|
|
11
|
+
* WeakMap for dedup: Element → Map<key, AbortController>
|
|
12
|
+
*/
|
|
13
|
+
const keyMap = new WeakMap();
|
|
14
|
+
/**
|
|
15
|
+
* Reserved keys that are not shorthand assign patterns.
|
|
16
|
+
*/
|
|
17
|
+
const RESERVED_KEYS = new Set([
|
|
18
|
+
'on', 'get', 'fromLHS', 'fromHost', 'fromEvent', 'fromTarget',
|
|
19
|
+
'toTarget', 'toHost', 'toLHS', 'withOptions', 'toTargetOptions',
|
|
20
|
+
'toHostOptions', 'toLHSOptions', 'dispatch', 'nudge'
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* Extract shorthand keys (implicit toHost) from a vector config.
|
|
24
|
+
* Any key not in RESERVED_KEYS that starts with '?.' or contains an operator is shorthand.
|
|
25
|
+
*/
|
|
26
|
+
function extractShorthand(config) {
|
|
27
|
+
const shorthand = {};
|
|
28
|
+
let hasAny = false;
|
|
29
|
+
for (const key of Object.keys(config)) {
|
|
30
|
+
if (!RESERVED_KEYS.has(key)) {
|
|
31
|
+
shorthand[key] = config[key];
|
|
32
|
+
hasAny = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return hasAny ? shorthand : null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Process a single AssignDispatchVector — execute all toTarget/toHost/toLHS assignments.
|
|
39
|
+
*/
|
|
40
|
+
function processVector(vector, source, target, host, lhs, inheritedOptions, useAssignFrom) {
|
|
41
|
+
const destinations = [
|
|
42
|
+
{ key: 'toTarget', dest: target },
|
|
43
|
+
{ key: 'toHost', dest: host },
|
|
44
|
+
{ key: 'toLHS', dest: lhs },
|
|
45
|
+
];
|
|
46
|
+
for (const { key, dest } of destinations) {
|
|
47
|
+
const pattern = vector[key];
|
|
48
|
+
if (!pattern || Object.keys(pattern).length === 0)
|
|
49
|
+
continue;
|
|
50
|
+
if (useAssignFrom && source != null) {
|
|
51
|
+
// Dynamic import assignFrom on demand (fire-and-forget context, already async)
|
|
52
|
+
import('../assignFrom.js').then(({ assignFrom }) => {
|
|
53
|
+
assignFrom(dest, pattern, { from: source, ...inheritedOptions, ...vector.withOptions });
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
assignGingerly(dest, pattern, inheritedOptions);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Handle shorthand (implicit toHost)
|
|
61
|
+
const shorthand = extractShorthand(vector);
|
|
62
|
+
if (shorthand) {
|
|
63
|
+
if (useAssignFrom && source != null) {
|
|
64
|
+
import('../assignFrom.js').then(({ assignFrom }) => {
|
|
65
|
+
assignFrom(host, shorthand, { from: source, ...inheritedOptions, ...vector.withOptions });
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
assignGingerly(host, shorthand, inheritedOptions);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Attach an event listener based on AddEventListenerConfig.
|
|
75
|
+
*
|
|
76
|
+
* @param lhs - The DOM element to attach the listener to
|
|
77
|
+
* @param config - The event handler configuration
|
|
78
|
+
* @param target - The assignFrom target (first arg)
|
|
79
|
+
* @param host - The options.from (source/view model)
|
|
80
|
+
* @param inheritedOptions - Parent assignFrom options (withMethods, aka, etc.)
|
|
81
|
+
*/
|
|
82
|
+
export function attachEventListener(lhs, config, target, host, inheritedOptions) {
|
|
83
|
+
const { on: eventName, get: getConfig, fromLHS, fromHost, fromTarget, fromEvent, dispatch } = config;
|
|
84
|
+
// Resolve get config values
|
|
85
|
+
const { abortController, key, nudge, options: listenerOptions, stopPropagation, preventDefault, dispatch: getDispatch } = getConfig ?? {};
|
|
86
|
+
// Handle dedup via key
|
|
87
|
+
let controller;
|
|
88
|
+
if (key) {
|
|
89
|
+
let elMap = keyMap.get(lhs);
|
|
90
|
+
if (!elMap) {
|
|
91
|
+
elMap = new Map();
|
|
92
|
+
keyMap.set(lhs, elMap);
|
|
93
|
+
}
|
|
94
|
+
// Abort previous listener with same key
|
|
95
|
+
const prev = elMap.get(key);
|
|
96
|
+
if (prev)
|
|
97
|
+
prev.abort();
|
|
98
|
+
// Create new controller for this key
|
|
99
|
+
controller = new AbortController();
|
|
100
|
+
elMap.set(key, controller);
|
|
101
|
+
}
|
|
102
|
+
else if (abortController instanceof AbortController) {
|
|
103
|
+
controller = abortController;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
controller = new AbortController();
|
|
107
|
+
}
|
|
108
|
+
// Attach the listener
|
|
109
|
+
lhs.addEventListener(eventName, (event) => {
|
|
110
|
+
if (stopPropagation)
|
|
111
|
+
event.stopPropagation();
|
|
112
|
+
if (preventDefault)
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
// Static assignments (no from) — top-level toTarget/toHost/toLHS + shorthand
|
|
115
|
+
processVector(config, null, target, host, lhs, inheritedOptions, false);
|
|
116
|
+
// fromLHS assignments
|
|
117
|
+
if (fromLHS) {
|
|
118
|
+
processVector(fromLHS, lhs, target, host, lhs, inheritedOptions, true);
|
|
119
|
+
}
|
|
120
|
+
// fromHost assignments
|
|
121
|
+
if (fromHost) {
|
|
122
|
+
processVector(fromHost, host, target, host, lhs, inheritedOptions, true);
|
|
123
|
+
}
|
|
124
|
+
// fromTarget assignments
|
|
125
|
+
if (fromTarget) {
|
|
126
|
+
processVector(fromTarget, target, target, host, lhs, inheritedOptions, true);
|
|
127
|
+
}
|
|
128
|
+
// fromEvent assignments
|
|
129
|
+
if (fromEvent) {
|
|
130
|
+
processVector(fromEvent, event, target, host, lhs, inheritedOptions, true);
|
|
131
|
+
}
|
|
132
|
+
// Dispatch custom event if configured
|
|
133
|
+
const dispatchConfig = dispatch || getDispatch;
|
|
134
|
+
if (dispatchConfig && dispatchConfig.type) {
|
|
135
|
+
const EventCtr = (typeof dispatchConfig.eventCtr === 'function'
|
|
136
|
+
? dispatchConfig.eventCtr
|
|
137
|
+
: CustomEvent);
|
|
138
|
+
const evt = new EventCtr(dispatchConfig.type, {
|
|
139
|
+
detail: dispatchConfig.detail,
|
|
140
|
+
bubbles: dispatchConfig.bubbles ?? true,
|
|
141
|
+
cancelable: dispatchConfig.cancelable ?? false,
|
|
142
|
+
composed: dispatchConfig.composed ?? true,
|
|
143
|
+
});
|
|
144
|
+
lhs.dispatchEvent(evt);
|
|
145
|
+
}
|
|
146
|
+
}, { signal: controller.signal, ...listenerOptions });
|
|
147
|
+
// Nudge: remove disabled, add interaction hints
|
|
148
|
+
if (nudge) {
|
|
149
|
+
import('./nudge.js').then(({ nudge: nudgeFn }) => {
|
|
150
|
+
nudgeFn(lhs);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* handlers/addEventListener.ts — Event binding handler for += operator.
|
|
3
|
+
*
|
|
4
|
+
* Dynamically imported when assignGingerly/assignFrom detects an += with
|
|
5
|
+
* an object RHS containing an `on` property on a DOM Element LHS.
|
|
6
|
+
*
|
|
7
|
+
* Attaches an event listener that executes assign vectors on event fire.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import assignGingerly from '../assignGingerly.js';
|
|
11
|
+
import type { AddEventListenerConfig, AssignDispatchVector } from '../types/assign-gingerly/types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* WeakMap for dedup: Element → Map<key, AbortController>
|
|
15
|
+
*/
|
|
16
|
+
const keyMap = new WeakMap<Element, Map<string, AbortController>>();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Reserved keys that are not shorthand assign patterns.
|
|
20
|
+
*/
|
|
21
|
+
const RESERVED_KEYS = new Set([
|
|
22
|
+
'on', 'get', 'fromLHS', 'fromHost', 'fromEvent', 'fromTarget',
|
|
23
|
+
'toTarget', 'toHost', 'toLHS', 'withOptions', 'toTargetOptions',
|
|
24
|
+
'toHostOptions', 'toLHSOptions', 'dispatch', 'nudge'
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract shorthand keys (implicit toHost) from a vector config.
|
|
29
|
+
* Any key not in RESERVED_KEYS that starts with '?.' or contains an operator is shorthand.
|
|
30
|
+
*/
|
|
31
|
+
function extractShorthand(config: Record<string, any>): Record<string, any> | null {
|
|
32
|
+
const shorthand: Record<string, any> = {};
|
|
33
|
+
let hasAny = false;
|
|
34
|
+
for (const key of Object.keys(config)) {
|
|
35
|
+
if (!RESERVED_KEYS.has(key)) {
|
|
36
|
+
shorthand[key] = config[key];
|
|
37
|
+
hasAny = true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return hasAny ? shorthand : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Process a single AssignDispatchVector — execute all toTarget/toHost/toLHS assignments.
|
|
45
|
+
*/
|
|
46
|
+
function processVector(
|
|
47
|
+
vector: AssignDispatchVector,
|
|
48
|
+
source: any,
|
|
49
|
+
target: any,
|
|
50
|
+
host: any,
|
|
51
|
+
lhs: Element,
|
|
52
|
+
inheritedOptions: any,
|
|
53
|
+
useAssignFrom: boolean
|
|
54
|
+
): void {
|
|
55
|
+
const destinations = [
|
|
56
|
+
{ key: 'toTarget' as const, dest: target },
|
|
57
|
+
{ key: 'toHost' as const, dest: host },
|
|
58
|
+
{ key: 'toLHS' as const, dest: lhs },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
for (const { key, dest } of destinations) {
|
|
62
|
+
const pattern = vector[key];
|
|
63
|
+
if (!pattern || Object.keys(pattern).length === 0) continue;
|
|
64
|
+
if (useAssignFrom && source != null) {
|
|
65
|
+
// Dynamic import assignFrom on demand (fire-and-forget context, already async)
|
|
66
|
+
import('../assignFrom.js').then(({ assignFrom }) => {
|
|
67
|
+
assignFrom(dest, pattern, { from: source, ...inheritedOptions, ...vector.withOptions });
|
|
68
|
+
});
|
|
69
|
+
} else {
|
|
70
|
+
assignGingerly(dest, pattern, inheritedOptions);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Handle shorthand (implicit toHost)
|
|
75
|
+
const shorthand = extractShorthand(vector as Record<string, any>);
|
|
76
|
+
if (shorthand) {
|
|
77
|
+
if (useAssignFrom && source != null) {
|
|
78
|
+
import('../assignFrom.js').then(({ assignFrom }) => {
|
|
79
|
+
assignFrom(host, shorthand, { from: source, ...inheritedOptions, ...vector.withOptions });
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
assignGingerly(host, shorthand, inheritedOptions);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Attach an event listener based on AddEventListenerConfig.
|
|
89
|
+
*
|
|
90
|
+
* @param lhs - The DOM element to attach the listener to
|
|
91
|
+
* @param config - The event handler configuration
|
|
92
|
+
* @param target - The assignFrom target (first arg)
|
|
93
|
+
* @param host - The options.from (source/view model)
|
|
94
|
+
* @param inheritedOptions - Parent assignFrom options (withMethods, aka, etc.)
|
|
95
|
+
*/
|
|
96
|
+
export function attachEventListener(
|
|
97
|
+
lhs: Element,
|
|
98
|
+
config: AddEventListenerConfig,
|
|
99
|
+
target: any,
|
|
100
|
+
host: any,
|
|
101
|
+
inheritedOptions: any
|
|
102
|
+
): void {
|
|
103
|
+
const { on: eventName, get: getConfig, fromLHS, fromHost, fromTarget, fromEvent, dispatch } = config;
|
|
104
|
+
|
|
105
|
+
// Resolve get config values
|
|
106
|
+
const { abortController, key, nudge, options: listenerOptions, stopPropagation, preventDefault, dispatch: getDispatch } = getConfig ?? {} as any;
|
|
107
|
+
|
|
108
|
+
// Handle dedup via key
|
|
109
|
+
let controller: AbortController;
|
|
110
|
+
if (key) {
|
|
111
|
+
let elMap = keyMap.get(lhs);
|
|
112
|
+
if (!elMap) {
|
|
113
|
+
elMap = new Map();
|
|
114
|
+
keyMap.set(lhs, elMap);
|
|
115
|
+
}
|
|
116
|
+
// Abort previous listener with same key
|
|
117
|
+
const prev = elMap.get(key);
|
|
118
|
+
if (prev) prev.abort();
|
|
119
|
+
// Create new controller for this key
|
|
120
|
+
controller = new AbortController();
|
|
121
|
+
elMap.set(key, controller);
|
|
122
|
+
} else if (abortController instanceof AbortController) {
|
|
123
|
+
controller = abortController;
|
|
124
|
+
} else {
|
|
125
|
+
controller = new AbortController();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Attach the listener
|
|
129
|
+
lhs.addEventListener(eventName, (event: Event) => {
|
|
130
|
+
if (stopPropagation) event.stopPropagation();
|
|
131
|
+
if (preventDefault) event.preventDefault();
|
|
132
|
+
|
|
133
|
+
// Static assignments (no from) — top-level toTarget/toHost/toLHS + shorthand
|
|
134
|
+
processVector(config, null, target, host, lhs, inheritedOptions, false);
|
|
135
|
+
|
|
136
|
+
// fromLHS assignments
|
|
137
|
+
if (fromLHS) {
|
|
138
|
+
processVector(fromLHS, lhs, target, host, lhs, inheritedOptions, true);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// fromHost assignments
|
|
142
|
+
if (fromHost) {
|
|
143
|
+
processVector(fromHost, host, target, host, lhs, inheritedOptions, true);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// fromTarget assignments
|
|
147
|
+
if (fromTarget) {
|
|
148
|
+
processVector(fromTarget, target, target, host, lhs, inheritedOptions, true);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// fromEvent assignments
|
|
152
|
+
if (fromEvent) {
|
|
153
|
+
processVector(fromEvent, event, target, host, lhs, inheritedOptions, true);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Dispatch custom event if configured
|
|
157
|
+
const dispatchConfig = dispatch || getDispatch;
|
|
158
|
+
if (dispatchConfig && dispatchConfig.type) {
|
|
159
|
+
const EventCtr = (typeof dispatchConfig.eventCtr === 'function'
|
|
160
|
+
? dispatchConfig.eventCtr
|
|
161
|
+
: CustomEvent) as typeof CustomEvent;
|
|
162
|
+
const evt = new EventCtr(dispatchConfig.type, {
|
|
163
|
+
detail: dispatchConfig.detail,
|
|
164
|
+
bubbles: dispatchConfig.bubbles ?? true,
|
|
165
|
+
cancelable: dispatchConfig.cancelable ?? false,
|
|
166
|
+
composed: dispatchConfig.composed ?? true,
|
|
167
|
+
});
|
|
168
|
+
lhs.dispatchEvent(evt);
|
|
169
|
+
}
|
|
170
|
+
}, { signal: controller.signal, ...listenerOptions });
|
|
171
|
+
|
|
172
|
+
// Nudge: remove disabled, add interaction hints
|
|
173
|
+
if (nudge) {
|
|
174
|
+
import('./nudge.js').then(({ nudge: nudgeFn }) => {
|
|
175
|
+
nudgeFn(lhs);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
package/handlers/lazyLoad.js
CHANGED
|
@@ -217,10 +217,10 @@ export class LazyLoadHandler {
|
|
|
217
217
|
const len = Math.min(elements.length, assign.configs.length);
|
|
218
218
|
for (let j = 0; j < len; j++) {
|
|
219
219
|
const cfg = assign.configs[j];
|
|
220
|
-
assignFrom(elements[j], cfg.
|
|
220
|
+
assignFrom(elements[j], cfg.toClone ?? {}, { from, ...cfg.withOptions });
|
|
221
221
|
}
|
|
222
|
-
} else if (assign.
|
|
223
|
-
assignFrom(elements[0], assign.
|
|
222
|
+
} else if (assign.toClone) {
|
|
223
|
+
assignFrom(elements[0], assign.toClone, { from, ...assign.withOptions });
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
async onCloneInserted(nodes, lhsTarget, resolvedParams) {
|
package/handlers/lazyLoad.ts
CHANGED
|
@@ -317,10 +317,10 @@ export class LazyLoadHandler implements AssignFromHandler {
|
|
|
317
317
|
const len = Math.min(elements.length, assign.configs.length);
|
|
318
318
|
for (let j = 0; j < len; j++) {
|
|
319
319
|
const cfg = assign.configs[j];
|
|
320
|
-
assignFrom(elements[j], cfg.
|
|
320
|
+
assignFrom(elements[j], cfg.toClone ?? {}, { from, ...cfg.withOptions });
|
|
321
321
|
}
|
|
322
|
-
} else if (assign.
|
|
323
|
-
assignFrom(elements[0], assign.
|
|
322
|
+
} else if (assign.toClone) {
|
|
323
|
+
assignFrom(elements[0], assign.toClone, { from, ...assign.withOptions });
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* instantiate: 'globalThis://country-ranking',
|
|
18
18
|
* },
|
|
19
19
|
* fromEachItem: {
|
|
20
|
-
*
|
|
20
|
+
* '?.querySelector?.tr?.ish': '?.',
|
|
21
21
|
* withOptions: { withMethods: ['querySelector'], infer: true },
|
|
22
22
|
* resolve: { key: '?.rank' }
|
|
23
23
|
* }
|
|
@@ -28,6 +28,24 @@ import { findMarkers, createMarkers } from '../markerUtils.js';
|
|
|
28
28
|
import { resolveValue } from '../resolveValues.js';
|
|
29
29
|
import { assignFrom } from '../assignFrom.js';
|
|
30
30
|
import { processInferredAssignments } from '../inferredAssignments.js';
|
|
31
|
+
/**
|
|
32
|
+
* Reserved keys in fromEachItem config — not treated as shorthand patterns.
|
|
33
|
+
*/
|
|
34
|
+
const RESERVED_KEYS = new Set(['toClone', 'withOptions', 'resolve', 'get', 'configs']);
|
|
35
|
+
/**
|
|
36
|
+
* Extract shorthand patterns from fromEachItem config (non-reserved keys → toClone).
|
|
37
|
+
*/
|
|
38
|
+
function extractToClone(config) {
|
|
39
|
+
if (!config)
|
|
40
|
+
return {};
|
|
41
|
+
const result = {};
|
|
42
|
+
for (const key of Object.keys(config)) {
|
|
43
|
+
if (!RESERVED_KEYS.has(key)) {
|
|
44
|
+
result[key] = config[key];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
31
49
|
const listStateMap = new WeakMap();
|
|
32
50
|
/**
|
|
33
51
|
* ManageTemplateListHandler — clones a template per iterable item with keyed reconciliation.
|
|
@@ -46,20 +64,23 @@ export class ManageTemplateListHandler {
|
|
|
46
64
|
if (!items || typeof items[Symbol.iterator] !== 'function') {
|
|
47
65
|
return; // Nothing to iterate
|
|
48
66
|
}
|
|
49
|
-
const fromEachItem = this.config
|
|
67
|
+
const { fromEachItem, fromHost: fromHostConfig, fromTarget: fromTargetConfig } = this.config;
|
|
50
68
|
const configs = fromEachItem?.configs; // Array form for multi-element templates
|
|
51
|
-
const assignToFragment = fromEachItem?.assignToFragment ?? {};
|
|
52
69
|
const withOptions = fromEachItem?.withOptions ?? {};
|
|
53
|
-
const perItemResolve = fromEachItem?.resolve ?? {};
|
|
54
|
-
const keyPath = perItemResolve
|
|
55
|
-
//
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
const perItemResolve = fromEachItem?.resolve ?? fromEachItem?.get ?? {};
|
|
71
|
+
const { key: keyPath } = perItemResolve; // e.g., '?.rank'
|
|
72
|
+
// Resolve toClone patterns: explicit `toClone` key, or shorthand (non-reserved keys)
|
|
73
|
+
const toClone = fromEachItem?.toClone ?? extractToClone(fromEachItem);
|
|
74
|
+
// fromHost config — assigns from options.from (host/VM) to each clone
|
|
75
|
+
const hostToClone = fromHostConfig ? (fromHostConfig.toClone ?? extractToClone(fromHostConfig)) : undefined;
|
|
76
|
+
const hostWithOptions = fromHostConfig?.withOptions ?? {};
|
|
77
|
+
// fromTarget config — assigns from the target element to each clone
|
|
78
|
+
const targetToClone = fromTargetConfig ? (fromTargetConfig.toClone ?? extractToClone(fromTargetConfig)) : undefined;
|
|
79
|
+
const targetWithOptions = fromTargetConfig?.withOptions ?? {};
|
|
80
|
+
// Detect fast path: no toClone patterns, just infer (only for non-configs mode)
|
|
81
|
+
const hasAssignPatterns = !configs && Object.keys(toClone).length > 0;
|
|
61
82
|
const inferredConfig = !configs && withOptions.infer;
|
|
62
|
-
const useFastPath = !configs && !hasAssignPatterns && inferredConfig && !
|
|
83
|
+
const useFastPath = !configs && !hasAssignPatterns && inferredConfig && !hostToClone && !targetToClone;
|
|
63
84
|
const name = markerName ?? getMarkerName(instantiate) ?? 'templateList';
|
|
64
85
|
// Find or create markers
|
|
65
86
|
let [startMarker, endMarker] = findMarkers(lhsTarget, name);
|
|
@@ -122,7 +143,7 @@ export class ManageTemplateListHandler {
|
|
|
122
143
|
const len = Math.min(elements.length, configs.length);
|
|
123
144
|
for (let j = 0; j < len; j++) {
|
|
124
145
|
const cfg = configs[j];
|
|
125
|
-
assignFrom(elements[j], cfg.
|
|
146
|
+
assignFrom(elements[j], cfg.toClone ?? {}, { from: item, ...cfg.withOptions });
|
|
126
147
|
}
|
|
127
148
|
}
|
|
128
149
|
else {
|
|
@@ -132,10 +153,13 @@ export class ManageTemplateListHandler {
|
|
|
132
153
|
processInferred(rootEl, item, inferredConfig === true ? { byItemprop: true } : inferredConfig);
|
|
133
154
|
}
|
|
134
155
|
else {
|
|
135
|
-
assignFrom(rootEl,
|
|
156
|
+
assignFrom(rootEl, toClone, { from: item, ...withOptions });
|
|
136
157
|
}
|
|
137
|
-
if (
|
|
138
|
-
assignFrom(rootEl,
|
|
158
|
+
if (hostToClone && options?.from) {
|
|
159
|
+
assignFrom(rootEl, hostToClone, { from: options.from, ...hostWithOptions });
|
|
160
|
+
}
|
|
161
|
+
if (targetToClone) {
|
|
162
|
+
assignFrom(rootEl, targetToClone, { from: lhsTarget, ...targetWithOptions });
|
|
139
163
|
}
|
|
140
164
|
}
|
|
141
165
|
}
|
|
@@ -168,7 +192,7 @@ export class ManageTemplateListHandler {
|
|
|
168
192
|
const len = Math.min(elements.length, configs.length);
|
|
169
193
|
for (let j = 0; j < len; j++) {
|
|
170
194
|
const cfg = configs[j];
|
|
171
|
-
assignFrom(elements[j], cfg.
|
|
195
|
+
assignFrom(elements[j], cfg.toClone ?? {}, { from: item, ...cfg.withOptions });
|
|
172
196
|
}
|
|
173
197
|
}
|
|
174
198
|
else {
|
|
@@ -180,10 +204,13 @@ export class ManageTemplateListHandler {
|
|
|
180
204
|
processInferred(rootEl, item, inferredConfig === true ? { byItemprop: true } : inferredConfig);
|
|
181
205
|
}
|
|
182
206
|
else {
|
|
183
|
-
assignFrom(rootEl,
|
|
207
|
+
assignFrom(rootEl, toClone, { from: item, ...withOptions });
|
|
208
|
+
}
|
|
209
|
+
if (hostToClone && options?.from) {
|
|
210
|
+
assignFrom(rootEl, hostToClone, { from: options.from, ...hostWithOptions });
|
|
184
211
|
}
|
|
185
|
-
if (
|
|
186
|
-
assignFrom(rootEl,
|
|
212
|
+
if (targetToClone) {
|
|
213
|
+
assignFrom(rootEl, targetToClone, { from: lhsTarget, ...targetWithOptions });
|
|
187
214
|
}
|
|
188
215
|
}
|
|
189
216
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* instantiate: 'globalThis://country-ranking',
|
|
18
18
|
* },
|
|
19
19
|
* fromEachItem: {
|
|
20
|
-
*
|
|
20
|
+
* '?.querySelector?.tr?.ish': '?.',
|
|
21
21
|
* withOptions: { withMethods: ['querySelector'], infer: true },
|
|
22
22
|
* resolve: { key: '?.rank' }
|
|
23
23
|
* }
|
|
@@ -32,6 +32,25 @@ import { resolveValue } from '../resolveValues.js';
|
|
|
32
32
|
import { assignFrom } from '../assignFrom.js';
|
|
33
33
|
import { processInferredAssignments } from '../inferredAssignments.js';
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Reserved keys in fromEachItem config — not treated as shorthand patterns.
|
|
37
|
+
*/
|
|
38
|
+
const RESERVED_KEYS = new Set(['toClone', 'withOptions', 'resolve', 'get', 'configs']);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Extract shorthand patterns from fromEachItem config (non-reserved keys → toClone).
|
|
42
|
+
*/
|
|
43
|
+
function extractToClone(config: any): Record<string, any> {
|
|
44
|
+
if (!config) return {};
|
|
45
|
+
const result: Record<string, any> = {};
|
|
46
|
+
for (const key of Object.keys(config)) {
|
|
47
|
+
if (!RESERVED_KEYS.has(key)) {
|
|
48
|
+
result[key] = config[key];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
/**
|
|
36
55
|
* State stored per list instance (keyed by start marker).
|
|
37
56
|
* Tracks the mapping of keys to their cloned DOM nodes.
|
|
@@ -74,22 +93,27 @@ export class ManageTemplateListHandler implements AssignFromHandler {
|
|
|
74
93
|
return; // Nothing to iterate
|
|
75
94
|
}
|
|
76
95
|
|
|
77
|
-
const fromEachItem = this.config
|
|
96
|
+
const { fromEachItem, fromHost: fromHostConfig, fromTarget: fromTargetConfig } = this.config;
|
|
78
97
|
const configs = fromEachItem?.configs; // Array form for multi-element templates
|
|
79
|
-
const assignToFragment = fromEachItem?.assignToFragment ?? {};
|
|
80
98
|
const withOptions = fromEachItem?.withOptions ?? {};
|
|
81
|
-
const perItemResolve = fromEachItem?.resolve ?? {};
|
|
82
|
-
const keyPath = perItemResolve
|
|
99
|
+
const perItemResolve = fromEachItem?.resolve ?? fromEachItem?.get ?? {};
|
|
100
|
+
const { key: keyPath } = perItemResolve; // e.g., '?.rank'
|
|
101
|
+
|
|
102
|
+
// Resolve toClone patterns: explicit `toClone` key, or shorthand (non-reserved keys)
|
|
103
|
+
const toClone = fromEachItem?.toClone ?? extractToClone(fromEachItem);
|
|
83
104
|
|
|
84
|
-
//
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const sourceWithOptions = fromSource?.withOptions ?? {};
|
|
105
|
+
// fromHost config — assigns from options.from (host/VM) to each clone
|
|
106
|
+
const hostToClone = fromHostConfig ? (fromHostConfig.toClone ?? extractToClone(fromHostConfig)) : undefined;
|
|
107
|
+
const hostWithOptions = fromHostConfig?.withOptions ?? {};
|
|
88
108
|
|
|
89
|
-
//
|
|
90
|
-
const
|
|
109
|
+
// fromTarget config — assigns from the target element to each clone
|
|
110
|
+
const targetToClone = fromTargetConfig ? (fromTargetConfig.toClone ?? extractToClone(fromTargetConfig)) : undefined;
|
|
111
|
+
const targetWithOptions = fromTargetConfig?.withOptions ?? {};
|
|
112
|
+
|
|
113
|
+
// Detect fast path: no toClone patterns, just infer (only for non-configs mode)
|
|
114
|
+
const hasAssignPatterns = !configs && Object.keys(toClone).length > 0;
|
|
91
115
|
const inferredConfig = !configs && withOptions.infer;
|
|
92
|
-
const useFastPath = !configs && !hasAssignPatterns && inferredConfig && !
|
|
116
|
+
const useFastPath = !configs && !hasAssignPatterns && inferredConfig && !hostToClone && !targetToClone;
|
|
93
117
|
|
|
94
118
|
const name = markerName ?? getMarkerName(instantiate) ?? 'templateList';
|
|
95
119
|
|
|
@@ -159,7 +183,7 @@ export class ManageTemplateListHandler implements AssignFromHandler {
|
|
|
159
183
|
const len = Math.min(elements.length, configs.length);
|
|
160
184
|
for (let j = 0; j < len; j++) {
|
|
161
185
|
const cfg = configs[j];
|
|
162
|
-
assignFrom(elements[j], cfg.
|
|
186
|
+
assignFrom(elements[j], cfg.toClone ?? {}, { from: item, ...cfg.withOptions });
|
|
163
187
|
}
|
|
164
188
|
} else {
|
|
165
189
|
const rootEl = existingNodes.find(n => n instanceof Element) as Element | undefined;
|
|
@@ -167,10 +191,13 @@ export class ManageTemplateListHandler implements AssignFromHandler {
|
|
|
167
191
|
if (processInferred) {
|
|
168
192
|
processInferred(rootEl, item, inferredConfig === true ? { byItemprop: true } : inferredConfig);
|
|
169
193
|
} else {
|
|
170
|
-
assignFrom(rootEl,
|
|
194
|
+
assignFrom(rootEl, toClone, { from: item, ...withOptions });
|
|
195
|
+
}
|
|
196
|
+
if (hostToClone && options?.from) {
|
|
197
|
+
assignFrom(rootEl, hostToClone, { from: options.from, ...hostWithOptions });
|
|
171
198
|
}
|
|
172
|
-
if (
|
|
173
|
-
assignFrom(rootEl,
|
|
199
|
+
if (targetToClone) {
|
|
200
|
+
assignFrom(rootEl, targetToClone, { from: lhsTarget, ...targetWithOptions });
|
|
174
201
|
}
|
|
175
202
|
}
|
|
176
203
|
}
|
|
@@ -201,7 +228,7 @@ export class ManageTemplateListHandler implements AssignFromHandler {
|
|
|
201
228
|
const len = Math.min(elements.length, configs.length);
|
|
202
229
|
for (let j = 0; j < len; j++) {
|
|
203
230
|
const cfg = configs[j];
|
|
204
|
-
assignFrom(elements[j], cfg.
|
|
231
|
+
assignFrom(elements[j], cfg.toClone ?? {}, { from: item, ...cfg.withOptions });
|
|
205
232
|
}
|
|
206
233
|
} else {
|
|
207
234
|
const rootEl = clonedNodes.find(n => n instanceof Element) as Element | undefined;
|
|
@@ -212,11 +239,14 @@ export class ManageTemplateListHandler implements AssignFromHandler {
|
|
|
212
239
|
if (processInferred) {
|
|
213
240
|
processInferred(rootEl, item, inferredConfig === true ? { byItemprop: true } : inferredConfig);
|
|
214
241
|
} else {
|
|
215
|
-
assignFrom(rootEl,
|
|
242
|
+
assignFrom(rootEl, toClone, { from: item, ...withOptions });
|
|
216
243
|
}
|
|
217
244
|
|
|
218
|
-
if (
|
|
219
|
-
assignFrom(rootEl,
|
|
245
|
+
if (hostToClone && options?.from) {
|
|
246
|
+
assignFrom(rootEl, hostToClone, { from: options.from, ...hostWithOptions });
|
|
247
|
+
}
|
|
248
|
+
if (targetToClone) {
|
|
249
|
+
assignFrom(rootEl, targetToClone, { from: lhsTarget, ...targetWithOptions });
|
|
220
250
|
}
|
|
221
251
|
}
|
|
222
252
|
}
|