assign-gingerly 0.0.17 → 0.0.18

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
@@ -23,8 +23,14 @@ One can achieve the same functionality with a little more work, and "playing nic
23
23
 
24
24
  Not only does this polyfill package allow merging data properties onto objects that are expecting them, this polyfill also provides the ability to merge *augmented behavior* onto run-time objects without sub classing all such objects of the same type. This includes the ability to spawn an instance of a class and "merge" it into the API of the original object in an elegant way that is easy to wrap one's brain around, without ever blocking access to the original object or breaking it.
25
25
 
26
+
27
+
26
28
  So we are providing a form of the ["Decorator Pattern"](https://en.wikipedia.org/wiki/Decorator_pattern) or perhaps more accurately the [Extension Object Pattern](https://swiftorial.com/swiftlessons/design-patterns/structural-patterns/extension-object-pattern) as tailored for the quirks of the web.
27
29
 
30
+ ## Custom Enhancement Registry
31
+
32
+ On top of that, this polyfill package builds on the newly minted Custom Element Registry, adding an additional EnhancementRegistry object on top of the customElementRegistry object associated with all elements, to be able to manage namespace conflicts, and, importantly, as a basis for defining custom attributes associated with the enhancements.
33
+
28
34
  So in our view this package helps fill the void left by not supporting the "is" attribute for built-in elements (but is not a complete solution, just a critical building block). Mount-observer, mount-observer-script-element, and custom enhancements builds on top of the critical role that assign-gingerly plays.
29
35
 
30
36
  Anyway, let's start out detailing the more innocent features of this package / polyfill.
@@ -33,7 +39,7 @@ The two utility functions are:
33
39
 
34
40
  ## assignGingerly
35
41
 
36
- assignGingerly builds on Object.assign. Like Object.assign, the object getting assigned can be a JSON stringified object. Some of the unusual syntax we see with assignGingerly is there to continue to support JSON deserialized objects as a viable argument to be passed.
42
+ assignGingerly builds on Object.assign. Like Object.assign, the object getting assigned can often be a JSON stringified object. Some of the unusual syntax we see with assignGingerly is there to continue to support JSON deserialized objects as a viable argument to be passed.
37
43
 
38
44
  assign-gingerly adds support for:
39
45
 
@@ -96,7 +102,9 @@ console.log(obj);
96
102
 
97
103
  When the right hand side of an expression is an object, assignGingerly is recursively applied (passing the third argument in if applicable, which will be discussed below).
98
104
 
99
- 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
105
+ Of course, just as Object.assign led to object spread notation, assignGingerly could lead to some sort of deep structural JavaScript syntax, but that is outside the scope of this polyfill package.
106
+
107
+ 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.
100
108
 
101
109
  ## Example 4 - Incrementing values with += command
102
110
 
@@ -213,8 +221,7 @@ console.log(obj);
213
221
  - Non-existent properties are silently skipped
214
222
  - If the parent path doesn't exist, the command is silently skipped
215
223
  - For root-level deletion, use ` -=` (space before -=)
216
- // }
217
- // }
224
+
218
225
 
219
226
 
220
227
 
@@ -285,9 +292,9 @@ This guarantees that applying the reversal object restores the object to its exa
285
292
  ## Dependency injection based on a registry object and a Symbolic reference mapping
286
293
 
287
294
  ```Typescript
288
- interface IBaseRegistryItem<T = any> {
289
- spawn: {new(): T} | Promise<{new(): T}>
290
- symlinks: {[key: symbol]: keyof T}
295
+ interface IBaseRegistryItem<T = any, TObjToExtend = any> {
296
+ spawn: {new(objToExtend: TObjToExtend, ctx: SpawnContext, initVals: Partial<T>): T}
297
+ symlinks?: {[key: symbol]: keyof T}
291
298
  // Optional: for element enhancement access
292
299
  enhKey?: string
293
300
  // Optional: automatic attribute parsing
@@ -508,6 +515,9 @@ The prototype extensions are non-enumerable and won't appear in `Object.keys()`
508
515
 
509
516
  This package includes support for Chrome's scoped custom element registries, which automatically integrates dependency injection in harmony with scoped custom elements DOM sections or ShadowRoots.
510
517
 
518
+ > [!NOTE]
519
+ > Safari/WebKit played a critical role in pushing scoped custom element registries forward, and announced with little fanfare or documentation that [Safari 26 supports it](https://developer.apple.com/documentation/safari-release-notes/safari-26-release-notes). However, the Playwright test machinery's cross platform Safari test browser doesn't yet support it.
520
+
511
521
  <details>
512
522
  <summary>Automatic Registry Population</summary>
513
523
 
@@ -515,7 +525,6 @@ When `assignGingerly` or `assignTentatively` is called on an Element instance wi
515
525
 
516
526
  ```TypeScript
517
527
  import 'assign-gingerly/object-extension.js';
518
- import { BaseRegistry } from 'assign-gingerly';
519
528
 
520
529
  // Set up a registry on the custom element registry
521
530
  const myElement = document.createElement('div');
@@ -649,7 +658,7 @@ This approach is part of a proposal to WHATWG for standardizing element enhancem
649
658
 
650
659
  ### Constructor Signature
651
660
 
652
- Enhancement classes should follow this constructor signature:
661
+ Element enhancement classes should follow this constructor signature:
653
662
 
654
663
  ```TypeScript
655
664
  interface SpawnContext<T, TMountContext = any> {
@@ -657,7 +666,7 @@ interface SpawnContext<T, TMountContext = any> {
657
666
  mountCtx?: TMountContext; // Optional custom context passed by caller
658
667
  }
659
668
 
660
- class Enhancement {
669
+ class Enhancement<T> {
661
670
  constructor(
662
671
  oElement?: Element, // The element being enhanced
663
672
  ctx?: SpawnContext, // Context with registry item info and optional mountCtx
@@ -671,6 +680,8 @@ class Enhancement {
671
680
 
672
681
  All parameters are optional for backward compatibility with existing code.
673
682
 
683
+ Note that the class need not extend any base class or leverage any mixins. In fact, ES5 prototype functions can be used, and in both cases are instanted using new .... Arrow functions cannot be used.
684
+
674
685
  <details>
675
686
  <summary>Passing Custom Context</summary>
676
687
 
@@ -705,10 +716,10 @@ This is useful for:
705
716
  In addition to spawn and symlinks, registry items support optional properties `enhKey`, `withAttrs`, `canSpawn`, and `lifecycleKeys`:
706
717
 
707
718
  ```TypeScript
708
- interface IBaseRegistryItem<T> {
719
+ interface IBaseRegistryItem<T, TObj = Element> {
709
720
  spawn: {
710
- new (oElement?: Element, ctx?: SpawnContext<T>, initVals?: Partial<T>): T;
711
- canSpawn?: (obj: any, ctx?: SpawnContext<T>) => boolean; // Optional spawn guard
721
+ new (obj?: TObj, ctx?: SpawnContext<T>, initVals?: Partial<T>): T;
722
+ canSpawn?: (obj: TObj, ctx?: SpawnContext<T>) => boolean; // Optional spawn guard
712
723
  };
713
724
  symlinks?: { [key: string | symbol]: keyof T };
714
725
  enhKey?: string; // String identifier for set proxy access
@@ -818,7 +829,7 @@ console.log(item.enhKey); // 'myEnh'
818
829
 
819
830
  ### Programmatic Instance Spawning with `enh.get()`
820
831
 
821
- The `enh.get(registryItem)` method provides a programmatic way to spawn or retrieve enhancement instances:
832
+ The `enh.get(registryItem)` method provides a programmatic way to spawn or retrieve previously instantiated enhancement instances:
822
833
 
823
834
  ```TypeScript
824
835
  const registryItem = {
@@ -948,9 +959,9 @@ Note: Symbol event names are not yet supported by the platform but have been req
948
959
 
949
960
  </details>
950
961
 
951
- ### Disposing Enhancement Instances with `enh.dispose()`
962
+ ### Disposing Enhancement Instances with `enh.dispose(regItem)`
952
963
 
953
- The `enh.dispose()` method provides a way to clean up and remove enhancement instances:
964
+ The `enh.dispose(regItem)` method provides a way to clean up and remove enhancement instances:
954
965
 
955
966
  ```TypeScript
956
967
  class MyEnhancement {
@@ -991,7 +1002,7 @@ const instance = element.enh.get(registryItem);
991
1002
  element.enh.dispose(registryItem);
992
1003
  ```
993
1004
 
994
- **How `enh.dispose()` works:**
1005
+ **How `enh.dispose(regItem)` works:**
995
1006
 
996
1007
  1. **Retrieves instance**: Gets the spawned instance from the global instance map
997
1008
  2. **Calls lifecycle method**: If `lifecycleKeys.dispose` is specified, calls that method on the instance (passing the registry item)
@@ -1043,9 +1054,9 @@ element.enh.dispose(registryItem); // Stops timer and cleans up
1043
1054
  - Calling `enh.get()` again will create a new instance
1044
1055
  - The enhancement property is removed from the enh container
1045
1056
 
1046
- ### Waiting for Async Initialization with `enh.whenResolved()`
1057
+ ### Waiting for Async Initialization with `enh.whenResolved(regItem)`
1047
1058
 
1048
- The `enh.whenResolved()` method provides a way to wait for asynchronous enhancement initialization:
1059
+ The `enh.whenResolved(regItem)` method provides a way to wait for asynchronous enhancement initialization:
1049
1060
 
1050
1061
  ```TypeScript
1051
1062
  class AsyncEnhancement extends EventTarget {
@@ -1351,11 +1362,11 @@ assignGingerly(element, { [enhSymbol]: 'test' }, { registry });
1351
1362
 
1352
1363
  ## Parsing Attributes with `parseWithAttrs`
1353
1364
 
1354
- The `parseWithAttrs` function provides a declarative way to read and parse HTML attributes into structured data objects. It's particularly useful for custom elements and web components that need to extract configuration from attributes.
1365
+ The `parseWithAttrs` function provides a declarative way to read and parse HTML attributes and pass the parsed values into the spawned enhancement constructor.
1355
1366
 
1356
1367
  ### Automatic Integration with Enhancement Spawning
1357
1368
 
1358
- **Important**: When using the `enh.get()`, `enh.set`, or `assignGingerly()` methods with registry items, you typically **do not need to call `parseWithAttrs()` manually**. The attribute parsing happens automatically during enhancement spawning when you include a `withAttrs` property in your registry item.
1369
+ **Important**: When using the `enh.get()`, `enh.set`, or `assignGingerly()` methods with registry items, you typically **do not need to call `parseWithAttrs()` manually**. The attribute parsing happens automatically during enhancement spawning when you include a `withAttrs` property in your registry item configuration.
1359
1370
 
1360
1371
  ```html
1361
1372
  <my-element my-enhancement-count="42" my-enhancement-theme="dark"></my-element>
@@ -1747,6 +1758,8 @@ const result = parseWithAttrs(element, config);
1747
1758
 
1748
1759
  **Built-in Named Parsers:**
1749
1760
 
1761
+ [TODO]: Check if this is all needed
1762
+
1750
1763
  The following parsers are pre-registered in `globalParserRegistry`:
1751
1764
 
1752
1765
  - `'timestamp'` - Parses ISO date string to Unix timestamp (milliseconds)
@@ -2209,7 +2222,11 @@ const elements = document.querySelectorAll(query);
2209
2222
  **Without selectors (matches any element):**
2210
2223
 
2211
2224
  ```TypeScript
2225
+ // Omit the selectors parameter
2226
+ const query = buildCSSQuery(config);
2227
+ // or explicitly pass empty string
2212
2228
  const query = buildCSSQuery(config, '');
2229
+
2213
2230
  console.log(query);
2214
2231
  // '[my-component], [enh-my-component], [my-component-theme], [enh-my-component-theme]'
2215
2232
 
@@ -2300,7 +2317,7 @@ buildCSSQuery(config, 'div');
2300
2317
 
2301
2318
  ### Edge Cases
2302
2319
 
2303
- **Empty selectors return attribute-only selectors:**
2320
+ **Omitting or empty selectors return attribute-only selectors:**
2304
2321
  ```TypeScript
2305
2322
  const config = {
2306
2323
  spawn: MyClass,
@@ -2310,8 +2327,10 @@ const config = {
2310
2327
  }
2311
2328
  };
2312
2329
 
2313
- buildCSSQuery(config, '');
2314
- // '[my-attr], [enh-my-attr], [my-attr-theme], [enh-my-attr-theme]'
2330
+ buildCSSQuery(config); // Omit selectors parameter
2331
+ // or
2332
+ buildCSSQuery(config, ''); // Empty string
2333
+ // Both return: '[my-attr], [enh-my-attr], [my-attr-theme], [enh-my-attr-theme]'
2315
2334
  // Matches any element with these attributes
2316
2335
  ```
2317
2336
 
@@ -2338,7 +2357,7 @@ buildCSSQuery(config, ' div , span , p ');
2338
2357
  1. **Mount Observer Integration**: Find elements that need enhancement
2339
2358
  ```TypeScript
2340
2359
  // Match any element with the attributes
2341
- const query = buildCSSQuery(enhancementConfig, '');
2360
+ const query = buildCSSQuery(enhancementConfig);
2342
2361
  const observer = new MutationObserver(() => {
2343
2362
  const elements = document.querySelectorAll(query);
2344
2363
  elements.forEach(el => enhance(el));
@@ -2364,19 +2383,19 @@ buildCSSQuery(config, ' div , span , p ');
2364
2383
  ```TypeScript
2365
2384
  function buildCSSQuery(
2366
2385
  config: EnhancementConfig,
2367
- selectors: string
2386
+ selectors?: string
2368
2387
  ): string
2369
2388
  ```
2370
2389
 
2371
2390
  **Parameters:**
2372
2391
  - `config`: Enhancement configuration with `withAttrs` property
2373
- - `selectors`: Comma-separated CSS selectors (e.g., `'div, span'`)
2374
- - If empty string or whitespace only, returns attribute selectors without element prefix
2392
+ - `selectors` (optional): Comma-separated CSS selectors (e.g., `'div, span'`)
2393
+ - If omitted or empty string, returns attribute selectors without element prefix
2375
2394
  - This matches any element with the specified attributes
2376
2395
 
2377
2396
  **Returns:**
2378
2397
  - CSS query string with cross-product of selectors and attributes
2379
- - If selectors is empty: returns attribute-only selectors (e.g., `'[attr], [enh-attr]'`)
2398
+ - If selectors is omitted or empty: returns attribute-only selectors (e.g., `'[attr], [enh-attr]'`)
2380
2399
  - If withAttrs is missing or empty: returns empty string
2381
2400
 
2382
2401
  **Throws:**
package/buildCSSQuery.js CHANGED
@@ -64,8 +64,8 @@ function extractAttributeNames(withAttrs) {
64
64
  * Creates a cross-product of selectors and attribute names (both prefixed and unprefixed)
65
65
  *
66
66
  * @param config - Enhancement configuration with withAttrs
67
- * @param selectors - Comma-separated CSS selectors to match (e.g., 'template, script')
68
- * If empty, returns just the attribute selectors without element prefix
67
+ * @param selectors - Optional comma-separated CSS selectors to match (e.g., 'template, script')
68
+ * If omitted or empty, returns just the attribute selectors without element prefix
69
69
  * @returns CSS query string with cross-product of selectors and attributes
70
70
  *
71
71
  * @example
@@ -83,6 +83,8 @@ function extractAttributeNames(withAttrs) {
83
83
  * // div[my-attr-theme], span[my-attr-theme], div[enh-my-attr-theme], span[enh-my-attr-theme]'
84
84
  *
85
85
  * // Without selectors (matches any element)
86
+ * buildCSSQuery(config);
87
+ * // or
86
88
  * buildCSSQuery(config, '');
87
89
  * // Returns: '[my-attr], [enh-my-attr], [my-attr-theme], [enh-my-attr-theme]'
88
90
  */
package/buildCSSQuery.ts CHANGED
@@ -82,8 +82,8 @@ function extractAttributeNames(withAttrs: AttrPatterns<any>): string[] {
82
82
  * Creates a cross-product of selectors and attribute names (both prefixed and unprefixed)
83
83
  *
84
84
  * @param config - Enhancement configuration with withAttrs
85
- * @param selectors - Comma-separated CSS selectors to match (e.g., 'template, script')
86
- * If empty, returns just the attribute selectors without element prefix
85
+ * @param selectors - Optional comma-separated CSS selectors to match (e.g., 'template, script')
86
+ * If omitted or empty, returns just the attribute selectors without element prefix
87
87
  * @returns CSS query string with cross-product of selectors and attributes
88
88
  *
89
89
  * @example
@@ -101,12 +101,14 @@ function extractAttributeNames(withAttrs: AttrPatterns<any>): string[] {
101
101
  * // div[my-attr-theme], span[my-attr-theme], div[enh-my-attr-theme], span[enh-my-attr-theme]'
102
102
  *
103
103
  * // Without selectors (matches any element)
104
+ * buildCSSQuery(config);
105
+ * // or
104
106
  * buildCSSQuery(config, '');
105
107
  * // Returns: '[my-attr], [enh-my-attr], [my-attr-theme], [enh-my-attr-theme]'
106
108
  */
107
109
  export function buildCSSQuery(
108
110
  config: EnhancementConfig,
109
- selectors: string
111
+ selectors?: string
110
112
  ): string {
111
113
  // Validate inputs
112
114
  if (!config.withAttrs) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
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": {
@@ -25,16 +25,18 @@ type DisposeEvent =
25
25
  //reference count outside any enhancements goes to zero
26
26
  | 'dispose'
27
27
 
28
+ export type Spawner<T = any, Obj = Element> = {
29
+ new (obj?: Obj, ctx?: SpawnContext<T>, initVals?: Partial<T>): T;
30
+ canSpawn?: (obj: any, ctx?: SpawnContext<T>) => boolean;
31
+ }
32
+
28
33
  /**
29
34
  * Configuration for enhancing elements with class instances
30
35
  * Defines how to spawn and initialize enhancement classes
31
36
  */
32
- export interface EnhancementConfig<T = any> {
37
+ export interface EnhancementConfig<T = any, Obj = Element> {
33
38
 
34
- spawn: {
35
- new (obj?: any, ctx?: SpawnContext<T>, initVals?: Partial<T>): T;
36
- canSpawn?: (obj: any, ctx?: SpawnContext<T>) => boolean;
37
- };
39
+ spawn: Spawner<T, Obj>;
38
40
 
39
41
  //Applicable to passing in the initVals during the spawn lifecycle event
40
42
  withAttrs?: AttrPatterns<T>;
@@ -180,3 +182,11 @@ export declare function assignGingerly(
180
182
  ): any;
181
183
 
182
184
  export default assignGingerly;
185
+
186
+ export declare class ElementEnhancementGateway{
187
+ enh: ElementEnhancement;
188
+ }
189
+
190
+ export interface ElementEnhancement{
191
+ dispose(regItem: EnhancementConfig): void;
192
+ }