assign-gingerly 0.0.69 → 0.0.70

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.
@@ -21,8 +21,9 @@
21
21
  * // Now el.command delegates to el.behaviors.commandBehavior.command
22
22
  */
23
23
 
24
- import { resolveValue, ResolveValuesOptions } from '../resolveValues.js';
25
- import assignGingerly, { IAssignGingerlyOptions } from '../assignGingerly.js';
24
+ import { resolveValue} from '../resolveValues.js';
25
+ import {ResolveValuesOptions, IAssignGingerlyOptions} from '../types/assign-gingerly/types.js';
26
+ import assignGingerly, { } from '../assignGingerly.js';
26
27
 
27
28
  export interface InstallForwardingOptions extends ResolveValuesOptions, IAssignGingerlyOptions {}
28
29
 
@@ -14,7 +14,7 @@ function computeChildPath(root: Element, target: Element): number[] | null {
14
14
  let current: Element | null = target;
15
15
 
16
16
  while (current && current !== root) {
17
- const parent = current.parentElement;
17
+ const parent = current.parentElement as HTMLElement | null;
18
18
  if (!parent) return null;
19
19
  const idx = Array.prototype.indexOf.call(parent.children, current);
20
20
  if (idx === -1) return null;
package/assignFrom.js CHANGED
@@ -68,6 +68,29 @@ function resolveTernaryValue(value, source, options) {
68
68
  }
69
69
  return value;
70
70
  }
71
+ /**
72
+ * Check if a value in a result position is a nested ternary expression.
73
+ * Trigger: an array of length ≥ 2 whose first element is a `?.`-prefixed
74
+ * string (truthiness mode) or an array (comparison mode). Other arrays
75
+ * are treated as literal values.
76
+ */
77
+ function isNestedTernary(value) {
78
+ if (!Array.isArray(value) || value.length < 2)
79
+ return false;
80
+ const first = value[0];
81
+ return Array.isArray(first) || (typeof first === 'string' && first.startsWith('?.'));
82
+ }
83
+ /**
84
+ * Resolve a value in a result position (then/else/neither, comparison results,
85
+ * chain candidates). Nested ternary expressions are evaluated recursively;
86
+ * everything else goes through resolveTernaryValue.
87
+ */
88
+ function resolveResult(value, source, options) {
89
+ if (isNestedTernary(value)) {
90
+ return evaluateTernary(value, source, options);
91
+ }
92
+ return resolveTernaryValue(value, source, options);
93
+ }
71
94
  /**
72
95
  * Evaluate a ?= ternary expression.
73
96
  *
@@ -77,8 +100,12 @@ function resolveTernaryValue(value, source, options) {
77
100
  * - [ifTrue, trueResult, falseResult, neither] — three-state (true/false/nullish)
78
101
  * - [[lhs, rhs], ifEqual, ifNotEqual?] — equality comparison
79
102
  * - [[lhs, rhs], ifEqual] — equality guard
103
+ * - [c1, '||', c2, '||', c3, ...] — first truthy candidate (c1 || c2 || c3)
104
+ * - [c1, '??', c2, '??', c3, ...] — first non-nullish candidate (c1 ?? c2 ?? c3)
105
+ * - [c1, t1, [c2, t2, e2]] — nested ternary in any result position
80
106
  *
81
- * Returns undefined to signal "skip assignment" (guard forms when condition not met).
107
+ * Returns TERNARY_SKIP to signal "skip assignment" (guard forms when condition
108
+ * not met). A skip from a nested guard propagates outward.
82
109
  */
83
110
  const TERNARY_SKIP = Symbol('ternary-skip');
84
111
  function evaluateTernary(arr, source, options) {
@@ -90,10 +117,10 @@ function evaluateTernary(arr, source, options) {
90
117
  // Equality: [[lhs, rhs], result, elseResult?]
91
118
  const rhs = resolveTernaryValue(condition[1], source, options);
92
119
  if (lhs === rhs) {
93
- return resolveTernaryValue(arr[1], source, options);
120
+ return resolveResult(arr[1], source, options);
94
121
  }
95
122
  else {
96
- return arr.length > 2 ? resolveTernaryValue(arr[2], source, options) : TERNARY_SKIP;
123
+ return arr.length > 2 ? resolveResult(arr[2], source, options) : TERNARY_SKIP;
97
124
  }
98
125
  }
99
126
  else {
@@ -102,32 +129,79 @@ function evaluateTernary(arr, source, options) {
102
129
  const rhs = resolveTernaryValue(condition[2], source, options);
103
130
  const satisfied = compareWithOp(lhs, op, rhs);
104
131
  if (satisfied) {
105
- return resolveTernaryValue(arr[1], source, options);
132
+ return resolveResult(arr[1], source, options);
106
133
  }
107
134
  else {
108
- return arr.length > 2 ? resolveTernaryValue(arr[2], source, options) : TERNARY_SKIP;
135
+ return arr.length > 2 ? resolveResult(arr[2], source, options) : TERNARY_SKIP;
109
136
  }
110
137
  }
111
138
  }
112
139
  else {
140
+ // Chain shortcut: [c1, '||', c2, ...] or [c1, '??', c2, ...]
141
+ // Checked before the length-based dispatch so a length-4 chain
142
+ // isn't misread as the three-state form.
143
+ if (arr[1] === '||' || arr[1] === '??') {
144
+ return evaluateChain(arr, source, options);
145
+ }
113
146
  // Truthiness mode
114
147
  const resolved = resolveTernaryValue(condition, source, options);
115
148
  if (arr.length === 4) {
116
149
  // [ifTrue, trueResult, falseResult, neitherResult]
117
150
  if (resolved == null)
118
- return resolveTernaryValue(arr[3], source, options);
119
- return resolved ? resolveTernaryValue(arr[1], source, options) : resolveTernaryValue(arr[2], source, options);
151
+ return resolveResult(arr[3], source, options);
152
+ return resolved ? resolveResult(arr[1], source, options) : resolveResult(arr[2], source, options);
120
153
  }
121
154
  else if (arr.length === 3) {
122
155
  // [ifTruthy, thenResult, elseResult]
123
- return resolved ? resolveTernaryValue(arr[1], source, options) : resolveTernaryValue(arr[2], source, options);
156
+ return resolved ? resolveResult(arr[1], source, options) : resolveResult(arr[2], source, options);
124
157
  }
125
158
  else {
126
159
  // [ifTruthy, thenResult] — guard, skip if falsy
127
- return resolved ? resolveTernaryValue(arr[1], source, options) : TERNARY_SKIP;
160
+ return resolved ? resolveResult(arr[1], source, options) : TERNARY_SKIP;
128
161
  }
129
162
  }
130
163
  }
164
+ /**
165
+ * Evaluate a chain shortcut: [c1, '||', c2, ...] or [c1, '??', c2, ...].
166
+ *
167
+ * - '||' returns the first truthy candidate (JS `c1 || c2 || c3`).
168
+ * - '??' returns the first non-nullish candidate (JS `c1 ?? c2 ?? c3`).
169
+ *
170
+ * Candidates sit at even indices and are resolved lazily — evaluation stops
171
+ * at the first match. What happens when no candidate passes depends on the
172
+ * trailing element:
173
+ * - Ends with a candidate ([c1, '||', c2]): the last candidate doubles as the
174
+ * fallback (returned even when it fails the test, matching JS semantics).
175
+ * - Ends with a non-marker element after the last candidate ([c1, '||', c2, fb]):
176
+ * that element is the explicit fallback.
177
+ * - Ends with a dangling marker ([c1, '||'] or [c1, '||', c2, '||']): guard form —
178
+ * nothing to assign (TERNARY_SKIP).
179
+ *
180
+ * Mixing marker types in one chain is not supported; the first marker sets the mode.
181
+ *
182
+ * Candidates may themselves be nested ternaries (see resolveResult). A nested
183
+ * guard that skips counts as a failed candidate — the chain continues; if it
184
+ * was the final fallback, the skip propagates.
185
+ */
186
+ function evaluateChain(arr, source, options) {
187
+ const isNullish = arr[1] === '??';
188
+ const n = arr.length;
189
+ const endsWithMarker = arr[n - 1] === '||' || arr[n - 1] === '??';
190
+ const lastCandidateIdx = n - (endsWithMarker || n % 2 === 0 ? 2 : 1);
191
+ let lastVal;
192
+ for (let i = 0; i <= lastCandidateIdx; i += 2) {
193
+ lastVal = resolveResult(arr[i], source, options);
194
+ if (lastVal === TERNARY_SKIP)
195
+ continue; // nested guard skipped — try next candidate
196
+ const pass = isNullish ? lastVal != null : !!lastVal;
197
+ if (pass)
198
+ return lastVal;
199
+ }
200
+ if (endsWithMarker)
201
+ return TERNARY_SKIP;
202
+ // Odd count: last candidate doubles as fallback. Even count: explicit trailing fallback.
203
+ return n % 2 === 1 ? lastVal : resolveResult(arr[n - 1], source, options);
204
+ }
131
205
  /**
132
206
  * Compare two values with a given operator.
133
207
  */
package/assignFrom.ts CHANGED
@@ -79,6 +79,30 @@ function resolveTernaryValue(value: any, source: any, options: AssignFromOptions
79
79
  return value;
80
80
  }
81
81
 
82
+ /**
83
+ * Check if a value in a result position is a nested ternary expression.
84
+ * Trigger: an array of length ≥ 2 whose first element is a `?.`-prefixed
85
+ * string (truthiness mode) or an array (comparison mode). Other arrays
86
+ * are treated as literal values.
87
+ */
88
+ function isNestedTernary(value: any): boolean {
89
+ if (!Array.isArray(value) || value.length < 2) return false;
90
+ const first = value[0];
91
+ return Array.isArray(first) || (typeof first === 'string' && first.startsWith('?.'));
92
+ }
93
+
94
+ /**
95
+ * Resolve a value in a result position (then/else/neither, comparison results,
96
+ * chain candidates). Nested ternary expressions are evaluated recursively;
97
+ * everything else goes through resolveTernaryValue.
98
+ */
99
+ function resolveResult(value: any, source: any, options: AssignFromOptions): any {
100
+ if (isNestedTernary(value)) {
101
+ return evaluateTernary(value, source, options);
102
+ }
103
+ return resolveTernaryValue(value, source, options);
104
+ }
105
+
82
106
  /**
83
107
  * Evaluate a ?= ternary expression.
84
108
  *
@@ -88,8 +112,12 @@ function resolveTernaryValue(value: any, source: any, options: AssignFromOptions
88
112
  * - [ifTrue, trueResult, falseResult, neither] — three-state (true/false/nullish)
89
113
  * - [[lhs, rhs], ifEqual, ifNotEqual?] — equality comparison
90
114
  * - [[lhs, rhs], ifEqual] — equality guard
115
+ * - [c1, '||', c2, '||', c3, ...] — first truthy candidate (c1 || c2 || c3)
116
+ * - [c1, '??', c2, '??', c3, ...] — first non-nullish candidate (c1 ?? c2 ?? c3)
117
+ * - [c1, t1, [c2, t2, e2]] — nested ternary in any result position
91
118
  *
92
- * Returns undefined to signal "skip assignment" (guard forms when condition not met).
119
+ * Returns TERNARY_SKIP to signal "skip assignment" (guard forms when condition
120
+ * not met). A skip from a nested guard propagates outward.
93
121
  */
94
122
  const TERNARY_SKIP = Symbol('ternary-skip');
95
123
 
@@ -103,9 +131,9 @@ function evaluateTernary(arr: any[], source: any, options: AssignFromOptions): a
103
131
  // Equality: [[lhs, rhs], result, elseResult?]
104
132
  const rhs = resolveTernaryValue(condition[1], source, options);
105
133
  if (lhs === rhs) {
106
- return resolveTernaryValue(arr[1], source, options);
134
+ return resolveResult(arr[1], source, options);
107
135
  } else {
108
- return arr.length > 2 ? resolveTernaryValue(arr[2], source, options) : TERNARY_SKIP;
136
+ return arr.length > 2 ? resolveResult(arr[2], source, options) : TERNARY_SKIP;
109
137
  }
110
138
  } else {
111
139
  // Operator: [[lhs, op, rhs], result, elseResult?]
@@ -113,28 +141,73 @@ function evaluateTernary(arr: any[], source: any, options: AssignFromOptions): a
113
141
  const rhs = resolveTernaryValue(condition[2], source, options);
114
142
  const satisfied = compareWithOp(lhs, op, rhs);
115
143
  if (satisfied) {
116
- return resolveTernaryValue(arr[1], source, options);
144
+ return resolveResult(arr[1], source, options);
117
145
  } else {
118
- return arr.length > 2 ? resolveTernaryValue(arr[2], source, options) : TERNARY_SKIP;
146
+ return arr.length > 2 ? resolveResult(arr[2], source, options) : TERNARY_SKIP;
119
147
  }
120
148
  }
