assign-gingerly 0.0.21 → 0.0.23
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 +460 -23
- package/assignGingerly.js +134 -29
- package/assignGingerly.ts +188 -28
- package/handleIshProperty.js +92 -0
- package/handleIshProperty.ts +115 -0
- package/index.js +1 -1
- package/index.ts +1 -1
- package/object-extension.js +20 -1
- package/object-extension.ts +23 -2
- package/package.json +6 -4
- package/parseWithAttrs.js +29 -23
- package/parseWithAttrs.ts +41 -24
- package/playwright-browser.config.ts +42 -0
- package/playwright.config.ts +15 -15
- package/types/assign-gingerly/types.d.ts +58 -6
- package/types/global.d.ts +4 -0
package/assignGingerly.js
CHANGED
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
// Polyfill for Map.prototype.getOrInsert and WeakMap.prototype.getOrInsert
|
|
2
|
-
if (typeof Map.prototype.getOrInsertComputed !== 'function') {
|
|
3
|
-
Map.prototype.getOrInsertComputed = function (key, insert) {
|
|
4
|
-
if (this.has(key))
|
|
5
|
-
return this.get(key);
|
|
6
|
-
const value = insert();
|
|
7
|
-
this.set(key, value);
|
|
8
|
-
return value;
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
if (typeof WeakMap.prototype.getOrInsertComputed !== 'function') {
|
|
12
|
-
WeakMap.prototype.getOrInsertComputed = function (key, insert) {
|
|
13
|
-
if (this.has(key))
|
|
14
|
-
return this.get(key);
|
|
15
|
-
const value = insert();
|
|
16
|
-
this.set(key, value);
|
|
17
|
-
return value;
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
1
|
/**
|
|
21
2
|
* GUID for global instance map storage to ensure uniqueness across package versions
|
|
22
3
|
*/
|
|
@@ -35,34 +16,126 @@ export function getInstanceMap() {
|
|
|
35
16
|
/**
|
|
36
17
|
* Base registry class for managing enhancement configurations
|
|
37
18
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Event dispatched when enhancement configs are registered
|
|
21
|
+
*/
|
|
22
|
+
export class EnhancementRegisteredEvent extends Event {
|
|
23
|
+
config;
|
|
24
|
+
static eventName = 'register';
|
|
25
|
+
constructor(config) {
|
|
26
|
+
super(EnhancementRegisteredEvent.eventName);
|
|
27
|
+
this.config = config;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Registry for enhancement configurations
|
|
32
|
+
* Extends EventTarget to dispatch events when configs are registered
|
|
33
|
+
*/
|
|
34
|
+
export class EnhancementRegistry extends EventTarget {
|
|
35
|
+
#items = new Set();
|
|
40
36
|
push(items) {
|
|
41
37
|
if (Array.isArray(items)) {
|
|
42
|
-
this.#items.
|
|
38
|
+
items.forEach(item => this.#items.add(item));
|
|
43
39
|
}
|
|
44
40
|
else {
|
|
45
|
-
this.#items.
|
|
41
|
+
this.#items.add(items);
|
|
46
42
|
}
|
|
43
|
+
// Dispatch event after adding items
|
|
44
|
+
this.dispatchEvent(new EnhancementRegisteredEvent(items));
|
|
47
45
|
}
|
|
48
46
|
getItems() {
|
|
49
|
-
return this.#items;
|
|
47
|
+
return Array.from(this.#items);
|
|
50
48
|
}
|
|
51
49
|
findBySymbol(symbol) {
|
|
52
|
-
|
|
50
|
+
for (const item of this.#items) {
|
|
53
51
|
const symlinks = item.symlinks;
|
|
54
52
|
if (!symlinks)
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
continue;
|
|
54
|
+
const hasSymbol = Object.keys(symlinks).some(key => {
|
|
57
55
|
if (typeof key === 'symbol' || (typeof symlinks[key] === 'symbol')) {
|
|
58
56
|
return key === symbol || symlinks[key] === symbol;
|
|
59
57
|
}
|
|
60
58
|
return false;
|
|
61
59
|
}) || Object.getOwnPropertySymbols(symlinks).some(sym => sym === symbol);
|
|
62
|
-
|
|
60
|
+
if (hasSymbol)
|
|
61
|
+
return item;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
63
64
|
}
|
|
64
65
|
findByEnhKey(enhKey) {
|
|
65
|
-
|
|
66
|
+
for (const item of this.#items) {
|
|
67
|
+
if (item.enhKey === enhKey)
|
|
68
|
+
return item;
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Registry for ItemScope Manager configurations
|
|
75
|
+
* Extends EventTarget to support lazy registration via events
|
|
76
|
+
*/
|
|
77
|
+
export class ItemscopeRegistry extends EventTarget {
|
|
78
|
+
#configs = new Map();
|
|
79
|
+
#pendingSetups = new Map();
|
|
80
|
+
/**
|
|
81
|
+
* Define a new manager configuration
|
|
82
|
+
* @param name - Manager name (matches itemscope attribute value)
|
|
83
|
+
* @param config - Manager configuration object
|
|
84
|
+
* @throws Error if name is already registered
|
|
85
|
+
*/
|
|
86
|
+
define(name, config) {
|
|
87
|
+
if (this.#configs.has(name)) {
|
|
88
|
+
throw new Error('Already registered');
|
|
89
|
+
}
|
|
90
|
+
this.#configs.set(name, config);
|
|
91
|
+
this.dispatchEvent(new Event(name));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get a manager configuration by name
|
|
95
|
+
* @param name - Manager name
|
|
96
|
+
* @returns Manager configuration or undefined
|
|
97
|
+
*/
|
|
98
|
+
get(name) {
|
|
99
|
+
return this.#configs.get(name);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Wait for a manager to be defined and all pending setups to complete
|
|
103
|
+
* @param name - Manager name to wait for
|
|
104
|
+
* @returns Promise that resolves when manager is defined and all setups are complete
|
|
105
|
+
*/
|
|
106
|
+
async whenDefined(name) {
|
|
107
|
+
// If not yet defined, wait for definition
|
|
108
|
+
if (!this.#configs.has(name)) {
|
|
109
|
+
await new Promise((resolve) => {
|
|
110
|
+
this.addEventListener(name, () => resolve(), { once: true });
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Wait for all pending setups for this manager
|
|
114
|
+
const pending = this.#pendingSetups.get(name);
|
|
115
|
+
if (pending && pending.length > 0) {
|
|
116
|
+
await Promise.all(pending);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Internal method to track a pending setup
|
|
121
|
+
* @param name - Manager name
|
|
122
|
+
* @param promise - Promise representing the setup operation
|
|
123
|
+
*/
|
|
124
|
+
_trackSetup(name, promise) {
|
|
125
|
+
if (!this.#pendingSetups.has(name)) {
|
|
126
|
+
this.#pendingSetups.set(name, []);
|
|
127
|
+
}
|
|
128
|
+
this.#pendingSetups.get(name).push(promise);
|
|
129
|
+
// Clean up after completion
|
|
130
|
+
promise.finally(() => {
|
|
131
|
+
const pending = this.#pendingSetups.get(name);
|
|
132
|
+
if (pending) {
|
|
133
|
+
const index = pending.indexOf(promise);
|
|
134
|
+
if (index > -1) {
|
|
135
|
+
pending.splice(index, 1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
66
139
|
}
|
|
67
140
|
}
|
|
68
141
|
/**
|
|
@@ -187,6 +260,38 @@ export function assignGingerly(target, source, options) {
|
|
|
187
260
|
for (const sym of Object.getOwnPropertySymbols(source)) {
|
|
188
261
|
processedSource[sym] = source[sym];
|
|
189
262
|
}
|
|
263
|
+
// Process 'ish' property for HTMLElements with itemscope (async, non-blocking)
|
|
264
|
+
if ('ish' in processedSource) {
|
|
265
|
+
if (typeof HTMLElement !== 'undefined' && target instanceof HTMLElement) {
|
|
266
|
+
// Capture the value before deleting
|
|
267
|
+
const ishValue = processedSource['ish'];
|
|
268
|
+
// Remove 'ish' from processedSource to prevent normal assignment
|
|
269
|
+
delete processedSource['ish'];
|
|
270
|
+
// Get the itemscope attribute to track the setup
|
|
271
|
+
const itemscopeValue = target.getAttribute('itemscope');
|
|
272
|
+
// Load handler on demand and process asynchronously
|
|
273
|
+
const setupPromise = (async () => {
|
|
274
|
+
try {
|
|
275
|
+
const { handleIshProperty } = await import('./handleIshProperty.js');
|
|
276
|
+
await handleIshProperty(target, ishValue, options, assignGingerly);
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
console.error('Error in handleIshProperty:', err);
|
|
280
|
+
// Re-throw errors asynchronously so they're visible
|
|
281
|
+
setTimeout(() => { throw err; }, 0);
|
|
282
|
+
}
|
|
283
|
+
})();
|
|
284
|
+
// Track the setup promise with the registry if we have an itemscope value
|
|
285
|
+
if (itemscopeValue && typeof itemscopeValue === 'string' && itemscopeValue.length > 0) {
|
|
286
|
+
const registry = target.customElementRegistry?.itemscopeRegistry
|
|
287
|
+
?? (typeof customElements !== 'undefined' ? customElements.itemscopeRegistry : undefined);
|
|
288
|
+
if (registry && typeof registry._trackSetup === 'function') {
|
|
289
|
+
registry._trackSetup(itemscopeValue, setupPromise);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// For non-HTMLElement targets, 'ish' is processed as a normal property
|
|
294
|
+
}
|
|
190
295
|
// First pass: handle all non-symbol keys and sync operations
|
|
191
296
|
for (const key of Object.keys(processedSource)) {
|
|
192
297
|
const value = processedSource[key];
|
package/assignGingerly.ts
CHANGED
|
@@ -2,34 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
import { EnhancementConfig } from "./types/assign-gingerly/types";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
this.set(key, value);
|
|
11
|
-
return value;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
if (typeof WeakMap.prototype.getOrInsertComputed !== 'function') {
|
|
15
|
-
WeakMap.prototype.getOrInsertComputed = function(key, insert) {
|
|
16
|
-
if (this.has(key)) return this.get(key);
|
|
17
|
-
const value = insert();
|
|
18
|
-
this.set(key, value);
|
|
19
|
-
return value;
|
|
20
|
-
};
|
|
5
|
+
/**
|
|
6
|
+
* Constructor signature for ItemScope Manager classes
|
|
7
|
+
*/
|
|
8
|
+
export type ItemscopeManager<T = any> = {
|
|
9
|
+
new (element: HTMLElement, initVals?: Partial<T>): T;
|
|
21
10
|
}
|
|
22
11
|
|
|
23
12
|
/**
|
|
24
|
-
*
|
|
13
|
+
* Configuration for ItemScope Manager registration
|
|
25
14
|
*/
|
|
26
|
-
export
|
|
15
|
+
export interface ItemscopeManagerConfig<T = any> {
|
|
16
|
+
/**
|
|
17
|
+
* Manager class constructor
|
|
18
|
+
*/
|
|
19
|
+
manager: ItemscopeManager<T>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Optional lifecycle method keys
|
|
23
|
+
* - dispose: Method name to call when manager is disposed
|
|
24
|
+
* - resolved: Property/event name indicating manager is ready
|
|
25
|
+
*/
|
|
26
|
+
lifecycleKeys?: {
|
|
27
|
+
dispose?: string | symbol;
|
|
28
|
+
resolved?: string | symbol;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Polyfill for WeakMap.prototype.getOrInsert
|
|
33
|
+
|
|
34
|
+
// if (typeof WeakMap.prototype.getOrInsertComputed !== 'function') {
|
|
35
|
+
// WeakMap.prototype.getOrInsertComputed = function(key, insert) {
|
|
36
|
+
// if (this.has(key)) return this.get(key);
|
|
37
|
+
// const value = insert();
|
|
38
|
+
// this.set(key, value);
|
|
39
|
+
// return value;
|
|
40
|
+
// };
|
|
41
|
+
// }
|
|
42
|
+
|
|
43
|
+
// /**
|
|
44
|
+
// * @deprecated Use EnhancementConfig instead
|
|
45
|
+
// */
|
|
46
|
+
// export type IBaseRegistryItem<T = any> = EnhancementConfig<T>;
|
|
27
47
|
|
|
28
48
|
/**
|
|
29
49
|
* Interface for the options passed to assignGingerly
|
|
30
50
|
*/
|
|
31
51
|
export interface IAssignGingerlyOptions {
|
|
32
52
|
registry?: typeof EnhancementRegistry | EnhancementRegistry;
|
|
53
|
+
bypassChecks?: boolean;
|
|
33
54
|
}
|
|
34
55
|
|
|
35
56
|
/**
|
|
@@ -52,39 +73,142 @@ export function getInstanceMap(): WeakMap<object, Map<EnhancementConfig, any>> {
|
|
|
52
73
|
/**
|
|
53
74
|
* Base registry class for managing enhancement configurations
|
|
54
75
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Event dispatched when enhancement configs are registered
|
|
78
|
+
*/
|
|
79
|
+
export class EnhancementRegisteredEvent extends Event {
|
|
80
|
+
static eventName = 'register';
|
|
81
|
+
|
|
82
|
+
constructor(
|
|
83
|
+
public config: EnhancementConfig | EnhancementConfig[]
|
|
84
|
+
) {
|
|
85
|
+
super(EnhancementRegisteredEvent.eventName);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Registry for enhancement configurations
|
|
91
|
+
* Extends EventTarget to dispatch events when configs are registered
|
|
92
|
+
*/
|
|
93
|
+
export class EnhancementRegistry extends EventTarget {
|
|
94
|
+
#items: Set<EnhancementConfig> = new Set();
|
|
57
95
|
|
|
58
96
|
push(items: EnhancementConfig | EnhancementConfig[]): void {
|
|
59
97
|
if (Array.isArray(items)) {
|
|
60
|
-
this.#items.
|
|
98
|
+
items.forEach(item => this.#items.add(item));
|
|
61
99
|
} else {
|
|
62
|
-
this.#items.
|
|
100
|
+
this.#items.add(items);
|
|
63
101
|
}
|
|
102
|
+
|
|
103
|
+
// Dispatch event after adding items
|
|
104
|
+
this.dispatchEvent(new EnhancementRegisteredEvent(items));
|
|
64
105
|
}
|
|
65
106
|
|
|
66
107
|
getItems(): EnhancementConfig[] {
|
|
67
|
-
return this.#items;
|
|
108
|
+
return Array.from(this.#items);
|
|
68
109
|
}
|
|
69
110
|
|
|
70
111
|
findBySymbol(symbol: symbol | string): EnhancementConfig | undefined {
|
|
71
|
-
|
|
112
|
+
for (const item of this.#items) {
|
|
72
113
|
const symlinks = item.symlinks;
|
|
73
|
-
if (!symlinks)
|
|
74
|
-
|
|
114
|
+
if (!symlinks) continue;
|
|
115
|
+
|
|
116
|
+
const hasSymbol = Object.keys(symlinks).some(key => {
|
|
75
117
|
if (typeof key === 'symbol' || (typeof symlinks[key as any] === 'symbol')) {
|
|
76
118
|
return key === symbol || symlinks[key as any] === symbol;
|
|
77
119
|
}
|
|
78
120
|
return false;
|
|
79
121
|
}) || Object.getOwnPropertySymbols(symlinks).some(sym => sym === symbol);
|
|
80
|
-
|
|
122
|
+
|
|
123
|
+
if (hasSymbol) return item;
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
81
126
|
}
|
|
82
127
|
|
|
83
128
|
findByEnhKey(enhKey: string | symbol): EnhancementConfig | undefined {
|
|
84
|
-
|
|
129
|
+
for (const item of this.#items) {
|
|
130
|
+
if (item.enhKey === enhKey) return item;
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
85
133
|
}
|
|
86
134
|
}
|
|
87
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Registry for ItemScope Manager configurations
|
|
138
|
+
* Extends EventTarget to support lazy registration via events
|
|
139
|
+
*/
|
|
140
|
+
export class ItemscopeRegistry extends EventTarget {
|
|
141
|
+
#configs: Map<string, ItemscopeManagerConfig> = new Map();
|
|
142
|
+
#pendingSetups: Map<string, Promise<void>[]> = new Map();
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Define a new manager configuration
|
|
146
|
+
* @param name - Manager name (matches itemscope attribute value)
|
|
147
|
+
* @param config - Manager configuration object
|
|
148
|
+
* @throws Error if name is already registered
|
|
149
|
+
*/
|
|
150
|
+
define(name: string, config: ItemscopeManagerConfig): void {
|
|
151
|
+
if (this.#configs.has(name)) {
|
|
152
|
+
throw new Error('Already registered');
|
|
153
|
+
}
|
|
154
|
+
this.#configs.set(name, config);
|
|
155
|
+
this.dispatchEvent(new Event(name));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get a manager configuration by name
|
|
160
|
+
* @param name - Manager name
|
|
161
|
+
* @returns Manager configuration or undefined
|
|
162
|
+
*/
|
|
163
|
+
get(name: string): ItemscopeManagerConfig | undefined {
|
|
164
|
+
return this.#configs.get(name);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Wait for a manager to be defined and all pending setups to complete
|
|
169
|
+
* @param name - Manager name to wait for
|
|
170
|
+
* @returns Promise that resolves when manager is defined and all setups are complete
|
|
171
|
+
*/
|
|
172
|
+
async whenDefined(name: string): Promise<void> {
|
|
173
|
+
// If not yet defined, wait for definition
|
|
174
|
+
if (!this.#configs.has(name)) {
|
|
175
|
+
await new Promise<void>((resolve) => {
|
|
176
|
+
this.addEventListener(name, () => resolve(), { once: true });
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Wait for all pending setups for this manager
|
|
181
|
+
const pending = this.#pendingSetups.get(name);
|
|
182
|
+
if (pending && pending.length > 0) {
|
|
183
|
+
await Promise.all(pending);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Internal method to track a pending setup
|
|
189
|
+
* @param name - Manager name
|
|
190
|
+
* @param promise - Promise representing the setup operation
|
|
191
|
+
*/
|
|
192
|
+
_trackSetup(name: string, promise: Promise<void>): void {
|
|
193
|
+
if (!this.#pendingSetups.has(name)) {
|
|
194
|
+
this.#pendingSetups.set(name, []);
|
|
195
|
+
}
|
|
196
|
+
this.#pendingSetups.get(name)!.push(promise);
|
|
197
|
+
|
|
198
|
+
// Clean up after completion
|
|
199
|
+
promise.finally(() => {
|
|
200
|
+
const pending = this.#pendingSetups.get(name);
|
|
201
|
+
if (pending) {
|
|
202
|
+
const index = pending.indexOf(promise);
|
|
203
|
+
if (index > -1) {
|
|
204
|
+
pending.splice(index, 1);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
88
212
|
/**
|
|
89
213
|
* Helper function to check if a string key represents a Symbol.for expression
|
|
90
214
|
*/
|
|
@@ -223,6 +347,42 @@ export function assignGingerly(
|
|
|
223
347
|
processedSource[sym] = source[sym];
|
|
224
348
|
}
|
|
225
349
|
|
|
350
|
+
// Process 'ish' property for HTMLElements with itemscope (async, non-blocking)
|
|
351
|
+
if ('ish' in processedSource) {
|
|
352
|
+
if (typeof HTMLElement !== 'undefined' && target instanceof HTMLElement) {
|
|
353
|
+
// Capture the value before deleting
|
|
354
|
+
const ishValue = processedSource['ish'];
|
|
355
|
+
// Remove 'ish' from processedSource to prevent normal assignment
|
|
356
|
+
delete processedSource['ish'];
|
|
357
|
+
|
|
358
|
+
// Get the itemscope attribute to track the setup
|
|
359
|
+
const itemscopeValue = target.getAttribute('itemscope');
|
|
360
|
+
|
|
361
|
+
// Load handler on demand and process asynchronously
|
|
362
|
+
const setupPromise = (async () => {
|
|
363
|
+
try {
|
|
364
|
+
const { handleIshProperty } = await import('./handleIshProperty.js');
|
|
365
|
+
await handleIshProperty(target, ishValue, options, assignGingerly);
|
|
366
|
+
} catch (err) {
|
|
367
|
+
console.error('Error in handleIshProperty:', err);
|
|
368
|
+
// Re-throw errors asynchronously so they're visible
|
|
369
|
+
setTimeout(() => { throw err; }, 0);
|
|
370
|
+
}
|
|
371
|
+
})();
|
|
372
|
+
|
|
373
|
+
// Track the setup promise with the registry if we have an itemscope value
|
|
374
|
+
if (itemscopeValue && typeof itemscopeValue === 'string' && itemscopeValue.length > 0) {
|
|
375
|
+
const registry = (target as any).customElementRegistry?.itemscopeRegistry
|
|
376
|
+
?? (typeof customElements !== 'undefined' ? customElements.itemscopeRegistry : undefined);
|
|
377
|
+
|
|
378
|
+
if (registry && typeof registry._trackSetup === 'function') {
|
|
379
|
+
registry._trackSetup(itemscopeValue, setupPromise);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// For non-HTMLElement targets, 'ish' is processed as a normal property
|
|
384
|
+
}
|
|
385
|
+
|
|
226
386
|
// First pass: handle all non-symbol keys and sync operations
|
|
227
387
|
for (const key of Object.keys(processedSource)) {
|
|
228
388
|
const value = processedSource[key];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle the 'ish' property assignment for HTMLElements with itemscope attributes.
|
|
3
|
+
* This function validates the element and value, then defines or updates the 'ish' property.
|
|
4
|
+
*
|
|
5
|
+
* @param element - The HTMLElement to assign the 'ish' property to
|
|
6
|
+
* @param value - The value to assign (must be an object)
|
|
7
|
+
* @param options - Optional assignGingerly options
|
|
8
|
+
* @param assignGingerlyFn - Reference to the assignGingerly function for recursive calls
|
|
9
|
+
*/
|
|
10
|
+
export async function handleIshProperty(element, value, options, assignGingerlyFn) {
|
|
11
|
+
// Validate itemscope attribute
|
|
12
|
+
const itemscopeValue = element.getAttribute('itemscope');
|
|
13
|
+
if (typeof itemscopeValue !== 'string' || itemscopeValue.length === 0) {
|
|
14
|
+
throw new Error('Element must have itemscope attribute set to a non-empty string value');
|
|
15
|
+
}
|
|
16
|
+
// Validate value is an object
|
|
17
|
+
if (typeof value !== 'object' || value === null) {
|
|
18
|
+
throw new Error('ish property value must be an object');
|
|
19
|
+
}
|
|
20
|
+
// Get or create the 'ish' property on the element
|
|
21
|
+
if (!('ish' in element)) {
|
|
22
|
+
await defineIshProperty(element, itemscopeValue, options, assignGingerlyFn);
|
|
23
|
+
}
|
|
24
|
+
// Queue the value for assignment
|
|
25
|
+
const ishDescriptor = Object.getOwnPropertyDescriptor(element, 'ish');
|
|
26
|
+
if (ishDescriptor && ishDescriptor.set) {
|
|
27
|
+
ishDescriptor.set.call(element, value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Define the 'ish' property on an HTMLElement with itemscope attribute.
|
|
32
|
+
* This function handles both immediate and lazy manager instantiation.
|
|
33
|
+
*
|
|
34
|
+
* @param element - The HTMLElement to define the 'ish' property on
|
|
35
|
+
* @param managerName - The name of the manager (from itemscope attribute)
|
|
36
|
+
* @param options - Optional assignGingerly options
|
|
37
|
+
* @param assignGingerlyFn - Reference to the assignGingerly function for recursive calls
|
|
38
|
+
*/
|
|
39
|
+
async function defineIshProperty(element, managerName, options, assignGingerlyFn) {
|
|
40
|
+
// Determine which registry to use
|
|
41
|
+
const registry = element.customElementRegistry?.itemscopeRegistry
|
|
42
|
+
?? (typeof customElements !== 'undefined' ? customElements.itemscopeRegistry : undefined);
|
|
43
|
+
if (!registry) {
|
|
44
|
+
throw new Error('ItemscopeRegistry not available');
|
|
45
|
+
}
|
|
46
|
+
// Check if manager is registered
|
|
47
|
+
let config = registry.get(managerName);
|
|
48
|
+
// If not registered, wait for registration
|
|
49
|
+
if (!config) {
|
|
50
|
+
const { waitForEvent } = await import('./waitForEvent.js');
|
|
51
|
+
await waitForEvent(registry, managerName);
|
|
52
|
+
config = registry.get(managerName);
|
|
53
|
+
if (!config) {
|
|
54
|
+
throw new Error(`Manager "${managerName}" not found after registration event`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Create manager instance
|
|
58
|
+
let managerInstance = null;
|
|
59
|
+
const valueQueue = [];
|
|
60
|
+
// Define the 'ish' property
|
|
61
|
+
Object.defineProperty(element, 'ish', {
|
|
62
|
+
get() {
|
|
63
|
+
return managerInstance;
|
|
64
|
+
},
|
|
65
|
+
set(newValue) {
|
|
66
|
+
// If setting the same instance, do nothing
|
|
67
|
+
if (newValue === managerInstance) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Queue the value
|
|
71
|
+
valueQueue.push(newValue);
|
|
72
|
+
// If manager not yet instantiated, create it
|
|
73
|
+
if (!managerInstance) {
|
|
74
|
+
// Merge all queued values for initVals
|
|
75
|
+
const initVals = Object.assign({}, ...valueQueue);
|
|
76
|
+
managerInstance = new config.manager(element, initVals);
|
|
77
|
+
valueQueue.length = 0; // Clear queue
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Process queue asynchronously
|
|
81
|
+
(async () => {
|
|
82
|
+
while (valueQueue.length > 0) {
|
|
83
|
+
const queuedValue = valueQueue.shift();
|
|
84
|
+
await assignGingerlyFn(managerInstance, queuedValue, options);
|
|
85
|
+
}
|
|
86
|
+
})();
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
enumerable: true,
|
|
90
|
+
configurable: true,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { IAssignGingerlyOptions, ItemscopeManagerConfig } from './assignGingerly.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handle the 'ish' property assignment for HTMLElements with itemscope attributes.
|
|
5
|
+
* This function validates the element and value, then defines or updates the 'ish' property.
|
|
6
|
+
*
|
|
7
|
+
* @param element - The HTMLElement to assign the 'ish' property to
|
|
8
|
+
* @param value - The value to assign (must be an object)
|
|
9
|
+
* @param options - Optional assignGingerly options
|
|
10
|
+
* @param assignGingerlyFn - Reference to the assignGingerly function for recursive calls
|
|
11
|
+
*/
|
|
12
|
+
export async function handleIshProperty(
|
|
13
|
+
element: HTMLElement,
|
|
14
|
+
value: any,
|
|
15
|
+
options: IAssignGingerlyOptions | undefined,
|
|
16
|
+
assignGingerlyFn: (target: any, source: any, options?: IAssignGingerlyOptions) => any
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
// Validate itemscope attribute
|
|
19
|
+
const itemscopeValue = element.getAttribute('itemscope');
|
|
20
|
+
if (typeof itemscopeValue !== 'string' || itemscopeValue.length === 0) {
|
|
21
|
+
throw new Error('Element must have itemscope attribute set to a non-empty string value');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Validate value is an object
|
|
25
|
+
if (typeof value !== 'object' || value === null) {
|
|
26
|
+
throw new Error('ish property value must be an object');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Get or create the 'ish' property on the element
|
|
30
|
+
if (!('ish' in element)) {
|
|
31
|
+
await defineIshProperty(element, itemscopeValue, options, assignGingerlyFn);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Queue the value for assignment
|
|
35
|
+
const ishDescriptor = Object.getOwnPropertyDescriptor(element, 'ish');
|
|
36
|
+
if (ishDescriptor && ishDescriptor.set) {
|
|
37
|
+
ishDescriptor.set.call(element, value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Define the 'ish' property on an HTMLElement with itemscope attribute.
|
|
43
|
+
* This function handles both immediate and lazy manager instantiation.
|
|
44
|
+
*
|
|
45
|
+
* @param element - The HTMLElement to define the 'ish' property on
|
|
46
|
+
* @param managerName - The name of the manager (from itemscope attribute)
|
|
47
|
+
* @param options - Optional assignGingerly options
|
|
48
|
+
* @param assignGingerlyFn - Reference to the assignGingerly function for recursive calls
|
|
49
|
+
*/
|
|
50
|
+
async function defineIshProperty(
|
|
51
|
+
element: HTMLElement,
|
|
52
|
+
managerName: string,
|
|
53
|
+
options: IAssignGingerlyOptions | undefined,
|
|
54
|
+
assignGingerlyFn: (target: any, source: any, options?: IAssignGingerlyOptions) => any
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
// Determine which registry to use
|
|
57
|
+
const registry = (element as any).customElementRegistry?.itemscopeRegistry
|
|
58
|
+
?? (typeof customElements !== 'undefined' ? customElements.itemscopeRegistry : undefined);
|
|
59
|
+
|
|
60
|
+
if (!registry) {
|
|
61
|
+
throw new Error('ItemscopeRegistry not available');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check if manager is registered
|
|
65
|
+
let config = registry.get(managerName);
|
|
66
|
+
|
|
67
|
+
// If not registered, wait for registration
|
|
68
|
+
if (!config) {
|
|
69
|
+
const { waitForEvent } = await import('./waitForEvent.js');
|
|
70
|
+
await waitForEvent(registry, managerName);
|
|
71
|
+
config = registry.get(managerName);
|
|
72
|
+
|
|
73
|
+
if (!config) {
|
|
74
|
+
throw new Error(`Manager "${managerName}" not found after registration event`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Create manager instance
|
|
79
|
+
let managerInstance: any = null;
|
|
80
|
+
const valueQueue: any[] = [];
|
|
81
|
+
|
|
82
|
+
// Define the 'ish' property
|
|
83
|
+
Object.defineProperty(element, 'ish', {
|
|
84
|
+
get() {
|
|
85
|
+
return managerInstance;
|
|
86
|
+
},
|
|
87
|
+
set(newValue: any) {
|
|
88
|
+
// If setting the same instance, do nothing
|
|
89
|
+
if (newValue === managerInstance) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Queue the value
|
|
94
|
+
valueQueue.push(newValue);
|
|
95
|
+
|
|
96
|
+
// If manager not yet instantiated, create it
|
|
97
|
+
if (!managerInstance) {
|
|
98
|
+
// Merge all queued values for initVals
|
|
99
|
+
const initVals = Object.assign({}, ...valueQueue);
|
|
100
|
+
managerInstance = new config!.manager(element, initVals);
|
|
101
|
+
valueQueue.length = 0; // Clear queue
|
|
102
|
+
} else {
|
|
103
|
+
// Process queue asynchronously
|
|
104
|
+
(async () => {
|
|
105
|
+
while (valueQueue.length > 0) {
|
|
106
|
+
const queuedValue = valueQueue.shift();
|
|
107
|
+
await assignGingerlyFn(managerInstance, queuedValue, options);
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
enumerable: true,
|
|
113
|
+
configurable: true,
|
|
114
|
+
});
|
|
115
|
+
}
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { assignGingerly } from './assignGingerly.js';
|
|
2
2
|
export { assignTentatively } from './assignTentatively.js';
|
|
3
|
-
export { EnhancementRegistry } from './assignGingerly.js';
|
|
3
|
+
export { EnhancementRegistry, ItemscopeRegistry, EnhancementRegisteredEvent } 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';
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export {assignGingerly} from './assignGingerly.js';
|
|
2
2
|
export {assignTentatively} from './assignTentatively.js';
|
|
3
|
-
export {EnhancementRegistry} from './assignGingerly.js';
|
|
3
|
+
export {EnhancementRegistry, ItemscopeRegistry, EnhancementRegisteredEvent} 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';
|