assign-gingerly 0.0.31 → 0.0.32
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 +371 -17
- package/assignFrom.js +27 -0
- package/assignFrom.ts +37 -0
- package/assignGingerly.js +221 -5
- package/assignGingerly.ts +287 -5
- package/eachTime.js +110 -0
- package/eachTime.ts +137 -0
- package/index.js +2 -0
- package/index.ts +2 -0
- package/object-extension.js +65 -12
- package/object-extension.ts +74 -15
- package/package.json +9 -1
- package/resolveValues.js +44 -0
- package/resolveValues.ts +45 -0
- package/types/assign-gingerly/types.d.ts +11 -2
package/README.md
CHANGED
|
@@ -411,25 +411,31 @@ assignGingerly(elementRef, {
|
|
|
411
411
|
// Equivalent to: elementRef.deref().classList.add('active')
|
|
412
412
|
```
|
|
413
413
|
|
|
414
|
-
**Complex chaining:**
|
|
414
|
+
**Complex chaining with real DOM elements:**
|
|
415
|
+
|
|
416
|
+
Methods are called on the objects found through chained accessors, not just on the root object:
|
|
415
417
|
|
|
416
418
|
```TypeScript
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
};
|
|
419
|
+
const div = document.createElement('div');
|
|
420
|
+
div.innerHTML = `
|
|
421
|
+
<my-element>
|
|
422
|
+
<your-element></your-element>
|
|
423
|
+
</my-element>
|
|
424
|
+
`;
|
|
425
425
|
|
|
426
|
-
assignGingerly(
|
|
427
|
-
'?.querySelector?.my-element?.classList?.add': 'highlighted'
|
|
426
|
+
assignGingerly(div, {
|
|
427
|
+
'?.querySelector?.my-element?.querySelector?.your-element?.classList?.add': 'highlighted'
|
|
428
428
|
}, { withMethods: ['querySelector', 'add'] });
|
|
429
429
|
|
|
430
|
-
// Equivalent to:
|
|
430
|
+
// Equivalent to:
|
|
431
|
+
// div.querySelector('my-element').querySelector('your-element').classList.add('highlighted')
|
|
432
|
+
|
|
433
|
+
const yourElement = div.querySelector('my-element')?.querySelector('your-element');
|
|
434
|
+
console.log(yourElement?.classList.contains('highlighted')); // true
|
|
431
435
|
```
|
|
432
436
|
|
|
437
|
+
The key insight: `querySelector` is called on each intermediate result in the chain. First on `div`, then on the `my-element` result, demonstrating that methods work naturally with the object hierarchy you're navigating.
|
|
438
|
+
|
|
433
439
|
**Using Set for withMethods:**
|
|
434
440
|
|
|
435
441
|
For better performance with many methods, use a Set:
|
|
@@ -464,6 +470,321 @@ assignGingerly(element, {
|
|
|
464
470
|
- Silent failure for non-existent methods (garbage in, garbage out)
|
|
465
471
|
- Supports method chaining and complex navigation patterns
|
|
466
472
|
|
|
473
|
+
## Example 3d - Aliasing with aka
|
|
474
|
+
|
|
475
|
+
The `aka` option allows you to define custom shortcuts (aliases) for property and method names, reducing verbosity in repetitive patterns. This is inspired by jQuery's `$` shortcut for `querySelectorAll`, but fully customizable.
|
|
476
|
+
|
|
477
|
+
```TypeScript
|
|
478
|
+
import assignGingerly from 'assign-gingerly';
|
|
479
|
+
|
|
480
|
+
const div = document.createElement('div');
|
|
481
|
+
div.innerHTML = `
|
|
482
|
+
<my-element>
|
|
483
|
+
<your-element></your-element>
|
|
484
|
+
</my-element>
|
|
485
|
+
`;
|
|
486
|
+
|
|
487
|
+
// Without aliases (verbose)
|
|
488
|
+
assignGingerly(div, {
|
|
489
|
+
'?.querySelector?.my-element?.classList?.add': 'highlighted',
|
|
490
|
+
'?.querySelector?.your-element?.classList?.add': 'active'
|
|
491
|
+
}, { withMethods: ['querySelector', 'add'] });
|
|
492
|
+
|
|
493
|
+
// With aliases (concise)
|
|
494
|
+
assignGingerly(div, {
|
|
495
|
+
'?.$?.my-element?.c?.+': 'highlighted',
|
|
496
|
+
'?.$?.your-element?.c?.+': 'active'
|
|
497
|
+
}, {
|
|
498
|
+
withMethods: ['querySelector', 'add'],
|
|
499
|
+
aka: { '$': 'querySelector', 'c': 'classList', '+': 'add' }
|
|
500
|
+
});
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**How it works:**
|
|
504
|
+
|
|
505
|
+
- Aliases are substituted **before** path evaluation
|
|
506
|
+
- Matches complete tokens between `?.` delimiters (not substrings)
|
|
507
|
+
- Works for both properties and methods
|
|
508
|
+
- Single or multi-character aliases supported
|
|
509
|
+
|
|
510
|
+
**Reserved characters:**
|
|
511
|
+
|
|
512
|
+
Cannot be used in aliases: space (` `), backtick (`` ` ``)
|
|
513
|
+
|
|
514
|
+
**Multi-character aliases:**
|
|
515
|
+
|
|
516
|
+
```TypeScript
|
|
517
|
+
assignGingerly(element, {
|
|
518
|
+
'?.qs?.my-element?.cl?.add': 'highlighted'
|
|
519
|
+
}, {
|
|
520
|
+
withMethods: ['querySelector', 'add'],
|
|
521
|
+
aka: { 'qs': 'querySelector', 'cl': 'classList' }
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**Multiple aliases in one path:**
|
|
526
|
+
|
|
527
|
+
```TypeScript
|
|
528
|
+
assignGingerly(element, {
|
|
529
|
+
'?.c?.+': 'class1',
|
|
530
|
+
'?.p?.+': 'part1',
|
|
531
|
+
'?.ds?.userId': '123'
|
|
532
|
+
}, {
|
|
533
|
+
withMethods: ['add'],
|
|
534
|
+
aka: {
|
|
535
|
+
'c': 'classList',
|
|
536
|
+
'p': 'part',
|
|
537
|
+
'ds': 'dataset',
|
|
538
|
+
'+': 'add'
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// Equivalent to:
|
|
543
|
+
// element.classList.add('class1')
|
|
544
|
+
// element.part.add('part1')
|
|
545
|
+
// element.dataset.userId = '123'
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
**Benefits:**
|
|
549
|
+
|
|
550
|
+
- Reduces verbosity in repetitive patterns
|
|
551
|
+
- Fully customizable shortcuts
|
|
552
|
+
- Improves readability when you have many similar operations
|
|
553
|
+
- Works seamlessly with `withMethods`
|
|
554
|
+
|
|
555
|
+
## Example 3e - ForEach with @each
|
|
556
|
+
|
|
557
|
+
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.
|
|
558
|
+
|
|
559
|
+
```TypeScript
|
|
560
|
+
import assignGingerly from 'assign-gingerly';
|
|
561
|
+
|
|
562
|
+
const div = document.createElement('div');
|
|
563
|
+
div.innerHTML = `
|
|
564
|
+
<my-element></my-element>
|
|
565
|
+
<my-element></my-element>
|
|
566
|
+
<my-element></my-element>
|
|
567
|
+
`;
|
|
568
|
+
|
|
569
|
+
// Apply to each element in the collection
|
|
570
|
+
assignGingerly(div, {
|
|
571
|
+
'?.querySelectorAll?.my-element?.@each?.classList?.add': 'highlighted'
|
|
572
|
+
}, { withMethods: ['querySelectorAll', 'add'] });
|
|
573
|
+
|
|
574
|
+
// All my-element elements now have the 'highlighted' class
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**How it works:**
|
|
578
|
+
|
|
579
|
+
- `@each` marks the point where iteration begins
|
|
580
|
+
- Everything before `@each` navigates to the iterable
|
|
581
|
+
- Everything after `@each` is applied to each item in the collection
|
|
582
|
+
- Empty collections are handled gracefully (no errors)
|
|
583
|
+
|
|
584
|
+
**With regular arrays:**
|
|
585
|
+
|
|
586
|
+
```TypeScript
|
|
587
|
+
const obj = {
|
|
588
|
+
items: [
|
|
589
|
+
{ value: null },
|
|
590
|
+
{ value: null },
|
|
591
|
+
{ value: null }
|
|
592
|
+
]
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
assignGingerly(obj, {
|
|
596
|
+
'?.items?.@each?.value': 'test'
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// All items now have value: 'test'
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**Nested forEach:**
|
|
603
|
+
|
|
604
|
+
```TypeScript
|
|
605
|
+
const obj = {
|
|
606
|
+
groups: [
|
|
607
|
+
{ items: [{ value: null }, { value: null }] },
|
|
608
|
+
{ items: [{ value: null }, { value: null }] }
|
|
609
|
+
]
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
assignGingerly(obj, {
|
|
613
|
+
'?.groups?.@each?.items?.@each?.value': 'nested'
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// All nested items now have value: 'nested'
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
**With aliases:**
|
|
620
|
+
|
|
621
|
+
```TypeScript
|
|
622
|
+
assignGingerly(div, {
|
|
623
|
+
'?.qsa?.my-element?.*?.c?.+': 'highlighted'
|
|
624
|
+
}, {
|
|
625
|
+
withMethods: ['querySelectorAll', 'add'],
|
|
626
|
+
aka: {
|
|
627
|
+
'qsa': 'querySelectorAll',
|
|
628
|
+
'c': 'classList',
|
|
629
|
+
'+': 'add',
|
|
630
|
+
'*': '@each' // Alias * to @each for brevity
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
**Method calls on each item:**
|
|
636
|
+
|
|
637
|
+
```TypeScript
|
|
638
|
+
assignGingerly(div, {
|
|
639
|
+
'?.querySelectorAll?.div?.@each?.setAttribute': ['data-id', '123']
|
|
640
|
+
}, { withMethods: ['querySelectorAll', 'setAttribute'] });
|
|
641
|
+
|
|
642
|
+
// All div elements now have data-id="123"
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**Accessing iterable properties:**
|
|
646
|
+
|
|
647
|
+
When you omit `@each`, you access properties on the iterable itself, not its items:
|
|
648
|
+
|
|
649
|
+
```TypeScript
|
|
650
|
+
const obj = {
|
|
651
|
+
items: [1, 2, 3],
|
|
652
|
+
customProp: null
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
// Set property on the array itself
|
|
656
|
+
assignGingerly(obj, {
|
|
657
|
+
'?.items?.customProp': 'test'
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
console.log(obj.items.customProp); // 'test'
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**Benefits:**
|
|
664
|
+
|
|
665
|
+
- Works with any iterable (Arrays, NodeList, HTMLCollection, etc.)
|
|
666
|
+
- Supports nested iterations
|
|
667
|
+
- Integrates seamlessly with `withMethods` and `aka`
|
|
668
|
+
- Clear distinction between iterating and accessing iterable properties
|
|
669
|
+
- Graceful handling of empty collections
|
|
670
|
+
|
|
671
|
+
## Example 3f - Reactive Iteration with @eachTime
|
|
672
|
+
|
|
673
|
+
The `@eachTime` symbol enables reactive iteration over elements as they mount or appear dynamically. Unlike `@each` which operates on static collections, `@eachTime` subscribes to events and applies operations to elements as they arrive over time.
|
|
674
|
+
|
|
675
|
+
**Important:** This feature requires an `AbortSignal` for cleanup and is designed to work with EventTarget objects that emit 'mount' events (such as [mount-observer](https://github.com/bahrus/mount-observer)).
|
|
676
|
+
|
|
677
|
+
```TypeScript
|
|
678
|
+
import assignGingerly from 'assign-gingerly';
|
|
679
|
+
|
|
680
|
+
const controller = new AbortController();
|
|
681
|
+
const div = document.createElement('div');
|
|
682
|
+
|
|
683
|
+
// Assume mountObserver is an IMountObserver instance that emits 'mount' events
|
|
684
|
+
// when new elements matching 'my-element' are added to the DOM
|
|
685
|
+
|
|
686
|
+
assignGingerly(div, {
|
|
687
|
+
'?.mountObserver?.@eachTime?.classList?.add': 'highlighted'
|
|
688
|
+
}, {
|
|
689
|
+
withMethods: ['add'],
|
|
690
|
+
signal: controller.signal // Required for cleanup
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// As elements mount, they automatically get the 'highlighted' class
|
|
694
|
+
// Later, cleanup all listeners:
|
|
695
|
+
controller.abort();
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
**How it works:**
|
|
699
|
+
|
|
700
|
+
- `@eachTime` marks the point where reactive iteration begins
|
|
701
|
+
- Everything before `@eachTime` must navigate to an EventTarget
|
|
702
|
+
- The EventTarget must emit 'mount' events with a `mountedElement` property
|
|
703
|
+
- Everything after `@eachTime` is applied to each mounted element
|
|
704
|
+
- Event listeners are automatically cleaned up when the AbortSignal is aborted
|
|
705
|
+
|
|
706
|
+
**With method calls:**
|
|
707
|
+
|
|
708
|
+
```TypeScript
|
|
709
|
+
const controller = new AbortController();
|
|
710
|
+
|
|
711
|
+
assignGingerly(div, {
|
|
712
|
+
'?.mountObserver?.@eachTime?.setAttribute': ['data-mounted', 'true']
|
|
713
|
+
}, {
|
|
714
|
+
withMethods: ['setAttribute'],
|
|
715
|
+
signal: controller.signal
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// Each mounted element gets data-mounted="true"
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
**With aliases:**
|
|
722
|
+
|
|
723
|
+
```TypeScript
|
|
724
|
+
const controller = new AbortController();
|
|
725
|
+
|
|
726
|
+
assignGingerly(div, {
|
|
727
|
+
'?.mo?.@*?.c?.+': 'active'
|
|
728
|
+
}, {
|
|
729
|
+
withMethods: ['add'],
|
|
730
|
+
aka: {
|
|
731
|
+
'mo': 'mountObserver',
|
|
732
|
+
'@*': '@eachTime',
|
|
733
|
+
'c': 'classList',
|
|
734
|
+
'+': 'add'
|
|
735
|
+
},
|
|
736
|
+
signal: controller.signal
|
|
737
|
+
});
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
**Cleanup is required:**
|
|
741
|
+
|
|
742
|
+
```TypeScript
|
|
743
|
+
const controller = new AbortController();
|
|
744
|
+
|
|
745
|
+
// Setup reactive iteration
|
|
746
|
+
assignGingerly(div, {
|
|
747
|
+
'?.mountObserver?.@eachTime?.classList?.add': 'mounted'
|
|
748
|
+
}, {
|
|
749
|
+
withMethods: ['add'],
|
|
750
|
+
signal: controller.signal
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
// Later, when you're done observing:
|
|
754
|
+
controller.abort(); // Removes all event listeners
|
|
755
|
+
|
|
756
|
+
// Attempting to use @eachTime without a signal throws an error:
|
|
757
|
+
assignGingerly(div, {
|
|
758
|
+
'?.mountObserver?.@eachTime?.classList?.add': 'mounted'
|
|
759
|
+
}, { withMethods: ['add'] });
|
|
760
|
+
// Error: @eachTime requires an AbortSignal in options.signal for cleanup
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
**Key differences from @each:**
|
|
764
|
+
|
|
765
|
+
| Feature | @each | @eachTime |
|
|
766
|
+
|---------|-------|-----------|
|
|
767
|
+
| **Type** | Static iteration | Reactive iteration |
|
|
768
|
+
| **Timing** | Immediate (synchronous) | Over time (asynchronous) |
|
|
769
|
+
| **Use case** | Existing collections | Elements appearing dynamically |
|
|
770
|
+
| **Cleanup** | Not needed | Required (AbortSignal) |
|
|
771
|
+
| **Requirements** | Any iterable | EventTarget with 'mount' events |
|
|
772
|
+
|
|
773
|
+
**Benefits:**
|
|
774
|
+
|
|
775
|
+
- Declarative reactive programming without RxJS complexity
|
|
776
|
+
- Automatic cleanup via standard AbortSignal API
|
|
777
|
+
- JSON-serializable configuration (behavior is in implementation)
|
|
778
|
+
- Fire-and-forget async pattern (doesn't block)
|
|
779
|
+
- Minimal weight impact (~3% when not used, dynamically loaded when needed)
|
|
780
|
+
|
|
781
|
+
**Limitations:**
|
|
782
|
+
|
|
783
|
+
- Requires EventTarget that emits 'mount' events
|
|
784
|
+
- AbortSignal is mandatory for cleanup
|
|
785
|
+
- Testing is done in mount-observer package (no tests in assign-gingerly)
|
|
786
|
+
- Single @eachTime per path (nested @eachTime not currently supported)
|
|
787
|
+
|
|
467
788
|
While we are in the business of passing values of object A into object B, we might as well add some extremely common behavior that allows updating properties of object B based on the current values of object B -- things like incrementing, toggling, and deleting. Deleting is critical for assignTentatively, but is included with both functions.
|
|
468
789
|
|
|
469
790
|
## Example 4 - Incrementing values with += command
|
|
@@ -649,10 +970,12 @@ console.log(string1 === string2); // true
|
|
|
649
970
|
|
|
650
971
|
This guarantees that applying the reversal object restores the object to its exact original state.
|
|
651
972
|
|
|
973
|
+
# Object and Element Enhancements via assign-gingerly
|
|
974
|
+
|
|
652
975
|
## Dependency injection based on a registry object and a Symbolic reference mapping
|
|
653
976
|
|
|
654
977
|
```Typescript
|
|
655
|
-
interface
|
|
978
|
+
interface EnhancementConfig<T = any, TObjToExtend = any> {
|
|
656
979
|
spawn: {new(objToExtend: TObjToExtend, ctx: SpawnContext, initVals: Partial<T>): T}
|
|
657
980
|
symlinks?: {[key: symbol]: keyof T}
|
|
658
981
|
// Optional: for element enhancement access
|
|
@@ -678,7 +1001,7 @@ class YourEnhancement{
|
|
|
678
1001
|
}
|
|
679
1002
|
|
|
680
1003
|
class EnhancementRegistry{
|
|
681
|
-
push(
|
|
1004
|
+
push(EnhancementConfig | EnhancementConfig[]){
|
|
682
1005
|
...
|
|
683
1006
|
}
|
|
684
1007
|
}
|
|
@@ -1035,7 +1358,7 @@ Element enhancement classes should follow this constructor signature:
|
|
|
1035
1358
|
|
|
1036
1359
|
```TypeScript
|
|
1037
1360
|
interface SpawnContext<T, TMountContext = any> {
|
|
1038
|
-
config:
|
|
1361
|
+
config: EnhancementConfig<T>;
|
|
1039
1362
|
mountCtx?: TMountContext; // Optional custom context passed by caller
|
|
1040
1363
|
}
|
|
1041
1364
|
|
|
@@ -1089,7 +1412,7 @@ This is useful for:
|
|
|
1089
1412
|
In addition to spawn and symlinks, registry items support optional properties `enhKey`, `withAttrs`, `canSpawn`, and `lifecycleKeys`:
|
|
1090
1413
|
|
|
1091
1414
|
```TypeScript
|
|
1092
|
-
interface
|
|
1415
|
+
interface EnhancementConfig<T, TObj = Element> {
|
|
1093
1416
|
spawn: {
|
|
1094
1417
|
new (obj?: TObj, ctx?: SpawnContext<T>, initVals?: Partial<T>): T;
|
|
1095
1418
|
canSpawn?: (obj: TObj, ctx?: SpawnContext<T>) => boolean; // Optional spawn guard
|
|
@@ -1229,6 +1552,37 @@ console.log(element.enh.myEnh === instance); // true
|
|
|
1229
1552
|
- **Shared instances**: Uses the same global instance map as `assignGingerly` and `enh.set`, ensuring only one instance per registry item
|
|
1230
1553
|
- **Auto-registration**: Automatically adds registry items to the element's registry if not present
|
|
1231
1554
|
|
|
1555
|
+
**Lookup by enhKey (string or symbol):**
|
|
1556
|
+
|
|
1557
|
+
Instead of passing the full registry item object, you can pass a string or symbol matching the `enhKey` of a previously registered enhancement:
|
|
1558
|
+
|
|
1559
|
+
```TypeScript
|
|
1560
|
+
// First, register the enhancement (e.g., via mount-observer or manually)
|
|
1561
|
+
registry.push({
|
|
1562
|
+
spawn: MyEnhancement,
|
|
1563
|
+
enhKey: 'myEnh'
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
// Later, retrieve by enhKey string
|
|
1567
|
+
const instance = element.enh.get('myEnh');
|
|
1568
|
+
|
|
1569
|
+
// Or by symbol enhKey
|
|
1570
|
+
const enhSym = Symbol.for('myEnh');
|
|
1571
|
+
const instance2 = element.enh.get(enhSym);
|
|
1572
|
+
```
|
|
1573
|
+
|
|
1574
|
+
If the enhKey is not found in the registry, an error is thrown: `"myEnh not in registry"`.
|
|
1575
|
+
|
|
1576
|
+
This also works with `enh.dispose()` and `enh.whenResolved()`:
|
|
1577
|
+
|
|
1578
|
+
```TypeScript
|
|
1579
|
+
// Dispose by enhKey
|
|
1580
|
+
element.enh.dispose('myEnh');
|
|
1581
|
+
|
|
1582
|
+
// Wait for resolution by enhKey
|
|
1583
|
+
const resolved = await element.enh.whenResolved('myEnh');
|
|
1584
|
+
```
|
|
1585
|
+
|
|
1232
1586
|
<details>
|
|
1233
1587
|
<summary>Example with shared instances</summary>
|
|
1234
1588
|
|
|
@@ -1779,7 +2133,7 @@ static canSpawn(obj: any, ctx?: SpawnContext<T>): boolean
|
|
|
1779
2133
|
```
|
|
1780
2134
|
|
|
1781
2135
|
- `obj`: The target object being enhanced (element, plain object, etc.)
|
|
1782
|
-
- `ctx`: Optional spawn context containing `{ config:
|
|
2136
|
+
- `ctx`: Optional spawn context containing `{ config: EnhancementConfig<T> }`
|
|
1783
2137
|
- Returns: `true` to allow spawning, `false` to block
|
|
1784
2138
|
|
|
1785
2139
|
### Use Cases
|
package/assignFrom.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve RHS path strings against a source object, then assign the
|
|
3
|
+
* resolved values into a target using assignGingerly.
|
|
4
|
+
*
|
|
5
|
+
* Combines resolveValues + assignGingerly into a single call.
|
|
6
|
+
* Inherits all assignGingerly options (withMethods, aka, signal, etc.).
|
|
7
|
+
*
|
|
8
|
+
* @param target - Object to merge resolved values into
|
|
9
|
+
* @param pattern - Object whose RHS values may contain `?.` path strings
|
|
10
|
+
* @param options - Options including `from` (source object) and any assignGingerly options
|
|
11
|
+
* @returns The target object after merging
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const source = { theme: { color: 'red' }, label: 'Hello' };
|
|
15
|
+
* const target = { color: 'blue', text: '' };
|
|
16
|
+
* assignFrom(target, {
|
|
17
|
+
* color: '?.theme?.color',
|
|
18
|
+
* text: '?.label'
|
|
19
|
+
* }, { from: source });
|
|
20
|
+
* // target is now { color: 'red', text: 'Hello' }
|
|
21
|
+
*/
|
|
22
|
+
import { resolveValues } from './resolveValues.js';
|
|
23
|
+
import assignGingerly from './assignGingerly.js';
|
|
24
|
+
export function assignFrom(target, pattern, options) {
|
|
25
|
+
const resolved = resolveValues(pattern, options.from);
|
|
26
|
+
return assignGingerly(target, resolved, options);
|
|
27
|
+
}
|
package/assignFrom.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve RHS path strings against a source object, then assign the
|
|
3
|
+
* resolved values into a target using assignGingerly.
|
|
4
|
+
*
|
|
5
|
+
* Combines resolveValues + assignGingerly into a single call.
|
|
6
|
+
* Inherits all assignGingerly options (withMethods, aka, signal, etc.).
|
|
7
|
+
*
|
|
8
|
+
* @param target - Object to merge resolved values into
|
|
9
|
+
* @param pattern - Object whose RHS values may contain `?.` path strings
|
|
10
|
+
* @param options - Options including `from` (source object) and any assignGingerly options
|
|
11
|
+
* @returns The target object after merging
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const source = { theme: { color: 'red' }, label: 'Hello' };
|
|
15
|
+
* const target = { color: 'blue', text: '' };
|
|
16
|
+
* assignFrom(target, {
|
|
17
|
+
* color: '?.theme?.color',
|
|
18
|
+
* text: '?.label'
|
|
19
|
+
* }, { from: source });
|
|
20
|
+
* // target is now { color: 'red', text: 'Hello' }
|
|
21
|
+
*/
|
|
22
|
+
import { resolveValues } from './resolveValues.js';
|
|
23
|
+
import assignGingerly, { IAssignGingerlyOptions } from './assignGingerly.js';
|
|
24
|
+
|
|
25
|
+
export interface AssignFromOptions extends IAssignGingerlyOptions {
|
|
26
|
+
/** Source object to resolve RHS path strings against */
|
|
27
|
+
from: any;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function assignFrom(
|
|
31
|
+
target: any,
|
|
32
|
+
pattern: Record<string, any>,
|
|
33
|
+
options: AssignFromOptions
|
|
34
|
+
): any {
|
|
35
|
+
const resolved = resolveValues(pattern, options.from);
|
|
36
|
+
return assignGingerly(target, resolved, options);
|
|
37
|
+
}
|