assign-gingerly 0.0.6 → 0.0.7
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 +183 -4
- package/assignGingerly.js +3 -0
- package/assignGingerly.ts +9 -1
- package/object-extension.js +56 -0
- package/object-extension.ts +69 -0
- package/package.json +1 -1
- package/types.d.ts +9 -1
package/README.md
CHANGED
|
@@ -282,8 +282,8 @@ baseRegistry.push([
|
|
|
282
282
|
const result = assignGingerly({}, {
|
|
283
283
|
[isHappy]: true,
|
|
284
284
|
[isMellow]: true,
|
|
285
|
-
'?.style
|
|
286
|
-
'?.
|
|
285
|
+
'?.style?.height': '40px',
|
|
286
|
+
'?.enh?.mellowYellow?.madAboutFourteen': true
|
|
287
287
|
}, {
|
|
288
288
|
registry: BaseRegistry
|
|
289
289
|
});
|
|
@@ -292,7 +292,7 @@ result.set[isMellow] = false;
|
|
|
292
292
|
|
|
293
293
|
The assignGingerly searches the registry for any items that has a mapping with a matching symbol of isHappy and isMellow, and if found, sees if it already has an instance of the spawn class associated with the first passed in parameter. If no such instance is found, it instantiates one, associates the instance with the first parameter, then sets the property value.
|
|
294
294
|
|
|
295
|
-
It also adds a lazy property to the first passed in parameter, "set", which returns a proxy, and that proxy watches for symbol references passed in a value, and sets the value from that spawned instance. Again, if the spawned instance is not found, it
|
|
295
|
+
It also adds a lazy property to the first passed in parameter, "set", which returns a proxy, and that proxy watches for symbol references passed in a value, and sets the value from that spawned instance. Again, if the spawned instance is not found, it re-spawns it.
|
|
296
296
|
|
|
297
297
|
The suggestion to use Symbol.for with a guid, as opposed to just Symbol(), is based on some negative experiences I've had with multiple versions of the same library being referenced, but is not required. Regular symbols could also be used when that risk can be avoided.
|
|
298
298
|
|
|
@@ -303,7 +303,7 @@ const result = assignGingerly({}, {
|
|
|
303
303
|
"[Symbol.for('TFWsx0YH5E6eSfhE7zfLxA')]": true,
|
|
304
304
|
"[Symbol.for('BqnnTPWRHkWdVGWcGQoAiw')]": true,
|
|
305
305
|
'?.style.height': '40px',
|
|
306
|
-
'?.
|
|
306
|
+
'?.enh?.mellowYellow?.madAboutFourteen': true
|
|
307
307
|
}, {
|
|
308
308
|
registry: BaseRegistry
|
|
309
309
|
});
|
|
@@ -406,3 +406,182 @@ myElement.assignGingerly({
|
|
|
406
406
|
```
|
|
407
407
|
|
|
408
408
|
**Browser Support**: This feature requires Chrome 146+ with scoped custom element registry support. The implementation is designed as a polyfill for the web standards proposal and does not include fallback behavior for older browsers.
|
|
409
|
+
|
|
410
|
+
## Enhanced Element Property Assignment with `set` Proxy (Chrome 146+)
|
|
411
|
+
|
|
412
|
+
Building on the Custom Element Registry integration, this package provides a powerful `set` proxy on all Element instances that enables automatic enhancement spawning and simplified property assignment syntax.
|
|
413
|
+
|
|
414
|
+
### Basic Usage
|
|
415
|
+
|
|
416
|
+
The `set` proxy allows you to assign properties to enhancements using a clean, chainable syntax:
|
|
417
|
+
|
|
418
|
+
```TypeScript
|
|
419
|
+
import 'assign-gingerly/object-extension.js';
|
|
420
|
+
import { BaseRegistry } from 'assign-gingerly';
|
|
421
|
+
|
|
422
|
+
// Define an enhancement class
|
|
423
|
+
class MyEnhancement {
|
|
424
|
+
element;
|
|
425
|
+
ctx;
|
|
426
|
+
myProp = null;
|
|
427
|
+
anotherProp = null;
|
|
428
|
+
|
|
429
|
+
constructor(oElement, ctx, initVals) {
|
|
430
|
+
this.element = oElement;
|
|
431
|
+
this.ctx = ctx;
|
|
432
|
+
if (initVals) {
|
|
433
|
+
Object.assign(this, initVals);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Register the enhancement with an enhKey
|
|
439
|
+
const myElement = document.createElement('div');
|
|
440
|
+
const registry = myElement.customElementRegistry.assignGingerlyRegistry;
|
|
441
|
+
|
|
442
|
+
registry.push({
|
|
443
|
+
spawn: MyEnhancement,
|
|
444
|
+
map: {},
|
|
445
|
+
enhKey: 'myEnh' // Key identifier for this enhancement
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// Use the set proxy to automatically spawn and assign properties
|
|
449
|
+
myElement.set.myEnh.myProp = 'hello';
|
|
450
|
+
myElement.set.myEnh.anotherProp = 'world';
|
|
451
|
+
|
|
452
|
+
console.log(myElement.myEnh instanceof MyEnhancement); // true
|
|
453
|
+
console.log(myElement.myEnh.myProp); // 'hello'
|
|
454
|
+
console.log(myElement.myEnh.element === myElement); // true
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### How It Works
|
|
458
|
+
|
|
459
|
+
When you access `element.set.enhKey.property`, the proxy:
|
|
460
|
+
|
|
461
|
+
1. **Checks the registry**: Looks for a registry item with `enhKey` matching the property name
|
|
462
|
+
2. **Spawns if needed**: If found and the enhancement doesn't exist or is the wrong type:
|
|
463
|
+
- Creates a `SpawnContext` with `{ mountInfo: registryItem }`
|
|
464
|
+
- Calls the constructor with `(element, ctx, initVals)`
|
|
465
|
+
- If a non-matching object already exists at `element[enhKey]`, it's passed as `initVals`
|
|
466
|
+
- Stores the spawned instance at `element[enhKey]`
|
|
467
|
+
3. **Reuses existing instances**: If the enhancement already exists and is the correct type, it reuses it
|
|
468
|
+
4. **Falls back to plain objects**: If no registry item is found, creates a plain object at `element[enhKey]`
|
|
469
|
+
|
|
470
|
+
### Constructor Signature
|
|
471
|
+
|
|
472
|
+
Enhancement classes should follow this constructor signature:
|
|
473
|
+
|
|
474
|
+
```TypeScript
|
|
475
|
+
interface SpawnContext<T> {
|
|
476
|
+
mountInfo: IBaseRegistryItem<T>;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
class Enhancement {
|
|
480
|
+
constructor(
|
|
481
|
+
oElement?: Element, // The element being enhanced
|
|
482
|
+
ctx?: SpawnContext, // Context with registry item info
|
|
483
|
+
initVals?: Partial<T> // Initial values if property existed
|
|
484
|
+
) {
|
|
485
|
+
// Your initialization logic
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
All parameters are optional for backward compatibility with existing code.
|
|
491
|
+
|
|
492
|
+
### Registry Item with enhKey
|
|
493
|
+
|
|
494
|
+
Registry items now support an optional `enhKey` property:
|
|
495
|
+
|
|
496
|
+
```TypeScript
|
|
497
|
+
interface IBaseRegistryItem<T> {
|
|
498
|
+
spawn: { new (oElement?: Element, ctx?: SpawnContext<T>, initVals?: Partial<T>): T };
|
|
499
|
+
map: { [key: string | symbol]: keyof T };
|
|
500
|
+
enhKey?: string; // String identifier for set proxy access
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Advanced Examples
|
|
505
|
+
|
|
506
|
+
**Multiple Enhancements:**
|
|
507
|
+
```TypeScript
|
|
508
|
+
class StyleEnhancement {
|
|
509
|
+
constructor(oElement, ctx, initVals) {
|
|
510
|
+
this.element = oElement;
|
|
511
|
+
}
|
|
512
|
+
height = null;
|
|
513
|
+
width = null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
class DataEnhancement {
|
|
517
|
+
constructor(oElement, ctx, initVals) {
|
|
518
|
+
this.element = oElement;
|
|
519
|
+
}
|
|
520
|
+
value = null;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const element = document.createElement('div');
|
|
524
|
+
const registry = element.customElementRegistry.assignGingerlyRegistry;
|
|
525
|
+
|
|
526
|
+
registry.push([
|
|
527
|
+
{ spawn: StyleEnhancement, map: {}, enhKey: 'styles' },
|
|
528
|
+
{ spawn: DataEnhancement, map: {}, enhKey: 'data' }
|
|
529
|
+
]);
|
|
530
|
+
|
|
531
|
+
element.set.styles.height = '100px';
|
|
532
|
+
element.set.data.value = 'test';
|
|
533
|
+
|
|
534
|
+
console.log(element.styles instanceof StyleEnhancement); // true
|
|
535
|
+
console.log(element.data instanceof DataEnhancement); // true
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**Preserving Existing Data with initVals:**
|
|
539
|
+
```TypeScript
|
|
540
|
+
const element = document.createElement('div');
|
|
541
|
+
const registry = element.customElementRegistry.assignGingerlyRegistry;
|
|
542
|
+
|
|
543
|
+
registry.push({
|
|
544
|
+
spawn: MyEnhancement,
|
|
545
|
+
map: {},
|
|
546
|
+
enhKey: 'config'
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Set a plain object first
|
|
550
|
+
element.config = { existingProp: 'preserved', anotherProp: 'also preserved' };
|
|
551
|
+
|
|
552
|
+
// Access via set proxy - spawns enhancement with initVals
|
|
553
|
+
element.set.config.newProp = 'added';
|
|
554
|
+
|
|
555
|
+
console.log(element.config instanceof MyEnhancement); // true
|
|
556
|
+
console.log(element.config.existingProp); // 'preserved'
|
|
557
|
+
console.log(element.config.newProp); // 'added'
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**Plain Objects Without Registry:**
|
|
561
|
+
```TypeScript
|
|
562
|
+
const element = document.createElement('div');
|
|
563
|
+
|
|
564
|
+
// No registry item for 'plainData' - creates plain object
|
|
565
|
+
element.set.plainData.prop1 = 'value1';
|
|
566
|
+
element.set.plainData.prop2 = 'value2';
|
|
567
|
+
|
|
568
|
+
console.log(element.plainData); // { prop1: 'value1', prop2: 'value2' }
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Finding Registry Items by enhKey
|
|
572
|
+
|
|
573
|
+
The `BaseRegistry` class includes a `findByEnhKey` method:
|
|
574
|
+
|
|
575
|
+
```TypeScript
|
|
576
|
+
const registry = new BaseRegistry();
|
|
577
|
+
registry.push({
|
|
578
|
+
spawn: MyEnhancement,
|
|
579
|
+
map: {},
|
|
580
|
+
enhKey: 'myEnh'
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
const item = registry.findByEnhKey('myEnh');
|
|
584
|
+
console.log(item.enhKey); // 'myEnh'
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**Browser Support**: This feature requires Chrome 146+ with scoped custom element registry support.
|
package/assignGingerly.js
CHANGED
|
@@ -29,6 +29,9 @@ export class BaseRegistry {
|
|
|
29
29
|
}) || Object.getOwnPropertySymbols(map).some(sym => sym === symbol);
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
+
findByEnhKey(enhKey) {
|
|
33
|
+
return this.items.find(item => item.enhKey === enhKey);
|
|
34
|
+
}
|
|
32
35
|
}
|
|
33
36
|
/**
|
|
34
37
|
* Helper function to check if a string key represents a Symbol.for expression
|
package/assignGingerly.ts
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
* Interface for registry items that define dependency injection mappings
|
|
3
3
|
*/
|
|
4
4
|
export interface IBaseRegistryItem<T = any> {
|
|
5
|
-
spawn: { new (): T };
|
|
5
|
+
spawn: { new (oElement?: Element, ctx?: SpawnContext<T>, initVals?: Partial<T>): T };
|
|
6
6
|
map: { [key: string | symbol]: keyof T };
|
|
7
7
|
enhKey?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export interface SpawnContext<T = any> {
|
|
11
|
+
mountInfo: IBaseRegistryItem<T>;
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
/**
|
|
11
15
|
* Interface for the options passed to assignGingerly
|
|
12
16
|
*/
|
|
@@ -48,6 +52,10 @@ export class BaseRegistry {
|
|
|
48
52
|
}) || Object.getOwnPropertySymbols(map).some(sym => sym === symbol);
|
|
49
53
|
});
|
|
50
54
|
}
|
|
55
|
+
|
|
56
|
+
findByEnhKey(enhKey: string | symbol): IBaseRegistryItem | undefined {
|
|
57
|
+
return this.items.find(item => item.enhKey === enhKey);
|
|
58
|
+
}
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/**
|
package/object-extension.js
CHANGED
|
@@ -20,6 +20,62 @@ if (typeof CustomElementRegistry !== 'undefined') {
|
|
|
20
20
|
configurable: true,
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Adds 'set' proxy to Element prototype for enhanced property assignment
|
|
25
|
+
* Supports automatic spawning of enhancement classes based on registry
|
|
26
|
+
*/
|
|
27
|
+
if (typeof Element !== 'undefined') {
|
|
28
|
+
const setProxyWeakMap = new WeakMap();
|
|
29
|
+
Object.defineProperty(Element.prototype, 'set', {
|
|
30
|
+
get: function () {
|
|
31
|
+
if (!setProxyWeakMap.has(this)) {
|
|
32
|
+
const self = this;
|
|
33
|
+
const proxy = new Proxy(self, {
|
|
34
|
+
get(obj, prop) {
|
|
35
|
+
// Get the registry from customElementRegistry
|
|
36
|
+
const registry = self.customElementRegistry?.assignGingerlyRegistry;
|
|
37
|
+
if (registry) {
|
|
38
|
+
// Check if there's a registry item with matching enhKey
|
|
39
|
+
const registryItem = registry.findByEnhKey(prop);
|
|
40
|
+
if (registryItem) {
|
|
41
|
+
const SpawnClass = registryItem.spawn;
|
|
42
|
+
// Check if enhancement already exists and is correct instance
|
|
43
|
+
if (self[prop] && self[prop] instanceof SpawnClass) {
|
|
44
|
+
// Already exists, just return it
|
|
45
|
+
return self[prop];
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Need to spawn
|
|
49
|
+
let initVals = undefined;
|
|
50
|
+
// If property exists but isn't the right instance, pass it as initVals
|
|
51
|
+
if (self[prop] && !(self[prop] instanceof SpawnClass)) {
|
|
52
|
+
initVals = self[prop];
|
|
53
|
+
}
|
|
54
|
+
// Create spawn context
|
|
55
|
+
const ctx = { mountInfo: registryItem };
|
|
56
|
+
// Spawn the instance
|
|
57
|
+
const instance = new SpawnClass(self, ctx, initVals);
|
|
58
|
+
// Set it on the element
|
|
59
|
+
self[prop] = instance;
|
|
60
|
+
return instance;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// No registry item found - create plain object if needed
|
|
65
|
+
if (self[prop] === undefined) {
|
|
66
|
+
self[prop] = {};
|
|
67
|
+
}
|
|
68
|
+
return self[prop];
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
setProxyWeakMap.set(this, proxy);
|
|
72
|
+
}
|
|
73
|
+
return setProxyWeakMap.get(this);
|
|
74
|
+
},
|
|
75
|
+
enumerable: true,
|
|
76
|
+
configurable: true,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
23
79
|
/**
|
|
24
80
|
* Adds assignGingerly method to all objects via the Object prototype
|
|
25
81
|
*/
|
package/object-extension.ts
CHANGED
|
@@ -79,6 +79,75 @@ if (typeof CustomElementRegistry !== 'undefined') {
|
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Adds 'set' proxy to Element prototype for enhanced property assignment
|
|
84
|
+
* Supports automatic spawning of enhancement classes based on registry
|
|
85
|
+
*/
|
|
86
|
+
if (typeof Element !== 'undefined') {
|
|
87
|
+
const setProxyWeakMap = new WeakMap<Element, ProxyHandler<Element>>();
|
|
88
|
+
|
|
89
|
+
Object.defineProperty(Element.prototype, 'set', {
|
|
90
|
+
get: function (this: Element) {
|
|
91
|
+
if (!setProxyWeakMap.has(this)) {
|
|
92
|
+
const self = this;
|
|
93
|
+
const proxy = new Proxy(self, {
|
|
94
|
+
get(obj: any, prop: string | symbol) {
|
|
95
|
+
// Get the registry from customElementRegistry
|
|
96
|
+
const registry = (self as any).customElementRegistry?.assignGingerlyRegistry;
|
|
97
|
+
|
|
98
|
+
if (registry) {
|
|
99
|
+
// Check if there's a registry item with matching enhKey
|
|
100
|
+
const registryItem = registry.findByEnhKey(prop);
|
|
101
|
+
|
|
102
|
+
if (registryItem) {
|
|
103
|
+
const SpawnClass = registryItem.spawn;
|
|
104
|
+
|
|
105
|
+
// Check if enhancement already exists and is correct instance
|
|
106
|
+
if (self[prop as any] && self[prop as any] instanceof SpawnClass) {
|
|
107
|
+
// Already exists, just return it
|
|
108
|
+
return self[prop as any];
|
|
109
|
+
} else {
|
|
110
|
+
// Need to spawn
|
|
111
|
+
let initVals: any = undefined;
|
|
112
|
+
|
|
113
|
+
// If property exists but isn't the right instance, pass it as initVals
|
|
114
|
+
if (self[prop as any] && !(self[prop as any] instanceof SpawnClass)) {
|
|
115
|
+
initVals = self[prop as any];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Create spawn context
|
|
119
|
+
const ctx = { mountInfo: registryItem };
|
|
120
|
+
|
|
121
|
+
// Spawn the instance
|
|
122
|
+
const instance = new SpawnClass(self, ctx, initVals);
|
|
123
|
+
|
|
124
|
+
// Set it on the element
|
|
125
|
+
(self as any)[prop] = instance;
|
|
126
|
+
|
|
127
|
+
return instance;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// No registry item found - create plain object if needed
|
|
133
|
+
if (self[prop as any] === undefined) {
|
|
134
|
+
(self as any)[prop] = {};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return self[prop as any];
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
setProxyWeakMap.set(this, proxy);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return setProxyWeakMap.get(this);
|
|
145
|
+
},
|
|
146
|
+
enumerable: true,
|
|
147
|
+
configurable: true,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
82
151
|
/**
|
|
83
152
|
* Adds assignGingerly method to all objects via the Object prototype
|
|
84
153
|
*/
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
export type EnhKey = string | symbol;
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Interface for registry items that define dependency injection mappings
|
|
3
5
|
*/
|
|
4
6
|
export interface IBaseRegistryItem<T = any> {
|
|
5
|
-
spawn: { new (
|
|
7
|
+
spawn: { new (oElement?: Element, ctx?: SpawnContext<T>, initVals?: Partial<T>): T };
|
|
6
8
|
map: { [key: string | symbol]: keyof T };
|
|
9
|
+
enhKey?: EnhKey;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SpawnContext<T = any> {
|
|
13
|
+
mountInfo: IBaseRegistryItem<T>;
|
|
7
14
|
}
|
|
8
15
|
|
|
9
16
|
/**
|
|
@@ -21,6 +28,7 @@ export declare class BaseRegistry {
|
|
|
21
28
|
push(items: IBaseRegistryItem | IBaseRegistryItem[]): void;
|
|
22
29
|
getItems(): IBaseRegistryItem[];
|
|
23
30
|
findBySymbol(symbol: symbol | string): IBaseRegistryItem | undefined;
|
|
31
|
+
findByEnhKey(enhKey: string | symbol): IBaseRegistryItem | undefined;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
/**
|