assign-gingerly 0.0.22 → 0.0.24

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.
@@ -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';
@@ -1,4 +1,4 @@
1
- import assignGingerly, { EnhancementRegistry, getInstanceMap } from './assignGingerly.js';
1
+ import assignGingerly, { EnhancementRegistry, ItemscopeRegistry, getInstanceMap } from './assignGingerly.js';
2
2
  import { parseWithAttrs } from './parseWithAttrs.js';
3
3
  /**
4
4
  * Normalizes lifecycleKeys to always return an object with dispose and resolved keys
@@ -34,6 +34,25 @@ if (typeof CustomElementRegistry !== 'undefined') {
34
34
  enumerable: false,
35
35
  configurable: true,
36
36
  });
37
+ /**
38
+ * Adds itemscopeRegistry to CustomElementRegistry prototype as a lazy getter
39
+ */
40
+ Object.defineProperty(CustomElementRegistry.prototype, 'itemscopeRegistry', {
41
+ get: function () {
42
+ // Create a new ItemscopeRegistry instance on first access and cache it
43
+ const registry = new ItemscopeRegistry();
44
+ // Replace the getter with the actual value
45
+ Object.defineProperty(this, 'itemscopeRegistry', {
46
+ value: registry,
47
+ writable: true,
48
+ enumerable: false,
49
+ configurable: true,
50
+ });
51
+ return registry;
52
+ },
53
+ enumerable: false,
54
+ configurable: true,
55
+ });
37
56
  }
38
57
  /**
39
58
  * Enhancement container class for Element.prototype.enh
@@ -1,4 +1,4 @@
1
- import assignGingerly, { EnhancementRegistry, IAssignGingerlyOptions, getInstanceMap, INSTANCE_MAP_GUID } from './assignGingerly.js';
1
+ import assignGingerly, { EnhancementRegistry, ItemscopeRegistry, IAssignGingerlyOptions, getInstanceMap, INSTANCE_MAP_GUID } from './assignGingerly.js';
2
2
  import { parseWithAttrs } from './parseWithAttrs.js';
3
3
 
4
4
  /**
@@ -17,11 +17,12 @@ function normalizeLifecycleKeys(lifecycleKeys: true | { dispose?: string | symbo
17
17
  }
18
18
 
19
19
  /**
20
- * Extends the CustomElementRegistry interface to include enhancementRegistry
20
+ * Extends the CustomElementRegistry interface to include enhancementRegistry and itemscopeRegistry
21
21
  */
22
22
  declare global {
23
23
  interface CustomElementRegistry {
24
24
  enhancementRegistry: typeof EnhancementRegistry | EnhancementRegistry;
25
+ itemscopeRegistry: ItemscopeRegistry;
25
26
  }
26
27
 
27
28
  interface Element {
@@ -97,6 +98,26 @@ if (typeof CustomElementRegistry !== 'undefined') {
97
98
  enumerable: false,
98
99
  configurable: true,
99
100
  });
101
+
102
+ /**
103
+ * Adds itemscopeRegistry to CustomElementRegistry prototype as a lazy getter
104
+ */
105
+ Object.defineProperty(CustomElementRegistry.prototype, 'itemscopeRegistry', {
106
+ get: function () {
107
+ // Create a new ItemscopeRegistry instance on first access and cache it
108
+ const registry = new ItemscopeRegistry();
109
+ // Replace the getter with the actual value
110
+ Object.defineProperty(this, 'itemscopeRegistry', {
111
+ value: registry,
112
+ writable: true,
113
+ enumerable: false,
114
+ configurable: true,
115
+ });
116
+ return registry;
117
+ },
118
+ enumerable: false,
119
+ configurable: true,
120
+ });
100
121
  }
101
122
 
