assign-gingerly 0.0.73 → 0.0.75

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
@@ -5487,13 +5487,12 @@ class ClubMember extends HTMLElement {
5487
5487
  }
5488
5488
  }
5489
5489
 
5490
- customElements.define('club-member', ClubMember);
5491
-
5492
- // No spawn provided — will use fallbackSpawn
5493
5490
  customElements.assignFeatures(ClubMember, {
5494
5491
  photoTaker: {}
5495
5492
  });
5496
5493
 
5494
+ customElements.define('club-member', ClubMember);
5495
+
5497
5496
  const el = document.createElement('club-member');
5498
5497
  console.log(el.photoTaker.takePicture()); // works via fallbackSpawn
5499
5498
  ```
@@ -5541,14 +5540,14 @@ class ClubMember extends HTMLElement {
5541
5540
  }
5542
5541
  }
5543
5542
 
5544
- customElements.define('club-member', ClubMember);
5545
-
5546
5543
  // Can assign all at once
5547
5544
  customElements.assignFeatures(ClubMember, {
5548
5545
  photoTaker: { spawn: PhotoTakerImpl },
5549
5546
  badgeMaker: { spawn: BadgeMakerImpl }
5550
5547
  });
5551
5548
 
5549
+ customElements.define('club-member', ClubMember);
5550
+
5552
5551
  // Or incrementally (different keys each call)
5553
5552
  // customElements.assignFeatures(ClubMember, { photoTaker: { spawn: PhotoTakerImpl } });
5554
5553
  // customElements.assignFeatures(ClubMember, { badgeMaker: { spawn: BadgeMakerImpl } });
@@ -5619,14 +5618,15 @@ While designed with custom elements in mind, `assignFeatures` works with any con
5619
5618
  customElements.assignFeatures(
5620
5619
  ctr: Function, // The class constructor
5621
5620
  features: FeatureConfigsMap // Map of feature keys to FeatureConfig
5622
- ): void;
5621
+ ): Promise<void> | undefined;
5623
5622
 
5624
5623
  // FeatureConfig — passed to assignFeatures for each feature key:
5625
5624
  interface FeatureConfig {
5626
- // Synchronous constructor or async function returning one
5625
+ // Synchronous constructor, async function returning one, or import-path string
5627
5626
  spawn?:
5628
5627
  | { new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }
5629
- | (() => Promise<{ new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }>);
5628
+ | (() => Promise<{ new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }>)
5629
+ | string; // import path or builtIns.* alias
5630
5630
 
5631
5631
  // Attribute patterns for parsing element attributes into initVals
5632
5632
  withAttrs?: AttrPatterns<any>;
@@ -5639,7 +5639,6 @@ interface FeatureConfig {
5639
5639
  interface SupportedFeatureConfig {
5640
5640
  fallbackSpawn?: /* same type as FeatureConfig.spawn */;
5641
5641
  validateShape?: (instance: any) => boolean;
5642
- lifecycleKeys?: true | { whenFeatureReady?: string };
5643
5642
  getSharedContext?: (instance: any) => any;
5644
5643
  }
5645
5644
 
@@ -5739,7 +5738,7 @@ console.log(el.photoTaker.count); // 42
5739
5738
 
5740
5739
  ### Async spawn (lazy-loading features)
5741
5740
 
5742
- Feature implementations can be loaded asynchronously. Instead of providing a constructor directly, provide a function that returns a Promise resolving to a constructor:
5741
+ Feature implementations can be loaded asynchronously by providing an async function or an import-path string as `spawn`. The spawn is resolved before the feature getter is installed, so `assignFeatures` returns a Promise when any configured spawn is async:
5743
5742
 
5744
5743
  ```JavaScript
