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 CHANGED
@@ -282,8 +282,8 @@ baseRegistry.push([
282
282
  const result = assignGingerly({}, {
283
283
  [isHappy]: true,
284
284
  [isMellow]: true,
285
- '?.style.height': '40px',
286
- '?.enhancements?.mellowYellow?.madAboutFourteen': true
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 respawns 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
- '?.enhancements?.mellowYellow?.madAboutFourteen': true
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
  /**
@@ -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
  */
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "This package provides a utility function for carefully merging one object into another.",
5
5
  "homepage": "https://github.com/bahrus/assign-gingerly#readme",
6
6
  "bugs": {
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 (): T } | Promise<{ new (): T }>;
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
  /**