mount-observer 0.1.14 → 0.1.16

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.
Files changed (40) hide show
  1. package/ElementMountExtension.js +5 -2
  2. package/ElementMountExtension.ts +7 -2
  3. package/MountObserver.js +3 -0
  4. package/MountObserver.ts +3 -0
  5. package/RegistryMountCoordinator.js +5 -5
  6. package/RegistryMountCoordinator.ts +8 -6
  7. package/{DefineCustomElementHandler.js → handlers/DefineCustomElement.js} +103 -99
  8. package/handlers/DefineCustomElement.ts +123 -0
  9. package/{EnhanceMountedElementHandler.js → handlers/EnhanceMountedElement.js} +109 -96
  10. package/handlers/EnhanceMountedElement.ts +126 -0
  11. package/handlers/Events.js +110 -0
  12. package/handlers/EvtRt.js +59 -0
  13. package/handlers/GenIds.js +37 -0
  14. package/handlers/GenIds.ts +45 -0
  15. package/handlers/HTMLInclude.js +393 -0
  16. package/handlers/HTMLInclude.ts +453 -0
  17. package/handlers/HoistTemplate.js +77 -0
  18. package/handlers/HoistTemplate.ts +89 -0
  19. package/handlers/MountObserver.js +941 -0
  20. package/handlers/MountObserverScript.js +78 -0
  21. package/handlers/MountObserverScript.ts +89 -0
  22. package/handlers/ScriptExport.js +83 -0
  23. package/handlers/ScriptExport.ts +97 -0
  24. package/handlers/SharedMutationObserver.js +78 -0
  25. package/handlers/arr.js +16 -0
  26. package/handlers/connectionMonitor.js +122 -0
  27. package/handlers/elementIntersection.js +73 -0
  28. package/handlers/emitEvents.js +187 -0
  29. package/handlers/getRegistryRoot.js +52 -0
  30. package/handlers/loadImports.js +129 -0
  31. package/handlers/mediaQuery.js +90 -0
  32. package/handlers/rootSizeObserver.js +131 -0
  33. package/handlers/upShadowSearch.js +70 -0
  34. package/handlers/withScopePerimeter.js +22 -0
  35. package/package.json +12 -2
  36. package/types/assign-gingerly/types.d.ts +244 -0
  37. package/types/be-a-beacon/types.d.ts +3 -0
  38. package/types/global.d.ts +29 -0
  39. package/types/id-generation/types.d.ts +26 -0
  40. package/types/mount-observer/types.d.ts +332 -0
