assign-gingerly 0.0.29 → 0.0.31

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
@@ -1,4 +1,4 @@
1
- # assign-gingerly and assign-tentatively
1
+ # assign-gingerly and assign-tentatively
2
2
 
3
3
  [![Playwright Tests](https://github.com/bahrus/assign-gingerly/actions/workflows/CI.yml/badge.svg?branch=baseline)](https://github.com/bahrus/assign-gingerly/actions/workflows/CI.yml)
4
4
  [![NPM version](https://badge.fury.io/js/assign-gingerly.png)](http://badge.fury.io/js/assign-gingerly)
@@ -1460,19 +1460,19 @@ element.remove();
1460
1460
 
1461
1461
  // Case 1: Temporarily removed, will be re-added
1462
1462
  setTimeout(() => document.body.append(element), 1000);
1463
- // Don't dispose - enhancement should persist
1463
+ // ? Don't dispose - enhancement should persist
1464
1464
 
1465
1465
  // Case 2: Moved to another location
1466
1466
  otherContainer.append(element);
1467
- // Don't dispose - enhancement should persist
1467
+ // ? Don't dispose - enhancement should persist
1468
1468
 
1469
1469
  // Case 3: Cached for reuse
1470
1470
  elementCache.set('myElement', element);
1471
- // Don't dispose - enhancement should persist
1471
+ // ? Don't dispose - enhancement should persist
1472
1472
 
1473
1473
  // Case 4: Truly done, ready for GC
1474
1474
  element = null;
1475
- // Should dispose, but no way to detect this automatically
1475
+ // ? Should dispose, but no way to detect this automatically
1476
1476
  ```
1477
1477
 
1478
1478
  **Practical disposal strategies:**
@@ -1566,10 +1566,10 @@ class MyEnhancement {
1566
1566
  ```
1567
1567
 
1568
1568
  **Summary:**
1569
- - Storage mechanism prevents memory leaks via WeakMap
1570
- - ⚠️ Enhancement internals need manual cleanup via dispose()
1571
- - No automatic way to detect when disposal should happen
1572
- - 👍 Choose disposal strategy based on your application's lifecycle
1569
+ - ? Storage mechanism prevents memory leaks via WeakMap
1570
+ - ?? Enhancement internals need manual cleanup via dispose()
1571
+ - ? No automatic way to detect when disposal should happen
1572
+ - ?? Choose disposal strategy based on your application's lifecycle
1573
1573
 
1574
1574
  ### Waiting for Async Initialization with `enh.whenResolved(regItem)`
1575
1575
 
@@ -1976,7 +1976,7 @@ console.log(instance.value); // 'test123' (parsed from attribute)
1976
1976
 
1977
1977
  </details>
1978
1978
 
1979
- > ![NOTE]
1979
+ > [!NOTE]
1980
1980
  > `withAttrs` works with or without `enhKey`. When there's no `enhKey`, the parsed attributes are passed directly to the constructor. When there is an `enhKey`, they're merged with any pre-existing values on the enh container.
1981
1981
 
1982
1982
  ### The `enh-` Prefix for Attribute Isolation
@@ -2150,7 +2150,7 @@ The `base` attribute must contain either a dash (`-`) or a non-ASCII character t
2150
2150
  ```TypeScript
2151
2151
  // Valid base attributes
2152
2152
  const enhConfig1 = { base: 'data-config' }; // Has dash
2153
- const enhConfig2 = { base: '🎨-theme' }); // Has non-ASCII (and dash)
2153
+ const enhConfig2 = { base: '??-theme' }); // Has non-ASCII (and dash)
2154
2154
 
2155
2155
  // Invalid - throws error
2156
2156
  const enhConig3 = { base: 'config' }; // No dash or non-ASCII
@@ -2198,6 +2198,33 @@ const result = parseWithAttrs(element, {
2198
2198
  // Result: { name: 'Alice', age: '30' }
2199
2199
  ```
2200
2200
 
2201
+ **Deep Nesting:**
2202
+
2203
+ Template variables can reference other template variables to any depth, creating hierarchical attribute naming patterns:
2204
+
2205
+ ```TypeScript
2206
+ // HTML: <div data-app-user-profile-name="Alice" data-app-user-profile-email="alice@example.com"></div>
2207
+
2208
+ const result = parseWithAttrs(element, {
2209
+ base: 'data-',
2210
+ app: '${base}app',
2211
+ user: '${app}-user',
2212
+ profile: '${user}-profile',
2213
+ name: '${profile}-name',
2214
+ email: '${profile}-email'
2215
+ });
2216
+ // Result: { name: 'Alice', email: 'alice@example.com' }
2217
+
2218
+ // The resolution chain: base ? app ? user ? profile ? name/email
2219
+ // Resolves to: data-app-user-profile-name and data-app-user-profile-email
2220
+ ```
2221
+
2222
+ **Benefits of hierarchical variables:**
2223
+ - Build complex attribute names from simple parts
2224
+ - Maintain consistency across related attributes
2225
+ - Easy to refactor by changing a single variable
2226
+ - Self-documenting attribute structure
2227
+
2201
2228
  Template variables are resolved recursively and cached for performance. Circular references are detected and throw an error.
2202
2229
 
2203
2230
  ### Type Parsing with instanceOf
@@ -2376,12 +2403,12 @@ registerCommonParsers(globalParserRegistry);
2376
2403
 
2377
2404
  **Benefits of Named Parsers:**
2378
2405
 
2379
- - **JSON serializable** - Configs can be stored/transmitted as JSON
2380
- - **Reusable** - Define once, use everywhere
2381
- - **Maintainable** - Update parser logic in one place
2382
- - **Testable** - Test parsers independently
2383
- - **Discoverable** - `globalParserRegistry.getNames()` lists all available parsers
2384
- - **Backward compatible** - Inline functions still work
2406
+ - ? **JSON serializable** - Configs can be stored/transmitted as JSON
2407
+ - ? **Reusable** - Define once, use everywhere
2408
+ - ? **Maintainable** - Update parser logic in one place
2409
+ - ? **Testable** - Test parsers independently
2410
+ - ? **Discoverable** - `globalParserRegistry.getNames()` lists all available parsers
2411
+ - ? **Backward compatible** - Inline functions still work
2385
2412
 
2386
2413
  **Mixing Inline and Named Parsers:**
2387
2414
 
@@ -3408,6 +3435,3 @@ ItemScope Managers follow these design principles:
3408
3435
 
3409
3436
  This design ensures backward compatibility while providing powerful new capabilities for managing DOM fragments.
3410
3437
 
3411
-
3412
-
3413
-
@@ -284,6 +284,74 @@ if (typeof Element !== 'undefined') {
284
284
  enumerable: true,
285
285
  configurable: true,
286
286
  });
287
+ /**
288
+ * Adds 'set' property to Element prototype for symbol-based dependency injection
289
+ * Returns a proxy that intercepts symbol property assignments
290
+ */
291
+ Object.defineProperty(Element.prototype, 'set', {
292
+ get: function () {
293
+ const element = this;
294
+ return new Proxy({}, {
295
+ set: (_, prop, value) => {
296
+ if (typeof prop === 'symbol') {
297
+ // Get the registry from customElementRegistry (scoped or global)
298
+ const registry = element.customElementRegistry?.enhancementRegistry
299
+ ?? (typeof customElements !== 'undefined' ? customElements.enhancementRegistry : undefined);
300
+ if (registry) {
301
+ const registryItem = registry.findBySymbol(prop);
302
+ if (registryItem) {
303
+ const instanceMap = getInstanceMap();
304
+ const instances = instanceMap.getOrInsertComputed(element, () => new Map());
305
+ let instance = instances.get(registryItem);
306
+ if (!instance) {
307
+ const SpawnClass = registryItem.spawn;
308
+ // Check canSpawn if it exists
309
+ if (typeof SpawnClass.canSpawn === 'function') {
310
+ const ctx = { config: registryItem };
311
+ if (!SpawnClass.canSpawn(element, ctx)) {
312
+ // canSpawn returned false, skip spawning
313
+ return true;
314
+ }
315
+ }
316
+ // If target is an Element and registryItem has enhKey, pass element to constructor
317
+ if (registryItem.enhKey) {
318
+ const ctx = { config: registryItem };
319
+ const initVals = element.enh?.[registryItem.enhKey] &&
320
+ !(element.enh[registryItem.enhKey] instanceof SpawnClass)
321
+ ? element.enh[registryItem.enhKey]
322
+ : undefined;
323
+ instance = new SpawnClass(element, ctx, initVals);
324
+ }
325
+ else {
326
+ const ctx = { config: registryItem };
327
+ instance = new SpawnClass(element, ctx);
328
+ }
329
+ instances.set(registryItem, instance);
330
+ // If registryItem has enhKey, store on enh
331
+ if (registryItem.enhKey) {
332
+ if (!element.enh) {
333
+ // This shouldn't happen since enh is a getter, but be safe
334
+ element.enh = {};
335
+ }
336
+ element.enh[registryItem.enhKey] = instance;
337
+ }
338
+ }
339
+ if (registryItem.symlinks) {
340
+ const mappedKey = registryItem.symlinks[prop];
341
+ if (mappedKey && instance && typeof instance === 'object') {
342
+ instance[mappedKey] = value;
343
+ }
344
+ }
345
+ }
346
+ }
347
+ }
348
+ return true;
349
+ },
350
+ });
351
+ },
352
+ enumerable: false,
353
+ configurable: true,
354
+ });
287
355
  }
288
356
  /**
289
357
  * Adds assignGingerly method to all objects via the Object prototype
@@ -404,6 +404,84 @@ if (typeof Element !== 'undefined') {
404
404
  enumerable: true,
405
405
  configurable: true,
406
406
  });
407
+
408
+ /**
409
+ * Adds 'set' property to Element prototype for symbol-based dependency injection
410
+ * Returns a proxy that intercepts symbol property assignments
411
+ */
412
+ Object.defineProperty(Element.prototype, 'set', {
413
+ get: function (this: Element) {
414
+ const element = this;
415
+ return new Proxy(
416
+ {},
417
+ {
418
+ set: (_: any, prop: string | symbol, value: any) => {
419
+ if (typeof prop === 'symbol') {
420
+ // Get the registry from customElementRegistry (scoped or global)
421
+ const registry = (element as any).customElementRegistry?.enhancementRegistry
422
+ ?? (typeof customElements !== 'undefined' ? customElements.enhancementRegistry : undefined);
423
+
424
+ if (registry) {
425
+ const registryItem = registry.findBySymbol(prop);
426
+ if (registryItem) {
427
+ const instanceMap = getInstanceMap();
428
+ const instances = instanceMap.getOrInsertComputed(element, () => new Map());
429
+ let instance = instances.get(registryItem);
430
+
431
+ if (!instance) {
432
+ const SpawnClass = registryItem.spawn;
433
+
434
+ // Check canSpawn if it exists
435
+ if (typeof SpawnClass.canSpawn === 'function') {
436
+ const ctx = { config: registryItem };
437
+ if (!SpawnClass.canSpawn(element, ctx)) {
438
+ // canSpawn returned false, skip spawning
439
+ return true;
440
+ }
441
+ }
442
+
443
+ // If target is an Element and registryItem has enhKey, pass element to constructor
444
+ if (registryItem.enhKey) {
445
+ const ctx = { config: registryItem };
446
+ const initVals = (element as any).enh?.[registryItem.enhKey] &&
447
+ !((element as any).enh[registryItem.enhKey] instanceof SpawnClass)
448
+ ? (element as any).enh[registryItem.enhKey]
449
+ : undefined;
450
+ instance = new SpawnClass(element, ctx, initVals);
451
+ } else {
452
+ const ctx = { config: registryItem };
453
+ instance = new SpawnClass(element, ctx);
454
+ }
455
+
456
+ instances.set(registryItem, instance);
457
+
458
+ // If registryItem has enhKey, store on enh
459
+ if (registryItem.enhKey) {
460
+ if (!(element as any).enh) {
461
+ // This shouldn't happen since enh is a getter, but be safe
462
+ (element as any).enh = {};
463
+ }
464
+ (element as any).enh[registryItem.enhKey] = instance;
465
+ }
466
+ }
467
+
468
+ if(registryItem.symlinks){
469
+ const mappedKey = registryItem.symlinks[prop];
470
+ if (mappedKey && instance && typeof instance === 'object') {
471
+ (instance as any)[mappedKey] = value;
472
+ }
473
+ }
474
+ }
475
+ }
476
+ }
477
+ return true;
478
+ },
479
+ }
480
+ );
481
+ },
482
+ enumerable: false,
483
+ configurable: true,
484
+ });
407
485
  }
408
486
 
409
487
  /**
package/package.json CHANGED
@@ -1,75 +1,75 @@
1
- {
2
- "name": "assign-gingerly",
3
- "version": "0.0.29",
4
- "description": "This package provides a utility function for carefully merging one object into another.",
5
- "homepage": "https://github.com/bahrus/assign-gingerly#readme",
6
- "bugs": {
7
- "url": "https://github.com/bahrus/assign-gingerly/issues"
8
- },
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/bahrus/assign-gingerly.git"
12
- },
13
- "license": "MIT",
14
- "author": "Bruce B. Anderson <andeson.bruce.b@gmail.com>",
15
- "type": "module",
16
- "types": "types/assign-gingerly/types.d.ts",
17
- "files": [
18
- "*.js",
19
- "*.ts",
20
- "README.md",
21
- "LICENSE",
22
- "types/assign-gingerly/types.d.ts"
23
- ],
24
- "exports": {
25
- ".": {
26
- "default": "./index.js",
27
- "types": "./index.ts"
28
- },
29
- "./assignGingerly.js": {
30
- "default": "./assignGingerly.js",
31
- "types": "./assignGingerly.ts"
32
- },
33
- "./assignTentatively.js": {
34
- "default": "./assignTentatively.js",
35
- "types": "./assignTentatively.ts"
36
- },
37
- "./waitForEvent.js": {
38
- "default": "./waitForEvent.js"
39
- },
40
- "./parserRegistry.js": {
41
- "default": "./parserRegistry.js"
42
- },
43
- "./parseWithAttrs.js": {
44
- "default": "./parseWithAttrs.js",
45
- "types": "./parseWithAttrs.ts"
46
- },
47
- "./buildCSSQuery.js": {
48
- "default": "./buildCSSQuery.js",
49
- "types": "./buildCSSQuery.ts"
50
- },
51
- "./resolveTemplate.js": {
52
- "default": "./resolveTemplate.js",
53
- "types": "./resolveTemplate.ts"
54
- },
55
- "./getHost.js": {
56
- "default": "./getHost.js",
57
- "types": "./getHost.ts"
58
- }
59
- },
60
- "main": "index.js",
61
- "module": "index.js",
62
- "scripts": {
63
- "serve": "node ./node_modules/spa-ssi/serve.js",
64
- "test": "playwright test",
65
- "update": "ncu -u && npm install",
66
- "safari": "npx playwright wk http://localhost:8000",
67
- "chrome": "npx playwright cr http://localhost:8000"
68
- },
69
- "devDependencies": {
70
- "@playwright/test": "1.59.1",
71
- "spa-ssi": "0.0.27",
72
- "@types/node": "25.5.2",
73
- "typescript": "6.0.2"
74
- }
75
- }
1
+ {
2
+ "name": "assign-gingerly",
3
+ "version": "0.0.31",
4
+ "description": "This package provides a utility function for carefully merging one object into another.",
5
+ "homepage": "https://github.com/bahrus/assign-gingerly#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/bahrus/assign-gingerly/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/bahrus/assign-gingerly.git"
12
+ },
13
+ "license": "MIT",
14
+ "author": "Bruce B. Anderson <andeson.bruce.b@gmail.com>",
15
+ "type": "module",
16
+ "types": "types/assign-gingerly/types.d.ts",
17
+ "files": [
18
+ "*.js",
19
+ "*.ts",
20
+ "README.md",
21
+ "LICENSE",
22
+ "types/assign-gingerly/types.d.ts"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "default": "./index.js",
27
+ "types": "./index.ts"
28
+ },
29
+ "./assignGingerly.js": {
30
+ "default": "./assignGingerly.js",
31
+ "types": "./assignGingerly.ts"
32
+ },
33
+ "./assignTentatively.js": {
34
+ "default": "./assignTentatively.js",
35
+ "types": "./assignTentatively.ts"
36
+ },
37
+ "./waitForEvent.js": {
38
+ "default": "./waitForEvent.js"
39
+ },
40
+ "./parserRegistry.js": {
41
+ "default": "./parserRegistry.js"
42
+ },
43
+ "./parseWithAttrs.js": {
44
+ "default": "./parseWithAttrs.js",
45
+ "types": "./parseWithAttrs.ts"
46
+ },
47
+ "./buildCSSQuery.js": {
48
+ "default": "./buildCSSQuery.js",
49
+ "types": "./buildCSSQuery.ts"
50
+ },
51
+ "./resolveTemplate.js": {
52
+ "default": "./resolveTemplate.js",
53
+ "types": "./resolveTemplate.ts"
54
+ },
55
+ "./getHost.js": {
56
+ "default": "./getHost.js",
57
+ "types": "./getHost.ts"
58
+ }
59
+ },
60
+ "main": "index.js",
61
+ "module": "index.js",
62
+ "scripts": {
63
+ "serve": "node ./node_modules/spa-ssi/serve.js",
64
+ "test": "playwright test",
65
+ "update": "ncu -u && npm install",
66
+ "safari": "npx playwright wk http://localhost:8000",
67
+ "chrome": "npx playwright cr http://localhost:8000"
68
+ },
69
+ "devDependencies": {
70
+ "@playwright/test": "1.59.1",
71
+ "spa-ssi": "0.0.27",
72
+ "@types/node": "25.6.0",
73
+ "typescript": "6.0.3"
74
+ }
75
+ }
package/parseWithAttrs.js CHANGED
@@ -147,7 +147,8 @@ function hasDashOrNonASCII(str) {
147
147
  * @returns The attribute value or null
148
148
  */
149
149
  function getAttributeValue(element, attrName, allowUnprefixed) {
150
- const isCustomElement = element.tagName.includes('-');
150
+ const { localName } = element;
151
+ const isCustomElement = localName.includes('-');
151
152
  const isSVGElement = typeof SVGElement !== 'undefined' && element instanceof SVGElement;
152
153
  // For custom elements and SVG - strict enh- requirement
153
154
  if (isCustomElement || isSVGElement) {
@@ -157,8 +158,7 @@ function getAttributeValue(element, attrName, allowUnprefixed) {
157
158
  // Only fallback if tag name matches the allowUnprefixed pattern
158
159
  if (allowUnprefixed) {
159
160
  const pattern = typeof allowUnprefixed === 'string' ? new RegExp(allowUnprefixed) : allowUnprefixed;
160
- const tagName = element.tagName.toLowerCase();
161
- if (pattern.test(tagName)) {
161
+ if (pattern.test(localName)) {
162
162
  return element.getAttribute(attrName);
163
163
  }
164
164
  }
package/parseWithAttrs.ts CHANGED
@@ -186,7 +186,8 @@ function getAttributeValue(
186
186
  attrName: string,
187
187
  allowUnprefixed?: string | RegExp
188
188
  ): string | null {
189
- const isCustomElement = element.tagName.includes('-');
189
+ const { localName } = element;
190
+ const isCustomElement = localName.includes('-');
190
191
  const isSVGElement = typeof SVGElement !== 'undefined' && element instanceof SVGElement;
191
192
 
192
193
  // For custom elements and SVG - strict enh- requirement
@@ -197,8 +198,7 @@ function getAttributeValue(
197
198
  // Only fallback if tag name matches the allowUnprefixed pattern
198
199
  if (allowUnprefixed) {
199
200
  const pattern = typeof allowUnprefixed === 'string' ? new RegExp(allowUnprefixed) : allowUnprefixed;
200
- const tagName = element.tagName.toLowerCase();
201
- if (pattern.test(tagName)) {
201
+ if (pattern.test(localName)) {
202
202
  return element.getAttribute(attrName);
203
203
  }
204
204
  }
@@ -20,7 +20,7 @@ export default defineConfig({
20
20
  /* Opt out of parallel tests on CI. */
21
21
  workers: process.env.CI ? 1 : undefined,
22
22
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
23
- reporter: [ ['html', { open: 'never' }] ],
23
+ reporter: [ ['html', { open: 'never' }] ],
24
24
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
25
25
  use: {
26
26
  /* Base URL to use in actions like `await page.goto('/')`. */
@@ -298,6 +298,7 @@ export declare function assignGingerly(
298
298
  export default assignGingerly;
299
299
 
300
300
  export declare class ElementEnhancementGateway{
301
+ //TODO: this isn't right
301
302
  enh: ElementEnhancement;
302
303
  }
303
304