assign-gingerly 0.0.14 → 0.0.16
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 +218 -3
- package/buildCSSQuery.js +114 -0
- package/buildCSSQuery.ts +142 -0
- package/index.js +1 -0
- package/index.ts +1 -0
- package/object-extension.js +13 -13
- package/object-extension.ts +17 -17
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1400,14 +1400,48 @@ console.log(instance.count); // 42 (parsed from attribute)
|
|
|
1400
1400
|
console.log(instance.theme); // 'dark' (parsed from attribute)
|
|
1401
1401
|
```
|
|
1402
1402
|
|
|
1403
|
+
**Example without enhKey:**
|
|
1404
|
+
|
|
1405
|
+
```TypeScript
|
|
1406
|
+
// withAttrs works even without enhKey
|
|
1407
|
+
class SimpleEnhancement {
|
|
1408
|
+
element;
|
|
1409
|
+
ctx;
|
|
1410
|
+
value = null;
|
|
1411
|
+
|
|
1412
|
+
constructor(oElement, ctx, initVals) {
|
|
1413
|
+
this.element = oElement;
|
|
1414
|
+
this.ctx = ctx;
|
|
1415
|
+
if (initVals) {
|
|
1416
|
+
Object.assign(this, initVals);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
const element = document.createElement('div');
|
|
1422
|
+
element.setAttribute('data-value', 'test123');
|
|
1423
|
+
|
|
1424
|
+
const config = {
|
|
1425
|
+
spawn: SimpleEnhancement,
|
|
1426
|
+
// No enhKey - attributes still parsed!
|
|
1427
|
+
withAttrs: {
|
|
1428
|
+
base: 'data-',
|
|
1429
|
+
value: '${base}value'
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
const instance = element.enh.get(config);
|
|
1434
|
+
console.log(instance.value); // 'test123' (parsed from attribute)
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1403
1437
|
**How it works:**
|
|
1404
1438
|
1. When an enhancement is spawned via `enh.get()`, `enh.set`, or `assignGingerly()`
|
|
1405
1439
|
2. If the registry item has a `withAttrs` property defined
|
|
1406
1440
|
3. `parseWithAttrs(element, registryItem.withAttrs)` is automatically called
|
|
1407
|
-
4. The parsed attributes are
|
|
1408
|
-
5.
|
|
1441
|
+
4. The parsed attributes are passed to the enhancement constructor as `initVals`
|
|
1442
|
+
5. If the registry item also has an `enhKey`, the parsed attributes are merged with any existing values from `element.enh[enhKey]` (existing values take precedence)
|
|
1409
1443
|
|
|
1410
|
-
**
|
|
1444
|
+
**Note**: `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.
|
|
1411
1445
|
|
|
1412
1446
|
|
|
1413
1447
|
|
|
@@ -2146,6 +2180,187 @@ assignGingerly(element, attrs);
|
|
|
2146
2180
|
|
|
2147
2181
|
</details>
|
|
2148
2182
|
|
|
2183
|
+
## Building CSS Queries with `buildCSSQuery`
|
|
2184
|
+
|
|
2185
|
+
The `buildCSSQuery` function generates CSS selector strings that match elements with attributes defined in an enhancement configuration's `withAttrs`. This is particularly useful for libraries like mount-observer that need to find elements that should be enhanced.
|
|
2186
|
+
|
|
2187
|
+
### Basic Usage
|
|
2188
|
+
|
|
2189
|
+
```TypeScript
|
|
2190
|
+
import { buildCSSQuery } from 'assign-gingerly';
|
|
2191
|
+
|
|
2192
|
+
const config = {
|
|
2193
|
+
spawn: MyEnhancement,
|
|
2194
|
+
withAttrs: {
|
|
2195
|
+
base: 'my-component',
|
|
2196
|
+
theme: '${base}-theme'
|
|
2197
|
+
}
|
|
2198
|
+
};
|
|
2199
|
+
|
|
2200
|
+
const query = buildCSSQuery(config, 'div, span');
|
|
2201
|
+
console.log(query);
|
|
2202
|
+
// 'div[my-component], span[my-component], div[enh-my-component], span[enh-my-component],
|
|
2203
|
+
// div[my-component-theme], span[my-component-theme], div[enh-my-component-theme], span[enh-my-component-theme]'
|
|
2204
|
+
|
|
2205
|
+
// Use with querySelector
|
|
2206
|
+
const elements = document.querySelectorAll(query);
|
|
2207
|
+
```
|
|
2208
|
+
|
|
2209
|
+
### How It Works
|
|
2210
|
+
|
|
2211
|
+
`buildCSSQuery` creates a cross-product of:
|
|
2212
|
+
1. **Selectors**: The CSS selectors you provide (e.g., `'div, span'`)
|
|
2213
|
+
2. **Attributes**: All attribute names from `withAttrs` (resolving template variables)
|
|
2214
|
+
3. **Prefixes**: Both unprefixed and `enh-` prefixed versions
|
|
2215
|
+
|
|
2216
|
+
This ensures you find all elements that might be enhanced, regardless of whether they use the `enh-` prefix or not.
|
|
2217
|
+
|
|
2218
|
+
### Template Variable Resolution
|
|
2219
|
+
|
|
2220
|
+
Template variables in `withAttrs` are automatically resolved:
|
|
2221
|
+
|
|
2222
|
+
```TypeScript
|
|
2223
|
+
const config = {
|
|
2224
|
+
spawn: BeABeacon,
|
|
2225
|
+
withAttrs: {
|
|
2226
|
+
base: 'be-a-beacon',
|
|
2227
|
+
theme: '${base}-theme',
|
|
2228
|
+
size: '${base}-size'
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
|
|
2232
|
+
buildCSSQuery(config, 'template, script');
|
|
2233
|
+
// Returns selectors for: be-a-beacon, be-a-beacon-theme, be-a-beacon-size
|
|
2234
|
+
// Each with both prefixed and unprefixed versions
|
|
2235
|
+
```
|
|
2236
|
+
|
|
2237
|
+
### Complex Selectors
|
|
2238
|
+
|
|
2239
|
+
The function supports any valid CSS selector:
|
|
2240
|
+
|
|
2241
|
+
```TypeScript
|
|
2242
|
+
const config = {
|
|
2243
|
+
spawn: MyEnhancement,
|
|
2244
|
+
withAttrs: {
|
|
2245
|
+
base: 'data-enhanced'
|
|
2246
|
+
}
|
|
2247
|
+
};
|
|
2248
|
+
|
|
2249
|
+
// Classes and IDs
|
|
2250
|
+
buildCSSQuery(config, 'div.highlight, span#special');
|
|
2251
|
+
// 'div.highlight[data-enhanced], span#special[data-enhanced], ...'
|
|
2252
|
+
|
|
2253
|
+
// Combinators
|
|
2254
|
+
buildCSSQuery(config, 'div > span, ul li');
|
|
2255
|
+
// 'div > span[data-enhanced], ul li[data-enhanced], ...'
|
|
2256
|
+
|
|
2257
|
+
// Pseudo-classes
|
|
2258
|
+
buildCSSQuery(config, 'div:hover, span:first-child');
|
|
2259
|
+
// 'div:hover[data-enhanced], span:first-child[data-enhanced], ...'
|
|
2260
|
+
|
|
2261
|
+
// Attribute selectors
|
|
2262
|
+
buildCSSQuery(config, 'div[existing-attr]');
|
|
2263
|
+
// 'div[existing-attr][data-enhanced], ...'
|
|
2264
|
+
```
|
|
2265
|
+
|
|
2266
|
+
### Underscore-Prefixed Keys Excluded
|
|
2267
|
+
|
|
2268
|
+
Configuration keys starting with `_` are excluded from the query:
|
|
2269
|
+
|
|
2270
|
+
```TypeScript
|
|
2271
|
+
const config = {
|
|
2272
|
+
spawn: MyEnhancement,
|
|
2273
|
+
withAttrs: {
|
|
2274
|
+
base: 'my-attr',
|
|
2275
|
+
_base: {
|
|
2276
|
+
mapsTo: 'something' // Config only, not an attribute
|
|
2277
|
+
},
|
|
2278
|
+
theme: '${base}-theme',
|
|
2279
|
+
_theme: {
|
|
2280
|
+
instanceOf: 'String' // Config only
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
|
|
2285
|
+
buildCSSQuery(config, 'div');
|
|
2286
|
+
// Only includes: my-attr and my-attr-theme
|
|
2287
|
+
// Does NOT include: _base or _theme
|
|
2288
|
+
```
|
|
2289
|
+
|
|
2290
|
+
### Edge Cases
|
|
2291
|
+
|
|
2292
|
+
**Empty inputs return empty string:**
|
|
2293
|
+
```TypeScript
|
|
2294
|
+
buildCSSQuery({ spawn: MyClass }, 'div'); // '' (no withAttrs)
|
|
2295
|
+
buildCSSQuery({ spawn: MyClass, withAttrs: {} }, 'div'); // '' (empty withAttrs)
|
|
2296
|
+
buildCSSQuery({ spawn: MyClass, withAttrs: { base: 'x' } }, ''); // '' (empty selectors)
|
|
2297
|
+
```
|
|
2298
|
+
|
|
2299
|
+
**Deduplication:**
|
|
2300
|
+
```TypeScript
|
|
2301
|
+
buildCSSQuery(config, 'div, div, div');
|
|
2302
|
+
// Duplicates are removed automatically
|
|
2303
|
+
```
|
|
2304
|
+
|
|
2305
|
+
**Whitespace handling:**
|
|
2306
|
+
```TypeScript
|
|
2307
|
+
buildCSSQuery(config, ' div , span , p ');
|
|
2308
|
+
// Whitespace is trimmed automatically
|
|
2309
|
+
```
|
|
2310
|
+
|
|
2311
|
+
### Use Cases
|
|
2312
|
+
|
|
2313
|
+
1. **Mount Observer Integration**: Find elements that need enhancement
|
|
2314
|
+
```TypeScript
|
|
2315
|
+
const query = buildCSSQuery(enhancementConfig, '*');
|
|
2316
|
+
const observer = new MutationObserver(() => {
|
|
2317
|
+
const elements = document.querySelectorAll(query);
|
|
2318
|
+
elements.forEach(el => enhance(el));
|
|
2319
|
+
});
|
|
2320
|
+
```
|
|
2321
|
+
|
|
2322
|
+
2. **Batch Enhancement**: Enhance all matching elements at once
|
|
2323
|
+
```TypeScript
|
|
2324
|
+
const query = buildCSSQuery(config, 'template, script');
|
|
2325
|
+
document.querySelectorAll(query).forEach(el => {
|
|
2326
|
+
const instance = el.enh.get(config);
|
|
2327
|
+
});
|
|
2328
|
+
```
|
|
2329
|
+
|
|
2330
|
+
3. **Conditional Enhancement**: Find elements in specific contexts
|
|
2331
|
+
```TypeScript
|
|
2332
|
+
const query = buildCSSQuery(config, '.container > div');
|
|
2333
|
+
const elements = document.querySelectorAll(query);
|
|
2334
|
+
```
|
|
2335
|
+
|
|
2336
|
+
### API Reference
|
|
2337
|
+
|
|
2338
|
+
```TypeScript
|
|
2339
|
+
function buildCSSQuery(
|
|
2340
|
+
config: EnhancementConfig,
|
|
2341
|
+
selectors: string
|
|
2342
|
+
): string
|
|
2343
|
+
```
|
|
2344
|
+
|
|
2345
|
+
**Parameters:**
|
|
2346
|
+
- `config`: Enhancement configuration with `withAttrs` property
|
|
2347
|
+
- `selectors`: Comma-separated CSS selectors (e.g., `'div, span'`)
|
|
2348
|
+
|
|
2349
|
+
**Returns:**
|
|
2350
|
+
- CSS query string with cross-product of selectors and attributes
|
|
2351
|
+
- Empty string if `withAttrs` is missing or empty
|
|
2352
|
+
|
|
2353
|
+
**Throws:**
|
|
2354
|
+
- Error if template variables have circular references
|
|
2355
|
+
- Error if template variables reference undefined keys
|
|
2356
|
+
|
|
2357
|
+
### Performance Notes
|
|
2358
|
+
|
|
2359
|
+
- The function is synchronous and fast
|
|
2360
|
+
- Resulting queries can be long with many attributes, but CSS engines handle this efficiently
|
|
2361
|
+
- Queries are deduplicated automatically
|
|
2362
|
+
- Consider caching the result if calling repeatedly with the same config
|
|
2363
|
+
|
|
2149
2364
|
<!--
|
|
2150
2365
|
|
|
2151
2366
|
### Complete Example
|
package/buildCSSQuery.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves template variables in a string recursively
|
|
3
|
+
* @param template - Template string with ${var} placeholders
|
|
4
|
+
* @param patterns - The patterns object containing variable values
|
|
5
|
+
* @param resolvedCache - Cache of already resolved values
|
|
6
|
+
* @param visitedKeys - Set of keys being resolved (for cycle detection)
|
|
7
|
+
* @returns Resolved string
|
|
8
|
+
*/
|
|
9
|
+
function resolveTemplate(template, patterns, resolvedCache, visitedKeys = new Set()) {
|
|
10
|
+
return template.replace(/\$\{(\w+)\}/g, (match, varName) => {
|
|
11
|
+
// Check if already resolved
|
|
12
|
+
if (resolvedCache.has(varName)) {
|
|
13
|
+
return resolvedCache.get(varName);
|
|
14
|
+
}
|
|
15
|
+
// Check for circular reference
|
|
16
|
+
if (visitedKeys.has(varName)) {
|
|
17
|
+
throw new Error(`Circular reference detected in template variable: ${varName}`);
|
|
18
|
+
}
|
|
19
|
+
const value = patterns[varName];
|
|
20
|
+
if (value === undefined) {
|
|
21
|
+
throw new Error(`Undefined template variable: ${varName}`);
|
|
22
|
+
}
|
|
23
|
+
if (typeof value === 'string') {
|
|
24
|
+
// Recursively resolve
|
|
25
|
+
visitedKeys.add(varName);
|
|
26
|
+
const resolved = resolveTemplate(value, patterns, resolvedCache, visitedKeys);
|
|
27
|
+
visitedKeys.delete(varName);
|
|
28
|
+
resolvedCache.set(varName, resolved);
|
|
29
|
+
return resolved;
|
|
30
|
+
}
|
|
31
|
+
// Non-string value, return as-is
|
|
32
|
+
return String(value);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Extracts attribute names from withAttrs configuration
|
|
37
|
+
* Resolves template variables and excludes underscore-prefixed config keys
|
|
38
|
+
* @param withAttrs - The attribute patterns configuration
|
|
39
|
+
* @returns Array of resolved attribute names
|
|
40
|
+
*/
|
|
41
|
+
function extractAttributeNames(withAttrs) {
|
|
42
|
+
const names = [];
|
|
43
|
+
const resolvedCache = new Map();
|
|
44
|
+
// Add base if present
|
|
45
|
+
if ('base' in withAttrs && typeof withAttrs.base === 'string') {
|
|
46
|
+
names.push(withAttrs.base);
|
|
47
|
+
}
|
|
48
|
+
// Add other attributes (skip underscore-prefixed config keys)
|
|
49
|
+
for (const key in withAttrs) {
|
|
50
|
+
if (key === 'base' || key.startsWith('_')) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const value = withAttrs[key];
|
|
54
|
+
if (typeof value === 'string') {
|
|
55
|
+
// Resolve template variables
|
|
56
|
+
const resolved = resolveTemplate(value, withAttrs, resolvedCache);
|
|
57
|
+
names.push(resolved);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return names;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Builds a CSS query selector that matches elements with attributes from withAttrs
|
|
64
|
+
* Creates a cross-product of selectors and attribute names (both prefixed and unprefixed)
|
|
65
|
+
*
|
|
66
|
+
* @param config - Enhancement configuration with withAttrs
|
|
67
|
+
* @param selectors - Comma-separated CSS selectors to match (e.g., 'template, script')
|
|
68
|
+
* @returns CSS query string with cross-product of selectors and attributes
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* const config = {
|
|
72
|
+
* spawn: MyClass,
|
|
73
|
+
* withAttrs: {
|
|
74
|
+
* base: 'my-attr',
|
|
75
|
+
* theme: '${base}-theme'
|
|
76
|
+
* }
|
|
77
|
+
* };
|
|
78
|
+
*
|
|
79
|
+
* buildCSSQuery(config, 'div, span');
|
|
80
|
+
* // Returns: 'div[my-attr], span[my-attr], div[enh-my-attr], span[enh-my-attr],
|
|
81
|
+
* // div[my-attr-theme], span[my-attr-theme], div[enh-my-attr-theme], span[enh-my-attr-theme]'
|
|
82
|
+
*/
|
|
83
|
+
export function buildCSSQuery(config, selectors) {
|
|
84
|
+
// Validate inputs
|
|
85
|
+
if (!config.withAttrs || !selectors) {
|
|
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) {
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
96
|
+
// Extract and resolve attribute names
|
|
97
|
+
const attrNames = extractAttributeNames(config.withAttrs);
|
|
98
|
+
if (attrNames.length === 0) {
|
|
99
|
+
return '';
|
|
100
|
+
}
|
|
101
|
+
// Build cross-product of selectors × attributes × prefixes
|
|
102
|
+
const queries = [];
|
|
103
|
+
for (const selector of selectorList) {
|
|
104
|
+
for (const attrName of attrNames) {
|
|
105
|
+
// Unprefixed version
|
|
106
|
+
queries.push(`${selector}[${attrName}]`);
|
|
107
|
+
// enh- prefixed version
|
|
108
|
+
queries.push(`${selector}[enh-${attrName}]`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Deduplicate and join
|
|
112
|
+
const uniqueQueries = [...new Set(queries)];
|
|
113
|
+
return uniqueQueries.join(', ');
|
|
114
|
+
}
|
package/buildCSSQuery.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { EnhancementConfig, AttrPatterns } from './types/assign-gingerly/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves template variables in a string recursively
|
|
5
|
+
* @param template - Template string with ${var} placeholders
|
|
6
|
+
* @param patterns - The patterns object containing variable values
|
|
7
|
+
* @param resolvedCache - Cache of already resolved values
|
|
8
|
+
* @param visitedKeys - Set of keys being resolved (for cycle detection)
|
|
9
|
+
* @returns Resolved string
|
|
10
|
+
*/
|
|
11
|
+
function resolveTemplate(
|
|
12
|
+
template: string,
|
|
13
|
+
patterns: Record<string, any>,
|
|
14
|
+
resolvedCache: Map<string, string>,
|
|
15
|
+
visitedKeys: Set<string> = new Set()
|
|
16
|
+
): string {
|
|
17
|
+
return template.replace(/\$\{(\w+)\}/g, (match, varName) => {
|
|
18
|
+
// Check if already resolved
|
|
19
|
+
if (resolvedCache.has(varName)) {
|
|
20
|
+
return resolvedCache.get(varName)!;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check for circular reference
|
|
24
|
+
if (visitedKeys.has(varName)) {
|
|
25
|
+
throw new Error(`Circular reference detected in template variable: ${varName}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const value = patterns[varName];
|
|
29
|
+
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
throw new Error(`Undefined template variable: ${varName}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof value === 'string') {
|
|
35
|
+
// Recursively resolve
|
|
36
|
+
visitedKeys.add(varName);
|
|
37
|
+
const resolved = resolveTemplate(value, patterns, resolvedCache, visitedKeys);
|
|
38
|
+
visitedKeys.delete(varName);
|
|
39
|
+
resolvedCache.set(varName, resolved);
|
|
40
|
+
return resolved;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Non-string value, return as-is
|
|
44
|
+
return String(value);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extracts attribute names from withAttrs configuration
|
|
50
|
+
* Resolves template variables and excludes underscore-prefixed config keys
|
|
51
|
+
* @param withAttrs - The attribute patterns configuration
|
|
52
|
+
* @returns Array of resolved attribute names
|
|
53
|
+
*/
|
|
54
|
+
function extractAttributeNames(withAttrs: AttrPatterns<any>): string[] {
|
|
55
|
+
const names: string[] = [];
|
|
56
|
+
const resolvedCache = new Map<string, string>();
|
|
57
|
+
|
|
58
|
+
// Add base if present
|
|
59
|
+
if ('base' in withAttrs && typeof withAttrs.base === 'string') {
|
|
60
|
+
names.push(withAttrs.base);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Add other attributes (skip underscore-prefixed config keys)
|
|
64
|
+
for (const key in withAttrs) {
|
|
65
|
+
if (key === 'base' || key.startsWith('_')) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const value = withAttrs[key];
|
|
70
|
+
if (typeof value === 'string') {
|
|
71
|
+
// Resolve template variables
|
|
72
|
+
const resolved = resolveTemplate(value, withAttrs, resolvedCache);
|
|
73
|
+
names.push(resolved);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return names;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Builds a CSS query selector that matches elements with attributes from withAttrs
|
|
82
|
+
* Creates a cross-product of selectors and attribute names (both prefixed and unprefixed)
|
|
83
|
+
*
|
|
84
|
+
* @param config - Enhancement configuration with withAttrs
|
|
85
|
+
* @param selectors - Comma-separated CSS selectors to match (e.g., 'template, script')
|
|
86
|
+
* @returns CSS query string with cross-product of selectors and attributes
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* const config = {
|
|
90
|
+
* spawn: MyClass,
|
|
91
|
+
* withAttrs: {
|
|
92
|
+
* base: 'my-attr',
|
|
93
|
+
* theme: '${base}-theme'
|
|
94
|
+
* }
|
|
95
|
+
* };
|
|
96
|
+
*
|
|
97
|
+
* buildCSSQuery(config, 'div, span');
|
|
98
|
+
* // Returns: 'div[my-attr], span[my-attr], div[enh-my-attr], span[enh-my-attr],
|
|
99
|
+
* // div[my-attr-theme], span[my-attr-theme], div[enh-my-attr-theme], span[enh-my-attr-theme]'
|
|
100
|
+
*/
|
|
101
|
+
export function buildCSSQuery(
|
|
102
|
+
config: EnhancementConfig,
|
|
103
|
+
selectors: string
|
|
104
|
+
): string {
|
|
105
|
+
// Validate inputs
|
|
106
|
+
if (!config.withAttrs || !selectors) {
|
|
107
|
+
return '';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Parse and normalize selectors
|
|
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
|
|
121
|
+
const attrNames = extractAttributeNames(config.withAttrs);
|
|
122
|
+
|
|
123
|
+
if (attrNames.length === 0) {
|
|
124
|
+
return '';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Build cross-product of selectors × attributes × prefixes
|
|
128
|
+
const queries: string[] = [];
|
|
129
|
+
|
|
130
|
+
for (const selector of selectorList) {
|
|
131
|
+
for (const attrName of attrNames) {
|
|
132
|
+
// Unprefixed version
|
|
133
|
+
queries.push(`${selector}[${attrName}]`);
|
|
134
|
+
// enh- prefixed version
|
|
135
|
+
queries.push(`${selector}[enh-${attrName}]`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Deduplicate and join
|
|
140
|
+
const uniqueQueries = [...new Set(queries)];
|
|
141
|
+
return uniqueQueries.join(', ');
|
|
142
|
+
}
|
package/index.js
CHANGED
|
@@ -4,4 +4,5 @@ export { BaseRegistry } from './assignGingerly.js';
|
|
|
4
4
|
export { waitForEvent } from './waitForEvent.js';
|
|
5
5
|
export { ParserRegistry, globalParserRegistry } from './parserRegistry.js';
|
|
6
6
|
export { parseWithAttrs } from './parseWithAttrs.js';
|
|
7
|
+
export { buildCSSQuery } from './buildCSSQuery.js';
|
|
7
8
|
import './object-extension.js';
|
package/index.ts
CHANGED
|
@@ -4,4 +4,5 @@ export {BaseRegistry} from './assignGingerly.js';
|
|
|
4
4
|
export {waitForEvent} from './waitForEvent.js';
|
|
5
5
|
export {ParserRegistry, globalParserRegistry} from './parserRegistry.js';
|
|
6
6
|
export {parseWithAttrs} from './parseWithAttrs.js';
|
|
7
|
+
export {buildCSSQuery} from './buildCSSQuery.js';
|
|
7
8
|
import './object-extension.js';
|
package/object-extension.js
CHANGED
|
@@ -82,21 +82,21 @@ class ElementEnhancementContainer {
|
|
|
82
82
|
return undefined;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
// Parse attributes if withAttrs is defined (regardless of enhKey)
|
|
86
|
+
let attrInitVals = undefined;
|
|
87
|
+
if (registryItem.withAttrs && element) {
|
|
88
|
+
try {
|
|
89
|
+
attrInitVals = parseWithAttrs(element, registryItem.withAttrs, registryItem.allowUnprefixed || false);
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
console.error('Error parsing attributes:', e);
|
|
93
|
+
throw e;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
85
96
|
// Check if there's an enhKey
|
|
86
97
|
if (registryItem.enhKey) {
|
|
87
98
|
const ctx = { config: registryItem, mountCtx };
|
|
88
99
|
const self = this;
|
|
89
|
-
// Parse attributes if withAttrs is defined
|
|
90
|
-
let attrInitVals = undefined;
|
|
91
|
-
if (registryItem.withAttrs && element) {
|
|
92
|
-
try {
|
|
93
|
-
attrInitVals = parseWithAttrs(element, registryItem.withAttrs, registryItem.allowUnprefixed || false);
|
|
94
|
-
}
|
|
95
|
-
catch (e) {
|
|
96
|
-
console.error('Error parsing attributes:', e);
|
|
97
|
-
throw e;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
100
|
// Get existing initVals from enhKey
|
|
101
101
|
const existingInitVals = self[registryItem.enhKey] &&
|
|
102
102
|
!(self[registryItem.enhKey] instanceof SpawnClass)
|
|
@@ -111,9 +111,9 @@ class ElementEnhancementContainer {
|
|
|
111
111
|
self[registryItem.enhKey] = instance;
|
|
112
112
|
}
|
|
113
113
|
else {
|
|
114
|
-
// No enhKey,
|
|
114
|
+
// No enhKey, still pass attrInitVals
|
|
115
115
|
const ctx = { config: registryItem, mountCtx };
|
|
116
|
-
instance = new SpawnClass(element, ctx);
|
|
116
|
+
instance = new SpawnClass(element, ctx, attrInitVals);
|
|
117
117
|
}
|
|
118
118
|
// Store in global instance map
|
|
119
119
|
instances.set(registryItem, instance);
|
package/object-extension.ts
CHANGED
|
@@ -156,26 +156,26 @@ class ElementEnhancementContainer {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
// Parse attributes if withAttrs is defined (regardless of enhKey)
|
|
160
|
+
let attrInitVals: any = undefined;
|
|
161
|
+
if (registryItem.withAttrs && element) {
|
|
162
|
+
try {
|
|
163
|
+
attrInitVals = parseWithAttrs(
|
|
164
|
+
element,
|
|
165
|
+
registryItem.withAttrs,
|
|
166
|
+
registryItem.allowUnprefixed || false
|
|
167
|
+
);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.error('Error parsing attributes:', e);
|
|
170
|
+
throw e;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
159
174
|
// Check if there's an enhKey
|
|
160
175
|
if (registryItem.enhKey) {
|
|
161
176
|
const ctx = { config: registryItem, mountCtx };
|
|
162
177
|
const self = this as any;
|
|
163
178
|
|
|
164
|
-
// Parse attributes if withAttrs is defined
|
|
165
|
-
let attrInitVals: any = undefined;
|
|
166
|
-
if (registryItem.withAttrs && element) {
|
|
167
|
-
try {
|
|
168
|
-
attrInitVals = parseWithAttrs(
|
|
169
|
-
element,
|
|
170
|
-
registryItem.withAttrs,
|
|
171
|
-
registryItem.allowUnprefixed || false
|
|
172
|
-
);
|
|
173
|
-
} catch (e) {
|
|
174
|
-
console.error('Error parsing attributes:', e);
|
|
175
|
-
throw e;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
179
|
// Get existing initVals from enhKey
|
|
180
180
|
const existingInitVals = self[registryItem.enhKey] &&
|
|
181
181
|
!(self[registryItem.enhKey] instanceof SpawnClass)
|
|
@@ -192,9 +192,9 @@ class ElementEnhancementContainer {
|
|
|
192
192
|
// Store on enh container
|
|
193
193
|
self[registryItem.enhKey] = instance;
|
|
194
194
|
} else {
|
|
195
|
-
// No enhKey,
|
|
195
|
+
// No enhKey, still pass attrInitVals
|
|
196
196
|
const ctx = { config: registryItem, mountCtx };
|
|
197
|
-
instance = new SpawnClass(element, ctx);
|
|
197
|
+
instance = new SpawnClass(element, ctx, attrInitVals);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
// Store in global instance map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
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": {
|
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
"./parseWithAttrs.js": {
|
|
44
44
|
"default": "./parseWithAttrs.js",
|
|
45
45
|
"types": "./parseWithAttrs.ts"
|
|
46
|
+
},
|
|
47
|
+
"./buildCSSQuery.js": {
|
|
48
|
+
"default": "./buildCSSQuery.js",
|
|
49
|
+
"types": "./buildCSSQuery.ts"
|
|
46
50
|
}
|
|
47
51
|
},
|
|
48
52
|
"main": "index.js",
|
|
@@ -54,8 +58,8 @@
|
|
|
54
58
|
},
|
|
55
59
|
"devDependencies": {
|
|
56
60
|
"@playwright/test": "^1.58.2",
|
|
57
|
-
"spa-ssi": "0.0.
|
|
58
|
-
"@types/node": "^25.
|
|
61
|
+
"spa-ssi": "0.0.27",
|
|
62
|
+
"@types/node": "^25.3.0",
|
|
59
63
|
"typescript": "^5.9.3"
|
|
60
64
|
}
|
|
61
65
|
}
|