assign-gingerly 0.0.31 → 0.0.32

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/eachTime.js ADDED
@@ -0,0 +1,110 @@
1
+ /**
2
+ * eachTime.ts - Reactive forEach implementation for @eachTime symbol
3
+ * This module is dynamically loaded only when @eachTime is encountered
4
+ * Provides event-driven iteration over elements as they mount
5
+ */
6
+ /**
7
+ * Check if a value is an EventTarget
8
+ */
9
+ function isEventTarget(value) {
10
+ return value != null && typeof value.addEventListener === 'function';
11
+ }
12
+ /**
13
+ * Handle reactive forEach (@eachTime) by setting up event listeners
14
+ *
15
+ * @param target - The root object to start navigation from
16
+ * @param pathParts - Complete path parts array including @eachTime
17
+ * @param forEachIndex - Index of @eachTime in pathParts
18
+ * @param value - Value to assign to each mounted element
19
+ * @param withMethods - Set of method names to call instead of assign
20
+ * @param aliasMap - Map of aliases for token substitution
21
+ * @param options - Options including required AbortSignal
22
+ */
23
+ export async function handleEachTime(target, pathParts, forEachIndex, value, withMethods, aliasMap, options) {
24
+ // Validate signal - required for cleanup
25
+ if (!options?.signal) {
26
+ throw new Error('@eachTime requires an AbortSignal in options.signal for cleanup');
27
+ }
28
+ // Split path into before and after @eachTime
29
+ const pathToEventSource = pathParts.slice(0, forEachIndex);
30
+ const pathAfterForEach = pathParts.slice(forEachIndex + 1);
31
+ // Navigate to the event source
32
+ let eventSource = target;
33
+ if (pathToEventSource.length > 0) {
34
+ // Import evaluatePathWithMethods for navigation
35
+ const { evaluatePathWithMethods } = await import('./assignGingerly.js');
36
+ if (withMethods && withMethods.size > 0) {
37
+ const result = evaluatePathWithMethods(target, pathToEventSource, value, withMethods);
38
+ eventSource = result.target;
39
+ }
40
+ else {
41
+ // Simple property navigation
42
+ for (const part of pathToEventSource) {
43
+ eventSource = eventSource[part];
44
+ }
45
+ }
46
+ }
47
+ // Validate event source is an EventTarget
48
+ if (!isEventTarget(eventSource)) {
49
+ throw new Error('@eachTime requires an EventTarget (e.g., IMountObserver)');
50
+ }
51
+ // Setup event listener for 'mount' events
52
+ const handler = (event) => {
53
+ // Extract mounted element from IMountEvent
54
+ const mountedElement = event.mountedElement;
55
+ if (!mountedElement)
56
+ return;
57
+ // Apply remaining path to mounted element (async to avoid blocking)
58
+ (async () => {
59
+ try {
60
+ // Import needed functions from assignGingerly
61
+ const { evaluatePathWithMethods, assignGingerly, isReadonlyProperty, isClassInstance } = await import('./assignGingerly.js');
62
+ if (pathAfterForEach.length > 0) {
63
+ const result = evaluatePathWithMethods(mountedElement, pathAfterForEach, value, withMethods || new Set());
64
+ if (result.isMethod) {
65
+ // Last segment is a method - call it
66
+ const method = result.target[result.lastKey];
67
+ if (typeof method === 'function') {
68
+ if (Array.isArray(value)) {
69
+ method.apply(result.target, value);
70
+ }
71
+ else {
72
+ method.call(result.target, value);
73
+ }
74
+ }
75
+ }
76
+ else {
77
+ // Normal assignment
78
+ const lastKey = result.lastKey;
79
+ const parent = result.target;
80
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
81
+ // Check if property exists and is readonly OR is a class instance
82
+ if (lastKey in parent && (isReadonlyProperty(parent, lastKey) || isClassInstance(parent[lastKey]))) {
83
+ const currentValue = parent[lastKey];
84
+ if (typeof currentValue !== 'object' || currentValue === null) {
85
+ throw new Error(`Cannot merge object into ${isReadonlyProperty(parent, lastKey) ? 'readonly ' : ''}primitive property '${String(lastKey)}'`);
86
+ }
87
+ // Recursively apply assignGingerly
88
+ assignGingerly(currentValue, value, options);
89
+ }
90
+ else {
91
+ // Property is writable and not a class instance - replace it
92
+ parent[lastKey] = value;
93
+ }
94
+ }
95
+ else {
96
+ // Primitive value - direct assignment
97
+ parent[lastKey] = value;
98
+ }
99
+ }
100
+ }
101
+ }
102
+ catch (error) {
103
+ console.error('Error applying @eachTime to mounted element:', error);
104
+ }
105
+ })();
106
+ };
107
+ // Register listener with AbortSignal for automatic cleanup
108
+ // Hardcode 'mount' event for IMountObserver
109
+ eventSource.addEventListener('mount', handler, { signal: options.signal });
110
+ }
package/eachTime.ts ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * eachTime.ts - Reactive forEach implementation for @eachTime symbol
3
+ * This module is dynamically loaded only when @eachTime is encountered
4
+ * Provides event-driven iteration over elements as they mount
5
+ */
6
+
7
+ import type { IAssignGingerlyOptions } from './assignGingerly.js';
8
+
9
+ /**
10
+ * Check if a value is an EventTarget
11
+ */
12
+ function isEventTarget(value: any): boolean {
13
+ return value != null && typeof value.addEventListener === 'function';
14
+ }
15
+
16
+ /**
17
+ * Handle reactive forEach (@eachTime) by setting up event listeners
18
+ *
19
+ * @param target - The root object to start navigation from
20
+ * @param pathParts - Complete path parts array including @eachTime
21
+ * @param forEachIndex - Index of @eachTime in pathParts
22
+ * @param value - Value to assign to each mounted element
23
+ * @param withMethods - Set of method names to call instead of assign
24
+ * @param aliasMap - Map of aliases for token substitution
25
+ * @param options - Options including required AbortSignal
26
+ */
27
+ export async function handleEachTime(
28
+ target: any,
29
+ pathParts: string[],
30
+ forEachIndex: number,
31
+ value: any,
32
+ withMethods: Set<string> | undefined,
33
+ aliasMap: Map<string, string>,
34
+ options?: IAssignGingerlyOptions
35
+ ): Promise<void> {
36
+ // Validate signal - required for cleanup
37
+ if (!options?.signal) {
38
+ throw new Error('@eachTime requires an AbortSignal in options.signal for cleanup');
39
+ }
40
+
41
+ // Split path into before and after @eachTime
42
+ const pathToEventSource = pathParts.slice(0, forEachIndex);
43
+ const pathAfterForEach = pathParts.slice(forEachIndex + 1);
44
+
45
+ // Navigate to the event source
46
+ let eventSource = target;
47
+ if (pathToEventSource.length > 0) {
48
+ // Import evaluatePathWithMethods for navigation
49
+ const { evaluatePathWithMethods } = await import('./assignGingerly.js');
50
+
51
+ if (withMethods && withMethods.size > 0) {
52
+ const result = evaluatePathWithMethods(target, pathToEventSource, value, withMethods);
53
+ eventSource = result.target;
54
+ } else {
55
+ // Simple property navigation
56
+ for (const part of pathToEventSource) {
57
+ eventSource = eventSource[part];
58
+ }
59
+ }
60
+ }
61
+
62
+ // Validate event source is an EventTarget
63
+ if (!isEventTarget(eventSource)) {
64
+ throw new Error('@eachTime requires an EventTarget (e.g., IMountObserver)');
65
+ }
66
+
67
+ // Setup event listener for 'mount' events
68
+ const handler = (event: Event) => {
69
+ // Extract mounted element from IMountEvent
70
+ const mountedElement = (event as any).mountedElement;
71
+ if (!mountedElement) return;
72
+
73
+ // Apply remaining path to mounted element (async to avoid blocking)
74
+ (async () => {
75
+ try {
76
+ // Import needed functions from assignGingerly
77
+ const {
78
+ evaluatePathWithMethods,
79
+ assignGingerly,
80
+ isReadonlyProperty,
81
+ isClassInstance
82
+ } = await import('./assignGingerly.js');
83
+
84
+ if (pathAfterForEach.length > 0) {
85
+ const result = evaluatePathWithMethods(
86
+ mountedElement,
87
+ pathAfterForEach,
88
+ value,
89
+ withMethods || new Set()
90
+ );
91
+
92
+ if (result.isMethod) {
93
+ // Last segment is a method - call it
94
+ const method = result.target[result.lastKey];
95
+ if (typeof method === 'function') {
96
+ if (Array.isArray(value)) {
97
+ method.apply(result.target, value);
98
+ } else {
99
+ method.call(result.target, value);
100
+ }
101
+ }
102
+ } else {
103
+ // Normal assignment
104
+ const lastKey = result.lastKey;
105
+ const parent = result.target;
106
+
107
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
108
+ // Check if property exists and is readonly OR is a class instance
109
+ if (lastKey in parent && (isReadonlyProperty(parent, lastKey) || isClassInstance(parent[lastKey]))) {
110
+ const currentValue = parent[lastKey];
111
+ if (typeof currentValue !== 'object' || currentValue === null) {
112
+ throw new Error(
113
+ `Cannot merge object into ${isReadonlyProperty(parent, lastKey) ? 'readonly ' : ''}primitive property '${String(lastKey)}'`
114
+ );
115
+ }
116
+ // Recursively apply assignGingerly
117
+ assignGingerly(currentValue, value, options);
118
+ } else {
119
+ // Property is writable and not a class instance - replace it
120
+ parent[lastKey] = value;
121
+ }
122
+ } else {
123
+ // Primitive value - direct assignment
124
+ parent[lastKey] = value;
125
+ }
126
+ }
127
+ }
128
+ } catch (error) {
129
+ console.error('Error applying @eachTime to mounted element:', error);
130
+ }
131
+ })();
132
+ };
133
+
134
+ // Register listener with AbortSignal for automatic cleanup
135
+ // Hardcode 'mount' event for IMountObserver
136
+ eventSource.addEventListener('mount', handler, { signal: options.signal });
137
+ }
package/index.js CHANGED
@@ -7,4 +7,6 @@ export { parseWithAttrs } from './parseWithAttrs.js';
7
7
  export { buildCSSQuery } from './buildCSSQuery.js';