121
149
  } else {
150
+ // Chain shortcut: [c1, '||', c2, ...] or [c1, '??', c2, ...]
151
+ // Checked before the length-based dispatch so a length-4 chain
152
+ // isn't misread as the three-state form.
153
+ if (arr[1] === '||' || arr[1] === '??') {
154
+ return evaluateChain(arr, source, options);
155
+ }
122
156
  // Truthiness mode
123
157
  const resolved = resolveTernaryValue(condition, source, options);
124
158
  if (arr.length === 4) {
125
159
  // [ifTrue, trueResult, falseResult, neitherResult]
126
- if (resolved == null) return resolveTernaryValue(arr[3], source, options);
127
- return resolved ? resolveTernaryValue(arr[1], source, options) : resolveTernaryValue(arr[2], source, options);
160
+ if (resolved == null) return resolveResult(arr[3], source, options);
161
+ return resolved ? resolveResult(arr[1], source, options) : resolveResult(arr[2], source, options);
128
162
  } else if (arr.length === 3) {
129
163
  // [ifTruthy, thenResult, elseResult]
130
- return resolved ? resolveTernaryValue(arr[1], source, options) : resolveTernaryValue(arr[2], source, options);
164
+ return resolved ? resolveResult(arr[1], source, options) : resolveResult(arr[2], source, options);
131
165
  } else {
132
166
  // [ifTruthy, thenResult] — guard, skip if falsy
133
- return resolved ? resolveTernaryValue(arr[1], source, options) : TERNARY_SKIP;
167
+ return resolved ? resolveResult(arr[1], source, options) : TERNARY_SKIP;
134
168
  }
135
169
  }
