assign-gingerly 0.0.81 → 0.0.83

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.
@@ -35,6 +35,7 @@ export async function assignFromAsync(target, pattern, options, permissionProces
35
35
  withMethods: options.withMethods,
36
36
  aka: options.aka,
37
37
  akaMethods: options.akaMethods,
38
+ substitutions: options.substitutions,
38
39
  protocols: options.protocols,
39
40
  root: target,
40
41
  permissionProcessor
@@ -47,7 +48,7 @@ export async function assignFromAsync(target, pattern, options, permissionProces
47
48
  if (idRefNormalKeys.length > 0 && (options.pin || options.at)) {
48
49
  const ids = { ...options.pin, ...options.at };
49
50
  const { resolveIdVariable, parseIdRef } = await import('./resolve/resolveIdRef.js');
50
- const { withMethods, aka, akaMethods, protocols, from } = options;
51
+ const { withMethods, aka, akaMethods, protocols, from, substitutions } = options;
51
52
  for (const key of idRefNormalKeys) {
52
53
  const parsed = parseIdRef(key);
53
54
  if (!parsed)
@@ -58,13 +59,13 @@ export async function assignFromAsync(target, pattern, options, permissionProces
58
59
  const value = expandedPattern[key];
59
60
  if (parsed.remainingPath) {
60
61
  // Resolve the RHS value
61
- const resolvedValue = await resolveValues({ __v: value }, from, { withMethods, aka, akaMethods, protocols, root: el, permissionProcessor });
62
+ const resolvedValue = await resolveValues({ __v: value }, from, { withMethods, aka, akaMethods, substitutions, protocols, root: el, permissionProcessor });
62
63
  // Apply remaining path on the resolved element
63
64
  assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options, permissionProcessor);
64
65
  }
65
66
  else {
66
67
  // No remaining path — resolve and assign directly to the element
67
- const resolvedValue = await resolveValues(typeof value === 'object' && value !== null ? value : { __v: value }, from, { withMethods, aka, akaMethods, protocols, root: el, permissionProcessor });
68
+ const resolvedValue = await resolveValues(typeof value === 'object' && value !== null ? value : { __v: value }, from, { withMethods, aka, akaMethods, substitutions, protocols, root: el, permissionProcessor });
68
69
  if ('__v' in resolvedValue) {
69
70
  // Single value — can't assign to element root without a path
70
71
  }
@@ -69,6 +69,7 @@ export async function assignFromAsync(
69
69
  withMethods: options.withMethods,
70
70
  aka: options.aka,
71
71
  akaMethods: options.akaMethods,
72
+ substitutions: options.substitutions,
72
73
  protocols: options.protocols,
73
74
  root: target,
74
75
  permissionProcessor
@@ -84,7 +85,7 @@ export async function assignFromAsync(
84
85
  if (idRefNormalKeys.length > 0 && (options.pin || options.at)) {
85
86
  const ids = { ...options.pin, ...options.at };
86
87
  const { resolveIdVariable, parseIdRef } = await import('./resolve/resolveIdRef.js');
87
- const { withMethods, aka, akaMethods, protocols, from } = options;
88
+ const { withMethods, aka, akaMethods, protocols, from, substitutions } = options;
88
89
  for (const key of idRefNormalKeys) {
89
90
  const parsed = parseIdRef(key);
90
91
  if (!parsed) continue;
@@ -97,7 +98,7 @@ export async function assignFromAsync(
97
98
  // Resolve the RHS value
98
99
  const resolvedValue = await resolveValues(
99
100
  { __v: value }, from,
100
- { withMethods, aka, akaMethods, protocols, root: el, permissionProcessor }
101
+ { withMethods, aka, akaMethods, substitutions, protocols, root: el, permissionProcessor }
101
102
  );
102
103
  // Apply remaining path on the resolved element
103
104
  assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options, permissionProcessor);
@@ -106,7 +107,7 @@ export async function assignFromAsync(
106
107
  const resolvedValue = await resolveValues(
107
108
  typeof value === 'object' && value !== null ? value : { __v: value },
108
109
  from,
109
- { withMethods, aka, akaMethods, protocols, root: el, permissionProcessor }
110
+ { withMethods, aka, akaMethods, substitutions, protocols, root: el, permissionProcessor }
110
111
  );
111
112
  if ('__v' in resolvedValue) {
112
113
  // Single value — can't assign to element root without a path
@@ -60,7 +60,7 @@ Create `types/[project-name]/types.d.ts` with the element's property interface:
60
60
  /**
61
61
  * Properties specific to this custom element
62
62
  */
63
- export interface ElementProps {
63
+ export interface EndUserProps {
64
64
  // Properties unique to this element
65
65
  myProp: string;
66
66
  disabled: boolean;
@@ -69,18 +69,21 @@ export interface ElementProps {
69
69
  /**
70
70
  * Full property set including internal state
71
71
  */
72
- export interface AllProps extends ElementProps {
72
+ export interface AllProps extends EndUserProps {
73
73
  idx: number;
74
74
  item: any;
75
75
  }
76
76
 
77
- export type T = AllProps;
77
+ export type AP = AllProps;
78
+
79
+ export interface RunTimeProps extends AllProps, HTMLElement
80
+
78
81
  ```
79
82
 
80
83
  **Key points:**
81
- - `ElementProps` — the public API specific to this element
84
+ - `EndUserProps` — the public API specific to this element
82
85
  - `AllProps` — includes internal/computed state managed by roundabout
83
- - Export `T` as a convenience alias for use in `defRef.mjs` type annotations
86
+ - Export `AP` as a convenience alias
84
87
 
85
88
 
86
89
  ## Step 4: Create imports.html
@@ -238,17 +238,17 @@ import { fileURLToPath } from 'url';
238
238
  import {akaMethods as m, aka, builtInEmoji} from 'assign-gingerly/DX/emojis.js';
239
239
  import {paths, doAssign, set, smoothOver} from 'assign-gingerly/DX/paths.js';
240
240
 
241
- /** @import {EndUserProps, AP} from './types'; */
241
+ /** @import {EndUserProps, AP, RuntimeProps} from './types'; */
242
242
  /** @import {RoundaboutOptions} from './types/roundabout/types' */
243
243
  /** @import {ElMakerConfig} from './types/el-maker/types' */
244
244
 
245
245
  const withMethods = [m['🔍']];
246
246
 
247
- const $ = (/** @type {typeof paths<AP>} */ (/** @type {any} */(paths)))({withMethods});
247
+ const $ = (/** @type {typeof paths<RuntimeProps>} */ (/** @type {any} */(paths)))({withMethods});
248
248
 
249
249
 
250
250
  /**
251
- * @type {RoundaboutOptions<EndUserProps>}
251
+ * @type {RoundaboutOptions<AP>}
252
252
  */
253
253
  const raConfig = {
254
254
  weakRef: {
@@ -262,30 +262,30 @@ const raConfig = {
262
262
  },
263
263
  compacts: {
264
264
  on_click_of_expandButton_assign: {
265
- [$.expandButton.hidden.path]: true,
266
- [$.collapseButton.hidden.path]: false,
265
+ [$.expandButton.hidden.Path]: true,
266
+ [$.collapseButton.hidden.Path]: false,
267
267
  expanded: true,
268
268
 
269
269
  },
270
270
  on_click_of_collapseButton_assign: {
271
271
  expanded: false,
272
- [$.expandButton.hidden.path]: false,
273
- [$.collapseButton.hidden.path]: true,
272
+ [$.expandButton.hidden.Path]: false,
273
+ [$.collapseButton.hidden.Path]: true,
274
274
  }
275
275
  },
276
276
  merges: smoothOver([
277
277
  {
278
278
  ifKeyIn: ['clone'],
279
- assign: {
280
- expandButton: $.clone.querySelector('[name=expand]'),
281
- collapseButton: $.clone.querySelector('[name=collapse]'),
282
- }
279
+ ...doAssign(
280
+ set($.expandButton).to($.clone.querySelector('[name=expand]')),
281
+ set($.collapseButton).to($.clone.querySelector('[name=collapse]')),
282
+ )
283
283
  },
284
284
  {
285
285
  ifKeyIn: ['expanded'],
286
286
  ...doAssign(
287
287
  set($.ariaExpanded).to($.expanded),
288
- set(`${$.ariaControlsElements.path}?.@each?.hidden =!`).to($.expanded)
288
+ set($.ariaControlsElements.Each.hidden.QMEq).to([$.expanded, false, 'until-found'])
289
289
  )
290
290
  },
291
291
  {
@@ -303,13 +303,25 @@ const raConfig = {
303
303
  }
304
304
  }
305
305
 
306
- /** @type {ElMakerConfig<EndUserProps>} */
306
+ // withAttrs configuration for parsing element attributes
307
+ const withAttrs = {
308
+ base: 'user-counter',
309
+ count: '${base}-count',
310
+ _count: {
311
+ instanceOf: 'Number',
312
+ valIfNull: 0,
313
+ },
314
+ username: '${base}-username',
315
+ };
316
+
317
+ /** @type {ElMakerConfig<AP>} */
307
318
  const features = {
308
319
  assignFeatures: {
309
320
  roundabout: {
310
321
  customData: {
311
322
  raConfig,
312
- }
323
+ },
324
+ withAttrs
313
325
  },
314
326
  templateMaker: {}
315
327
  }
@@ -326,6 +338,55 @@ writeFileSync(outputFile, render(), 'utf8');
326
338
 
327
339
  </details>
328
340
 
341
+ Some non obvious scenarios:
342
+
343
+ ### How can I perform an action when the escape key is pressed somewhere on the document?
344
+
345
+ Answer:
346
+
347
+ From the side-burger example.
348
+
349
+ ```JS
350
+ const raConfig = {
351
+ ...
352
+ compacts: {
353
+ on_keydown_of_ownerDocument_assignFromEvent: {
354
+ [$.escapeKeyPressed.QMEq.Path]: [['?.key', 'Escape'], true, false]
355
+ }
356
+ },
357
+ merges: smoothOver([
358
+ {
359
+ ifKeyIn: ['expanded'],
360
+ ...doAssign(
361
+ ...
362
+ set($.escapeKeyPressed).to(false)
363
+ )
364
+ },
365
+ ...
366
+ {
367
+ ifAllOf: ['escapeKeyPressed'],
368
+ assign: {
369
+ expanded: false
370
+ }
371
+ }
372
+ ]),
373
+ };
374
+ ```
375
+
376
+ ## How can I set focus after a delay?
377
+
378
+ Work is underway to improve the DX a bit, but for now:
379
+
380
+ ```JS
381
+ {
382
+ delay:10, //milliseconds
383
+ ifAllOf: ['expanded'],
384
+ assign: {
385
+ set($.querySelector('a').focus()).to({}),
386
+ }
387
+ },
388
+ ```
389
+
329
390
  ## Step 8
330
391
 
331
392
  Run `node el-maker.mjs` (or `npm run build-el-maker` if your `package.json` includes a watch script) to regenerate `el-maker.json`.
@@ -267,6 +267,17 @@ export interface IAssignGingerlyOptions {
267
267
  */
268
268
  aka?: Record<string, string>;
269
269
 
270
+ /**
271
+ * Value substitutions for path segments.
272
+ * Each key names a placeholder; the value is a `?.`-delimited path resolved
273
+ * against the source (`from`) object. The resolved string value replaces any
274
+ * matching whole path segment in RHS path strings before path evaluation.
275
+ *
276
+ * Substitution values must be strings and must not contain the `?.` sequence,
277
+ * otherwise an error is thrown.
278
+ */
279
+ substitutions?: Record<string, string>;
280
+
270
281
  /**
271
282
  * Shorthand for binding method aliases from the source object.
272
283
  * Each entry maps an alias to a method name and is normalized into
@@ -357,6 +368,16 @@ export interface AssignFromOptions {
357
368
  /** Alias mappings for path segments */
358
369
  aka?: Record<string, string>;
359
370
 
371
+ /**
372
+ * Value substitutions for path segments.
373
+ * Each key names a placeholder; the value is a `?.`-delimited path resolved
374
+ * against `from`. The resolved string value replaces any matching whole path
375
+ * segment in RHS path strings before path evaluation.
376
+ *
377
+ * Substitution values must be strings and must not contain `?.`.
378
+ */
379
+ substitutions?: Record<string, string>;
380
+
360
381
  /** AbortSignal for cleanup */
361
382
  signal?: AbortSignal;
362
383
 
@@ -2,6 +2,7 @@ import {RAConfig} from '../roundabout/types.js';
2
2
  import {FontFaceFeatureConfig} from '../font-face-feature/types.js';
3
3
  import {CustomData as TSCD} from '../truth-sourcer/types.js';
4
4
  import {CustomData as FUCD} from '../face-up/types.js';
5
+ import {AttrPatterns} from '../assign-gingerly/types.js';
5
6
 
6
7
  export interface ElMakerConfig<AllProps = any, TActions = AllProps> {
7
8
  assignFeatures: {
@@ -10,6 +11,7 @@ export interface ElMakerConfig<AllProps = any, TActions = AllProps> {
10
11
  customData: {
11
12
  raConfig: RAConfig<AllProps, TActions, TActions>
12
13
  }
14
+ withAttrs: AttrPatterns<AllProps>
13
15
  },
14
16
  fontMgr?: {
15
17
  spawn?: string,
@@ -54,6 +54,7 @@ export type Compacts<TProps = any, TActions = TProps, TEvents extends string = s
54
54
  | Partial<{[key in `on_${TEvents}_of_${keyof TProps & string}_inc_${keyof TProps & string}_by`]: number}>
55
55
  | Partial<{[key in `on_${TEvents}_of_${keyof TProps & string}_set_${keyof TProps & string}_to`]: any}>
56
56
  | Partial<{[key in `on_${TEvents}_of_${keyof TProps & string}_assign`]: Record<string, any>}>
57
+ | Partial<{[key in `on_${TEvents}_of_${keyof TProps & string}_assignFromEvent`]: Record<string, any>}>
57
58
  ;
58
59
 
59
60
  export type Hitches<TProps = any, TActions = TProps> =
@@ -160,7 +161,14 @@ export interface RAConfig<
160
161
  initialPropVals?: Partial<{[key in keyof TProps & string]: unknown}>,
161
162
  }
162
163
 
163
- export interface RoundaboutOptions<TProps = unknown, TActions = TProps, ETProps = TProps> extends RAConfig<TProps, TActions, ETProps> {
164
+ // export interface RoundaboutFeatureConfig<
165
+ // TProps = unknown, TActions = TProps, ETProps = TProps,
166
+ // TCustomData = unknown, TEvents extends string = string>{
167
+ // RAConfig: RAConfig<TProps, TActions, ETProps, TCustomData, TEvents>,
168
+
169
+ // }
170
+
171
+ export interface RoundaboutOptions<TProps = unknown, TActions = TProps, ETProps = TProps, EventTypes extends string = string> extends RAConfig<TProps, TActions, ETProps, unknown, EventTypes> {
164
172
  vm?: TProps & TActions & RoundaboutReady,
165
173
  //for enhanced elements, pass in the container, referenced via $0.
166
174
  container?: EventTarget,
@@ -0,0 +1,43 @@
1
+ import { FeatureSpawnContext } from "../assign-gingerly/types";
2
+
3
+ /**
4
+ * Public properties of the SwipeDismissFeature.
5
+ * These can be set directly on the feature instance or initialized via attributes.
6
+ */
7
+ export interface SwipeDismissProps {
8
+ /** Axis along which the dismiss gesture is measured. */
9
+ axis: 'x' | 'y';
10
+ /**
11
+ * Direction that counts toward dismissal.
12
+ * 1 = right/down, -1 = left/up.
13
+ * Set to 'both' to allow swiping in either direction (e.g. toasts/snackbars).
14
+ */
15
+ direction: 1 | -1 | 'both';
16
+ /** Fraction of the panel size that triggers commit. */
17
+ distanceThreshold: number;
18
+ /** Velocity threshold in px/ms; a fast flick commits even under distanceThreshold. */
19
+ velocityThreshold: number;
20
+ /** CSS selector for the drag handle. Defaults to the host element. */
21
+ handleSelector: string | null;
22
+ /** CSS selector for the panel that visually follows the drag. Defaults to the handle. */
23
+ panelSelector: string | null;
24
+ /** Called on every pointermove with the current delta and fraction of the threshold. */
25
+ onProgress: ((deltaPx: number, fraction: number) => void) | null;
26
+ /** Called when the gesture crosses the commit threshold. */
27
+ onCommit: (() => void) | null;
28
+ /** Called when the gesture is released before the commit threshold. */
29
+ onCancel: (() => void) | null;
30
+ }
31
+
32
+ /**
33
+ * Internal state of the feature.
34
+ */
35
+ export interface AllProps extends SwipeDismissProps {
36
+ /** WeakRef to the host custom element. */
37
+ hostRef: WeakRef<Element>;
38
+ }
39
+
40
+ export type AP = AllProps;
41
+ export type PAP = Partial<AllProps>;
42
+
43
+ export { FeatureSpawnContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.81",
3
+ "version": "0.0.83",
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": {
@@ -171,6 +171,7 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
171
171
  resolvedParams = getValues(config.get, options.from, {
172
172
  withMethods: options.withMethods,
173
173
  aka: options.aka,
174
+ substitutions: options.substitutions,
174
175
  protocols: options.protocols,
175
176
  root: target
176
177
  });
@@ -180,6 +181,7 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
180
181
  const asyncResolved = await resolveValues(config.resolve, options.from, {
181
182
  withMethods: options.withMethods,
182
183
  aka: options.aka,
184
+ substitutions: options.substitutions,
183
185
  protocols: options.protocols,
184
186
  root: target
185
187
  });
@@ -192,6 +192,7 @@ export async function processHandlerCommands(
192
192
  resolvedParams = getValues(config.get, options.from, {
193
193
  withMethods: options.withMethods,
194
194
  aka: options.aka,
195
+ substitutions: options.substitutions,
195
196
  protocols: options.protocols,
196
197
  root: target
197
198
  });
@@ -201,6 +202,7 @@ export async function processHandlerCommands(
201
202
  const asyncResolved = await resolveValues(config.resolve, options.from, {
202
203
  withMethods: options.withMethods,
203
204
  aka: options.aka,
205
+ substitutions: options.substitutions,
204
206
  protocols: options.protocols,
205
207
  root: target
206
208
  });
@@ -67,6 +67,45 @@ function applyAliases(path, aliasMap) {
67
67
  const substituted = parts.map(part => aliasMap.get(part) ?? part);
68
68
  return substituted.join('?.');
69
69
  }
70
+ /**
71
+ * Apply value substitutions to a path string.
72
+ * Replaces complete tokens between `?.` delimiters with their resolved values.
73
+ * Substitutions are applied before aliases.
74
+ */
75
+ function applySubstitutions(path, substitutionMap) {
76
+ if (!substitutionMap || substitutionMap.size === 0)
77
+ return path;
78
+ const parts = path.split('?.');
79
+ const substituted = parts.map(part => substitutionMap.get(part) ?? part);
80
+ return substituted.join('?.');
81
+ }
82
+ /**
83
+ * Resolve substitution values declared in options.substitutions.
84
+ * Each substitution path is resolved against the source object without
85
+ * applying substitutions itself, to avoid infinite recursion.
86
+ * Resolved values must be strings and must not contain `?.`.
87
+ */
88
+ function resolveSubstitutions(substitutions, source, options) {
89
+ const map = new Map();
90
+ if (!substitutions)
91
+ return map;
92
+ for (const [name, path] of Object.entries(substitutions)) {
93
+ // Resolve the substitution path against the source, but do not apply
94
+ // substitutions to that path. Root references ($0) are also disabled
95
+ // for substitution paths so values are sourced from `from` only.
96
+ const resolved = getValue(path, source, options
97
+ ? { ...options, substitutions: undefined, root: undefined }
98
+ : undefined);
99
+ if (typeof resolved !== 'string') {
100
+ throw new Error(`Substitution '${name}' must resolve to a string, got ${typeof resolved}`);
101
+ }
102
+ if (resolved.includes('?.')) {
103
+ throw new Error(`Substitution '${name}' resolved to a string containing '?.', which would alter the path structure: '${resolved}'`);
104
+ }
105
+ map.set(name, resolved);
106
+ }
107
+ return map;
108
+ }
70
109
  /**
71
110
  * Resolve a special root-reference token at the start of a string.
72
111
  * '$0' refers to the first argument passed to assignFrom / resolveValues.
@@ -159,12 +198,13 @@ function getProtocolValue(value, protocols, options) {
159
198
  * Resolve path strings and protocols within an array (synchronous).
160
199
  * Recurses into nested arrays and plain objects.
161
200
  */
162
- function getArray(arr, source, aliasMap, withMethods, protocols, options) {
201
+ function getArray(arr, source, aliasMap, withMethods, protocols, options, substitutionMap) {
163
202
  const permissionProcessor = options?.permissionProcessor;
164
203
  const result = [];
165
204
  for (const item of arr) {
166
205
  if (typeof item === 'string' && item.startsWith('?.')) {
167
- const aliased = applyAliases(item, aliasMap);
206
+ const substituted = applySubstitutions(item, substitutionMap);
207
+ const aliased = applyAliases(substituted, aliasMap);
168
208
  const parts = parseCachedPath(aliased);
169
209
  result.push(parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor));
170
210
  }
@@ -174,7 +214,8 @@ function getArray(arr, source, aliasMap, withMethods, protocols, options) {
174
214
  result.push(source);
175
215
  }
176
216
  else {
177
- const aliased = applyAliases(rootRef.path, aliasMap);
217
+ const substitutedPath = applySubstitutions(rootRef.path, substitutionMap);
218
+ const aliased = applyAliases(substitutedPath, aliasMap);
178
219
  const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
179
220
  const parts = parseCachedPath(normalizedPath);
180
221
  result.push(parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor));
@@ -184,7 +225,7 @@ function getArray(arr, source, aliasMap, withMethods, protocols, options) {
184
225
  result.push(getProtocolValue(item, protocols, options));
185
226
  }
186
227
  else if (Array.isArray(item)) {
187
- result.push(getArray(item, source, aliasMap, withMethods, protocols, options));
228
+ result.push(getArray(item, source, aliasMap, withMethods, protocols, options, substitutionMap));
188
229
  }
189
230
  else if (item && typeof item === 'object') {
190
231
  const proto = Object.getPrototypeOf(item);
@@ -215,12 +256,14 @@ function getArray(arr, source, aliasMap, withMethods, protocols, options) {
215
256
  */
216
257
  export function getValues(pattern, source, options) {
217
258
  const { aliasMap, withMethods } = normalizeAliasOptions(options);
259
+ const substitutionMap = resolveSubstitutions(options?.substitutions, source, options);
218
260
  const protocols = options?.protocols;
219
261
  const permissionProcessor = options?.permissionProcessor;
220
262
  const result = {};
221
263
  for (const [key, value] of Object.entries(pattern)) {
222
264
  if (typeof value === 'string' && value.startsWith('?.')) {
223
- const aliased = applyAliases(value, aliasMap);
265
+ const substituted = applySubstitutions(value, substitutionMap);
266
+ const aliased = applyAliases(substituted, aliasMap);
224
267
  const parts = parseCachedPath(aliased);
225
268
  result[key] = parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor);
226
269
  }
@@ -230,7 +273,8 @@ export function getValues(pattern, source, options) {
230
273
  result[key] = source;
231
274
  }
232
275
  else {
233
- const aliased = applyAliases(rootRef.path, aliasMap);
276
+ const substitutedPath = applySubstitutions(rootRef.path, substitutionMap);
277
+ const aliased = applyAliases(substitutedPath, aliasMap);
234
278
  const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
235
279
  const parts = parseCachedPath(normalizedPath);
236
280
  result[key] = parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor);
@@ -240,7 +284,7 @@ export function getValues(pattern, source, options) {
240
284
  result[key] = getProtocolValue(value, protocols, options);
241
285
  }
242
286
  else if (Array.isArray(value)) {
243
- result[key] = getArray(value, source, aliasMap, withMethods, protocols, options);
287
+ result[key] = getArray(value, source, aliasMap, withMethods, protocols, options, substitutionMap);
244
288
  }
245
289
  else if (typeof value === 'object' && value !== null) {
246
290
  const proto = Object.getPrototypeOf(value);
@@ -274,10 +318,12 @@ export function getValue(path, source, options) {
274
318
  else if (!path.startsWith('?.')) {
275
319
  return path;
276
320
  }
277
- let aliased = path;
321
+ const substitutionMap = resolveSubstitutions(options?.substitutions, source, options);
322
+ const substituted = applySubstitutions(path, substitutionMap);
323
+ let aliased = substituted;
278
324
  const { aliasMap } = normalizeAliasOptions(options);
279
325
  if (aliasMap.size > 0) {
280
- aliased = applyAliases(path, aliasMap);
326
+ aliased = applyAliases(substituted, aliasMap);
281
327
  }
282
328
  const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
283
329
  const parts = parseCachedPath(normalizedPath);