assign-gingerly 0.0.16 → 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 +76 -28
- package/buildCSSQuery.js +31 -17
- package/buildCSSQuery.ts +32 -20
- package/package.json +1 -1
- package/types/assign-gingerly/types.d.ts +15 -5
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
|
-
|
|
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(
|
|
290
|
-
symlinks
|
|
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
|
-
|
|
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 (
|
|
711
|
-
canSpawn?: (obj:
|
|
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
|
|
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)
|
|
@@ -2206,6 +2219,21 @@ console.log(query);
|
|
|
2206
2219
|
const elements = document.querySelectorAll(query);
|
|
2207
2220
|
```
|
|
2208
2221
|
|
|
2222
|
+
**Without selectors (matches any element):**
|
|
2223
|
+
|
|
2224
|
+
```TypeScript
|
|
2225
|
+
// Omit the selectors parameter
|
|
2226
|
+
const query = buildCSSQuery(config);
|
|
2227
|
+
// or explicitly pass empty string
|
|
2228
|
+
const query = buildCSSQuery(config, '');
|
|
2229
|
+
|
|
2230
|
+
console.log(query);
|
|
2231
|
+
// '[my-component], [enh-my-component], [my-component-theme], [enh-my-component-theme]'
|
|
2232
|
+
|
|
2233
|
+
// Matches any element with these attributes
|
|
2234
|
+
const elements = document.querySelectorAll(query);
|
|
2235
|
+
```
|
|
2236
|
+
|
|
2209
2237
|
### How It Works
|
|
2210
2238
|
|
|
2211
2239
|
`buildCSSQuery` creates a cross-product of:
|
|
@@ -2289,11 +2317,27 @@ buildCSSQuery(config, 'div');
|
|
|
2289
2317
|
|
|
2290
2318
|
### Edge Cases
|
|
2291
2319
|
|
|
2292
|
-
**
|
|
2320
|
+
**Omitting or empty selectors return attribute-only selectors:**
|
|
2321
|
+
```TypeScript
|
|
2322
|
+
const config = {
|
|
2323
|
+
spawn: MyClass,
|
|
2324
|
+
withAttrs: {
|
|
2325
|
+
base: 'my-attr',
|
|
2326
|
+
theme: '${base}-theme'
|
|
2327
|
+
}
|
|
2328
|
+
};
|
|
2329
|
+
|
|
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]'
|
|
2334
|
+
// Matches any element with these attributes
|
|
2335
|
+
```
|
|
2336
|
+
|
|
2337
|
+
**Empty withAttrs returns empty string:**
|
|
2293
2338
|
```TypeScript
|
|
2294
2339
|
buildCSSQuery({ spawn: MyClass }, 'div'); // '' (no withAttrs)
|
|
2295
2340
|
buildCSSQuery({ spawn: MyClass, withAttrs: {} }, 'div'); // '' (empty withAttrs)
|
|
2296
|
-
buildCSSQuery({ spawn: MyClass, withAttrs: { base: 'x' } }, ''); // '' (empty selectors)
|
|
2297
2341
|
```
|
|
2298
2342
|
|
|
2299
2343
|
**Deduplication:**
|
|
@@ -2312,14 +2356,15 @@ buildCSSQuery(config, ' div , span , p ');
|
|
|
2312
2356
|
|
|
2313
2357
|
1. **Mount Observer Integration**: Find elements that need enhancement
|
|
2314
2358
|
```TypeScript
|
|
2315
|
-
|
|
2359
|
+
// Match any element with the attributes
|
|
2360
|
+
const query = buildCSSQuery(enhancementConfig);
|
|
2316
2361
|
const observer = new MutationObserver(() => {
|
|
2317
2362
|
const elements = document.querySelectorAll(query);
|
|
2318
2363
|
elements.forEach(el => enhance(el));
|
|
2319
2364
|
});
|
|
2320
2365
|
```
|
|
2321
2366
|
|
|
2322
|
-
2. **
|
|
2367
|
+
2. **Specific Element Types**: Enhance only certain element types
|
|
2323
2368
|
```TypeScript
|
|
2324
2369
|
const query = buildCSSQuery(config, 'template, script');
|
|
2325
2370
|
document.querySelectorAll(query).forEach(el => {
|
|
@@ -2338,17 +2383,20 @@ buildCSSQuery(config, ' div , span , p ');
|
|
|
2338
2383
|
```TypeScript
|
|
2339
2384
|
function buildCSSQuery(
|
|
2340
2385
|
config: EnhancementConfig,
|
|
2341
|
-
selectors
|
|
2386
|
+
selectors?: string
|
|
2342
2387
|
): string
|
|
2343
2388
|
```
|
|
2344
2389
|
|
|
2345
2390
|
**Parameters:**
|
|
2346
2391
|
- `config`: Enhancement configuration with `withAttrs` property
|
|
2347
|
-
- `selectors
|
|
2392
|
+
- `selectors` (optional): Comma-separated CSS selectors (e.g., `'div, span'`)
|
|
2393
|
+
- If omitted or empty string, returns attribute selectors without element prefix
|
|
2394
|
+
- This matches any element with the specified attributes
|
|
2348
2395
|
|
|
2349
2396
|
**Returns:**
|
|
2350
2397
|
- CSS query string with cross-product of selectors and attributes
|
|
2351
|
-
-
|
|
2398
|
+
- If selectors is omitted or empty: returns attribute-only selectors (e.g., `'[attr], [enh-attr]'`)
|
|
2399
|
+
- If withAttrs is missing or empty: returns empty string
|
|
2352
2400
|
|
|
2353
2401
|
**Throws:**
|
|
2354
2402
|
- Error if template variables have circular references
|
package/buildCSSQuery.js
CHANGED
|
@@ -64,7 +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 -
|
|
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
|
|
68
69
|
* @returns CSS query string with cross-product of selectors and attributes
|
|
69
70
|
*
|
|
70
71
|
* @example
|
|
@@ -76,36 +77,49 @@ function extractAttributeNames(withAttrs) {
|
|
|
76
77
|
* }
|
|
77
78
|
* };
|
|
78
79
|
*
|
|
80
|
+
* // With selectors
|
|
79
81
|
* buildCSSQuery(config, 'div, span');
|
|
80
82
|
* // Returns: 'div[my-attr], span[my-attr], div[enh-my-attr], span[enh-my-attr],
|
|
81
83
|
* // div[my-attr-theme], span[my-attr-theme], div[enh-my-attr-theme], span[enh-my-attr-theme]'
|
|
84
|
+
*
|
|
85
|
+
* // Without selectors (matches any element)
|
|
86
|
+
* buildCSSQuery(config);
|
|
87
|
+
* // or
|
|
88
|
+
* buildCSSQuery(config, '');
|
|
89
|
+
* // Returns: '[my-attr], [enh-my-attr], [my-attr-theme], [enh-my-attr-theme]'
|
|
82
90
|
*/
|
|
83
91
|
export function buildCSSQuery(config, selectors) {
|
|
84
92
|
// Validate inputs
|
|
85
|
-
if (!config.withAttrs
|
|
86
|
-
return '';
|
|
87
|
-
}
|
|
88
|
-
// Parse and normalize selectors
|
|
89
|
-
const selectorList = selectors
|
|
90
|
-
.split(',')
|
|
91
|
-
.map(s => s.trim())
|
|
92
|
-
.filter(s => s.length > 0);
|
|
93
|
-
if (selectorList.length === 0) {
|
|
93
|
+
if (!config.withAttrs) {
|
|
94
94
|
return '';
|
|
95
95
|
}
|
|
96
|
-
// Extract and resolve attribute names
|
|
96
|
+
// Extract and resolve attribute names first
|
|
97
97
|
const attrNames = extractAttributeNames(config.withAttrs);
|
|
98
98
|
if (attrNames.length === 0) {
|
|
99
99
|
return '';
|
|
100
100
|
}
|
|
101
|
-
//
|
|
101
|
+
// Parse and normalize selectors
|
|
102
|
+
const selectorList = selectors
|
|
103
|
+
? selectors.split(',').map(s => s.trim()).filter(s => s.length > 0)
|
|
104
|
+
: [];
|
|
105
|
+
// Build queries
|
|
102
106
|
const queries = [];
|
|
103
|
-
|
|
107
|
+
if (selectorList.length === 0) {
|
|
108
|
+
// No selectors provided - return just attribute selectors
|
|
104
109
|
for (const attrName of attrNames) {
|
|
105
|
-
|
|
106
|
-
queries.push(
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
queries.push(`[${attrName}]`);
|
|
111
|
+
queries.push(`[enh-${attrName}]`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// Build cross-product of selectors × attributes × prefixes
|
|
116
|
+
for (const selector of selectorList) {
|
|
117
|
+
for (const attrName of attrNames) {
|
|
118
|
+
// Unprefixed version
|
|
119
|
+
queries.push(`${selector}[${attrName}]`);
|
|
120
|
+
// enh- prefixed version
|
|
121
|
+
queries.push(`${selector}[enh-${attrName}]`);
|
|
122
|
+
}
|
|
109
123
|
}
|
|
110
124
|
}
|
|
111
125
|
// Deduplicate and join
|
package/buildCSSQuery.ts
CHANGED
|
@@ -82,7 +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 -
|
|
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
|
|
86
87
|
* @returns CSS query string with cross-product of selectors and attributes
|
|
87
88
|
*
|
|
88
89
|
* @example
|
|
@@ -94,45 +95,56 @@ function extractAttributeNames(withAttrs: AttrPatterns<any>): string[] {
|
|
|
94
95
|
* }
|
|
95
96
|
* };
|
|
96
97
|
*
|
|
98
|
+
* // With selectors
|
|
97
99
|
* buildCSSQuery(config, 'div, span');
|
|
98
100
|
* // Returns: 'div[my-attr], span[my-attr], div[enh-my-attr], span[enh-my-attr],
|
|
99
101
|
* // div[my-attr-theme], span[my-attr-theme], div[enh-my-attr-theme], span[enh-my-attr-theme]'
|
|
102
|
+
*
|
|
103
|
+
* // Without selectors (matches any element)
|
|
104
|
+
* buildCSSQuery(config);
|
|
105
|
+
* // or
|
|
106
|
+
* buildCSSQuery(config, '');
|
|
107
|
+
* // Returns: '[my-attr], [enh-my-attr], [my-attr-theme], [enh-my-attr-theme]'
|
|
100
108
|
*/
|
|
101
109
|
export function buildCSSQuery(
|
|
102
110
|
config: EnhancementConfig,
|
|
103
|
-
selectors
|
|
111
|
+
selectors?: string
|
|
104
112
|
): string {
|
|
105
113
|
// Validate inputs
|
|
106
|
-
if (!config.withAttrs
|
|
114
|
+
if (!config.withAttrs) {
|
|
107
115
|
return '';
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
//
|
|
111
|
-
const selectorList = selectors
|
|
112
|
-
.split(',')
|
|
113
|
-
.map(s => s.trim())
|
|
114
|
-
.filter(s => s.length > 0);
|
|
115
|
-
|
|
116
|
-
if (selectorList.length === 0) {
|
|
117
|
-
return '';
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Extract and resolve attribute names
|
|
118
|
+
// Extract and resolve attribute names first
|
|
121
119
|
const attrNames = extractAttributeNames(config.withAttrs);
|
|
122
120
|
|
|
123
121
|
if (attrNames.length === 0) {
|
|
124
122
|
return '';
|
|
125
123
|
}
|
|
126
124
|
|
|
127
|
-
//
|
|
125
|
+
// Parse and normalize selectors
|
|
126
|
+
const selectorList = selectors
|
|
127
|
+
? selectors.split(',').map(s => s.trim()).filter(s => s.length > 0)
|
|
128
|
+
: [];
|
|
129
|
+
|
|
130
|
+
// Build queries
|
|
128
131
|
const queries: string[] = [];
|
|
129
132
|
|
|
130
|
-
|
|
133
|
+
if (selectorList.length === 0) {
|
|
134
|
+
// No selectors provided - return just attribute selectors
|
|
131
135
|
for (const attrName of attrNames) {
|
|
132
|
-
|
|
133
|
-
queries.push(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
queries.push(`[${attrName}]`);
|
|
137
|
+
queries.push(`[enh-${attrName}]`);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
// Build cross-product of selectors × attributes × prefixes
|
|
141
|
+
for (const selector of selectorList) {
|
|
142
|
+
for (const attrName of attrNames) {
|
|
143
|
+
// Unprefixed version
|
|
144
|
+
queries.push(`${selector}[${attrName}]`);
|
|
145
|
+
// enh- prefixed version
|
|
146
|
+
queries.push(`${selector}[enh-${attrName}]`);
|
|
147
|
+
}
|
|
136
148
|
}
|
|
137
149
|
}
|
|
138
150
|
|
package/package.json
CHANGED
|
@@ -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
|
+
}
|