136
170
  }
137
171
 
172
+ /**
173
+ * Evaluate a chain shortcut: [c1, '||', c2, ...] or [c1, '??', c2, ...].
174
+ *
175
+ * - '||' returns the first truthy candidate (JS `c1 || c2 || c3`).
176
+ * - '??' returns the first non-nullish candidate (JS `c1 ?? c2 ?? c3`).
177
+ *
178
+ * Candidates sit at even indices and are resolved lazily — evaluation stops
179
+ * at the first match. What happens when no candidate passes depends on the
180
+ * trailing element:
181
+ * - Ends with a candidate ([c1, '||', c2]): the last candidate doubles as the
182
+ * fallback (returned even when it fails the test, matching JS semantics).
183
+ * - Ends with a non-marker element after the last candidate ([c1, '||', c2, fb]):
184
+ * that element is the explicit fallback.
185
+ * - Ends with a dangling marker ([c1, '||'] or [c1, '||', c2, '||']): guard form —
186
+ * nothing to assign (TERNARY_SKIP).
187
+ *
188
+ * Mixing marker types in one chain is not supported; the first marker sets the mode.
189
+ *
190
+ * Candidates may themselves be nested ternaries (see resolveResult). A nested
191
+ * guard that skips counts as a failed candidate — the chain continues; if it
192
+ * was the final fallback, the skip propagates.
193
+ */
194
+ function evaluateChain(arr: any[], source: any, options: AssignFromOptions): any {
195
+ const isNullish = arr[1] === '??';
196
+ const n = arr.length;
197
+ const endsWithMarker = arr[n - 1] === '||' || arr[n - 1] === '??';
198
+ const lastCandidateIdx = n - (endsWithMarker || n % 2 === 0 ? 2 : 1);
199
+ let lastVal: any;
200
+ for (let i = 0; i <= lastCandidateIdx; i += 2) {
201
+ lastVal = resolveResult(arr[i], source, options);
202
+ if (lastVal === TERNARY_SKIP) continue; // nested guard skipped — try next candidate
203
+ const pass = isNullish ? lastVal != null : !!lastVal;
204
+ if (pass) return lastVal;
205
+ }
206
+ if (endsWithMarker) return TERNARY_SKIP;
207
+ // Odd count: last candidate doubles as fallback. Even count: explicit trailing fallback.
208
+ return n % 2 === 1 ? lastVal : resolveResult(arr[n - 1], source, options);
209
+ }
210
+
138
211
  /**
139
212
  * Compare two values with a given operator.
140
213
  */
