assign-gingerly 0.0.22 → 0.0.23

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
@@ -23,13 +23,17 @@ One can achieve the same functionality with a little more work, and "playing nic
23
23
 
24
24
  Not only does this polyfill package allow merging data properties onto objects that are expecting them, this polyfill also provides the ability to merge *augmented behavior* onto run-time objects without sub classing all such objects of the same type. This includes the ability to spawn an instance of a class and "merge" it into the API of the original object in an elegant way that is easy to wrap one's brain around, without ever blocking access to the original object or breaking it.
25
25
 
26
+ So we are providing a form of the ["Decorator Pattern"](https://en.wikipedia.org/wiki/Decorator_pattern) or perhaps more accurately the [Extension Object Pattern](https://swiftorial.com/swiftlessons/design-patterns/structural-patterns/extension-object-pattern) as tailored for the quirks of the web.
26
27
 
28
+ ## Custom Registries
27
29
 
28
- So we are providing a form of the ["Decorator Pattern"](https://en.wikipedia.org/wiki/Decorator_pattern) or perhaps more accurately the [Extension Object Pattern](https://swiftorial.com/swiftlessons/design-patterns/structural-patterns/extension-object-pattern) as tailored for the quirks of the web.
30
+ On top of that, this polyfill package builds on the newly minted Custom Element Registry, adding additional sub-registries:
31
+
32
+ 1. [enhancementRegistry](#enhancement-registry-addendum-to-the-custom-element-registry) object on top of the customElementRegistry object associated with all elements, to be able to lazy load object extensions on demand while avoiding namespace conflicts, and, importantly, as a basis for defining custom attributes associated with the enhancements.
29
33
 
30
- ## Custom Enhancement Registry
34
+ 2. [itemscopeRegistry for Itemscope Managers](#itemscoperegistry) to automatically associate a function prototype or class instance with the itemscope attribute of an HTMLElement.
31
35
 
32
- On top of that, this polyfill package builds on the newly minted Custom Element Registry, adding an additional EnhancementRegistry object on top of the customElementRegistry object associated with all elements, to be able to manage namespace conflicts, and, importantly, as a basis for defining custom attributes associated with the enhancements.
36
+ 3. Custom Element Features [TODO]
33
37
 
34
38
  So in our view this package helps fill the void left by not supporting the "is" attribute for built-in elements (but is not a complete solution, just a critical building block). Mount-observer, mount-observer-script-element, and custom enhancements builds on top of the critical role that assign-gingerly plays.
35
39
 
@@ -296,7 +300,7 @@ interface IEnhancementRegistryItem<T = any, TObjToExtend = any> {
296
300
  spawn: {new(objToExtend: TObjToExtend, ctx: SpawnContext, initVals: Partial<T>): T}
297
301
  symlinks?: {[key: symbol]: keyof T}
298
302
  // Optional: for element enhancement access
299
- enhKey?: string
303
+ enhKey?: string | symbol
300
304
  // Optional: automatic attribute parsing
301
305
  withAttrs?: AttrPatterns<T>
302
306
  }
@@ -332,7 +336,7 @@ EnhancementRegistry.push([
332
336
  },
333
337
  spawn: MyEnhancement,
334
338
  },{
335
-
339
+ enhKey: 'mellowYellow',
336
340
  symlinks: {
337
341
  [isMellow]: 'isMellow'
338
342
  },
@@ -511,12 +515,16 @@ The prototype extensions are non-enumerable and won't appear in `Object.keys()`
511
515
 
512
516
  -->
513
517
 
514
- ## Custom Element Registry Integration (Chrome 146+)
518
+ ## Enhancement Registry Addendum to the Custom Element Registry
515
519
 
516
- This package includes support for Chrome's scoped custom element registries, which automatically integrates dependency injection in harmony with scoped custom elements DOM sections or ShadowRoots.
520
+ This package polyfill adds an "enhancementRegistry" registry on the CustomElementRegistry prototype.
521
+
522
+ In this way, we achieve dependency injection in harmony with scoped custom elements DOM registry scope islands.
517
523
 
518
524
  > [!NOTE]
519
- > Safari/WebKit played a critical role in pushing scoped custom element registries forward, and announced with little fanfare or documentation that [Safari 26 supports it](https://developer.apple.com/documentation/safari-release-notes/safari-26-release-notes). However, the Playwright test machinery's cross platform Safari test browser doesn't yet support it.
525
+ > Safari/WebKit played a critical role in pushing scoped custom element registries forward, and announced with little fanfare or documentation that [Safari 26 supports it](https://developer.apple.com/documentation/safari-release-notes/safari-26-release-notes). However, the Playwright test machinery's cross platform Safari test browser doesn't yet support it. For now, only Chrome 146+ has been tested / vetted for this functionality.
526
+ >
527
+ > For more information about scoped custom element registries, see [Chrome's announcement and guide](https://developer.chrome.com/blog/scoped-registries).
520
528
 
521
529
  <details>
522
530
  <summary>Automatic Registry Population</summary>
@@ -680,7 +688,7 @@ class Enhancement<T> {
680
688
 
681
689
  All parameters are optional for backward compatibility with existing code.
682
690
 
683
- Note that the class need not extend any base class or leverage any mixins. In fact, ES5 prototype functions can be used, and in both cases are instanted using new .... Arrow functions cannot be used.
691
+ Note that the class need not extend any base class or leverage any mixins. In fact, ES5 prototype functions can be used, and in both cases are instantiated using new .... Arrow functions cannot be used.
684
692
 
685
693
  <details>
686
694
  <summary>Passing Custom Context</summary>
@@ -1652,7 +1660,10 @@ interface AttrPatterns<T> {
1652
1660
  interface AttrConfig<T> {
1653
1661
  mapsTo?: keyof T | '.'; // Target property name (or '.' to spread)
1654
1662
  instanceOf?: string | Function; // Type for default parser
1655
- parser?: (v: string | null) => any; // Custom parser function
1663
+ parser?:
1664
+ | ((v: string | null) => any) // Inline parser function
1665
+ | string // Named parser from globalParserRegistry
1666
+ | [string, string]; // [CustomElementName, StaticMethodName]
1656
1667
  }
1657
1668
  ```
1658
1669
 
@@ -1772,7 +1783,7 @@ The following parsers are pre-registered in `globalParserRegistry`:
1772
1783
 
1773
1784
  **Custom Element Static Method Parsers:**
1774
1785
 
1775
- You can also reference static methods on custom elements using dot notation:
1786
+ You can reference static methods on custom elements using tuple syntax `[elementName, methodName]`:
1776
1787
 
1777
1788
  ```TypeScript
1778
1789
  class MyWidget extends HTMLElement {
@@ -1786,29 +1797,47 @@ class MyWidget extends HTMLElement {
1786
1797
  }
1787
1798
  customElements.define('my-widget', MyWidget);
1788
1799
 
1789
- // Reference custom element parsers
1800
+ // Reference custom element parsers using tuple syntax
1790
1801
  const config = {
1791
1802
  base: 'data-',
1792
1803
  value: '${base}value',
1793
1804
  _value: {
1794
- parser: 'my-widget.parseSpecialFormat' // element-name.methodName
1805
+ parser: ['my-widget', 'parseSpecialFormat'] // [element-name, methodName]
1806
+ },
1807
+ title: '${base}title',
1808
+ _title: {
1809
+ parser: ['my-widget', 'parseWithPrefix']
1795
1810
  }
1796
1811
  };
1812
+
1813
+ const result = parseWithAttrs(element, config);
1797
1814
  ```
1798
1815
 
1799
- **Parser Resolution Order:**
1816
+ **Parser Resolution:**
1817
+
1818
+ When a parser is specified, it can be:
1819
+
1820
+ 1. **Inline function** - `parser: (v) => v.toUpperCase()` - Used directly
1821
+ 2. **String reference** - `parser: 'timestamp'` - Looks up in `globalParserRegistry`
1822
+ 3. **Tuple reference** - `parser: ['my-widget', 'parseMethod']` - Looks up static method on custom element constructor
1823
+
1824
+ **Error Handling:**
1800
1825
 
1801
- When a string parser is specified:
1826
+ The tuple syntax provides clear error messages:
1802
1827
 
1803
- 1. **Check for dot notation** - If parser contains `.`, try to resolve as `element-name.methodName`
1804
- 2. **Try custom element** - Look up element in `customElements` registry and check for static method
1805
- 3. **Fall back to global registry** - If custom element not found, check `globalParserRegistry`
1806
- 4. **Throw error** - If not found anywhere, throw descriptive error
1828
+ ```TypeScript
1829
+ // Element not found
1830
+ parser: ['non-existent', 'method']
1831
+ // Error: Cannot resolve parser [non-existent, method]: custom element "non-existent" not found
1832
+
1833
+ // Method not found
1834
+ parser: ['my-widget', 'nonExistent']
1835
+ // Error: Cannot resolve parser [my-widget, nonExistent]: static method "nonExistent" not found on custom element "my-widget"
1807
1836
 
1808
- This allows:
1809
- - Element-specific parsers to be scoped to their custom elements
1810
- - Fallback to global registry for shared parsers
1811
- - Dot notation in global registry names (e.g., `'utils.parseDate'`)
1837
+ // String not found in registry
1838
+ parser: 'unknown'
1839
+ // Error: Parser "unknown" not found in globalParserRegistry. If you want to reference a custom element static method, use tuple syntax: ["element-name", "methodName"]
1840
+ ```
1812
1841
 
1813
1842
  **Example: Organizing Parsers**
1814
1843
 
@@ -2452,6 +2481,414 @@ console.log(result);
2452
2481
 
2453
2482
  -->
2454
2483
 
2484
+ ## Itemscope Managers (Chrome 146+)
2485
+
2486
+ Itemscope Managers provide a way to manage DOM fragments and their associated data/view models for elements with the `itemscope` attribute. This feature enables frameworks and libraries to manage light children of web components, DOM fragments from looping constructs, and scenarios where custom element wrapping is not feasible.
2487
+
2488
+ > [!NOTE]
2489
+ > This feature requires Chrome 146+ with scoped custom element registry support. It follows the same browser support requirements as the Enhancement Registry integration.
2490
+ >
2491
+ > For more information about scoped custom element registries, see [Chrome's announcement and guide](https://developer.chrome.com/blog/scoped-registries).
2492
+
2493
+ ### Why Itemscope Managers?
2494
+
2495
+ The `itemscope` attribute (from the Microdata specification) provides a semantic way to mark elements that represent distinct data items. ItemScope Managers build on this by allowing us to:
2496
+
2497
+ - **Manage light children**: Attach behavior to light DOM children of web components without wrapping them in custom elements
2498
+ - **Handle template loops**: Manage repeated DOM fragments generated by template systems
2499
+ - **Avoid custom element overhead**: Enhance elements where custom element registration isn't appropriate or possible
2500
+ - **Separate concerns**: Keep data/view model logic separate from the DOM structure
2501
+
2502
+ ### Basic Usage
2503
+
2504
+ ```html
2505
+ <div itemscope="user-card">
2506
+ <h2>User Profile</h2>
2507
+ <p itemprop="name"></p>
2508
+ <p itemprop="email"></p>
2509
+ </div>
2510
+ ```
2511
+
2512
+ ```TypeScript
2513
+ import 'assign-gingerly/object-extension.js';
2514
+
2515
+ // Define a manager class
2516
+ class UserCardManager {
2517
+ element;
2518
+ name = '';
2519
+ email = '';
2520
+
2521
+ constructor(element, initVals) {
2522
+ this.element = element;
2523
+ if (initVals) {
2524
+ Object.assign(this, initVals);
2525
+ this.render();
2526
+ }
2527
+ }
2528
+
2529
+ render() {
2530
+ this.element.querySelector('[itemprop="name"]').textContent = this.name;
2531
+ this.element.querySelector('[itemprop="email"]').textContent = this.email;
2532
+ }
2533
+ }
2534
+
2535
+ // Register the manager
2536
+ customElements.itemscopeRegistry.define('user-card', {
2537
+ manager: UserCardManager
2538
+ });
2539
+
2540
+ // Use assignGingerly with the 'ish' property
2541
+ const element = document.querySelector('[itemscope="user-card"]');
2542
+ element.assignGingerly({
2543
+ ish: {
2544
+ name: 'Alice',
2545
+ email: 'alice@example.com'
2546
+ }
2547
+ });
2548
+
2549
+ // Wait for async setup to complete
2550
+ await customElements.itemscopeRegistry.whenDefined('user-card');
2551
+
2552
+ // Access the manager instance
2553
+ console.log(element.ish instanceof UserCardManager); // true
2554
+ console.log(element.ish.name); // 'Alice'
2555
+ ```
2556
+
2557
+ ### The 'ish' Property
2558
+
2559
+ The `ish` property (short for "itemscope host") is the key to ItemScope Managers:
2560
+
2561
+ - **Special behavior for HTMLElements**: When you assign an `ish` property to an HTMLElement with an `itemscope` attribute, it triggers manager instantiation
2562
+ - **Normal property for other objects**: For non-HTMLElement objects, `ish` is just a regular property with no special behavior
2563
+ - **Asynchronous setup**: The manager is instantiated asynchronously, so use `whenDefined()` to wait for completion
2564
+
2565
+ ```TypeScript
2566
+ // HTMLElement with itemscope - special behavior
2567
+ const div = document.createElement('div');
2568
+ div.setAttribute('itemscope', 'my-manager');
2569
+ div.assignGingerly({ ish: { prop: 'value' } });
2570
+ // Manager will be instantiated asynchronously
2571
+
2572
+ // Plain object - normal property
2573
+ const obj = {};
2574
+ obj.assignGingerly({ ish: { prop: 'value' } });
2575
+ console.log(obj.ish.prop); // 'value' - just a regular property
2576
+ ```
2577
+
2578
+ ### ItemscopeRegistry
2579
+
2580
+ The `ItemscopeRegistry` class manages manager configurations and extends `EventTarget` to support lazy registration:
2581
+
2582
+ ```TypeScript
2583
+ // Access the global registry
2584
+ const registry = customElements.itemscopeRegistry;
2585
+
2586
+ // Define a manager
2587
+ registry.define('manager-name', {
2588
+ manager: ManagerClass,
2589
+ lifecycleKeys: {
2590
+ dispose: 'cleanup',
2591
+ resolved: 'isReady'
2592
+ }
2593
+ });
2594
+
2595
+ // Get a manager configuration
2596
+ const config = registry.get('manager-name');
2597
+
2598
+ // Wait for a manager to be defined and all setups to complete
2599
+ await registry.whenDefined('manager-name');
2600
+ ```
2601
+
2602
+ **Methods:**
2603
+
2604
+ - `define(name, config)` - Register a manager configuration
2605
+ - Throws `Error: Already registered` if name already exists
2606
+ - Dispatches an event with the manager name when successful
2607
+
2608
+ - `get(name)` - Retrieve a manager configuration
2609
+ - Returns the configuration or `undefined` if not found
2610
+
2611
+ - `whenDefined(name)` - Wait for manager definition and setup completion
2612
+ - Returns a Promise that resolves when:
2613
+ 1. The manager is defined (waits for definition if not yet registered)
2614
+ 2. All pending `ish` property setups for this manager are complete
2615
+ - This is the recommended way to wait for async manager instantiation
2616
+
2617
+ ### Manager Configuration
2618
+
2619
+ Manager configurations follow this interface:
2620
+
2621
+ ```TypeScript
2622
+ interface ItemscopeManagerConfig<T = any> {
2623
+ manager: {
2624
+ new (element: HTMLElement, initVals?: Partial<T>): T;
2625
+ };
2626
+ lifecycleKeys?: {
2627
+ dispose?: string | symbol;
2628
+ resolved?: string | symbol;
2629
+ };
2630
+ }
2631
+ ```
2632
+
2633
+ **Properties:**
2634
+
2635
+ - `manager` (required): Constructor function that receives:
2636
+ - `element`: The HTMLElement with the itemscope attribute
2637
+ - `initVals`: Merged values from all queued `ish` assignments
2638
+
2639
+ - `lifecycleKeys` (optional): Lifecycle method names
2640
+ - `dispose`: Method to call when cleaning up
2641
+ - `resolved`: Property/event name for async initialization
2642
+
2643
+ ### Lazy Registration
2644
+
2645
+ Managers can be registered after elements are already using them. The system queues values and instantiates the manager when it's registered:
2646
+
2647
+ ```TypeScript
2648
+ const element = document.createElement('div');
2649
+ element.setAttribute('itemscope', 'lazy-manager');
2650
+
2651
+ // Assign before manager is registered - values are queued
2652
+ element.assignGingerly({ ish: { prop1: 'value1' } });
2653
+ element.assignGingerly({ ish: { prop2: 'value2' } });
2654
+
2655
+ // Register the manager later
2656
+ setTimeout(() => {
2657
+ customElements.itemscopeRegistry.define('lazy-manager', {
2658
+ manager: class LazyManager {
2659
+ constructor(element, initVals) {
2660
+ this.element = element;
2661
+ Object.assign(this, initVals);
2662
+ // initVals contains both prop1 and prop2
2663
+ }
2664
+ }
2665
+ });
2666
+ }, 100);
2667
+
2668
+ // Wait for registration and setup
2669
+ await customElements.itemscopeRegistry.whenDefined('lazy-manager');
2670
+
2671
+ console.log(element.ish.prop1); // 'value1'
2672
+ console.log(element.ish.prop2); // 'value2'
2673
+ ```
2674
+
2675
+ ### Instance Caching
2676
+
2677
+ Manager instances are cached per element. Subsequent `ish` assignments merge values into the existing instance:
2678
+
2679
+ ```TypeScript
2680
+ const element = document.createElement('div');
2681
+ element.setAttribute('itemscope', 'my-manager');
2682
+
2683
+ // First assignment - creates instance
2684
+ element.assignGingerly({ ish: { prop1: 'value1' } });
2685
+ await customElements.itemscopeRegistry.whenDefined('my-manager');
2686
+
2687
+ const firstInstance = element.ish;
2688
+
2689
+ // Second assignment - reuses instance
2690
+ element.assignGingerly({ ish: { prop2: 'value2' } });
2691
+ await customElements.itemscopeRegistry.whenDefined('my-manager');
2692
+
2693
+ console.log(element.ish === firstInstance); // true - same instance
2694
+ console.log(element.ish.prop1); // 'value1'
2695
+ console.log(element.ish.prop2); // 'value2'
2696
+ ```
2697
+
2698
+ ### Validation and Error Handling
2699
+
2700
+ The system validates `ish` property assignments and throws descriptive errors:
2701
+
2702
+ ```TypeScript
2703
+ // Error: Element must have itemscope attribute
2704
+ const div1 = document.createElement('div');
2705
+ div1.assignGingerly({ ish: { prop: 'value' } });
2706
+ // Throws asynchronously
2707
+
2708
+ // Error: itemscope must be non-empty string
2709
+ const div2 = document.createElement('div');
2710
+ div2.setAttribute('itemscope', '');
2711
+ div2.assignGingerly({ ish: { prop: 'value' } });
2712
+ // Throws asynchronously
2713
+
2714
+ // Error: ish value must be an object
2715
+ const div3 = document.createElement('div');
2716
+ div3.setAttribute('itemscope', 'my-manager');
2717
+ div3.assignGingerly({ ish: 'string' });
2718
+ // Throws asynchronously
2719
+ ```
2720
+
2721
+ **Note**: Errors are thrown asynchronously since the `ish` property setup happens in the background. They will appear in the console but won't be catchable with try/catch around the `assignGingerly` call.
2722
+
2723
+ ### Scoped Registries
2724
+
2725
+ ItemScope Managers integrate with scoped custom element registries. Each element can have its own registry:
2726
+
2727
+ ```TypeScript
2728
+ // Create a scoped registry
2729
+ const scopedRegistry = new CustomElementRegistry();
2730
+
2731
+ // Define a manager in the scoped registry
2732
+ scopedRegistry.itemscopeRegistry.define('scoped-manager', {
2733
+ manager: ScopedManager
2734
+ });
2735
+
2736
+ // Attach the registry to an element
2737
+ const element = document.createElement('div');
2738
+ element.customElementRegistry = scopedRegistry;
2739
+ element.setAttribute('itemscope', 'scoped-manager');
2740
+
2741
+ // The element uses its scoped registry
2742
+ element.assignGingerly({ ish: { prop: 'value' } });
2743
+ await scopedRegistry.itemscopeRegistry.whenDefined('scoped-manager');
2744
+ ```
2745
+
2746
+ If an element doesn't have a `customElementRegistry` property, it falls back to the global `customElements.itemscopeRegistry`.
2747
+
2748
+ ### Complete Example
2749
+
2750
+ ```html
2751
+ <!DOCTYPE html>
2752
+ <html>
2753
+ <head>
2754
+ <script type="module">
2755
+ import 'assign-gingerly/object-extension.js';
2756
+
2757
+ // Define a todo item manager
2758
+ class TodoItemManager {
2759
+ element;
2760
+ text = '';
2761
+ completed = false;
2762
+
2763
+ constructor(element, initVals) {
2764
+ this.element = element;
2765
+ if (initVals) {
2766
+ Object.assign(this, initVals);
2767
+ }
2768
+ this.render();
2769
+ this.attachListeners();
2770
+ }
2771
+
2772
+ render() {
2773
+ const checkbox = this.element.querySelector('input[type="checkbox"]');
2774
+ const label = this.element.querySelector('label');
2775
+
2776
+ if (checkbox) checkbox.checked = this.completed;
2777
+ if (label) label.textContent = this.text;
2778
+ }
2779
+
2780
+ attachListeners() {
2781
+ const checkbox = this.element.querySelector('input[type="checkbox"]');
2782
+ if (checkbox) {
2783
+ checkbox.addEventListener('change', (e) => {
2784
+ this.completed = e.target.checked;
2785
+ this.render();
2786
+ });
2787
+ }
2788
+ }
2789
+
2790
+ cleanup() {
2791
+ // Remove event listeners, etc.
2792
+ console.log('Cleaning up todo item');
2793
+ }
2794
+ }
2795
+
2796
+ // Register the manager
2797
+ customElements.itemscopeRegistry.define('todo-item', {
2798
+ manager: TodoItemManager,
2799
+ lifecycleKeys: {
2800
+ dispose: 'cleanup'
2801
+ }
2802
+ });
2803
+
2804
+ // Initialize todo items
2805
+ async function initTodos() {
2806
+ const items = document.querySelectorAll('[itemscope="todo-item"]');
2807
+
2808
+ items.forEach((item, index) => {
2809
+ item.assignGingerly({
2810
+ ish: {
2811
+ text: `Todo item ${index + 1}`,
2812
+ completed: false
2813
+ }
2814
+ });
2815
+ });
2816
+
2817
+ // Wait for all setups to complete
2818
+ await customElements.itemscopeRegistry.whenDefined('todo-item');
2819
+
2820
+ console.log('All todo items initialized');
2821
+ }
2822
+
2823
+ // Run on page load
2824
+ document.addEventListener('DOMContentLoaded', initTodos);
2825
+ </script>
2826
+ </head>
2827
+ <body>
2828
+ <h1>Todo List</h1>
2829
+ <ul>
2830
+ <li itemscope="todo-item">
2831
+ <input type="checkbox">
2832
+ <label></label>
2833
+ </li>
2834
+ <li itemscope="todo-item">
2835
+ <input type="checkbox">
2836
+ <label></label>
2837
+ </li>
2838
+ <li itemscope="todo-item">
2839
+ <input type="checkbox">
2840
+ <label></label>
2841
+ </li>
2842
+ </ul>
2843
+ </body>
2844
+ </html>
2845
+ ```
2846
+
2847
+ ### Testing with whenDefined
2848
+
2849
+ When writing tests for code that uses ItemScope Managers, use `whenDefined()` to wait for async setup:
2850
+
2851
+ ```TypeScript
2852
+ // Test example
2853
+ test('should initialize manager with values', async () => {
2854
+ const element = document.createElement('div');
2855
+ element.setAttribute('itemscope', 'test-manager');
2856
+
2857
+ // Register manager
2858
+ customElements.itemscopeRegistry.define('test-manager', {
2859
+ manager: class TestManager {
2860
+ constructor(element, initVals) {
2861
+ this.element = element;
2862
+ Object.assign(this, initVals);
2863
+ }
2864
+ }
2865
+ });
2866
+
2867
+ // Assign values
2868
+ element.assignGingerly({ ish: { prop: 'value' } });
2869
+
2870
+ // Wait for setup to complete
2871
+ await customElements.itemscopeRegistry.whenDefined('test-manager');
2872
+
2873
+ // Now we can assert
2874
+ expect(element.ish.prop).toBe('value');
2875
+ expect(element.ish.element).toBe(element);
2876
+ });
2877
+ ```
2878
+
2879
+ ### Design Rationale
2880
+
2881
+ ItemScope Managers follow these design principles:
2882
+
2883
+ 1. **Synchronous API**: `assignGingerly` remains synchronous and returns immediately
2884
+ 2. **Async setup**: Manager instantiation happens asynchronously in the background
2885
+ 3. **Explicit waiting**: Use `whenDefined()` when you need to wait for setup completion
2886
+ 4. **Dual behavior**: The `ish` property has special meaning only for HTMLElements with `itemscope` attributes
2887
+ 5. **Registry-based**: Follows the same pattern as `EnhancementRegistry` for consistency
2888
+ 6. **Event-driven**: Uses EventTarget for lazy registration support
2889
+
2890
+ This design ensures backward compatibility while providing powerful new capabilities for managing DOM fragments.
2891
+
2455
2892
 
2456
2893
 
2457
2894