assign-gingerly 0.0.63 → 0.0.65
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 +59 -18
- package/assignFrom.js +21 -19
- package/assignFrom.ts +26 -24
- package/assignFromAsync.js +10 -7
- package/assignFromAsync.ts +13 -96
- package/assignGingerly.js +3 -17
- package/assignGingerly.ts +10 -18
- package/assignTentatively.ts +3 -4
- package/getValues.js +81 -25
- package/getValues.ts +85 -26
- package/handlers/arr.js +13 -0
- package/handlers/arr.ts +13 -0
- package/handlers/manageTemplateList.js +1 -1
- package/handlers/manageTemplateList.ts +1 -1
- package/handlers/nudge.js +23 -0
- package/handlers/nudge.ts +23 -0
- package/index.js +2 -0
- package/index.ts +2 -0
- package/inferencer/types/assign-gingerly/types.d.ts +269 -1
- package/inferencer/types/mount-observer/types.d.ts +10 -2
- package/inferencer/types/roundabout/types.d.ts +1 -1
- package/inferencer/types/three-peat/types.d.ts +18 -0
- package/object-extension.js +1 -1
- package/object-extension.ts +4 -3
- package/package.json +9 -1
- package/paths.ts +1 -1
- package/{withIdsCorrector.js → pinCorrector.js} +5 -5
- package/{withIdsCorrector.ts → pinCorrector.ts} +5 -5
- package/processHandlerCommands.js +4 -2
- package/processHandlerCommands.ts +4 -2
- package/resolveIdRef.js +8 -8
- package/resolveIdRef.ts +9 -9
- package/resolveValues.js +20 -88
- package/resolveValues.ts +17 -91
- package/types/assign-gingerly/types.d.ts +168 -0
package/README.md
CHANGED
|
@@ -94,10 +94,24 @@ For most use cases — including `manageTemplateList`, reactive merge cycles, an
|
|
|
94
94
|
assignFrom adds support for:
|
|
95
95
|
|
|
96
96
|
1. Resolving RHS path strings against a source object (`from`).
|
|
97
|
-
2.
|
|
98
|
-
3.
|
|
99
|
-
4.
|
|
100
|
-
5.
|
|
97
|
+
2. Target-relative root references via `$0` so paths can resolve from the first argument passed to `assignFrom`/`assignFromAsync` (the target object) instead of the `from` object.
|
|
98
|
+
3. Protocol resolution (`globalThis://`, `localStorage://`, custom sync protocols).
|
|
99
|
+
4. Handler plugins via the ` =>` operator for custom logic (fire-and-forget in sync mode, awaitable in async mode).
|
|
100
|
+
5. Looped substitution with `where_x_in` / `where_y_in` / `where_z_in` for expanding template patterns into multiple concrete assignments.
|
|
101
|
+
6. Spread merging via the `"..."` key.
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
|
|
105
|
+
```TypeScript
|
|
106
|
+
const target = { value: 'target-value' };
|
|
107
|
+
const source = { value: 'from-value' };
|
|
108
|
+
|
|
109
|
+
assignFrom(target, {
|
|
110
|
+
resolved: '$0?.value'
|
|
111
|
+
}, { from: source });
|
|
112
|
+
|
|
113
|
+
console.log(target.resolved); // 'target-value'
|
|
114
|
+
```
|
|
101
115
|
|
|
102
116
|
All of assignGingerly's features (nested paths, `withMethods`, `aka`, `@each`, `@eachTime`, registry, etc.) are inherited.
|
|
103
117
|
|
|
@@ -476,11 +490,11 @@ assignGingerly(div, {
|
|
|
476
490
|
|
|
477
491
|
// With aliases (concise)
|
|
478
492
|
assignGingerly(div, {
|
|
479
|
-
'
|
|
480
|
-
'
|
|
493
|
+
'?.🔍?.my-element?.🎨?.+': 'highlighted',
|
|
494
|
+
'?.🔍?.your-element?.🎨?.+': 'active'
|
|
481
495
|
}, {
|
|
482
496
|
withMethods: ['querySelector', 'add'],
|
|
483
|
-
aka: { '
|
|
497
|
+
aka: { '🔍': 'querySelector', '🎨': 'classList', '+': 'add' }
|
|
484
498
|
});
|
|
485
499
|
```
|
|
486
500
|
|
|
@@ -536,6 +550,33 @@ assignGingerly(element, {
|
|
|
536
550
|
- Improves readability when you have many similar operations
|
|
537
551
|
- Works seamlessly with `withMethods`
|
|
538
552
|
|
|
553
|
+
**Shorthand for method aliases:**
|
|
554
|
+
|
|
555
|
+
If you frequently pair aliases with method calls, you can use `akaMethods` as a compact alternative to passing both `withMethods` and `aka` separately. Each entry in `akaMethods` maps an alias to the method name that should be treated as a callable method:
|
|
556
|
+
|
|
557
|
+
```TypeScript
|
|
558
|
+
const div = document.createElement('div');
|
|
559
|
+
|
|
560
|
+
div.innerHTML = `
|
|
561
|
+
<my-element></my-element>
|
|
562
|
+
`;
|
|
563
|
+
|
|
564
|
+
assignGingerly(div, {
|
|
565
|
+
'?.🔍?.my-element?.🎨?.+': 'highlighted'
|
|
566
|
+
}, {
|
|
567
|
+
akaMethods: {
|
|
568
|
+
'🔍': 'querySelector',
|
|
569
|
+
'🎨': 'classList',
|
|
570
|
+
'+': 'add'
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const myElement = div.querySelector('my-element');
|
|
575
|
+
console.log(myElement?.classList.contains('highlighted')); // true
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
`akaMethods` is additive: it complements the existing `withMethods` and `aka` options rather than replacing them.
|
|
579
|
+
|
|
539
580
|
## Example 3e - ForEach with @each
|
|
540
581
|
|
|
541
582
|
The `@each` symbol allows you to iterate over collections and apply operations to each item. This works with any iterable including Arrays, NodeList, HTMLCollection, and more.
|
|
@@ -4387,7 +4428,7 @@ md`${$.firstName} ${{ prop: 'birthDate', val: $.birthDT, format: 'long' }}`
|
|
|
4387
4428
|
|
|
4388
4429
|
Both auto-detect path proxies (no `.path` needed inside template literals) and preserve nested arrays for optional segments.
|
|
4389
4430
|
|
|
4390
|
-
## Cached Element Resolution with `#[x]` and `
|
|
4431
|
+
## Cached Element Resolution with `#[x]` and `pin`
|
|
4391
4432
|
|
|
4392
4433
|
`assignFrom` supports cached element references via the `#[x]` syntax in LHS keys. This provides near-zero-cost repeated access to DOM elements (~10ns via WeakRef) instead of expensive `querySelector` calls (~3,000-17,000ns for class selectors at scale).
|
|
4393
4434
|
|
|
@@ -4402,7 +4443,7 @@ await assignFromAsync(document.body, {
|
|
|
4402
4443
|
}
|
|
4403
4444
|
}, {
|
|
4404
4445
|
from: viewModel,
|
|
4405
|
-
|
|
4446
|
+
pin: {
|
|
4406
4447
|
main: { qry: '.mainView' } // find by class, auto-assign ID, cache
|
|
4407
4448
|
}
|
|
4408
4449
|
});
|
|
@@ -4416,12 +4457,12 @@ await assignFromAsync(document.body, {
|
|
|
4416
4457
|
4. On subsequent calls, the cached `WeakRef.deref()` returns the element in ~10ns.
|
|
4417
4458
|
5. If the WeakRef is collected (element was GC'd), falls back to `getElementById` (~10-100ns).
|
|
4418
4459
|
|
|
4419
|
-
**`
|
|
4460
|
+
**`pin` — stable references with auto-assigned IDs:**
|
|
4420
4461
|
|
|
4421
4462
|
All forms assign a unique ID to the resolved element (if it doesn't have one). This makes the reference resilient to future DOM mutations — once an element has an ID, it can be found regardless of structural changes.
|
|
4422
4463
|
|
|
4423
4464
|
```TypeScript
|
|
4424
|
-
|
|
4465
|
+
pin: {
|
|
4425
4466
|
x: { qry: '.mainView' }, // querySelector on target, auto-assign ID
|
|
4426
4467
|
y: 'existingId', // element already has an ID, cache via WeakRef
|
|
4427
4468
|
z: { path: [0, 1], expect: 'input', fallback: true }, // child index path + auto-ID + validation
|
|
@@ -4451,14 +4492,14 @@ at: {
|
|
|
4451
4492
|
- You want a clean DOM (no auto-generated `id` attributes)
|
|
4452
4493
|
- You're rendering many items (1,000 rows × 2 refs = 2,000 fewer DOM attributes)
|
|
4453
4494
|
|
|
4454
|
-
Both `
|
|
4495
|
+
Both `pin` and `at` use the same `#[x]` syntax on LHS and RHS keys.
|
|
4455
4496
|
|
|
4456
4497
|
**Validated paths with `expect` and `fallback`:**
|
|
4457
4498
|
|
|
4458
|
-
Both `
|
|
4499
|
+
Both `pin` and `at` support the `{ path, expect, fallback }` form for dev-time validation:
|
|
4459
4500
|
|
|
4460
4501
|
```TypeScript
|
|
4461
|
-
|
|
4502
|
+
pin: {
|
|
4462
4503
|
nameInput: { path: [1], expect: 'input' }, // warn if [1] isn't an <input>
|
|
4463
4504
|
label: { path: [0], expect: 'label', fallback: true } // warn + recover via querySelector
|
|
4464
4505
|
}
|
|
@@ -4481,7 +4522,7 @@ assignFrom(document.body, {
|
|
|
4481
4522
|
}, {
|
|
4482
4523
|
from: viewModel,
|
|
4483
4524
|
withMethods: ['querySelector'],
|
|
4484
|
-
|
|
4525
|
+
pin: {
|
|
4485
4526
|
form: { qry: '.registration-form' },
|
|
4486
4527
|
header: 'page-header'
|
|
4487
4528
|
}
|
|
@@ -4498,7 +4539,7 @@ await assignFromAsync(container, {
|
|
|
4498
4539
|
}
|
|
4499
4540
|
}, {
|
|
4500
4541
|
from: router,
|
|
4501
|
-
|
|
4542
|
+
pin: { outlet: { qry: '.router-outlet' } },
|
|
4502
4543
|
protocols: { globalThis: k => globalThis[k] }
|
|
4503
4544
|
});
|
|
4504
4545
|
```
|
|
@@ -4511,7 +4552,7 @@ await assignFromAsync(container, {
|
|
|
4511
4552
|
| `getElementById(id)` | 100ns | 20ns | 10ns |
|
|
4512
4553
|
| `Map<id, WeakRef>.deref()` | 12ns | 40ns | 10ns |
|
|
4513
4554
|
|
|
4514
|
-
The `#[x]` + `
|
|
4555
|
+
The `#[x]` + `pin` pattern gives you the Map+WeakRef speed tier automatically.
|
|
4515
4556
|
|
|
4516
4557
|
**RHS references (`#[x]` on the value side):**
|
|
4517
4558
|
|
|
@@ -4524,7 +4565,7 @@ assignFrom(form, {
|
|
|
4524
4565
|
'?.headerText': '#[info]?.dataset?.user', // → nested property access
|
|
4525
4566
|
}, {
|
|
4526
4567
|
from: {},
|
|
4527
|
-
|
|
4568
|
+
pin: { nameInput: { qry: 'input' }, info: { qry: '.info' } },
|
|
4528
4569
|
withMethods: ['querySelector']
|
|
4529
4570
|
});
|
|
4530
4571
|
```
|
package/assignFrom.js
CHANGED
|
@@ -49,7 +49,8 @@ function resolveTernaryValue(value, source, options) {
|
|
|
49
49
|
return getValue(value, source, {
|
|
50
50
|
withMethods: options.withMethods,
|
|
51
51
|
aka: options.aka,
|
|
52
|
-
protocols: options.protocols
|
|
52
|
+
protocols: options.protocols,
|
|
53
|
+
root: options.root
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
56
|
if (typeof value === 'string' && value.includes('://') && options.protocols) {
|
|
@@ -60,7 +61,8 @@ function resolveTernaryValue(value, source, options) {
|
|
|
60
61
|
return getValue(value, source, {
|
|
61
62
|
withMethods: options.withMethods,
|
|
62
63
|
aka: options.aka,
|
|
63
|
-
protocols: options.protocols
|
|
64
|
+
protocols: options.protocols,
|
|
65
|
+
root: options.root
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -287,16 +289,16 @@ export function categorizeKeys(expandedPattern) {
|
|
|
287
289
|
return { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys, ternaryKeys };
|
|
288
290
|
}
|
|
289
291
|
/**
|
|
290
|
-
* Merge
|
|
292
|
+
* Merge pin and at into a single lookup map for resolveIdVariable.
|
|
291
293
|
*/
|
|
292
294
|
function getEffectiveIds(options) {
|
|
293
|
-
if (!options.
|
|
295
|
+
if (!options.pin && !options.at)
|
|
294
296
|
return undefined;
|
|
295
|
-
if (options.
|
|
296
|
-
return options.
|
|
297
|
-
if (!options.
|
|
297
|
+
if (options.pin && !options.at)
|
|
298
|
+
return options.pin;
|
|
299
|
+
if (!options.pin && options.at)
|
|
298
300
|
return options.at;
|
|
299
|
-
return { ...options.
|
|
301
|
+
return { ...options.pin, ...options.at };
|
|
300
302
|
}
|
|
301
303
|
/**
|
|
302
304
|
* Process #[x] normal keys synchronously.
|
|
@@ -305,6 +307,7 @@ function processIdRefNormalKeys(idRefNormalKeys, expandedPattern, target, option
|
|
|
305
307
|
const ids = getEffectiveIds(options);
|
|
306
308
|
if (!ids)
|
|
307
309
|
return;
|
|
310
|
+
//TODO
|
|
308
311
|
for (const key of idRefNormalKeys) {
|
|
309
312
|
const parsed = parseIdRef(key);
|
|
310
313
|
if (!parsed)
|
|
@@ -314,11 +317,11 @@ function processIdRefNormalKeys(idRefNormalKeys, expandedPattern, target, option
|
|
|
314
317
|
continue;
|
|
315
318
|
const value = expandedPattern[key];
|
|
316
319
|
if (parsed.remainingPath) {
|
|
317
|
-
const resolvedValue = getValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
320
|
+
const resolvedValue = getValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: target });
|
|
318
321
|
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
319
322
|
}
|
|
320
323
|
else {
|
|
321
|
-
const resolvedValue = getValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
324
|
+
const resolvedValue = getValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: target });
|
|
322
325
|
if (!('__v' in resolvedValue)) {
|
|
323
326
|
assignGingerly(el, resolvedValue, options);
|
|
324
327
|
}
|
|
@@ -342,6 +345,7 @@ export function assignFrom(target, pattern, options, permissions) {
|
|
|
342
345
|
const expandedPattern = expandSubstitutions(pattern, options);
|
|
343
346
|
// Categorize keys
|
|
344
347
|
const { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys, ternaryKeys } = categorizeKeys(expandedPattern);
|
|
348
|
+
const resolveOptions = { ...options, root: target };
|
|
345
349
|
// Process ?= ternary keys (sync)
|
|
346
350
|
if (ternaryKeys.length > 0) {
|
|
347
351
|
const ternaryResolved = {};
|
|
@@ -352,7 +356,7 @@ export function assignFrom(target, pattern, options, permissions) {
|
|
|
352
356
|
const arr = expandedPattern[key];
|
|
353
357
|
if (!Array.isArray(arr) || arr.length < 2)
|
|
354
358
|
continue;
|
|
355
|
-
const result = evaluateTernary(arr, options.from,
|
|
359
|
+
const result = evaluateTernary(arr, options.from, resolveOptions);
|
|
356
360
|
if (result !== TERNARY_SKIP) {
|
|
357
361
|
ternaryResolved[lhsPath] = result;
|
|
358
362
|
}
|
|
@@ -364,7 +368,7 @@ export function assignFrom(target, pattern, options, permissions) {
|
|
|
364
368
|
// Process normal keys via getValues (sync) + assignGingerly
|
|
365
369
|
if (Object.keys(normalPattern).length > 0) {
|
|
366
370
|
// Resolve #[x] references on RHS values before getValues
|
|
367
|
-
if (options.
|
|
371
|
+
if (options.pin || options.at) {
|
|
368
372
|
const ids = getEffectiveIds(options);
|
|
369
373
|
for (const key of Object.keys(normalPattern)) {
|
|
370
374
|
const value = normalPattern[key];
|
|
@@ -379,7 +383,9 @@ export function assignFrom(target, pattern, options, permissions) {
|
|
|
379
383
|
normalPattern[key] = getValue(remainingPath, el, {
|
|
380
384
|
withMethods: options.withMethods,
|
|
381
385
|
aka: options.aka,
|
|
382
|
-
|
|
386
|
+
akaMethods: options.akaMethods,
|
|
387
|
+
protocols: options.protocols,
|
|
388
|
+
root: target
|
|
383
389
|
});
|
|
384
390
|
}
|
|
385
391
|
else {
|
|
@@ -390,11 +396,7 @@ export function assignFrom(target, pattern, options, permissions) {
|
|
|
390
396
|
}
|
|
391
397
|
}
|
|
392
398
|
}
|
|
393
|
-
const resolved = getValues(normalPattern, options.from,
|
|
394
|
-
withMethods: options.withMethods,
|
|
395
|
-
aka: options.aka,
|
|
396
|
-
protocols: options.protocols
|
|
397
|
-
});
|
|
399
|
+
const resolved = getValues(normalPattern, options.from, resolveOptions);
|
|
398
400
|
handleSpreads(resolved);
|
|
399
401
|
assignGingerly(target, resolved, options);
|
|
400
402
|
}
|
|
@@ -409,7 +411,7 @@ export function assignFrom(target, pattern, options, permissions) {
|
|
|
409
411
|
});
|
|
410
412
|
}
|
|
411
413
|
// Process #[x] handler keys — fire-and-forget (async)
|
|
412
|
-
if (idRefHandlerKeys.length > 0 && (options.
|
|
414
|
+
if (idRefHandlerKeys.length > 0 && (options.pin || options.at)) {
|
|
413
415
|
const ids = getEffectiveIds(options);
|
|
414
416
|
import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
|
|
415
417
|
for (const key of idRefHandlerKeys) {
|
package/assignFrom.ts
CHANGED
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { getValues, getValue } from './getValues.js';
|
|
13
|
-
import assignGingerly
|
|
13
|
+
import assignGingerly from './assignGingerly.js';
|
|
14
14
|
import { resolveIdVariable, parseIdRef } from './resolveIdRef.js';
|
|
15
15
|
import { processInferredAssignments } from './inferredAssignments.js';
|
|
16
16
|
import type { AssignPermissions } from './isAllowedImportPath.js';
|
|
17
|
+
import type { AssignFromOptions, AssignFromHandler, AssignFromHandlerConstructor } from './types/assign-gingerly/types.js';
|
|
17
18
|
|
|
18
|
-
// Re-export types
|
|
19
|
-
export type { AssignFromOptions, AssignFromHandler, AssignFromHandlerConstructor }
|
|
20
|
-
import type { AssignFromOptions } from './assignFromAsync.js';
|
|
19
|
+
// Re-export types for consumers
|
|
20
|
+
export type { AssignFromOptions, AssignFromHandler, AssignFromHandlerConstructor };
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Supported substitution variables and their option keys.
|
|
@@ -54,12 +54,13 @@ export function parseTernaryCommand(key: string): string | null {
|
|
|
54
54
|
* Resolve a single value — if it's a `?.` path string, resolve against source.
|
|
55
55
|
* If it's a protocol string, resolve via protocol. Otherwise pass through as literal.
|
|
56
56
|
*/
|
|
57
|
-
function resolveTernaryValue(value: any, source: any, options: AssignFromOptions): any {
|
|
57
|
+
function resolveTernaryValue(value: any, source: any, options: AssignFromOptions | any): any {
|
|
58
58
|
if (typeof value === 'string' && value.startsWith('?.')) {
|
|
59
59
|
return getValue(value, source, {
|
|
60
60
|
withMethods: options.withMethods,
|
|
61
61
|
aka: options.aka,
|
|
62
|
-
protocols: options.protocols
|
|
62
|
+
protocols: options.protocols,
|
|
63
|
+
root: options.root
|
|
63
64
|
});
|
|
64
65
|
}
|
|
65
66
|
if (typeof value === 'string' && value.includes('://') && options.protocols) {
|
|
@@ -70,7 +71,8 @@ function resolveTernaryValue(value: any, source: any, options: AssignFromOptions
|
|
|
70
71
|
return getValue(value, source, {
|
|
71
72
|
withMethods: options.withMethods,
|
|
72
73
|
aka: options.aka,
|
|
73
|
-
protocols: options.protocols
|
|
74
|
+
protocols: options.protocols,
|
|
75
|
+
root: options.root
|
|
74
76
|
});
|
|
75
77
|
}
|
|
76
78
|
}
|
|
@@ -299,13 +301,13 @@ export function categorizeKeys(expandedPattern: Record<string, any>) {
|
|
|
299
301
|
}
|
|
300
302
|
|
|
301
303
|
/**
|
|
302
|
-
* Merge
|
|
304
|
+
* Merge pin and at into a single lookup map for resolveIdVariable.
|
|
303
305
|
*/
|
|
304
306
|
function getEffectiveIds(options: AssignFromOptions): Record<string, any> | undefined {
|
|
305
|
-
if (!options.
|
|
306
|
-
if (options.
|
|
307
|
-
if (!options.
|
|
308
|
-
return { ...options.
|
|
307
|
+
if (!options.pin && !options.at) return undefined;
|
|
308
|
+
if (options.pin && !options.at) return options.pin;
|
|
309
|
+
if (!options.pin && options.at) return options.at;
|
|
310
|
+
return { ...options.pin, ...options.at };
|
|
309
311
|
}
|
|
310
312
|
|
|
311
313
|
/**
|
|
@@ -319,7 +321,7 @@ function processIdRefNormalKeys(
|
|
|
319
321
|
): void {
|
|
320
322
|
const ids = getEffectiveIds(options);
|
|
321
323
|
if (!ids) return;
|
|
322
|
-
|
|
324
|
+
//TODO
|
|
323
325
|
for (const key of idRefNormalKeys) {
|
|
324
326
|
const parsed = parseIdRef(key);
|
|
325
327
|
if (!parsed) continue;
|
|
@@ -331,14 +333,14 @@ function processIdRefNormalKeys(
|
|
|
331
333
|
if (parsed.remainingPath) {
|
|
332
334
|
const resolvedValue = getValues(
|
|
333
335
|
{ __v: value }, options.from,
|
|
334
|
-
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
336
|
+
{ withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: target }
|
|
335
337
|
);
|
|
336
338
|
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
337
339
|
} else {
|
|
338
340
|
const resolvedValue = getValues(
|
|
339
341
|
typeof value === 'object' && value !== null ? value : { __v: value },
|
|
340
342
|
options.from,
|
|
341
|
-
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
343
|
+
{ withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: target }
|
|
342
344
|
);
|
|
343
345
|
if (!('__v' in resolvedValue)) {
|
|
344
346
|
assignGingerly(el, resolvedValue, options);
|
|
@@ -371,6 +373,8 @@ export function assignFrom(
|
|
|
371
373
|
// Categorize keys
|
|
372
374
|
const { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys, ternaryKeys } = categorizeKeys(expandedPattern);
|
|
373
375
|
|
|
376
|
+
const resolveOptions = { ...options, root: target } as any;
|
|
377
|
+
|
|
374
378
|
// Process ?= ternary keys (sync)
|
|
375
379
|
if (ternaryKeys.length > 0) {
|
|
376
380
|
const ternaryResolved: Record<string, any> = {};
|
|
@@ -379,7 +383,7 @@ export function assignFrom(
|
|
|
379
383
|
if (!lhsPath) continue;
|
|
380
384
|
const arr = expandedPattern[key];
|
|
381
385
|
if (!Array.isArray(arr) || arr.length < 2) continue;
|
|
382
|
-
const result = evaluateTernary(arr, options.from,
|
|
386
|
+
const result = evaluateTernary(arr, options.from, resolveOptions);
|
|
383
387
|
if (result !== TERNARY_SKIP) {
|
|
384
388
|
ternaryResolved[lhsPath] = result;
|
|
385
389
|
}
|
|
@@ -392,7 +396,7 @@ export function assignFrom(
|
|
|
392
396
|
// Process normal keys via getValues (sync) + assignGingerly
|
|
393
397
|
if (Object.keys(normalPattern).length > 0) {
|
|
394
398
|
// Resolve #[x] references on RHS values before getValues
|
|
395
|
-
if (options.
|
|
399
|
+
if (options.pin || options.at) {
|
|
396
400
|
const ids = getEffectiveIds(options)!;
|
|
397
401
|
for (const key of Object.keys(normalPattern)) {
|
|
398
402
|
const value = normalPattern[key];
|
|
@@ -407,7 +411,9 @@ export function assignFrom(
|
|
|
407
411
|
normalPattern[key] = getValue(remainingPath, el, {
|
|
408
412
|
withMethods: options.withMethods,
|
|
409
413
|
aka: options.aka,
|
|
410
|
-
|
|
414
|
+
akaMethods: options.akaMethods,
|
|
415
|
+
protocols: options.protocols,
|
|
416
|
+
root: target
|
|
411
417
|
});
|
|
412
418
|
} else {
|
|
413
419
|
normalPattern[key] = el.id; // bare #[x] → ID string
|
|
@@ -418,11 +424,7 @@ export function assignFrom(
|
|
|
418
424
|
}
|
|
419
425
|
}
|
|
420
426
|
|
|
421
|
-
const resolved = getValues(normalPattern, options.from,
|
|
422
|
-
withMethods: options.withMethods,
|
|
423
|
-
aka: options.aka,
|
|
424
|
-
protocols: options.protocols
|
|
425
|
-
});
|
|
427
|
+
const resolved = getValues(normalPattern, options.from, resolveOptions);
|
|
426
428
|
|
|
427
429
|
handleSpreads(resolved);
|
|
428
430
|
assignGingerly(target, resolved, options);
|
|
@@ -441,7 +443,7 @@ export function assignFrom(
|
|
|
441
443
|
}
|
|
442
444
|
|
|
443
445
|
// Process #[x] handler keys — fire-and-forget (async)
|
|
444
|
-
if (idRefHandlerKeys.length > 0 && (options.
|
|
446
|
+
if (idRefHandlerKeys.length > 0 && (options.pin || options.at)) {
|
|
445
447
|
const ids = getEffectiveIds(options)!;
|
|
446
448
|
import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
|
|
447
449
|
for (const key of idRefHandlerKeys) {
|
package/assignFromAsync.js
CHANGED
|
@@ -34,16 +34,19 @@ export async function assignFromAsync(target, pattern, options, permissions) {
|
|
|
34
34
|
const resolved = await resolveValues(normalPattern, options.from, {
|
|
35
35
|
withMethods: options.withMethods,
|
|
36
36
|
aka: options.aka,
|
|
37
|
-
|
|
37
|
+
akaMethods: options.akaMethods,
|
|
38
|
+
protocols: options.protocols,
|
|
39
|
+
root: target
|
|
38
40
|
});
|
|
39
41
|
// Recursively handle "..." spread keys at all nesting levels
|
|
40
42
|
handleSpreads(resolved);
|
|
41
43
|
assignGingerly(target, resolved, options);
|
|
42
44
|
}
|
|
43
45
|
// Process #[x] normal keys — resolve element, then apply remaining path + value
|
|
44
|
-
if (idRefNormalKeys.length > 0 && (options.
|
|
45
|
-
const ids = { ...options.
|
|
46
|
+
if (idRefNormalKeys.length > 0 && (options.pin || options.at)) {
|
|
47
|
+
const ids = { ...options.pin, ...options.at };
|
|
46
48
|
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
49
|
+
//TODO
|
|
47
50
|
for (const key of idRefNormalKeys) {
|
|
48
51
|
const parsed = parseIdRef(key);
|
|
49
52
|
if (!parsed)
|
|
@@ -54,13 +57,13 @@ export async function assignFromAsync(target, pattern, options, permissions) {
|
|
|
54
57
|
const value = expandedPattern[key];
|
|
55
58
|
if (parsed.remainingPath) {
|
|
56
59
|
// Resolve the RHS value
|
|
57
|
-
const resolvedValue = await resolveValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
60
|
+
const resolvedValue = await resolveValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: el });
|
|
58
61
|
// Apply remaining path on the resolved element
|
|
59
62
|
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
60
63
|
}
|
|
61
64
|
else {
|
|
62
65
|
// No remaining path — resolve and assign directly to the element
|
|
63
|
-
const resolvedValue = await resolveValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
66
|
+
const resolvedValue = await resolveValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: el });
|
|
64
67
|
if ('__v' in resolvedValue) {
|
|
65
68
|
// Single value — can't assign to element root without a path
|
|
66
69
|
}
|
|
@@ -76,8 +79,8 @@ export async function assignFromAsync(target, pattern, options, permissions) {
|
|
|
76
79
|
await _processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
|
|
77
80
|
}
|
|
78
81
|
// Process #[x] handler keys — resolve element, then pass to handler processing
|
|
79
|
-
if (idRefHandlerKeys.length > 0 && (options.
|
|
80
|
-
const ids = { ...options.
|
|
82
|
+
if (idRefHandlerKeys.length > 0 && (options.pin || options.at)) {
|
|
83
|
+
const ids = { ...options.pin, ...options.at };
|
|
81
84
|
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
82
85
|
_processHandlerCommands ??= (await import('./processHandlerCommands.js')).processHandlerCommands;
|
|
83
86
|
for (const key of idRefHandlerKeys) {
|
package/assignFromAsync.ts
CHANGED
|
@@ -22,117 +22,31 @@
|
|
|
22
22
|
import { resolveValues } from './resolveValues.js';
|
|
23
23
|
import assignGingerly, { IAssignGingerlyOptions } from './assignGingerly.js';
|
|
24
24
|
import type { AssignPermissions } from './isAllowedImportPath.js';
|
|
25
|
+
import type { AssignFromHandler, AssignFromHandlerConstructor } from './types/assign-gingerly/types.js';
|
|
25
26
|
import {
|
|
26
27
|
expandSubstitutions, categorizeKeys, handleSpreads, isHandlerCommand
|
|
27
28
|
} from './assignFrom.js';
|
|
28
29
|
|
|
29
30
|
export interface AssignFromOptions extends IAssignGingerlyOptions {
|
|
30
|
-
/** Source object to resolve RHS path strings against */
|
|
31
31
|
from: any;
|
|
32
|
-
|
|
33
|
-
/** Protocol handlers (sync or async) */
|
|
34
32
|
protocols?: Record<string, (key: string) => any | Promise<any>>;
|
|
35
|
-
|
|
36
|
-
/** Loop variable bindings — expand pattern entries containing ${x} */
|
|
37
33
|
where_x_in?: string[];
|
|
38
|
-
/** Loop variable bindings — expand pattern entries containing ${y} */
|
|
39
34
|
where_y_in?: string[];
|
|
40
|
-
/** Loop variable bindings — expand pattern entries containing ${z} */
|
|
41
35
|
where_z_in?: string[];
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Cached element references by variable name.
|
|
45
|
-
* Used with `#[varName]` syntax in LHS keys for fast repeated element access.
|
|
46
|
-
*
|
|
47
|
-
* - String value: existing element ID (uses getElementById)
|
|
48
|
-
* - Object value: { qry: 'selector' } — finds element via querySelector on target, auto-assigns an ID
|
|
49
|
-
*
|
|
50
|
-
* Elements are cached via WeakRef with getElementById fallback on cache miss.
|
|
51
|
-
*/
|
|
52
|
-
withIds?: Record<string, string | { qry: string }>;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Positional element references for use with `#[varName]` syntax.
|
|
56
|
-
* Resolves elements by child index path — no IDs assigned, no caching.
|
|
57
|
-
*
|
|
58
|
-
* - Array value: child index path (e.g., [0, 1] = target.children[0].children[1])
|
|
59
|
-
* - Object value: { path: [...], expect?: 'selector', fallback?: true }
|
|
60
|
-
* expect: validates via element.matches(), logs correction if wrong
|
|
61
|
-
* fallback: on mismatch, recovers via querySelector(expect)
|
|
62
|
-
*/
|
|
36
|
+
pin?: Record<string, string | { qry: string } | { path: number[]; expect?: string; fallback?: boolean }>;
|
|
63
37
|
at?: Record<string, number[] | { path: number[]; expect?: string; fallback?: boolean }>;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Handler implementations scoped to this call.
|
|
67
|
-
* Key: the `do` name referenced in handler configs.
|
|
68
|
-
* Value: a class constructor, or an import path to dynamically load one.
|
|
69
|
-
*
|
|
70
|
-
* Import paths must be local (relative, absolute, or bare specifier — no cross-domain URLs).
|
|
71
|
-
* The module's default export is checked first; otherwise the first exported class
|
|
72
|
-
* with an `assign` method on its prototype is used.
|
|
73
|
-
*
|
|
74
|
-
* Built-in handlers (builtIns.*) auto-load without needing to be listed here.
|
|
75
|
-
*
|
|
76
|
-
* @example
|
|
77
|
-
* handlers: {
|
|
78
|
-
* 'my-list': MyListHandler, // class constructor
|
|
79
|
-
* 'my-chart': './handlers/chart.js', // dynamic import path
|
|
80
|
-
* 'vendor-widget': 'some-package/handler.js', // bare specifier (import map)
|
|
81
|
-
* }
|
|
82
|
-
*/
|
|
83
38
|
handlers?: Record<string, AssignFromHandlerConstructor | string>;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Inferred assignments — automatically distribute source values to matching
|
|
87
|
-
* DOM elements based on structural conventions (itemprop, name, etc.).
|
|
88
|
-
*
|
|
89
|
-
* Uses the inferencer submodule to determine the correct property for each
|
|
90
|
-
* matched element (textContent, value, checked, dateTime, ish, etc.).
|
|
91
|
-
*
|
|
92
|
-
* @example
|
|
93
|
-
* infer: {
|
|
94
|
-
* byItemprop: ['user', 'name', 'email'], // or true for all source keys
|
|
95
|
-
* beVigilant: true, // watch for new matching elements (requires signal)
|
|
96
|
-
* }
|
|
97
|
-
*/
|
|
98
39
|
infer?: {
|
|
99
40
|
byItemprop?: string[] | true;
|
|
100
41
|
'|'?: string[] | true;
|
|
101
42
|
byName?: string[] | true | { props: string[] | true; outside: string };
|
|
102
43
|
'@'?: string[] | true | { props: string[] | true; outside: string };
|
|
103
|
-
/** Watch for new matching elements via MutationObserver. Requires options.signal for cleanup. */
|
|
104
44
|
beVigilant?: boolean;
|
|
105
45
|
};
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Bulk enhancement application via EMC JSON configs.
|
|
109
|
-
* Finds matching elements and spawns enhancements on them.
|
|
110
|
-
*
|
|
111
|
-
* Each entry specifies an EMC JSON path and optionally overrides the matching selector.
|
|
112
|
-
* Enhancements are auto-registered if not already present in the enhancement registry.
|
|
113
|
-
*
|
|
114
|
-
* No scope perimeter is applied — use mount-observer for reactive/scoped enhancement.
|
|
115
|
-
*
|
|
116
|
-
* @example
|
|
117
|
-
* enhance: [
|
|
118
|
-
* { emc: 'be-bound/emc.json', matching: '[name]' },
|
|
119
|
-
* { emc: 'be-observant/emc.json', matching: '[itemprop]' },
|
|
120
|
-
* ]
|
|
121
|
-
*/
|
|
122
46
|
enhance?: Array<{ emc: string; matching?: string; parse?: boolean }>;
|
|
123
47
|
}
|
|
124
48
|
|
|
125
|
-
|
|
126
|
-
* Interface for assignFrom handler classes.
|
|
127
|
-
* Handlers are invoked when a LHS key ends with ' =>'.
|
|
128
|
-
*/
|
|
129
|
-
export interface AssignFromHandler {
|
|
130
|
-
assign(lhsTarget: any, resolvedParams: Record<string, any>, options: AssignFromOptions): Promise<void> | void;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export interface AssignFromHandlerConstructor {
|
|
134
|
-
new (config: any): AssignFromHandler;
|
|
135
|
-
}
|
|
49
|
+
export type { AssignFromHandler, AssignFromHandlerConstructor };
|
|
136
50
|
|
|
137
51
|
// Module cache for processHandlerCommands — avoids await on dynamic import after first call
|
|
138
52
|
let _processHandlerCommands: any;
|
|
@@ -154,7 +68,9 @@ export async function assignFromAsync(
|
|
|
154
68
|
const resolved = await resolveValues(normalPattern, options.from, {
|
|
155
69
|
withMethods: options.withMethods,
|
|
156
70
|
aka: options.aka,
|
|
157
|
-
|
|
71
|
+
akaMethods: options.akaMethods,
|
|
72
|
+
protocols: options.protocols,
|
|
73
|
+
root: target
|
|
158
74
|
});
|
|
159
75
|
|
|
160
76
|
// Recursively handle "..." spread keys at all nesting levels
|
|
@@ -164,9 +80,10 @@ export async function assignFromAsync(
|
|
|
164
80
|
}
|
|
165
81
|
|
|
166
82
|
// Process #[x] normal keys — resolve element, then apply remaining path + value
|
|
167
|
-
if (idRefNormalKeys.length > 0 && (options.
|
|
168
|
-
const ids = { ...options.
|
|
83
|
+
if (idRefNormalKeys.length > 0 && (options.pin || options.at)) {
|
|
84
|
+
const ids = { ...options.pin, ...options.at };
|
|
169
85
|
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
86
|
+
//TODO
|
|
170
87
|
for (const key of idRefNormalKeys) {
|
|
171
88
|
const parsed = parseIdRef(key);
|
|
172
89
|
if (!parsed) continue;
|
|
@@ -179,7 +96,7 @@ export async function assignFromAsync(
|
|
|
179
96
|
// Resolve the RHS value
|
|
180
97
|
const resolvedValue = await resolveValues(
|
|
181
98
|
{ __v: value }, options.from,
|
|
182
|
-
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
99
|
+
{ withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: el }
|
|
183
100
|
);
|
|
184
101
|
// Apply remaining path on the resolved element
|
|
185
102
|
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
@@ -188,7 +105,7 @@ export async function assignFromAsync(
|
|
|
188
105
|
const resolvedValue = await resolveValues(
|
|
189
106
|
typeof value === 'object' && value !== null ? value : { __v: value },
|
|
190
107
|
options.from,
|
|
191
|
-
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
108
|
+
{ withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: el }
|
|
192
109
|
);
|
|
193
110
|
if ('__v' in resolvedValue) {
|
|
194
111
|
// Single value — can't assign to element root without a path
|
|
@@ -205,8 +122,8 @@ export async function assignFromAsync(
|
|
|
205
122
|
await _processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
|
|
206
123
|
}
|
|
207
124
|
// Process #[x] handler keys — resolve element, then pass to handler processing
|
|
208
|
-
if (idRefHandlerKeys.length > 0 && (options.
|
|
209
|
-
const ids = { ...options.
|
|
125
|
+
if (idRefHandlerKeys.length > 0 && (options.pin || options.at)) {
|
|
126
|
+
const ids = { ...options.pin, ...options.at };
|
|
210
127
|
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
211
128
|
_processHandlerCommands ??= (await import('./processHandlerCommands.js')).processHandlerCommands;
|
|
212
129
|
|