assign-gingerly 0.0.30 → 0.0.32
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 +415 -346
- package/assignFrom.js +27 -0
- package/assignFrom.ts +37 -0
- package/assignGingerly.js +221 -5
- package/assignGingerly.ts +287 -5
- package/eachTime.js +110 -0
- package/eachTime.ts +137 -0
- package/index.js +2 -0
- package/index.ts +2 -0
- package/object-extension.js +65 -12
- package/object-extension.ts +74 -15
- package/package.json +10 -6
- package/playwright.config.ts +1 -1
- package/resolveValues.js +44 -0
- package/resolveValues.ts +45 -0
- package/types/assign-gingerly/types.d.ts +12 -2
- package/Infer.js +0 -154
- package/Infer.ts +0 -190
package/resolveValues.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve RHS path strings in a pattern object against a source object.
|
|
3
|
+
*
|
|
4
|
+
* Any value that is a string starting with `?.` is treated as a path
|
|
5
|
+
* and resolved against the source object using optional chaining semantics.
|
|
6
|
+
* Non-string values and strings not starting with `?.` pass through unchanged.
|
|
7
|
+
*
|
|
8
|
+
* @param pattern - Object whose RHS values may contain `?.` path strings
|
|
9
|
+
* @param source - Object to resolve paths against
|
|
10
|
+
* @returns New object with path strings replaced by resolved values
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const pattern = {
|
|
14
|
+
* hello: '?.myPropContainer?.stringProp',
|
|
15
|
+
* foo: '?.myFooString',
|
|
16
|
+
* literal: 42
|
|
17
|
+
* };
|
|
18
|
+
* const source = {
|
|
19
|
+
* myPropContainer: { stringProp: 'Venus' },
|
|
20
|
+
* myFooString: 'bar'
|
|
21
|
+
* };
|
|
22
|
+
* const result = resolveValues(pattern, source);
|
|
23
|
+
* // { hello: 'Venus', foo: 'bar', literal: 42 }
|
|
24
|
+
*/
|
|
25
|
+
export function resolveValues(
|
|
26
|
+
pattern: Record<string, any>,
|
|
27
|
+
source: any
|
|
28
|
+
): Record<string, any> {
|
|
29
|
+
const result: Record<string, any> = {};
|
|
30
|
+
for (const [key, value] of Object.entries(pattern)) {
|
|
31
|
+
if (typeof value === 'string' && value.startsWith('?.')) {
|
|
32
|
+
// Parse path: split by '.', strip '?', filter empties
|
|
33
|
+
const parts = value.split('.').map(p => p.replace(/\?/g, '')).filter(p => p.length > 0);
|
|
34
|
+
let current = source;
|
|
35
|
+
for (const part of parts) {
|
|
36
|
+
if (current == null) break;
|
|
37
|
+
current = current[part];
|
|
38
|
+
}
|
|
39
|
+
result[key] = current;
|
|
40
|
+
} else {
|
|
41
|
+
result[key] = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
@@ -226,6 +226,14 @@ export interface IAssignGingerlyOptions {
|
|
|
226
226
|
registry?: typeof EnhancementRegistry | EnhancementRegistry;
|
|
227
227
|
bypassChecks?: boolean;
|
|
228
228
|
withMethods?: string[] | Set<string>;
|
|
229
|
+
aka?: Record<string, string>;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* AbortSignal for cleaning up reactive subscriptions (@eachTime)
|
|
233
|
+
* Required when using @eachTime symbol for reactive iteration
|
|
234
|
+
* When the signal is aborted, all event listeners are automatically removed
|
|
235
|
+
*/
|
|
236
|
+
signal?: AbortSignal;
|
|
229
237
|
}
|
|
230
238
|
|
|
231
239
|
/**
|
|
@@ -242,7 +250,6 @@ export declare class EnhancementRegisteredEvent extends Event {
|
|
|
242
250
|
* Extends EventTarget to dispatch events when configs are registered
|
|
243
251
|
*/
|
|
244
252
|
export declare class EnhancementRegistry extends EventTarget {
|
|
245
|
-
private items;
|
|
246
253
|
push(items: EnhancementConfig | EnhancementConfig[]): void;
|
|
247
254
|
getItems(): EnhancementConfig[];
|
|
248
255
|
findBySymbol(symbol: symbol | string): EnhancementConfig | undefined;
|
|
@@ -298,9 +305,12 @@ export declare function assignGingerly(
|
|
|
298
305
|
export default assignGingerly;
|
|
299
306
|
|
|
300
307
|
export declare class ElementEnhancementGateway{
|
|
308
|
+
//TODO: this isn't right
|
|
301
309
|
enh: ElementEnhancement;
|
|
302
310
|
}
|
|
303
311
|
|
|
304
312
|
export interface ElementEnhancement{
|
|
305
|
-
|
|
313
|
+
get(registryItem: EnhancementConfig | string | symbol, mountCtx?: any): any;
|
|
314
|
+
dispose(registryItem: EnhancementConfig | string | symbol): void;
|
|
315
|
+
whenResolved(registryItem: EnhancementConfig | string | symbol, mountCtx?: any): Promise<any>;
|
|
306
316
|
}
|
package/Infer.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
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;
|