assign-gingerly 0.0.54 → 0.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -2
- package/assignFrom.js +37 -18
- package/assignFrom.ts +62 -3
- package/assignGingerly.js +34 -1
- package/assignGingerly.ts +54 -1
- package/beVigilant.js +73 -0
- package/beVigilant.ts +85 -0
- package/enhanceAll.js +106 -0
- package/enhanceAll.ts +138 -0
- package/handlers/join.js +74 -0
- package/handlers/join.ts +80 -0
- package/handlers/lazyLoad.js +186 -0
- package/handlers/lazyLoad.ts +281 -0
- package/handlers/lazyLoadSwitch.js +58 -0
- package/handlers/lazyLoadSwitch.ts +64 -0
- package/handlers/microDataJoin.js +184 -0
- package/handlers/microDataJoin.ts +270 -0
- package/inferencer/.gitmodules +3 -0
- package/inferencer/.vscode/settings.json +2 -0
- package/inferencer/InferencedPropagator.js +230 -0
- package/inferencer/InferencedPropagator.ts +269 -0
- package/inferencer/LICENSE +21 -0
- package/inferencer/README.md +524 -0
- package/inferencer/Requirements/SupportForPropagator.md +368 -0
- package/inferencer/imports.html +7 -0
- package/inferencer/inferencer.js +254 -0
- package/inferencer/inferencer.ts +292 -0
- package/inferencer/package-lock.json +129 -0
- package/inferencer/package.json +60 -0
- package/inferencer/playwright-report/data/507ad515125e13390ea07de92f22331c913fa068.md +55 -0
- package/inferencer/playwright-report/index.html +90 -0
- package/inferencer/playwright.config.ts +54 -0
- package/inferencer/test-results/.last-run.json +6 -0
- package/inferencer/test-results/inferencer-Inferencer-Enha-535bc-inferencer-tests-in-browser-chromium/error-context.md +55 -0
- package/inferencer/tests/inferencedPropagator.html +428 -0
- package/inferencer/tests/inferencedPropagator.spec.ts +18 -0
- package/inferencer/tests/inferencer.html +355 -0
- package/inferencer/tests/inferencer.spec.ts +19 -0
- package/inferencer/tsconfig.json +19 -0
- package/inferencer/types/.kiro/specs/conversion-template/README.md +128 -0
- package/inferencer/types/.kiro/specs/conversion-template/design.md +360 -0
- package/inferencer/types/.kiro/specs/conversion-template/requirements.md +191 -0
- package/inferencer/types/.kiro/specs/conversion-template/tasks.md +174 -0
- package/inferencer/types/.kiro/steering/coding-standards.md +53 -0
- package/inferencer/types/.kiro/steering/conversion-guide.md +108 -0
- package/inferencer/types/.kiro/steering/declarative-configuration.md +108 -0
- package/inferencer/types/.kiro/steering/emc-json-serializability.md +306 -0
- package/inferencer/types/EnhancementConversionInstructions.md +1854 -0
- package/inferencer/types/LICENSE +21 -0
- package/inferencer/types/NewCustomElement.md +388 -0
- package/inferencer/types/NewCustomElementFeature.md +683 -0
- package/inferencer/types/NewEnhancementInstructions.md +705 -0
- package/inferencer/types/README.md +2 -0
- package/inferencer/types/agrace/types.d.ts +11 -0
- package/inferencer/types/assign-gingerly/types.d.ts +572 -0
- package/inferencer/types/be-a-beacon/types.d.ts +17 -0
- package/inferencer/types/be-bound/types.d.ts +66 -0
- package/inferencer/types/be-buttoned-up/types.d.ts +19 -0
- package/inferencer/types/be-calculating/types.d.ts +54 -0
- package/inferencer/types/be-clonable/types.d.ts +38 -0
- package/inferencer/types/be-committed/types.d.ts +22 -0
- package/inferencer/types/be-consoling/types.d.ts +24 -0
- package/inferencer/types/be-decked-with/types.d.ts +26 -0
- package/inferencer/types/be-delible/types.d.ts +27 -0
- package/inferencer/types/be-dispatching/types.d.ts +34 -0
- package/inferencer/types/be-evanescent/types.d.ts +20 -0
- package/inferencer/types/be-flashy/types.d.ts +21 -0
- package/inferencer/types/be-gone/types.d.ts +25 -0
- package/inferencer/types/be-observing/types.d.ts +55 -0
- package/inferencer/types/be-reflective/types.d.ts +78 -0
- package/inferencer/types/be-reformable/types.d.ts +49 -0
- package/inferencer/types/be-render-neutral/types.d.ts +32 -0
- package/inferencer/types/be-switched/types.d.ts +146 -0
- package/inferencer/types/be-typed/types.d.ts +32 -0
- package/inferencer/types/be-valued/types.d.ts +22 -0
- package/inferencer/types/data-props/types.d.ts +34 -0
- package/inferencer/types/do-inc/types.d.ts +56 -0
- package/inferencer/types/do-invoke/types.d.ts +38 -0
- package/inferencer/types/do-merge/types.d.ts +28 -0
- package/inferencer/types/do-toggle/types.d.ts +31 -0
- package/inferencer/types/face-up/types.d.ts +100 -0
- package/inferencer/types/fetch-for/types.d.ts +36 -0
- package/inferencer/types/folder-picker/types.d.ts +21 -0
- package/inferencer/types/global.d.ts +29 -0
- package/inferencer/types/id-generation/types.d.ts +26 -0
- package/inferencer/types/inferencer/types.d.ts +46 -0
- package/inferencer/types/mount-observer/types.d.ts +363 -0
- package/inferencer/types/nested-regex-groups/types.d.ts +107 -0
- package/inferencer/types/pipe-in/types.d.ts +52 -0
- package/inferencer/types/roundabout/types.d.ts +268 -0
- package/inferencer/types/soak-up/types.d.ts +40 -0
- package/inferencer/types/templ-maker/types.d.ts +43 -0
- package/inferencer/types/time-ticker/types.d.ts +62 -0
- package/inferencer/types/truth-sourcer/types.d.ts +44 -0
- package/inferencer/upSearch.js +27 -0
- package/inferencer/upSearch.ts +26 -0
- package/inferencer/withScopePerimeter.js +27 -0
- package/inferencer/withScopePerimeter.ts +33 -0
- package/inferredAssignments.js +38 -0
- package/inferredAssignments.ts +65 -0
- package/isAllowedImportPath.js +42 -0
- package/isAllowedImportPath.ts +53 -0
- package/markerUtils.js +127 -0
- package/markerUtils.ts +162 -0
- package/package.json +31 -1
- package/paths.js +49 -1
- package/paths.ts +80 -1
- package/processHandlerCommands.js +19 -19
- package/processHandlerCommands.ts +10 -13
- package/resolveIdRef.js +144 -125
- package/resolveIdRef.ts +30 -0
- package/types/assign-gingerly/types.d.ts +87 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { resolveValues } from './resolveValues.js';
|
|
7
7
|
import { evaluatePathWithMethods } from './assignGingerly.js';
|
|
8
|
+
import { isAllowedImportPath } from './isAllowedImportPath.js';
|
|
8
9
|
/**
|
|
9
10
|
* Map of built-in handler names to their module paths.
|
|
10
11
|
* These are auto-loaded on demand — no explicit import required.
|
|
@@ -15,22 +16,17 @@ const BUILT_IN_MAP = {
|
|
|
15
16
|
'builtIns.join': './handlers/join.js',
|
|
16
17
|
'builtIns.microDataJoin': './handlers/microDataJoin.js',
|
|
17
18
|
};
|
|
18
|
-
/**
|
|
19
|
-
* Check if an import path is allowed (non-cross-domain).
|
|
20
|
-
*/
|
|
21
|
-
function isAllowedImportPath(path) {
|
|
22
|
-
return path.startsWith('./') || path.startsWith('../') || path.startsWith('/')
|
|
23
|
-
|| (!path.includes('://') && !path.startsWith('//'));
|
|
24
|
-
}
|
|
25
19
|
/**
|
|
26
20
|
* Find a handler class in a dynamically imported module.
|
|
27
21
|
* Checks default export first, then searches for the first class with `assign` on prototype.
|
|
28
22
|
*/
|
|
29
23
|
function findHandlerInModule(module) {
|
|
24
|
+
// Check default export first
|
|
30
25
|
if (module.default && typeof module.default === 'function'
|
|
31
26
|
&& module.default.prototype && 'assign' in module.default.prototype) {
|
|
32
27
|
return module.default;
|
|
33
28
|
}
|
|
29
|
+
// Search other exports
|
|
34
30
|
for (const key of Object.keys(module)) {
|
|
35
31
|
const exported = module[key];
|
|
36
32
|
if (typeof exported === 'function' && exported.prototype && 'assign' in exported.prototype) {
|
|
@@ -41,6 +37,7 @@ function findHandlerInModule(module) {
|
|
|
41
37
|
}
|
|
42
38
|
/**
|
|
43
39
|
* Dynamically load a built-in handler by name.
|
|
40
|
+
* Returns the handler constructor, or undefined if the name isn't a recognized built-in.
|
|
44
41
|
*/
|
|
45
42
|
async function loadBuiltIn(name) {
|
|
46
43
|
const path = BUILT_IN_MAP[name];
|
|
@@ -52,26 +49,25 @@ async function loadBuiltIn(name) {
|
|
|
52
49
|
/**
|
|
53
50
|
* Resolve a handler from options.handlers (class constructor or import path).
|
|
54
51
|
*/
|
|
55
|
-
async function resolveFromHandlers(name, handlers) {
|
|
52
|
+
async function resolveFromHandlers(name, handlers, permissions) {
|
|
56
53
|
if (!handlers || !(name in handlers))
|
|
57
54
|
return undefined;
|
|
58
55
|
const entry = handlers[name];
|
|
56
|
+
// Class constructor — use directly
|
|
59
57
|
if (typeof entry === 'function') {
|
|
60
58
|
return entry;
|
|
61
59
|
}
|
|
60
|
+
// Import path string — validate and dynamically import
|
|
62
61
|
if (typeof entry === 'string') {
|
|
63
|
-
if (!isAllowedImportPath(entry)) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
`
|
|
66
|
-
`
|
|
67
|
-
);
|
|
62
|
+
if (!permissions?.crossDomainImports && !isAllowedImportPath(entry)) {
|
|
63
|
+
throw new Error(`assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
|
|
64
|
+
`Only relative, absolute, or bare specifier paths are allowed (no cross-domain URLs). ` +
|
|
65
|
+
`Pass { crossDomainImports: true } in permissions to override.`);
|
|
68
66
|
}
|
|
69
67
|
const module = await import(entry);
|
|
70
68
|
const HandlerClass = findHandlerInModule(module);
|
|
71
69
|
if (!HandlerClass) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`assignFrom: handler "${name}" — module "${entry}" does not export a valid handler class.`
|
|
74
|
-
);
|
|
70
|
+
throw new Error(`assignFrom: handler "${name}" — module "${entry}" does not export a valid handler class.`);
|
|
75
71
|
}
|
|
76
72
|
return HandlerClass;
|
|
77
73
|
}
|
|
@@ -84,8 +80,9 @@ async function resolveFromHandlers(name, handlers) {
|
|
|
84
80
|
* @param handlerKeys - Array of keys ending with ' =>'
|
|
85
81
|
* @param pattern - The original pattern object
|
|
86
82
|
* @param options - The assignFrom options
|
|
83
|
+
* @param handlerRegistry - The registry of handler classes
|
|
87
84
|
*/
|
|
88
|
-
export async function processHandlerCommands(target, handlerKeys, pattern, options) {
|
|
85
|
+
export async function processHandlerCommands(target, handlerKeys, pattern, options, permissions) {
|
|
89
86
|
for (const key of handlerKeys) {
|
|
90
87
|
const lhsPath = key.substring(0, key.length - 3); // Remove ' =>'
|
|
91
88
|
const rhs = pattern[key];
|
|
@@ -104,6 +101,7 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
|
|
|
104
101
|
if (configs.length === 0)
|
|
105
102
|
continue;
|
|
106
103
|
// Resolve the LHS path, preserving parent + key for return-value assignment.
|
|
104
|
+
// lhsParent[lhsKey] === lhsTarget (the current value at the path)
|
|
107
105
|
let lhsTarget;
|
|
108
106
|
let lhsParent = undefined;
|
|
109
107
|
let lhsKey = undefined;
|
|
@@ -119,13 +117,15 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
|
|
|
119
117
|
lhsParent = result.target;
|
|
120
118
|
lhsKey = result.lastKey;
|
|
121
119
|
lhsTarget = result.target[result.lastKey];
|
|
120
|
+
// If last key is a method, call it to get the target
|
|
122
121
|
if (result.isMethod && typeof result.target[result.lastKey] === 'function') {
|
|
123
122
|
lhsTarget = result.target[result.lastKey].call(result.target);
|
|
124
|
-
lhsParent = undefined;
|
|
123
|
+
lhsParent = undefined; // Can't assign back to a method call result
|
|
125
124
|
lhsKey = undefined;
|
|
126
125
|
}
|
|
127
126
|
}
|
|
128
127
|
else {
|
|
128
|
+
// Simple path navigation — walk to parent, keep last key
|
|
129
129
|
if (pathParts.length === 0) {
|
|
130
130
|
lhsTarget = target;
|
|
131
131
|
}
|
|
@@ -158,7 +158,7 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
|
|
|
158
158
|
// Execute handlers sequentially, sharing the same lhsTarget
|
|
159
159
|
for (const config of configs) {
|
|
160
160
|
// 1. Check options.handlers (local, per-call)
|
|
161
|
-
let HandlerClass = await resolveFromHandlers(config.do, options.handlers);
|
|
161
|
+
let HandlerClass = await resolveFromHandlers(config.do, options.handlers, permissions);
|
|
162
162
|
// 2. Fallback to built-in auto-load
|
|
163
163
|
if (!HandlerClass && config.do.startsWith('builtIns.')) {
|
|
164
164
|
HandlerClass = await loadBuiltIn(config.do);
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import { resolveValues } from './resolveValues.js';
|
|
8
8
|
import { evaluatePathWithMethods } from './assignGingerly.js';
|
|
9
|
+
import { isAllowedImportPath } from './isAllowedImportPath.js';
|
|
10
|
+
import type { AssignPermissions } from './isAllowedImportPath.js';
|
|
9
11
|
import type { AssignFromOptions, AssignFromHandlerConstructor } from './assignFrom.js';
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -19,14 +21,6 @@ const BUILT_IN_MAP: Record<string, string> = {
|
|
|
19
21
|
'builtIns.microDataJoin': './handlers/microDataJoin.js',
|
|
20
22
|
};
|
|
21
23
|
|
|
22
|
-
/**
|
|
23
|
-
* Check if an import path is allowed (non-cross-domain).
|
|
24
|
-
*/
|
|
25
|
-
function isAllowedImportPath(path: string): boolean {
|
|
26
|
-
return path.startsWith('./') || path.startsWith('../') || path.startsWith('/')
|
|
27
|
-
|| (!path.includes('://') && !path.startsWith('//'));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
24
|
/**
|
|
31
25
|
* Find a handler class in a dynamically imported module.
|
|
32
26
|
* Checks default export first, then searches for the first class with `assign` on prototype.
|
|
@@ -63,7 +57,8 @@ async function loadBuiltIn(name: string): Promise<AssignFromHandlerConstructor |
|
|
|
63
57
|
*/
|
|
64
58
|
async function resolveFromHandlers(
|
|
65
59
|
name: string,
|
|
66
|
-
handlers: Record<string, AssignFromHandlerConstructor | string> | undefined
|
|
60
|
+
handlers: Record<string, AssignFromHandlerConstructor | string> | undefined,
|
|
61
|
+
permissions?: AssignPermissions
|
|
67
62
|
): Promise<AssignFromHandlerConstructor | undefined> {
|
|
68
63
|
if (!handlers || !(name in handlers)) return undefined;
|
|
69
64
|
|
|
@@ -76,10 +71,11 @@ async function resolveFromHandlers(
|
|
|
76
71
|
|
|
77
72
|
// Import path string — validate and dynamically import
|
|
78
73
|
if (typeof entry === 'string') {
|
|
79
|
-
if (!isAllowedImportPath(entry)) {
|
|
74
|
+
if (!permissions?.crossDomainImports && !isAllowedImportPath(entry)) {
|
|
80
75
|
throw new Error(
|
|
81
76
|
`assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
|
|
82
|
-
`Only relative, absolute, or bare specifier paths are allowed (no cross-domain URLs)
|
|
77
|
+
`Only relative, absolute, or bare specifier paths are allowed (no cross-domain URLs). ` +
|
|
78
|
+
`Pass { crossDomainImports: true } in permissions to override.`
|
|
83
79
|
);
|
|
84
80
|
}
|
|
85
81
|
const module = await import(entry);
|
|
@@ -108,7 +104,8 @@ export async function processHandlerCommands(
|
|
|
108
104
|
target: any,
|
|
109
105
|
handlerKeys: string[],
|
|
110
106
|
pattern: Record<string, any>,
|
|
111
|
-
options: AssignFromOptions
|
|
107
|
+
options: AssignFromOptions,
|
|
108
|
+
permissions?: AssignPermissions
|
|
112
109
|
): Promise<void> {
|
|
113
110
|
for (const key of handlerKeys) {
|
|
114
111
|
const lhsPath = key.substring(0, key.length - 3); // Remove ' =>'
|
|
@@ -185,7 +182,7 @@ export async function processHandlerCommands(
|
|
|
185
182
|
// Execute handlers sequentially, sharing the same lhsTarget
|
|
186
183
|
for (const config of configs) {
|
|
187
184
|
// 1. Check options.handlers (local, per-call)
|
|
188
|
-
let HandlerClass = await resolveFromHandlers(config.do, options.handlers);
|
|
185
|
+
let HandlerClass = await resolveFromHandlers(config.do, options.handlers, permissions);
|
|
189
186
|
|
|
190
187
|
// 2. Fallback to built-in auto-load
|
|
191
188
|
if (!HandlerClass && config.do.startsWith('builtIns.')) {
|
package/resolveIdRef.js
CHANGED
|
@@ -1,125 +1,144 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* resolveIdRef.
|
|
3
|
-
*
|
|
4
|
-
* Dynamically imported by assignFrom when `withIds` is provided or `#[x]` patterns are detected.
|
|
5
|
-
* Provides lazy, WeakRef-cached element lookups keyed by variable name.
|
|
6
|
-
*
|
|
7
|
-
* First access: runs the query against the target, auto-assigns an ID if needed, caches via WeakRef.
|
|
8
|
-
* Subsequent access: WeakRef.deref() (~10ns) or getElementById fallback (~20-100ns).
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
1
|
+
/**
|
|
2
|
+
* resolveIdRef.ts — Cached element resolution via #[x] syntax.
|
|
3
|
+
*
|
|
4
|
+
* Dynamically imported by assignFrom when `withIds` is provided or `#[x]` patterns are detected.
|
|
5
|
+
* Provides lazy, WeakRef-cached element lookups keyed by variable name.
|
|
6
|
+
*
|
|
7
|
+
* First access: runs the query against the target, auto-assigns an ID if needed, caches via WeakRef.
|
|
8
|
+
* Subsequent access: WeakRef.deref() (~10ns) or getElementById fallback (~20-100ns).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Module-level cache: rootNode → Map<varName, { id, WeakRef }>
|
|
12
|
+
* WeakMap ensures cleanup when rootNode is GC'd.
|
|
13
|
+
*/
|
|
14
|
+
const idCacheMap = new WeakMap();
|
|
15
|
+
/**
|
|
16
|
+
* Counter per rootNode for generating unique IDs.
|
|
17
|
+
*/
|
|
18
|
+
const idCounterMap = new WeakMap();
|
|
19
|
+
/**
|
|
20
|
+
* Generate a unique ID within a rootNode.
|
|
21
|
+
* Format: _ag0, _ag1, _ag2, ...
|
|
22
|
+
*/
|
|
23
|
+
function generateUniqueId(rootNode) {
|
|
24
|
+
let counter = idCounterMap.get(rootNode) ?? 0;
|
|
25
|
+
let id;
|
|
26
|
+
// Ensure uniqueness (skip if ID already exists in the document)
|
|
27
|
+
do {
|
|
28
|
+
id = `_ag${counter}`;
|
|
29
|
+
counter++;
|
|
30
|
+
} while (rootNode.getElementById?.(id));
|
|
31
|
+
idCounterMap.set(rootNode, counter);
|
|
32
|
+
return id;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve a single #[varName] reference lazily.
|
|
36
|
+
*
|
|
37
|
+
* @param varName - The variable name (e.g., 'x' from '#[x]')
|
|
38
|
+
* @param target - The target element to query against
|
|
39
|
+
* @param withIds - The withIds configuration map
|
|
40
|
+
* @returns The resolved element, or undefined if not found
|
|
41
|
+
*/
|
|
42
|
+
export function resolveIdVariable(varName, target, withIds) {
|
|
43
|
+
const config = withIds[varName];
|
|
44
|
+
if (config === undefined)
|
|
45
|
+
return undefined;
|
|
46
|
+
const rootNode = target.getRootNode?.() ?? target;
|
|
47
|
+
// Get or create cache for this rootNode
|
|
48
|
+
let cache = idCacheMap.get(rootNode);
|
|
49
|
+
if (!cache) {
|
|
50
|
+
cache = new Map();
|
|
51
|
+
idCacheMap.set(rootNode, cache);
|
|
52
|
+
}
|
|
53
|
+
// Check cache first
|
|
54
|
+
const cached = cache.get(varName);
|
|
55
|
+
if (cached) {
|
|
56
|
+
const el = cached.ref.deref();
|
|
57
|
+
if (el)
|
|
58
|
+
return el;
|
|
59
|
+
// WeakRef was collected — try getElementById fallback
|
|
60
|
+
const el2 = rootNode.getElementById?.(cached.id);
|
|
61
|
+
if (el2) {
|
|
62
|
+
cache.set(varName, { id: cached.id, ref: new WeakRef(el2) });
|
|
63
|
+
return el2;
|
|
64
|
+
}
|
|
65
|
+
// Element no longer exists — fall through to re-query
|
|
66
|
+
}
|
|
67
|
+
// First time or cache miss — resolve the element
|
|
68
|
+
let el = null;
|
|
69
|
+
if (typeof config === 'string') {
|
|
70
|
+
// String form: existing ID — use getElementById directly
|
|
71
|
+
el = rootNode.getElementById?.(config) ?? null;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Object form: { qry } — run querySelector against target
|
|
75
|
+
el = target.querySelector?.(config.qry) ?? null;
|
|
76
|
+
}
|
|
77
|
+
if (!el)
|
|
78
|
+
return undefined;
|
|
79
|
+
// Ensure the element has an ID
|
|
80
|
+
let id = el.id;
|
|
81
|
+
if (!id) {
|
|
82
|
+
id = generateUniqueId(rootNode);
|
|
83
|
+
el.id = id;
|
|
84
|
+
}
|
|
85
|
+
// Cache it
|
|
86
|
+
cache.set(varName, { id, ref: new WeakRef(el) });
|
|
87
|
+
return el;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if a key starts with #[...] syntax.
|
|
91
|
+
*/
|
|
92
|
+
export function hasIdRef(key) {
|
|
93
|
+
return key.startsWith('#[');
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Extract the variable name and remaining path from a #[x]... key.
|
|
97
|
+
* Returns [varName, remainingPath] or null if not a valid #[x] reference.
|
|
98
|
+
*
|
|
99
|
+
* Examples:
|
|
100
|
+
* '#[x]' → ['x', '']
|
|
101
|
+
* '#[x] =>' → ['x', ''] (handler suffix handled separately)
|
|
102
|
+
* '#[x]?.querySelector?..child' → ['x', '?.querySelector?..child']
|
|
103
|
+
* '#[x]?.textContent =>' → ['x', '?.textContent'] (handler suffix handled separately)
|
|
104
|
+
*/
|
|
105
|
+
export function parseIdRef(key) {
|
|
106
|
+
const closeIdx = key.indexOf(']');
|
|
107
|
+
if (closeIdx === -1)
|
|
108
|
+
return null;
|
|
109
|
+
const varName = key.substring(2, closeIdx); // skip '#['
|
|
110
|
+
if (!varName)
|
|
111
|
+
return null;
|
|
112
|
+
let remainingPath = key.substring(closeIdx + 1);
|
|
113
|
+
// Remove handler suffix if present (caller handles it separately)
|
|
114
|
+
if (remainingPath.endsWith(' =>')) {
|
|
115
|
+
remainingPath = remainingPath.substring(0, remainingPath.length - 3);
|
|
116
|
+
}
|
|
117
|
+
return { varName, remainingPath };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Register an element in the WeakRef cache for fast subsequent access.
|
|
121
|
+
* Auto-assigns an ID if the element doesn't have one.
|
|
122
|
+
*
|
|
123
|
+
* Used by beVigilant to cache newly discovered elements so that future
|
|
124
|
+
* assignFrom calls can resolve them via getElementById/WeakRef.
|
|
125
|
+
*
|
|
126
|
+
* @param target - The target element (used to determine rootNode)
|
|
127
|
+
* @param varName - The cache key (typically the itemprop name)
|
|
128
|
+
* @param element - The element to cache
|
|
129
|
+
*/
|
|
130
|
+
export function registerInCache(target, varName, element) {
|
|
131
|
+
const rootNode = target.getRootNode?.() ?? target;
|
|
132
|
+
let cache = idCacheMap.get(rootNode);
|
|
133
|
+
if (!cache) {
|
|
134
|
+
cache = new Map();
|
|
135
|
+
idCacheMap.set(rootNode, cache);
|
|
136
|
+
}
|
|
137
|
+
// Ensure the element has an ID
|
|
138
|
+
let id = element.id;
|
|
139
|
+
if (!id) {
|
|
140
|
+
id = generateUniqueId(rootNode);
|
|
141
|
+
element.id = id;
|
|
142
|
+
}
|
|
143
|
+
cache.set(varName, { id, ref: new WeakRef(element) });
|
|
144
|
+
}
|
package/resolveIdRef.ts
CHANGED
|
@@ -138,3 +138,33 @@ export function parseIdRef(key: string): { varName: string; remainingPath: strin
|
|
|
138
138
|
|
|
139
139
|
return { varName, remainingPath };
|
|
140
140
|
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Register an element in the WeakRef cache for fast subsequent access.
|
|
144
|
+
* Auto-assigns an ID if the element doesn't have one.
|
|
145
|
+
*
|
|
146
|
+
* Used by beVigilant to cache newly discovered elements so that future
|
|
147
|
+
* assignFrom calls can resolve them via getElementById/WeakRef.
|
|
148
|
+
*
|
|
149
|
+
* @param target - The target element (used to determine rootNode)
|
|
150
|
+
* @param varName - The cache key (typically the itemprop name)
|
|
151
|
+
* @param element - The element to cache
|
|
152
|
+
*/
|
|
153
|
+
export function registerInCache(target: any, varName: string, element: Element): void {
|
|
154
|
+
const rootNode = target.getRootNode?.() ?? target;
|
|
155
|
+
|
|
156
|
+
let cache = idCacheMap.get(rootNode);
|
|
157
|
+
if (!cache) {
|
|
158
|
+
cache = new Map();
|
|
159
|
+
idCacheMap.set(rootNode, cache);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Ensure the element has an ID
|
|
163
|
+
let id = element.id;
|
|
164
|
+
if (!id) {
|
|
165
|
+
id = generateUniqueId(rootNode);
|
|
166
|
+
element.id = id;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
cache.set(varName, { id, ref: new WeakRef(element) });
|
|
170
|
+
}
|
|
@@ -64,6 +64,12 @@ export interface EnhancementConfig<T = any, Obj = Element> extends EnhancementCo
|
|
|
64
64
|
|
|
65
65
|
//only applicable when spawning from a DOM Element reference
|
|
66
66
|
enhKey?: EnhKey;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Optional features to associate with the spawn class.
|
|
70
|
+
* Calls assignFeatures(spawn, features) automatically on registration.
|
|
71
|
+
*/
|
|
72
|
+
features?: FeatureConfigsMap;
|
|
67
73
|
|
|
68
74
|
}
|
|
69
75
|
|
|
@@ -300,6 +306,12 @@ export interface ItemscopeManagerConfig<T = any> {
|
|
|
300
306
|
dispose?: string | symbol;
|
|
301
307
|
resolved?: string | symbol;
|
|
302
308
|
};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Optional features to associate with the manager class.
|
|
312
|
+
* Calls assignFeatures(manager, features) automatically on registration.
|
|
313
|
+
*/
|
|
314
|
+
features?: FeatureConfigsMap;
|
|
303
315
|
}
|
|
304
316
|
|
|
305
317
|
/**
|
|
@@ -526,15 +538,90 @@ export interface LazyLoadConfig extends HandlerConfig {
|
|
|
526
538
|
if: string;
|
|
527
539
|
/** Template element to clone (resolved via protocol or path) */
|
|
528
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;
|
|
529
600
|
/** Insert method: 'appendChild' (default) or 'prepend' */
|
|
530
601
|
method?: string;
|
|
531
602
|
/** If true, removes nodes when hiding instead of adding hidden attribute */
|
|
532
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;
|
|
533
608
|
/** Optional async callback invoked after cloning, resolved from the VM */
|
|
534
609
|
onInstantiated?: string;
|
|
535
610
|
};
|
|
536
611
|
}
|
|
537
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
|
+
|
|
538
625
|
/**
|
|
539
626
|
* Context passed to onInstantiated callbacks after template cloning.
|
|
540
627
|
*/
|