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 +29 -92
- package/assignFeatures.js +145 -254
- package/assignFeatures.ts +713 -837
- package/defineWithFeatures.js +12 -72
- package/defineWithFeatures.ts +92 -167
- package/enhanceAll.js +8 -4
- package/enhanceAll.ts +8 -7
- package/inferencer/types/assign-gingerly/types.d.ts +2 -16
- package/inferencer/types/font-face-feature/types.d.ts +9 -0
- package/package.json +10 -1
- package/processHandlerCommands.js +11 -34
- package/processHandlerCommands.ts +13 -40
- package/resolveAndAssignFeatures.js +9 -40
- package/resolveAndAssignFeatures.ts +43 -79
- package/types/assign-gingerly/types.d.ts +2 -16
- package/utils/findClassPrototypeInPath.js +57 -0
- package/utils/findClassPrototypeInPath.ts +62 -0
- package/utils/isAsyncSpawn.js +20 -0
- package/utils/isAsyncSpawn.ts +17 -0
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
|
|
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
|
|
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: {
|
|
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
|
-
|
|
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`
|
|
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.
|
|
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
|
-
'?.
|
|
5945
|
-
}, { withAsyncMethods: ['
|
|
5946
|
-
// Equivalent to: (await el.
|
|
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
|
-
'?.
|
|
5895
|
+
'?.fetchValue?.classList?.add': 'active'
|
|
5959
5896
|
}, {
|
|
5960
|
-
withAsyncMethods: ['
|
|
5897
|
+
withAsyncMethods: ['fetchValue'],
|
|
5961
5898
|
withMethods: ['add']
|
|
5962
5899
|
});
|
|
5963
|
-
// (await el.
|
|
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.
|