102
123
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
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": {
@@ -65,7 +65,7 @@
65
65
  "devDependencies": {
66
66
  "@playwright/test": "1.59.0-alpha-2026-02-28",
67
67
  "spa-ssi": "0.0.27",
68
- "@types/node": "^25.3.3",
69
- "typescript": "^5.9.3"
68
+ "@types/node": "25.5.0",
69
+ "typescript": "6.0.2"
70
70
  }
71
71
  }
package/parseWithAttrs.js CHANGED
@@ -23,35 +23,38 @@ function resolveParser(parserSpec) {
23
23
  if (typeof parserSpec === 'function') {
24
24
  return parserSpec;
25
25
  }
26
- // String reference - resolve it
27
- if (typeof parserSpec === 'string') {
28
- // Check if it's a custom element reference (contains dot)
29
- if (parserSpec.includes('.')) {
30
- const dotIndex = parserSpec.indexOf('.');
31
- const elementName = parserSpec.substring(0, dotIndex);
32
- const methodName = parserSpec.substring(dotIndex + 1);
33
- // Try custom element lookup
34
- if (typeof customElements !== 'undefined') {
35
- try {
36
- const ctr = customElements.get(elementName);
37
- if (ctr && typeof ctr[methodName] === 'function') {
38
- return ctr[methodName];
39
- }
40
- }
41
- catch (e) {
42
- // customElements.get might throw, fall through to registry
43
- }
26
+ // Tuple [CustomElementName, StaticMethodName] - resolve custom element static method
27
+ if (Array.isArray(parserSpec)) {
28
+ const [elementName, methodName] = parserSpec;
29
+ if (typeof customElements === 'undefined') {
30
+ throw new Error(`Cannot resolve parser [${elementName}, ${methodName}]: customElements is not available`);
31
+ }
32
+ try {
33
+ const ctr = customElements.get(elementName);
34
+ if (!ctr) {
35
+ throw new Error(`Cannot resolve parser [${elementName}, ${methodName}]: custom element "${elementName}" not found`);
44
36
  }
45
- // Fall through to global registry (allows dot notation in registry too)
37
+ if (typeof ctr[methodName] !== 'function') {
38
+ throw new Error(`Cannot resolve parser [${elementName}, ${methodName}]: static method "${methodName}" not found on custom element "${elementName}"`);
39
+ }
40
+ return ctr[methodName];
41
+ }
42
+ catch (e) {
43
+ if (e instanceof Error && e.message.startsWith('Cannot resolve parser')) {
44
+ throw e;
45
+ }
46
+ throw new Error(`Cannot resolve parser [${elementName}, ${methodName}]: ${e instanceof Error ? e.message : String(e)}`);
46
47
  }
47
- // Try global registry
48
+ }
49
+ // String reference - resolve from global registry
50
+ if (typeof parserSpec === 'string') {
48
51
  const parser = globalParserRegistry.get(parserSpec);
49
52
  if (parser) {
50
53
  return parser;
51
54
  }
52
- // Not found anywhere
53
- throw new Error(`Parser "${parserSpec}" not found. ` +
54
- `Check that it's registered in globalParserRegistry or exists as a static method on the custom element.`);
55
+ // Not found in registry
56
+ throw new Error(`Parser "${parserSpec}" not found in globalParserRegistry. ` +
57
+ `If you want to reference a custom element static method, use tuple syntax: ["element-name", "methodName"]`);
55
58
  }
56
59
  return undefined;
57
60
  }
@@ -71,6 +74,9 @@ function getCacheKey(config) {
71
74
  else if (typeof config.parser === 'string') {
72
75
  parserStr = `named:${config.parser}`;
73
76
  }
77
+ else if (Array.isArray(config.parser)) {
78
+ parserStr = `tuple:${config.parser[0]}.${config.parser[1]}`;
79
+ }
74
80
  else {
75
81
  parserStr = 'custom';
76
82
  }
package/parseWithAttrs.ts CHANGED
@@ -17,7 +17,9 @@ const parseCache = new Map<string, Map<string, any>>();
17
17
  * @returns The resolved parser function
18
18
  * @throws Error if parser cannot be resolved
19
19
  */
20
- function resolveParser(parserSpec: ((v: string | null) => any) | string | undefined): ((v: string | null) => any) | undefined {
20
+ function resolveParser(
21
+ parserSpec: ((v: string | null) => any) | string | [string, string] | undefined
22
+ ): ((v: string | null) => any) | undefined {
21
23
  // Undefined - no parser specified
22
24
  if (parserSpec === undefined) {
23
25
  return undefined;
@@ -28,39 +30,52 @@ function resolveParser(parserSpec: ((v: string | null) => any) | string | undefi
28
30
  return parserSpec;
29
31
  }
30
32
 
31
- // String reference - resolve it
32
- if (typeof parserSpec === 'string') {
33
- // Check if it's a custom element reference (contains dot)
34
- if (parserSpec.includes('.')) {
35
- const dotIndex = parserSpec.indexOf('.');
36
- const elementName = parserSpec.substring(0, dotIndex);
37
- const methodName = parserSpec.substring(dotIndex + 1);
33
+ // Tuple [CustomElementName, StaticMethodName] - resolve custom element static method
34
+ if (Array.isArray(parserSpec)) {
35
+ const [elementName, methodName] = parserSpec;
36
+
37
+ if (typeof customElements === 'undefined') {
38
+ throw new Error(
39
+ `Cannot resolve parser [${elementName}, ${methodName}]: customElements is not available`
40
+ );
41
+ }
42
+
43
+ try {
44
+ const ctr = customElements.get(elementName);
45
+ if (!ctr) {
46
+ throw new Error(
47
+ `Cannot resolve parser [${elementName}, ${methodName}]: custom element "${elementName}" not found`
48
+ );
49
+ }
38
50
 
39
- // Try custom element lookup
40
- if (typeof customElements !== 'undefined') {
41
- try {
42
- const ctr = customElements.get(elementName);
43
- if (ctr && typeof (ctr as any)[methodName] === 'function') {
44
- return (ctr as any)[methodName];
45
- }
46
- } catch (e) {
47
- // customElements.get might throw, fall through to registry
48
- }
51
+ if (typeof (ctr as any)[methodName] !== 'function') {
52
+ throw new Error(
53
+ `Cannot resolve parser [${elementName}, ${methodName}]: static method "${methodName}" not found on custom element "${elementName}"`
54
+ );
49
55
  }
50
56
 
51
- // Fall through to global registry (allows dot notation in registry too)
57
+ return (ctr as any)[methodName];
58
+ } catch (e) {
59
+ if (e instanceof Error && e.message.startsWith('Cannot resolve parser')) {
60
+ throw e;
61
+ }
62
+ throw new Error(
63
+ `Cannot resolve parser [${elementName}, ${methodName}]: ${e instanceof Error ? e.message : String(e)}`
64
+ );
52
65
  }
53
-
54
- // Try global registry
66
+ }
67
+
68
+ // String reference - resolve from global registry
69
+ if (typeof parserSpec === 'string') {
55
70
  const parser = globalParserRegistry.get(parserSpec);
56
71
  if (parser) {
57
72
  return parser;
58
73
  }
59
74
 
60
- // Not found anywhere
75
+ // Not found in registry
61
76
  throw new Error(
62
- `Parser "${parserSpec}" not found. ` +
63
- `Check that it's registered in globalParserRegistry or exists as a static method on the custom element.`
77
+ `Parser "${parserSpec}" not found in globalParserRegistry. ` +
78
+ `If you want to reference a custom element static method, use tuple syntax: ["element-name", "methodName"]`
64
79
  );
65
80
  }
66
81
 
@@ -82,6 +97,8 @@ function getCacheKey(config: AttrConfig<any>): string {
82
97
  parserStr = 'builtin';
83
98
  } else if (typeof config.parser === 'string') {
84
99
  parserStr = `named:${config.parser}`;
100
+ } else if (Array.isArray(config.parser)) {
101
+ parserStr = `tuple:${config.parser[0]}.${config.parser[1]}`;
85
102
  } else {
86
103
  parserStr = 'custom';
87
104
  }
@@ -20,7 +20,7 @@ export default defineConfig({
20
20
  /* Opt out of parallel tests on CI. */
21
21
  workers: process.env.CI ? 1 : undefined,
22
22
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
23
- reporter: 'html',
23
+ reporter: [ ['html', { open: 'never' }] ],
24
24
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
25
25
  use: {
26
26
  /* Base URL to use in actions like `await page.goto('/')`. */
@@ -34,15 +34,15 @@ export default defineConfig({
34
34
  use: { ...devices['Desktop Chrome'] },
35
35
  },
36
36
 
37
- {
38
- name: 'firefox',
39
- use: { ...devices['Desktop Firefox'] },
40
- },
37
+ // {
38
+ // name: 'firefox',
39
+ // use: { ...devices['Desktop Firefox'] },
40
+ // },
41
41
 
42
- {
43
- name: 'webkit',
44
- use: { ...devices['Desktop Safari'] },
45
- },
42
+ // {
43
+ // name: 'webkit',
44
+ // use: { ...devices['Desktop Safari'] },
45
+ // },
46
46
  ],
47
47
 
48
48
  /* Run your local dev server before starting the tests */
@@ -30,35 +30,40 @@ export type Spawner<T = any, Obj = Element> = {
30
30
  canSpawn?: (obj: any, ctx?: SpawnContext<T>) => boolean;
31
31
  }
32
32
 
33
+ export interface EnhancementConfigBase<T = any> {
34
+ //Allow unprefixed attributes for custom elements and SVG when element tag name matches pattern
35
+ allowUnprefixed?: string | RegExp;
36
+
37
+ //keys of type symbol are used for dependency injection
38
+ //and are used by assign-gingerly
39
+ symlinks?: { [key: symbol]: keyof T };
40
+
41
+ lifecycleKeys?:
42
+ | true // Use standard names: "dispose" method, "resolved" property/event
43
+ | {
44
+ dispose?: string | symbol,
45
+ resolved?: string | symbol
46
+ }
47
+ //used by mount-observer, not by assign-gingerly
48
+ //impossible to polyfill, but will always be disposed
49
+ //when oElement's reference count goes to zero
50
+ disposeOn?: DisposeEvent | DisposeEvent[]
51
+ }
52
+
33
53
  /**
34
54
  * Configuration for enhancing elements with class instances
35
55
  * Defines how to spawn and initialize enhancement classes
36
56
  */
37
- export interface EnhancementConfig<T = any, Obj = Element> {
57
+ export interface EnhancementConfig<T = any, Obj = Element> extends EnhancementConfigBase<T> {
38
58
 
39
59
  spawn: Spawner<T, Obj>;
40
60
 
41
61
  //Applicable to passing in the initVals during the spawn lifecycle event
42
62
  withAttrs?: AttrPatterns<T>;
43
63
 
44
- //Allow unprefixed attributes for custom elements and SVG when element tag name matches pattern
45
- allowUnprefixed?: string | RegExp;
46
-
47
- //keys of type symbol are used for dependency injection
48
- //and are used by assign-gingerly
49
- symlinks?: { [key: symbol]: keyof T };
64
+
50
65
  //only applicable when spawning from a DOM Element reference
51
66
  enhKey?: EnhKey;
52
- lifecycleKeys?:
53
- | true // Use standard names: "dispose" method, "resolved" property/event
54
- | {
55
- dispose?: string | symbol,
56
- resolved?: string | symbol
57
- }
58
- //used by mount-observer, not by assign-gingerly
59
- //impossible to polyfill, but will always be disposed
60
- //when oElement's reference count goes to zero
61
- disposeOn?: DisposeEvent | DisposeEvent[]
62
67
 
63
68
  }
64
69
 
@@ -66,6 +71,9 @@ export type Constructor = new (...args: any[]) => any;
66
71
 
67
72
  export type pathString = `?.${string}`;
68
73
 
74
+ export type CustomElementName = string;
75
+ export type CustomElementConstructorStaticMethodName = string;
76
+
69
77
  export interface AttrConfig<T = any> {
70
78
  /**
71
79
  * Type of the property value (JSON-serializable string format)
@@ -92,12 +100,14 @@ export interface AttrConfig<T = any> {
92
100
  /**
93
101
  * Parser to transform attribute string value
94
102
  * - Function: Inline parser function (not JSON serializable)
95
- * - String: Named parser reference (JSON serializable)
96
- * - Simple name: Looks up in global parser registry (e.g., 'timestamp', 'csv')
97
- * - Dot notation: Looks up static method on custom element (e.g., 'my-widget.parseSpecial')
98
- * Falls back to global registry if custom element not found
103
+ * - String: Named parser reference (JSON serializable) - looks up in global parser registry (e.g., 'timestamp', 'csv')
104
+ * - Tuple: [CustomElementName, StaticMethodName] - looks up static method on custom element constructor (e.g., ['my-widget', 'parseSpecial'])
99
105
  */
100
- parser?: ((attrValue: string | null) => any) | string;
106
+ parser?:
107
+ | ((attrValue: string | null) => any)
108
+ | string
109
+ | [CustomElementName, CustomElementConstructorStaticMethodName]
110
+ ;
101
111
 
102
112
  /**
103
113
  * Default value to use when attribute is missing
@@ -161,10 +171,20 @@ export interface IAssignGingerlyOptions {
161
171
  bypassChecks?: boolean;
162
172
  }
163
173
 
174
+ /**
175
+ * Event dispatched when enhancement configs are registered
176
+ */
177
+ export declare class EnhancementRegisteredEvent extends Event {
178
+ static eventName: string;
179
+ config: EnhancementConfig | EnhancementConfig[];
180
+ constructor(config: EnhancementConfig | EnhancementConfig[]);
181
+ }
182
+
164
183
  /**
165
184
  * Base registry class for managing enhancement configurations
185
+ * Extends EventTarget to dispatch events when configs are registered
166
186
  */
167
- export declare class EnhancementRegistry {
187
+ export declare class EnhancementRegistry extends EventTarget {
168
188
  private items;
169
189
  push(items: EnhancementConfig | EnhancementConfig[]): void;
170
190
  getItems(): EnhancementConfig[];
@@ -172,6 +192,43 @@ export declare class EnhancementRegistry {
172
192
  findByEnhKey(enhKey: string | symbol): EnhancementConfig | undefined;
173
193
  }
174
194
 
195
+ /**
196
+ * Constructor signature for ItemScope Manager classes
197
+ */
198
+ export type ItemscopeManager<T = any> = {
199
+ new (element: HTMLElement, initVals?: Partial<T>): T;
200
+ }
201
+
202
+ /**
203
+ * Configuration for ItemScope Manager registration
204
+ */
205
+ export interface ItemscopeManagerConfig<T = any> {
206
+ /**
207
+ * Manager class constructor
208
+ */
209
+ manager: ItemscopeManager<T>;
210
+
211
+ /**
212
+ * Optional lifecycle method keys
213
+ * - dispose: Method name to call when manager is disposed
214
+ * - resolved: Property/event name indicating manager is ready
215
+ */
216
+ lifecycleKeys?: {
217
+ dispose?: string | symbol;
218
+ resolved?: string | symbol;
219
+ };
220
+ }
221
+
222
+ /**
223
+ * Registry for ItemScope Manager configurations
224
+ * Extends EventTarget to support lazy registration via events
225
+ */
226
+ export declare class ItemscopeRegistry extends EventTarget {
227
+ define(name: string, config: ItemscopeManagerConfig): void;
228
+ get(name: string): ItemscopeManagerConfig | undefined;
229
+ whenDefined(name: string): Promise<void>;
230
+ }
231
+
175
232
  /**
176
233
  * Main assignGingerly function
177
234
  */
package/types/global.d.ts CHANGED
@@ -23,3 +23,7 @@ interface WeakMap<K extends object, V> {
23
23
  */
24
24
  getOrInsertComputed(key: K, insert: () => V): V;
25
25
  }
26
+
27
+ interface HTMLTemplateElement {
28
+ remoteContent?: Node;
29
+ }
package/waitForEvent.js CHANGED
@@ -3,17 +3,31 @@
3
3
  * @param et - The EventTarget to listen on
4
4
  * @param eventName - The event name to wait for (resolves the promise)
5
5
  * @param failureEventName - Optional event name that rejects the promise
6
+ * @param timeout - Optional timeout in milliseconds (rejects if exceeded)
6
7
  * @returns Promise that resolves with the event
7
8
  */
8
- export function waitForEvent(et, eventName, failureEventName) {
9
+ export function waitForEvent(et, eventName, failureEventName, timeout) {
9
10
  return new Promise((resolve, reject) => {
11
+ let timeoutId;
12
+ const cleanup = () => {
13
+ if (timeoutId !== undefined) {
14
+ clearTimeout(timeoutId);
15
+ }
16
+ };
10
17
  et.addEventListener(eventName, (e) => {
18
+ cleanup();
11
19
  resolve(e);
12
20
  }, { once: true });
13
21
  if (failureEventName !== undefined) {
14
22
  et.addEventListener(failureEventName, (e) => {
23
+ cleanup();
15
24
  reject(e);
16
25
  }, { once: true });
17
26
  }
27
+ if (timeout !== undefined && timeout > 0) {
28
+ timeoutId = setTimeout(() => {
29
+ reject(new Error(`Timeout waiting for event '${eventName}' after ${timeout}ms`));
30
+ }, timeout);
31
+ }
18
32
  });
19
33
  }
package/waitForEvent.ts CHANGED
@@ -3,17 +3,28 @@
3
3
  * @param et - The EventTarget to listen on
4
4
  * @param eventName - The event name to wait for (resolves the promise)
5
5
  * @param failureEventName - Optional event name that rejects the promise
6
+ * @param timeout - Optional timeout in milliseconds (rejects if exceeded)
6
7
  * @returns Promise that resolves with the event
7
8
  */
8
9
  export function waitForEvent<TEvent extends Event = Event>(
9
10
  et: EventTarget,
10
11
  eventName: string,
11
- failureEventName?: string
12
+ failureEventName?: string,
13
+ timeout?: number
12
14
  ): Promise<TEvent> {
13
15
  return new Promise((resolve, reject) => {
16
+ let timeoutId: number | undefined;
17
+
18
+ const cleanup = () => {
19
+ if (timeoutId !== undefined) {
20
+ clearTimeout(timeoutId);
21
+ }
22
+ };
23
+
14
24
  et.addEventListener(
15
25
  eventName,
16
26
  (e) => {
27
+ cleanup();
17
28
  resolve(e as TEvent);
18
29
  },
19
30
  { once: true }
@@ -23,10 +34,17 @@ export function waitForEvent<TEvent extends Event = Event>(
23
34
  et.addEventListener(
24
35
  failureEventName,
25
36
  (e) => {
37
+ cleanup();
26
38
  reject(e as TEvent);
27
39
  },
28
40
  { once: true }
29
41
  );
30
42
  }
43
+
44
+ if (timeout !== undefined && timeout > 0) {
45
+ timeoutId = setTimeout(() => {
46
+ reject(new Error(`Timeout waiting for event '${eventName}' after ${timeout}ms`));
47
+ }, timeout) as unknown as number;
48
+ }
31
49
  });
32
50
  }