assign-gingerly 0.0.72 → 0.0.74

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/enhanceAll.js CHANGED
@@ -15,7 +15,8 @@
15
15
  * { emc: 'be-observant/emc.json', matching: '[itemprop]' },
16
16
  * ]);
17
17
  */
18
- import { isAllowedImportPath } from './isAllowedImportPath.js';
18
+ import { isAllowedImportPath } from './assignPermissions/isAllowedImportPath.js';
19
+ import { findClassPrototypeInPath } from './utils/findClassPrototypeInPath.js';
19
20
  /**
20
21
  * Apply enhancements in bulk to matching elements within a target.
21
22
  *
@@ -37,7 +38,7 @@ export async function enhanceAll(target, configs, permissions) {
37
38
  // Validate EMC path unless cross-domain imports are explicitly permitted
38
39
  if (!permissions?.crossDomainImports && !isAllowedImportPath(config.emc)) {
39
40
  throw new Error(`enhanceAll: EMC path "${config.emc}" is a cross-domain URL. ` +
40
- `Only relative, absolute, or bare specifier paths are allowed by default. ` +
41
+ `Only same-origin paths or import-map-covered specifiers are allowed by default. ` +
41
42
  `Pass { crossDomainImports: true } in permissions to override.`);
42
43
  }
43
44
  // 1. Import the EMC JSON
@@ -85,13 +86,16 @@ async function resolveAndRegister(enhConfig, target) {
85
86
  if (existing)
86
87
  return existing;
87
88
  }
88
- // Not registered — dynamically import the spawn module
89
+ // Not registered — dynamically import the spawn module and extract the spawn class
89
90
  if (!spawnPath)
90
91
  return null;
91
- const spawnModule = await import(spawnPath);
92
- const SpawnClass = spawnModule.default ?? Object.values(spawnModule).find((v) => typeof v === 'function' && v.prototype);
93
- if (!SpawnClass)
92
+ let SpawnClass;
93
+ try {
94
+ SpawnClass = await findClassPrototypeInPath(spawnPath);
95
+ }
96
+ catch {
94
97
  return null;
98
+ }
95
99
  // Build and register the registry item
96
100
  const registryItem = {
97
101
  spawn: SpawnClass,
package/enhanceAll.ts CHANGED
@@ -16,8 +16,9 @@
16
16
  * ]);
17
17
  */
18
18
 
19
- import { isAllowedImportPath } from './isAllowedImportPath.js';
20
- import type { AssignPermissions } from './isAllowedImportPath.js';
19
+ import { isAllowedImportPath } from './assignPermissions/isAllowedImportPath.js';
20
+ import { findClassPrototypeInPath } from './utils/findClassPrototypeInPath.js';
21
+ import type { AssignPermissions } from './types/assign-gingerly/types.js';
21
22
 