@@ -71,6 +71,9 @@ Object.defineProperty(Node.prototype, 'mount', {
71
71
  if (!(this instanceof Element)) {
72
72
  throw new Error('mount() can only be called on Element, ShadowRoot, or Document');
73
73
  }
74
+ if (this instanceof HTMLScriptElement) {
75
+ options.mose = new WeakRef(this);
76
+ }
74
77
  const scope = options.scope ?? 'registry'; // NEW DEFAULT
75
78
  let thingToObserve;
76
79
  if (scope === 'registry') {
@@ -84,7 +87,7 @@ Object.defineProperty(Node.prototype, 'mount', {
84
87
  const registry = this.customElementRegistry;
85
88
  // Register with coordinator if registry exists
86
89
  if (registry) {
87
- await getOrInsertObserverEntry(registry, config, thingToObserve);
90
+ await getOrInsertObserverEntry(registry, config, thingToObserve, options);
88
91
  }
89
92
  else {
90
93
  // No registry, just create a standalone observer
@@ -146,7 +149,7 @@ Object.defineProperty(Element.prototype, 'mountScope', {
146
149
  const configs = registry.mountConfigRegistry.items;
147
150
  // For each config, ensure an observer exists for this registry root
148
151
  for (const config of configs) {
149
- await getOrInsertObserverEntry(registry, config, registryRoot);
152
+ await getOrInsertObserverEntry(registry, config, registryRoot, {});
150
153
  }
151
154
  },
152
155
  writable: true,
@@ -95,6 +95,7 @@ Object.defineProperty(Node.prototype, 'mount', {
95
95
  config: MountConfig,
96
96
  options: MountObserverOptions = {}
97
97
  ): Promise<T> {
98
+
98
99
  // For ShadowRoot and Document, observe directly
99
100
  if (this instanceof ShadowRoot || this instanceof Document) {
100
101
  const mo = new MountObserver(config, options);
@@ -106,6 +107,10 @@ Object.defineProperty(Node.prototype, 'mount', {
106
107
  if (!(this instanceof Element)) {
107
108
  throw new Error('mount() can only be called on Element, ShadowRoot, or Document');
108
109
  }
110
+
111
+ if(this instanceof HTMLScriptElement) {
112
+ options.mose = new WeakRef(this);
113
+ }
109
114
 
110
115
  const scope = options.scope ?? 'registry'; // NEW DEFAULT
111
116
  let thingToObserve: Node;
@@ -123,7 +128,7 @@ Object.defineProperty(Node.prototype, 'mount', {
123
128
 
124
129
  // Register with coordinator if registry exists
125
130
  if (registry) {
126
- await getOrInsertObserverEntry(registry, config, thingToObserve);
131
+ await getOrInsertObserverEntry(registry, config, thingToObserve, options);
127
132
  } else {
128
133
  // No registry, just create a standalone observer
129
134
  const mo = new MountObserver(config, options);
@@ -185,7 +190,7 @@ Object.defineProperty(Element.prototype, 'mountScope', {
185
190
 
186
191
  // For each config, ensure an observer exists for this registry root
187
192
  for (const config of configs) {
188
- await getOrInsertObserverEntry(registry, config, registryRoot);
193
+ await getOrInsertObserverEntry(registry, config, registryRoot, {});
189
194
  }
190
195
  },
191
196
  writable: true,
package/MountObserver.js CHANGED
@@ -13,6 +13,9 @@ export class MountObserver extends EventTarget {
13
13
  }
14
14
  #init;
15
15
  #options;
16
+ get options() {
17
+ return { ...this.#options };
18
+ }
16
19
  #abortController;
17
20
  #modules = [];
18
21
  #configFromPromise;
package/MountObserver.ts CHANGED
@@ -37,6 +37,9 @@ export class MountObserver<TKeys extends string = string> extends EventTarget im
37
37
 
38
38
  #init: MountConfig;
39
39
  #options: MountObserverOptions;
40
+ get options(): MountObserverOptions {
41
+ return { ...this.#options };
42
+ }
40
43
  #abortController: AbortController;
41
44
  #modules: any[] = [];
42
45
  #configFromPromise: Promise<void> | undefined;
@@ -55,10 +55,10 @@ if (typeof WeakMap.prototype.getOrInsertComputed !== 'function') {
55
55
  * Helper to create an observer entry asynchronously.
56
56
  * Separated to handle async operations cleanly.
57
57
  */
58
- async function createObserverEntry(config, registryRoot) {
58
+ async function createObserverEntry(config, registryRoot, options) {
59
59
  // Dynamically import to avoid circular dependency
60
60
  const { MountObserver: MountObserverClass } = await import('./MountObserver.js');
61
- const observer = new MountObserverClass(config);
61
+ const observer = new MountObserverClass(config, options);
62
62
  await observer.observe(registryRoot);
63
63
  return {
64
64
  config,
@@ -76,7 +76,7 @@ async function createObserverEntry(config, registryRoot) {
76
76
  *
77
77
  * @returns The ObserverEntry for the requested combination
78
78
  */
79
- export async function getOrInsertObserverEntry(registry, config, registryRoot) {
79
+ export async function getOrInsertObserverEntry(registry, config, registryRoot, options) {
80
80
  // Add config to the registry's config list (if not already there)
81
81
  registry.mountConfigRegistry.push(config);
82
82
  // Get or create the nested map structure
@@ -85,7 +85,7 @@ export async function getOrInsertObserverEntry(registry, config, registryRoot) {
85
85
  // Get or create the observer for this specific registry root
86
86
  let observerEntry = nodeToObserverMap.get(registryRoot);
87
87
  if (!observerEntry) {
88
- observerEntry = await createObserverEntry(config, registryRoot);
88
+ observerEntry = await createObserverEntry(config, registryRoot, options);
89
89
  nodeToObserverMap.set(registryRoot, observerEntry);
90
90
  }
91
91
  // Track this registry root in the scopes set
@@ -116,7 +116,7 @@ export async function getOrInsertObserverEntry(registry, config, registryRoot) {
116
116
  const confObserverMap = mountConfigMap.getOrInsertComputed(conf, () => new WeakMap());
117
117
  let existingEntry = confObserverMap.get(regRoot);
118
118
  if (!existingEntry) {
119
- existingEntry = await createObserverEntry(conf, regRoot);
119
+ existingEntry = await createObserverEntry(conf, regRoot, options);
120
120
  confObserverMap.set(regRoot, existingEntry);
121
121
  }
122
122
  }
@@ -4,7 +4,7 @@
4
4
  * where all scopes with the same registry share mount observers.
5
5
  */
6
6
 
7
- import type { MountConfig, WeakDual } from './types/mount-observer/types.js';
7
+ import type { MountConfig, MountObserverOptions, WeakDual } from './types/mount-observer/types.js';
8
8
  import type { MountObserver } from './MountObserver.js';
9
9
 
10
10
  /**
@@ -98,11 +98,12 @@ if (typeof WeakMap.prototype.getOrInsertComputed !== 'function') {
98
98
  */
99
99
  async function createObserverEntry(
100
100
  config: MountConfig,
101
- registryRoot: Node
101
+ registryRoot: Node,
102
+ options: MountObserverOptions
102
103
  ): Promise<ObserverEntry> {
103
104
  // Dynamically import to avoid circular dependency
104
105
  const { MountObserver: MountObserverClass } = await import('./MountObserver.js');
105
- const observer = new MountObserverClass(config);
106
+ const observer = new MountObserverClass(config, options);
106
107
  await observer.observe(registryRoot);
107
108
  return {
108
109
  config,
@@ -124,7 +125,8 @@ async function createObserverEntry(
124
125
  export async function getOrInsertObserverEntry(
125
126
  registry: CustomElementRegistry,
126
127
  config: MountConfig,
127
- registryRoot: Node
128
+ registryRoot: Node,
129
+ options: MountObserverOptions
128
130
  ): Promise<ObserverEntry> {
129
131
  // Add config to the registry's config list (if not already there)
130
132
  (registry as any).mountConfigRegistry.push(config);
@@ -136,7 +138,7 @@ export async function getOrInsertObserverEntry(
136
138
  // Get or create the observer for this specific registry root
137
139
  let observerEntry = nodeToObserverMap.get(registryRoot);
138
140
  if (!observerEntry) {
139
- observerEntry = await createObserverEntry(config, registryRoot);
141
+ observerEntry = await createObserverEntry(config, registryRoot, options);
140
142
  nodeToObserverMap.set(registryRoot, observerEntry);
141
143
  }
142
144
 
@@ -171,7 +173,7 @@ export async function getOrInsertObserverEntry(
171
173
  const confObserverMap = mountConfigMap.getOrInsertComputed(conf, () => new WeakMap());
172
174
  let existingEntry = confObserverMap.get(regRoot);
173
175
  if (!existingEntry) {
174
- existingEntry = await createObserverEntry(conf, regRoot);
176
+ existingEntry = await createObserverEntry(conf, regRoot, options);
175
177
  confObserverMap.set(regRoot, existingEntry);
176
178
  }
177
179
  }
@@ -1,99 +1,103 @@
1
- import { EvtRt } from './EvtRt.js';
2
- export class DefineCustomElementHandler extends EvtRt {
3
- mount(mountedElement, MountConfig, context) {
4
- this.abort();
5
- // Check if modules are specified
6
- if (!context.modules || context.modules.length === 0) {
7
- throw new Error('Must specify an ES Module');
8
- }
9
- const module = context.modules[0];
10
- const tagName = mountedElement.localName;
11
- // Check if already defined
12
- if (customElements.get(tagName)) {
13
- return;
14
- }
15
- // Find suitable class
16
- const ElementClass = this.findSuitableClass(module);
17
- // Validate that ElementClass is a constructor
18
- if (typeof ElementClass !== 'function') {
19
- throw new Error(`Found class is not a constructor: ${typeof ElementClass}`);
20
- }
21
- // Create wrapper class to allow reuse
22
- // Use anonymous class expression which works across all browsers
23
- const WrapperClass = class extends ElementClass {
24
- };
25
- // Define the custom element using the define method
26
- this.define(tagName, WrapperClass, mountedElement);
27
- }
28
- /**
29
- * Define the custom element in the appropriate registry.
30
- * Override this method in subclasses to use scoped registries.
31
- * @param tagName - The custom element tag name
32
- * @param ElementClass - The element class constructor
33
- * @param mountedElement - The mounted element (used for scoped registry access)
34
- */
35
- define(tagName, ElementClass, mountedElement) {
36
- customElements.define(tagName, ElementClass);
37
- }
38
- findSuitableClass(module) {
39
- // Check default export first
40
- const defaultExport = module.default;
41
- if (defaultExport && this.extendsHTMLElement(defaultExport)) {
42
- return defaultExport;
43
- }
44
- // Find all exports that extend HTMLElement
45
- const htmlElementClasses = Object.values(module)
46
- .filter(exp => typeof exp === 'function' && this.extendsHTMLElement(exp));
47
- if (htmlElementClasses.length === 0) {
48
- throw new Error('No suitable class found in module');
49
- }
50
- if (htmlElementClasses.length > 1) {
51
- throw new Error('More than one class found in module');
52
- }
53
- return htmlElementClasses[0];
54
- }
55
- extendsHTMLElement(cls) {
56
- try {
57
- // Must be a function
58
- if (typeof cls !== 'function') {
59
- return false;
60
- }
61
- // Handle direct HTMLElement export
62
- if (cls === HTMLElement) {
63
- return true;
64
- }
65
- // Check if it has a prototype and extends HTMLElement
66
- if (cls.prototype && cls.prototype instanceof HTMLElement) {
67
- return true;
68
- }
69
- return false;
70
- }
71
- catch {
72
- return false;
73
- }
74
- }
75
- }
76
- /**
77
- * Handler for defining custom elements in scoped registries.
78
- * Uses the element's customElementRegistry property to define elements
79
- * in the appropriate scoped registry instead of the global registry.
80
- */
81
- export class DefineScopedCustomElementHandler extends DefineCustomElementHandler {
82
- /**
83
- * Define the custom element in the element's scoped registry.
84
- * @param tagName - The custom element tag name
85
- * @param ElementClass - The element class constructor
86
- * @param mountedElement - The mounted element with customElementRegistry
87
- */
88
- define(tagName, ElementClass, mountedElement) {
89
- const registry = mountedElement.customElementRegistry;
90
- if (!registry) {
91
- throw new Error('Element does not have a customElementRegistry. Scoped registries require Chrome 146+ or latest WebKit/Safari.');
92
- }
93
- // Check if already defined in this scoped registry
94
- if (registry.get(tagName)) {
95
- return;
96
- }
97
- registry.define(tagName, ElementClass);
98
- }
99
- }
1
+ import { EvtRt } from '../EvtRt.js';
2
+ export class DefineCustomElementHandler extends EvtRt {
3
+ mount(mountedElement, MountConfig, context) {
4
+ this.abort();
5
+ // Check if modules are specified
6
+ if (!context.modules || context.modules.length === 0) {
7
+ throw new Error('Must specify an ES Module');
8
+ }
9
+ const module = context.modules[0];
10
+ const tagName = mountedElement.localName;
11
+ // Check if already defined
12
+ if (customElements.get(tagName)) {
13
+ return;
14
+ }
15
+ // Find suitable class
16
+ const ElementClass = this.findSuitableClass(module);
17
+ // Validate that ElementClass is a constructor
18
+ if (typeof ElementClass !== 'function') {
19
+ throw new Error(`Found class is not a constructor: ${typeof ElementClass}`);
20
+ }
21
+ // Create wrapper class to allow reuse
22
+ // Use anonymous class expression which works across all browsers
23
+ const WrapperClass = class extends ElementClass {
24
+ };
25
+ // Define the custom element using the define method
26
+ this.define(tagName, WrapperClass, mountedElement);
27
+ }
28
+ /**
29
+ * Define the custom element in the appropriate registry.
30
+ * Override this method in subclasses to use scoped registries.
31
+ * @param tagName - The custom element tag name
32
+ * @param ElementClass - The element class constructor
33
+ * @param mountedElement - The mounted element (used for scoped registry access)
34
+ */
35
+ define(tagName, ElementClass, mountedElement) {
36
+ customElements.define(tagName, ElementClass);
37
+ }
38
+ findSuitableClass(module) {
39
+ // Check default export first
40
+ const defaultExport = module.default;
41
+ if (defaultExport && this.extendsHTMLElement(defaultExport)) {
42
+ return defaultExport;
43
+ }
44
+ // Find all exports that extend HTMLElement
45
+ const htmlElementClasses = Object.values(module)
46
+ .filter(exp => typeof exp === 'function' && this.extendsHTMLElement(exp));
47
+ if (htmlElementClasses.length === 0) {
48
+ throw new Error('No suitable class found in module');
49
+ }
50
+ if (htmlElementClasses.length > 1) {
51
+ throw new Error('More than one class found in module');
52
+ }
53
+ return htmlElementClasses[0];
54
+ }
55
+ extendsHTMLElement(cls) {
56
+ try {
57
+ // Must be a function
58
+ if (typeof cls !== 'function') {
59
+ return false;
60
+ }
61
+ // Handle direct HTMLElement export
62
+ if (cls === HTMLElement) {
63
+ return true;
64
+ }
65
+ // Check if it has a prototype and extends HTMLElement
66
+ if (cls.prototype && cls.prototype instanceof HTMLElement) {
67
+ return true;
68
+ }
69
+ return false;
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ }
76
+ /**
77
+ * Handler for defining custom elements in scoped registries.
78
+ * Uses the element's customElementRegistry property to define elements
79
+ * in the appropriate scoped registry instead of the global registry.
80
+ */
81
+ export class DefineScopedCustomElementHandler extends DefineCustomElementHandler {
82
+ /**
83
+ * Define the custom element in the element's scoped registry.
84
+ * @param tagName - The custom element tag name
85
+ * @param ElementClass - The element class constructor
86
+ * @param mountedElement - The mounted element with customElementRegistry
87
+ */
88
+ define(tagName, ElementClass, mountedElement) {
89
+ const registry = mountedElement.customElementRegistry;
90
+ if (!registry) {
91
+ throw new Error('Element does not have a customElementRegistry. Scoped registries require Chrome 146+ or latest WebKit/Safari.');
92
+ }
93
+ // Check if already defined in this scoped registry
94
+ if (registry.get(tagName)) {
95
+ return;
96
+ }
97
+ registry.define(tagName, ElementClass);
98
+ }
99
+ }
100
+ // Register built-in handlers
101
+ import { MountObserver } from '../MountObserver.js';
102
+ MountObserver.define('builtIns.defineCustomElement', DefineCustomElementHandler);
103
+ MountObserver.define('buildIns.defineScopedCustomElement', DefineScopedCustomElementHandler);
@@ -0,0 +1,123 @@
1
+ import { EvtRt } from '../EvtRt.js';
2
+ import { MountConfig, MountContext } from '../types/mount-observer/types.js';
3
+
4
+ export class DefineCustomElementHandler extends EvtRt {
5
+ mount(mountedElement: Element, MountConfig: MountConfig, context: MountContext): void {
6
+ this.abort();
7
+ // Check if modules are specified
8
+ if (!context.modules || context.modules.length === 0) {
9
+ throw new Error('Must specify an ES Module');
10
+ }
11
+
12
+ const module = context.modules[0];
13
+ const tagName = mountedElement.localName;
14
+
15
+ // Check if already defined
16
+ if (customElements.get(tagName)) {
17
+ return;
18
+ }
19
+
20
+ // Find suitable class
21
+ const ElementClass = this.findSuitableClass(module);
22
+
23
+ // Validate that ElementClass is a constructor
24
+ if (typeof ElementClass !== 'function') {
25
+ throw new Error(`Found class is not a constructor: ${typeof ElementClass}`);
26
+ }
27
+
28
+ // Create wrapper class to allow reuse
29
+ // Use anonymous class expression which works across all browsers
30
+ const WrapperClass = class extends ElementClass {};
31
+
32
+ // Define the custom element using the define method
33
+ this.define(tagName, WrapperClass, mountedElement);
34
+ }
35
+
36
+ /**
37
+ * Define the custom element in the appropriate registry.
38
+ * Override this method in subclasses to use scoped registries.
39
+ * @param tagName - The custom element tag name
40
+ * @param ElementClass - The element class constructor
41
+ * @param mountedElement - The mounted element (used for scoped registry access)
42
+ */
43
+ protected define(tagName: string, ElementClass: CustomElementConstructor, mountedElement: Element): void {
44
+ customElements.define(tagName, ElementClass);
45
+ }
46
+
47
+ private findSuitableClass(module: any): typeof HTMLElement {
48
+ // Check default export first
49
+ const defaultExport = module.default;
50
+
51
+ if (defaultExport && this.extendsHTMLElement(defaultExport)) {
52
+ return defaultExport;
53
+ }
54
+
55
+ // Find all exports that extend HTMLElement
56
+ const htmlElementClasses = Object.values(module)
57
+ .filter(exp => typeof exp === 'function' && this.extendsHTMLElement(exp));
58
+
59
+ if (htmlElementClasses.length === 0) {
60
+ throw new Error('No suitable class found in module');
61
+ }
62
+
63
+ if (htmlElementClasses.length > 1) {
64
+ throw new Error('More than one class found in module');
65
+ }
66
+
67
+ return htmlElementClasses[0] as typeof HTMLElement;
68
+ }
69
+
70
+ private extendsHTMLElement(cls: any): boolean {
71
+ try {
72
+ // Must be a function
73
+ if (typeof cls !== 'function') {
74
+ return false;
75
+ }
76
+ // Handle direct HTMLElement export
77
+ if (cls === HTMLElement) {
78
+ return true;
79
+ }
80
+ // Check if it has a prototype and extends HTMLElement
81
+ if (cls.prototype && cls.prototype instanceof HTMLElement) {
82
+ return true;
83
+ }
84
+ return false;
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Handler for defining custom elements in scoped registries.
93
+ * Uses the element's customElementRegistry property to define elements
94
+ * in the appropriate scoped registry instead of the global registry.
95
+ */
96
+ export class DefineScopedCustomElementHandler extends DefineCustomElementHandler {
97
+ /**
98
+ * Define the custom element in the element's scoped registry.
99
+ * @param tagName - The custom element tag name
100
+ * @param ElementClass - The element class constructor
101
+ * @param mountedElement - The mounted element with customElementRegistry
102
+ */
103
+ protected define(tagName: string, ElementClass: CustomElementConstructor, mountedElement: Element): void {
104
+ const registry = (mountedElement as any).customElementRegistry;
105
+
106
+ if (!registry) {
107
+ throw new Error('Element does not have a customElementRegistry. Scoped registries require Chrome 146+ or latest WebKit/Safari.');
108
+ }
109
+
110
+ // Check if already defined in this scoped registry
111
+ if (registry.get(tagName)) {
112
+ return;
113
+ }
114
+
115
+ registry.define(tagName, ElementClass);
116
+ }
117
+ }
118
+
119
+ // Register built-in handlers
120
+ import { MountObserver } from '../MountObserver.js';
121
+
122
+ MountObserver.define('builtIns.defineCustomElement', DefineCustomElementHandler);
123
+ MountObserver.define('buildIns.defineScopedCustomElement', DefineScopedCustomElementHandler);