assign-gingerly 0.0.29 → 0.0.30
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/Infer.js +154 -0
- package/Infer.ts +190 -0
- package/README.md +309 -0
- package/object-extension.js +68 -0
- package/object-extension.ts +78 -0
- package/package.json +79 -75
- package/parseWithAttrs.js +3 -3
- package/parseWithAttrs.ts +3 -3
package/Infer.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbol for smart value assignment
|
|
3
|
+
* When used with element.set[value], it infers and sets the appropriate value property
|
|
4
|
+
*/
|
|
5
|
+
export const value = Symbol.for('assign-gingerly:value');
|
|
6
|
+
/**
|
|
7
|
+
* Symbol for smart display assignment
|
|
8
|
+
* When used with element.set[display], it infers and sets the appropriate display property
|
|
9
|
+
*/
|
|
10
|
+
export const display = Symbol.for('assign-gingerly:display');
|
|
11
|
+
/**
|
|
12
|
+
* Enhancement class that provides smart value and display property inference
|
|
13
|
+
* Automatically determines the correct property to set based on element type
|
|
14
|
+
*/
|
|
15
|
+
export class Infer {
|
|
16
|
+
#weakRef;
|
|
17
|
+
get enhancedElement() {
|
|
18
|
+
return this.#weakRef.deref();
|
|
19
|
+
}
|
|
20
|
+
constructor(enhancedElement) {
|
|
21
|
+
this.#weakRef = new WeakRef(enhancedElement);
|
|
22
|
+
}
|
|
23
|
+
#value;
|
|
24
|
+
get value() {
|
|
25
|
+
return this.#value;
|
|
26
|
+
}
|
|
27
|
+
set value(nv) {
|
|
28
|
+
this.#value = nv;
|
|
29
|
+
const { enhancedElement } = this;
|
|
30
|
+
enhancedElement[inferValueProperty(enhancedElement)] = nv;
|
|
31
|
+
}
|
|
32
|
+
#display;
|
|
33
|
+
get display() {
|
|
34
|
+
return this.#display;
|
|
35
|
+
}
|
|
36
|
+
set display(nv) {
|
|
37
|
+
this.#display = nv;
|
|
38
|
+
const { enhancedElement } = this;
|
|
39
|
+
enhancedElement[inferDisplayProperty(enhancedElement)] = nv;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get the inferred event type for the element
|
|
43
|
+
* @returns The most appropriate event type for this element
|
|
44
|
+
*/
|
|
45
|
+
get eventType() {
|
|
46
|
+
return inferEventType(this.enhancedElement);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Registry item for the Infer enhancement
|
|
51
|
+
* Register this with customElements.enhancementRegistry to enable smart value/display assignment
|
|
52
|
+
*/
|
|
53
|
+
export const registryItem = {
|
|
54
|
+
spawn: Infer,
|
|
55
|
+
enhKey: 'infer',
|
|
56
|
+
symlinks: {
|
|
57
|
+
[value]: 'value',
|
|
58
|
+
[display]: 'display'
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Infer the most appropriate value property for an element
|
|
63
|
+
* @param element - The element to infer the property for
|
|
64
|
+
* @returns The property name to use for value assignment
|
|
65
|
+
*/
|
|
66
|
+
export function inferValueProperty(element) {
|
|
67
|
+
const tagName = element.localName;
|
|
68
|
+
// Input elements - check type attribute
|
|
69
|
+
if (tagName === 'input') {
|
|
70
|
+
const type = element.getAttribute('type')?.toLowerCase();
|
|
71
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
72
|
+
return 'checked';
|
|
73
|
+
}
|
|
74
|
+
return 'value';
|
|
75
|
+
}
|
|
76
|
+
// Form controls with value property
|
|
77
|
+
if (tagName === 'textarea' || tagName === 'select') {
|
|
78
|
+
return 'value';
|
|
79
|
+
}
|
|
80
|
+
// Semantic HTML elements with specific properties
|
|
81
|
+
if (tagName === 'time') {
|
|
82
|
+
return 'dateTime';
|
|
83
|
+
}
|
|
84
|
+
if (tagName === 'data') {
|
|
85
|
+
return 'value';
|
|
86
|
+
}
|
|
87
|
+
if (tagName === 'meter' || tagName === 'progress') {
|
|
88
|
+
return 'value';
|
|
89
|
+
}
|
|
90
|
+
if (tagName === 'output') {
|
|
91
|
+
return 'value';
|
|
92
|
+
}
|
|
93
|
+
// Check for itemprop attribute as a hint
|
|
94
|
+
const itemprop = element.getAttribute('itemprop');
|
|
95
|
+
if (itemprop) {
|
|
96
|
+
return itemprop;
|
|
97
|
+
}
|
|
98
|
+
// Default fallback
|
|
99
|
+
return 'textContent';
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Infer the most appropriate display property for an element
|
|
103
|
+
* @param element - The element to infer the property for
|
|
104
|
+
* @returns The property name to use for display assignment
|
|
105
|
+
*/
|
|
106
|
+
export function inferDisplayProperty(element) {
|
|
107
|
+
const tagName = element.localName;
|
|
108
|
+
// Form controls display their value
|
|
109
|
+
if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
|
|
110
|
+
return 'value';
|
|
111
|
+
}
|
|
112
|
+
// Time elements display formatted time
|
|
113
|
+
if (tagName === 'time') {
|
|
114
|
+
return 'textContent';
|
|
115
|
+
}
|
|
116
|
+
// Data elements display human-readable content
|
|
117
|
+
if (tagName === 'data') {
|
|
118
|
+
return 'textContent';
|
|
119
|
+
}
|
|
120
|
+
// Progress/meter elements use ARIA for display
|
|
121
|
+
if (tagName === 'meter' || tagName === 'progress') {
|
|
122
|
+
return 'ariaValueText';
|
|
123
|
+
}
|
|
124
|
+
// Default fallback
|
|
125
|
+
return 'textContent';
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Infer the most appropriate event type for an element
|
|
129
|
+
* Used when no explicit event type is provided
|
|
130
|
+
* @param element - The element to infer the event type for
|
|
131
|
+
* @returns The event type name like 'input', 'change', 'click', 'submit'
|
|
132
|
+
*/
|
|
133
|
+
export function inferEventType(element) {
|
|
134
|
+
const tagName = element.localName;
|
|
135
|
+
// Form controls that support input event
|
|
136
|
+
if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
|
|
137
|
+
return 'input';
|
|
138
|
+
}
|
|
139
|
+
// Form submission
|
|
140
|
+
if (tagName === 'form') {
|
|
141
|
+
return 'submit';
|
|
142
|
+
}
|
|
143
|
+
// Details element
|
|
144
|
+
if (tagName === 'details') {
|
|
145
|
+
return 'toggle';
|
|
146
|
+
}
|
|
147
|
+
// Dialog element
|
|
148
|
+
if (tagName === 'dialog') {
|
|
149
|
+
return 'close';
|
|
150
|
+
}
|
|
151
|
+
// Default fallback for interactive elements
|
|
152
|
+
return 'click';
|
|
153
|
+
}
|
|
154
|
+
export default registryItem;
|
package/Infer.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { EnhancementConfig } from "./types/assign-gingerly/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Symbol for smart value assignment
|
|
5
|
+
* When used with element.set[value], it infers and sets the appropriate value property
|
|
6
|
+
*/
|
|
7
|
+
export const value = Symbol.for('assign-gingerly:value');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Symbol for smart display assignment
|
|
11
|
+
* When used with element.set[display], it infers and sets the appropriate display property
|
|
12
|
+
*/
|
|
13
|
+
export const display = Symbol.for('assign-gingerly:display');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Enhancement class that provides smart value and display property inference
|
|
17
|
+
* Automatically determines the correct property to set based on element type
|
|
18
|
+
*/
|
|
19
|
+
export class Infer<TValue = any, TDisplay = any> {
|
|
20
|
+
#weakRef: WeakRef<Element>;
|
|
21
|
+
|
|
22
|
+
get enhancedElement(){
|
|
23
|
+
return this.#weakRef.deref()!;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
constructor(enhancedElement?: Element){
|
|
27
|
+
this.#weakRef = new WeakRef(enhancedElement!);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#value: TValue | undefined;
|
|
31
|
+
|
|
32
|
+
get value(): TValue | undefined {
|
|
33
|
+
return this.#value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
set value(nv: TValue){
|
|
37
|
+
this.#value = nv;
|
|
38
|
+
const {enhancedElement} = this;
|
|
39
|
+
(enhancedElement as any)[inferValueProperty(enhancedElement)] = nv;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#display: TDisplay | undefined;
|
|
43
|
+
|
|
44
|
+
get display(): TDisplay | undefined {
|
|
45
|
+
return this.#display;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
set display(nv: TDisplay){
|
|
49
|
+
this.#display = nv;
|
|
50
|
+
const {enhancedElement} = this;
|
|
51
|
+
(enhancedElement as any)[inferDisplayProperty(enhancedElement)] = nv;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get the inferred event type for the element
|
|
56
|
+
* @returns The most appropriate event type for this element
|
|
57
|
+
*/
|
|
58
|
+
get eventType(): string {
|
|
59
|
+
return inferEventType(this.enhancedElement);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Registry item for the Infer enhancement
|
|
65
|
+
* Register this with customElements.enhancementRegistry to enable smart value/display assignment
|
|
66
|
+
*/
|
|
67
|
+
export const registryItem: EnhancementConfig = {
|
|
68
|
+
spawn: Infer,
|
|
69
|
+
enhKey: 'infer',
|
|
70
|
+
symlinks: {
|
|
71
|
+
[value]: 'value',
|
|
72
|
+
[display]: 'display'
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Infer the most appropriate value property for an element
|
|
78
|
+
* @param element - The element to infer the property for
|
|
79
|
+
* @returns The property name to use for value assignment
|
|
80
|
+
*/
|
|
81
|
+
export function inferValueProperty(element: Element): string {
|
|
82
|
+
const tagName = element.localName;
|
|
83
|
+
|
|
84
|
+
// Input elements - check type attribute
|
|
85
|
+
if (tagName === 'input') {
|
|
86
|
+
const type = element.getAttribute('type')?.toLowerCase();
|
|
87
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
88
|
+
return 'checked';
|
|
89
|
+
}
|
|
90
|
+
return 'value';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Form controls with value property
|
|
94
|
+
if (tagName === 'textarea' || tagName === 'select') {
|
|
95
|
+
return 'value';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Semantic HTML elements with specific properties
|
|
99
|
+
if (tagName === 'time') {
|
|
100
|
+
return 'dateTime';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (tagName === 'data') {
|
|
104
|
+
return 'value';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (tagName === 'meter' || tagName === 'progress') {
|
|
108
|
+
return 'value';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (tagName === 'output') {
|
|
112
|
+
return 'value';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check for itemprop attribute as a hint
|
|
116
|
+
const itemprop = element.getAttribute('itemprop');
|
|
117
|
+
if (itemprop) {
|
|
118
|
+
return itemprop;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Default fallback
|
|
122
|
+
return 'textContent';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Infer the most appropriate display property for an element
|
|
127
|
+
* @param element - The element to infer the property for
|
|
128
|
+
* @returns The property name to use for display assignment
|
|
129
|
+
*/
|
|
130
|
+
export function inferDisplayProperty(element: Element): string {
|
|
131
|
+
const tagName = element.localName;
|
|
132
|
+
|
|
133
|
+
// Form controls display their value
|
|
134
|
+
if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
|
|
135
|
+
return 'value';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Time elements display formatted time
|
|
139
|
+
if (tagName === 'time') {
|
|
140
|
+
return 'textContent';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Data elements display human-readable content
|
|
144
|
+
if (tagName === 'data') {
|
|
145
|
+
return 'textContent';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Progress/meter elements use ARIA for display
|
|
149
|
+
if (tagName === 'meter' || tagName === 'progress') {
|
|
150
|
+
return 'ariaValueText';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Default fallback
|
|
154
|
+
return 'textContent';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Infer the most appropriate event type for an element
|
|
159
|
+
* Used when no explicit event type is provided
|
|
160
|
+
* @param element - The element to infer the event type for
|
|
161
|
+
* @returns The event type name like 'input', 'change', 'click', 'submit'
|
|
162
|
+
*/
|
|
163
|
+
export function inferEventType(element: Element): string {
|
|
164
|
+
const tagName = element.localName;
|
|
165
|
+
|
|
166
|
+
// Form controls that support input event
|
|
167
|
+
if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
|
|
168
|
+
return 'input';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Form submission
|
|
172
|
+
if (tagName === 'form') {
|
|
173
|
+
return 'submit';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Details element
|
|
177
|
+
if (tagName === 'details') {
|
|
178
|
+
return 'toggle';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Dialog element
|
|
182
|
+
if (tagName === 'dialog') {
|
|
183
|
+
return 'close';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Default fallback for interactive elements
|
|
187
|
+
return 'click';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default registryItem;
|
package/README.md
CHANGED
|
@@ -3411,3 +3411,312 @@ This design ensures backward compatibility while providing powerful new capabili
|
|
|
3411
3411
|
|
|
3412
3412
|
|
|
3413
3413
|
|
|
3414
|
+
## Smart Value Assignment with Infer Enhancement
|
|
3415
|
+
|
|
3416
|
+
The Infer enhancement provides a symbol-based API for smart value and display property assignment. Instead of manually determining which property to set on different element types (e.g., `value` for inputs, `checked` for checkboxes, `textContent` for divs), the Infer enhancement automatically infers the correct property based on the element type.
|
|
3417
|
+
|
|
3418
|
+
### Why Infer?
|
|
3419
|
+
|
|
3420
|
+
Different HTML elements use different properties to represent their value:
|
|
3421
|
+
- Input text fields use `value`
|
|
3422
|
+
- Checkboxes and radio buttons use `checked`
|
|
3423
|
+
- Time elements use `dateTime`
|
|
3424
|
+
- Divs and spans use `textContent`
|
|
3425
|
+
- Progress and meter elements use `value` but display with `ariaValueText`
|
|
3426
|
+
|
|
3427
|
+
The Infer enhancement eliminates the need to remember these differences by providing two symbols that automatically map to the correct property:
|
|
3428
|
+
|
|
3429
|
+
- `value` symbol - Sets the element's data value
|
|
3430
|
+
- `display` symbol - Sets the element's display/presentation value
|
|
3431
|
+
|
|
3432
|
+
### Basic Usage
|
|
3433
|
+
|
|
3434
|
+
```TypeScript
|
|
3435
|
+
import { value, display, registryItem } from 'assign-gingerly/Infer.js';
|
|
3436
|
+
import 'assign-gingerly/object-extension.js';
|
|
3437
|
+
|
|
3438
|
+
// Register the Infer enhancement
|
|
3439
|
+
customElements.enhancementRegistry.push(registryItem);
|
|
3440
|
+
|
|
3441
|
+
// Use the value symbol - automatically sets the right property
|
|
3442
|
+
const input = document.createElement('input');
|
|
3443
|
+
input.type = 'text';
|
|
3444
|
+
input.set[value] = 'hello';
|
|
3445
|
+
console.log(input.value); // 'hello'
|
|
3446
|
+
|
|
3447
|
+
const checkbox = document.createElement('input');
|
|
3448
|
+
checkbox.type = 'checkbox';
|
|
3449
|
+
checkbox.set[value] = true;
|
|
3450
|
+
console.log(checkbox.checked); // true
|
|
3451
|
+
|
|
3452
|
+
const div = document.createElement('div');
|
|
3453
|
+
div.set[value] = 'content';
|
|
3454
|
+
console.log(div.textContent); // 'content'
|
|
3455
|
+
|
|
3456
|
+
const time = document.createElement('time');
|
|
3457
|
+
time.set[value] = '2024-01-01T00:00:00Z';
|
|
3458
|
+
console.log(time.dateTime); // '2024-01-01T00:00:00Z'
|
|
3459
|
+
```
|
|
3460
|
+
|
|
3461
|
+
### Value Property Inference
|
|
3462
|
+
|
|
3463
|
+
The `value` symbol automatically maps to the most appropriate property for each element type:
|
|
3464
|
+
|
|
3465
|
+
| Element Type | Property Set | Example |
|
|
3466
|
+
|-------------|-------------|---------|
|
|
3467
|
+
| `<input type="text">` | `value` | Text input value |
|
|
3468
|
+
| `<input type="checkbox">` | `checked` | Checkbox state |
|
|
3469
|
+
| `<input type="radio">` | `checked` | Radio button state |
|
|
3470
|
+
| `<textarea>` | `value` | Textarea content |
|
|
3471
|
+
| `<select>` | `value` | Selected option |
|
|
3472
|
+
| `<time>` | `dateTime` | ISO datetime string |
|
|
3473
|
+
| `<data>` | `value` | Machine-readable value |
|
|
3474
|
+
| `<meter>` | `value` | Numeric value |
|
|
3475
|
+
| `<progress>` | `value` | Progress value |
|
|
3476
|
+
| `<output>` | `value` | Output value |
|
|
3477
|
+
| Elements with `itemprop` | `itemprop` value | Custom property name |
|
|
3478
|
+
| Other elements | `textContent` | Text content |
|
|
3479
|
+
|
|
3480
|
+
### Display Property Inference
|
|
3481
|
+
|
|
3482
|
+
The `display` symbol sets the human-readable display value:
|
|
3483
|
+
|
|
3484
|
+
```TypeScript
|
|
3485
|
+
// Time element - display formatted time
|
|
3486
|
+
const time = document.createElement('time');
|
|
3487
|
+
time.set[value] = '2024-01-01T00:00:00Z'; // Machine-readable
|
|
3488
|
+
time.set[display] = 'January 1, 2024'; // Human-readable
|
|
3489
|
+
console.log(time.dateTime); // '2024-01-01T00:00:00Z'
|
|
3490
|
+
console.log(time.textContent); // 'January 1, 2024'
|
|
3491
|
+
|
|
3492
|
+
// Meter element - display with ARIA
|
|
3493
|
+
const meter = document.createElement('meter');
|
|
3494
|
+
meter.min = 0;
|
|
3495
|
+
meter.max = 100;
|
|
3496
|
+
meter.set[value] = 75; // Numeric value
|
|
3497
|
+
meter.set[display] = '75 percent'; // Screen reader text
|
|
3498
|
+
console.log(meter.value); // 75
|
|
3499
|
+
console.log(meter.ariaValueText); // '75 percent'
|
|
3500
|
+
```
|
|
3501
|
+
|
|
3502
|
+
| Element Type | Property Set | Example |
|
|
3503
|
+
|-------------|-------------|---------|
|
|
3504
|
+
| `<input>`, `<textarea>`, `<select>` | `value` | Form control value |
|
|
3505
|
+
| `<time>` | `textContent` | Formatted time string |
|
|
3506
|
+
| `<data>` | `textContent` | Human-readable content |
|
|
3507
|
+
| `<meter>`, `<progress>` | `ariaValueText` | Screen reader text |
|
|
3508
|
+
| Other elements | `textContent` | Text content |
|
|
3509
|
+
|
|
3510
|
+
### Accessing the Enhancement Instance
|
|
3511
|
+
|
|
3512
|
+
The Infer enhancement is accessible via `element.enh.infer`:
|
|
3513
|
+
|
|
3514
|
+
```TypeScript
|
|
3515
|
+
const input = document.createElement('input');
|
|
3516
|
+
input.set[value] = 'test';
|
|
3517
|
+
|
|
3518
|
+
// Access the enhancement instance
|
|
3519
|
+
console.log(input.enh.infer.value); // 'test' (cached value)
|
|
3520
|
+
|
|
3521
|
+
// The instance maintains references to both value and display
|
|
3522
|
+
input.set[display] = 'Test Display';
|
|
3523
|
+
console.log(input.enh.infer.value); // 'test'
|
|
3524
|
+
console.log(input.enh.infer.display); // 'Test Display'
|
|
3525
|
+
```
|
|
3526
|
+
|
|
3527
|
+
### Using with assignGingerly
|
|
3528
|
+
|
|
3529
|
+
The Infer enhancement integrates seamlessly with `assignGingerly`:
|
|
3530
|
+
|
|
3531
|
+
```TypeScript
|
|
3532
|
+
import { value, display } from 'assign-gingerly/Infer.js';
|
|
3533
|
+
|
|
3534
|
+
const element = document.createElement('input');
|
|
3535
|
+
element.type = 'text';
|
|
3536
|
+
|
|
3537
|
+
// Use symbols in assignGingerly
|
|
3538
|
+
element.assignGingerly({
|
|
3539
|
+
[value]: 'hello world',
|
|
3540
|
+
style: {
|
|
3541
|
+
color: 'blue'
|
|
3542
|
+
}
|
|
3543
|
+
});
|
|
3544
|
+
|
|
3545
|
+
console.log(element.value); // 'hello world'
|
|
3546
|
+
console.log(element.style.color); // 'blue'
|
|
3547
|
+
```
|
|
3548
|
+
|
|
3549
|
+
### Itemprop Support
|
|
3550
|
+
|
|
3551
|
+
Elements with an `itemprop` attribute use that attribute's value as the property name:
|
|
3552
|
+
|
|
3553
|
+
```html
|
|
3554
|
+
<span itemprop="title"></span>
|
|
3555
|
+
```
|
|
3556
|
+
|
|
3557
|
+
```TypeScript
|
|
3558
|
+
const span = document.querySelector('[itemprop="title"]');
|
|
3559
|
+
span.set[value] = 'My Title';
|
|
3560
|
+
console.log(span.title); // 'My Title'
|
|
3561
|
+
```
|
|
3562
|
+
|
|
3563
|
+
### Implementation Details
|
|
3564
|
+
|
|
3565
|
+
The Infer enhancement is implemented as a standard enhancement class:
|
|
3566
|
+
|
|
3567
|
+
```TypeScript
|
|
3568
|
+
class Infer<TValue = any, TDisplay = any> {
|
|
3569
|
+
#weakRef: WeakRef<Element>;
|
|
3570
|
+
|
|
3571
|
+
constructor(enhancedElement?: Element) {
|
|
3572
|
+
this.#weakRef = new WeakRef(enhancedElement!);
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
get value(): TValue | undefined { /* ... */ }
|
|
3576
|
+
set value(nv: TValue) {
|
|
3577
|
+
const element = this.#weakRef.deref()!;
|
|
3578
|
+
element[inferValueProperty(element)] = nv;
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
get display(): TDisplay | undefined { /* ... */ }
|
|
3582
|
+
set display(nv: TDisplay) {
|
|
3583
|
+
const element = this.#weakRef.deref()!;
|
|
3584
|
+
element[inferDisplayProperty(element)] = nv;
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
```
|
|
3588
|
+
|
|
3589
|
+
**Registry Configuration:**
|
|
3590
|
+
|
|
3591
|
+
```TypeScript
|
|
3592
|
+
export const registryItem: EnhancementConfig = {
|
|
3593
|
+
spawn: Infer,
|
|
3594
|
+
enhKey: 'infer',
|
|
3595
|
+
symlinks: {
|
|
3596
|
+
[value]: 'value',
|
|
3597
|
+
[display]: 'display'
|
|
3598
|
+
}
|
|
3599
|
+
};
|
|
3600
|
+
```
|
|
3601
|
+
|
|
3602
|
+
The `symlinks` mapping connects the symbols to the enhancement's properties, enabling the `element.set[symbol]` syntax.
|
|
3603
|
+
|
|
3604
|
+
### Helper Functions
|
|
3605
|
+
|
|
3606
|
+
The Infer module exports helper functions for manual property and event type inference:
|
|
3607
|
+
|
|
3608
|
+
```TypeScript
|
|
3609
|
+
import { inferValueProperty, inferDisplayProperty, inferEventType } from 'assign-gingerly/Infer.js';
|
|
3610
|
+
|
|
3611
|
+
const input = document.createElement('input');
|
|
3612
|
+
input.type = 'checkbox';
|
|
3613
|
+
|
|
3614
|
+
const valueProp = inferValueProperty(input);
|
|
3615
|
+
console.log(valueProp); // 'checked'
|
|
3616
|
+
|
|
3617
|
+
const displayProp = inferDisplayProperty(input);
|
|
3618
|
+
console.log(displayProp); // 'value'
|
|
3619
|
+
|
|
3620
|
+
const eventType = inferEventType(input);
|
|
3621
|
+
console.log(eventType); // 'input'
|
|
3622
|
+
```
|
|
3623
|
+
|
|
3624
|
+
These functions can be useful when you need to determine the property or event type name without actually setting a value or attaching a listener.
|
|
3625
|
+
|
|
3626
|
+
**Event Type Inference:**
|
|
3627
|
+
|
|
3628
|
+
The `inferEventType` function returns the most appropriate event type for different element types:
|
|
3629
|
+
|
|
3630
|
+
| Element Type | Event Type | Use Case |
|
|
3631
|
+
|-------------|-----------|----------|
|
|
3632
|
+
| `<input>`, `<textarea>`, `<select>` | `input` | Form control value changes |
|
|
3633
|
+
| `<form>` | `submit` | Form submission |
|
|
3634
|
+
| `<details>` | `toggle` | Details element open/close |
|
|
3635
|
+
| `<dialog>` | `close` | Dialog dismissal |
|
|
3636
|
+
| Other elements | `click` | Default interactive event |
|
|
3637
|
+
|
|
3638
|
+
**Accessing via Enhancement Instance:**
|
|
3639
|
+
|
|
3640
|
+
The inferred event type is also available as a getter on the enhancement instance:
|
|
3641
|
+
|
|
3642
|
+
```TypeScript
|
|
3643
|
+
const input = document.createElement('input');
|
|
3644
|
+
input.set[value] = 'test';
|
|
3645
|
+
|
|
3646
|
+
console.log(input.enh.infer.eventType); // 'input'
|
|
3647
|
+
|
|
3648
|
+
const form = document.createElement('form');
|
|
3649
|
+
form.set[value] = 'test';
|
|
3650
|
+
|
|
3651
|
+
console.log(form.enh.infer.eventType); // 'submit'
|
|
3652
|
+
```
|
|
3653
|
+
|
|
3654
|
+
This is particularly useful when building enhancements that need to attach event listeners but don't know the element type in advance.
|
|
3655
|
+
|
|
3656
|
+
### Benefits
|
|
3657
|
+
|
|
3658
|
+
1. **Type-agnostic code**: Write code that works with any element type without conditionals
|
|
3659
|
+
2. **Cleaner syntax**: No need to remember which property each element type uses
|
|
3660
|
+
3. **Accessibility**: Separate value and display properties support screen readers
|
|
3661
|
+
4. **Framework-friendly**: Symbols work well with reactive frameworks and data binding
|
|
3662
|
+
5. **Extensible**: Based on the enhancement registry system, can be customized or extended
|
|
3663
|
+
|
|
3664
|
+
### Complete Example
|
|
3665
|
+
|
|
3666
|
+
```html
|
|
3667
|
+
<!DOCTYPE html>
|
|
3668
|
+
<html>
|
|
3669
|
+
<head>
|
|
3670
|
+
<script type="module">
|
|
3671
|
+
import { value, display, registryItem } from './Infer.js';
|
|
3672
|
+
import './object-extension.js';
|
|
3673
|
+
|
|
3674
|
+
// Register the enhancement
|
|
3675
|
+
customElements.enhancementRegistry.push(registryItem);
|
|
3676
|
+
|
|
3677
|
+
// Create various elements
|
|
3678
|
+
const input = document.createElement('input');
|
|
3679
|
+
input.type = 'text';
|
|
3680
|
+
|
|
3681
|
+
const checkbox = document.createElement('input');
|
|
3682
|
+
checkbox.type = 'checkbox';
|
|
3683
|
+
|
|
3684
|
+
const time = document.createElement('time');
|
|
3685
|
+
|
|
3686
|
+
const meter = document.createElement('meter');
|
|
3687
|
+
meter.min = 0;
|
|
3688
|
+
meter.max = 100;
|
|
3689
|
+
|
|
3690
|
+
// Set values using the same symbol - each element handles it correctly
|
|
3691
|
+
input.set[value] = 'Hello World';
|
|
3692
|
+
checkbox.set[value] = true;
|
|
3693
|
+
time.set[value] = '2024-01-01T00:00:00Z';
|
|
3694
|
+
time.set[display] = 'January 1, 2024';
|
|
3695
|
+
meter.set[value] = 75;
|
|
3696
|
+
meter.set[display] = '75 percent';
|
|
3697
|
+
|
|
3698
|
+
// Add to document
|
|
3699
|
+
document.body.append(input, checkbox, time, meter);
|
|
3700
|
+
|
|
3701
|
+
console.log('Input value:', input.value); // 'Hello World'
|
|
3702
|
+
console.log('Checkbox checked:', checkbox.checked); // true
|
|
3703
|
+
console.log('Time dateTime:', time.dateTime); // '2024-01-01T00:00:00Z'
|
|
3704
|
+
console.log('Time display:', time.textContent); // 'January 1, 2024'
|
|
3705
|
+
console.log('Meter value:', meter.value); // 75
|
|
3706
|
+
console.log('Meter display:', meter.ariaValueText); // '75 percent'
|
|
3707
|
+
</script>
|
|
3708
|
+
</head>
|
|
3709
|
+
<body>
|
|
3710
|
+
<h1>Infer Enhancement Demo</h1>
|
|
3711
|
+
</body>
|
|
3712
|
+
</html>
|
|
3713
|
+
```
|
|
3714
|
+
|
|
3715
|
+
### Browser Support
|
|
3716
|
+
|
|
3717
|
+
The Infer enhancement requires:
|
|
3718
|
+
- Chrome 146+ (for scoped custom element registries)
|
|
3719
|
+
- Modern browsers with Symbol support
|
|
3720
|
+
- WeakRef support (all modern browsers)
|
|
3721
|
+
|
|
3722
|
+
For browsers without scoped registry support, the enhancement falls back to the global `customElements.enhancementRegistry`.
|
package/object-extension.js
CHANGED
|
@@ -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
|
package/object-extension.ts
CHANGED
|
@@ -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,79 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "assign-gingerly",
|
|
3
|
+
"version": "0.0.30",
|
|
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
|
+
"./Infer.js": {
|
|
60
|
+
"default": "./Infer.js",
|
|
61
|
+
"types": "./Infer.ts"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"main": "index.js",
|
|
65
|
+
"module": "index.js",
|
|
66
|
+
"scripts": {
|
|
67
|
+
"serve": "node ./node_modules/spa-ssi/serve.js",
|
|
68
|
+
"test": "playwright test",
|
|
69
|
+
"update": "ncu -u && npm install",
|
|
70
|
+
"safari": "npx playwright wk http://localhost:8000",
|
|
71
|
+
"chrome": "npx playwright cr http://localhost:8000"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@playwright/test": "1.59.1",
|
|
75
|
+
"spa-ssi": "0.0.27",
|
|
76
|
+
"@types/node": "25.5.2",
|
|
77
|
+
"typescript": "6.0.2"
|
|
78
|
+
}
|
|
79
|
+
}
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
201
|
-
if (pattern.test(tagName)) {
|
|
201
|
+
if (pattern.test(localName)) {
|
|
202
202
|
return element.getAttribute(attrName);
|
|
203
203
|
}
|
|
204
204
|
}
|