8
8
  export { resolveTemplate } from './resolveTemplate.js';
9
9
  export { getHost } from './getHost.js';
10
+ export { resolveValues } from './resolveValues.js';
11
+ export { assignFrom } from './assignFrom.js';
10
12
  import './object-extension.js';
package/index.ts CHANGED
@@ -7,4 +7,6 @@ export {parseWithAttrs} from './parseWithAttrs.js';
7
7
  export {buildCSSQuery} from './buildCSSQuery.js';
8
8
  export {resolveTemplate} from './resolveTemplate.js';
9
9
  export {getHost} from './getHost.js';
10
+ export {resolveValues} from './resolveValues.js';
11
+ export {assignFrom} from './assignFrom.js';
10
12
  import './object-extension.js';
@@ -64,9 +64,27 @@ class ElementEnhancementContainer {
64
64
  constructor(element) {
65
65
  this.element = element;
66
66
  }
67
+ /**
68
+ * Resolve a registryItem parameter to an EnhancementConfig.
69
+ * If a string or symbol is passed, looks it up via findByEnhKey in the registry.
70
+ * @param registryItem - EnhancementConfig object, or string/symbol enhKey
71
+ * @param registry - The enhancement registry to search
72
+ * @returns The resolved EnhancementConfig
73
+ * @throws Error if string/symbol not found in registry
74
+ */
75
+ resolveRegistryItem(registryItem, registry) {
76
+ if (typeof registryItem === 'string' || typeof registryItem === 'symbol') {
77
+ const found = registry.findByEnhKey(registryItem);
78
+ if (!found) {
79
+ throw new Error(`${String(registryItem)} not in registry`);
80
+ }
81
+ return found;
82
+ }
83
+ return registryItem;
84
+ }
67
85
  /**
68
86
  * Get or spawn an instance for a registry item
69
- * @param registryItem - The registry item to get/spawn instance for
87
+ * @param registryItem - The registry item (EnhancementConfig object, or string/symbol enhKey) to get/spawn instance for
70
88
  * @param mountCtx - Optional context to pass to the spawned instance
71
89
  * @returns The spawned instance
72
90
  */
@@ -77,6 +95,8 @@ class ElementEnhancementContainer {
77
95
  if (!registry) {
78
96
  throw new Error('customElementRegistry.enhancementRegistry not available');
79
97
  }
98
+ // Resolve string/symbol to EnhancementConfig via enhKey lookup
99
+ registryItem = this.resolveRegistryItem(registryItem, registry);
80
100
  // Check if registryItem is in the registry
81
101
  const items = registry.getItems();
82
102
  if (!items.includes(registryItem)) {
@@ -109,7 +129,7 @@ class ElementEnhancementContainer {
109
129
  mountCtx,
110
130
  synthesizerElement: mountCtx?.synthesizerElement
111
131
  };
112
- attrInitVals = parseWithAttrs(element, registryItem.withAttrs, registryItem.allowUnprefixed || false, spawnContext);
132
+ attrInitVals = parseWithAttrs(element, registryItem.withAttrs, registryItem.allowUnprefixed, spawnContext);
113
133
  }
114
134
  catch (e) {
115
135
  console.error('Error parsing attributes:', e);
@@ -145,48 +165,81 @@ class ElementEnhancementContainer {
145
165
  }
146
166
  /**
147
167
  * Dispose of an enhancement instance
148
- * @param registryItem - The registry item to dispose
168
+ * @param registryItem - The registry item (EnhancementConfig object, or string/symbol enhKey) to dispose
149
169
  */
150
170
  dispose(registryItem) {
151
171
  const element = this.element;
172
+ // Resolve string/symbol to EnhancementConfig via enhKey lookup
173
+ let resolved;
174
+ if (typeof registryItem === 'string' || typeof registryItem === 'symbol') {
175
+ const registry = element.customElementRegistry?.enhancementRegistry;
176
+ if (!registry) {
177
+ return; // No registry, nothing to dispose
178
+ }
179
+ const found = registry.findByEnhKey(registryItem);
180
+ if (!found) {
181
+ throw new Error(`${String(registryItem)} not in registry`);
182
+ }
183
+ resolved = found;
184
+ }
185
+ else {
186
+ resolved = registryItem;
187
+ }
152
188
  // Get the instance map
153
189
  const instanceMap = getInstanceMap();
154
190
  if (!instanceMap.has(element)) {
155
191
  return; // No instances for this element
156
192
  }
157
193
  const instances = instanceMap.get(element);
158
- const spawnedInstance = instances.get(registryItem);
194
+ const spawnedInstance = instances.get(resolved);
159
195
  if (!spawnedInstance) {
160
196
  return; // No instance for this registry item
161
197
  }
162
198
  // Call dispose lifecycle method if it exists
163
- const lifecycleKeys = normalizeLifecycleKeys(registryItem?.lifecycleKeys);
199
+ const lifecycleKeys = normalizeLifecycleKeys(resolved.lifecycleKeys);
164
200
  const disposeKey = lifecycleKeys?.dispose;
165
201
  if (disposeKey && typeof spawnedInstance[disposeKey] === 'function') {
166
- spawnedInstance[disposeKey](registryItem);
202
+ spawnedInstance[disposeKey](resolved);
167
203
  }
168
204
  // Remove from instance map
169
- instances.delete(registryItem);
205
+ instances.delete(resolved);
170
206
  // Remove from enh container if it has an enhKey
171
- if (registryItem.enhKey) {
207
+ if (resolved.enhKey) {
172
208
  const self = this;
173
- delete self[registryItem.enhKey];
209
+ delete self[resolved.enhKey];
174
210
  }
175
211
  }
176
212
  /**
177
213
  * Wait for an enhancement instance to be resolved
178
- * @param registryItem - The registry item to wait for
214
+ * @param registryItem - The registry item (EnhancementConfig object, or string/symbol enhKey) to wait for
179
215
  * @param mountCtx - Optional context to pass to the spawned instance
180
216
  * @returns Promise that resolves with the spawned instance
181
217
  */
182
218
  async whenResolved(registryItem, mountCtx) {
183
- const lifecycleKeys = normalizeLifecycleKeys(registryItem?.lifecycleKeys);
219
+ // Resolve string/symbol to EnhancementConfig via enhKey lookup
220
+ let resolved;
221
+ if (typeof registryItem === 'string' || typeof registryItem === 'symbol') {
222
+ const element = this.element;
223
+ const registry = element.customElementRegistry?.enhancementRegistry;
224
+ if (!registry) {
225
+ throw new Error('customElementRegistry.enhancementRegistry not available');
226
+ }
227
+ const found = registry.findByEnhKey(registryItem);
228
+ if (!found) {
229
+ throw new Error(`${String(registryItem)} not in registry`);
230
+ }
231
+ resolved = found;
232
+ }
233
+ else {
234
+ resolved = registryItem;
235
+ }
236
+ const lifecycleKeys = normalizeLifecycleKeys(resolved.lifecycleKeys);
184
237
  const resolvedKey = lifecycleKeys?.resolved;
185
238
  if (resolvedKey === undefined) {
186
239
  throw new Error('Must specify resolved key in lifecycleKeys');
187
240
  }
188
241
  // Get or spawn the instance (pass mountCtx through)
189
- const spawnedInstance = this.get(registryItem, mountCtx);
242
+ const spawnedInstance = this.get(resolved, mountCtx);
190
243
  // Check if already resolved
191
244
  if (spawnedInstance[resolvedKey]) {
192
245
  return spawnedInstance;
@@ -1,4 +1,5 @@
1
1
  import assignGingerly, { EnhancementRegistry, ItemscopeRegistry, IAssignGingerlyOptions, getInstanceMap, INSTANCE_MAP_GUID } from './assignGingerly.js';
2
+ import { EnhancementConfig } from './types/assign-gingerly/types.js';
2
3
  import { parseWithAttrs } from './parseWithAttrs.js';
3
4
 
4
5
  /**
@@ -132,13 +133,35 @@ class ElementEnhancementContainer {
132
133
  this.element = element;
133
134
  }
134
135
 
136
+ /**
137
+ * Resolve a registryItem parameter to an EnhancementConfig.
138
+ * If a string or symbol is passed, looks it up via findByEnhKey in the registry.
139
+ * @param registryItem - EnhancementConfig object, or string/symbol enhKey
140
+ * @param registry - The enhancement registry to search
141
+ * @returns The resolved EnhancementConfig
142
+ * @throws Error if string/symbol not found in registry
143
+ */
144
+ private resolveRegistryItem(
145
+ registryItem: EnhancementConfig | string | symbol,
146
+ registry: EnhancementRegistry
147
+ ): EnhancementConfig {
148
+ if (typeof registryItem === 'string' || typeof registryItem === 'symbol') {
149
+ const found = registry.findByEnhKey(registryItem);
150
+ if (!found) {
151
+ throw new Error(`${String(registryItem)} not in registry`);
152
+ }
153
+ return found;
154
+ }
155
+ return registryItem;
156
+ }
157
+
135
158
  /**
136
159
  * Get or spawn an instance for a registry item
137
- * @param registryItem - The registry item to get/spawn instance for
160
+ * @param registryItem - The registry item (EnhancementConfig object, or string/symbol enhKey) to get/spawn instance for
138
161
  * @param mountCtx - Optional context to pass to the spawned instance
139
162
  * @returns The spawned instance
140
163
  */
141
- get(registryItem: any, mountCtx?: any): any {
164
+ get(registryItem: EnhancementConfig | string | symbol, mountCtx?: any): any {
142
165
  const element = this.element;
143
166
 
144
167
  // Get the registry from customElementRegistry
@@ -148,6 +171,9 @@ class ElementEnhancementContainer {
148
171
  throw new Error('customElementRegistry.enhancementRegistry not available');
149
172
  }
150
173
 
174
+ // Resolve string/symbol to EnhancementConfig via enhKey lookup
175
+ registryItem = this.resolveRegistryItem(registryItem, registry);
176
+
151
177
  // Check if registryItem is in the registry
152
178
  const items = registry.getItems();
153
179
  if (!items.includes(registryItem)) {
@@ -188,7 +214,7 @@ class ElementEnhancementContainer {
188
214
  attrInitVals = parseWithAttrs(
189
215
  element,
190
216
  registryItem.withAttrs,
191
- registryItem.allowUnprefixed || false,
217
+ registryItem.allowUnprefixed,
192
218
  spawnContext
193
219
  );
194
220
  } catch (e) {
@@ -232,11 +258,27 @@ class ElementEnhancementContainer {
232
258
 
233
259
  /**
234
260
  * Dispose of an enhancement instance
235
- * @param registryItem - The registry item to dispose
261
+ * @param registryItem - The registry item (EnhancementConfig object, or string/symbol enhKey) to dispose
236
262
  */
237
- dispose(registryItem: any): void {
263
+ dispose(registryItem: EnhancementConfig | string | symbol): void {
238
264
  const element = this.element;
239
265
 
266
+ // Resolve string/symbol to EnhancementConfig via enhKey lookup
267
+ let resolved: EnhancementConfig;
268
+ if (typeof registryItem === 'string' || typeof registryItem === 'symbol') {
269
+ const registry = (element as any).customElementRegistry?.enhancementRegistry;
270
+ if (!registry) {
271
+ return; // No registry, nothing to dispose
272
+ }
273
+ const found = registry.findByEnhKey(registryItem);
274
+ if (!found) {
275
+ throw new Error(`${String(registryItem)} not in registry`);
276
+ }
277
+ resolved = found;
278
+ } else {
279
+ resolved = registryItem;
280
+ }
281
+
240
282
  // Get the instance map
241
283
  const instanceMap = getInstanceMap();
242
284
  if (!instanceMap.has(element)) {
@@ -244,37 +286,54 @@ class ElementEnhancementContainer {
244
286
  }
245
287
 
246
288
  const instances = instanceMap.get(element)!;
247
- const spawnedInstance = instances.get(registryItem);
289
+ const spawnedInstance = instances.get(resolved);
248
290
 
249
291
  if (!spawnedInstance) {
250
292
  return; // No instance for this registry item
251
293
  }
252
294
 
253
295
  // Call dispose lifecycle method if it exists
254
- const lifecycleKeys = normalizeLifecycleKeys(registryItem?.lifecycleKeys);
296
+ const lifecycleKeys = normalizeLifecycleKeys(resolved.lifecycleKeys);
255
297
  const disposeKey = lifecycleKeys?.dispose;
256
298
  if (disposeKey && typeof spawnedInstance[disposeKey] === 'function') {
257
- spawnedInstance[disposeKey](registryItem);
299
+ spawnedInstance[disposeKey](resolved);
258
300
  }
259
301
 
260
302
  // Remove from instance map
261
- instances.delete(registryItem);
303
+ instances.delete(resolved);
262
304
 
263
305
  // Remove from enh container if it has an enhKey
264
- if (registryItem.enhKey) {
306
+ if (resolved.enhKey) {
265
307
  const self = this as any;
266
- delete self[registryItem.enhKey];
308
+ delete self[resolved.enhKey];
267
309
  }
268
310
  }
269
311
 
270
312
  /**
271
313
  * Wait for an enhancement instance to be resolved
272
- * @param registryItem - The registry item to wait for
314
+ * @param registryItem - The registry item (EnhancementConfig object, or string/symbol enhKey) to wait for
273
315
  * @param mountCtx - Optional context to pass to the spawned instance
274
316
  * @returns Promise that resolves with the spawned instance
275
317
  */
276
- async whenResolved(registryItem: any, mountCtx?: any): Promise<any> {
277
- const lifecycleKeys = normalizeLifecycleKeys(registryItem?.lifecycleKeys);
318
+ async whenResolved(registryItem: EnhancementConfig | string | symbol, mountCtx?: any): Promise<any> {
319
+ // Resolve string/symbol to EnhancementConfig via enhKey lookup
320
+ let resolved: EnhancementConfig;
321
+ if (typeof registryItem === 'string' || typeof registryItem === 'symbol') {
322
+ const element = this.element;
323
+ const registry = (element as any).customElementRegistry?.enhancementRegistry;
324
+ if (!registry) {
325
+ throw new Error('customElementRegistry.enhancementRegistry not available');
326
+ }
327
+ const found = registry.findByEnhKey(registryItem);
328
+ if (!found) {
329
+ throw new Error(`${String(registryItem)} not in registry`);
330
+ }
331
+ resolved = found;
332
+ } else {
333
+ resolved = registryItem;
334
+ }
335
+
336
+ const lifecycleKeys = normalizeLifecycleKeys(resolved.lifecycleKeys);
278
337
  const resolvedKey = lifecycleKeys?.resolved;
279
338
 
280
339
  if (resolvedKey === undefined) {
@@ -282,7 +341,7 @@ class ElementEnhancementContainer {
282
341
  }
283
342
 
284
343
  // Get or spawn the instance (pass mountCtx through)
285
- const spawnedInstance = this.get(registryItem, mountCtx);
344
+ const spawnedInstance = this.get(resolved, mountCtx);
286
345
 
287
346
  // Check if already resolved
288
347
  if ((spawnedInstance as any)[resolvedKey]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.31",
3
+ "version": "0.0.32",
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": {
@@ -55,6 +55,14 @@
55
55
  "./getHost.js": {
56
56
  "default": "./getHost.js",
57
57
  "types": "./getHost.ts"
58
+ },
59
+ "./resolveValues.js": {
60
+ "default": "./resolveValues.js",
61
+ "types": "./resolveValues.ts"
62
+ },
63
+ "./assignFrom.js": {
64
+ "default": "./assignFrom.js",
65
+ "types": "./assignFrom.ts"
58
66
  }
59
67
  },
60
68
  "main": "index.js",
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Resolve RHS path strings in a pattern object against a source object.
3
+ *
4
+ * Any value that is a string starting with `?.` is treated as a path
5
+ * and resolved against the source object using optional chaining semantics.
6
+ * Non-string values and strings not starting with `?.` pass through unchanged.
7
+ *
8
+ * @param pattern - Object whose RHS values may contain `?.` path strings
9
+ * @param source - Object to resolve paths against
10
+ * @returns New object with path strings replaced by resolved values
11
+ *
12
+ * @example
13
+ * const pattern = {
14
+ * hello: '?.myPropContainer?.stringProp',
15
+ * foo: '?.myFooString',
16
+ * literal: 42
17
+ * };
18
+ * const source = {
19
+ * myPropContainer: { stringProp: 'Venus' },
20
+ * myFooString: 'bar'
21
+ * };
22
+ * const result = resolveValues(pattern, source);
23
+ * // { hello: 'Venus', foo: 'bar', literal: 42 }
24
+ */
25
+ export function resolveValues(pattern, source) {
26
+ const result = {};
27
+ for (const [key, value] of Object.entries(pattern)) {
28
+ if (typeof value === 'string' && value.startsWith('?.')) {
29
+ // Parse path: split by '.', strip '?', filter empties
30
+ const parts = value.split('.').map(p => p.replace(/\?/g, '')).filter(p => p.length > 0);
31
+ let current = source;
32
+ for (const part of parts) {
33
+ if (current == null)
34
+ break;
35
+ current = current[part];
36
+ }
37
+ result[key] = current;
38
+ }
39
+ else {
40
+ result[key] = value;
41
+ }
42
+ }
43
+ return result;
44
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Resolve RHS path strings in a pattern object against a source object.
3
+ *
4
+ * Any value that is a string starting with `?.` is treated as a path
5
+ * and resolved against the source object using optional chaining semantics.
6
+ * Non-string values and strings not starting with `?.` pass through unchanged.
7
+ *
8
+ * @param pattern - Object whose RHS values may contain `?.` path strings
9
+ * @param source - Object to resolve paths against
10
+ * @returns New object with path strings replaced by resolved values
11
+ *
12
+ * @example
13
+ * const pattern = {
14
+ * hello: '?.myPropContainer?.stringProp',
15
+ * foo: '?.myFooString',
16
+ * literal: 42
17
+ * };
18
+ * const source = {
19
+ * myPropContainer: { stringProp: 'Venus' },
20
+ * myFooString: 'bar'
21
+ * };
22
+ * const result = resolveValues(pattern, source);
23
+ * // { hello: 'Venus', foo: 'bar', literal: 42 }
24
+ */
25
+ export function resolveValues(
26
+ pattern: Record<string, any>,
27
+ source: any
28
+ ): Record<string, any> {
29
+ const result: Record<string, any> = {};
30
+ for (const [key, value] of Object.entries(pattern)) {
31
+ if (typeof value === 'string' && value.startsWith('?.')) {
32
+ // Parse path: split by '.', strip '?', filter empties
33
+ const parts = value.split('.').map(p => p.replace(/\?/g, '')).filter(p => p.length > 0);
34
+ let current = source;
35
+ for (const part of parts) {
36
+ if (current == null) break;
37
+ current = current[part];
38
+ }
39
+ result[key] = current;
40
+ } else {
41
+ result[key] = value;
42
+ }
43
+ }
44
+ return result;
45
+ }