mount-observer 0.1.9 → 0.1.11

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.
@@ -6,13 +6,12 @@
6
6
  import { MountObserver } from './MountObserver.js';
7
7
  import { getRootRegistryContainer } from './getRootRegistryContainer.js';
8
8
  import type { MountConfig, MountObserverOptions } from './types/mount-observer/types.js';
9
- import type { EnhancementConfig } from './types/assign-gingerly/types.js';
10
9
 
11
10
  declare global {
12
11
  interface Element {
13
12
  mount<T extends Element>(
14
13
  this: T,
15
- config: MountConfig | EnhancementConfig[],
14
+ config: MountConfig,
16
15
  options?: MountObserverOptions
17
16
  ): Promise<T>;
18
17
  }
@@ -28,7 +27,7 @@ declare global {
28
27
  Object.defineProperty(Element.prototype, 'mount', {
29
28
  value: async function <T extends Element>(
30
29
  this: T,
31
- config: MountConfig | EnhancementConfig[],
30
+ config: MountConfig,
32
31
  options: MountObserverOptions = {}
33
32
  ): Promise<T> {
34
33
  const scope = options.scope ?? 'registry';
@@ -0,0 +1,95 @@
1
+ import { EvtRt } from './EvtRt.js';
2
+ //import { buildCSSQuery } from 'assign-gingerly/buildCSSQuery.js';
3
+ import 'assign-gingerly/object-extension.js';
4
+ /**
5
+ * Handler for automatically enhancing mounted elements using assign-gingerly.
6
+ * Searches the first imported module for an export with a "spawn" property
7
+ * and uses element.enh.get() to spawn the enhancement.
8
+ */
9
+ export class EnhanceMountedElementHandler extends EvtRt {
10
+ async mount(mountedElement, MountConfig, context) {
11
+ // Check if modules are specified
12
+ if (!context.modules || context.modules.length === 0) {
13
+ throw new Error('Must specify an ES Module with import property');
14
+ }
15
+ const module = context.modules[0];
16
+ // Find registry item (object with spawn property)
17
+ const registryItem = await this._findRegistryItem(module, mountedElement);
18
+ if (!registryItem) {
19
+ throw new Error('No registry item found in module. Expected an export with a "spawn" property.');
20
+ }
21
+ // Validate spawn is a constructor
22
+ if (typeof registryItem.spawn !== 'function') {
23
+ throw new Error('Registry item "spawn" property must be a constructor function');
24
+ }
25
+ // Spawn the enhancement
26
+ this._spawnEnhancement(mountedElement, registryItem, context);
27
+ }
28
+ /**
29
+ * Spawn the enhancement using element.enh.get().
30
+ * Polyfills customElementRegistry if needed for browsers without scoped registry support.
31
+ */
32
+ _spawnEnhancement(element, registryItem, context) {
33
+ // Polyfill element.customElementRegistry if it doesn't exist (for browsers without scoped registries)
34
+ if (!element.customElementRegistry) {
35
+ Object.defineProperty(element, 'customElementRegistry', {
36
+ value: customElements,
37
+ writable: true,
38
+ enumerable: false,
39
+ configurable: true
40
+ });
41
+ }
42
+ // Use element.enh.get() to spawn the enhancement
43
+ const enh = element.enh;
44
+ if (!enh || typeof enh.get !== 'function') {
45
+ throw new Error('Element does not have enh.get() method. Make sure assign-gingerly/object-extension.js is loaded.');
46
+ }
47
+ enh.get(registryItem, context);
48
+ }
49
+ /**
50
+ * Find a registry item in the module exports.
51
+ * A registry item is an object with a "spawn" property.
52
+ * @param module - The imported module
53
+ * @returns The registry item or null if not found
54
+ */
55
+ async _findRegistryItem(module, el) {
56
+ // Check default export first
57
+ if (module.default && await this._isRegistryItem(module.default, el)) {
58
+ return module.default;
59
+ }
60
+ // Search all exports for a registry item
61
+ const exports = Object.values(module);
62
+ const registryItems = [];
63
+ for (const e of exports) {
64
+ const isRegistryItem = await this._isRegistryItem(e, el);
65
+ if (isRegistryItem)
66
+ registryItems.push(e);
67
+ }
68
+ if (registryItems.length === 0) {
69
+ return null;
70
+ }
71
+ if (registryItems.length > 1) {
72
+ throw new Error('More than one registry item found in module. Expected exactly one export with a "spawn" property.');
73
+ }
74
+ return registryItems[0];
75
+ }
76
+ /**
77
+ * Check if an export is a registry item (has a spawn property).
78
+ * @param exp - The export to check
79
+ * @returns True if the export is a registry item
80
+ */
81
+ async _isRegistryItem(exp, mountedElement) {
82
+ let test = exp !== null
83
+ && typeof exp === 'object'
84
+ && 'spawn' in exp
85
+ && typeof exp.spawn === 'function';
86
+ if (!test)
87
+ return false;
88
+ const emc = exp;
89
+ if (emc.withAttrs !== undefined) {
90
+ const cssQuery = (await import('assign-gingerly/buildCSSQuery.js')).buildCSSQuery(emc);
91
+ return mountedElement.matches(cssQuery);
92
+ }
93
+ return true;
94
+ }
95
+ }
@@ -0,0 +1,110 @@
1
+ import { EvtRt } from './EvtRt.js';
2
+ import {EnhancementConfig} from './types/assign-gingerly/types.js';
3
+ import { MountConfig, MountContext } from './types/mount-observer/types.js';
4
+ //import { buildCSSQuery } from 'assign-gingerly/buildCSSQuery.js';
5
+ import 'assign-gingerly/object-extension.js';
6
+
7
+ /**
8
+ * Handler for automatically enhancing mounted elements using assign-gingerly.
9
+ * Searches the first imported module for an export with a "spawn" property
10
+ * and uses element.enh.get() to spawn the enhancement.
11
+ */
12
+ export class EnhanceMountedElementHandler extends EvtRt {
13
+ async mount(mountedElement: Element, MountConfig: MountConfig, context: MountContext){
14
+ // Check if modules are specified
15
+ if (!context.modules || context.modules.length === 0) {
16
+ throw new Error('Must specify an ES Module with import property');
17
+ }
18
+
19
+ const module = context.modules[0];
20
+
21
+ // Find registry item (object with spawn property)
22
+ const registryItem = await this._findRegistryItem(module, mountedElement);
23
+
24
+ if (!registryItem) {
25
+ throw new Error('No registry item found in module. Expected an export with a "spawn" property.');
26
+ }
27
+
28
+ // Validate spawn is a constructor
29
+ if (typeof registryItem.spawn !== 'function') {
30
+ throw new Error('Registry item "spawn" property must be a constructor function');
31
+ }
32
+
33
+ // Spawn the enhancement
34
+ this._spawnEnhancement(mountedElement, registryItem, context);
35
+ }
36
+
37
+ /**
38
+ * Spawn the enhancement using element.enh.get().
39
+ * Polyfills customElementRegistry if needed for browsers without scoped registry support.
40
+ */
41
+ protected _spawnEnhancement(element: Element, registryItem: any, context: MountContext): void {
42
+ // Polyfill element.customElementRegistry if it doesn't exist (for browsers without scoped registries)
43
+ if (!(element as any).customElementRegistry) {
44
+ Object.defineProperty(element, 'customElementRegistry', {
45
+ value: customElements,
46
+ writable: true,
47
+ enumerable: false,
48
+ configurable: true
49
+ });
50
+ }
51
+
52
+ // Use element.enh.get() to spawn the enhancement
53
+ const enh = (element as any).enh;
54
+ if (!enh || typeof enh.get !== 'function') {
55
+ throw new Error('Element does not have enh.get() method. Make sure assign-gingerly/object-extension.js is loaded.');
56
+ }
57
+
58
+ enh.get(registryItem, context);
59
+ }
60
+
61
+ /**
62
+ * Find a registry item in the module exports.
63
+ * A registry item is an object with a "spawn" property.
64
+ * @param module - The imported module
65
+ * @returns The registry item or null if not found
66
+ */
67
+ protected async _findRegistryItem(module: any, el: Element): Promise<any | null> {
68
+ // Check default export first
69
+ if (module.default && await this._isRegistryItem(module.default, el)) {
70
+ return module.default;
71
+ }
72
+
73
+ // Search all exports for a registry item
74
+ const exports = Object.values(module);
75
+ const registryItems = [];
76
+ for(const e of exports){
77
+ const isRegistryItem = await this._isRegistryItem(e, el);
78
+ if(isRegistryItem) registryItems.push(e);
79
+ }
80
+
81
+ if (registryItems.length === 0) {
82
+ return null;
83
+ }
84
+
85
+ if (registryItems.length > 1) {
86
+ throw new Error('More than one registry item found in module. Expected exactly one export with a "spawn" property.');
87
+ }
88
+
89
+ return registryItems[0];
90
+ }
91
+
92
+ /**
93
+ * Check if an export is a registry item (has a spawn property).
94
+ * @param exp - The export to check
95
+ * @returns True if the export is a registry item
96
+ */
97
+ protected async _isRegistryItem(exp: any, mountedElement: Element): Promise<boolean> {
98
+ let test = exp !== null
99
+ && typeof exp === 'object'
100
+ && 'spawn' in exp
101
+ && typeof exp.spawn === 'function';
102
+ if(!test) return false;
103
+ const emc = exp as EnhancementConfig;
104
+ if(emc.withAttrs !== undefined){
105
+ const cssQuery = (await import('assign-gingerly/buildCSSQuery.js')).buildCSSQuery(emc);
106
+ return mountedElement.matches(cssQuery);
107
+ }
108
+ return true;
109
+ }
110
+ }
package/MountObserver.js CHANGED
@@ -35,21 +35,10 @@ export class MountObserver extends EventTarget {
35
35
  #notifierMountedElements = new WeakSet();
36
36
  constructor(config, options = {}) {
37
37
  super();
38
- // Handle array shorthand - convert EnhancementConfig[] to MountConfig
39
- let init;
40
- if (Array.isArray(config)) {
41
- init = {
42
- matching: '*', // Match all elements, let withAttrs do the filtering
43
- enhancementConfig: config
44
- };
45
- }
46
- else {
47
- init = config;
48
- }
49
- this.#init = init;
38
+ this.#init = config;
50
39
  this.#options = options;
51
40
  this.#abortController = new AbortController();
52
- const { assignOnMount, assignOnDismount, stageOnMount, do: doValue, reference, loadingEagerness, import: imp } = init;
41
+ const { assignOnMount, assignOnDismount, stageOnMount, do: doValue, reference, loadingEagerness, import: imp } = config;
53
42
  // Make a copy of assignOnMount config using structuredClone
54
43
  if (assignOnMount !== undefined) {
55
44
  this.#asgMtSource = structuredClone(assignOnMount);
@@ -157,11 +146,6 @@ export class MountObserver extends EventTarget {
157
146
  if (this.#init.loadingEagerness === 'eager' && this.#init.import && !this.#importsLoaded) {
158
147
  await this.#loadImports();
159
148
  }
160
- // Register enhancement configs if no imports (inline only)
161
- // If imports exist, registration happens in #loadImports after modules are loaded
162
- if (!this.#init.import && this.#init.enhancementConfig) {
163
- await this.#registerEnhancementConfigs();
164
- }
165
149
  // Process existing elements only if media matches
166
150
  if (this.#mediaMatches) {
167
151
  this.#processNode(rootNode);
@@ -234,94 +218,8 @@ export class MountObserver extends EventTarget {
234
218
  }
235
219
  }
236
220
  }
237
- // Register enhancement configs after imports are loaded
238
- await this.#registerEnhancementConfigs();
239
221
  this.dispatchEvent(new LoadEvent(this.#modules, this.#init));
240
222
  }
241
- async #registerEnhancementConfigs() {
242
- const rootNode = this.#rootNode?.deref();
243
- if (!rootNode || !(rootNode instanceof Element)) {
244
- return;
245
- }
246
- const registry = rootNode.customElementRegistry?.enhancementRegistry;
247
- if (!registry) {
248
- return;
249
- }
250
- const items = registry.getItems();
251
- // Collect all enhancement configs to register
252
- const configsToRegister = [];
253
- // First, add inline enhancementConfig(s)
254
- if (this.#init.enhancementConfig) {
255
- const inlineConfigs = arr(this.#init.enhancementConfig);
256
- configsToRegister.push(...inlineConfigs);
257
- }
258
- // Then, add referenced enhancementConfig(s) from imported modules
259
- if (this.#importsLoaded && this.#init.reference !== undefined) {
260
- const references = arr(this.#init.reference);
261
- for (const index of references) {
262
- const module = this.#modules[index];
263
- if (module && module.enhancementConfig !== undefined) {
264
- const referencedConfigs = arr(module.enhancementConfig);
265
- configsToRegister.push(...referencedConfigs);
266
- }
267
- }
268
- }
269
- // Register each config if not already registered (using reference equality)
270
- for (const config of configsToRegister) {
271
- if (!items.includes(config)) {
272
- registry.push(config);
273
- }
274
- }
275
- }
276
- /**
277
- * Resolves template variables in a string recursively
278
- * @param template - Template string with ${var} placeholders
279
- * @param patterns - The patterns object containing variable values
280
- * @returns Resolved string
281
- */
282
- #resolveAttrTemplate(template, patterns) {
283
- return template.replace(/\$\{(\w+)\}/g, (match, varName) => {
284
- const value = patterns[varName];
285
- if (value === undefined) {
286
- throw new Error(`Undefined template variable: ${varName}`);
287
- }
288
- if (typeof value === 'string') {
289
- // Recursively resolve
290
- return this.#resolveAttrTemplate(value, patterns);
291
- }
292
- return String(value);
293
- });
294
- }
295
- /**
296
- * Checks if element has attribute with enh- prefix handling
297
- * @param element - The element to check
298
- * @param attrName - The attribute name (without enh- prefix)
299
- * @param allowUnprefixed - Pattern that element tag name must match to allow unprefixed attributes
300
- * @returns true if element has the attribute
301
- */
302
- #hasAttributeWithEnhPrefix(element, attrName, allowUnprefixed) {
303
- const isCustomElement = element.tagName.includes('-');
304
- const isSVGElement = element instanceof SVGElement;
305
- // For custom elements and SVG - strict enh- requirement
306
- if (isCustomElement || isSVGElement) {
307
- if (element.hasAttribute(`enh-${attrName}`)) {
308
- return true;
309
- }
310
- // Only check unprefixed if tag name matches allowUnprefixed pattern
311
- if (allowUnprefixed) {
312
- const pattern = typeof allowUnprefixed === 'string'
313
- ? new RegExp(allowUnprefixed)
314
- : allowUnprefixed;
315
- const tagName = element.tagName.toLowerCase();
316
- if (pattern.test(tagName)) {
317
- return element.hasAttribute(attrName);
318
- }
319
- }
320
- return false;
321
- }
322
- // For built-in elements - enh- is alias (check both)
323
- return element.hasAttribute(`enh-${attrName}`) || element.hasAttribute(attrName);
324
- }
325
223
  #processNode(node) {
326
224
  // If it's an element node, check if it matches
327
225
  if (node.nodeType === Node.ELEMENT_NODE) {
@@ -382,65 +280,6 @@ export class MountObserver extends EventTarget {
382
280
  }
383
281
  }
384
282
  }
385
- //TODO: move to a separate file?
386
- // Check withAttrs condition if specified (attribute-based matching)
387
- // Check ALL enhancementConfigs (inline + referenced)
388
- const enhancementConfigs = [];
389
- // Add inline configs
390
- if (this.#init.enhancementConfig) {
391
- enhancementConfigs.push(...arr(this.#init.enhancementConfig));
392
- }
393
- // Add referenced configs if imports are loaded
394
- if (this.#importsLoaded && this.#init.reference !== undefined) {
395
- const references = arr(this.#init.reference);
396
- for (const index of references) {
397
- const module = this.#modules[index];
398
- if (module && module.enhancementConfig !== undefined) {
399
- enhancementConfigs.push(...arr(module.enhancementConfig));
400
- }
401
- }
402
- }
403
- // Check if ANY enhancementConfig has withAttrs - if so, element must match at least ONE
404
- let hasAnyWithAttrs = false;
405
- let matchesAnyWithAttrs = false;
406
- for (const config of enhancementConfigs) {
407
- if (!config.withAttrs) {
408
- continue; // Skip configs without withAttrs
409
- }
410
- hasAnyWithAttrs = true;
411
- const withAttrs = config.withAttrs;
412
- const allowUnprefixed = config.allowUnprefixed;
413
- // Collect all attribute names to check for this config
414
- const attrNames = [];
415
- for (const key in withAttrs) {
416
- // Skip base and underscore-prefixed config keys
417
- if (key === 'base' || key.startsWith('_')) {
418
- continue;
419
- }
420
- const value = withAttrs[key];
421
- if (typeof value === 'string') {
422
- // Resolve template string to get actual attribute name
423
- const attrName = this.#resolveAttrTemplate(value, withAttrs);
424
- attrNames.push(attrName);
425
- }
426
- }
427
- // Handle base attribute specially if present
428
- if ('base' in withAttrs && typeof withAttrs.base === 'string') {
429
- attrNames.push(withAttrs.base);
430
- }
431
- // Check if element has at least ONE of the specified attributes (OR logic within config)
432
- if (attrNames.length > 0) {
433
- const hasAnyAttribute = attrNames.some(attrName => this.#hasAttributeWithEnhPrefix(element, attrName, allowUnprefixed));
434
- if (hasAnyAttribute) {
435
- matchesAnyWithAttrs = true;
436
- break; // Found a matching config, no need to check others
437
- }
438
- }
439
- }
440
- // If any config has withAttrs but element doesn't match any of them, reject
441
- if (hasAnyWithAttrs && !matchesAnyWithAttrs) {
442
- return false;
443
- }
444
283
  // All conditions passed
445
284
  return true;
446
285
  }
@@ -479,32 +318,6 @@ export class MountObserver extends EventTarget {
479
318
  this.#assignTentatively(element, this.#stageMtSource, { reversal });
480
319
  this.#stageReversals.set(element, reversal);
481
320
  }
482
- // Spawn enhancements if configured
483
- // Process inline configs first, then referenced configs
484
- const enhancementConfigs = [];
485
- // Add inline configs
486
- if (this.#init.enhancementConfig) {
487
- enhancementConfigs.push(...arr(this.#init.enhancementConfig));
488
- }
489
- // Add referenced configs if imports are loaded
490
- if (this.#importsLoaded && this.#init.reference !== undefined) {
491
- const references = arr(this.#init.reference);
492
- for (const index of references) {
493
- const module = this.#modules[index];
494
- if (module && module.enhancementConfig !== undefined) {
495
- enhancementConfigs.push(...arr(module.enhancementConfig));
496
- }
497
- }
498
- }
499
- // Spawn each enhancement that has a spawn property
500
- if (enhancementConfigs.length > 0) {
501
- await import('assign-gingerly/object-extension.js');
502
- for (const config of enhancementConfigs) {
503
- if (config.spawn) {
504
- element.enh.get(config, context);
505
- }
506
- }
507
- }
508
321
  // Check if notifier exists BEFORE calling do callback
509
322
  const notifierExistedBeforeDo = this.#elementNotifiers.has(element);
510
323
  // Call do callback(s) - can be string, function, or array
package/MountObserver.ts CHANGED
@@ -22,7 +22,6 @@ import {
22
22
  } from './SharedMutationObserver.js';
23
23
  import { withScopePerimeter } from './withScopePerimeter.js';
24
24
  import type { assignTentatively as AssignTentativelyType } from 'assign-gingerly/assignTentatively.js';
25
- import type { BaseRegistry, EnhancementConfig } from './types/assign-gingerly/types.js';
26
25
 
27
26
  export class MountObserver extends EventTarget implements IMountObserver {
28
27
  // Static registry for registered handlers
@@ -58,28 +57,17 @@ export class MountObserver extends EventTarget implements IMountObserver {
58
57
  #elementNotifiers = new WeakMap<Element, EventTarget>();
59
58
  #notifierMountedElements = new WeakSet<Element>();
60
59
 
61
- constructor(config: MountConfig | EnhancementConfig[], options: MountObserverOptions = {}) {
60
+ constructor(config: MountConfig, options: MountObserverOptions = {}) {
62
61
  super();
63
62
 
64
- // Handle array shorthand - convert EnhancementConfig[] to MountConfig
65
- let init: MountConfig;
66
- if (Array.isArray(config)) {
67
- init = {
68
- matching: '*', // Match all elements, let withAttrs do the filtering
69
- enhancementConfig: config
70
- };
71
- } else {
72
- init = config;
73
- }
74
-
75
- this.#init = init;
63
+ this.#init = config;
76
64
  this.#options = options;
77
65
  this.#abortController = new AbortController();
78
66
 
79
67
  const {
80
68
  assignOnMount, assignOnDismount, stageOnMount, do: doValue, reference, loadingEagerness,
81
69
  import: imp
82
- } = init;
70
+ } = config;
83
71
  // Make a copy of assignOnMount config using structuredClone
84
72
  if (assignOnMount !== undefined) {
85
73
  this.#asgMtSource = structuredClone(assignOnMount);
@@ -218,12 +206,6 @@ export class MountObserver extends EventTarget implements IMountObserver {
218
206
  await this.#loadImports();
219
207
  }
220
208
 
221
- // Register enhancement configs if no imports (inline only)
222
- // If imports exist, registration happens in #loadImports after modules are loaded
223
- if (!this.#init.import && this.#init.enhancementConfig) {
224
- await this.#registerEnhancementConfigs();
225
- }
226
-
227
209
  // Process existing elements only if media matches
228
210
  if (this.#mediaMatches) {
229
211
  this.#processNode(rootNode);
@@ -310,113 +292,9 @@ export class MountObserver extends EventTarget implements IMountObserver {
310
292
  }
311
293
  }
312
294
 
313
- // Register enhancement configs after imports are loaded
314
- await this.#registerEnhancementConfigs();
315
-
316
295
  this.dispatchEvent(new LoadEvent(this.#modules, this.#init));
317
296
  }
318
297
 
319
- async #registerEnhancementConfigs(): Promise<void> {
320
- const rootNode = this.#rootNode?.deref();
321
- if (!rootNode || !(rootNode instanceof Element)) {
322
- return;
323
- }
324
-
325
- const registry = (rootNode as any).customElementRegistry?.enhancementRegistry as BaseRegistry | undefined;
326
- if (!registry) {
327
- return;
328
- }
329
-
330
- const items = registry.getItems();
331
-
332
- // Collect all enhancement configs to register
333
- const configsToRegister: EnhancementConfig[] = [];
334
-
335
- // First, add inline enhancementConfig(s)
336
- if (this.#init.enhancementConfig) {
337
- const inlineConfigs = arr(this.#init.enhancementConfig);
338
- configsToRegister.push(...inlineConfigs);
339
- }
340
-
341
- // Then, add referenced enhancementConfig(s) from imported modules
342
- if (this.#importsLoaded && this.#init.reference !== undefined) {
343
- const references = arr(this.#init.reference);
344
-
345
- for (const index of references) {
346
- const module = this.#modules[index];
347
- if (module && module.enhancementConfig !== undefined) {
348
- const referencedConfigs = arr(module.enhancementConfig);
349
- configsToRegister.push(...referencedConfigs);
350
- }
351
- }
352
- }
353
-
354
- // Register each config if not already registered (using reference equality)
355
- for (const config of configsToRegister) {
356
- if (!items.includes(config)) {
357
- registry.push(config);
358
- }
359
- }
360
- }
361
-
362
- /**
363
- * Resolves template variables in a string recursively
364
- * @param template - Template string with ${var} placeholders
365
- * @param patterns - The patterns object containing variable values
366
- * @returns Resolved string
367
- */
368
- #resolveAttrTemplate(template: string, patterns: Record<string, any>): string {
369
- return template.replace(/\$\{(\w+)\}/g, (match, varName) => {
370
- const value = patterns[varName];
371
- if (value === undefined) {
372
- throw new Error(`Undefined template variable: ${varName}`);
373
- }
374
- if (typeof value === 'string') {
375
- // Recursively resolve
376
- return this.#resolveAttrTemplate(value, patterns);
377
- }
378
- return String(value);
379
- });
380
- }
381
-
382
- /**
383
- * Checks if element has attribute with enh- prefix handling
384
- * @param element - The element to check
385
- * @param attrName - The attribute name (without enh- prefix)
386
- * @param allowUnprefixed - Pattern that element tag name must match to allow unprefixed attributes
387
- * @returns true if element has the attribute
388
- */
389
- #hasAttributeWithEnhPrefix(
390
- element: Element,
391
- attrName: string,
392
- allowUnprefixed?: string | RegExp
393
- ): boolean {
394
- const isCustomElement = element.tagName.includes('-');
395
- const isSVGElement = element instanceof SVGElement;
396
-
397
- // For custom elements and SVG - strict enh- requirement
398
- if (isCustomElement || isSVGElement) {
399
- if (element.hasAttribute(`enh-${attrName}`)) {
400
- return true;
401
- }
402
-
403
- // Only check unprefixed if tag name matches allowUnprefixed pattern
404
- if (allowUnprefixed) {
405
- const pattern = typeof allowUnprefixed === 'string'
406
- ? new RegExp(allowUnprefixed)
407
- : allowUnprefixed;
408
- const tagName = element.tagName.toLowerCase();
409
- if (pattern.test(tagName)) {
410
- return element.hasAttribute(attrName);
411
- }
412
- }
413
- return false;
414
- }
415
-
416
- // For built-in elements - enh- is alias (check both)
417
- return element.hasAttribute(`enh-${attrName}`) || element.hasAttribute(attrName);
418
- }
419
-
420
298
  #processNode(node: Node): void {
421
299
  // If it's an element node, check if it matches
422
300
  if (node.nodeType === Node.ELEMENT_NODE) {
@@ -490,80 +368,7 @@ export class MountObserver extends EventTarget implements IMountObserver {
490
368
  }
491
369
  }
492
370
  }
493
- //TODO: move to a separate file?
494
- // Check withAttrs condition if specified (attribute-based matching)
495
- // Check ALL enhancementConfigs (inline + referenced)
496
- const enhancementConfigs: EnhancementConfig[] = [];
497
-
498
- // Add inline configs
499
- if (this.#init.enhancementConfig) {
500
- enhancementConfigs.push(...arr(this.#init.enhancementConfig));
501
- }
502
-
503
- // Add referenced configs if imports are loaded
504
- if (this.#importsLoaded && this.#init.reference !== undefined) {
505
- const references = arr(this.#init.reference);
506
- for (const index of references) {
507
- const module = this.#modules[index];
508
- if (module && module.enhancementConfig !== undefined) {
509
- enhancementConfigs.push(...arr(module.enhancementConfig));
510
- }
511
- }
512
- }
513
-
514
- // Check if ANY enhancementConfig has withAttrs - if so, element must match at least ONE
515
- let hasAnyWithAttrs = false;
516
- let matchesAnyWithAttrs = false;
517
-
518
- for (const config of enhancementConfigs) {
519
- if (!config.withAttrs) {
520
- continue; // Skip configs without withAttrs
521
- }
522
-
523
- hasAnyWithAttrs = true;
524
- const withAttrs = config.withAttrs;
525
- const allowUnprefixed = config.allowUnprefixed;
526
-
527
- // Collect all attribute names to check for this config
528
- const attrNames: string[] = [];
529
-
530
- for (const key in withAttrs) {
531
- // Skip base and underscore-prefixed config keys
532
- if (key === 'base' || key.startsWith('_')) {
533
- continue;
534
- }
535
-
536
- const value = withAttrs[key];
537
- if (typeof value === 'string') {
538
- // Resolve template string to get actual attribute name
539
- const attrName = this.#resolveAttrTemplate(value, withAttrs);
540
- attrNames.push(attrName);
541
- }
542
- }
543
-
544
- // Handle base attribute specially if present
545
- if ('base' in withAttrs && typeof withAttrs.base === 'string') {
546
- attrNames.push(withAttrs.base);
547
- }
548
-
549
- // Check if element has at least ONE of the specified attributes (OR logic within config)
550
- if (attrNames.length > 0) {
551
- const hasAnyAttribute = attrNames.some(attrName =>
552
- this.#hasAttributeWithEnhPrefix(element, attrName, allowUnprefixed)
553
- );
554
-
555
- if (hasAnyAttribute) {
556
- matchesAnyWithAttrs = true;
557
- break; // Found a matching config, no need to check others
558
- }
559
- }
560
- }
561
-
562
- // If any config has withAttrs but element doesn't match any of them, reject
563
- if (hasAnyWithAttrs && !matchesAnyWithAttrs) {
564
- return false;
565
- }
566
-
371
+
567
372
  // All conditions passed
568
373
  return true;
569
374
  }
@@ -611,36 +416,6 @@ export class MountObserver extends EventTarget implements IMountObserver {
611
416
  this.#stageReversals.set(element, reversal);
612
417
  }
613
418
 
614
- // Spawn enhancements if configured
615
- // Process inline configs first, then referenced configs
616
- const enhancementConfigs: EnhancementConfig[] = [];
617
-
618
- // Add inline configs
619
- if (this.#init.enhancementConfig) {
620
- enhancementConfigs.push(...arr(this.#init.enhancementConfig));
621
- }
622
-
623
- // Add referenced configs if imports are loaded
624
- if (this.#importsLoaded && this.#init.reference !== undefined) {
625
- const references = arr(this.#init.reference);
626
- for (const index of references) {
627
- const module = this.#modules[index];
628
- if (module && module.enhancementConfig !== undefined) {
629
- enhancementConfigs.push(...arr(module.enhancementConfig));
630
- }
631
- }
632
- }
633
-
634
- // Spawn each enhancement that has a spawn property
635
- if (enhancementConfigs.length > 0) {
636
- await import('assign-gingerly/object-extension.js');
637
- for (const config of enhancementConfigs) {
638
- if (config.spawn) {
639
- (element as any).enh.get(config, context);
640
- }
641
- }
642
- }
643
-
644
419
  // Check if notifier exists BEFORE calling do callback
645
420
  const notifierExistedBeforeDo = this.#elementNotifiers.has(element);
646
421
 
package/README.md CHANGED
@@ -25,9 +25,7 @@ The following features have been implemented and tested:
25
25
  - ✅ **assignOnMount**: Property assignment when elements mount
26
26
  - ✅ **assignOnDismount**: Property assignment when elements dismount
27
27
  - ✅ **stageOnMount**: Reversible property assignment (auto-restores on dismount)
28
- - ✅ **spawn**: Automatic enhancement spawning via assign-gingerly integration
29
28
  - ✅ **do callbacks**: Mount/dismount/disconnect/reconnect lifecycle hooks
30
- - ✅ **Array argument shorthand**: Pass EnhancementConfig[] directly to constructor
31
29
  - ✅ **Element mount extension**: element.mount() method for scoped registry observation
32
30
  - ✅ **Shared MutationObserver**: Efficient observer sharing across instances
33
31
  - ✅ **Code splitting**: Conditional features loaded on-demand
@@ -44,9 +42,9 @@ The following features have been implemented and tested:
44
42
 
45
43
  Author: Bruce B. Anderson (with valuable feedback from @doeixd)
46
44
 
47
- Issues / PRs / polyfill: [mount-observer](https://github.com/bahrus/mount-observer)
45
+ Issues / PRs / polyfill: [mount-observer](https://github.com/bahrus/mount-observer/tree/v2)
48
46
 
49
- Last Update: Aug 7, 2025
47
+ Last Update: Feb 23, 2026
50
48
 
51
49
  ## Benefits of this API
52
50
 
@@ -102,7 +100,7 @@ The extra flexibility this new primitive would provide could be quite useful to
102
100
 
103
101
  Before getting into the weeds, let's demonstrate the two most prominent use cases:
104
102
 
105
- ### Use Case 1: Custom Attribute Enhancement
103
+ ### Use Case 1: Custom Attribute Enhancement [TODO]: out of date
106
104
 
107
105
  ```html
108
106
  <body>
@@ -110,6 +108,8 @@ Before getting into the weeds, let's demonstrate the two most prominent use case
110
108
 
111
109
  <script type=module>
112
110
  import 'mount-observer/ElementMountExtension.js';
111
+
112
+
113
113
  document.body.mount([{
114
114
  withAttrs:{base: 'log-to-console'},
115
115
  spawn: function(el){
@@ -165,6 +165,56 @@ element.mount({
165
165
  });
166
166
  ```
167
167
 
168
+ ## Enhancing Elements with assign-gingerly
169
+
170
+ The `builtIns.enhanceMountedElement` handler automatically enhances mounted elements using the [assign-gingerly](https://www.npmjs.com/package/assign-gingerly) enhancement system. This allows you to attach behavior and state to elements without subclassing.
171
+
172
+ ```JavaScript
173
+ // MyEnhancement.js
174
+ class ButtonEnhancement {
175
+ constructor(element, ctx, initVals) {
176
+ this.element = element;
177
+ this.ctx = ctx;
178
+ this.clickCount = 0;
179
+
180
+ element.addEventListener('click', () => {
181
+ this.clickCount++;
182
+ element.setAttribute('data-clicks', this.clickCount);
183
+ });
184
+ }
185
+ }
186
+
187
+ export default {
188
+ spawn: ButtonEnhancement,
189
+ enhKey: 'buttonEnh'
190
+ };
191
+
192
+ // main.js
193
+ import 'mount-observer/ElementMountExtension.js';
194
+
195
+ document.mount({
196
+ matching: '.enhance-me',
197
+ import: './MyEnhancement.js',
198
+ do: 'builtIns.enhanceMountedElement'
199
+ });
200
+
201
+ // HTML
202
+ <button class="enhance-me">Click me</button>
203
+
204
+ // Access the enhancement
205
+ const button = document.querySelector('.enhance-me');
206
+ console.log(button.enh.buttonEnh.clickCount); // 0
207
+ button.click();
208
+ console.log(button.enh.buttonEnh.clickCount); // 1
209
+ ```
210
+
211
+ The handler:
212
+ 1. Searches the imported module for an export with a `spawn` property (the enhancement class)
213
+ 2. Calls `element.enh.get(registryItem, context)` to spawn the enhancement
214
+ 3. Stores the enhancement instance on `element.enh[enhKey]` if an `enhKey` is provided
215
+
216
+ This works with browsers that don't support scoped custom element registries by polyfilling the `customElementRegistry` property on elements.
217
+
168
218
 
169
219
  # Thorough Exposition Begins Here
170
220
 
@@ -414,51 +464,6 @@ This optimization ensures that with lazy loading, elements that don't match the
414
464
 
415
465
  [Implemented as [Requirement12](requirements/Done/Requirement12.md)]
416
466
 
417
- ## Simplified API: Array Argument Shorthand
418
-
419
- For simple use cases where you just want to enhance elements based on attributes without needing the full `MountConfig` object, you can pass an array of [EnhancementConfig` objects](https://github.com/bahrus/assign-gingerly) directly to the constructor:
420
-
421
- ```JavaScript
422
- import { MountObserver } from 'mount-observer';
423
-
424
- // Instead of wrapping in MountConfig:
425
- // const observer = new MountObserver({
426
- // enhancementConfig: [config1, config2]
427
- // });
428
-
429
- // You can use the shorthand:
430
- const observer = new MountObserver([
431
- {
432
- spawn: Enhancement1,
433
- enhKey: 'enh1',
434
- withAttrs: {
435
- base: 'data-',
436
- action: '${base}action'
437
- }
438
- },
439
- {
440
- spawn: Enhancement2,
441
- enhKey: 'enh2',
442
- withAttrs: {
443
- base: 'data-',
444
- theme: '${base}theme'
445
- }
446
- }
447
- ]);
448
-
449
- await observer.observe(document.body);
450
- ```
451
-
452
- When you pass an array directly:
453
- - The array is automatically converted to `{ matching: '*', enhancementConfig: [...] }`
454
- - All elements are considered (matching: '*'), with filtering done by `withAttrs` in each config
455
- - This is perfect for attribute-based progressive enhancement scenarios
456
- - You can still use all `EnhancementConfig` features like `spawn`, `withAttrs`, `canSpawn`, etc.
457
-
458
- This "lite" API makes it easier to do the right thing by reducing boilerplate for common enhancement patterns.
459
-
460
- [Implemented as ArrayArgument requirement](requirements/Done/ArrayArgument.md).
461
-
462
467
  ## Element Mount Extension
463
468
 
464
469
  For even more convenience, you can use the `element.mount()` method to observe elements within their scoped custom element registry context. This is particularly useful with scoped custom element registries (Chrome 146+, latest WebKit/Safari).
@@ -473,18 +478,6 @@ await document.body.mount({
473
478
  element.classList.add('enhanced');
474
479
  }
475
480
  });
476
-
477
- // Or use the array shorthand directly
478
- await document.body.mount([
479
- {
480
- spawn: ButtonEnhancement,
481
- enhKey: 'btn-enh',
482
- withAttrs: {
483
- base: 'data-',
484
- action: '${base}action'
485
- }
486
- }
487
- ]);
488
481
  ```
489
482
 
490
483
  The `mount()` method:
@@ -492,7 +485,6 @@ The `mount()` method:
492
485
  - Creates a `MountObserver` with the provided config
493
486
  - Observes the determined scope
494
487
  - Returns the element for chaining (as a Promise)
495
- - Accepts both `MountConfig` objects and `EnhancementConfig[]` arrays
496
488
 
497
489
  Scope options (via `options.scope`):
498
490
  - `'registry'` (default): Observes the root registry container (highest element with same customElementRegistry)
@@ -1097,231 +1089,6 @@ button.classList.remove('loading'); // Dismount: disabled restored to true (the
1097
1089
 
1098
1090
  [Implemented as [Requirement13](requirements/Done/Requirement13.md)]
1099
1091
 
1100
- ## Spawning enhancements with assign-gingerly integration
1101
-
1102
- MountObserver integrates with the [assign-gingerly](https://github.com/bahrus/assign-gingerly) enhancement system to automatically spawn enhancement instances when elements mount. This provides a powerful way to attach behaviors and functionality to elements using the enhancement registry pattern.
1103
-
1104
- ### What is spawn?
1105
-
1106
- In the assign-gingerly enhancement system, `spawn` is a class constructor (not a boolean) that defines what enhancement instance to create. The `enhancementConfig` object is a registry item that gets registered with the element's enhancement registry.
1107
-
1108
- ### Basic spawn usage
1109
-
1110
- ```JavaScript
1111
- // Define an enhancement class
1112
- class ButtonEnhancement {
1113
- constructor(element, ctx, initVals) {
1114
- this.element = element;
1115
- this.onClick = this.onClick.bind(this);
1116
- element.addEventListener('click', this.onClick);
1117
- }
1118
-
1119
- onClick(e) {
1120
- console.log('Button clicked!', this.element);
1121
- }
1122
- }
1123
-
1124
- const observer = new MountObserver({
1125
- matching: 'button[data-enhance]',
1126
- enhancementConfig: {
1127
- spawn: ButtonEnhancement, // The class constructor
1128
- enhKey: 'buttonEnh'
1129
- }
1130
- });
1131
- observer.observe(document);
1132
- ```
1133
-
1134
- When an element mounts, if `enhancementConfig.spawn` is defined, MountObserver will:
1135
- 1. Import the assign-gingerly object extension module
1136
- 2. Call `element.enh.get(enhancementConfig, mountContext)` to spawn the enhancement
1137
- 3. Pass the mount context as the second parameter, making it available to the enhancement constructor
1138
-
1139
- ### How spawn works
1140
-
1141
- The spawn feature leverages the `element.enh` property from assign-gingerly, which provides access to the enhancement registry. The `enhancementConfig` is a registry item with this structure:
1142
-
1143
- ```TypeScript
1144
- interface IBaseRegistryItem<T> {
1145
- spawn: {new(element?: Element, ctx?: SpawnContext<T>, initVals?: Partial<T>): T};
1146
- symlinks?: {[key: symbol]: keyof T};
1147
- enhKey?: string;
1148
- withAttrs?: AttrPatterns<T>;
1149
- canSpawn?: (obj: any, ctx?: SpawnContext<T>) => boolean;
1150
- }
1151
- ```
1152
-
1153
- When you call `element.enh.get(enhancementConfig, mountContext)`:
1154
- - If an enhancement matching the config already exists for this element, it returns the existing instance
1155
- - If no enhancement exists, it creates a new one by calling `new enhancementConfig.spawn(element, ctx, initVals)`
1156
- - The enhancement is registered in the element's custom element registry's enhancement registry
1157
- - The mount context is passed to the enhancement constructor via `ctx.mountCtx`
1158
-
1159
- ### Spawn happens once per element
1160
-
1161
- The spawn operation only occurs the first time an element mounts. If the element is removed and re-added to the DOM:
1162
- - The spawn code won't run again (element already in `#processedDoForElement`)
1163
- - The existing enhancement instance persists with the element
1164
- - This ensures enhancements are singletons per element instance
1165
-
1166
- ### Mount context in enhancements
1167
-
1168
- The mount context passed to spawned enhancements includes:
1169
-
1170
- ```TypeScript
1171
- interface MountContext {
1172
- modules: any[]; // Imported modules (if import was specified)
1173
- observer: MountObserver; // The MountObserver instance
1174
- rootNode: Node; // The observed root node
1175
- MountConfig: MountConfig; // The full configuration object
1176
- }
1177
- ```
1178
-
1179
- This allows enhancements to access imported dependencies, communicate with the observer, and understand their mounting context.
1180
-
1181
- ### Combining spawn with other features
1182
-
1183
- Spawn works seamlessly with other MountObserver features:
1184
-
1185
- ```JavaScript
1186
- class WidgetEnhancement {
1187
- constructor(element, ctx, initVals) {
1188
- this.element = element;
1189
- this.modules = ctx.mountCtx?.modules || [];
1190
- console.log('Widget enhanced with', this.modules);
1191
- }
1192
-
1193
- theme = 'light';
1194
- mode = 'default';
1195
- }
1196
-
1197
- const observer = new MountObserver({
1198
- matching: 'my-widget',
1199
- import: './widget-helpers.js',
1200
- assignOnMount: {
1201
- dataset: { initialized: 'true' }
1202
- },
1203
- stageOnMount: {
1204
- disabled: true // Temporarily disable during setup
1205
- },
1206
- enhancementConfig: {
1207
- spawn: WidgetEnhancement,
1208
- enhKey: 'widget',
1209
- withAttrs: {
1210
- base: 'data-config',
1211
- theme: '${base}-theme',
1212
- mode: '${base}-mode'
1213
- }
1214
- },
1215
- do: (element, ctx) => {
1216
- console.log('Additional setup after spawn');
1217
- }
1218
- });
1219
- ```
1220
-
1221
- **Execution order on mount:**
1222
- 1. `assignOnMount` properties applied
1223
- 2. `stageOnMount` properties applied
1224
- 3. **Spawn enhancement** (if configured)
1225
- 4. `do` callbacks executed
1226
- 5. Mount event dispatched
1227
-
1228
- ### Attribute-based enhancement spawning
1229
-
1230
- When combined with `withAttrs`, spawn only occurs for elements that have the specified attributes:
1231
-
1232
- ```JavaScript
1233
- class ActionEnhancement {
1234
- constructor(element, ctx, initVals) {
1235
- this.element = element;
1236
- this.onClick = this.onClick.bind(this);
1237
- element.addEventListener('click', this.onClick);
1238
- }
1239
-
1240
- onClick(e) {
1241
- const action = this.element.dataset.action;
1242
- console.log(`Action: ${action}`);
1243
- }
1244
- }
1245
-
1246
- const observer = new MountObserver({
1247
- matching: 'button',
1248
- enhancementConfig: {
1249
- spawn: ActionEnhancement,
1250
- enhKey: 'action',
1251
- withAttrs: {
1252
- base: 'data-action'
1253
- }
1254
- }
1255
- });
1256
- ```
1257
-
1258
- Only buttons with a `data-action` attribute (or `enh-data-action` for custom elements) will have the enhancement spawned.
1259
-
1260
- ### Guard conditions with canSpawn
1261
-
1262
- The `canSpawn` property in `enhancementConfig` provides conditional spawning:
1263
-
1264
- ```JavaScript
1265
- class InputEnhancement {
1266
- constructor(element, ctx, initVals) {
1267
- this.element = element;
1268
- this.onInput = this.onInput.bind(this);
1269
- element.addEventListener('input', this.onInput);
1270
- }
1271
-
1272
- onInput(e) {
1273
- console.log('Input changed:', e.target.value);
1274
- }
1275
-
1276
- static canSpawn(element) {
1277
- // Only spawn for inputs that aren't readonly
1278
- return !element.readOnly;
1279
- }
1280
- }
1281
-
1282
- const observer = new MountObserver({
1283
- matching: 'input',
1284
- enhancementConfig: {
1285
- spawn: InputEnhancement,
1286
- enhKey: 'inputEnh'
1287
- }
1288
- });
1289
- ```
1290
-
1291
- If `canSpawn` returns `false`, the enhancement won't be spawned for that element.
1292
-
1293
- ### Browser compatibility
1294
-
1295
- The spawn feature requires:
1296
- - `Element.prototype.customElementRegistry` (Chrome 146+)
1297
- - `customElementRegistry.enhancementRegistry` (Chrome 146+)
1298
-
1299
- For older browsers, you'll need to polyfill these features or the spawn functionality won't work. The test suite includes a polyfill example:
1300
-
1301
- ```JavaScript
1302
- // Polyfill for browsers without customElementRegistry
1303
- if (!Element.prototype.hasOwnProperty('customElementRegistry')) {
1304
- Object.defineProperty(Element.prototype, 'customElementRegistry', {
1305
- get() {
1306
- if (!this._customElementRegistry) {
1307
- this._customElementRegistry = {
1308
- enhancementRegistry: new BaseRegistry()
1309
- };
1310
- }
1311
- return this._customElementRegistry;
1312
- }
1313
- });
1314
- }
1315
- ```
1316
-
1317
- ### Performance considerations
1318
-
1319
- - The assign-gingerly object extension module is only loaded when `spawn` is configured
1320
- - Enhancements are created once per element (singleton pattern)
1321
- - The enhancement registry uses weak references to allow garbage collection
1322
-
1323
- [Implemented as [SpawnOnMount](requirements/Done/SpawnOnMount.md)]
1324
-
1325
1092
  ## Emitting events from mounted elements
1326
1093
 
1327
1094
  MountObserver can automatically dispatch custom events from elements when they mount. This is useful for:
package/index.js CHANGED
@@ -5,11 +5,14 @@ export { emitMountedElementEvents } from './emitEvents.js';
5
5
  export { arr } from './arr.js';
6
6
  export { EvtRt } from './EvtRt.js';
7
7
  export { DefineCustomElementHandler } from './DefineCustomElementHandler.js';
8
+ export { EnhanceMountedElementHandler } from './EnhanceMountedElementHandler.js';
8
9
  export { mountEventName, dismountEventName, disconnectEventName, loadEventName, mediamatchEventName, mediaunmatchEventName } from './Events.js';
9
10
  // Register built-in handlers
10
11
  import { MountObserver } from './MountObserver.js';
11
12
  import { EvtRt } from './EvtRt.js';
12
13
  import { DefineCustomElementHandler, DefineScopedCustomElementHandler } from './DefineCustomElementHandler.js';
14
+ import { EnhanceMountedElementHandler } from './EnhanceMountedElementHandler.js';
13
15
  MountObserver.define('builtIns.logToConsole', EvtRt);
14
16
  MountObserver.define('builtIns.defineCustomElement', DefineCustomElementHandler);
15
17
  MountObserver.define('buildIns.defineScopedCustomElement', DefineScopedCustomElementHandler);
18
+ MountObserver.define('builtIns.enhanceMountedElement', EnhanceMountedElementHandler);
package/index.ts CHANGED
@@ -5,6 +5,7 @@ export { emitMountedElementEvents } from './emitEvents.js';
5
5
  export { arr } from './arr.js';
6
6
  export { EvtRt } from './EvtRt.js';
7
7
  export { DefineCustomElementHandler } from './DefineCustomElementHandler.js';
8
+ export { EnhanceMountedElementHandler } from './EnhanceMountedElementHandler.js';
8
9
  export type {
9
10
  MountConfig,
10
11
  MountObserverOptions,
@@ -28,7 +29,9 @@ export {
28
29
  import { MountObserver } from './MountObserver.js';
29
30
  import { EvtRt } from './EvtRt.js';
30
31
  import { DefineCustomElementHandler, DefineScopedCustomElementHandler } from './DefineCustomElementHandler.js';
32
+ import { EnhanceMountedElementHandler } from './EnhanceMountedElementHandler.js';
31
33
 
32
34
  MountObserver.define('builtIns.logToConsole', EvtRt);
33
35
  MountObserver.define('builtIns.defineCustomElement', DefineCustomElementHandler);
34
36
  MountObserver.define('buildIns.defineScopedCustomElement', DefineScopedCustomElementHandler);
37
+ MountObserver.define('builtIns.enhanceMountedElement', EnhanceMountedElementHandler);
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "mount-observer",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Observe and act on css matches.",
5
5
  "main": "MountObserver.js",
6
6
  "module": "MountObserver.js",
7
7
  "dependencies": {
8
- "assign-gingerly": "0.0.14"
8
+ "assign-gingerly": "0.0.18"
9
9
  },
10
10
  "devDependencies": {
11
11
  "@playwright/test": "1.58.2",
@@ -39,6 +39,10 @@
39
39
  "./DefineCustomElementHandler.js": {
40
40
  "default": "./DefineCustomElementHandler.js",
41
41
  "types": "./DefineCustomElementHandler.ts"
42
+ },
43
+ "./EnhanceMountedElementHandler.js": {
44
+ "default": "./EnhanceMountedElementHandler.js",
45
+ "types": "./EnhanceMountedElementHandler.ts"
42
46
  }
43
47
  },
44
48
  "files": [