assign-gingerly 0.0.33 → 0.0.35
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 +24 -2
- package/assignGingerly.js +74 -42
- package/assignGingerly.ts +65 -44
- package/package.json +1 -1
- package/resolveValues.js +4 -2
- package/resolveValues.ts +4 -2
package/README.md
CHANGED
|
@@ -734,7 +734,7 @@ console.log(obj);
|
|
|
734
734
|
// }
|
|
735
735
|
```
|
|
736
736
|
|
|
737
|
-
The `+=` command syntax is `<path> +=` where the path
|
|
737
|
+
The `+=` command syntax is `<path> +=` where the path uses the `?.` nested notation for nested properties, or a plain key for direct properties. The right-hand side value is added to the existing value using `+=`. If the path doesn't exist, it's created and set directly to the value. If the expression is a string, string concatenation is used. If the expression can't be "added to", it allows JavaScript to throw its natural error.
|
|
738
738
|
|
|
739
739
|
## Example 5 - Toggling boolean values and negating
|
|
740
740
|
|
|
@@ -764,7 +764,7 @@ console.log(obj);
|
|
|
764
764
|
// }
|
|
765
765
|
```
|
|
766
766
|
|
|
767
|
-
The `=!` command syntax is `<path> =!` where the path
|
|
767
|
+
The `=!` command syntax is `<path> =!` where the path uses the `?.` nested notation for nested properties, or a plain key for direct properties.
|
|
768
768
|
|
|
769
769
|
For existing values, the toggle is performed using JavaScript's logical NOT operator (`!value`), regardless of what type it is.
|
|
770
770
|
|
|
@@ -3303,6 +3303,28 @@ console.log(result);
|
|
|
3303
3303
|
|
|
3304
3304
|
-->
|
|
3305
3305
|
|
|
3306
|
+
## Resolving and Assigning with `assignFrom`
|
|
3307
|
+
|
|
3308
|
+
The `assignFrom` function combines RHS path resolution with `assignGingerly` in a single call. It resolves `?.`-prefixed RHS values against a source object, then assigns the results into a target. Use `?.` alone as a RHS value to reference the entire source object itself.
|
|
3309
|
+
|
|
3310
|
+
```TypeScript
|
|
3311
|
+
import { assignFrom } from 'assign-gingerly/assignFrom.js';
|
|
3312
|
+
|
|
3313
|
+
const viewModel = { username: 'Alice', clone: someDocumentFragment };
|
|
3314
|
+
|
|
3315
|
+
assignFrom(target, {
|
|
3316
|
+
'?.appendChild': '?..clone', // source.clone
|
|
3317
|
+
'?.clone?.q?..username?.textContent': '?.username', // source.username
|
|
3318
|
+
ref: '?.' // the source object itself
|
|
3319
|
+
}, {
|
|
3320
|
+
from: viewModel,
|
|
3321
|
+
withMethods: ['appendChild'],
|
|
3322
|
+
aka: { 'q': 'querySelector' }
|
|
3323
|
+
});
|
|
3324
|
+
```
|
|
3325
|
+
|
|
3326
|
+
For full documentation, see [docs/assignFrom.md](docs/assignFrom.md).
|
|
3327
|
+
|
|
3306
3328
|
## Itemscope Managers (Chrome 146+)
|
|
3307
3329
|
|
|
3308
3330
|
Itemscope Managers provide a way to manage DOM fragments and their associated data/view models for elements with the `itemscope` attribute. This feature enables frameworks and libraries to manage light children of web components, DOM fragments from looping constructs, and scenarios where custom element wrapping is not feasible.
|
package/assignGingerly.js
CHANGED
|
@@ -201,11 +201,13 @@ function parseDeleteCommand(key) {
|
|
|
201
201
|
}
|
|
202
202
|
/**
|
|
203
203
|
* Helper function to parse a path string with ?. notation
|
|
204
|
+
* Always splits on '?.' delimiter, preserving dots that are part of values
|
|
205
|
+
* (e.g., CSS class selectors like '.username')
|
|
206
|
+
* Paths must use ?. notation — plain dot notation is not supported.
|
|
204
207
|
*/
|
|
205
208
|
function parsePath(path) {
|
|
206
209
|
return path
|
|
207
|
-
.split('
|
|
208
|
-
.map(part => part.replace(/\?/g, ''))
|
|
210
|
+
.split('?.')
|
|
209
211
|
.filter(part => part.length > 0);
|
|
210
212
|
}
|
|
211
213
|
/**
|
|
@@ -572,16 +574,25 @@ export function assignGingerly(target, source, options) {
|
|
|
572
574
|
if (isIncCommand(key)) {
|
|
573
575
|
const path = parseIncCommand(key);
|
|
574
576
|
if (path) {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
577
|
+
if (isNestedPath(path)) {
|
|
578
|
+
const pathParts = parsePath(path);
|
|
579
|
+
const lastKey = pathParts[pathParts.length - 1];
|
|
580
|
+
const parent = ensureNestedPath(target, pathParts);
|
|
581
|
+
if (!(lastKey in parent)) {
|
|
582
|
+
parent[lastKey] = value;
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
parent[lastKey] += value;
|
|
586
|
+
}
|
|
581
587
|
}
|
|
582
588
|
else {
|
|
583
|
-
//
|
|
584
|
-
|
|
589
|
+
// Plain key - direct operation on target
|
|
590
|
+
if (!(path in target)) {
|
|
591
|
+
target[path] = value;
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
target[path] += value;
|
|
595
|
+
}
|
|
585
596
|
}
|
|
586
597
|
}
|
|
587
598
|
continue;
|
|
@@ -591,10 +602,18 @@ export function assignGingerly(target, source, options) {
|
|
|
591
602
|
const lhsPath = parseToggleCommand(key);
|
|
592
603
|
if (lhsPath) {
|
|
593
604
|
const rhsPath = value;
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
605
|
+
// Resolve LHS
|
|
606
|
+
let lhsParent;
|
|
607
|
+
let lhsLastKey;
|
|
608
|
+
if (isNestedPath(lhsPath)) {
|
|
609
|
+
const lhsPathParts = parsePath(lhsPath);
|
|
610
|
+
lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
|
|
611
|
+
lhsParent = ensureNestedPath(target, lhsPathParts);
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
lhsLastKey = lhsPath;
|
|
615
|
+
lhsParent = target;
|
|
616
|
+
}
|
|
598
617
|
// Determine what to negate
|
|
599
618
|
let valueToNegate;
|
|
600
619
|
if (rhsPath === '.') {
|
|
@@ -603,26 +622,30 @@ export function assignGingerly(target, source, options) {
|
|
|
603
622
|
valueToNegate = lhsParent[lhsLastKey];
|
|
604
623
|
}
|
|
605
624
|
else {
|
|
606
|
-
// LHS doesn't exist, treat as undefined -> !undefined = true
|
|
607
625
|
valueToNegate = undefined;
|
|
608
626
|
}
|
|
609
627
|
}
|
|
610
628
|
else {
|
|
611
629
|
// RHS path: navigate to get the value (don't create paths)
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
current
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
630
|
+
if (isNestedPath(rhsPath)) {
|
|
631
|
+
const rhsPathParts = parsePath(rhsPath);
|
|
632
|
+
let current = target;
|
|
633
|
+
let exists = true;
|
|
634
|
+
for (const part of rhsPathParts) {
|
|
635
|
+
if (current && typeof current === 'object' && part in current) {
|
|
636
|
+
current = current[part];
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
exists = false;
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
622
642
|
}
|
|
643
|
+
valueToNegate = exists ? current : true;
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
// Plain key RHS
|
|
647
|
+
valueToNegate = (rhsPath in target) ? target[rhsPath] : true;
|
|
623
648
|
}
|
|
624
|
-
// If RHS doesn't exist, treat as truthy (will become false)
|
|
625
|
-
valueToNegate = exists ? current : true;
|
|
626
649
|
}
|
|
627
650
|
// Apply negation to LHS
|
|
628
651
|
lhsParent[lhsLastKey] = !valueToNegate;
|
|
@@ -633,28 +656,37 @@ export function assignGingerly(target, source, options) {
|
|
|
633
656
|
if (isDeleteCommand(key)) {
|
|
634
657
|
const path = parseDeleteCommand(key);
|
|
635
658
|
if (path !== null) {
|
|
636
|
-
const pathParts = parsePath(path);
|
|
637
659
|
// Determine the parent object
|
|
638
660
|
let parent = target;
|
|
639
661
|
let canDelete = true;
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
662
|
+
if (isNestedPath(path)) {
|
|
663
|
+
const pathParts = parsePath(path);
|
|
664
|
+
if (pathParts.length === 0) {
|
|
665
|
+
parent = target;
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
for (const part of pathParts) {
|
|
669
|
+
if (parent && typeof parent === 'object' && part in parent) {
|
|
670
|
+
parent = parent[part];
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
canDelete = false;
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
653
676
|
}
|
|
654
677
|
}
|
|
655
678
|
}
|
|
679
|
+
else if (path.length > 0) {
|
|
680
|
+
// Plain key - navigate one level
|
|
681
|
+
if (parent && typeof parent === 'object' && path in parent) {
|
|
682
|
+
parent = parent[path];
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
canDelete = false;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
// else: empty path = delete from root
|
|
656
689
|
if (canDelete && typeof parent === 'object' && parent !== null) {
|
|
657
|
-
// RHS can be a string (single property) or array (multiple properties)
|
|
658
690
|
const propertiesToDelete = Array.isArray(value) ? value : [value];
|
|
659
691
|
for (const prop of propertiesToDelete) {
|
|
660
692
|
if (prop in parent) {
|
package/assignGingerly.ts
CHANGED
|
@@ -326,11 +326,13 @@ function parseDeleteCommand(key: string): string | null {
|
|
|
326
326
|
|
|
327
327
|
/**
|
|
328
328
|
* Helper function to parse a path string with ?. notation
|
|
329
|
+
* Always splits on '?.' delimiter, preserving dots that are part of values
|
|
330
|
+
* (e.g., CSS class selectors like '.username')
|
|
331
|
+
* Paths must use ?. notation — plain dot notation is not supported.
|
|
329
332
|
*/
|
|
330
333
|
function parsePath(path: string): string[] {
|
|
331
334
|
return path
|
|
332
|
-
.split('
|
|
333
|
-
.map(part => part.replace(/\?/g, ''))
|
|
335
|
+
.split('?.')
|
|
334
336
|
.filter(part => part.length > 0);
|
|
335
337
|
}
|
|
336
338
|
|
|
@@ -736,16 +738,22 @@ export function assignGingerly(
|
|
|
736
738
|
if (isIncCommand(key)) {
|
|
737
739
|
const path = parseIncCommand(key);
|
|
738
740
|
if (path) {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
741
|
+
if (isNestedPath(path)) {
|
|
742
|
+
const pathParts = parsePath(path);
|
|
743
|
+
const lastKey = pathParts[pathParts.length - 1];
|
|
744
|
+
const parent = ensureNestedPath(target, pathParts);
|
|
745
|
+
if (!(lastKey in parent)) {
|
|
746
|
+
parent[lastKey] = value;
|
|
747
|
+
} else {
|
|
748
|
+
parent[lastKey] += value;
|
|
749
|
+
}
|
|
746
750
|
} else {
|
|
747
|
-
//
|
|
748
|
-
|
|
751
|
+
// Plain key - direct operation on target
|
|
752
|
+
if (!(path in target)) {
|
|
753
|
+
target[path] = value;
|
|
754
|
+
} else {
|
|
755
|
+
target[path] += value;
|
|
756
|
+
}
|
|
749
757
|
}
|
|
750
758
|
}
|
|
751
759
|
continue;
|
|
@@ -757,10 +765,17 @@ export function assignGingerly(
|
|
|
757
765
|
if (lhsPath) {
|
|
758
766
|
const rhsPath = value;
|
|
759
767
|
|
|
760
|
-
//
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
768
|
+
// Resolve LHS
|
|
769
|
+
let lhsParent: any;
|
|
770
|
+
let lhsLastKey: string;
|
|
771
|
+
if (isNestedPath(lhsPath)) {
|
|
772
|
+
const lhsPathParts = parsePath(lhsPath);
|
|
773
|
+
lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
|
|
774
|
+
lhsParent = ensureNestedPath(target, lhsPathParts);
|
|
775
|
+
} else {
|
|
776
|
+
lhsLastKey = lhsPath;
|
|
777
|
+
lhsParent = target;
|
|
778
|
+
}
|
|
764
779
|
|
|
765
780
|
// Determine what to negate
|
|
766
781
|
let valueToNegate;
|
|
@@ -769,26 +784,27 @@ export function assignGingerly(
|
|
|
769
784
|
if (lhsLastKey in lhsParent) {
|
|
770
785
|
valueToNegate = lhsParent[lhsLastKey];
|
|
771
786
|
} else {
|
|
772
|
-
// LHS doesn't exist, treat as undefined -> !undefined = true
|
|
773
787
|
valueToNegate = undefined;
|
|
774
788
|
}
|
|
775
789
|
} else {
|
|
776
790
|
// RHS path: navigate to get the value (don't create paths)
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
791
|
+
if (isNestedPath(rhsPath)) {
|
|
792
|
+
const rhsPathParts = parsePath(rhsPath);
|
|
793
|
+
let current = target;
|
|
794
|
+
let exists = true;
|
|
795
|
+
for (const part of rhsPathParts) {
|
|
796
|
+
if (current && typeof current === 'object' && part in current) {
|
|
797
|
+
current = current[part];
|
|
798
|
+
} else {
|
|
799
|
+
exists = false;
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
787
802
|
}
|
|
803
|
+
valueToNegate = exists ? current : true;
|
|
804
|
+
} else {
|
|
805
|
+
// Plain key RHS
|
|
806
|
+
valueToNegate = (rhsPath in target) ? target[rhsPath] : true;
|
|
788
807
|
}
|
|
789
|
-
|
|
790
|
-
// If RHS doesn't exist, treat as truthy (will become false)
|
|
791
|
-
valueToNegate = exists ? current : true;
|
|
792
808
|
}
|
|
793
809
|
|
|
794
810
|
// Apply negation to LHS
|
|
@@ -801,31 +817,36 @@ export function assignGingerly(
|
|
|
801
817
|
if (isDeleteCommand(key)) {
|
|
802
818
|
const path = parseDeleteCommand(key);
|
|
803
819
|
if (path !== null) {
|
|
804
|
-
const pathParts = parsePath(path);
|
|
805
|
-
|
|
806
820
|
// Determine the parent object
|
|
807
821
|
let parent = target;
|
|
808
822
|
let canDelete = true;
|
|
809
823
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
824
|
+
if (isNestedPath(path)) {
|
|
825
|
+
const pathParts = parsePath(path);
|
|
826
|
+
if (pathParts.length === 0) {
|
|
827
|
+
parent = target;
|
|
828
|
+
} else {
|
|
829
|
+
for (const part of pathParts) {
|
|
830
|
+
if (parent && typeof parent === 'object' && part in parent) {
|
|
831
|
+
parent = parent[part];
|
|
832
|
+
} else {
|
|
833
|
+
canDelete = false;
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
821
836
|
}
|
|
822
837
|
}
|
|
838
|
+
} else if (path.length > 0) {
|
|
839
|
+
// Plain key - navigate one level
|
|
840
|
+
if (parent && typeof parent === 'object' && path in parent) {
|
|
841
|
+
parent = parent[path];
|
|
842
|
+
} else {
|
|
843
|
+
canDelete = false;
|
|
844
|
+
}
|
|
823
845
|
}
|
|
846
|
+
// else: empty path = delete from root
|
|
824
847
|
|
|
825
848
|
if (canDelete && typeof parent === 'object' && parent !== null) {
|
|
826
|
-
// RHS can be a string (single property) or array (multiple properties)
|
|
827
849
|
const propertiesToDelete = Array.isArray(value) ? value : [value];
|
|
828
|
-
|
|
829
850
|
for (const prop of propertiesToDelete) {
|
|
830
851
|
if (prop in parent) {
|
|
831
852
|
delete parent[prop];
|
package/package.json
CHANGED
package/resolveValues.js
CHANGED
|
@@ -26,8 +26,10 @@ export function resolveValues(pattern, source) {
|
|
|
26
26
|
const result = {};
|
|
27
27
|
for (const [key, value] of Object.entries(pattern)) {
|
|
28
28
|
if (typeof value === 'string' && value.startsWith('?.')) {
|
|
29
|
-
// Parse path: split
|
|
30
|
-
|
|
29
|
+
// Parse path: split on '?.' delimiter, filter empties
|
|
30
|
+
// Use '?.' as the sole delimiter to preserve dots in values (e.g., CSS selectors)
|
|
31
|
+
// Special case: '?.' alone (empty path) resolves to the source object itself
|
|
32
|
+
const parts = value.split('?.').filter(p => p.length > 0);
|
|
31
33
|
let current = source;
|
|
32
34
|
for (const part of parts) {
|
|
33
35
|
if (current == null)
|
package/resolveValues.ts
CHANGED
|
@@ -29,8 +29,10 @@ export function resolveValues(
|
|
|
29
29
|
const result: Record<string, any> = {};
|
|
30
30
|
for (const [key, value] of Object.entries(pattern)) {
|
|
31
31
|
if (typeof value === 'string' && value.startsWith('?.')) {
|
|
32
|
-
// Parse path: split
|
|
33
|
-
|
|
32
|
+
// Parse path: split on '?.' delimiter, filter empties
|
|
33
|
+
// Use '?.' as the sole delimiter to preserve dots in values (e.g., CSS selectors)
|
|
34
|
+
// Special case: '?.' alone (empty path) resolves to the source object itself
|
|
35
|
+
const parts = value.split('?.').filter(p => p.length > 0);
|
|
34
36
|
let current = source;
|
|
35
37
|
for (const part of parts) {
|
|
36
38
|
if (current == null) break;
|