assign-gingerly 0.0.59 → 0.0.60
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 +269 -12
- package/assignFrom-extension.js +25 -0
- package/assignFrom-extension.ts +53 -0
- package/assignFrom.js +190 -12
- package/assignFrom.ts +184 -11
- package/assignFromAsync-extension.js +28 -0
- package/assignFromAsync-extension.ts +58 -0
- package/assignFromAsync.js +11 -9
- package/assignFromAsync.ts +27 -11
- package/assignGingerly.js +50 -0
- package/assignGingerly.ts +51 -0
- package/builtInEmoji.js +25 -0
- package/builtInEmoji.ts +33 -0
- package/handlers/lazyLoad.js +33 -2
- package/handlers/lazyLoad.ts +46 -1
- package/handlers/manageTemplateList.js +54 -31
- package/handlers/manageTemplateList.ts +51 -28
- package/inferencer/inferencer.js +9 -21
- package/inferencer/inferencer.ts +10 -21
- package/inferredAssignments.js +34 -4
- package/inferredAssignments.ts +56 -6
- package/package.json +13 -1
- package/playwright.config.ts +3 -2
- package/processHandlerCommands.js +6 -1
- package/processHandlerCommands.ts +7 -1
- package/resolveIdRef.js +70 -19
- package/resolveIdRef.ts +73 -21
- package/types/assign-gingerly/types.d.ts +10 -0
- package/withIdsCorrector.js +47 -0
- package/withIdsCorrector.ts +59 -0
package/resolveIdRef.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
/**
|
|
12
12
|
* Configuration for a withIds entry.
|
|
13
13
|
*/
|
|
14
|
-
export type WithIdConfig = string | { qry: string };
|
|
14
|
+
export type WithIdConfig = string | { qry: string } | number[] | { path: number[]; expect?: string; fallback?: boolean };
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Module-level cache: rootNode → Map<varName, { id, WeakRef }>
|
|
@@ -26,14 +26,15 @@ const idCounterMap = new WeakMap<object, number>();
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Generate a unique ID within a rootNode.
|
|
29
|
-
* Format:
|
|
29
|
+
* Format: -ag:0, -ag:1, -ag:2, ...
|
|
30
|
+
* Starts with '-' and contains ':' to avoid collision with JS identifiers/globalThis properties.
|
|
30
31
|
*/
|
|
31
32
|
function generateUniqueId(rootNode: any): string {
|
|
32
33
|
let counter = idCounterMap.get(rootNode) ?? 0;
|
|
33
34
|
let id: string;
|
|
34
35
|
// Ensure uniqueness (skip if ID already exists in the document)
|
|
35
36
|
do {
|
|
36
|
-
id =
|
|
37
|
+
id = `-ag:${counter}`;
|
|
37
38
|
counter++;
|
|
38
39
|
} while (rootNode.getElementById?.(id));
|
|
39
40
|
idCounterMap.set(rootNode, counter);
|
|
@@ -56,6 +57,57 @@ export function resolveIdVariable(
|
|
|
56
57
|
const config = withIds[varName];
|
|
57
58
|
if (config === undefined) return undefined;
|
|
58
59
|
|
|
60
|
+
// For 'at' option path-based configs (array or { path }), resolve directly from target — no ID, no caching
|
|
61
|
+
// These are target-relative and fast (~2-4ns), for when structure is guaranteed stable
|
|
62
|
+
if (Array.isArray(config)) {
|
|
63
|
+
let current: any = target;
|
|
64
|
+
for (const idx of config) {
|
|
65
|
+
if (!current || !current.children) break;
|
|
66
|
+
current = current.children[idx];
|
|
67
|
+
}
|
|
68
|
+
return current instanceof Element ? current : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof config === 'object' && 'path' in config && !('qry' in config)) {
|
|
72
|
+
// Determine if this is from 'at' (no ID assignment) or 'withIds' with path (assigns ID + caches)
|
|
73
|
+
// When called from 'at', we skip ID/caching. When from 'withIds', we assign ID and cache.
|
|
74
|
+
// Distinguish by presence in the options — caller passes the merged map.
|
|
75
|
+
// For now: { path } without 'noId' → assign ID + cache (withIds behavior)
|
|
76
|
+
const rootNode = target.getRootNode?.() ?? target;
|
|
77
|
+
let current: any = target;
|
|
78
|
+
for (const idx of config.path) {
|
|
79
|
+
if (!current || !current.children) break;
|
|
80
|
+
current = current.children[idx];
|
|
81
|
+
}
|
|
82
|
+
let el: Element | null = current instanceof Element ? current : null;
|
|
83
|
+
|
|
84
|
+
// Validation: check if resolved element matches expected selector
|
|
85
|
+
if (config.expect) {
|
|
86
|
+
const didNotMatch = !el || !el.matches(config.expect);
|
|
87
|
+
if (didNotMatch) {
|
|
88
|
+
if (config.fallback) {
|
|
89
|
+
el = target.querySelector?.(config.expect) ?? el;
|
|
90
|
+
}
|
|
91
|
+
// Fire-and-forget: log correction suggestion
|
|
92
|
+
const capturedConfig = config;
|
|
93
|
+
const capturedVarName = varName;
|
|
94
|
+
import('./withIdsCorrector.js').then(module => {
|
|
95
|
+
module.logConfigCorrection(target, capturedVarName, capturedConfig);
|
|
96
|
+
}).catch(() => {});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!el) return undefined;
|
|
101
|
+
|
|
102
|
+
// Assign ID for stability against future DOM mutations
|
|
103
|
+
let id = el.id;
|
|
104
|
+
if (!id) {
|
|
105
|
+
id = generateUniqueId(rootNode);
|
|
106
|
+
el.id = id;
|
|
107
|
+
}
|
|
108
|
+
return el;
|
|
109
|
+
}
|
|
110
|
+
|
|
59
111
|
const rootNode = target.getRootNode?.() ?? target;
|
|
60
112
|
|
|
61
113
|
// Get or create cache for this rootNode
|
|
@@ -65,29 +117,27 @@ export function resolveIdVariable(
|
|
|
65
117
|
idCacheMap.set(rootNode, cache);
|
|
66
118
|
}
|
|
67
119
|
|
|
68
|
-
// Check cache first
|
|
69
|
-
const cached = cache.get(varName);
|
|
70
|
-
if (cached) {
|
|
71
|
-
const el = cached.ref.deref();
|
|
72
|
-
if (el) return el;
|
|
73
|
-
|
|
74
|
-
// WeakRef was collected — try getElementById fallback
|
|
75
|
-
const el2 = rootNode.getElementById?.(cached.id);
|
|
76
|
-
if (el2) {
|
|
77
|
-
cache.set(varName, { id: cached.id, ref: new WeakRef(el2) });
|
|
78
|
-
return el2;
|
|
79
|
-
}
|
|
80
|
-
// Element no longer exists — fall through to re-query
|
|
81
|
-
}
|
|
82
|
-
|
|
83
120
|
// First time or cache miss — resolve the element
|
|
84
121
|
let el: Element | null = null;
|
|
85
122
|
|
|
86
123
|
if (typeof config === 'string') {
|
|
87
124
|
// String form: existing ID — use getElementById directly
|
|
125
|
+
// Check cache first (getElementById lookups are cacheable — global to rootNode)
|
|
126
|
+
const cached = cache.get(varName);
|
|
127
|
+
if (cached) {
|
|
128
|
+
const cachedEl = cached.ref.deref();
|
|
129
|
+
if (cachedEl) return cachedEl;
|
|
130
|
+
|
|
131
|
+
// WeakRef was collected — try getElementById fallback
|
|
132
|
+
const el2 = rootNode.getElementById?.(cached.id);
|
|
133
|
+
if (el2) {
|
|
134
|
+
cache.set(varName, { id: cached.id, ref: new WeakRef(el2) });
|
|
135
|
+
return el2;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
88
138
|
el = rootNode.getElementById?.(config) ?? null;
|
|
89
139
|
} else {
|
|
90
|
-
// Object form: { qry } — run querySelector against target
|
|
140
|
+
// Object form: { qry } — run querySelector against target (target-relative, no cache)
|
|
91
141
|
el = target.querySelector?.(config.qry) ?? null;
|
|
92
142
|
}
|
|
93
143
|
|
|
@@ -100,8 +150,10 @@ export function resolveIdVariable(
|
|
|
100
150
|
el.id = id;
|
|
101
151
|
}
|
|
102
152
|
|
|
103
|
-
// Cache
|
|
104
|
-
|
|
153
|
+
// Cache only for string-form (getElementById) — not for qry form (target-relative)
|
|
154
|
+
if (typeof config === 'string') {
|
|
155
|
+
cache.set(varName, { id, ref: new WeakRef(el) });
|
|
156
|
+
}
|
|
105
157
|
return el;
|
|
106
158
|
}
|
|
107
159
|
|
|
@@ -616,6 +616,16 @@ export interface LazyLoadResolvedParams {
|
|
|
616
616
|
toggleInert?: boolean;
|
|
617
617
|
/** Set disabled property on hidden form elements */
|
|
618
618
|
toggleDisabled?: boolean;
|
|
619
|
+
/** Name of a pre-existing marker pair whose content should be removed on first activation.
|
|
620
|
+
* Used for SSR placeholder content (e.g., "Loading..." text) that disappears once real content loads. */
|
|
621
|
+
placeholder?: string;
|
|
622
|
+
/** Assignment config applied to cloned content before insertion.
|
|
623
|
+
* Same shape as manageTemplateList's fromEachItem: { assignToFragment, withOptions } or { configs: [...] } */
|
|
624
|
+
assign?: {
|
|
625
|
+
assignToFragment?: Record<string, any>;
|
|
626
|
+
withOptions?: Record<string, any>;
|
|
627
|
+
configs?: Array<{ assignToFragment?: Record<string, any>; withOptions?: Record<string, any> }>;
|
|
628
|
+
};
|
|
619
629
|
}
|
|
620
630
|
|
|
621
631
|
/**
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* withIdsCorrector.ts — Dev-time diagnostic for stale withIds coordinates.
|
|
3
|
+
*
|
|
4
|
+
* Dynamically imported only on mismatch — zero cost in production or when coordinates are correct.
|
|
5
|
+
* Computes and logs the correct child index path for a given selector.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Compute the child index path from a root element to a target element.
|
|
9
|
+
* Returns the array of children indices, or null if not found.
|
|
10
|
+
*/
|
|
11
|
+
function computeChildPath(root, target) {
|
|
12
|
+
const path = [];
|
|
13
|
+
let current = target;
|
|
14
|
+
while (current && current !== root) {
|
|
15
|
+
const parent = current.parentElement;
|
|
16
|
+
if (!parent)
|
|
17
|
+
return null;
|
|
18
|
+
const idx = Array.prototype.indexOf.call(parent.children, current);
|
|
19
|
+
if (idx === -1)
|
|
20
|
+
return null;
|
|
21
|
+
path.unshift(idx);
|
|
22
|
+
current = parent;
|
|
23
|
+
}
|
|
24
|
+
return current === root ? path : null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Log a correction suggestion for a mismatched withIds config.
|
|
28
|
+
*/
|
|
29
|
+
export function logConfigCorrection(target, varName, config) {
|
|
30
|
+
if (!config.expect)
|
|
31
|
+
return;
|
|
32
|
+
const correctEl = target.querySelector?.(config.expect);
|
|
33
|
+
if (!correctEl) {
|
|
34
|
+
console.warn(`withIds["${varName}"]: path [${config.path}] did not match "${config.expect}" ` +
|
|
35
|
+
`and querySelector also found no match. Check that the selector is correct.`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const correctPath = computeChildPath(target, correctEl);
|
|
39
|
+
if (correctPath) {
|
|
40
|
+
console.warn(`withIds["${varName}"]: path [${config.path}] did not match "${config.expect}". ` +
|
|
41
|
+
`Suggested correction: [${correctPath.join(', ')}]`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.warn(`withIds["${varName}"]: path [${config.path}] did not match "${config.expect}". ` +
|
|
45
|
+
`Could not compute a child index path (element may not be a descendant of target).`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* withIdsCorrector.ts — Dev-time diagnostic for stale withIds coordinates.
|
|
3
|
+
*
|
|
4
|
+
* Dynamically imported only on mismatch — zero cost in production or when coordinates are correct.
|
|
5
|
+
* Computes and logs the correct child index path for a given selector.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Compute the child index path from a root element to a target element.
|
|
10
|
+
* Returns the array of children indices, or null if not found.
|
|
11
|
+
*/
|
|
12
|
+
function computeChildPath(root: Element, target: Element): number[] | null {
|
|
13
|
+
const path: number[] = [];
|
|
14
|
+
let current: Element | null = target;
|
|
15
|
+
|
|
16
|
+
while (current && current !== root) {
|
|
17
|
+
const parent = current.parentElement;
|
|
18
|
+
if (!parent) return null;
|
|
19
|
+
const idx = Array.prototype.indexOf.call(parent.children, current);
|
|
20
|
+
if (idx === -1) return null;
|
|
21
|
+
path.unshift(idx);
|
|
22
|
+
current = parent;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return current === root ? path : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Log a correction suggestion for a mismatched withIds config.
|
|
30
|
+
*/
|
|
31
|
+
export function logConfigCorrection(
|
|
32
|
+
target: any,
|
|
33
|
+
varName: string,
|
|
34
|
+
config: { path: number[]; expect?: string; fallback?: boolean }
|
|
35
|
+
): void {
|
|
36
|
+
if (!config.expect) return;
|
|
37
|
+
|
|
38
|
+
const correctEl = target.querySelector?.(config.expect);
|
|
39
|
+
if (!correctEl) {
|
|
40
|
+
console.warn(
|
|
41
|
+
`withIds["${varName}"]: path [${config.path}] did not match "${config.expect}" ` +
|
|
42
|
+
`and querySelector also found no match. Check that the selector is correct.`
|
|
43
|
+
);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const correctPath = computeChildPath(target, correctEl);
|
|
48
|
+
if (correctPath) {
|
|
49
|
+
console.warn(
|
|
50
|
+
`withIds["${varName}"]: path [${config.path}] did not match "${config.expect}". ` +
|
|
51
|
+
`Suggested correction: [${correctPath.join(', ')}]`
|
|
52
|
+
);
|
|
53
|
+
} else {
|
|
54
|
+
console.warn(
|
|
55
|
+
`withIds["${varName}"]: path [${config.path}] did not match "${config.expect}". ` +
|
|
56
|
+
`Could not compute a child index path (element may not be a descendant of target).`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|