mount-observer 0.1.13 → 0.1.15
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/Events.js +9 -0
- package/Events.ts +12 -0
- package/MountObserver.js +21 -0
- package/MountObserver.ts +27 -0
- package/README.md +406 -69
- package/{DefineCustomElementHandler.js → handlers/DefineCustomElement.js} +103 -99
- package/handlers/DefineCustomElement.ts +123 -0
- package/handlers/EnhanceMountedElement.js +99 -0
- package/handlers/EnhanceMountedElement.ts +116 -0
- package/handlers/Events.js +110 -0
- package/handlers/EvtRt.js +59 -0
- package/handlers/GenIds.js +37 -0
- package/handlers/GenIds.ts +45 -0
- package/handlers/HTMLInclude.js +393 -0
- package/handlers/HTMLInclude.ts +453 -0
- package/handlers/HoistTemplate.js +77 -0
- package/handlers/HoistTemplate.ts +89 -0
- package/handlers/MountObserver.js +941 -0
- package/handlers/MountObserverScript.js +78 -0
- package/handlers/MountObserverScript.ts +89 -0
- package/handlers/ScriptExport.js +83 -0
- package/handlers/ScriptExport.ts +97 -0
- package/handlers/SharedMutationObserver.js +78 -0
- package/handlers/arr.js +16 -0
- package/handlers/connectionMonitor.js +122 -0
- package/handlers/elementIntersection.js +73 -0
- package/handlers/emitEvents.js +187 -0
- package/handlers/getRegistryRoot.js +52 -0
- package/handlers/loadImports.js +129 -0
- package/handlers/mediaQuery.js +90 -0
- package/handlers/rootSizeObserver.js +131 -0
- package/handlers/upShadowSearch.js +70 -0
- package/handlers/withScopePerimeter.js +22 -0
- package/index.js +2 -2
- package/index.ts +2 -2
- package/package.json +13 -3
- package/types/assign-gingerly/types.d.ts +244 -0
- package/types/be-a-beacon/types.d.ts +3 -0
- package/types/global.d.ts +29 -0
- package/types/id-generation/types.d.ts +26 -0
- package/types/mount-observer/types.d.ts +330 -0
- package/upShadowSearch.js +6 -3
- package/upShadowSearch.ts +6 -3
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ The following features have been implemented and tested:
|
|
|
10
10
|
|
|
11
11
|
### Core Functionality
|
|
12
12
|
- ✅ **matching**: CSS selector-based element matching
|
|
13
|
+
- ✅ **whenDefined**: Wait for custom elements to be defined before mounting
|
|
13
14
|
- ✅ **whereInstanceOf**: Constructor-based element filtering (single or array)
|
|
14
15
|
- ✅ **whereLocalNameMatches**: Regular expression-based localName filtering
|
|
15
16
|
- ✅ **shouldMount**: Custom JavaScript check for complex mounting conditions
|
|
@@ -85,7 +86,7 @@ There is quite a bit of functionality this proposal would open up that is exceed
|
|
|
85
86
|
|
|
86
87
|
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
|
|
|
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.
|
|
89
|
+
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. Learn more about [scoped custom element registries](https://developer.chrome.com/blog/scoped-registries).
|
|
89
90
|
|
|
90
91
|
|
|
91
92
|
### Most significant use cases
|
|
@@ -103,7 +104,7 @@ The extra flexibility this new primitive would provide could be quite useful to
|
|
|
103
104
|
|
|
104
105
|
## Quick Examples of the Most Common Use Cases
|
|
105
106
|
|
|
106
|
-
Before getting into the weeds, let's demonstrate the
|
|
107
|
+
Before getting into the weeds, let's demonstrate a few of the most prominent use cases:
|
|
107
108
|
|
|
108
109
|
### Use Case 1: Custom Attribute Enhancement
|
|
109
110
|
|
|
@@ -217,58 +218,136 @@ The handler:
|
|
|
217
218
|
3. Stores the enhancement instance on `element.enh[enhKey]` if an `enhKey` is provided
|
|
218
219
|
|
|
219
220
|
|
|
220
|
-
##
|
|
221
|
+
## Exposing Module Exports from Script Elements
|
|
221
222
|
|
|
222
|
-
The `builtIns.
|
|
223
|
+
The `builtIns.scriptExport` handler solves a long-standing limitation: accessing ES module exports from script elements. It also provides a clean way to import JSON and other data formats declaratively.
|
|
224
|
+
|
|
225
|
+
### Problem 1: ES Module Export Access
|
|
226
|
+
|
|
227
|
+
The browser doesn't expose module exports from `<script type="module">` elements. There's been a [decade-old proposal](https://github.com/whatwg/html/issues/1013) to add this, but it remains unimplemented.
|
|
228
|
+
|
|
229
|
+
**The Solution:**
|
|
223
230
|
|
|
224
231
|
```html
|
|
225
|
-
<!--
|
|
232
|
+
<!-- Use nomodule to prevent browser from loading it separately -->
|
|
226
233
|
<script nomodule src="./config.js" id="myConfig"></script>
|
|
227
234
|
|
|
228
|
-
<!-- Load JSON data with import assertion -->
|
|
229
|
-
<script nomodule src="./data.json" with-type="json" id="myData"></script>
|
|
230
|
-
|
|
231
235
|
<script type="module">
|
|
232
236
|
import { MountObserver } from 'mount-observer/MountObserver.js';
|
|
233
237
|
|
|
234
|
-
// Handler provides matching and whereInstanceOf via static properties
|
|
235
238
|
const observer = new MountObserver({
|
|
236
|
-
do: 'builtIns.
|
|
239
|
+
do: 'builtIns.scriptExport'
|
|
237
240
|
});
|
|
238
241
|
observer.observe(document);
|
|
239
242
|
|
|
240
|
-
// Access the
|
|
243
|
+
// Access the module's exports via element.export
|
|
241
244
|
const config = document.getElementById('myConfig').export;
|
|
242
|
-
|
|
245
|
+
console.log(config.apiKey);
|
|
246
|
+
console.log(config.endpoints);
|
|
247
|
+
</script>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Why `nomodule`?**
|
|
251
|
+
- Prevents the browser from loading the module separately
|
|
252
|
+
- Avoids having the module loaded twice in memory
|
|
253
|
+
- The handler imports it once and exposes exports via `element.export`
|
|
254
|
+
|
|
255
|
+
### Problem 2: Declarative JSON Import
|
|
256
|
+
|
|
257
|
+
Importing JSON typically requires `fetch()` or dynamic `import()` with assertions. This handler provides a declarative alternative.
|
|
258
|
+
|
|
259
|
+
**The Solution:**
|
|
260
|
+
|
|
261
|
+
```html
|
|
262
|
+
<!-- Load JSON data -->
|
|
263
|
+
<script src="./data.json" type="json" id="myData"></script>
|
|
264
|
+
|
|
265
|
+
<!-- Also supports full MIME types -->
|
|
266
|
+
<script src="./config.json" type="application/json" id="config"></script>
|
|
267
|
+
<script src="./linked-data.json" type="application/ld+json" id="linkedData"></script>
|
|
268
|
+
|
|
269
|
+
<script type="module">
|
|
270
|
+
import { MountObserver } from 'mount-observer/MountObserver.js';
|
|
243
271
|
|
|
244
|
-
|
|
245
|
-
|
|
272
|
+
const observer = new MountObserver({
|
|
273
|
+
do: 'builtIns.scriptExport'
|
|
274
|
+
});
|
|
275
|
+
observer.observe(document);
|
|
276
|
+
|
|
277
|
+
// Access the JSON data via element.export.default
|
|
278
|
+
const data = document.getElementById('myData').export.default;
|
|
279
|
+
console.log(data.items);
|
|
280
|
+
console.log(data.config);
|
|
246
281
|
</script>
|
|
247
282
|
```
|
|
248
283
|
|
|
284
|
+
<details>
|
|
285
|
+
<summary>Talking points</summary>
|
|
286
|
+
|
|
287
|
+
**Why no `nomodule` for JSON?**
|
|
288
|
+
- The browser ignores script elements with non-standard type attributes
|
|
289
|
+
- No risk of double-loading since the browser won't load it at all
|
|
290
|
+
- The handler imports it with the appropriate JSON assertion
|
|
291
|
+
|
|
292
|
+
**Supported JSON types:**
|
|
293
|
+
- `type="json"` - Simple and clean
|
|
294
|
+
- `type="application/json"` - Standard MIME type
|
|
295
|
+
- `type="application/ld+json"` - JSON-LD linked data
|
|
296
|
+
- Any type containing "json" triggers JSON import assertion
|
|
297
|
+
|
|
249
298
|
**How it works:**
|
|
250
|
-
1.
|
|
251
|
-
2.
|
|
252
|
-
3.
|
|
253
|
-
4.
|
|
254
|
-
5.
|
|
299
|
+
1. Matches `script[src]` elements (via static properties)
|
|
300
|
+
2. Skips `type="module"` scripts (browser-handled)
|
|
301
|
+
3. Processes scripts with `nomodule` attribute OR type containing "json"
|
|
302
|
+
4. Resolves the `src` relative to the document
|
|
303
|
+
5. Imports with appropriate assertion (JSON if type contains "json")
|
|
304
|
+
6. Stores the imported module on `element.export`
|
|
305
|
+
7. Dispatches a `resolved` event with the imported module
|
|
306
|
+
|
|
307
|
+
**Reusing imported modules:**
|
|
308
|
+
|
|
309
|
+
The handler stores the imported module on the script element's `export` property and dispatches a `resolved` event. This allows other code to access the module without re-importing:
|
|
310
|
+
|
|
311
|
+
```html
|
|
312
|
+
<script src="./data.json" type="json" id="myData"></script>
|
|
313
|
+
|
|
314
|
+
<script type="module">
|
|
315
|
+
const dataScript = document.getElementById('myData');
|
|
316
|
+
|
|
317
|
+
// Listen for the resolved event
|
|
318
|
+
dataScript.addEventListener('resolved', (e) => {
|
|
319
|
+
console.log('Data loaded:', e.export);
|
|
320
|
+
// e.export contains the imported module
|
|
321
|
+
// For JSON: e.export.default contains the data
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Or access directly after processing
|
|
325
|
+
// dataScript.export will contain the imported module
|
|
326
|
+
</script>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
This is particularly useful when multiple components need to access the same data or configuration without triggering multiple imports.
|
|
255
330
|
|
|
256
331
|
**Benefits:**
|
|
257
|
-
-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
-
|
|
332
|
+
- Access ES module exports from script elements (finally!)
|
|
333
|
+
- Declarative JSON loading without fetch
|
|
334
|
+
- Prevents double-loading of modules
|
|
335
|
+
- Clean, intuitive syntax
|
|
261
336
|
- Works with relative and absolute URLs
|
|
337
|
+
- No need to specify `matching` or `whereInstanceOf` (handler provides defaults)
|
|
262
338
|
|
|
263
339
|
**Use cases:**
|
|
264
|
-
-
|
|
265
|
-
-
|
|
340
|
+
- Accessing configuration module exports in HTML
|
|
341
|
+
- Loading JSON data declaratively
|
|
266
342
|
- Progressive enhancement with module loading
|
|
267
|
-
- Declarative dependency management
|
|
343
|
+
- Declarative dependency management
|
|
344
|
+
- Loading JSON-LD structured data
|
|
345
|
+
|
|
346
|
+
</details>
|
|
268
347
|
|
|
269
348
|
## Mount Observer Script Elements (MOSEs)
|
|
270
349
|
|
|
271
|
-
|
|
350
|
+
Inspired by the [speculation rules api](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API) `builtIns.mountObserverScript` handler enables fully declarative mount observer configuration using `<script type="mountobserver">` elements. This provides the ultimate in HTML-first progressive enhancement.
|
|
272
351
|
|
|
273
352
|
```html
|
|
274
353
|
<!-- Inline JSON configuration -->
|
|
@@ -295,12 +374,73 @@ The `builtIns.mountObserverScript` handler enables fully declarative mount obser
|
|
|
295
374
|
</script>
|
|
296
375
|
```
|
|
297
376
|
|
|
377
|
+
<details>
|
|
378
|
+
<summary>Talking points</summary>
|
|
379
|
+
|
|
298
380
|
**How it works:**
|
|
299
381
|
1. The handler matches `script[type="mountobserver"]` elements (via static properties)
|
|
300
382
|
2. If the script has a `src` attribute, imports JSON from that URL
|
|
301
383
|
3. Otherwise, parses the script's textContent as JSON
|
|
302
|
-
4.
|
|
303
|
-
5.
|
|
384
|
+
4. Supports both single config objects and arrays of configs
|
|
385
|
+
5. Stores the parsed config on `scriptElement.export` for reuse
|
|
386
|
+
6. Dispatches a `resolved` event with the parsed config
|
|
387
|
+
7. Calls `scriptElement.mount(config)` for each configuration
|
|
388
|
+
8. The `mount()` method creates a MountObserver for that configuration
|
|
389
|
+
|
|
390
|
+
**Reusing parsed configurations:**
|
|
391
|
+
|
|
392
|
+
The handler optimizes performance by storing the parsed configuration on the script element's `export` property and dispatching a `resolved` event. When the same script element is observed again (e.g., after being cloned or moved), the handler reuses the existing `export` instead of re-parsing:
|
|
393
|
+
|
|
394
|
+
```html
|
|
395
|
+
<script type="mountobserver" id="myConfig">
|
|
396
|
+
{
|
|
397
|
+
"matching": ".my-element",
|
|
398
|
+
"do": "builtIns.logToConsole"
|
|
399
|
+
}
|
|
400
|
+
</script>
|
|
401
|
+
|
|
402
|
+
<script type="module">
|
|
403
|
+
const configScript = document.getElementById('myConfig');
|
|
404
|
+
|
|
405
|
+
// Listen for the resolved event (fires only once on first parse)
|
|
406
|
+
configScript.addEventListener('resolved', (e) => {
|
|
407
|
+
console.log('Config loaded:', e.export);
|
|
408
|
+
// e.export contains the parsed configuration
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Or access directly after processing
|
|
412
|
+
// configScript.export will contain the parsed config
|
|
413
|
+
|
|
414
|
+
// If observed again, the handler will reuse configScript.export
|
|
415
|
+
// without re-parsing or firing the resolved event
|
|
416
|
+
</script>
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
This is particularly useful when inheriting mount observer configurations across shadow DOM boundaries, as the parsed config can be reused without re-parsing JSON. The `resolved` event fires only once (on first parse), but the handler will still process the configuration on subsequent observations.
|
|
420
|
+
|
|
421
|
+
**Multiple configs in one script:**
|
|
422
|
+
|
|
423
|
+
You can define multiple mount observer configurations in a single script element using a JSON array:
|
|
424
|
+
|
|
425
|
+
```html
|
|
426
|
+
<script type="mountobserver">
|
|
427
|
+
[
|
|
428
|
+
{
|
|
429
|
+
"do": "builtIns.hoistTemplate"
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
"do": "builtIns.HTMLInclude"
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
"matching": "my-button",
|
|
436
|
+
"import": "./my-button.js",
|
|
437
|
+
"do": "builtIns.defineCustomElement"
|
|
438
|
+
}
|
|
439
|
+
]
|
|
440
|
+
</script>
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
This is equivalent to having three separate `<script type="mountobserver">` elements, but more concise. Each config in the array is processed independently and creates its own MountObserver.
|
|
304
444
|
|
|
305
445
|
**Benefits:**
|
|
306
446
|
- Zero JavaScript required for observer configuration
|
|
@@ -347,12 +487,16 @@ The `builtIns.mountObserverScript` handler enables fully declarative mount obser
|
|
|
347
487
|
</script>
|
|
348
488
|
```
|
|
349
489
|
|
|
490
|
+
</details>
|
|
491
|
+
|
|
350
492
|
## Hoisting Templates for Performance
|
|
351
493
|
|
|
352
|
-
The `builtIns.hoistTemplate` handler optimizes template usage by moving a template element's content from shadow roots to `document.head`.
|
|
494
|
+
The `builtIns.hoistTemplate` handler optimizes template usage by moving a template element's content from shadow roots to `document.head`.
|
|
353
495
|
|
|
354
496
|
**Why hoist templates?**
|
|
355
497
|
|
|
498
|
+
Template hoisting is particularly useful when you need to share conditional or repeated templates across multiple custom element instances.
|
|
499
|
+
|
|
356
500
|
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
501
|
- Reduces memory usage (one template instead of many copies)
|
|
358
502
|
- Improves cloning performance (single source of truth)
|
|
@@ -467,6 +611,9 @@ The `builtIns.HTMLInclude` handler enables declarative HTML fragment reuse withi
|
|
|
467
611
|
</script>
|
|
468
612
|
```
|
|
469
613
|
|
|
614
|
+
<details>
|
|
615
|
+
<summary>More discussion
|
|
616
|
+
|
|
470
617
|
**What happens:**
|
|
471
618
|
1. The handler finds templates with `src` attributes starting with `#`
|
|
472
619
|
2. Searches for an element with that ID (across shadow boundaries)
|
|
@@ -507,6 +654,8 @@ The `builtIns.HTMLInclude` handler enables declarative HTML fragment reuse withi
|
|
|
507
654
|
</script>
|
|
508
655
|
```
|
|
509
656
|
|
|
657
|
+
</details>
|
|
658
|
+
|
|
510
659
|
### Shadow DOM Support
|
|
511
660
|
|
|
512
661
|
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.
|
|
@@ -857,9 +1006,68 @@ The handler provides helpful error messages:
|
|
|
857
1006
|
|
|
858
1007
|
- Uses WeakMap caching for repeated ID lookups
|
|
859
1008
|
- Efficient for scenarios like periodic tables with many repeated elements
|
|
860
|
-
- Searches across shadow boundaries using `upShadowSearch`
|
|
1009
|
+
- Searches across shadow boundaries using `upShadowSearch` (registry-aware)
|
|
1010
|
+
- Respects scoped custom element registry boundaries
|
|
861
1011
|
- Cleans up cache entries when elements are garbage collected
|
|
862
1012
|
|
|
1013
|
+
**MOSE Export Optimization:**
|
|
1014
|
+
|
|
1015
|
+
When cloning live DOM elements (not templates) that contain Mount Observer Script Elements (MOSEs) across shadow DOM boundaries, the HTMLInclude handler automatically copies the parsed `export` property from source scripts to cloned scripts. This optimization avoids re-parsing JSON when the same MOSE configuration is reused in multiple shadow roots.
|
|
1016
|
+
|
|
1017
|
+
**How it works:**
|
|
1018
|
+
1. Detects when cloning a live element (not a template) from a different root node
|
|
1019
|
+
2. Finds all `script[type="mountobserver"]` elements in both source and clone
|
|
1020
|
+
3. Matches scripts by their `id` attribute
|
|
1021
|
+
4. Copies the `export` property from source to clone (by reference)
|
|
1022
|
+
5. If source script hasn't been processed yet, waits for the `resolved` event
|
|
1023
|
+
|
|
1024
|
+
**Example:**
|
|
1025
|
+
|
|
1026
|
+
```html
|
|
1027
|
+
<!-- Source element with MOSE in light DOM -->
|
|
1028
|
+
<div id="observer-config">
|
|
1029
|
+
<script type="mountobserver" id="my-config">
|
|
1030
|
+
{
|
|
1031
|
+
"matching": ".interactive",
|
|
1032
|
+
"import": "./interactive.js",
|
|
1033
|
+
"do": "builtIns.enhanceMountedElement"
|
|
1034
|
+
}
|
|
1035
|
+
</script>
|
|
1036
|
+
<div class="interactive">Content</div>
|
|
1037
|
+
</div>
|
|
1038
|
+
|
|
1039
|
+
<!-- Clone into shadow DOM -->
|
|
1040
|
+
<my-component>
|
|
1041
|
+
#shadow
|
|
1042
|
+
<template src="#observer-config"></template>
|
|
1043
|
+
</my-component>
|
|
1044
|
+
|
|
1045
|
+
<script type="module">
|
|
1046
|
+
import { MountObserver } from 'mount-observer/MountObserver.js';
|
|
1047
|
+
|
|
1048
|
+
// Process MOSEs in light DOM
|
|
1049
|
+
new MountObserver({
|
|
1050
|
+
do: 'builtIns.mountObserverScript'
|
|
1051
|
+
}).observe(document.body);
|
|
1052
|
+
|
|
1053
|
+
// Clone into shadow roots
|
|
1054
|
+
new MountObserver({
|
|
1055
|
+
do: 'builtIns.HTMLInclude'
|
|
1056
|
+
}).observe(document);
|
|
1057
|
+
</script>
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
**Benefits:**
|
|
1061
|
+
- **Performance**: JSON is parsed only once, not for each clone
|
|
1062
|
+
- **Memory efficiency**: Cloned scripts share the same export object
|
|
1063
|
+
- **Consistency**: All clones use identical configuration
|
|
1064
|
+
- **Automatic**: No manual intervention required
|
|
1065
|
+
|
|
1066
|
+
**Requirements:**
|
|
1067
|
+
- Source and clone must be in different root nodes (document vs shadow root)
|
|
1068
|
+
- MOSE scripts must have `id` attributes for matching
|
|
1069
|
+
- Source script must be processed by `builtIns.mountObserverScript` or `builtIns.scriptExport` before cloning
|
|
1070
|
+
|
|
863
1071
|
[Implemented as MatchingInsertionsAndDeletionsWithIntraDocumentHTMLIncludes requirement](requirements/Done/MatchingInsertionsAndDeletionsWithIntraDocumentHTMLIncludes.md)
|
|
864
1072
|
|
|
865
1073
|
## Automatic ID Generation with genIds
|
|
@@ -873,18 +1081,23 @@ The `builtIns.generateIds` handler automatically generates unique IDs for elemen
|
|
|
873
1081
|
- Automatically updates ID references in attributes (aria-labelledby, for, etc.)
|
|
874
1082
|
- Provides shorthand syntax for common patterns
|
|
875
1083
|
- Handles deferred attribute activation
|
|
1084
|
+
- Removes `disabled` from fieldsets after processing
|
|
876
1085
|
|
|
877
1086
|
**Basic usage:**
|
|
878
1087
|
|
|
879
1088
|
```html
|
|
880
1089
|
<fieldset disabled>
|
|
881
|
-
<label
|
|
882
|
-
|
|
1090
|
+
<label>
|
|
1091
|
+
LHS: <input data-id={{lhs}}>
|
|
1092
|
+
</label>
|
|
883
1093
|
|
|
884
|
-
<label
|
|
885
|
-
|
|
1094
|
+
<label for=rhs>
|
|
1095
|
+
RHS: <input data-id={{rhs}}>
|
|
1096
|
+
</label>
|
|
886
1097
|
|
|
887
|
-
<
|
|
1098
|
+
<template -id defer-🎚️ 🎚️='on if isEqual, based on #{{lhs}} and #{{rhs}}.'>
|
|
1099
|
+
<div>LHS === RHS</div>
|
|
1100
|
+
</template>
|
|
888
1101
|
</fieldset>
|
|
889
1102
|
|
|
890
1103
|
<script type="module">
|
|
@@ -901,23 +1114,41 @@ The `builtIns.generateIds` handler automatically generates unique IDs for elemen
|
|
|
901
1114
|
|
|
902
1115
|
1. The handler watches for elements with the `-id` attribute (the trigger)
|
|
903
1116
|
2. Finds the nearest scope container (fieldset, [itemscope], or root)
|
|
904
|
-
3. Generates unique IDs for elements with `data-id`, `#`, `@`, or `|` attributes
|
|
1117
|
+
3. Generates unique IDs for elements with `data-id={{name}}`, `#`, `@`, or `|` attributes
|
|
905
1118
|
4. Replaces `#{{name}}` references with generated IDs in attributes
|
|
906
1119
|
5. Removes `-id` and `defer-*` attributes after processing
|
|
907
1120
|
6. Removes `disabled` from fieldset containers
|
|
908
1121
|
|
|
909
|
-
**
|
|
1122
|
+
**Result:**
|
|
910
1123
|
|
|
911
1124
|
```html
|
|
912
1125
|
<fieldset>
|
|
1126
|
+
<label>
|
|
1127
|
+
LHS: <input id=gid-0 data-id=lhs>
|
|
1128
|
+
</label>
|
|
1129
|
+
|
|
1130
|
+
<label for=rhs>
|
|
1131
|
+
RHS: <input id=gid-1 data-id=rhs>
|
|
1132
|
+
</label>
|
|
1133
|
+
|
|
1134
|
+
<template 🎚️='on if isEqual, based on #gid-0 and #gid-1.'>
|
|
1135
|
+
<div>LHS === RHS</div>
|
|
1136
|
+
</template>
|
|
1137
|
+
</fieldset>
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
**Shorthand attributes:**
|
|
1141
|
+
|
|
1142
|
+
```html
|
|
1143
|
+
<fieldset disabled>
|
|
913
1144
|
<!-- # uses element's tag name -->
|
|
914
|
-
<
|
|
1145
|
+
<my-element #></my-element> <!-- becomes id=gid-0 data-id=my-element -->
|
|
915
1146
|
|
|
916
1147
|
<!-- @ uses element's name attribute -->
|
|
917
|
-
<input @ name="email" type="email"> <!-- becomes data-id=
|
|
1148
|
+
<input @ name="email" type="email"> <!-- becomes id=gid-1 data-id=email -->
|
|
918
1149
|
|
|
919
1150
|
<!-- | uses element's itemprop attribute -->
|
|
920
|
-
<span | itemprop="price">$99</span> <!-- becomes data-id=
|
|
1151
|
+
<span | itemprop="price">$99</span> <!-- becomes id=gid-2 data-id=price -->
|
|
921
1152
|
|
|
922
1153
|
<button -id>Generate IDs</button>
|
|
923
1154
|
</fieldset>
|
|
@@ -928,31 +1159,55 @@ The `builtIns.generateIds` handler automatically generates unique IDs for elemen
|
|
|
928
1159
|
The `data-id` attribute supports special symbols that trigger side effects:
|
|
929
1160
|
|
|
930
1161
|
```html
|
|
931
|
-
<
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
<
|
|
953
|
-
|
|
1162
|
+
<form>
|
|
1163
|
+
<fieldset disabled>
|
|
1164
|
+
<label>
|
|
1165
|
+
LHS: <input data-id="{{@. lhs}}">
|
|
1166
|
+
</label>
|
|
1167
|
+
|
|
1168
|
+
<label for=rhs>
|
|
1169
|
+
RHS: <span contenteditable data-id="{{|.% rhs}}">
|
|
1170
|
+
</label>
|
|
1171
|
+
|
|
1172
|
+
<template -id defer-🎚️ 🎚️='on if isEqual, based on #{{lhs}} and #{{rhs}}.'>
|
|
1173
|
+
<div>LHS === RHS</div>
|
|
1174
|
+
</template>
|
|
1175
|
+
</fieldset>
|
|
1176
|
+
</form>
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
**Result:**
|
|
1180
|
+
|
|
1181
|
+
```html
|
|
1182
|
+
<form>
|
|
1183
|
+
<fieldset>
|
|
1184
|
+
<label>
|
|
1185
|
+
LHS: <input name=lhs class=lhs id=gid-0 data-id=lhs>
|
|
1186
|
+
</label>
|
|
1187
|
+
|
|
1188
|
+
<label for=rhs>
|
|
1189
|
+
RHS: <span contenteditable itemprop=rhs class=rhs part=rhs id=gid-1 data-id=rhs>
|
|
1190
|
+
</label>
|
|
1191
|
+
|
|
1192
|
+
<template 🎚️='on if isEqual, based on #gid-0 and #gid-1.'>
|
|
1193
|
+
<div>LHS === RHS</div>
|
|
1194
|
+
</template>
|
|
1195
|
+
</fieldset>
|
|
1196
|
+
</form>
|
|
954
1197
|
```
|
|
955
1198
|
|
|
1199
|
+
**Symbol meanings:**
|
|
1200
|
+
|
|
1201
|
+
| Symbol | Attribute | Meaning |
|
|
1202
|
+
|--------|-------------------|------------------------------------------------------------------------------|
|
|
1203
|
+
| @ | name | Second letter of name, common in social media for selecting names |
|
|
1204
|
+
| \| | itemprop | "Pipe" resembles itemprop, half of dollar sign, looks like an I |
|
|
1205
|
+
| $ | itemscope+itemprop| Combination of S for Scope and Pipe |
|
|
1206
|
+
| % | part | Starts with p, percent indicates proportion |
|
|
1207
|
+
| . | class | CSS selector |
|
|
1208
|
+
|
|
1209
|
+
Multiple symbols can be combined: `data-id="{{@.% myName}}"` adds name, class, and part attributes.
|
|
1210
|
+
|
|
956
1211
|
**Deferred attributes:**
|
|
957
1212
|
|
|
958
1213
|
Use `defer-*` prefix to prevent attributes from being applied until IDs are generated:
|
|
@@ -961,8 +1216,7 @@ Use `defer-*` prefix to prevent attributes from being applied until IDs are gene
|
|
|
961
1216
|
<fieldset disabled>
|
|
962
1217
|
<!-- These attributes won't work until IDs are generated -->
|
|
963
1218
|
<label defer-for="for: #{{email}}">Email:</label>
|
|
964
|
-
<input data-id=
|
|
965
|
-
<span data-id="emailHelp">Enter your email address</span>
|
|
1219
|
+
<input data-id={{email}} type="email">
|
|
966
1220
|
|
|
967
1221
|
<button -id>Activate Form</button>
|
|
968
1222
|
</fieldset>
|
|
@@ -1352,6 +1606,87 @@ const observer = new MountObserver({
|
|
|
1352
1606
|
|
|
1353
1607
|
[withMediaMatching implemented as [Requirement6](requirements/Done/Requirement6.md)]
|
|
1354
1608
|
|
|
1609
|
+
## Waiting for Custom Element Definitions
|
|
1610
|
+
|
|
1611
|
+
The `whenDefined` property allows you to wait for custom elements to be defined before mounting elements. This ensures that elements are only processed after their custom element definitions are available, preventing issues with undefined custom elements.
|
|
1612
|
+
|
|
1613
|
+
```javascript
|
|
1614
|
+
const observer = new MountObserver({
|
|
1615
|
+
matching: 'my-element',
|
|
1616
|
+
whenDefined: 'my-element', // Wait for my-element to be defined
|
|
1617
|
+
do: (element) => {
|
|
1618
|
+
console.log('my-element is now defined and mounted');
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
observer.observe(document);
|
|
1622
|
+
|
|
1623
|
+
// Later, when the custom element is defined
|
|
1624
|
+
customElements.define('my-element', class extends HTMLElement {
|
|
1625
|
+
// ...
|
|
1626
|
+
});
|
|
1627
|
+
```
|
|
1628
|
+
|
|
1629
|
+
**Waiting for multiple custom elements:**
|
|
1630
|
+
|
|
1631
|
+
You can specify an array of tag names to wait for all of them to be defined:
|
|
1632
|
+
|
|
1633
|
+
```javascript
|
|
1634
|
+
const observer = new MountObserver({
|
|
1635
|
+
matching: 'my-element, another-element',
|
|
1636
|
+
whenDefined: ['my-element', 'another-element'], // Wait for both
|
|
1637
|
+
do: (element) => {
|
|
1638
|
+
console.log('Both elements are defined, mounting:', element.localName);
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
```
|
|
1642
|
+
|
|
1643
|
+
**How it works:**
|
|
1644
|
+
|
|
1645
|
+
1. The check happens first, before any other `where*` conditions
|
|
1646
|
+
2. Uses `customElements.whenDefined()` for each specified tag name
|
|
1647
|
+
3. Uses the `customElementRegistry` of the observed root node
|
|
1648
|
+
4. Only checks once per observer instance (doesn't re-check on subsequent mounts)
|
|
1649
|
+
5. The `observe()` method waits for all definitions before processing elements
|
|
1650
|
+
|
|
1651
|
+
**Common use cases:**
|
|
1652
|
+
|
|
1653
|
+
```javascript
|
|
1654
|
+
// Lazy load and wait for custom element definition
|
|
1655
|
+
const observer = new MountObserver({
|
|
1656
|
+
matching: 'my-button',
|
|
1657
|
+
whenDefined: 'my-button',
|
|
1658
|
+
import: './my-button.js',
|
|
1659
|
+
do: 'builtIns.defineCustomElement'
|
|
1660
|
+
});
|
|
1661
|
+
|
|
1662
|
+
// Wait for dependencies before enhancing
|
|
1663
|
+
const observer = new MountObserver({
|
|
1664
|
+
matching: '.needs-custom-elements',
|
|
1665
|
+
whenDefined: ['base-element', 'helper-element'],
|
|
1666
|
+
do: (element) => {
|
|
1667
|
+
// Safe to interact with custom elements now
|
|
1668
|
+
const base = element.querySelector('base-element');
|
|
1669
|
+
const helper = element.querySelector('helper-element');
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
```
|
|
1673
|
+
|
|
1674
|
+
**AND condition logic:**
|
|
1675
|
+
|
|
1676
|
+
Like all `where*` properties, `whenDefined` forms an AND condition with other filters. However, it's checked first as a prerequisite before evaluating other conditions:
|
|
1677
|
+
|
|
1678
|
+
```javascript
|
|
1679
|
+
const observer = new MountObserver({
|
|
1680
|
+
matching: 'my-element',
|
|
1681
|
+
whenDefined: 'my-element', // Checked FIRST
|
|
1682
|
+
whereInstanceOf: HTMLElement, // Then checked
|
|
1683
|
+
whereLocalNameMatches: /^my-/, // Then checked
|
|
1684
|
+
do: (element) => { /* ... */ }
|
|
1685
|
+
});
|
|
1686
|
+
```
|
|
1687
|
+
|
|
1688
|
+
[whenDefined implemented as [SupportForWhenDefined](requirements/SupportForWhenDefined.md)]
|
|
1689
|
+
|
|
1355
1690
|
## LocalName Pattern Matching
|
|
1356
1691
|
|
|
1357
1692
|
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.
|
|
@@ -1492,6 +1827,8 @@ However, where this support for "whereInstanceOf" would be *most* helpful is whe
|
|
|
1492
1827
|
|
|
1493
1828
|
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.
|
|
1494
1829
|
|
|
1830
|
+
Learn more about [scoped custom element registries in Chrome's blog post](https://developer.chrome.com/blog/scoped-registries).
|
|
1831
|
+
|
|
1495
1832
|
**How it works:**
|
|
1496
1833
|
|
|
1497
1834
|
```javascript
|
|
@@ -1548,7 +1885,7 @@ This ensures that when we observe a shadow root with a scoped registry, we won't
|
|
|
1548
1885
|
|
|
1549
1886
|
## Element Mount Extension
|
|
1550
1887
|
|
|
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).
|
|
1888
|
+
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](https://developer.chrome.com/blog/scoped-registries) (Chrome 146+, latest WebKit/Safari).
|
|
1552
1889
|
|
|
1553
1890
|
```JavaScript
|
|
1554
1891
|
import 'mount-observer/ElementMountExtension.js';
|
|
@@ -1605,13 +1942,13 @@ class MyComponent extends HTMLElement {
|
|
|
1605
1942
|
}
|
|
1606
1943
|
```
|
|
1607
1944
|
|
|
1608
|
-
Browser support: Works in all browsers, but scoped registry features require Chrome 146+ or latest WebKit/Safari.
|
|
1945
|
+
Browser support: Works in all browsers, but [scoped registry features](https://developer.chrome.com/blog/scoped-registries) require Chrome 146+ or latest WebKit/Safari.
|
|
1609
1946
|
|
|
1610
1947
|
[Implemented as CustomElementRegistryMounting requirement](requirements/Done/CustomElementRegistryMounting.md).
|
|
1611
1948
|
|
|
1612
1949
|
### Global Propagation with `mountGlobally()`
|
|
1613
1950
|
|
|
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.
|
|
1951
|
+
The `mountGlobally()` method extends `mount()` to automatically propagate mount observers across [custom element registry](https://developer.chrome.com/blog/scoped-registries) 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
1952
|
|
|
1616
1953
|
```JavaScript
|
|
1617
1954
|
import 'mount-observer/ElementMountExtension.js';
|