22
23
  /**
23
24
  * Configuration for a single enhancement to apply in bulk.
@@ -57,7 +58,7 @@ export async function enhanceAll(
57
58
  if (!permissions?.crossDomainImports && !isAllowedImportPath(config.emc)) {
58
59
  throw new Error(
59
60
  `enhanceAll: EMC path "${config.emc}" is a cross-domain URL. ` +
60
- `Only relative, absolute, or bare specifier paths are allowed by default. ` +
61
+ `Only same-origin paths or import-map-covered specifiers are allowed by default. ` +
61
62
  `Pass { crossDomainImports: true } in permissions to override.`
62
63
  );
63
64
  }
@@ -112,15 +113,15 @@ async function resolveAndRegister(enhConfig: any, target: Element): Promise<any>
112
113
  if (existing) return existing;
113
114
  }
114
115
 
115
- // Not registered — dynamically import the spawn module
116
+ // Not registered — dynamically import the spawn module and extract the spawn class
116
117
  if (!spawnPath) return null;
117
118
 
118
- const spawnModule = await import(spawnPath);
119
- const SpawnClass = spawnModule.default ?? Object.values(spawnModule).find(
120
- (v: any) => typeof v === 'function' && v.prototype
121
- );
122
-
123
- if (!SpawnClass) return null;
119
+ let SpawnClass;
120
+ try {
121
+ SpawnClass = await findClassPrototypeInPath(spawnPath);
122
+ } catch {
123
+ return null;
124
+ }
124
125
 
125
126
  // Build and register the registry item
126
127
  const registryItem: any = {
@@ -9,7 +9,7 @@
9
9
 
10
10
  import assignGingerly from '../assignGingerly.js';
11
11
  import type { AddEventListenerConfig, AssignDispatchVector } from '../types/assign-gingerly/types.js';
12
- import type { AssignPermissions } from '../isAllowedImportPath.js';
12
+ import type { AssignPermissions } from '../types/assign-gingerly/types.js';
13
13
 
14
14
  /**
15
15
  * WeakMap for dedup: Element → Map<key, AbortController>
@@ -24,7 +24,7 @@ import type { LazyLoadResolvedParams, LazyLoadInstantiatedContext } from '../typ
24
24
  import { withTransition, ensureHideStyle, DEFAULT_HIDE_CLASS } from '../transitionHelper.js';
25
25
  import { findMarkers, createMarkers, getNodesBetweenMarkers, findMarkersSibling, createMarkersSibling, MARKER_START_PREFIX, MARKER_END } from '../markerUtils.js';
26
26
  import { assignFrom } from '../assignFrom.js';
27
- import type { AssignPermissions } from '../isAllowedImportPath.js';
27
+ import type { AssignPermissions } from '../types/assign-gingerly/types.js';
28
28
 
29
29
  export type { LazyLoadResolvedParams, LazyLoadInstantiatedContext };
30
30
 
@@ -21,7 +21,7 @@
21
21
  */
22
22
 
23
23
  import { LazyLoadHandler } from './lazyLoad.js';
24
- import type { AssignPermissions } from '../isAllowedImportPath.js';
24
+ import type { AssignPermissions } from '../types/assign-gingerly/types.js';
25
25
  import type { AssignFromHandler } from '../assignFromAsync.js';
26
26
  import type { LazyLoadSwitchResolvedParams } from '../types/assign-gingerly/types.js';
27
27
 
@@ -31,7 +31,7 @@ import { findMarkers, createMarkers, getNodesBetweenMarkers, MARKER_START_PREFIX
31
31
  import { resolveValue } from '../resolveValues.js';
32
32
  import { assignFrom } from '../assignFrom.js';
33
33
  import { processInferredAssignments } from '../inferredAssignments.js';
34
- import type { AssignPermissions } from '../isAllowedImportPath.js';
34
+ import type { AssignPermissions } from '../types/assign-gingerly/types.js';
35
35
 
36
36
  /**
37
37
  * Reserved keys in fromEachItem config — not treated as shorthand patterns.
@@ -23,7 +23,7 @@
23
23
 
24
24
  import type { AssignFromHandler } from '../assignFromAsync.js';
25
25
  import assignGingerly from '../assignGingerly.js';
26
- import type { AssignPermissions } from '../isAllowedImportPath.js';
26
+ import type { AssignPermissions } from '../types/assign-gingerly/types.js';
27
27
 
28
28
  /**
29
29
  * Operator keys recognized in case objects.
@@ -1,3 +1,5 @@
1
+ import {ParserOptions} from '../nested-regex-groups/types.js';
2
+
1
3
  export type EnhKey = string | symbol;
2
4
 
3
5
  // type NoUnderscore<T extends string> = T extends `_${string}` ? never : T;
@@ -196,7 +198,7 @@ export interface AttrConfig<T = unknown, TParserConfig = unknown> {
196
198
  * For named parsers like 'parse-pattern-statements', this is forwarded
197
199
  * as the options argument to the underlying parse function.
198
200
  */
199
- parserOptions?: any;
201
+ parserOptions?: ParserOptions;
200
202
  }
201
203
 