@@ -20,7 +20,8 @@
20
20
  * // target is now { color: 'red', text: 'Hello' }
21
21
  */
22
22
  import { resolveValues } from './resolveValues.js';
23
- import assignGingerly, { IAssignGingerlyOptions } from './assignGingerly.js';
23
+ import {IAssignGingerlyOptions} from './types/assign-gingerly/types.js';
24
+ import assignGingerly from './assignGingerly.js';
24
25
  import type { AssignPermissions } from './isAllowedImportPath.js';
25
26
  import type { AssignFromHandler, AssignFromHandlerConstructor } from './types/assign-gingerly/types.js';
26
27
  import {
package/assignGingerly.js CHANGED
@@ -571,9 +571,9 @@ export function assignGingerly(target, source, options, permissions) {
571
571
  : undefined;
572
572
  const registry = options?.registry instanceof EnhancementRegistry
573
573
  ? options.registry
574
- : options?.registry
575
- ? new options.registry()
576
- : undefined;
574
+ // : options?.registry
575
+ // ? new options.registry()
576
+ : undefined;
577
577
  // Convert Symbol.for string keys to actual symbols and apply aliases
578
578
  const processedSource = {};
579
579
  for (const key of Object.keys(source)) {
@@ -653,6 +653,7 @@ export function assignGingerly(target, source, options, permissions) {
653
653
  lhsParent = ensureNestedPath(target, pathParts);
654
654
  lhsValue = lhsParent[lhsKey];
655
655
  }
656
+ //TODO: this logic seems to occur twice at least. Maybe make it a method?
656
657
  // Event handler: Element LHS + object RHS with 'on' property
657
658
  if (lhsValue instanceof Element && value && typeof value === 'object' && !Array.isArray(value) && 'on' in value) {
658
659
  const capturedLhs = lhsValue;
package/assignGingerly.ts CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
 
3
3
  import { EnhancementConfig } from "./types/assign-gingerly/types";
4
- import type { FeatureConfigsMap } from "./types/assign-gingerly/types";
4
+ import type { AssignFromOptions, FeatureConfigsMap, IAssignGingerlyOptions } from "./types/assign-gingerly/types";
5
5
  import type { AssignPermissions } from "./isAllowedImportPath.js";
6
6
  import { normalizeAliasOptions } from './getValues.js';
7
7
 
@@ -32,113 +32,6 @@ export interface ItemscopeManagerConfig<T = any> {
32
32
  };
33
33
  }
34
34
 
35
- /**
36
- * Interface for the options passed to assignGingerly
37
- */
38
- export interface IAssignGingerlyOptions {
39
- registry?: typeof EnhancementRegistry | EnhancementRegistry;
40
- bypassChecks?: boolean;
41
-
42
- /**
43
- * List of property names that should be treated as methods to call
44
- * rather than properties to assign.
45
- *
46
- * When a path segment matches a name in this array/set:
47
- * - If the property is a function, call it with appropriate arguments
48
- * - For the last segment: use RHS value as argument (spread if array)
49
- * - For middle segments: use next segment as string argument (if next is not also a method)
50
- * - If consecutive segments are both methods, first is called with no arguments
51
- * - If the property is not a function, silently skip
52
- *
53
- * Example:
54
- * assignGingerly(element, {
55
- * '?.classList?.add': 'myClass'
56
- * }, { withMethods: ['add'] });
57
- * // Calls: element.classList.add('myClass')
58
- *
59
- * Chained methods:
60
- * assignGingerly(elementRef, {
61
- * '?.deref?.querySelector?.myElement?.classList?.add': 'active'
62
- * }, { withMethods: ['deref', 'querySelector', 'add'] });
63
- * // Calls: elementRef.deref().querySelector('myElement').classList.add('active')
64
- */
65
- withMethods?: string[] | Set<string>;
66
-
67
- /**
68
- * Alias mappings for property and method names.
69
- * Allows shorter, customizable shortcuts in path expressions.
70
- *
71
- * Aliases are substituted before path evaluation, matching complete tokens
72
- * between `?.` delimiters (not substrings).
73
- *
74
- * Reserved characters (cannot be used in aliases): space, backtick (`)
75
- *
76
- * Example:
77
- * assignGingerly(element, {
78
- * '?.$?.my-element?.c?.+': 'highlighted'
79
- * }, {
80
- * withMethods: ['querySelector', 'add'],
81
- * aka: { '$': 'querySelector', 'c': 'classList', '+': 'add' }
82
- * });
83
- * // Equivalent to: element.querySelector('my-element').classList.add('highlighted')
84
- */
85
- aka?: Record<string, string>;
86
-
87
- /**
88
- * Shorthand for binding method aliases from the source object.
89
- * Each entry maps an alias to a method name, and is normalized into
90
- * the existing withMethods + aka behavior.
91
- */
92
- akaMethods?: Record<string, string>;
93
-
94
- /**
95
- * AbortSignal for cleaning up reactive subscriptions (@eachTime)
96
- * Required when using @eachTime symbol for reactive iteration
97
- * When the signal is aborted, all event listeners are automatically removed
98
- *
99
- * Example:
100
- * const controller = new AbortController();
101
- * assignGingerly(div, {
102
- * '?.mountObserver?.@eachTime?.classList?.add': 'highlighted'
103
- * }, {
104
- * withMethods: ['add'],
105
- * signal: controller.signal
106
- * });
107
- * // Later: controller.abort(); // Cleanup all listeners
108
- */
109
- signal?: AbortSignal;
110
-
111
- /**
112
- * List of property names that should be treated as async methods.
113
- * Works together with withMethods — async methods are awaited before
114
- * continuing the chain.
115
- *
116
- * The path evaluation for keys containing async methods is fire-and-forget:
117
- * assignGingerly remains synchronous and returns immediately. The async
118
- * chain completes in the background.
119
- *
120
- * NOTE: Interaction with @each and @eachTime is not yet implemented.
121
- *
122
- * Example:
123
- * assignGingerly(el, {
124
- * '?.whenFeatureReady?.photoTaker?.someProp': 'hello'
125
- * }, { withAsyncMethods: ['whenFeatureReady'] });
126
- * // Calls: (await el.whenFeatureReady('photoTaker')).someProp = 'hello'
127
- */
128
- withAsyncMethods?: string[] | Set<string>;
129
-
130
- /**
131
- * Bulk enhancement application via EMC JSON configs.
132
- * Finds matching elements and spawns enhancements on them.
133
- * Fire-and-forget (async) — assignGingerly remains synchronous.
134
- *
135
- * @example
136
- * enhance: [
137
- * { emc: 'be-bound/emc.json', matching: '[name]' },
138
- * ]
139
- */
140
- enhance?: Array<{ emc: string; matching?: string; parse?: boolean }>;
141
- }
142
35
 
143
36
  /**
144
37
  * GUID for global instance map storage to ensure uniqueness across package versions
@@ -779,8 +672,8 @@ export function assignGingerly(
779
672
 
780
673
  const registry = options?.registry instanceof EnhancementRegistry
781
674
  ? options.registry
782
- : options?.registry
783
- ? new options.registry()
675
+ // : options?.registry
676
+ // ? new options.registry()
784
677
  : undefined;
785
678
 
786
679
  // Convert Symbol.for string keys to actual symbols and apply aliases
@@ -867,12 +760,13 @@ export function assignGingerly(
867
760
  lhsValue = lhsParent[lhsKey];
868
761
  }
869
762
 
763
+ //TODO: this logic seems to occur twice at least. Maybe make it a method?
870
764
  // Event handler: Element LHS + object RHS with 'on' property
871
765
  if (lhsValue instanceof Element && value && typeof value === 'object' && !Array.isArray(value) && 'on' in value) {
872
766
  const capturedLhs = lhsValue;
873
767
  const capturedValue = value;
874
768
  const capturedTarget = target;
875
- const capturedOptions = options;
769
+ const capturedOptions = options as AssignFromOptions | undefined;
876
770
  import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
877
771
  attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {});
878
772
  });
@@ -898,7 +792,7 @@ export function assignGingerly(
898
792
  const capturedLhs = target[path];
899
793
  const capturedValue = value;
900
794
  const capturedTarget = target;
901
- const capturedOptions = options;
795
+ const capturedOptions = options as AssignFromOptions | undefined;
902
796
  import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
903
797
  attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {});
904
798
  });
package/eachTime.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Provides event-driven iteration over elements as they mount
5
5
  */
6
6
 
7
- import type { IAssignGingerlyOptions } from './assignGingerly.js';
7
+ import type { IAssignGingerlyOptions } from './types/assign-gingerly/types.js';
8
8
 
9
9
  /**
10
10
  * Check if a value is an EventTarget
@@ -1,4 +1,4 @@
1
- import type { IAssignGingerlyOptions, ItemscopeManagerConfig } from './assignGingerly.js';
1
+ import type { IAssignGingerlyOptions } from './types/assign-gingerly/types.js';
2
2
 
3
3
  /**
4
4
  * Handle the 'ish' property assignment for HTMLElements with itemscope attributes.
@@ -272,6 +272,37 @@ export interface IAssignGingerlyOptions {
272
272
  * When the signal is aborted, all event listeners are automatically removed
273
273
  */
274
274
  signal?: AbortSignal;
275
+
276
+ /**
277
+ * List of property names that should be treated as async methods.
278
+ * Works together with withMethods — async methods are awaited before
279
+ * continuing the chain.
280
+ *
281
+ * The path evaluation for keys containing async methods is fire-and-forget:
282
+ * assignGingerly remains synchronous and returns immediately. The async
283
+ * chain completes in the background.
284
+ *
285
+ * NOTE: Interaction with @each and @eachTime is not yet implemented.
286
+ *
287
+ * Example:
288
+ * assignGingerly(el, {
289
+ * '?.whenFeatureReady?.photoTaker?.someProp': 'hello'
290
+ * }, { withAsyncMethods: ['whenFeatureReady'] });
291
+ * // Calls: (await el.whenFeatureReady('photoTaker')).someProp = 'hello'
292
+ */
293
+ withAsyncMethods?: string[] | Set<string>;
294
+
295
+ /**
296
+ * Bulk enhancement application via EMC JSON configs.
297
+ * Finds matching elements and spawns enhancements on them.
298
+ * Fire-and-forget (async) — assignGingerly remains synchronous.
299
+ *
300
+ * @example
301
+ * enhance: [
302
+ * { emc: 'be-bound/emc.json', matching: '[name]' },
303
+ * ]
304
+ */
305
+ enhance?: Array<{ emc: string; matching?: string; parse?: boolean }>;
275
306
  }
276
307
 
277
308
  /**
@@ -1,4 +1,5 @@
1
- import assignGingerly, { EnhancementRegistry, ItemscopeRegistry, IAssignGingerlyOptions, getInstanceMap, INSTANCE_MAP_GUID } from './assignGingerly.js';
1
+ import assignGingerly, { EnhancementRegistry, ItemscopeRegistry, getInstanceMap } from './assignGingerly.js';
2
+ import {IAssignGingerlyOptions,} from './types/assign-gingerly/types.js';
2
3
  import assignTentatively from './assignTentatively.js';
3
4
  import type { IAssignTentativelyOptions } from './types/assign-gingerly/types.js';
4
5
  import { EnhancementConfig, SpawnContext } from './types/assign-gingerly/types.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.69",
3
+ "version": "0.0.70",
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": {
@@ -111,6 +111,26 @@
111
111
  "default": "./handlers/manageTemplateList.js",
112
112
  "types": "./handlers/manageTemplateList.ts"
113
113
  },
114
+ "./handlers/nudge.js": {
115
+ "default": "./handlers/nudge.js",
116
+ "types": "./handlers/nudge.ts"
117
+ },
118
+ "./handlers/arr.js": {
119
+ "default": "./handlers/arr.js",
120
+ "types": "./handlers/arr.ts"
121
+ },
122
+ "./inferencer/inferencer.js": {
123
+ "default": "./inferencer/inferencer.js",
124
+ "types": "./inferencer/inferencer.ts"
125
+ },
126
+ "./inferencer/upSearch.js": {
127
+ "default": "./inferencer/upSearch.js",
128
+ "types": "./inferencer/upSearch.ts"
129
+ },
130
+ "./inferencer/withScopePerimeter.js": {
131
+ "default": "./inferencer/withScopePerimeter.js",
132
+ "types": "./inferencer/withScopePerimeter.ts"
133
+ },
114
134
  "./DX/paths.js": {
115
135
  "default": "./DX/paths.js",
116
136
  "types": "./DX/paths.ts"
@@ -143,14 +163,6 @@
143
163
  "default": "./beVigilant.js",
144
164
  "types": "./beVigilant.ts"
145
165
  },
146
- "./inferencer/inferencer.js": {
147
- "default": "./inferencer/inferencer.js",
148
- "types": "./inferencer/inferencer.ts"
149
- },
150
- "./inferencer/withScopePerimeter.js": {
151
- "default": "./inferencer/withScopePerimeter.js",
152
- "types": "./inferencer/withScopePerimeter.ts"
153
- },
154
166
  "./assignFromAsync.js": {
155
167
  "default": "./assignFromAsync.js",
156
168
  "types": "./assignFromAsync.ts"
@@ -178,14 +190,6 @@
178
190
  "./evaluatePathWithAsyncMethods.js": {
179
191
  "default": "./evaluatePathWithAsyncMethods.js",
180
192
  "types": "./evaluatePathWithAsyncMethods.ts"
181
- },
182
- "./handlers/nudge.js": {
183
- "default": "./handlers/nudge.js",
184
- "types": "./handlers/nudge.ts"
185
- },
186
- "./handlers/arr.js": {
187
- "default": "./handlers/arr.js",
188
- "types": "./handlers/arr.ts"
189
193
  }
190
194
  },
191
195
  "main": "index.js",
@@ -272,6 +272,37 @@ export interface IAssignGingerlyOptions {
272
272
  * When the signal is aborted, all event listeners are automatically removed
273
273
  */
274
274
  signal?: AbortSignal;
275
+
276
+ /**
277
+ * List of property names that should be treated as async methods.
278
+ * Works together with withMethods — async methods are awaited before
279
+ * continuing the chain.
280
+ *
281
+ * The path evaluation for keys containing async methods is fire-and-forget:
282
+ * assignGingerly remains synchronous and returns immediately. The async
283
+ * chain completes in the background.
284
+ *
285
+ * NOTE: Interaction with @each and @eachTime is not yet implemented.
286
+ *
287
+ * Example:
288
+ * assignGingerly(el, {
289
+ * '?.whenFeatureReady?.photoTaker?.someProp': 'hello'
290
+ * }, { withAsyncMethods: ['whenFeatureReady'] });
291
+ * // Calls: (await el.whenFeatureReady('photoTaker')).someProp = 'hello'
292
+ */
293
+ withAsyncMethods?: string[] | Set<string>;
294
+
295
+ /**
296
+ * Bulk enhancement application via EMC JSON configs.
297
+ * Finds matching elements and spawns enhancements on them.
298
+ * Fire-and-forget (async) — assignGingerly remains synchronous.
299
+ *
300
+ * @example
301
+ * enhance: [
302
+ * { emc: 'be-bound/emc.json', matching: '[name]' },
303
+ * ]
304
+ */
305
+ enhance?: Array<{ emc: string; matching?: string; parse?: boolean }>;
275
306
  }
276
307
 
277
308
  /**