5745
5744
  customElements.assignFeatures(ClubMember, {
@@ -5747,85 +5746,16 @@ customElements.assignFeatures(ClubMember, {
5747
5746
  spawn: () => import('./photo-taker.js').then(m => m.PhotoTakerImpl)
5748
5747
  }
5749
5748
  });
5750
- ```
5751
-
5752
- **How it works:**
5753
-
5754
- 1. On first access, the getter detects that `spawn` is an async function (arrow function or `async function`).
5755
- 2. It creates a `{}` placeholder object, stores it, and returns it immediately.
5756
- 3. In the background, the async function is called and awaited.
5757
- 4. When the Promise resolves, the real class is instantiated with the placeholder as `initVals` (so any properties merged into the placeholder are passed to the constructor).
5758
- 5. The placeholder is replaced in storage with the real instance.
5759
- 6. Subsequent getter accesses return the real instance.
5760
-
5761
- **During the loading window:**
5762
-
5763
- ```JavaScript
5764
- const el = document.createElement('club-member');
5765
-
5766
- // First access — returns placeholder {}
5767
- assignGingerly(el, { photoTaker: { someProp: 'hello' } });
5768
- // Merges into the placeholder: { someProp: 'hello' }
5769
-
5770
- // Later, after async resolution:
5771
- console.log(el.photoTaker.someProp); // 'hello' — now on the real instance
5772
- console.log(el.photoTaker instanceof PhotoTakerImpl); // true
5773
- ```
5774
-
5775
- **Error handling:**
5776
-
5777
- If the async import fails, the error is stored. The next getter access throws with the original error attached:
5778
-
5779
- ```JavaScript
5780
- try {
5781
- el.photoTaker;
5782
- } catch (e) {
5783
- console.log(e.message); // 'assignFeatures: async spawn for "photoTaker" failed: ...'
5784
- console.log(e.placeholder); // the accumulated placeholder object
5785
- console.log(e.cause); // the original import/network error
5786
- }
5787
- ```
5788
-
5789
- **Detection heuristic:** A function is treated as an async spawner if it's an `AsyncFunction` or has no `.prototype` (arrow functions). Classes and `function` declarations (which have `.prototype`) are treated as synchronous constructors.
5790
-
5791
- ### `whenFeatureReady` lifecycle method
5792
-
5793
- For code that needs to wait for an async feature to be fully instantiated, configure `lifecycleKeys` on the supported feature:
5794
-
5795
- ```JavaScript
5796
- class ClubMember extends HTMLElement {
5797
- static supportedFeatures = {
5798
- photoTaker: {
5799
- fallbackSpawn: PhotoTakerImpl,
5800
- lifecycleKeys: true // installs 'whenFeatureReady' method
5801
- }
5802
- };
5803
- static featuresConfig = {
5804
- lifecycleKeys: true
5805
- }
5806
- }
5807
5749
 
5750
+ // Or use an import path string:
5808
5751
  customElements.assignFeatures(ClubMember, {
5809
- photoTaker: { spawn: () => import('./photo-taker.js').then(m => m.PhotoTakerImpl) }
5752
+ photoTaker: {
5753
+ spawn: './photo-taker.js'
5754
+ }
5810
5755
  });
5811
-
5812
- const el = document.createElement('club-member');
5813
-
5814
- // Wait for the async feature to be ready
5815
- const photoTaker = await el.whenFeatureReady('photoTaker');
5816
- console.log(photoTaker instanceof PhotoTakerImpl); // true
5817
5756
  ```
5818
5757
 
5819
- **Configuration:**
5820
-
5821
- - `lifecycleKeys: true` — installs a method named `'whenFeatureReady'` on the prototype.
5822
- - `lifecycleKeys: { whenFeatureReady: 'awaitFeature' }` — custom method name (in case `whenFeatureReady` conflicts with an existing method).
5823
-
5824
- **Behavior:**
5825
-
5826
- - For **synchronous** features: returns `Promise.resolve(instance)` immediately.
5827
- - For **async** features: returns a Promise that resolves when the async spawn completes and the real instance is stored.
5828
- - The method triggers the getter (starting async resolution if it hasn't started yet).
5758
+ Because the real class is resolved before the getter is installed, the first access returns the actual instance rather than a placeholder. If an async spawn fails, the `assignFeatures` Promise rejects with the original error.
5829
5759
 
5830
5760
  ### Attribute parsing with `withAttrs`
5831
5761
 
@@ -5863,7 +5793,7 @@ withAttrs: {
5863
5793
 
5864
5794
  **Merge priority (lowest to highest):**
5865
5795
  1. Attribute-parsed values (`withAttrs`)
5866
- 2. Programmatic `initVals` (from `captureFeatureInitVals` or placeholder accumulation)
5796
+ 2. Programmatic `initVals` (from `captureFeatureInitVals`)
5867
5797
 
5868
5798
  Attributes are always unprefixed for features (no `enh-` prefix). The same `parseWithAttrs` function used by enhancements is reused here.
5869
5799
 
@@ -5935,15 +5865,22 @@ class PhotoTakerImpl {
5935
5865
 
5936
5866
  ### `withAsyncMethods` in assignGingerly
5937
5867
 
5938
- assignGingerly supports async method calls in path expressions via the `withAsyncMethods` option. This is particularly useful with `whenFeatureReady`:
5868
+ assignGingerly supports async method calls in path expressions via the `withAsyncMethods` option. When a path segment matches a name in `withAsyncMethods`, the method is called and its return value is awaited before continuing the chain.
5939
5869
 
5940
5870
  ```JavaScript
5941
5871
  import assignGingerly from 'assign-gingerly/assignGingerly.js';
5942
5872
 
5873
+ class ViewModel {
5874
+ async fetchValue() {
5875
+ await someAsyncWork();
5876
+ return this;
5877
+ }
5878
+ }
5879
+
5943
5880
  assignGingerly(el, {
5944
- '?.whenFeatureReady?.photoTaker?.someProp': 'hello'
5945
- }, { withAsyncMethods: ['whenFeatureReady'] });
5946
- // Equivalent to: (await el.whenFeatureReady('photoTaker')).someProp = 'hello'
5881
+ '?.fetchValue?.someProp': 'hello'
5882
+ }, { withAsyncMethods: ['fetchValue'] });
5883
+ // Equivalent to: (await el.fetchValue()).someProp = 'hello'
5947
5884
  ```
5948
5885
 
5949
5886
  **How it works:**
@@ -5955,12 +5892,12 @@ assignGingerly(el, {
5955
5892
 
5956
5893
  ```JavaScript
5957
5894
  assignGingerly(el, {
5958
- '?.whenFeatureReady?.photoTaker?.classList?.add': 'active'
5895
+ '?.fetchValue?.classList?.add': 'active'
5959
5896
  }, {
5960
- withAsyncMethods: ['whenFeatureReady'],
5897
+ withAsyncMethods: ['fetchValue'],
5961
5898
  withMethods: ['add']
5962
5899
  });
5963
- // (await el.whenFeatureReady('photoTaker')).classList.add('active')
5900
+ // (await el.fetchValue()).classList.add('active')
5964
5901
  ```
5965
5902
 
5966
5903
  **Note:** Interaction with `@each` and `@eachTime` is not yet supported for async methods.