mount-observer 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,6 @@
3
3
  [![How big is this package in your project?](https://img.shields.io/bundlephobia/minzip/mount-observer?style=for-the-badge)](https://bundlephobia.com/result?p=mount-observer)
4
4
  <img src="http://img.badgesize.io/https://cdn.jsdelivr.net/npm/mount-observer?compression=gzip">
5
5
 
6
- Note that much of what is described below has not yet been polyfilled.
7
6
 
8
7
  ## Implementation Status
9
8
 
@@ -12,6 +11,9 @@ The following features have been implemented and tested:
12
11
  ### Core Functionality
13
12
  - ✅ **matching**: CSS selector-based element matching
14
13
  - ✅ **whereInstanceOf**: Constructor-based element filtering (single or array)
14
+ - ✅ **whereLocalNameMatches**: Regular expression-based localName filtering
15
+ - ✅ **shouldMount**: Custom JavaScript check for complex mounting conditions
16
+ - ✅ **Registry matching**: Automatic filtering by customElementRegistry (Chrome 146+)
15
17
  - ✅ **withMediaMatching**: Media query-based conditional mounting (string or MediaQueryList)
16
18
  - ✅ **whereObservedRootSizeMatches**: Container query-based conditional mounting (observes root element size)
17
19
  - ✅ **whereElementIntersectsWith**: Intersection observer-based conditional mounting (observes element visibility)
@@ -29,6 +31,7 @@ The following features have been implemented and tested:
29
31
  - ✅ **assignOnDismount**: Property assignment when elements dismount
30
32
  - ✅ **stageOnMount**: Reversible property assignment (auto-restores on dismount)
31
33
  - ✅ **do callbacks**: Mount/dismount/disconnect/reconnect lifecycle hooks
34
+ - ✅ **with property**: Hierarchical observer composition with sub-observers
32
35
  - ✅ **Element mount extension**: element.mount() method for scoped registry observation
33
36
  - ✅ **Shared MutationObserver**: Efficient observer sharing across instances
34
37
  - ✅ **Code splitting**: Conditional features loaded on-demand
@@ -80,7 +83,9 @@ There is quite a bit of functionality this proposal would open up that is exceed
80
83
 
81
84
  3. Knowing when an element previously being monitored passes totally "out-of-scope" so that no more hard references to the element remain. This would allow for cleanup of no longer needed weak references without requiring polling.
82
85
 
83
- 4. Some CSS selectors, such as the [scope donut hole range](https://css-tricks.com/solved-by-css-donuts-scopes/#aa-donut-scoping-with-scope), aren't supported by oEl.querySelectorAll(...) or oEl.matches(...).
86
+ 4. Some CSS selectors, such as the [donut hole scope range](https://css-tricks.com/solved-by-css-donuts-scopes/#aa-donut-scoping-with-scope), aren't supported by oEl.querySelectorAll(...) or oEl.matches(...).
87
+
88
+ 5. Scoped custom element registries form natural "islands" of DOM that have many commonalities with css "donut hole scoping", and which mutation observers aren't really designed around. The mount-observer is designed to work with scoped custom element registries as first-class citizens.
84
89
 
85
90
 
86
91
  ### Most significant use cases
@@ -107,7 +112,7 @@ Before getting into the weeds, let's demonstrate the two most prominent use case
107
112
  <div log-to-console="clicked on a div">hello</div>
108
113
 
109
114
  <script type=module>
110
- document.body.mount({
115
+ document.mount({
111
116
  matching: '[log-to-console]',
112
117
  do: (el) => {
113
118
  el.addEventListener('click', e => {
@@ -166,7 +171,7 @@ element.mount({
166
171
 
167
172
  ## Enhancing Elements with assign-gingerly
168
173
 
169
- 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.
174
+ The `builtIns.enhanceMountedElement` handler automatically enhances mounted elements using the [assign-gingerly](https://www.npmjs.com/package/assign-gingerly) enhancement system. This allows us to attach behavior and state to elements without subclassing.
170
175
 
171
176
  ```JavaScript
172
177
  // MyEnhancement.js
@@ -188,28 +193,818 @@ export default {
188
193
  enhKey: 'buttonEnh'
189
194
  };
190
195
 
196
+ // main.js
197
+
191
198
  document.mount({
192
199
  matching: '.enhance-me',
193
200
  import: './MyEnhancement.js',
194
201
  do: 'builtIns.enhanceMountedElement'
195
202
  });
196
203
 
197
- // HTML
198
- <button class="enhance-me">Click me</button>
204
+ // HTML
205
+ <button class="enhance-me">Click me</button>
206
+
207
+ // Access the enhancement
208
+ const button = document.querySelector('.enhance-me');
209
+ console.log(button.enh.buttonEnh.clickCount); // 0
210
+ button.click();
211
+ console.log(button.enh.buttonEnh.clickCount); // 1
212
+ ```
213
+
214
+ The handler:
215
+ 1. Searches the imported module for an export with a `spawn` property (the enhancement class), starting with default.
216
+ 2. Calls `element.enh.get(registryItem, context)` to spawn the enhancement
217
+ 3. Stores the enhancement instance on `element.enh[enhKey]` if an `enhKey` is provided
218
+
219
+
220
+ ## Loading ES Modules from Script Elements
221
+
222
+ The `builtIns.scriptNoModule` handler enables declarative module loading using `<script nomodule>` elements. This provides a way to import ES modules and JSON data directly from HTML without writing JavaScript.
223
+
224
+ ```html
225
+ <!-- Load a JavaScript module -->
226
+ <script nomodule src="./config.js" id="myConfig"></script>
227
+
228
+ <!-- Load JSON data with import assertion -->
229
+ <script nomodule src="./data.json" with-type="json" id="myData"></script>
230
+
231
+ <script type="module">
232
+ import { MountObserver } from 'mount-observer/MountObserver.js';
233
+
234
+ // Handler provides matching and whereInstanceOf via static properties
235
+ const observer = new MountObserver({
236
+ do: 'builtIns.scriptNoModule'
237
+ });
238
+ observer.observe(document);
239
+
240
+ // Access the imported modules
241
+ const config = document.getElementById('myConfig').export;
242
+ const data = document.getElementById('myData').export.default;
243
+
244
+ console.log(config.mountConfig); // Access exported values
245
+ console.log(data); // Access JSON data
246
+ </script>
247
+ ```
248
+
249
+ **How it works:**
250
+ 1. The handler matches `script[nomodule][src]` elements (via static properties)
251
+ 2. Reads the `src` attribute and resolves it relative to the document
252
+ 3. Checks for optional `with-type` attribute for import assertions (e.g., `"json"`)
253
+ 4. Dynamically imports the module: `import(src, { with: { type: withType } })`
254
+ 5. Stores the imported module on `element.export`
255
+
256
+ **Benefits:**
257
+ - Declarative module loading directly in HTML
258
+ - Supports JSON imports with `with-type="json"` attribute
259
+ - No need to specify `matching` or `whereInstanceOf` (handler provides defaults)
260
+ - Modules are accessible via `scriptElement.export`
261
+ - Works with relative and absolute URLs
262
+
263
+ **Use cases:**
264
+ - Loading configuration files declaratively
265
+ - Importing JSON data without fetch
266
+ - Progressive enhancement with module loading
267
+ - Declarative dependency management in HTML
268
+
269
+ ## Mount Observer Script Elements (MOSEs)
270
+
271
+ The `builtIns.mountObserverScript` handler enables fully declarative mount observer configuration using `<script type="mountobserver">` elements. This provides the ultimate in HTML-first progressive enhancement.
272
+
273
+ ```html
274
+ <!-- Inline JSON configuration -->
275
+ <script type="mountobserver">
276
+ {
277
+ "matching": "my-fancy-button",
278
+ "import": "./fancy-button.js",
279
+ "do": "builtIns.defineCustomElement"
280
+ }
281
+ </script>
282
+
283
+ <!-- External JSON configuration -->
284
+ <script type="mountobserver" src="./observer-config.json"></script>
285
+
286
+ <!-- Bootstrap the handler -->
287
+ <script type="module">
288
+ import { MountObserver } from 'mount-observer/MountObserver.js';
289
+
290
+ // Handler provides matching and whereInstanceOf via static properties
291
+ const observer = new MountObserver({
292
+ do: 'builtIns.mountObserverScript'
293
+ });
294
+ observer.observe(document);
295
+ </script>
296
+ ```
297
+
298
+ **How it works:**
299
+ 1. The handler matches `script[type="mountobserver"]` elements (via static properties)
300
+ 2. If the script has a `src` attribute, imports JSON from that URL
301
+ 3. Otherwise, parses the script's textContent as JSON
302
+ 4. Calls `scriptElement.mount(config)` with the parsed configuration
303
+ 5. The `mount()` method creates a MountObserver for that configuration
304
+
305
+ **Benefits:**
306
+ - Zero JavaScript required for observer configuration
307
+ - Configurations are pure JSON (fully serializable)
308
+ - Easy to generate server-side or from build tools
309
+ - Supports both inline and external configurations
310
+ - Leverages the `element.mount()` API for automatic scope management
311
+ - No need to specify `matching` or `whereInstanceOf` for the handler itself
312
+
313
+ **Use cases:**
314
+ - Server-side rendering with progressive enhancement
315
+ - Build-time generation of observer configurations
316
+ - CMS-driven component loading
317
+ - Declarative micro-frontend architecture
318
+ - Configuration management without JavaScript bundling
319
+
320
+ **Example with multiple configurations:**
321
+ ```html
322
+ <!-- Load custom elements -->
323
+ <script type="mountobserver">
324
+ {
325
+ "matching": "my-button",
326
+ "import": "./components/my-button.js",
327
+ "do": "builtIns.defineCustomElement"
328
+ }
329
+ </script>
330
+
331
+ <!-- Enhance existing elements -->
332
+ <script type="mountobserver">
333
+ {
334
+ "matching": ".interactive",
335
+ "import": "./enhancements/interactive.js",
336
+ "do": "builtIns.enhanceMountedElement"
337
+ }
338
+ </script>
339
+
340
+ <!-- Single bootstrap script activates all configurations -->
341
+ <script type="module">
342
+ import { MountObserver } from 'mount-observer/MountObserver.js';
343
+
344
+ new MountObserver({
345
+ do: 'builtIns.mountObserverScript'
346
+ }).observe(document);
347
+ </script>
348
+ ```
349
+
350
+ ## Hoisting Templates for Performance
351
+
352
+ The `builtIns.hoistTemplate` handler optimizes template usage by moving a template element's content from shadow roots to `document.head`. This is particularly useful when templates with IDs are repeated across multiple custom elements.
353
+
354
+ **Why hoist templates?**
355
+
356
+ When HTML-first custom elements repeat throughout a page, each instance typically contains its own copy of template content. Moving these templates to a centralized location:
357
+ - Reduces memory usage (one template instead of many copies)
358
+ - Improves cloning performance (single source of truth)
359
+ - Maintains the same API through the `remoteContent` getter
360
+
361
+ **Basic usage:**
362
+
363
+ ```html
364
+ <my-web-component>
365
+ #shadow
366
+ <template id="my-template">
367
+ <div>My content</div>
368
+ </template>
369
+ </my-web-component>
370
+
371
+ <script type="module">
372
+ import { MountObserver } from 'mount-observer/MountObserver.js';
373
+
374
+ const observer = new MountObserver({
375
+ do: 'builtIns.hoistTemplate'
376
+ });
377
+ observer.observe(document);
378
+ </script>
379
+ ```
380
+
381
+ **What happens:**
382
+ 1. The handler finds templates with IDs in shadow roots
383
+ 2. Moves the template content to a new template in `<head>`
384
+ 3. Updates the original template with `src="#mount-observer-0"` (unique ID)
385
+ 4. Defines a `remoteContent` getter that returns the hoisted template's content
386
+
387
+ **Accessing hoisted content:**
388
+
389
+ ```javascript
390
+ const template = shadowRoot.querySelector('#my-template');
391
+
392
+ // After hoisting, use remoteContent to access the content
393
+ const content = template.remoteContent; // Returns DocumentFragment
394
+ const clone = content.cloneNode(true); // Clone the content
395
+ ```
396
+ <details>
397
+ <summary>Matching criteria
398
+
399
+ The handler automatically hoists templates that:
400
+ - Have an `id` attribute
401
+ - Don't already have a `src` attribute
402
+ - Are in a shadow root (or disconnected, being cloned)
403
+ - Have content (empty templates are skipped)
404
+
405
+ **Declarative usage with MOSE:**
406
+
407
+ ```html
408
+ <script type="mountobserver">
409
+ {
410
+ "do": "builtIns.hoistTemplate"
411
+ }
412
+ </script>
413
+
414
+ <script type="module">
415
+ import { MountObserver } from 'mount-observer/MountObserver.js';
416
+
417
+ new MountObserver({
418
+ do: 'builtIns.mountObserverScript'
419
+ }).observe(document);
420
+ </script>
421
+ ```
422
+
423
+ [Implemented as HoistingTemplates requirement](requirements/Done/HoistingTemplates.md)
424
+
425
+ </details>
426
+
427
+ ## Intra-Document HTML Includes with HTMLInclude
428
+
429
+ The `builtIns.HTMLInclude` handler enables declarative HTML fragment reuse within a document using `<template src="#id">` syntax. Think of it as "constants for HTML" - define content once with an ID, then reference it multiple times throughout your document.
430
+
431
+ **Why use HTML includes?**
432
+
433
+ - Reduces duplication of repeated HTML structures
434
+ - Enables template-based content generation
435
+ - Supports partial updates via matching insertions
436
+ - Works across shadow DOM boundaries
437
+ - Supports declarative shadow DOM attachment
438
+ - Caches lookups for performance
439
+ - Detects circular references automatically
440
+ - Can be used to inherit from MOSEs
441
+
442
+ **Basic usage - Simple cloning:**
443
+
444
+ ```html
445
+ <!-- Define reusable content -->
446
+ <div id="reusable">
447
+ <p>This content can be reused</p>
448
+ <button>Click me</button>
449
+ </div>
450
+
451
+ <!-- Reference it with a template -->
452
+ <template src="#reusable"></template>
453
+
454
+ <!-- Results in: -->
455
+ <div>
456
+ <p>This content can be reused</p>
457
+ <button>Click me</button>
458
+ </div>
459
+
460
+ <script type="module">
461
+ import { MountObserver } from 'mount-observer/MountObserver.js';
462
+
463
+ const observer = new MountObserver({
464
+ do: 'builtIns.HTMLInclude'
465
+ });
466
+ observer.observe(document);
467
+ </script>
468
+ ```
469
+
470
+ **What happens:**
471
+ 1. The handler finds templates with `src` attributes starting with `#`
472
+ 2. Searches for an element with that ID (across shadow boundaries)
473
+ 3. Clones the content from the source element
474
+ 4. Replaces the template with the cloned content
475
+ 5. Removes the `id` attribute from cloned elements to avoid duplicate IDs
476
+
477
+ **Cloning priority:**
478
+ 1. `remoteContent` property (hoisted templates) - highest priority
479
+ 2. `content` property (regular templates)
480
+ 3. The element itself (any element with an ID)
481
+
482
+ **Works with hoisted templates:**
483
+
484
+ ```html
485
+ <my-web-component>
486
+ #shadow
487
+ <template id="my-template">
488
+ <div>Hoisted content</div>
489
+ </template>
490
+ </my-web-component>
491
+
492
+ <!-- After hoisting, this still works -->
493
+ <template src="#my-template"></template>
494
+
495
+ <script type="module">
496
+ import { MountObserver } from 'mount-observer/MountObserver.js';
497
+
498
+ // First hoist templates
499
+ new MountObserver({
500
+ do: 'builtIns.hoistTemplate'
501
+ }).observe(document);
502
+
503
+ // Then use them
504
+ new MountObserver({
505
+ do: 'builtIns.HTMLInclude'
506
+ }).observe(document);
507
+ </script>
508
+ ```
509
+
510
+ ### Shadow DOM Support
511
+
512
+ The HTMLInclude handler supports declarative shadow DOM attachment using the `shadowrootmodeonload` attribute. This allows you to attach cloned content directly to a parent element's shadow root, similar to the platform's [declarative shadow DOM](https://web.dev/articles/declarative-shadow-dom) feature.
513
+
514
+ **Basic shadow DOM usage:**
515
+
516
+ ```html
517
+ <!-- Define reusable shadow content -->
518
+ <template id="shadow-content">
519
+ <style>
520
+ :host {
521
+ display: block;
522
+ padding: 10px;
523
+ }
524
+ .shadow-text {
525
+ color: blue;
526
+ }
527
+ </style>
528
+ <div class="shadow-text">
529
+ <slot name="greeting"></slot>
530
+ <slot></slot>
531
+ </div>
532
+ </template>
533
+
534
+ <!-- Attach to shadow root -->
535
+ <div class="host-element">
536
+ <template src="#shadow-content" shadowrootmodeonload="open"></template>
537
+ <span slot="greeting">Hello</span>
538
+ <span>World!</span>
539
+ </div>
540
+
541
+ <script type="module">
542
+ import { MountObserver } from 'mount-observer/MountObserver.js';
543
+
544
+ new MountObserver({
545
+ do: 'builtIns.HTMLInclude'
546
+ }).observe(document);
547
+ </script>
548
+ ```
549
+
550
+ **What happens:**
551
+ 1. The handler checks for the `shadowrootmodeonload` attribute (case-insensitive)
552
+ 2. If present, it attaches the cloned content to the parent element's shadow root
553
+ 3. If the parent doesn't have a shadow root, one is created with the specified mode
554
+ 4. If a shadow root already exists, the content is appended to it
555
+ 5. The template is removed as usual
556
+
557
+ **Shadow root modes:**
558
+ - `open` - Shadow root is accessible via `element.shadowRoot`
559
+ - `closed` - Shadow root is not accessible from outside
560
+
561
+ **Slots work automatically:**
562
+
563
+ The native browser slot mechanism handles content distribution. Light DOM elements with `slot` attributes are automatically projected into the corresponding `<slot>` elements in the shadow DOM.
564
+
565
+ **Example - Complex nested structure:**
566
+
567
+ ```html
568
+ <template id="chorus">
569
+ <template src="#beautiful">
570
+ <span slot="subjectIs">
571
+ <slot name="subjectIs1"></slot>
572
+ </span>
573
+ </template>
574
+ <div>No matter what they say</div>
575
+ <div>Words <slot name="verb1"></slot> bring <slot name="pronoun1"></slot> down</div>
576
+ </template>
577
+
578
+ <div class="chorus">
579
+ <template src="#chorus" shadowrootmodeonload="open"></template>
580
+ <span slot="verb1">can't</span>
581
+ <span slot="pronoun1">me</span>
582
+ <span slot="subjectIs1">I am</span>
583
+ </div>
584
+ ```
585
+
586
+ **Nested templates in shadow DOM:**
587
+
588
+ Templates inside shadow roots are not automatically processed by the parent observer. To process nested templates, you need to observe the shadow root separately:
589
+
590
+ ```javascript
591
+ const host = document.querySelector('.host-element');
592
+ if (host.shadowRoot) {
593
+ const shadowObserver = new MountObserver({
594
+ do: 'builtIns.HTMLInclude'
595
+ });
596
+ await shadowObserver.observe(host.shadowRoot);
597
+ }
598
+ ```
599
+
600
+ **Error handling:**
601
+
602
+ - Invalid mode values: Logs warning if mode is not `"open"` or `"closed"`
603
+ - Missing parent: Logs warning if template has no parent element
604
+ - Attachment failures: Logs error if shadow root cannot be attached
605
+
606
+ ### Matching Insertions - Partial Updates
607
+
608
+ When a template has children, they are used to match elements in the cloned content and selectively update them. This enables partial modifications and "nulling out" content without duplicating the entire structure.
609
+
610
+ **How it works:**
611
+ 1. Template children generate CSS selectors (tag, classes, attributes)
612
+ 2. Matching elements in the cloned content are found
613
+ 3. Matched elements have their children replaced and attributes updated
614
+ 4. The `-i` attribute specifies which attributes to update
615
+
616
+ **Example - Updating attributes:**
617
+
618
+ ```html
619
+ <!-- Source content -->
620
+ <div itemscope id="love">
621
+ <data value="false" itemprop="todayIsFriday">It's Thursday</data>
622
+ </div>
623
+
624
+ <!-- Template with matching insertion -->
625
+ <template src="#love">
626
+ <data value="true" itemprop="todayIsFriday" -i="value"></data>
627
+ </template>
628
+
629
+ <!-- Results in: -->
630
+ <div itemscope>
631
+ <data value="true" itemprop="todayIsFriday">It's Thursday</data>
632
+ </div>
633
+ <!-- The value attribute is updated, but content stays "It's Thursday" -->
634
+ ```
635
+
636
+ **The `-i` attribute:**
637
+
638
+ The `-i` (insert) attribute is a space-separated list of attribute names to update on matched elements. Attributes listed in `-i` are:
639
+ - Excluded from the CSS selector (allows matching elements with different values)
640
+ - Updated on matched elements with values from the template child
641
+
642
+ ```html
643
+ <template src="#form">
644
+ <!-- Update both value and placeholder -->
645
+ <input type="text" name="username" value="new" placeholder="Updated" -i="value placeholder">
646
+ </template>
647
+ ```
648
+
649
+ **Example - Replacing content:**
650
+
651
+ ```html
652
+ <!-- Source -->
653
+ <div id="greeting">
654
+ <p class="message">Hello</p>
655
+ </div>
656
+
657
+ <!-- Template replaces content -->
658
+ <template src="#greeting">
659
+ <p class="message">Goodbye</p>
660
+ </template>
661
+
662
+ <!-- Results in: -->
663
+ <div>
664
+ <p class="message">Goodbye</p>
665
+ </div>
666
+ ```
667
+
668
+ **Example - Multiple matching elements:**
669
+
670
+ ```html
671
+ <!-- Source with multiple items -->
672
+ <div id="list">
673
+ <span class="item">Item 1</span>
674
+ <span class="item">Item 2</span>
675
+ <span class="item">Item 3</span>
676
+ </div>
677
+
678
+ <!-- Update all matching items -->
679
+ <template src="#list">
680
+ <span class="item">Updated</span>
681
+ </template>
682
+
683
+ <!-- Results in: -->
684
+ <div>
685
+ <span class="item">Updated</span>
686
+ <span class="item">Updated</span>
687
+ <span class="item">Updated</span>
688
+ </div>
689
+ ```
690
+
691
+ **Example - Nulling out content:**
692
+
693
+ ```html
694
+ <!-- Source -->
695
+ <div id="status">
696
+ <span data-active="false" class="indicator">Inactive</span>
697
+ </div>
698
+
699
+ <!-- Update attribute, remove content -->
700
+ <template src="#status">
701
+ <span data-active="true" class="indicator" -i="data-active"></span>
702
+ </template>
703
+
704
+ <!-- Results in: -->
705
+ <div>
706
+ <span data-active="true" class="indicator"></span>
707
+ </div>
708
+ <!-- Content is removed, attribute is updated -->
709
+ ```
710
+
711
+ ### Use Case: Inheriting Groups of Mount-Observers
712
+
713
+ Matching insertions become particularly powerful when combined with Mount Observer Script Elements (MOSEs) for inheriting and customizing groups of mount-observers across shadow DOM boundaries.
714
+
715
+ **Scenario:** You have a base component with a set of mount-observers defined in its shadow root, and you want to reuse those observers in other components while making targeted modifications.
716
+
717
+ ```html
718
+ <!-- Base component with mount-observers -->
719
+ <template id="base-observers">
720
+ <script type="mountobserver">
721
+ {
722
+ "matching": "button.primary",
723
+ "import": "./primary-button.js",
724
+ "do": "builtIns.defineCustomElement"
725
+ }
726
+ </script>
727
+
728
+ <script type="mountobserver">
729
+ {
730
+ "matching": ".interactive",
731
+ "import": "./interactive.js",
732
+ "do": "builtIns.enhanceMountedElement"
733
+ }
734
+ </script>
735
+
736
+ <script type="mountobserver">
737
+ {
738
+ "matching": "form",
739
+ "import": "./form-validator.js",
740
+ "do": "builtIns.enhanceMountedElement"
741
+ }
742
+ </script>
743
+ </template>
744
+
745
+ <!-- Derived component - inherit and customize -->
746
+ <my-derived-component>
747
+ #shadow
748
+ <!-- Include base observers -->
749
+ <template src="#base-observers">
750
+ <!-- Override the form validator with a different one -->
751
+ <script type="mountobserver">
752
+ {
753
+ "matching": "form",
754
+ "import": "./custom-form-validator.js",
755
+ "do": "builtIns.enhanceMountedElement"
756
+ }
757
+ </script>
758
+ </template>
759
+
760
+ <!-- Component content -->
761
+ <form>...</form>
762
+ <button class="primary">Submit</button>
763
+ </my-derived-component>
764
+
765
+ <script type="module">
766
+ import { MountObserver } from 'mount-observer/MountObserver.js';
767
+
768
+ // Bootstrap HTMLInclude handler
769
+ new MountObserver({
770
+ do: 'builtIns.HTMLInclude'
771
+ }).observe(document);
772
+
773
+ // Bootstrap MOSE handler to activate the observers
774
+ new MountObserver({
775
+ do: 'builtIns.mountObserverScript'
776
+ }).observe(document);
777
+ </script>
778
+ ```
779
+
780
+ **What happens:**
781
+ 1. The `<template src="#base-observers">` clones all three MOSE scripts
782
+ 2. The matching insertion finds the form validator script (matching by `matching` attribute)
783
+ 3. Replaces its content with the custom validator configuration
784
+ 4. All three scripts are inserted into the shadow root
785
+ 5. The MOSE handler activates all observers in the shadow root's registry scope
786
+
787
+ **Benefits:**
788
+ - **Composition**: Build complex observer configurations from reusable pieces
789
+ - **Inheritance**: Derive new components with modified observer behavior
790
+ - **Scoped registries**: Each shadow root gets its own set of observers
791
+ - **Declarative**: No JavaScript required for observer inheritance
792
+ - **Maintainable**: Update base observers in one place, changes propagate
793
+
794
+ **Advanced pattern - Multiple inheritance:**
795
+
796
+ ```html
797
+ <!-- Base UI observers -->
798
+ <template id="ui-observers">
799
+ <script type="mountobserver">{"matching": "button", ...}</script>
800
+ <script type="mountobserver">{"matching": "input", ...}</script>
801
+ </template>
802
+
803
+ <!-- Base data observers -->
804
+ <template id="data-observers">
805
+ <script type="mountobserver">{"matching": "[itemscope]", ...}</script>
806
+ </template>
807
+
808
+ <!-- Component combines both -->
809
+ <my-component>
810
+ #shadow
811
+ <template src="#ui-observers"></template>
812
+ <template src="#data-observers"></template>
813
+
814
+ <!-- Add component-specific observers -->
815
+ <script type="mountobserver">
816
+ {
817
+ "matching": ".special",
818
+ "import": "./special.js",
819
+ "do": "builtIns.enhanceMountedElement"
820
+ }
821
+ </script>
822
+ </my-component>
823
+ ```
824
+
825
+ This pattern enables:
826
+ - **Mixins**: Combine multiple observer groups
827
+ - **Layering**: Stack observers from different concerns (UI, data, behavior)
828
+ - **Customization**: Override specific observers while keeping others
829
+ - **Reusability**: Share observer configurations across components
830
+
831
+ **Declarative usage with MOSE:**
832
+
833
+ ```html
834
+ <script type="mountobserver">
835
+ {
836
+ "do": "builtIns.HTMLInclude"
837
+ }
838
+ </script>
839
+
840
+ <script type="module">
841
+ import { MountObserver } from 'mount-observer/MountObserver.js';
842
+
843
+ new MountObserver({
844
+ do: 'builtIns.mountObserverScript'
845
+ }).observe(document);
846
+ </script>
847
+ ```
848
+
849
+ **Error handling:**
850
+
851
+ The handler provides helpful error messages:
852
+ - Missing elements: `data-include-error="Element with id='foo' not found"`
853
+ - Circular references: `data-include-error="Circular reference detected: #foo"`
854
+ - Clone failures: `data-include-error="Unable to clone content from #foo"`
855
+
856
+ **Performance:**
857
+
858
+ - Uses WeakMap caching for repeated ID lookups
859
+ - Efficient for scenarios like periodic tables with many repeated elements
860
+ - Searches across shadow boundaries using `upShadowSearch`
861
+ - Cleans up cache entries when elements are garbage collected
862
+
863
+ [Implemented as MatchingInsertionsAndDeletionsWithIntraDocumentHTMLIncludes requirement](requirements/Done/MatchingInsertionsAndDeletionsWithIntraDocumentHTMLIncludes.md)
864
+
865
+ ## Automatic ID Generation with genIds
866
+
867
+ The `builtIns.generateIds` handler automatically generates unique IDs for elements within scoped containers using the [id-generation](https://www.npmjs.com/package/id-generation) package. This is particularly useful for forms, microdata structures, and any scenario where you need unique IDs for accessibility or linking purposes.
868
+
869
+ **Why use automatic ID generation?**
870
+
871
+ - Eliminates manual ID management and conflicts
872
+ - Supports scoped ID generation within fieldsets or itemscope containers
873
+ - Automatically updates ID references in attributes (aria-labelledby, for, etc.)
874
+ - Provides shorthand syntax for common patterns
875
+ - Handles deferred attribute activation
876
+
877
+ **Basic usage:**
878
+
879
+ ```html
880
+ <fieldset disabled>
881
+ <label defer-for="for: #{{username}}">Username:</label>
882
+ <input data-id="username" type="text">
883
+
884
+ <label defer-for="for: #{{password}}">Password:</label>
885
+ <input data-id="password" type="password">
886
+
887
+ <button -id>Generate IDs</button>
888
+ </fieldset>
889
+
890
+ <script type="module">
891
+ import { MountObserver } from 'mount-observer/MountObserver.js';
892
+
893
+ const observer = new MountObserver({
894
+ do: 'builtIns.generateIds'
895
+ });
896
+ observer.observe(document);
897
+ </script>
898
+ ```
899
+
900
+ **What happens:**
901
+
902
+ 1. The handler watches for elements with the `-id` attribute (the trigger)
903
+ 2. Finds the nearest scope container (fieldset, [itemscope], or root)
904
+ 3. Generates unique IDs for elements with `data-id`, `#`, `@`, or `|` attributes
905
+ 4. Replaces `#{{name}}` references with generated IDs in attributes
906
+ 5. Removes `-id` and `defer-*` attributes after processing
907
+ 6. Removes `disabled` from fieldset containers
908
+
909
+ **Shorthand attributes:**
910
+
911
+ ```html
912
+ <fieldset>
913
+ <!-- # uses element's tag name -->
914
+ <input # type="text"> <!-- becomes data-id="input" -->
915
+
916
+ <!-- @ uses element's name attribute -->
917
+ <input @ name="email" type="email"> <!-- becomes data-id="email" -->
918
+
919
+ <!-- | uses element's itemprop attribute -->
920
+ <span | itemprop="price">$99</span> <!-- becomes data-id="price" -->
921
+
922
+ <button -id>Generate IDs</button>
923
+ </fieldset>
924
+ ```
925
+
926
+ **Side effects with data-id:**
927
+
928
+ The `data-id` attribute supports special symbols that trigger side effects:
929
+
930
+ ```html
931
+ <fieldset>
932
+ <!-- @ sets name attribute -->
933
+ <input data-id="@ username" type="text">
934
+ <!-- Result: id="gid-0" name="username" data-id="username" -->
935
+
936
+ <!-- | sets itemprop attribute -->
937
+ <span data-id="| price">$99</span>
938
+ <!-- Result: id="gid-1" itemprop="price" data-id="price" -->
939
+
940
+ <!-- $ sets itemscope and itemprop -->
941
+ <div data-id="$ product">...</div>
942
+ <!-- Result: id="gid-2" itemscope itemprop="product" data-id="product" -->
943
+
944
+ <!-- . adds to class attribute -->
945
+ <div data-id=". highlight">Content</div>
946
+ <!-- Result: id="gid-3" class="highlight" data-id="highlight" -->
947
+
948
+ <!-- % adds to part attribute -->
949
+ <div data-id="% header">Header</div>
950
+ <!-- Result: id="gid-4" part="header" data-id="header" -->
951
+
952
+ <button -id>Generate IDs</button>
953
+ </fieldset>
954
+ ```
955
+
956
+ **Deferred attributes:**
957
+
958
+ Use `defer-*` prefix to prevent attributes from being applied until IDs are generated:
959
+
960
+ ```html
961
+ <fieldset disabled>
962
+ <!-- These attributes won't work until IDs are generated -->
963
+ <label defer-for="for: #{{email}}">Email:</label>
964
+ <input data-id="email" type="email" defer-aria-describedby="aria-describedby: #{{emailHelp}}">
965
+ <span data-id="emailHelp">Enter your email address</span>
966
+
967
+ <button -id>Activate Form</button>
968
+ </fieldset>
969
+ ```
970
+
971
+ **Supported reference attributes:**
972
+
973
+ The handler automatically replaces `#{{name}}` references in these attributes:
974
+ - ARIA: `aria-labelledby`, `aria-describedby`, `aria-controls`, `aria-owns`, `aria-flowto`, `aria-activedescendant`
975
+ - Form: `for`, `form`, `list`
976
+ - Microdata: `itemref`
977
+ - Any `data-*` attribute
978
+ - Any attribute with a `defer-*` prefix
979
+
980
+ **Declarative usage with MOSE:**
981
+
982
+ ```html
983
+ <script type="mountobserver">
984
+ {
985
+ "do": "builtIns.generateIds"
986
+ }
987
+ </script>
988
+
989
+ <script type="module">
990
+ import { MountObserver } from 'mount-observer/MountObserver.js';
991
+
992
+ new MountObserver({
993
+ do: 'builtIns.mountObserverScript'
994
+ }).observe(document);
995
+ </script>
996
+ ```
997
+
998
+ **Scope containers:**
199
999
 
200
- // Access the enhancement
201
- const button = document.querySelector('.enhance-me');
202
- console.log(button.enh.buttonEnh.clickCount); // 0
203
- button.click();
204
- console.log(button.enh.buttonEnh.clickCount); // 1
205
- ```
1000
+ The handler looks for the nearest scope container using `.closest()`:
1001
+ - `<fieldset>` elements
1002
+ - Elements with `[itemscope]` attribute
1003
+ - Falls back to the root node if no scope is found
206
1004
 
207
- The handler:
208
- 1. Searches the imported module for an export with a `spawn` property (the enhancement class)
209
- 2. Calls `element.enh.get(registryItem, context)` to spawn the enhancement
210
- 3. Stores the enhancement instance on `element.enh[enhKey]` if an `enhKey` is provided
1005
+ **Global counter:**
211
1006
 
212
- This works with browsers that don't support scoped custom element registries by polyfilling the `customElementRegistry` property on elements.
1007
+ IDs are generated using a global counter (via `Symbol.for`) to ensure uniqueness across multiple module instances. Generated IDs follow the pattern `gid-0`, `gid-1`, `gid-2`, etc.
213
1008
 
214
1009
 
215
1010
  # Thorough Exposition Begins Here
@@ -282,6 +1077,47 @@ This polyfill in fact only supports this latter option ("matching"), and leaves
282
1077
 
283
1078
  [Implemented as Requirement 1](requirements/Done/Requirement1.md).
284
1079
 
1080
+ ## The observe() method
1081
+
1082
+ The `observe()` method begins observation of elements within the provided node:
1083
+
1084
+ ```typescript
1085
+ async observe(observedNode: Node): Promise<void>
1086
+ ```
1087
+
1088
+ **Parameter: `observedNode`**
1089
+
1090
+ The `observedNode` parameter is the node where observation takes place. In order to support the polyfill, a mutation observer is registered on this node to detect when matching elements are added or removed. All matching elements within this node and its descendants will trigger mount callbacks, as long as it belongs to the same scoped custom element registry as the observed node.
1091
+
1092
+ **Common usage:**
1093
+ ```javascript
1094
+ const observer = new MountObserver({
1095
+ matching: '.my-element',
1096
+ do: (el) => console.log('Mounted:', el)
1097
+ });
1098
+
1099
+ // Observe the entire document
1100
+ await observer.observe(document);
1101
+
1102
+ // Or observe a specific subtree
1103
+ const container = document.querySelector('#container');
1104
+ await observer.observe(container);
1105
+
1106
+ // Or observe within a shadow DOM
1107
+ const shadowRoot = element.shadowRoot;
1108
+ await observer.observe(shadowRoot);
1109
+ ```
1110
+
1111
+ **Note:** An observer can only observe one node at a time. Calling `observe()` again will throw an error. Call `disconnect()` first to observe a different node.
1112
+
1113
+ **Relationship with element.mount():**
1114
+
1115
+ When using the `element.mount()` convenience method, it internally determines which node to pass to `observe()` based on the `scope` option:
1116
+ - `'self'` - Observes the element itself
1117
+ - `'registryRoot'` - Finds and observes the element's registry root
1118
+ - `'registry'` - Finds and observes all DOM nodes that have the same custom element registry
1119
+ - `'shadow'` - Observes the element's shadow root
1120
+ - `'root'` - Observes the element's root node (via `getRootNode()`)
285
1121
 
286
1122
  ## The import key
287
1123
 
@@ -340,90 +1176,156 @@ The polyfill just loads the module into memory right away.
340
1176
  > [!NOTE]
341
1177
  > As a result of the google IO 2024 talks, I became aware that there is some similarity between this proposal and the [speculation rules api](https://developer.chrome.com/blog/speculation-rules-improvements). This motivated the change to the property from "loading" to loadingEagerness above.
342
1178
 
343
- ## Separating JS imperative code from JSON serializable config
1179
+ ## Importing Configuration with configFrom
344
1180
 
1181
+ The `configFrom` property provides a clean way to import MountConfig settings from external modules, enabling better code organization and reusability.
345
1182
 
346
- In order to support pure 100% declarative syntax in the passed in MountConfig argument, we need to be able to import the do function. This is done as follows:
1183
+ **Key benefit for JSON serialization**: One of the most important advantages of `configFrom` is that it allows us to separate non-JSON-serializable settings (like functions and class constructors) from JSON-serializable settings. This makes it possible to keep our inline MountConfig 100% JSON-serializable while still leveraging the full power of JavaScript in our imported configuration modules when needed.
347
1184
 
348
1185
  ```JavaScript
349
- //module myActions.js
350
- const doFunction = function({localName}, {modules, observer, MountConfig, rootNode}){
351
- if(!customElements.get(localName)) {
352
- // Find the first exported class constructor from the module
353
- const ElementClass = Object.values(modules[0]).find(exp =>
354
- typeof exp === 'function' && exp.prototype && exp.prototype.constructor === exp
355
- );
356
- if(ElementClass) {
357
- customElements.define(localName, ElementClass);
358
- }
1186
+ // Inline config - 100% JSON serializable
1187
+ const observer = new MountObserver({
1188
+ matching: '.my-element',
1189
+ configFrom: './my-handlers.js' // Non-serializable code lives here
1190
+ });
1191
+
1192
+ // my-handlers.js - Contains functions and class references
1193
+ export const mountConfig = {
1194
+ whereInstanceOf: HTMLButtonElement, // Class constructor
1195
+ do: (element, context) => { // Function
1196
+ element.addEventListener('click', () => console.log('clicked'));
359
1197
  }
360
- observer.disconnectedSignal.abort();
361
- }
362
- export {doFunction as do}
1198
+ };
1199
+ ```
1200
+
1201
+ This separation is crucial for scenarios like Mount Observer Script Elements (MOSEs) where configuration needs to be embedded in HTML as JSON, but we still want to leverage imperative JavaScript code.
1202
+
1203
+ ### Basic Usage
1204
+
1205
+ Create a configuration module that exports a `mountConfig` constant:
1206
+
1207
+ ```JavaScript
1208
+ // my-config.js
1209
+ export const mountConfig = {
1210
+ matching: '.my-element',
1211
+ do: (element, context) => {
1212
+ element.textContent = 'Configured!';
1213
+ }
1214
+ };
1215
+ ```
363
1216
 
364
- // observer setup
1217
+ Then reference it in your observer:
365
1218
 
1219
+ ```JavaScript
366
1220
  const observer = new MountObserver({
367
- matching:'my-element',
368
- import: [
369
- './my-element.js',
370
- ['./my-element-small.css', {type: 'css'}],
371
- './myActions.js'
372
- ],
373
- reference: 2
1221
+ configFrom: './my-config.js'
374
1222
  });
375
1223
  observer.observe(document);
376
-
377
1224
  ```
378
1225
 
379
- Here "2" refers to the imported module index ('./myActions.js' in this case).
1226
+ ### Multiple Configuration Modules
1227
+
1228
+ You can import multiple config modules. Later configs override earlier ones (left-to-right merge):
380
1229
 
381
- ### How the reference property works
1230
+ ```JavaScript
1231
+ const observer = new MountObserver({
1232
+ configFrom: ['./base-config.js', './override-config.js']
1233
+ });
1234
+ ```
382
1235
 
383
- The `reference` property allows us to call `do` functions from imported modules, enabling 100% JSON-serializable configuration. This is useful when you want to separate imperative code from declarative configuration.
1236
+ ### Inline Config Takes Precedence
384
1237
 
385
- **Key behaviors:**
386
- - The `reference` property can be a single number or an array of numbers, each referring to an import index
387
- - Referenced modules must be JavaScript modules (not CSS, JSON, or HTML imports)
388
- - If a referenced module exports a `do` function, it will be called after the inline `do` callback (if present)
389
- - If a referenced module doesn't export a `do` function, it's silently skipped
390
- - The inline `do` callback runs first, then referenced `do` functions run in the order specified
1238
+ Inline configuration always overrides imported configuration:
391
1239
 
392
- **Important:** Since `do` is a reserved keyword in JavaScript, you must export it using the syntax:
393
- ```javascript
394
- const doFunction = function(element, context) { /* ... */ };
395
- export { doFunction as do };
1240
+ ```JavaScript
1241
+ const observer = new MountObserver({
1242
+ configFrom: './base-config.js',
1243
+ matching: '.custom-selector' // Overrides matching from base-config.js
1244
+ });
396
1245
  ```
397
1246
 
398
- **Validation:** The `reference` property is validated in the constructor:
399
- - Throws an error if `import` is not defined
400
- - Throws an error if any index is out of bounds
401
- - Throws an error if any index points to a non-JS module (e.g., CSS or JSON import)
1247
+ ### Merge Semantics
1248
+
1249
+ - **Shallow merge**: Uses `Object.assign()` for merging
1250
+ - **Merge order**: First configFrom module second configFrom module ... inline config
1251
+ - **Arrays are replaced**: If multiple configs define the same array property, the later array completely replaces the earlier one
1252
+ - **Inline wins**: Inline configuration always takes final precedence
1253
+
1254
+ ### Supported Properties
1255
+
1256
+ Config modules can export any valid MountConfig property, including:
1257
+ - `matching`, `whereInstanceOf`, `withMediaMatching`
1258
+ - `whereObservedRootSizeMatches`, `whereElementIntersectsWith`
1259
+ - `whereConnectionHas`, `withScopePerimeter`
1260
+ - `import`, `do`, `loadingEagerness`
1261
+ - `assignOnMount`, `assignOnDismount`, `stageOnMount`
1262
+ - `mountedElemEmits`, `customData`, `getPlayByPlay`
1263
+
1264
+ ### Functions and Class References
1265
+
1266
+ Config modules can include non-JSON-serializable values like functions and class constructors:
402
1267
 
403
- Multiple references can also be made.
1268
+ ```JavaScript
1269
+ // button-config.js
1270
+ export const mountConfig = {
1271
+ matching: 'button',
1272
+ whereInstanceOf: HTMLButtonElement,
1273
+ do: (element, context) => {
1274
+ element.addEventListener('click', () => {
1275
+ console.log('Button clicked!');
1276
+ });
1277
+ }
1278
+ };
1279
+ ```
404
1280
 
405
- So for example:
1281
+ ### Error Handling
1282
+
1283
+ **Missing mountConfig export:**
1284
+ ```JavaScript
1285
+ // This will throw an error
1286
+ const observer = new MountObserver({
1287
+ configFrom: './module-without-mountConfig.js'
1288
+ });
1289
+ // Error: Module './module-without-mountConfig.js' does not export 'mountConfig'
1290
+ ```
406
1291
 
1292
+ **Duplicate modules:**
407
1293
  ```JavaScript
1294
+ // This will throw an error
1295
+ const observer = new MountObserver({
1296
+ configFrom: ['./config.js', './config.js']
1297
+ });
1298
+ // Error: Duplicate configFrom module: './config.js'
1299
+ ```
1300
+
1301
+ ### Circular Dependency Warning
1302
+
1303
+ Be careful to avoid circular dependencies when using `configFrom`. Config modules should only export configuration and avoid importing modules that create MountObserver instances.
408
1304
 
409
- import: [
410
- ['./my-element-small.css', {type: 'css'}],
411
- './component.js',
412
- './actions1.js',
413
- './actions2.js'
414
- ],
415
- reference: [2, 3] // Both actions1 and actions2 will have their 'do' called if present
1305
+ **Safe pattern:**
1306
+ ```JavaScript
1307
+ // config.js - Only exports configuration
1308
+ export const mountConfig = {
1309
+ matching: '.element',
1310
+ do: (el) => { /* ... */ }
1311
+ };
416
1312
  ```
417
1313
 
418
- [Implemented as [Requirement11](requirements/Done/Requirement11.md)]
1314
+ **Avoid:**
1315
+ ```JavaScript
1316
+ // config.js - Creates circular dependency
1317
+ import { MountObserver } from 'mount-observer/MountObserver.js';
1318
+ // This could cause issues if the importing module also imports MountObserver
1319
+ ```
419
1320
 
420
- ## Media / container queries / instanceOf / custom checks [TODO] out of date
1321
+ ## Media / container queries / instanceOf
421
1322
 
422
1323
  Unlike traditional CSS @import, CSS Modules don't support specifying different imports based on media queries. That can be another condition we can attach (and why not throw in container queries, based on the rootNode?):
423
1324
 
424
1325
  ```JavaScript
425
1326
  const observer = new MountObserver({
426
- select: 'div > p + p ~ span[class$="name"]', // not supported by polyfill
1327
+ // not supported by polyfill
1328
+ select: 'div > p + p ~ span[class$="name"]',
427
1329
  withMediaMatching: '(max-width: 1250px)',
428
1330
  whereObservedRootSizeMatches: '(min-width: 700px)',
429
1331
  whereElementIntersectsWith:{
@@ -436,7 +1338,9 @@ const observer = new MountObserver({
436
1338
  effectiveTypeIn: ["slow-2g"],
437
1339
  },
438
1340
  import: ['./my-element-small.css', {type: 'css'}],
439
- do: ...
1341
+ do: function(mountedElement, ctx){
1342
+ console.log({mountedElement, ctx});
1343
+ }
440
1344
  });
441
1345
  ```
442
1346
 
@@ -444,12 +1348,134 @@ const observer = new MountObserver({
444
1348
  [whereObservedRootSizeMatches implemented]
445
1349
  [whereElementIntersectsWith implemented]
446
1350
  [whereConnectionHas implemented]
1351
+ [whereLocalNameMatches implemented as [RegularExpressionNameMatching](requirements/Done/RegularExpressionNameMatching.md)]
447
1352
 
448
1353
  [withMediaMatching implemented as [Requirement6](requirements/Done/Requirement6.md)]
449
1354
 
1355
+ ## LocalName Pattern Matching
1356
+
1357
+ The `whereLocalNameMatches` property allows filtering elements by their `localName` using regular expressions. This is useful when you need to match elements based on naming patterns rather than CSS selectors.
1358
+
1359
+ ```javascript
1360
+ const observer = new MountObserver({
1361
+ matching: '*', // Match all elements
1362
+ whereLocalNameMatches: /^my-/, // Only mount elements starting with 'my-'
1363
+ do: (element) => {
1364
+ console.log('Mounted:', element.localName);
1365
+ }
1366
+ });
1367
+ observer.observe(document);
1368
+ ```
1369
+
1370
+ **String patterns are automatically converted to RegExp:**
1371
+
1372
+ ```javascript
1373
+ // These are equivalent
1374
+ whereLocalNameMatches: 'button|input'
1375
+ whereLocalNameMatches: /button|input/
1376
+ ```
1377
+
1378
+ **Common use cases:**
1379
+
1380
+ ```javascript
1381
+ // Match custom elements with a specific prefix
1382
+ whereLocalNameMatches: /^app-/
1383
+
1384
+ // Match elements ending with a suffix
1385
+ whereLocalNameMatches: /-widget$/
1386
+
1387
+ // Match multiple element types
1388
+ whereLocalNameMatches: /^(button|input|select)$/
1389
+
1390
+ // Match elements containing a pattern
1391
+ whereLocalNameMatches: /dialog/
1392
+ ```
1393
+
1394
+ **AND condition logic:**
1395
+
1396
+ Like all `where*` properties, `whereLocalNameMatches` forms an AND condition with other filters:
1397
+
1398
+ ```javascript
1399
+ const observer = new MountObserver({
1400
+ matching: '[data-enhanced]', // Must have data-enhanced attribute
1401
+ whereLocalNameMatches: /^custom-/, // AND localName starts with 'custom-'
1402
+ whereInstanceOf: HTMLElement, // AND is an HTMLElement instance
1403
+ do: (element) => { /* ... */ }
1404
+ });
1405
+ ```
1406
+
1407
+ This will only mount elements that satisfy ALL three conditions.
1408
+
1409
+ ## Custom JavaScript Checks with shouldMount
1410
+
1411
+ The `shouldMount` property provides a final JavaScript-based check that runs after all declarative `where*` conditions have passed. This is useful for complex logic that can't be expressed declaratively.
1412
+
1413
+ It's useful to be able to provide this check outside of the do method for separation of concerns reasons, and also because the do function only gets called once, and having this extra check allows us to combine all the checks together in a consistent way.
1414
+
1415
+ ```javascript
1416
+ const observer = new MountObserver({
1417
+ matching: '.protected-feature',
1418
+ shouldMount: (el, ctx) => {
1419
+ // Check user permission
1420
+ const requiredRole = el.dataset.requiredRole;
1421
+ return currentUser.hasRole(requiredRole);
1422
+ },
1423
+ do: (el) => {
1424
+ // Only called if shouldMount returned true
1425
+ enhanceProtectedFeature(el);
1426
+ }
1427
+ });
1428
+ ```
1429
+
1430
+ **Behavior:**
1431
+ - `shouldMount` is called after ALL `where*` conditions pass
1432
+ - If it returns `true`, the element mounts (do callback + mount event)
1433
+ - If it returns `false`, the element does NOT mount (no do callback, no mount event)
1434
+ - If it throws an error, it's treated as `false` and the error is logged
1435
+ - The element can be re-evaluated if removed and re-added to the DOM
1436
+
1437
+ **Use Cases:**
1438
+
1439
+ Authorization checks:
1440
+ ```javascript
1441
+ shouldMount: (el) => currentUser.hasPermission(el.dataset.permission)
1442
+ ```
1443
+
1444
+ Feature flags:
1445
+ ```javascript
1446
+ shouldMount: (el) => featureFlags.isEnabled(el.dataset.feature)
1447
+ ```
1448
+
1449
+ Data validation:
1450
+ ```javascript
1451
+ shouldMount: (el) => {
1452
+ return el.dataset.apiKey &&
1453
+ el.dataset.apiEndpoint &&
1454
+ el.dataset.apiKey.length > 0;
1455
+ }
1456
+ ```
1457
+
1458
+ Complex conditional logic:
1459
+ ```javascript
1460
+ shouldMount: (el, ctx) => {
1461
+ const parent = el.closest('[data-context]');
1462
+ if (!parent) return false;
1463
+
1464
+ const isActive = parent.dataset.context === 'active';
1465
+ const widgetType = el.dataset.widgetType;
1466
+ const enabledWidgets = parent.dataset.enabledWidgets?.split(',') || [];
1467
+
1468
+ return isActive && enabledWidgets.includes(widgetType);
1469
+ }
1470
+ ```
1471
+
1472
+ **Note:** For event-driven mounting (waiting for user clicks, etc.), use the `do` callback with event listeners rather than `shouldMount`. The `shouldMount` callback is for checking conditions, not waiting for events.
1473
+
1474
+ [Implemented as SupportForShouldMount requirement](requirements/Done/SupportForShouldMount.md)
1475
+
450
1476
  ## InstanceOf checks in detail
451
1477
 
452
- Carving out the special "whereInstanceOf" check is provided based on the assumption that there's a performance benefit from doing so. If not, the developer could just add that check inside the "confirm" callback logic (discussed later). For built-in elements, we can alternatively provide the string name, as indicated in the comment above, which certainly makes it JSON serializable, thus making it easy as pie to include in the MOSE JSON payload. I don't think there would be any ambiguity in doing so, which means I believe that answers the mystery in my mind whether it could be part of the low-level checklist that could be done within the c++/rust code / thread.
1478
+ Carving out the special "whereInstanceOf" check is provided based on the assumption that there's a performance benefit from doing so. If not, the developer could just add that check inside the "shouldMount" callback logic (discussed later). For built-in elements, we can alternatively provide the string name, as indicated in the comment above, which certainly makes it JSON serializable, thus making it easy as pie to include in the MOSE JSON payload. I don't think there would be any ambiguity in doing so, which means I believe that answers the mystery in my mind whether it could be part of the low-level checklist that could be done within the c++/rust code / thread.
453
1479
 
454
1480
  The picture becomes murkier for custom elements. The best solution in that case seems to be to utilize customElements.getName(...) as a basis for the match, but at first glance, that could preclude being able to use base classes which a family of custom elements subclass, if that superclass isn't itself a custom element. I suppose the solution to this conundrum, when warranted, is simply to burden the developer with defining a custom element for the superclass, and thus assigning it a name, applicable within ShadowDOM scopes as needed, even though it isn't actually necessarily used for any live custom elements. This would require already having imported the base class, only benefitting from lazy loading the code needed for each sub class, which might not always be all that high as a percentage, compared to the base class.
455
1481
 
@@ -462,52 +1488,67 @@ However, where this support for "whereInstanceOf" would be *most* helpful is whe
462
1488
 
463
1489
  -->
464
1490
 
465
- ### Referenced whereInstanceOf
1491
+ ## Custom Element Registry Matching
1492
+
1493
+ MountObserver automatically respects scoped custom element registry boundaries. When observing a root node, only elements that share the same `customElementRegistry` as the root node will be mounted by default. This is an implicit AND condition that applies to all observations.
466
1494
 
467
- Similar to the `do` function, the `whereInstanceOf` check can also be moved to imported modules for 100% JSON-serializable configuration:
1495
+ **How it works:**
468
1496
 
469
1497
  ```javascript
470
- // module mySettings.js
471
- const doFunction = function({localName}, {modules, observer, MountConfig, rootNode}) {
472
- if(!customElements.get(localName)) {
473
- customElements.define(localName, modules[1].MyElement);
474
- }
475
- observer.disconnectedSignal.abort();
476
- };
1498
+ // Observe document - only mounts elements in the global registry
1499
+ const observer1 = new MountObserver({
1500
+ matching: '.my-element',
1501
+ do: (el) => { /* ... */ }
1502
+ });
1503
+ observer1.observe(document);
1504
+
1505
+ // Observe shadow root - only mounts elements in that shadow root's registry
1506
+ const shadowRoot = host.attachShadow({ mode: 'open' });
1507
+ const observer2 = new MountObserver({
1508
+ matching: '.my-element',
1509
+ do: (el) => { /* ... */ }
1510
+ });
1511
+ observer2.observe(shadowRoot);
1512
+ ```
1513
+
1514
+ **Registry matching logic:**
1515
+
1516
+ The implementation is straightforward - it compares the `customElementRegistry` property of the root node with the `customElementRegistry` property of each candidate element:
1517
+
1518
+ ```javascript
1519
+ const registriesMatch = rootNode.customElementRegistry === element.customElementRegistry;
1520
+ ```
477
1521
 
478
- const whereInstanceOf = [HTMLMarqueeElement, SVGElement];
1522
+ **Default behavior** (same registry):
1523
+ - Elements with matching registries → mount ✓
1524
+ - Elements with different registries → don't mount ✓
479
1525
 
480
- export { doFunction as do, whereInstanceOf };
1526
+ **Inverted behavior** with `whereDifferentCustomElementRegistry: true`:
1527
+ - Elements with matching registries → don't mount ✓
1528
+ - Elements with different registries → mount ✓
481
1529
 
482
- // my local module
1530
+ **Use case for inverted matching:**
1531
+ ```javascript
1532
+ // Mount elements from OTHER registries (cross-registry observation)
483
1533
  const observer = new MountObserver({
484
- matching: 'my-element',
485
- import: [
486
- ['./my-element-small.css', {type: 'css'}],
487
- './my-element.js',
488
- './mySettings.js'
489
- ],
490
- reference: 2
1534
+ matching: '.external-component',
1535
+ whereDifferentCustomElementRegistry: true,
1536
+ do: (el) => { /* Handle elements from different registries */ }
491
1537
  });
492
- observer.observe(document);
1538
+ observer.observe(shadowRoot);
493
1539
  ```
494
1540
 
495
- **Behavior:**
496
- - **Combining checks**: If both inline `whereInstanceOf` and referenced `whereInstanceOf` exist, they are AND'd together (element must match both)
497
- - **Multiple references**: If multiple referenced modules export `whereInstanceOf`, the element must match ALL of them (AND logic)
498
- - **Validation**: Referenced `whereInstanceOf` is validated after imports load. Throws an error if not a Constructor or array of Constructors
499
- - **Optional export**: If a referenced module doesn't export `whereInstanceOf`, it's silently ignored
500
- - **Timing**:
501
- - With lazy loading (default): Inline `whereInstanceOf` is checked first (before imports), then referenced checks happen after imports load
502
- - With `loadingEagerness: 'eager'`: Both inline and referenced checks happen together after imports are loaded
1541
+ **Behavior across browser versions:**
1542
+ - **Pre-Chrome 146**: Both `customElementRegistry` properties are `undefined`, so `undefined === undefined` is `true` and elements match (backward compatible)
1543
+ - **Chrome 146+ with scoped registries**: Elements are filtered by registry reference equality
503
1544
 
504
- This optimization ensures that with lazy loading, elements that don't match the inline `whereInstanceOf` won't trigger unnecessary imports.
1545
+ This ensures that when we observe a shadow root with a scoped registry, we won't accidentally mount elements from the parent document or other shadow roots with different registries (unless explicitly requested with `whereDifferentCustomElementRegistry: true`). The registry check happens automatically before any other `where*` conditions are evaluated.
505
1546
 
506
- [Implemented as [Requirement12](requirements/Done/Requirement12.md)]
1547
+ [Implemented as [ExcludeMatchingElementsWhereCustomElementRegistriesDon'tMatch](requirements/ExcludeMatchingElementsWhereCustomElementRegistriesDon'tMatch.md)]
507
1548
 
508
1549
  ## Element Mount Extension
509
1550
 
510
- 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).
1551
+ For even more convenience, we 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).
511
1552
 
512
1553
  ```JavaScript
513
1554
  import 'mount-observer/ElementMountExtension.js';
@@ -532,7 +1573,7 @@ Scope options (via `options.scope`):
532
1573
  - `'self'`: Observes only this element
533
1574
  - `'root'`: Observes the root node (document or shadow root)
534
1575
  - `'shadow'`: Observes the element's shadowRoot (throws error if none exists)
535
- - `Element`: Observes a custom element you specify
1576
+ - `Element`: Observes a custom element we specify
536
1577
 
537
1578
  This is especially useful for web components that want to observe their own shadow DOM or scoped registry:
538
1579
 
@@ -567,14 +1608,183 @@ class MyComponent extends HTMLElement {
567
1608
  Browser support: Works in all browsers, but scoped registry features require Chrome 146+ or latest WebKit/Safari.
568
1609
 
569
1610
  [Implemented as CustomElementRegistryMounting requirement](requirements/Done/CustomElementRegistryMounting.md).
570
-
571
1611
 
1612
+ ### Global Propagation with `mountGlobally()`
1613
+
1614
+ The `mountGlobally()` method extends `mount()` to automatically propagate mount observers across custom element registry boundaries and shadow DOM scopes. This is useful for bootstrapping core handlers that should work everywhere, regardless of scoped registries. It should be used sparingly, as a last resort, probably limited to things that should arguably be built into the platform.
1615
+
1616
+ ```JavaScript
1617
+ import 'mount-observer/ElementMountExtension.js';
1618
+
1619
+ // Mount globally - propagates to all child registries and shadow roots
1620
+ await document.mountGlobally({
1621
+ matching: '.target',
1622
+ do: (el) => {
1623
+ el.setAttribute('data-mounted', 'true');
1624
+ }
1625
+ });
1626
+ ```
1627
+
1628
+ The `mountGlobally()` method:
1629
+ - Mounts the config in the current registry first (using `mount()`)
1630
+ - Creates two propagators that automatically mount in:
1631
+ - Elements with different custom element registries (`whereDifferentCustomElementRegistry: true`)
1632
+ - Shadow roots within the same registry (custom elements with shadow DOM)
1633
+ - Waits for custom elements to be defined before mounting (ensures shadow roots exist)
1634
+ - Recursively propagates through nested shadow roots
1635
+
1636
+ This enables "viral" propagation of mount observers, perfect for bootstrapping core handlers like `builtIns.mountObserverScript`:
1637
+
1638
+ ```JavaScript
1639
+ // Bootstrap mount observer script support globally
1640
+ await document.mountGlobally({
1641
+ do: 'builtIns.mountObserverScript'
1642
+ });
1643
+
1644
+ // Now MOSE scripts work everywhere, even in scoped registries
1645
+ ```
1646
+
1647
+ Both `Element.prototype.mountGlobally()` and `ShadowRoot.prototype.mountGlobally()` are available.
1648
+
1649
+ [Implemented as goViral requirement](requirements/Done/goViral.md).
1650
+
1651
+ ## Hierarchical Observer Composition with the `with` Property
1652
+
1653
+ The `with` property enables hierarchical composition of MountObservers, allowing a parent observer to declaratively create and manage multiple sub-observers that observe the same root node. This provides a clean way to organize complex observation scenarios and coordinate multiple observers.
1654
+
1655
+ ### Basic Usage
1656
+
1657
+ ```JavaScript
1658
+ const observer = new MountObserver({
1659
+ matching: '.container',
1660
+ with: {
1661
+ // Sub-observer for custom elements
1662
+ registry: {
1663
+ matching: 'my-element',
1664
+ import: './my-element.js',
1665
+ do: 'builtIns.defineCustomElement'
1666
+ },
1667
+ // Sub-observer for styles
1668
+ styles: {
1669
+ matching: '.styled',
1670
+ import: './styles.css'
1671
+ }
1672
+ }
1673
+ });
1674
+
1675
+ await observer.observe(document);
1676
+ ```
1677
+
1678
+ ### How It Works
1679
+
1680
+ 1. **Automatic Creation**: When the parent observer's `observe()` method is called, it automatically creates sub-observers for each entry in the `with` property.
1681
+
1682
+ 2. **Same Root Node**: All sub-observers observe the same root node as the parent.
1683
+
1684
+ 3. **Independent Configuration**: Each sub-observer operates independently with its own configuration. Sub-observers do NOT inherit properties from the parent.
1685
+
1686
+ 4. **Automatic Lifecycle**: Sub-observers are automatically disconnected when the parent disconnects.
1687
+
1688
+ 5. **Unlimited Nesting**: Sub-observers can have their own `with` property for unlimited nesting depth.
1689
+
1690
+ ### Accessing Sub-Observers in Handlers
1691
+
1692
+ Sub-observers are accessible in mount handlers via the `context.withObservers` property:
1693
+
1694
+ ```JavaScript
1695
+ const observer = new MountObserver({
1696
+ matching: '.parent',
1697
+ with: {
1698
+ registry: { matching: 'custom-element' },
1699
+ styles: { matching: '.styled' }
1700
+ },
1701
+ do: (el, ctx) => {
1702
+ // Access sub-observers with type safety
1703
+ const registryObserver = ctx.withObservers?.registry;
1704
+ const stylesObserver = ctx.withObservers?.styles;
1705
+
1706
+ if (registryObserver) {
1707
+ console.log('Registry observer:', registryObserver);
1708
+ console.log('Mounted elements:', registryObserver.mountedElements);
1709
+ }
1710
+ }
1711
+ });
1712
+ ```
1713
+
1714
+ ### Nested Sub-Observers
1715
+
1716
+ Sub-observers can have their own sub-observers, creating a tree structure:
1717
+
1718
+ ```JavaScript
1719
+ const observer = new MountObserver({
1720
+ matching: '.root',
1721
+ with: {
1722
+ level1: {
1723
+ matching: '.level1',
1724
+ with: {
1725
+ level2: {
1726
+ matching: '.level2',
1727
+ do: (el) => console.log('Level 2 mounted:', el)
1728
+ }
1729
+ }
1730
+ }
1731
+ }
1732
+ });
1733
+ ```
1734
+
1735
+ ### Use Case: Cross-Scope Registry Management
1736
+
1737
+ A practical use case is managing custom elements across different scoped registries:
1738
+
1739
+ ```JavaScript
1740
+ const observer = new MountObserver({
1741
+ matching: 'div[shadowroot]',
1742
+ with: {
1743
+ // Observe elements in the main registry
1744
+ mainRegistry: {
1745
+ matching: 'my-element',
1746
+ whereDifferentCustomElementRegistry: false,
1747
+ do: 'builtIns.defineCustomElement'
1748
+ },
1749
+ // Observe elements in shadow DOM registries
1750
+ shadowRegistry: {
1751
+ matching: 'shadow-element',
1752
+ whereDifferentCustomElementRegistry: true,
1753
+ do: 'builtIns.defineScopedCustomElement'
1754
+ }
1755
+ }
1756
+ });
1757
+ ```
1758
+
1759
+ ### Type Safety
572
1760
 
1761
+ When using TypeScript, the keys in the `with` property are inferred and provide autocomplete:
573
1762
 
1763
+ ```TypeScript
1764
+ const observer = new MountObserver({
1765
+ matching: '.parent',
1766
+ with: {
1767
+ registry: { matching: 'my-element' },
1768
+ styles: { import: './styles.css' }
1769
+ },
1770
+ do: (el, ctx) => {
1771
+ ctx.withObservers?.registry // ✓ TypeScript knows this exists
1772
+ ctx.withObservers?.unknown // ✗ TypeScript error
1773
+ }
1774
+ });
1775
+ ```
574
1776
 
1777
+ ### Key Benefits
575
1778
 
1779
+ 1. **Declarative Composition**: Define complex observer hierarchies in a single configuration
1780
+ 2. **Automatic Lifecycle**: Sub-observers are created and cleaned up automatically
1781
+ 3. **Independent Operation**: Each sub-observer has its own configuration and state
1782
+ 4. **Type Safety**: Full TypeScript support with key inference
1783
+ 5. **Unlimited Nesting**: Create arbitrarily deep observer hierarchies
576
1784
 
1785
+ ### Known Limitations
577
1786
 
1787
+ - **Circular References**: The library does not detect or prevent circular references in `with` configurations. Avoid configurations where observer A's `with` references observer B, and B's `with` references A, as this will cause a stack overflow.
578
1788
 
579
1789
  ## Mount Observer Script Elements (MOSEs)
580
1790
 
@@ -582,14 +1792,15 @@ Following an approach similar to the [speculation api](https://developer.chrome.
582
1792
 
583
1793
  ```JavaScript
584
1794
  // myPackage/myDefiner.js
585
- //my all powerful custom element definer
586
- const doFunction = function({localNme}, {modules, observer}){
587
- if(!customElements.get(localName)) {
588
- customElements.define(localName, modules[1].MyElement);
1795
+ // My all powerful custom element definer
1796
+ export const mountConfig = {
1797
+ do: function({localName}, {modules, observer}) {
1798
+ if(!customElements.get(localName)) {
1799
+ customElements.define(localName, modules[1].MyElement);
1800
+ }
1801
+ observer.disconnectedSignal.abort();
589
1802
  }
590
- observer.disconnectedSignal.abort();
591
- }
592
- export { doFunction as do };
1803
+ };
593
1804
  ```
594
1805
 
595
1806
  ```html
@@ -597,18 +1808,14 @@ export { doFunction as do };
597
1808
  {
598
1809
  "select":"my-element",
599
1810
  "import": [
600
- ["./my-element-small.css", {type: "css"}],
601
- "./my-element.js",
602
- "myPackage/myDefiner.js
1811
+ ["./my-element-small.css", {"type": "css"}],
1812
+ "./my-element.js"
603
1813
  ],
604
- "reference": 2
1814
+ "configFrom": "myPackage/myDefiner.js"
605
1815
  }
606
1816
  </script>
607
1817
  ```
608
1818
 
609
- To keep this proposal / polyfill of reasonable size, mount observer script elements has its own [repo / sub-proposal](https://github.com/bahrus/mount-observer-script-element). There's much more to it, including support for inheritance across containing scoped custom element registries.
610
-
611
- But I think it's important to think about this way of making the mount observer declarative, as it provides one significant reason why we place so much emphasis on making sure that the mount observer settings (MountConfig) is as JSON serializable as possible.
612
1819
 
613
1820
 
614
1821
  ## Binding from a distance
@@ -648,7 +1855,7 @@ This allows developers to create "stylesheet" like capabilities.
648
1855
 
649
1856
  ## Registering reusable handlers with MountObserver.define
650
1857
 
651
- To make MountConfig configurations more JSON-serializable and encourage code reuse, you can register handler classes with string names and reference them by name:
1858
+ To make MountConfig configurations more JSON-serializable and encourage code reuse, we can register handler classes with string names and reference them by name:
652
1859
 
653
1860
  ```JavaScript
654
1861
  import {EvtRt} from 'mount-observer/EvtRt.js';
@@ -701,25 +1908,6 @@ const observer = new MountObserver({
701
1908
 
702
1909
  Handlers execute in the order specified. If a handler constructor throws an error, execution stops and subsequent handlers won't run.
703
1910
 
704
- ### Interaction with the reference property
705
-
706
- When both `do` (with string/array) and `reference` are specified, the execution order is:
707
-
708
- 1. Inline `do` functions and registered handlers (from `do` strings), in whatever order they appear
709
- 2. Referenced `do` functions (from `reference` property)
710
-
711
- ```JavaScript
712
- MountObserver.define('setup', SetupHandler);
713
-
714
- const observer = new MountObserver({
715
- matching: 'button',
716
- import: './button-actions.js',
717
- reference: 0,
718
- do: ['setup', (el) => { el.dataset.ready = 'true'; }]
719
- });
720
- // Execution order: setup handler, inline function, then imported do function
721
- ```
722
-
723
1911
  ### Handler requirements
724
1912
 
725
1913
  Registered handlers must be classes (constructors) that accept `(mountedElement: Element, ctx: MountContext)` as constructor parameters. They can be:
@@ -758,13 +1946,13 @@ MountObserver.define('myHandler', Handler2); // Error: myHandler already in use
758
1946
 
759
1947
  ### Global registry
760
1948
 
761
- The handler registry is global and shared across all MountObserver instances, similar to the custom elements registry. Once a handler is registered, it can be used by any MountObserver instance in your application.
1949
+ The handler registry is global and shared across all MountObserver instances, similar to the global custom elements registry. Once a handler is registered, it can be used by any MountObserver instance in your application.
762
1950
 
763
1951
  [Implemented as [Requirement14](requirements/Done/Requirement14.md)]
764
1952
 
765
1953
  ### Handler defaults with static properties
766
1954
 
767
- Registered handler classes can specify default MountConfig properties using static class properties. When you reference a handler by name, its static properties are automatically merged with your inline configuration, with inline config always taking precedence:
1955
+ Registered handler classes can specify default MountConfig properties using static class properties. When we reference a handler by name, its static properties are automatically merged with your inline configuration, with inline config always taking precedence:
768
1956
 
769
1957
  ```JavaScript
770
1958
  import {EvtRt} from 'mount-observer/EvtRt.js';
@@ -869,7 +2057,7 @@ export default class MyElement extends HTMLElement {
869
2057
  }
870
2058
 
871
2059
  // main.js
872
- import { MountObserver } from 'mount-observer';
2060
+ import { MountObserver } from 'mount-observer/MountObserver.js';
873
2061
 
874
2062
  const observer = new MountObserver({
875
2063
  matching: 'my-element',
@@ -1019,7 +2207,7 @@ Using `assignOn*` provides several benefits:
1019
2207
 
1020
2208
  ### Dynamically updating assignGingerly configuration
1021
2209
 
1022
- The `MountObserver` class provides a public `assignGingerly()` method that allows you to merge new updates into the observer. This is useful for responding to user actions or application state changes:
2210
+ The `MountObserver` class provides a public `assignGingerly()` method that allows us to merge new updates into the observer. This is useful for responding to user actions or application state changes:
1023
2211
 
1024
2212
  ```JavaScript
1025
2213
  const observer = new MountObserver({
@@ -1046,7 +2234,7 @@ await observer.assignGingerly({
1046
2234
 
1047
2235
  3. **Applies to future elements**: Future elements that mount will receive the merged configuration.
1048
2236
 
1049
- 4. **Starting without initial config**: You can call the method even if no `assignGingerly` was specified in the constructor:
2237
+ 4. **Starting without initial config**: We can call the method even if no `assignGingerly` was specified in the constructor:
1050
2238
 
1051
2239
  ```JavaScript
1052
2240
  const observer = new MountObserver({
@@ -1080,7 +2268,7 @@ The method is async because the assign-gingerly library is loaded dynamically wh
1080
2268
 
1081
2269
  ## Reversible property assignment with stageOnMount
1082
2270
 
1083
- While `assignOnMount` and `assignOnDismount` provide permanent property assignments, sometimes you need temporary changes that automatically reverse when elements dismount. The `stageOnMount` property provides this capability using the `assignTentatively` function from assign-gingerly:
2271
+ While `assignOnMount` and `assignOnDismount` provide permanent property assignments, sometimes we need temporary changes that automatically reverse when elements dismount. The `stageOnMount` property provides this capability using the `assignTentatively` function from assign-gingerly:
1084
2272
 
1085
2273
  ```JavaScript
1086
2274
  const observer = new MountObserver({
@@ -1104,7 +2292,7 @@ When a matching button mounts, these properties are applied. When it dismounts (
1104
2292
  2. **Applies the new properties** when elements mount
1105
2293
  3. **Automatically reverses** to original values when elements dismount
1106
2294
 
1107
- This is different from `assignOnMount`/`assignOnDismount`, where you must explicitly specify both the mount and dismount values.
2295
+ This is different from `assignOnMount`/`assignOnDismount`, where we must explicitly specify both the mount and dismount values.
1108
2296
 
1109
2297
  ### When to use stageOnMount vs assignOnMount
1110
2298
 
@@ -1228,7 +2416,7 @@ const observer = new MountObserver({
1228
2416
  observer.observe(document);
1229
2417
  ```
1230
2418
 
1231
- This dispatches a `custom-ready` event from each matching button element when it mounts. Events bubble by default, so you can listen at the document level:
2419
+ This dispatches a `custom-ready` event from each matching button element when it mounts. Events bubble by default, so we can listen at the document level:
1232
2420
 
1233
2421
  ```JavaScript
1234
2422
  document.addEventListener('custom-ready', (e) => {
@@ -1396,7 +2584,7 @@ observer.observe(document);
1396
2584
 
1397
2585
  ## Element-specific lifecycle notifications with getNotifier
1398
2586
 
1399
- While the MountObserver dispatches lifecycle events (mount, dismount, disconnect) at the observer level, sometimes you need to listen for events specific to a single element. The `getNotifier()` method returns an EventTarget that dispatches filtered events for only that element.
2587
+ While the MountObserver dispatches lifecycle events (mount, dismount, disconnect) at the observer level, sometimes we need to listen for events specific to a single element. The `getNotifier()` method returns an EventTarget that dispatches filtered events for only that element.
1400
2588
 
1401
2589
  ### Basic usage
1402
2590
 
@@ -1494,7 +2682,7 @@ getNotifier(element: Element): EventTarget
1494
2682
  [Implemented as [Requirement13](requirements/Done/Requirement13.md)]
1495
2683
 
1496
2684
 
1497
- ## Extra lazy loading
2685
+ <!-- ## Extra lazy loading
1498
2686
 
1499
2687
  By default, the matches would be reported as soon as an element matching the criterion is found or added into the DOM, inside the node specified by rootNode.
1500
2688
 
@@ -1509,7 +2697,7 @@ const observer = new MountObserver({
1509
2697
  },
1510
2698
  import: './my-element.js'
1511
2699
  });
1512
- ```
2700
+ ``` -->
1513
2701
 
1514
2702
 
1515
2703
 
@@ -1518,7 +2706,8 @@ const observer = new MountObserver({
1518
2706
  Subscribing can be done via:
1519
2707
 
1520
2708
  ```JavaScript
1521
- observer.addEventListener('confirm', e => {
2709
+ //[TODO] not implemented yet
2710
+ observer.addEventListener('shouldMount', e => {
1522
2711
  e.isSatisfied = true; //or false to prevent the mount event below
1523
2712
  });
1524
2713
  observer.addEventListener('mount', e => {
@@ -1533,18 +2722,23 @@ observer.addEventListener('dismount', e => {
1533
2722
  observer.addEventListener('disconnect', e => {
1534
2723
  ...
1535
2724
  });
2725
+ //[TODO]
1536
2726
  observer.addEventListener('move', e => {
1537
2727
  ...
1538
2728
  });
2729
+ //[TODO]
1539
2730
  observer.addEventListener('reconnect', e => {
1540
2731
  ...
1541
2732
  });
2733
+ //[TODO]
1542
2734
  observer.addEventListener('reconfirm', e => {
1543
2735
  ...
1544
2736
  });
2737
+ //[TODO]
1545
2738
  observer.addEventListener('exit', e => {
1546
2739
  ...
1547
2740
  });
2741
+ //[TODO]
1548
2742
  observer.addEventListener('forget', e => {
1549
2743
  ...
1550
2744
  });