assign-gingerly 0.0.63 → 0.0.65

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/assignGingerly.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { normalizeAliasOptions } from './getValues.js';
1
2
  /**
2
3
  * GUID for global instance map storage to ensure uniqueness across package versions
3
4
  */
@@ -561,29 +562,14 @@ export function assignGingerly(target, source, options, permissions) {
561
562
  if (!target || typeof target !== 'object') {
562
563
  return target;
563
564
  }
564
- // Convert withMethods array to Set for O(1) lookup
565
- const withMethodsSet = options?.withMethods
566
- ? options.withMethods instanceof Set
567
- ? options.withMethods
568
- : new Set(options.withMethods)
569
- : undefined;
565
+ //TODO
566
+ const { aliasMap, withMethods: withMethodsSet } = normalizeAliasOptions(options);
570
567
  // Convert withAsyncMethods array to Set for O(1) lookup
571
568
  const withAsyncMethodsSet = options?.withAsyncMethods
572
569
  ? options.withAsyncMethods instanceof Set
573
570
  ? options.withAsyncMethods
574
571
  : new Set(options.withAsyncMethods)
575
572
  : undefined;
576
- // Convert aka object to Map for O(1) lookup and validate aliases
577
- const aliasMap = new Map();
578
- if (options?.aka) {
579
- for (const [alias, target] of Object.entries(options.aka)) {
580
- // Validate: disallow space and backtick in aliases
581
- if (alias.includes(' ') || alias.includes('`')) {
582
- throw new Error(`Invalid alias '${alias}': aliases cannot contain space or backtick characters`);
583
- }
584
- aliasMap.set(alias, target);
585
- }
586
- }
587
573
  const registry = options?.registry instanceof EnhancementRegistry
588
574
  ? options.registry
589
575
  : options?.registry
package/assignGingerly.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  import { EnhancementConfig } from "./types/assign-gingerly/types";
4
4
  import type { FeatureConfigsMap } from "./types/assign-gingerly/types";
5
5
  import type { AssignPermissions } from "./isAllowedImportPath.js";
6
+ import { normalizeAliasOptions } from './getValues.js';
6
7
 
7
8
  /**
8
9
  * Constructor signature for ItemScope Manager classes
@@ -82,6 +83,13 @@ export interface IAssignGingerlyOptions {
82
83
  * // Equivalent to: element.querySelector('my-element').classList.add('highlighted')
83
84
  */
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>;
85
93
 
86
94
  /**
87
95
  * AbortSignal for cleaning up reactive subscriptions (@eachTime)
@@ -759,13 +767,9 @@ export function assignGingerly(
759
767
  if (!target || typeof target !== 'object') {
760
768
  return target;
761
769
  }
770
+ //TODO
762
771
 
763
- // Convert withMethods array to Set for O(1) lookup
764
- const withMethodsSet = options?.withMethods
765
- ? options.withMethods instanceof Set
766
- ? options.withMethods
767
- : new Set(options.withMethods)
768
- : undefined;
772
+ const { aliasMap, withMethods: withMethodsSet } = normalizeAliasOptions(options);
769
773
 
770
774
  // Convert withAsyncMethods array to Set for O(1) lookup
771
775
  const withAsyncMethodsSet = options?.withAsyncMethods
@@ -774,18 +778,6 @@ export function assignGingerly(
774
778
  : new Set(options.withAsyncMethods)
775
779
  : undefined;
776
780
 
777
- // Convert aka object to Map for O(1) lookup and validate aliases
778
- const aliasMap = new Map<string, string>();
779
- if (options?.aka) {
780
- for (const [alias, target] of Object.entries(options.aka)) {
781
- // Validate: disallow space and backtick in aliases
782
- if (alias.includes(' ') || alias.includes('`')) {
783
- throw new Error(`Invalid alias '${alias}': aliases cannot contain space or backtick characters`);
784
- }
785
- aliasMap.set(alias, target);
786
- }
787
- }
788
-
789
781
  const registry = options?.registry instanceof EnhancementRegistry
790
782
  ? options.registry
791
783
  : options?.registry
@@ -1,9 +1,8 @@
1
1
  /**
2
- * Interface for assignTentatively options with reversal tracking
2
+ * assignTentatively reversible assignment with change tracking.
3
3
  */
4
- export interface IAssignTentativelyOptions {
5
- reversal?: Record<string | symbol, any>;
6
- }
4
+ import type { IAssignTentativelyOptions } from './types/assign-gingerly/types.js';
5
+ export type { IAssignTentativelyOptions };
7
6
 
8
7
  /**
9
8
  * Helper function to check if a string key represents an += command
package/getValues.js CHANGED
@@ -16,6 +16,39 @@
16
16
  * count: 42
17
17
  * }, source, { withMethods: ['querySelector'], aka: { q: 'querySelector' } });
18
18
  */
19
+ //TODO
20
+ export function normalizeAliasOptions(options) {
21
+ const aliasMap = new Map();
22
+ if (options?.aka) {
23
+ for (const [alias, target] of Object.entries(options.aka)) {
24
+ if (alias.includes(' ') || alias.includes('`')) {
25
+ throw new Error(`Invalid alias '${alias}': aliases cannot contain space or backtick characters`);
26
+ }
27
+ aliasMap.set(alias, target);
28
+ }
29
+ }
30
+ if (options?.akaMethods) {
31
+ for (const [alias, target] of Object.entries(options.akaMethods)) {
32
+ if (alias.includes(' ') || alias.includes('`')) {
33
+ throw new Error(`Invalid alias '${alias}': aliases cannot contain space or backtick characters`);
34
+ }
35
+ aliasMap.set(alias, target);
36
+ }
37
+ }
38
+ const withMethods = options?.withMethods
39
+ ? options.withMethods instanceof Set
40
+ ? new Set(options.withMethods)
41
+ : new Set(options.withMethods)
42
+ : options?.akaMethods
43
+ ? new Set()
44
+ : undefined;
45
+ if (options?.akaMethods) {
46
+ for (const target of Object.values(options.akaMethods)) {
47
+ withMethods?.add(target);
48
+ }
49
+ }
50
+ return { aliasMap, withMethods };
51
+ }
19
52
  /**
20
53
  * Apply alias substitutions to a path string.
21
54
  * Replaces complete tokens between `?.` delimiters with their aliased values.
@@ -27,6 +60,17 @@ function applyAliases(path, aliasMap) {
27
60
  const substituted = parts.map(part => aliasMap.get(part) ?? part);
28
61
  return substituted.join('?.');
29
62
  }
63
+ /**
64
+ * Resolve a special root-reference token at the start of a string.
65
+ * '$0' refers to the first argument passed to assignFrom / resolveValues.
66
+ */
67
+ function resolveRootReference(path, source, root) {
68
+ if (path === '$0')
69
+ return { source: root ?? source, path: '' };
70
+ if (path.startsWith('$0?.'))
71
+ return { source: root ?? source, path: path.substring(4) };
72
+ return null;
73
+ }
30
74
  /**
31
75
  * Path cache for parsed path strings.
32
76
  * Avoids re-splitting the same path on repeated calls.
@@ -116,6 +160,18 @@ function getArray(arr, source, aliasMap, withMethods, protocols, options) {
116
160
  const parts = parseCachedPath(aliased);
117
161
  result.push(parts.length === 0 ? source : navigatePath(source, parts, withMethods));
118
162
  }
163
+ else if (typeof item === 'string' && item.startsWith('$0')) {
164
+ const rootRef = resolveRootReference(item, source, options?.root);
165
+ if (rootRef === null) {
166
+ result.push(source);
167
+ }
168
+ else {
169
+ const aliased = applyAliases(rootRef.path, aliasMap);
170
+ const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
171
+ const parts = parseCachedPath(normalizedPath);
172
+ result.push(parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods));
173
+ }
174
+ }
119
175
  else if (typeof item === 'string' && protocols && hasProtocol(item)) {
120
176
  result.push(getProtocolValue(item, protocols, options));
121
177
  }
@@ -150,19 +206,7 @@ function getArray(arr, source, aliasMap, withMethods, protocols, options) {
150
206
  * @returns New object with path strings replaced by resolved values
151
207
  */
152
208
  export function getValues(pattern, source, options) {
153
- // Build alias map
154
- const aliasMap = new Map();
155
- if (options?.aka) {
156
- for (const [alias, target] of Object.entries(options.aka)) {
157
- aliasMap.set(alias, target);
158
- }
159
- }
160
- // Build methods set
161
- const withMethods = options?.withMethods
162
- ? options.withMethods instanceof Set
163
- ? options.withMethods
164
- : new Set(options.withMethods)
165
- : undefined;
209
+ const { aliasMap, withMethods } = normalizeAliasOptions(options);
166
210
  const protocols = options?.protocols;
167
211
  const result = {};
168
212
  for (const [key, value] of Object.entries(pattern)) {
@@ -171,6 +215,18 @@ export function getValues(pattern, source, options) {
171
215
  const parts = parseCachedPath(aliased);
172
216
  result[key] = parts.length === 0 ? source : navigatePath(source, parts, withMethods);
173
217
  }
218
+ else if (typeof value === 'string' && value.startsWith('$0')) {
219
+ const rootRef = resolveRootReference(value, source, options?.root);
220
+ if (rootRef === null) {
221
+ result[key] = source;
222
+ }
223
+ else {
224
+ const aliased = applyAliases(rootRef.path, aliasMap);
225
+ const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
226
+ const parts = parseCachedPath(normalizedPath);
227
+ result[key] = parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods);
228
+ }
229
+ }
174
230
  else if (typeof value === 'string' && protocols && hasProtocol(value)) {
175
231
  result[key] = getProtocolValue(value, protocols, options);
176
232
  }
@@ -201,23 +257,23 @@ export function getValues(pattern, source, options) {
201
257
  * @returns The resolved value, or undefined if any segment is nullish
202
258
  */
203
259
  export function getValue(path, source, options) {
204
- if (!path.startsWith('?.'))
260
+ const rootRef = resolveRootReference(path, source, options?.root);
261
+ if (rootRef) {
262
+ path = rootRef.path;
263
+ source = rootRef.source;
264
+ }
265
+ else if (!path.startsWith('?.')) {
205
266
  return path;
267
+ }
206
268
  let aliased = path;
207
- if (options?.aka) {
208
- const aliasMap = new Map();
209
- for (const [alias, target] of Object.entries(options.aka)) {
210
- aliasMap.set(alias, target);
211
- }
269
+ const { aliasMap } = normalizeAliasOptions(options);
270
+ if (aliasMap.size > 0) {
212
271
  aliased = applyAliases(path, aliasMap);
213
272
  }
214
- const parts = parseCachedPath(aliased);
273
+ const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
274
+ const parts = parseCachedPath(normalizedPath);
215
275
  if (parts.length === 0)
216
276
  return source;
217
- const withMethods = options?.withMethods
218
- ? options.withMethods instanceof Set
219
- ? options.withMethods
220
- : new Set(options.withMethods)
221
- : undefined;
277
+ const { withMethods } = normalizeAliasOptions(options);
222
278
  return navigatePath(source, parts, withMethods);
223
279
  }
package/getValues.ts CHANGED
@@ -18,6 +18,48 @@
18
18
  */
19
19
 
20
20
  import type { GetValuesOptions } from './types/assign-gingerly/types.js';
21
+ //TODO
22
+ export function normalizeAliasOptions(options?: {
23
+ aka?: Record<string, string>;
24
+ akaMethods?: Record<string, string>;
25
+ withMethods?: string[] | Set<string>;
26
+ }): { aliasMap: Map<string, string>; withMethods: Set<string> | undefined } {
27
+ const aliasMap = new Map<string, string>();
28
+
29
+ if (options?.aka) {
30
+ for (const [alias, target] of Object.entries(options.aka)) {
31
+ if (alias.includes(' ') || alias.includes('`')) {
32
+ throw new Error(`Invalid alias '${alias}': aliases cannot contain space or backtick characters`);
33
+ }
34
+ aliasMap.set(alias, target);
35
+ }
36
+ }
37
+
38
+ if (options?.akaMethods) {
39
+ for (const [alias, target] of Object.entries(options.akaMethods)) {
40
+ if (alias.includes(' ') || alias.includes('`')) {
41
+ throw new Error(`Invalid alias '${alias}': aliases cannot contain space or backtick characters`);
42
+ }
43
+ aliasMap.set(alias, target);
44
+ }
45
+ }
46
+
47
+ const withMethods = options?.withMethods
48
+ ? options.withMethods instanceof Set
49
+ ? new Set(options.withMethods)
50
+ : new Set(options.withMethods)
51
+ : options?.akaMethods
52
+ ? new Set<string>()
53
+ : undefined;
54
+
55
+ if (options?.akaMethods) {
56
+ for (const target of Object.values(options.akaMethods)) {
57
+ withMethods?.add(target);
58
+ }
59
+ }
60
+
61
+ return { aliasMap, withMethods };
62
+ }
21
63
 
22
64
  /**
23
65
  * Apply alias substitutions to a path string.
@@ -30,6 +72,16 @@ function applyAliases(path: string, aliasMap: Map<string, string>): string {
30
72
  return substituted.join('?.');
31
73
  }
32
74
 
75
+ /**
76
+ * Resolve a special root-reference token at the start of a string.
77
+ * '$0' refers to the first argument passed to assignFrom / resolveValues.
78
+ */
79
+ function resolveRootReference(path: string, source: any, root: any): { source: any; path: string } | null {
80
+ if (path === '$0') return { source: root ?? source, path: '' };
81
+ if (path.startsWith('$0?.')) return { source: root ?? source, path: path.substring(4) };
82
+ return null;
83
+ }
84
+
33
85
  /**
34
86
  * Path cache for parsed path strings.
35
87
  * Avoids re-splitting the same path on repeated calls.
@@ -142,6 +194,16 @@ function getArray(
142
194
  const aliased = applyAliases(item, aliasMap);
143
195
  const parts = parseCachedPath(aliased);
144
196
  result.push(parts.length === 0 ? source : navigatePath(source, parts, withMethods));
197
+ } else if (typeof item === 'string' && item.startsWith('$0')) {
198
+ const rootRef = resolveRootReference(item, source, options?.root);
199
+ if (rootRef === null) {
200
+ result.push(source);
201
+ } else {
202
+ const aliased = applyAliases(rootRef.path, aliasMap);
203
+ const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
204
+ const parts = parseCachedPath(normalizedPath);
205
+ result.push(parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods));
206
+ }
145
207
  } else if (typeof item === 'string' && protocols && hasProtocol(item)) {
146
208
  result.push(getProtocolValue(item, protocols, options));
147
209
  } else if (Array.isArray(item)) {
@@ -177,20 +239,7 @@ export function getValues(
177
239
  source: any,
178
240
  options?: GetValuesOptions
179
241
  ): Record<string, any> {
180
- // Build alias map
181
- const aliasMap = new Map<string, string>();
182
- if (options?.aka) {
183
- for (const [alias, target] of Object.entries(options.aka)) {
184
- aliasMap.set(alias, target);
185
- }
186
- }
187
-
188
- // Build methods set
189
- const withMethods = options?.withMethods
190
- ? options.withMethods instanceof Set
191
- ? options.withMethods
192
- : new Set(options.withMethods)
193
- : undefined;
242
+ const { aliasMap, withMethods } = normalizeAliasOptions(options);
194
243
 
195
244
  const protocols = options?.protocols;
196
245
 
@@ -200,6 +249,16 @@ export function getValues(
200
249
  const aliased = applyAliases(value, aliasMap);
201
250
  const parts = parseCachedPath(aliased);
202
251
  result[key] = parts.length === 0 ? source : navigatePath(source, parts, withMethods);
252
+ } else if (typeof value === 'string' && value.startsWith('$0')) {
253
+ const rootRef = resolveRootReference(value, source, options?.root);
254
+ if (rootRef === null) {
255
+ result[key] = source;
256
+ } else {
257
+ const aliased = applyAliases(rootRef.path, aliasMap);
258
+ const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
259
+ const parts = parseCachedPath(normalizedPath);
260
+ result[key] = parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods);
261
+ }
203
262
  } else if (typeof value === 'string' && protocols && hasProtocol(value)) {
204
263
  result[key] = getProtocolValue(value, protocols, options);
205
264
  } else if (Array.isArray(value)) {
@@ -231,25 +290,25 @@ export function getValue(
231
290
  source: any,
232
291
  options?: GetValuesOptions
233
292
  ): any {
234
- if (!path.startsWith('?.')) return path;
293
+ const rootRef = resolveRootReference(path, source, options?.root);
294
+ if (rootRef) {
295
+ path = rootRef.path;
296
+ source = rootRef.source;
297
+ } else if (!path.startsWith('?.')) {
298
+ return path;
299
+ }
235
300
 
236
301
  let aliased = path;
237
- if (options?.aka) {
238
- const aliasMap = new Map<string, string>();
239
- for (const [alias, target] of Object.entries(options.aka)) {
240
- aliasMap.set(alias, target);
241
- }
302
+ const { aliasMap } = normalizeAliasOptions(options);
303
+ if (aliasMap.size > 0) {
242
304
  aliased = applyAliases(path, aliasMap);
243
305
  }
244
306
 
245
- const parts = parseCachedPath(aliased);
307
+ const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
308
+ const parts = parseCachedPath(normalizedPath);
246
309
  if (parts.length === 0) return source;
247
310
 
248
- const withMethods = options?.withMethods
249
- ? options.withMethods instanceof Set
250
- ? options.withMethods
251
- : new Set(options.withMethods)
252
- : undefined;
311
+ const { withMethods } = normalizeAliasOptions(options);
253
312
 
254
313
  return navigatePath(source, parts, withMethods);
255
314
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Utility function to normalize a value to an array.
3
+ * - If undefined, returns empty array
4
+ * - If already an array, returns as-is
5
+ * - Otherwise, wraps the value in an array
6
+ *
7
+ * @param inp - Value to normalize to array
8
+ * @returns Array containing the value(s)
9
+ */
10
+ export function arr(inp) {
11
+ return inp === undefined ? []
12
+ : Array.isArray(inp) ? inp : [inp];
13
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Utility function to normalize a value to an array.
3
+ * - If undefined, returns empty array
4
+ * - If already an array, returns as-is
5
+ * - Otherwise, wraps the value in an array
6
+ *
7
+ * @param inp - Value to normalize to array
8
+ * @returns Array containing the value(s)
9
+ */
10
+ export function arr<T = any>(inp: T | T[] | undefined): T[] {
11
+ return inp === undefined ? []
12
+ : Array.isArray(inp) ? inp : [inp];
13
+ }
@@ -149,7 +149,7 @@ export class ManageTemplateListHandler {
149
149
  // New item — clone template
150
150
  let content;
151
151
  if (instantiate instanceof HTMLTemplateElement) {
152
- content = instantiate.content.cloneNode(true);
152
+ content = (instantiate.remoteContent || instantiate.content).cloneNode(true);
153
153
  }
154
154
  else if (instantiate instanceof DocumentFragment) {
155
155
  content = instantiate.cloneNode(true);
@@ -182,7 +182,7 @@ export class ManageTemplateListHandler implements AssignFromHandler {
182
182
  // New item — clone template
183
183
  let content: DocumentFragment;
184
184
  if (instantiate instanceof HTMLTemplateElement) {
185
- content = instantiate.content.cloneNode(true) as DocumentFragment;
185
+ content = (instantiate.remoteContent ||instantiate.content).cloneNode(true) as DocumentFragment;
186
186
  } else if (instantiate instanceof DocumentFragment) {
187
187
  content = instantiate.cloneNode(true) as DocumentFragment;
188
188
  } else {
@@ -0,0 +1,23 @@
1
+ import { arr } from './arr.js';
2
+ /**
3
+ * Decrement "disabled" counter, remove when reaches 0
4
+ * @param el
5
+ * Optional select the attribute or attributes to remove or decrement
6
+ * @param attr
7
+ */
8
+ export function nudge(el, attr = 'disabled') {
9
+ const attrs = arr(attr);
10
+ for (const attr of attrs) {
11
+ const da = el.getAttribute(attr);
12
+ if (da !== null) {
13
+ if (da.length === 0 || da === "1") {
14
+ el.removeAttribute(attr);
15
+ if (attr === 'disabled')
16
+ el.disabled = false;
17
+ }
18
+ else {
19
+ el.setAttribute(attr, (parseInt(da) - 1).toString());
20
+ }
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ import {arr} from './arr.js';
2
+ /**
3
+ * Decrement "disabled" counter, remove when reaches 0
4
+ * @param el
5
+ * Optional select the attribute or attributes to remove or decrement
6
+ * @param attr
7
+ */
8
+ export function nudge(el: Element, attr: string | Array<string> = 'disabled') { //TODO: Share with be-observant
9
+ const attrs = arr(attr);
10
+ for(const attr of attrs){
11
+ const da = el.getAttribute(attr);
12
+ if (da !== null) {
13
+ if (da.length === 0 || da === "1") {
14
+ el.removeAttribute(attr);
15
+ if(attr === 'disabled') (<any>el).disabled = false;
16
+ }
17
+ else {
18
+ el.setAttribute(attr, (parseInt(da) - 1).toString());
19
+ }
20
+ }
21
+ }
22
+
23
+ }
package/index.js CHANGED
@@ -14,4 +14,6 @@ export { assignFeatures, FeaturesRegistry, captureFeatureInitVals, PropertyBag,
14
14
  export { installForwarding } from './installForwarding.js';
15
15
  export { defineWithFeatures } from './defineWithFeatures.js';
16
16
  export { resolveAndAssignFeatures } from './resolveAndAssignFeatures.js';
17
+ export { nudge } from './handlers/nudge.js';
18
+ export { arr } from './handlers/arr.js';
17
19
  import './object-extension.js';
package/index.ts CHANGED
@@ -14,5 +14,7 @@ export {assignFeatures, FeaturesRegistry, captureFeatureInitVals, PropertyBag, s
14
14
  export {installForwarding} from './installForwarding.js';
15
15
  export {defineWithFeatures} from './defineWithFeatures.js';
16
16
  export {resolveAndAssignFeatures} from './resolveAndAssignFeatures.js';
17
+ export {nudge} from './handlers/nudge.js';
18
+ export {arr} from './handlers/arr.js';
17
19
  export {} from './handlers/manageTemplateList.js';
18
20
  import './object-extension.js';