assign-gingerly 0.0.41 → 0.0.43
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 +71 -0
- package/assignFeatures.js +22 -3
- package/assignFeatures.ts +28 -6
- package/package.json +1 -1
- package/types/assign-gingerly/types.d.ts +6 -140
package/README.md
CHANGED
|
@@ -4656,6 +4656,9 @@ When the custom element's `connectedCallback` fires, the feature's `connectedCal
|
|
|
4656
4656
|
| `disconnectedCallback` | Feature needs cleanup (remove listeners, abort fetches) |
|
|
4657
4657
|
| `attributeChangedCallback` | Feature reacts to attribute changes (limited to element's `observedAttributes`) |
|
|
4658
4658
|
| `adoptedCallback` | Feature reacts to document adoption |
|
|
4659
|
+
| `formDisabledCallback` | Feature reacts to disabled state changes (form-associated elements) |
|
|
4660
|
+
| `formResetCallback` | Feature reacts to form reset (form-associated elements) |
|
|
4661
|
+
| `formStateRestoreCallback` | Feature restores state after navigation/session restore (form-associated elements) |
|
|
4659
4662
|
|
|
4660
4663
|
**Example: Feature that reads computed styles on connect**
|
|
4661
4664
|
|
|
@@ -4723,6 +4726,74 @@ customElements.assignFeatures(MyElement, {
|
|
|
4723
4726
|
|
|
4724
4727
|
**Note on `attributeChangedCallback`:** The feature only receives callbacks for attributes listed in the element's `static observedAttributes`. Features cannot add to this list after `define()` is called.
|
|
4725
4728
|
|
|
4729
|
+
### Class-level setup with `static onAssigned`
|
|
4730
|
+
|
|
4731
|
+
Some features need one-time class-level setup before any instances are created — for example, installing prototype getter/setters or pre-loading modules. The `static onAssigned` method on the spawn class is called by `assignFeatures` immediately after registration:
|
|
4732
|
+
|
|
4733
|
+
```JavaScript
|
|
4734
|
+
class RoundaboutFeature {
|
|
4735
|
+
// Called once when assignFeatures processes this feature
|
|
4736
|
+
static async onAssigned(ctr, featureConfig) {
|
|
4737
|
+
// One-time class-level setup: install prototype getter/setters, pre-load modules
|
|
4738
|
+
await makeRoundaboutReady(ctr, featureConfig.customData);
|
|
4739
|
+
}
|
|
4740
|
+
|
|
4741
|
+
constructor(host, ctx, initVals) {
|
|
4742
|
+
// Instance-level setup (runs on first getter access)
|
|
4743
|
+
const [vm, propagator] = roundaboutSync({
|
|
4744
|
+
vm: host,
|
|
4745
|
+
...ctx.injection.customData,
|
|
4746
|
+
});
|
|
4747
|
+
this._vm = vm;
|
|
4748
|
+
this._propagator = propagator;
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
```
|
|
4752
|
+
|
|
4753
|
+
**Usage:**
|
|
4754
|
+
|
|
4755
|
+
```JavaScript
|
|
4756
|
+
// await is safe — returns undefined if no async onAssigned hooks exist
|
|
4757
|
+
await customElements.assignFeatures(MyElement, {
|
|
4758
|
+
roundabout: {
|
|
4759
|
+
spawn: RoundaboutFeature,
|
|
4760
|
+
customData: raConfig
|
|
4761
|
+
}
|
|
4762
|
+
});
|
|
4763
|
+
|
|
4764
|
+
// Now define — class is fully set up, connectedCallback will be synchronous
|
|
4765
|
+
customElements.define('my-element', MyElement);
|
|
4766
|
+
```
|
|
4767
|
+
|
|
4768
|
+
**How it works:**
|
|
4769
|
+
|
|
4770
|
+
- `assignFeatures` checks if the spawn class defines `static onAssigned` (via `Object.hasOwn`).
|
|
4771
|
+
- If found, calls `SpawnClass.onAssigned(ctr, featureConfig)` after installing the getter.
|
|
4772
|
+
- If `onAssigned` returns a Promise, `assignFeatures` returns a `Promise<void>` that resolves when all async hooks complete.
|
|
4773
|
+
- If no `onAssigned` hooks are async (or none exist), `assignFeatures` returns `undefined` (backward compatible — existing code that doesn't `await` still works).
|
|
4774
|
+
- Only applies to synchronous spawners (the class must be available at registration time). Async spawners can't define `onAssigned` since the class isn't loaded yet.
|
|
4775
|
+
|
|
4776
|
+
**`await` is always safe:**
|
|
4777
|
+
|
|
4778
|
+
```JavaScript
|
|
4779
|
+
// These are equivalent for sync features (no onAssigned or sync onAssigned):
|
|
4780
|
+
customElements.assignFeatures(MyElement, { feature: { spawn: SyncFeature } });
|
|
4781
|
+
await customElements.assignFeatures(MyElement, { feature: { spawn: SyncFeature } });
|
|
4782
|
+
// Both work — await on undefined is a no-op
|
|
4783
|
+
```
|
|
4784
|
+
|
|
4785
|
+
<details>
|
|
4786
|
+
<summary>Catalog of Published Custom Element Features</summary>
|
|
4787
|
+
|
|
4788
|
+
| Package | Description | Source |
|
|
4789
|
+
|---------|-------------|--------|
|
|
4790
|
+
| [truth-sourcer](https://www.npmjs.com/package/truth-sourcer) | Attribute/property binding and truth-sourcing for custom elements | [GitHub](https://github.com/bahrus/truth-sourcer) |
|
|
4791
|
+
| [be-reflective](https://www.npmjs.com/package/be-reflective) | CSS custom state reflection from computed styles | [GitHub](https://github.com/bahrus/be-reflective) |
|
|
4792
|
+
| [face-up](https://www.npmjs.com/package/face-up) | Form Associated Custom Element behavior via ElementInternals | [GitHub](https://github.com/bahrus/face-up) |
|
|
4793
|
+
| [time-ticker](https://www.npmjs.com/package/time-ticker) | Web component that fires events periodically (example of a feature-based component with no code in the class) | [GitHub](https://github.com/bahrus/time-ticker) |
|
|
4794
|
+
|
|
4795
|
+
</details>
|
|
4796
|
+
|
|
4726
4797
|
### Roadmap (future phases)
|
|
4727
4798
|
|
|
4728
4799
|
- **Nested features**: Support `?.path?.notation` keys directly in `assignFeatures` (without requiring `PropertyBag`).
|
package/assignFeatures.js
CHANGED
|
@@ -305,7 +305,10 @@ const VALID_CALLBACKS = new Set([
|
|
|
305
305
|
'connectedCallback',
|
|
306
306
|
'disconnectedCallback',
|
|
307
307
|
'attributeChangedCallback',
|
|
308
|
-
'adoptedCallback'
|
|
308
|
+
'adoptedCallback',
|
|
309
|
+
'formDisabledCallback',
|
|
310
|
+
'formResetCallback',
|
|
311
|
+
'formStateRestoreCallback'
|
|
309
312
|
]);
|
|
310
313
|
/**
|
|
311
314
|
* WeakMap tracking which callbacks have been patched on which constructors,
|
|
@@ -383,6 +386,7 @@ export function assignFeatures(ctr, features, featuresRegistry) {
|
|
|
383
386
|
if (!supportedFeatures) {
|
|
384
387
|
throw new Error(`assignFeatures: ${ctr.name || 'constructor'} does not define static supportedFeatures`);
|
|
385
388
|
}
|
|
389
|
+
const onAssignedPromises = [];
|
|
386
390
|
for (const key of Object.keys(features)) {
|
|
387
391
|
// 1. Confirm the key is opted-in via supportedFeatures
|
|
388
392
|
if (!(key in supportedFeatures)) {
|
|
@@ -411,8 +415,18 @@ export function assignFeatures(ctr, features, featuresRegistry) {
|
|
|
411
415
|
if (allCallbacks.length > 0) {
|
|
412
416
|
installCallbackForwarding(ctr, key, allCallbacks);
|
|
413
417
|
}
|
|
418
|
+
// 7. Call static onAssigned if the spawn class defines it
|
|
419
|
+
const SpawnClass = featureConfig.spawn;
|
|
420
|
+
if (SpawnClass && !isAsyncSpawn(SpawnClass) &&
|
|
421
|
+
Object.hasOwn(SpawnClass, 'onAssigned') &&
|
|
422
|
+
typeof SpawnClass.onAssigned === 'function') {
|
|
423
|
+
const result = SpawnClass.onAssigned(ctr, featureConfig);
|
|
424
|
+
if (result && typeof result.then === 'function') {
|
|
425
|
+
onAssignedPromises.push(result);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
414
428
|
}
|
|
415
|
-
//
|
|
429
|
+
// 8. Install whenFeatureReady method if featuresConfig.lifecycleKeys is configured
|
|
416
430
|
const featuresConfig = ctr.featuresConfig;
|
|
417
431
|
if (featuresConfig?.lifecycleKeys) {
|
|
418
432
|
const methodName = resolveWhenFeatureReadyName(featuresConfig.lifecycleKeys);
|
|
@@ -420,6 +434,11 @@ export function assignFeatures(ctr, features, featuresRegistry) {
|
|
|
420
434
|
installWhenFeatureReadyMethod(ctr, methodName);
|
|
421
435
|
}
|
|
422
436
|
}
|
|
437
|
+
// Return a Promise if any onAssigned hooks are async, otherwise undefined
|
|
438
|
+
if (onAssignedPromises.length > 0) {
|
|
439
|
+
return Promise.all(onAssignedPromises).then(() => { });
|
|
440
|
+
}
|
|
441
|
+
return undefined;
|
|
423
442
|
}
|
|
424
443
|
/**
|
|
425
444
|
* Captures own-properties that shadow feature getters and stores them as initVals.
|
|
@@ -525,7 +544,7 @@ if (typeof CustomElementRegistry !== 'undefined') {
|
|
|
525
544
|
});
|
|
526
545
|
Object.defineProperty(CustomElementRegistry.prototype, 'assignFeatures', {
|
|
527
546
|
value: function (ctr, features) {
|
|
528
|
-
assignFeatures(ctr, features, this.featuresRegistry);
|
|
547
|
+
return assignFeatures(ctr, features, this.featuresRegistry);
|
|
529
548
|
},
|
|
530
549
|
writable: true,
|
|
531
550
|
enumerable: false,
|
package/assignFeatures.ts
CHANGED
|
@@ -515,7 +515,10 @@ const VALID_CALLBACKS = new Set([
|
|
|
515
515
|
'connectedCallback',
|
|
516
516
|
'disconnectedCallback',
|
|
517
517
|
'attributeChangedCallback',
|
|
518
|
-
'adoptedCallback'
|
|
518
|
+
'adoptedCallback',
|
|
519
|
+
'formDisabledCallback',
|
|
520
|
+
'formResetCallback',
|
|
521
|
+
'formStateRestoreCallback'
|
|
519
522
|
]);
|
|
520
523
|
|
|
521
524
|
/**
|
|
@@ -607,7 +610,7 @@ export function assignFeatures(
|
|
|
607
610
|
ctr: Function,
|
|
608
611
|
features: FeatureConfigsMap,
|
|
609
612
|
featuresRegistry: FeaturesRegistry
|
|
610
|
-
): void {
|
|
613
|
+
): Promise<void> | undefined {
|
|
611
614
|
// Validate that the constructor has static supportedFeatures
|
|
612
615
|
const supportedFeatures: SupportedFeaturesMap | undefined = (ctr as any).supportedFeatures;
|
|
613
616
|
|
|
@@ -617,6 +620,8 @@ export function assignFeatures(
|
|
|
617
620
|
);
|
|
618
621
|
}
|
|
619
622
|
|
|
623
|
+
const onAssignedPromises: Promise<void>[] = [];
|
|
624
|
+
|
|
620
625
|
for (const key of Object.keys(features)) {
|
|
621
626
|
// 1. Confirm the key is opted-in via supportedFeatures
|
|
622
627
|
if (!(key in supportedFeatures)) {
|
|
@@ -656,9 +661,20 @@ export function assignFeatures(
|
|
|
656
661
|
if (allCallbacks.length > 0) {
|
|
657
662
|
installCallbackForwarding(ctr, key, allCallbacks);
|
|
658
663
|
}
|
|
664
|
+
|
|
665
|
+
// 7. Call static onAssigned if the spawn class defines it
|
|
666
|
+
const SpawnClass = featureConfig.spawn;
|
|
667
|
+
if (SpawnClass && !isAsyncSpawn(SpawnClass) &&
|
|
668
|
+
Object.hasOwn(SpawnClass as any, 'onAssigned') &&
|
|
669
|
+
typeof (SpawnClass as any).onAssigned === 'function') {
|
|
670
|
+
const result = (SpawnClass as any).onAssigned(ctr, featureConfig);
|
|
671
|
+
if (result && typeof result.then === 'function') {
|
|
672
|
+
onAssignedPromises.push(result);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
659
675
|
}
|
|
660
676
|
|
|
661
|
-
//
|
|
677
|
+
// 8. Install whenFeatureReady method if featuresConfig.lifecycleKeys is configured
|
|
662
678
|
const featuresConfig: FeaturesClassConfig | undefined = (ctr as any).featuresConfig;
|
|
663
679
|
if (featuresConfig?.lifecycleKeys) {
|
|
664
680
|
const methodName = resolveWhenFeatureReadyName(featuresConfig.lifecycleKeys);
|
|
@@ -666,6 +682,12 @@ export function assignFeatures(
|
|
|
666
682
|
installWhenFeatureReadyMethod(ctr, methodName);
|
|
667
683
|
}
|
|
668
684
|
}
|
|
685
|
+
|
|
686
|
+
// Return a Promise if any onAssigned hooks are async, otherwise undefined
|
|
687
|
+
if (onAssignedPromises.length > 0) {
|
|
688
|
+
return Promise.all(onAssignedPromises).then(() => {});
|
|
689
|
+
}
|
|
690
|
+
return undefined;
|
|
669
691
|
}
|
|
670
692
|
|
|
671
693
|
/**
|
|
@@ -772,7 +794,7 @@ export class PropertyBag {
|
|
|
772
794
|
declare global {
|
|
773
795
|
interface CustomElementRegistry {
|
|
774
796
|
featuresRegistry: FeaturesRegistry;
|
|
775
|
-
assignFeatures(ctr: Function, features: FeatureConfigsMap): void;
|
|
797
|
+
assignFeatures(ctr: Function, features: FeatureConfigsMap): Promise<void> | undefined;
|
|
776
798
|
}
|
|
777
799
|
}
|
|
778
800
|
|
|
@@ -793,8 +815,8 @@ if (typeof CustomElementRegistry !== 'undefined') {
|
|
|
793
815
|
});
|
|
794
816
|
|
|
795
817
|
Object.defineProperty(CustomElementRegistry.prototype, 'assignFeatures', {
|
|
796
|
-
value: function (ctr: Function, features: FeatureConfigsMap): void {
|
|
797
|
-
assignFeatures(ctr, features, this.featuresRegistry);
|
|
818
|
+
value: function (ctr: Function, features: FeatureConfigsMap): Promise<void> | undefined {
|
|
819
|
+
return assignFeatures(ctr, features, this.featuresRegistry);
|
|
798
820
|
},
|
|
799
821
|
writable: true,
|
|
800
822
|
enumerable: false,
|
package/package.json
CHANGED
|
@@ -179,13 +179,18 @@ export interface AttrConfig<T = unknown, TParserConfig = unknown> {
|
|
|
179
179
|
// * Defaults to true (initial read only)
|
|
180
180
|
// */
|
|
181
181
|
// initialOnly?: boolean;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Should make sure it is added to static observedAttribrutes
|
|
185
|
+
*/
|
|
186
|
+
sourceOfTruth?: boolean;
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
export type AttrPatterns<T = any> = {
|
|
185
190
|
/**
|
|
186
191
|
* Base prefix for attribute names
|
|
187
192
|
*/
|
|
188
|
-
base
|
|
193
|
+
base?: string;
|
|
189
194
|
|
|
190
195
|
/**
|
|
191
196
|
* Configuration for the base pattern
|
|
@@ -241,18 +246,6 @@ export interface IAssignGingerlyOptions {
|
|
|
241
246
|
* When the signal is aborted, all event listeners are automatically removed
|
|
242
247
|
*/
|
|
243
248
|
signal?: AbortSignal;
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* List of property names that should be treated as async methods.
|
|
247
|
-
* Works together with withMethods — async methods are awaited before
|
|
248
|
-
* continuing the chain.
|
|
249
|
-
*
|
|
250
|
-
* The path evaluation for keys containing async methods is fire-and-forget:
|
|
251
|
-
* assignGingerly remains synchronous and returns immediately.
|
|
252
|
-
*
|
|
253
|
-
* NOTE: Interaction with @each and @eachTime is not yet implemented.
|
|
254
|
-
*/
|
|
255
|
-
withAsyncMethods?: string[] | Set<string>;
|
|
256
249
|
}
|
|
257
250
|
|
|
258
251
|
/**
|
|
@@ -333,130 +326,3 @@ export interface ElementEnhancement{
|
|
|
333
326
|
dispose(registryItem: EnhancementConfig | string | symbol): void;
|
|
334
327
|
whenResolved(registryItem: EnhancementConfig | string | symbol, mountCtx?: any): Promise<any>;
|
|
335
328
|
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Context passed to feature spawn constructors
|
|
339
|
-
*/
|
|
340
|
-
export interface FeatureSpawnContext {
|
|
341
|
-
/** The feature key (e.g., 'photoTaker') */
|
|
342
|
-
key: string;
|
|
343
|
-
/** The SupportedFeatureConfig from static supportedFeatures */
|
|
344
|
-
optIn: SupportedFeatureConfig;
|
|
345
|
-
/** The FeatureConfig from assignFeatures */
|
|
346
|
-
injection: FeatureConfig;
|
|
347
|
-
/** The features registry reference */
|
|
348
|
-
featuresRegistry: FeaturesRegistry;
|
|
349
|
-
/** Shared context from the host element (via getSharedContext callback) */
|
|
350
|
-
shared?: any;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Configuration for a supported feature slot declared via static supportedFeatures
|
|
355
|
-
*/
|
|
356
|
-
export interface SupportedFeatureConfig {
|
|
357
|
-
/**
|
|
358
|
-
* Optional fallback class (or async spawner) to use if no implementation is injected.
|
|
359
|
-
*/
|
|
360
|
-
fallbackSpawn?:
|
|
361
|
-
| { new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }
|
|
362
|
-
| (() => Promise<{ new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }>);
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Optional runtime shape validation for the spawned instance.
|
|
366
|
-
* Return true if the instance is valid, false to throw.
|
|
367
|
-
*/
|
|
368
|
-
validateShape?: (spawnedInstance: any) => boolean;
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Optional callback to provide shared context (e.g., ElementInternals, private state)
|
|
372
|
-
* to the feature at construction time.
|
|
373
|
-
*
|
|
374
|
-
* Defined in the class body, this callback has access to #private fields.
|
|
375
|
-
* The returned object is passed to the feature constructor as `ctx.shared`.
|
|
376
|
-
*
|
|
377
|
-
* @param instance - The host element instance
|
|
378
|
-
* @returns An object containing shared data for the feature
|
|
379
|
-
*/
|
|
380
|
-
getSharedContext?: (instance: any) => any;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Class-level configuration for the features system.
|
|
385
|
-
* Declared as `static featuresConfig` on the class.
|
|
386
|
-
*/
|
|
387
|
-
export interface FeaturesClassConfig {
|
|
388
|
-
/**
|
|
389
|
-
* Lifecycle method configuration.
|
|
390
|
-
*
|
|
391
|
-
* If set to `true`, installs a method named 'whenFeatureReady' on the prototype.
|
|
392
|
-
* If set to an object, allows customizing the method name.
|
|
393
|
-
*
|
|
394
|
-
* The installed method accepts a feature key and returns a Promise that resolves
|
|
395
|
-
* with the feature instance once it's ready (useful for async spawners).
|
|
396
|
-
* For synchronous spawners, the Promise resolves immediately.
|
|
397
|
-
*
|
|
398
|
-
* Suggested default name: 'whenFeatureReady'
|
|
399
|
-
*/
|
|
400
|
-
lifecycleKeys?: true | {
|
|
401
|
-
/** Method name for awaiting feature readiness. Defaults to 'whenFeatureReady'. */
|
|
402
|
-
whenFeatureReady?: string;
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Configuration for a feature passed to assignFeatures.
|
|
408
|
-
* The feature equivalent of EnhancementConfig.
|
|
409
|
-
*/
|
|
410
|
-
export interface FeatureConfig {
|
|
411
|
-
/**
|
|
412
|
-
* The class to instantiate for this feature, or an async function that
|
|
413
|
-
* resolves to such a class (for lazy-loading).
|
|
414
|
-
*
|
|
415
|
-
* Synchronous: Constructor receives the host element as its first argument,
|
|
416
|
-
* a FeatureSpawnContext as second, and optional initVals as third.
|
|
417
|
-
*
|
|
418
|
-
* Asynchronous: A function (arrow or async) that returns a Promise resolving
|
|
419
|
-
* to a constructor. The getter returns a placeholder object immediately and
|
|
420
|
-
* instantiates the real class once the Promise resolves.
|
|
421
|
-
*/
|
|
422
|
-
spawn?:
|
|
423
|
-
| { new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }
|
|
424
|
-
| (() => Promise<{ new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }>);
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Attribute patterns for parsing element attributes into initVals.
|
|
428
|
-
* Attributes are the "base layer" — programmatic values override them.
|
|
429
|
-
* Always unprefixed for features (no enh- prefix).
|
|
430
|
-
*/
|
|
431
|
-
withAttrs?: AttrPatterns<any>;
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Reserved field for custom configuration data.
|
|
435
|
-
* Not interpreted by the library — available to the feature class
|
|
436
|
-
* via ctx.injection.customData in the constructor.
|
|
437
|
-
*/
|
|
438
|
-
customData?: any;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
export type SupportedFeaturesMap = Record<string, SupportedFeatureConfig>;
|
|
442
|
-
export type FeatureConfigsMap = Record<string, FeatureConfig>;
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Registry for feature configs, keyed by constructor
|
|
446
|
-
*/
|
|
447
|
-
export declare class FeaturesRegistry {
|
|
448
|
-
has(ctr: Function): boolean;
|
|
449
|
-
get(ctr: Function): Map<string, FeatureConfig> | undefined;
|
|
450
|
-
set(ctr: Function, key: string, config: FeatureConfig): void;
|
|
451
|
-
hasKey(ctr: Function, key: string): boolean;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
* Core assignFeatures function.
|
|
456
|
-
* Validates inputs, registers feature configs, and installs lazy getters on the class prototype.
|
|
457
|
-
*/
|
|
458
|
-
export declare function assignFeatures(
|
|
459
|
-
ctr: Function,
|
|
460
|
-
features: FeatureConfigsMap,
|
|
461
|
-
featuresRegistry: FeaturesRegistry
|
|
462
|
-
): void;
|