202
204
  export type AttrPatterns<T = any> = {
@@ -613,7 +615,8 @@ export interface FeatureConfig {
613
615
  */
614
616
  spawn?:
615
617
  | { new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }
616
- | (() => Promise<{ new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }>);
618
+ | (() => Promise<{ new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }>)
619
+ | string // import path or builtIns.* alias
617
620
 
618
621
  /** Attribute patterns for parsing element attributes into initVals. */
619
622
  withAttrs?: AttrPatterns<any>;
@@ -0,0 +1,9 @@
1
+ export interface FontFaceFeatureConfig {
2
+ fontFamilies: FontFaceConfig | FontFaceConfig[],
3
+ }
4
+
5
+ export interface FontFaceConfig {
6
+ name: string,
7
+ url: string,
8
+ descriptors: FontFaceDescriptors,
9
+ }
@@ -91,6 +91,18 @@ export interface ParserOptions {
91
91
  * as statement delimiters during splitting.
92
92
  */
93
93
  ignorePeriodInsideBraces?: boolean;
94
+
95
+ /**
96
+ * When true, normalizes whitespace in the input string before parsing.
97
+ * This replaces multiple whitespace characters with a single space and trims the string.
98
+ * Default is false.
99
+ *
100
+ * @example
101
+ * // Input: " First. Second. "
102
+ * // With normalizeWhitespace: true -> "First. Second."
103
+ * // With normalizeWhitespace: false -> " First. Second. "
104
+ */
105
+ normalizeWhitespace?: boolean;
94
106
  }
95
107
 
96
108
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.72",
3
+ "version": "0.0.74",
4
4
  "description": "This package provides a utility function for carefully merging one object into another.",
5
5
  "homepage": "https://github.com/bahrus/assign-gingerly#readme",
6
6
  "bugs": {
@@ -18,6 +18,7 @@
18
18
  "*.js",
19
19
  "*.ts",
20
20
  "DX/**",
21
+ "assignPermissions/**",
21
22
  "handlers/**",
22
23
  "inferencer/**",
23
24
  "README.md",
@@ -143,9 +144,17 @@
143
144
  "default": "./enhanceAll.js",
144
145
  "types": "./enhanceAll.ts"
145
146
  },
146
- "./isAllowedImportPath.js": {
147
- "default": "./isAllowedImportPath.js",
148
- "types": "./isAllowedImportPath.ts"
147
+ "./assignPermissions/isAllowedImportPath.js": {
148
+ "default": "./assignPermissions/isAllowedImportPath.js",
149
+ "types": "./assignPermissions/isAllowedImportPath.ts"
150
+ },
151
+ "./utils/findClassPrototypeInPath.js": {
152
+ "default": "./utils/findClassPrototypeInPath.js",
153
+ "types": "./utils/findClassPrototypeInPath.ts"
154
+ },
155
+ "./utils/isAsyncSpawn.js": {
156
+ "default": "./utils/isAsyncSpawn.js",
157
+ "types": "./utils/isAsyncSpawn.ts"
149
158
  },
150
159
  "./markerUtils.js": {
151
160
  "default": "./markerUtils.js",
@@ -6,7 +6,8 @@
6
6
  import { resolveValues } from './resolveValues.js';
7
7
  import { getValues } from './getValues.js';
8
8
  import { evaluatePathWithMethods } from './assignGingerly.js';
9
- import { buildRestrictedPropSet, isAllowedImportPath, redirectRestrictedProp } from './isAllowedImportPath.js';
9
+ import { findClassPrototypeInPath } from './utils/findClassPrototypeInPath.js';
10
+ import { buildRestrictedPropSet, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
10
11
  /**
11
12
  * Map of built-in handler names to their module paths.
12
13
  * These are auto-loaded on demand — no explicit import required.
@@ -20,23 +21,11 @@ const BUILT_IN_MAP = {
20
21
  'builtIns.rangeSelector': './handlers/rangeSelector.js',
21
22
  };
22
23
  /**
23
- * Find a handler class in a dynamically imported module.
24
- * Checks default export first, then searches for the first class with `assign` on prototype.
24
+ * Criteria for locating an assignFrom handler class: must expose an `assign` method
25
+ * on its prototype.
25
26
  */
26
- function findHandlerInModule(module) {
27
- // Check default export first
28
- if (module.default && typeof module.default === 'function'
29
- && module.default.prototype && 'assign' in module.default.prototype) {
30
- return module.default;
31
- }
32
- // Search other exports
33
- for (const key of Object.keys(module)) {
34
- const exported = module[key];
35
- if (typeof exported === 'function' && exported.prototype && 'assign' in exported.prototype) {
36
- return exported;
37
- }
38
- }
39
- return undefined;
27
+ function handlerCriteria(proto) {
28
+ return 'assign' in proto.prototype;
40
29
  }
41
30
  /**
42
31
  * Cache for loaded built-in handler classes — avoids await on subsequent calls.
@@ -54,16 +43,14 @@ async function loadBuiltIn(name) {
54
43
  const path = BUILT_IN_MAP[name];
55
44
  if (!path)
56
45
  return undefined;
57
- const module = await import(path);
58
- const cls = findHandlerInModule(module);
59
- if (cls)
60
- handlerCache.set(name, cls);
46
+ const cls = await findClassPrototypeInPath(path, handlerCriteria);
47
+ handlerCache.set(name, cls);
61
48
  return cls;
62
49
  }
63
50
  /**
64
51
  * Resolve a handler from options.handlers (class constructor or import path).
65
52
  */
66
- async function resolveFromHandlers(name, handlers, permissions) {
53
+ async function resolveFromHandlers(name, handlers) {
67
54
  if (!handlers || !(name in handlers))
68
55
  return undefined;
69
56
  const entry = handlers[name];
@@ -77,17 +64,8 @@ async function resolveFromHandlers(name, handlers, permissions) {
77
64
  if (entry.startsWith('builtIns.')) {
78
65
  return loadBuiltIn(entry);
79
66
  }
80
- // Import path string — validate and dynamically import
81
- if (!permissions?.crossDomainImports && !isAllowedImportPath(entry)) {
82
- throw new Error(`assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
83
- `Only relative, absolute, or bare specifier paths are allowed (no cross-domain URLs). ` +
84
- `Pass { crossDomainImports: true } in permissions to override.`);
85
- }
86
- const module = await import(entry);
87
- const HandlerClass = findHandlerInModule(module);
88
- if (!HandlerClass) {
89
- throw new Error(`assignFrom: handler "${name}" — module "${entry}" does not export a valid handler class.`);
90
- }
67
+ // Import path string — validate and extract handler class via shared utility
68
+ const HandlerClass = await findClassPrototypeInPath(entry, handlerCriteria);
91
69
  return HandlerClass;
92
70
  }
93
71
  return undefined;
@@ -179,7 +157,7 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
179
157
  for (const config of configs) {
180
158
  //return; //1.3ms
181
159
  // 1. Check options.handlers (local, per-call)
182
- let HandlerClass = await resolveFromHandlers(config.do, options.handlers, permissions);
160
+ let HandlerClass = await resolveFromHandlers(config.do, options.handlers);
183
161
  //return; // 1.4
184
162
  // 2. Fallback to built-in auto-load
185
163
  if (!HandlerClass && config.do.startsWith('builtIns.')) {
@@ -7,8 +7,9 @@
7
7
  import { resolveValues } from './resolveValues.js';
8
8
  import { getValues } from './getValues.js';
9
9
  import { evaluatePathWithMethods } from './assignGingerly.js';
10
- import { buildRestrictedPropSet, isAllowedImportPath, redirectRestrictedProp } from './isAllowedImportPath.js';
11
- import type { AssignPermissions } from './isAllowedImportPath.js';
10
+ import { findClassPrototypeInPath } from './utils/findClassPrototypeInPath.js';
11
+ import { buildRestrictedPropSet, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
12
+ import type { AssignPermissions } from './types/assign-gingerly/types.js';
12
13
  import type { AssignFromOptions, AssignFromHandlerConstructor } from './assignFromAsync.js';
13
14
 
14
15
  /**
@@ -25,23 +26,11 @@ const BUILT_IN_MAP: Record<string, string> = {
25
26
  };
26
27
 
27
28
  /**
28
- * Find a handler class in a dynamically imported module.
29
- * Checks default export first, then searches for the first class with `assign` on prototype.
29
+ * Criteria for locating an assignFrom handler class: must expose an `assign` method
30
+ * on its prototype.
30
31
  */
31
- function findHandlerInModule(module: any): AssignFromHandlerConstructor | undefined {
32
- // Check default export first
33
- if (module.default && typeof module.default === 'function'
34
- && module.default.prototype && 'assign' in module.default.prototype) {
35
- return module.default;
36
- }
37
- // Search other exports
38
- for (const key of Object.keys(module)) {
39
- const exported = module[key];
40
- if (typeof exported === 'function' && exported.prototype && 'assign' in exported.prototype) {
41
- return exported as AssignFromHandlerConstructor;
42
- }
43
- }
44
- return undefined;
32
+ function handlerCriteria(proto: any): boolean {
33
+ return 'assign' in proto.prototype;
45
34
  }
46
35
 
47
36
  /**
@@ -59,10 +48,9 @@ async function loadBuiltIn(name: string): Promise<AssignFromHandlerConstructor |
59
48
  if (cached) return cached;
60
49
  const path = BUILT_IN_MAP[name];
61
50
  if (!path) return undefined;
62
- const module = await import(path);
63
- const cls = findHandlerInModule(module);
64
- if (cls) handlerCache.set(name, cls);
65
- return cls;
51
+ const cls = await findClassPrototypeInPath(path, handlerCriteria);
52
+ handlerCache.set(name, cls);
53
+ return cls as AssignFromHandlerConstructor;
66
54
  }
67
55
 
68
56
  /**
@@ -70,8 +58,7 @@ async function loadBuiltIn(name: string): Promise<AssignFromHandlerConstructor |
70
58
  */
71
59
  async function resolveFromHandlers(
72
60
  name: string,
73
- handlers: Record<string, AssignFromHandlerConstructor | string> | undefined,
74
- permissions?: AssignPermissions
61
+ handlers: Record<string, AssignFromHandlerConstructor | string> | undefined
75
62
  ): Promise<AssignFromHandlerConstructor | undefined> {
76
63
  if (!handlers || !(name in handlers)) return undefined;
77
64
 
@@ -89,22 +76,9 @@ async function resolveFromHandlers(
89
76
  return loadBuiltIn(entry);
90
77
  }
91
78
 
92
- // Import path string — validate and dynamically import
93
- if (!permissions?.crossDomainImports && !isAllowedImportPath(entry)) {
94
- throw new Error(
95
- `assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
96
- `Only relative, absolute, or bare specifier paths are allowed (no cross-domain URLs). ` +
97
- `Pass { crossDomainImports: true } in permissions to override.`
98
- );
99
- }
100
- const module = await import(entry);
101
- const HandlerClass = findHandlerInModule(module);
102
- if (!HandlerClass) {
103
- throw new Error(
104
- `assignFrom: handler "${name}" — module "${entry}" does not export a valid handler class.`
105
- );
106
- }
107
- return HandlerClass;
79
+ // Import path string — validate and extract handler class via shared utility
80
+ const HandlerClass = await findClassPrototypeInPath(entry, handlerCriteria);
81
+ return HandlerClass as AssignFromHandlerConstructor;
108
82
  }
109
83
 
110
84
  return undefined;
@@ -203,7 +177,7 @@ export async function processHandlerCommands(
203
177
  for (const config of configs) {
204
178
  //return; //1.3ms
205
179
  // 1. Check options.handlers (local, per-call)
206
- let HandlerClass = await resolveFromHandlers(config.do, options.handlers, permissions);
180
+ let HandlerClass = await resolveFromHandlers(config.do, options.handlers);
207
181
  //return; // 1.4
208
182
  // 2. Fallback to built-in auto-load
209
183
  if (!HandlerClass && config.do.startsWith('builtIns.')) {
@@ -1,12 +1,12 @@
1
1
  /**
2
- * resolveAndAssignFeatures - Resolves async fallback spawns then calls assignFeatures.
2
+ * resolveAndAssignFeatures - Thin wrapper around assignFeatures for backward compatibility.
3
3
  *
4
- * For each feature in the config that doesn't have an explicit `spawn`, resolves
5
- * the async `fallbackSpawn` from the class's `static supportedFeatures` and sets
6
- * it as the spawn. Then calls `assignFeatures` with the fully resolved config.
4
+ * Resolves all configured spawns (including async fallback spawns and string import paths)
5
+ * and installs feature getters on the class prototype, then delegates to the registry's
6
+ * assignFeatures implementation.
7
7
  *
8
- * This eliminates the boilerplate of manually resolving async spawns before
9
- * calling assignFeatures.
8
+ * This is kept as a convenience entry point for callers that already await this function.
9
+ * The actual resolution logic lives in assignFeatures.
10
10
  *
11
11
  * @example
12
12
  * import { resolveAndAssignFeatures } from 'assign-gingerly/resolveAndAssignFeatures.js';
@@ -22,46 +22,15 @@
22
22
  * }
23
23
  * });
24
24
  */
25
+ import { assignFeatures } from './assignFeatures.js';
25
26
  /**
26
- * Determines if a function is an async spawner.
27
- */
28
- function isAsyncSpawn(fn) {
29
- if (typeof fn !== 'function')
30
- return false;
31
- if (fn.constructor.name === 'AsyncFunction')
32
- return true;
33
- if (fn.prototype === undefined)
34
- return true;
35
- return false;
36
- }
37
- /**
38
- * Resolves async fallback spawns for features that don't have an explicit spawn,
39
- * then calls assignFeatures on the registry.
27
+ * Resolves all configured spawns and calls assignFeatures on the registry.
40
28
  *
41
29
  * @param ElementClass - The custom element class (must have static supportedFeatures)
42
30
  * @param featuresConfig - Feature configurations (spawn will be resolved from fallbackSpawn if missing)
43
31
  * @param registry - Optional CustomElementRegistry (defaults to global customElements)
44
32
  */
45
33
  export async function resolveAndAssignFeatures(ElementClass, featuresConfig, registry) {
46
- const supportedFeatures = ElementClass.supportedFeatures;
47
- if (!supportedFeatures) {
48
- throw new Error(`resolveAndAssignFeatures: ${ElementClass.name || 'constructor'} does not define static supportedFeatures`);
49
- }
50
- // Resolve async fallback spawns in parallel for features without explicit spawn
51
- await Promise.all(Object.entries(featuresConfig).map(async ([key, featureConfig]) => {
52
- // Skip if spawn is already provided
53
- if (featureConfig.spawn)
54
- return;
55
- const optIn = supportedFeatures[key];
56
- if (!optIn?.fallbackSpawn)
57
- return;
58
- let spawn = optIn.fallbackSpawn;
59
- if (isAsyncSpawn(spawn)) {
60
- spawn = await spawn();
61
- }
62
- featureConfig.spawn = spawn;
63
- }));
64
- // Call assignFeatures on the registry
65
34
  const reg = registry || customElements;
66
- await reg.assignFeatures(ElementClass, featuresConfig);
35
+ await assignFeatures(ElementClass, featuresConfig, reg.featuresRegistry);
67
36
  }
@@ -1,79 +1,43 @@
1
- /**
2
- * resolveAndAssignFeatures - Resolves async fallback spawns then calls assignFeatures.
3
- *
4
- * For each feature in the config that doesn't have an explicit `spawn`, resolves
5
- * the async `fallbackSpawn` from the class's `static supportedFeatures` and sets
6
- * it as the spawn. Then calls `assignFeatures` with the fully resolved config.
7
- *
8
- * This eliminates the boilerplate of manually resolving async spawns before
9
- * calling assignFeatures.
10
- *
11
- * @example
12
- * import { resolveAndAssignFeatures } from 'assign-gingerly/resolveAndAssignFeatures.js';
13
- *
14
- * await resolveAndAssignFeatures(MyElement, {
15
- * roundabout: {
16
- * customData: {...},
17
- * withAttrs: {...},
18
- * callbackForwarding: ['connectedCallback']
19
- * },
20
- * faceUp: {
21
- * callbackForwarding: ['connectedCallback', 'disconnectedCallback']
22
- * }
23
- * });
24
- */
25
-
26
- import { FeatureConfigsMap, SupportedFeaturesMap } from './types/assign-gingerly/types.js';
27
-
28
- /**
29
- * Determines if a function is an async spawner.
30
- */
31
- function isAsyncSpawn(fn: any): boolean {
32
- if (typeof fn !== 'function') return false;
33
- if (fn.constructor.name === 'AsyncFunction') return true;
34
- if (fn.prototype === undefined) return true;
35
- return false;
36
- }
37
-
38
- /**
39
- * Resolves async fallback spawns for features that don't have an explicit spawn,
40
- * then calls assignFeatures on the registry.
41
- *
42
- * @param ElementClass - The custom element class (must have static supportedFeatures)
43
- * @param featuresConfig - Feature configurations (spawn will be resolved from fallbackSpawn if missing)
44
- * @param registry - Optional CustomElementRegistry (defaults to global customElements)
45
- */
46
- export async function resolveAndAssignFeatures(
47
- ElementClass: Function,
48
- featuresConfig: FeatureConfigsMap,
49
- registry?: any
50
- ): Promise<void> {
51
- const supportedFeatures: SupportedFeaturesMap | undefined = (ElementClass as any).supportedFeatures;
52
-
53
- if (!supportedFeatures) {
54
- throw new Error(
55
- `resolveAndAssignFeatures: ${(ElementClass as any).name || 'constructor'} does not define static supportedFeatures`
56
- );
57
- }
58
-
59
- // Resolve async fallback spawns in parallel for features without explicit spawn
60
- await Promise.all(
61
- Object.entries(featuresConfig).map(async ([key, featureConfig]) => {
62
- // Skip if spawn is already provided
63
- if (featureConfig.spawn) return;
64
-
65
- const optIn = supportedFeatures[key];
66
- if (!optIn?.fallbackSpawn) return;
67
-
68
- let spawn = optIn.fallbackSpawn;
69
- if (isAsyncSpawn(spawn)) {
70
- spawn = await (spawn as () => Promise<any>)();
71
- }
72
- (featureConfig as any).spawn = spawn;
73
- })
74
- );
75
-
76
- // Call assignFeatures on the registry
77
- const reg = registry || customElements;
78
- await reg.assignFeatures(ElementClass, featuresConfig);
79
- }
1
+ /**
2
+ * resolveAndAssignFeatures - Thin wrapper around assignFeatures for backward compatibility.
3
+ *
4
+ * Resolves all configured spawns (including async fallback spawns and string import paths)
5
+ * and installs feature getters on the class prototype, then delegates to the registry's
6
+ * assignFeatures implementation.
7
+ *
8
+ * This is kept as a convenience entry point for callers that already await this function.
9
+ * The actual resolution logic lives in assignFeatures.
10
+ *
11
+ * @example
12
+ * import { resolveAndAssignFeatures } from 'assign-gingerly/resolveAndAssignFeatures.js';
13
+ *
14
+ * await resolveAndAssignFeatures(MyElement, {
15
+ * roundabout: {
16
+ * customData: {...},
17
+ * withAttrs: {...},
18
+ * callbackForwarding: ['connectedCallback']
19
+ * },
20
+ * faceUp: {
21
+ * callbackForwarding: ['connectedCallback', 'disconnectedCallback']
22
+ * }
23
+ * });
24
+ */
25
+
26
+ import { FeatureConfigsMap } from './types/assign-gingerly/types.js';
27
+ import { assignFeatures } from './assignFeatures.js';
28
+
29
+ /**
30
+ * Resolves all configured spawns and calls assignFeatures on the registry.
31
+ *
32
+ * @param ElementClass - The custom element class (must have static supportedFeatures)
33
+ * @param featuresConfig - Feature configurations (spawn will be resolved from fallbackSpawn if missing)
34
+ * @param registry - Optional CustomElementRegistry (defaults to global customElements)
35
+ */
36
+ export async function resolveAndAssignFeatures(
37
+ ElementClass: Function,
38
+ featuresConfig: FeatureConfigsMap,
39
+ registry?: any
40
+ ): Promise<void> {
41
+ const reg = registry || customElements;
42
+ await assignFeatures(ElementClass, featuresConfig, reg.featuresRegistry);
43
+ }