assign-gingerly 0.0.24 → 0.0.26

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/README.md CHANGED
@@ -108,10 +108,40 @@ console.log(obj);
108
108
  // }
109
109
  ```
110
110
 
111
- When the right hand side of an expression is an object, assignGingerly is recursively applied (passing the third argument in if applicable, which will be discussed below).
111
+ When the right hand side of an expression is an object, assignGingerly behavior depends on the context:
112
+ - For **nested paths** (starting with `?.`): recursively merges into nested objects, creating them if needed
113
+ - For **plain keys**: performs simple assignment (like `Object.assign`), unless the target property is readonly or a class instance (see Examples 3a and 3b below)
112
114
 
113
115
  Of course, just as Object.assign led to object spread notation, assignGingerly could lead to some sort of deep structural JavaScript syntax, but that is outside the scope of this polyfill package.
114
116
 
117
+ ## Example 3-plain - Plain Key Object Assignment
118
+
119
+ For plain keys (without `?.` prefix), assignGingerly performs simple assignment, just like `Object.assign`:
120
+
121
+ ```TypeScript
122
+ const obj = {};
123
+ const template = document.createElement('template');
124
+ template.innerHTML = '<div>Hello</div>';
125
+
126
+ assignGingerly(obj, {
127
+ template: template,
128
+ config: { theme: 'dark', lang: 'en' }
129
+ });
130
+
131
+ console.log(obj.template === template); // true - direct assignment
132
+ console.log(obj.config); // { theme: 'dark', lang: 'en' } - direct assignment
133
+ ```
134
+
135
+ This is different from nested paths, which create intermediate objects:
136
+
137
+ ```TypeScript
138
+ const obj = {};
139
+ assignGingerly(obj, {
140
+ '?.config?.theme': 'dark'
141
+ });
142
+ console.log(obj.config); // { theme: 'dark' } - intermediate object created
143
+ ```
144
+
115
145
  ## Example 3a - Automatic Readonly Property Detection
116
146
 
117
147
  assignGingerly automatically detects readonly properties and merges into them instead of attempting to replace them. This makes working with DOM properties like `style` and `dataset` much more ergonomic:
@@ -325,6 +355,115 @@ assignGingerly(div, {
325
355
  // All instances and readonly objects preserved
326
356
  ```
327
357
 
358
+ ## Example 3c - Method Calls with withMethods
359
+
360
+ The `withMethods` option allows you to call methods as part of property assignment, which is particularly useful for DOM APIs like `classList` and `part`:
361
+
362
+ ```TypeScript
363
+ import assignGingerly from 'assign-gingerly';
364
+
365
+ const element = document.createElement('div');
366
+
367
+ // Simple method calls
368
+ assignGingerly(element, {
369
+ '?.classList?.add': 'myClass',
370
+ '?.part?.add': 'myPart'
371
+ }, { withMethods: ['add'] });
372
+
373
+ console.log(element.classList.contains('myClass')); // true
374
+ console.log(element.part.contains('myPart')); // true
375
+ ```
376
+
377
+ **How it works:**
378
+
379
+ When a path segment matches a name in the `withMethods` array/set:
380
+ - If it's the **last segment**: the method is called with the RHS value as an argument
381
+ - If it's a **middle segment** and the next segment is also a method: called with no arguments
382
+ - If it's a **middle segment** and the next segment is NOT a method: called with the next segment as a string argument
383
+ - If the property is not a function: silently skipped
384
+
385
+ **Array arguments:**
386
+
387
+ Arrays are spread as multiple arguments:
388
+
389
+ ```TypeScript
390
+ assignGingerly(element, {
391
+ '?.setAttribute': ['data-id', '123']
392
+ }, { withMethods: ['setAttribute'] });
393
+
394
+ // Equivalent to: element.setAttribute('data-id', '123')
395
+ ```
396
+
397
+ **Chained method calls:**
398
+
399
+ Methods can be chained to navigate through object hierarchies:
400
+
401
+ ```TypeScript
402
+ const elementRef = {
403
+ deref() { return this.element; },
404
+ element: document.createElement('div')
405
+ };
406
+
407
+ assignGingerly(elementRef, {
408
+ '?.deref?.classList?.add': 'active'
409
+ }, { withMethods: ['deref', 'add'] });
410
+
411
+ // Equivalent to: elementRef.deref().classList.add('active')
412
+ ```
413
+
414
+ **Complex chaining:**
415
+
416
+ ```TypeScript
417
+ const shadowRoot = {
418
+ querySelector(selector) {
419
+ return this.elements[selector];
420
+ },
421
+ elements: {
422
+ 'my-element': document.createElement('div')
423
+ }
424
+ };
425
+
426
+ assignGingerly(shadowRoot, {
427
+ '?.querySelector?.my-element?.classList?.add': 'highlighted'
428
+ }, { withMethods: ['querySelector', 'add'] });
429
+
430
+ // Equivalent to: shadowRoot.querySelector('my-element').classList.add('highlighted')
431
+ ```
432
+
433
+ **Using Set for withMethods:**
434
+
435
+ For better performance with many methods, use a Set:
436
+
437
+ ```TypeScript
438
+ const methods = new Set(['add', 'remove', 'toggle', 'setAttribute']);
439
+
440
+ assignGingerly(element, {
441
+ '?.classList?.add': 'class1',
442
+ '?.classList?.remove': 'class2',
443
+ '?.setAttribute': ['data-value', '42']
444
+ }, { withMethods: methods });
445
+ ```
446
+
447
+ **Mixing methods and normal assignments:**
448
+
449
+ ```TypeScript
450
+ assignGingerly(element, {
451
+ '?.classList?.add': 'active',
452
+ '?.dataset?.userId': '123',
453
+ '?.style?.height': '100px'
454
+ }, { withMethods: ['add'] });
455
+
456
+ // classList.add() is called
457
+ // dataset.userId and style.height are assigned normally
458
+ ```
459
+
460
+ **Benefits:**
461
+
462
+ - Cleaner syntax for DOM manipulation
463
+ - Works with any object methods, not just DOM APIs
464
+ - Silent failure for non-existent methods (garbage in, garbage out)
465
+ - Supports method chaining and complex navigation patterns
466
+
328
467
  While we are in the business of passing values of object A into object B, we might as well add some extremely common behavior that allows updating properties of object B based on the current values of object B -- things like incrementing, toggling, and deleting. Deleting is critical for assignTentatively, but is included with both functions.
329
468
 
330
469
  ## Example 4 - Incrementing values with += command
package/assignGingerly.js CHANGED
@@ -270,6 +270,55 @@ function isClassInstance(value) {
270
270
  // Plain objects have Object.prototype or null as prototype
271
271
  return proto !== Object.prototype && proto !== null;
272
272
  }
273
+ /**
274
+ * Helper function to evaluate a nested path with method calls
275
+ * Handles chained method calls where path segments can be methods
276
+ */
277
+ function evaluatePathWithMethods(target, pathParts, value, withMethods) {
278
+ let current = target;
279
+ let i = 0;
280
+ // Process all segments except the last one
281
+ while (i < pathParts.length - 1) {
282
+ const part = pathParts[i];
283
+ const nextPart = pathParts[i + 1];
284
+ if (withMethods.has(part)) {
285
+ const method = current[part];
286
+ if (typeof method === 'function') {
287
+ // Check if next part is also a method
288
+ if (withMethods.has(nextPart)) {
289
+ // Both are methods - call first with no args
290
+ current = method.call(current);
291
+ }
292
+ else {
293
+ // Only current is method - call with next part as string arg
294
+ current = method.call(current, nextPart);
295
+ i++; // Skip next part since we consumed it as argument
296
+ }
297
+ }
298
+ else {
299
+ // Not a function - just access property (create if needed)
300
+ if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {
301
+ current[part] = {};
302
+ }
303
+ current = current[part];
304
+ }
305
+ }
306
+ else {
307
+ // Not a method - normal property access (create if needed)
308
+ if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {
309
+ current[part] = {};
310
+ }
311
+ current = current[part];
312
+ }
313
+ i++;
314
+ }
315
+ const lastKey = pathParts[pathParts.length - 1];
316
+ return {
317
+ target: current,
318
+ lastKey,
319
+ isMethod: withMethods.has(lastKey)
320
+ };
321
+ }
273
322
  /**
274
323
  * Main assignGingerly function
275
324
  */
@@ -277,6 +326,12 @@ export function assignGingerly(target, source, options) {
277
326
  if (!target || typeof target !== 'object') {
278
327
  return target;
279
328
  }
329
+ // Convert withMethods array to Set for O(1) lookup
330
+ const withMethodsSet = options?.withMethods
331
+ ? options.withMethods instanceof Set
332
+ ? options.withMethods
333
+ : new Set(options.withMethods)
334
+ : undefined;
280
335
  const registry = options?.registry instanceof EnhancementRegistry
281
336
  ? options.registry
282
337
  : options?.registry
@@ -437,32 +492,86 @@ export function assignGingerly(target, source, options) {
437
492
  }
438
493
  if (isNestedPath(key)) {
439
494
  const pathParts = parsePath(key);
440
- const lastKey = pathParts[pathParts.length - 1];
441
- const parent = ensureNestedPath(target, pathParts);
442
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
443
- // Check if property exists and is readonly OR is a class instance
444
- if (lastKey in parent && (isReadonlyProperty(parent, lastKey) || isClassInstance(parent[lastKey]))) {
445
- // Property is readonly or a class instance - check if current value is an object
446
- const currentValue = parent[lastKey];
447
- if (typeof currentValue !== 'object' || currentValue === null) {
448
- throw new Error(`Cannot merge object into ${isReadonlyProperty(parent, lastKey) ? 'readonly ' : ''}primitive property '${String(lastKey)}'`);
495
+ // Check if we need to handle methods
496
+ if (withMethodsSet) {
497
+ const result = evaluatePathWithMethods(target, pathParts, value, withMethodsSet);
498
+ if (result.isMethod) {
499
+ // Last segment is a method - call it
500
+ const method = result.target[result.lastKey];
501
+ if (typeof method === 'function') {
502
+ if (Array.isArray(value)) {
503
+ method.apply(result.target, value);
504
+ }
505
+ else {
506
+ method.call(result.target, value);
507
+ }
449
508
  }
450
- // Recursively apply assignGingerly to the readonly object or class instance
451
- assignGingerly(currentValue, value, options);
509
+ // Silently skip if not a function
510
+ continue;
452
511
  }
453
- else {
454
- // Property is writable and not a class instance - normal recursive merge
455
- if (!(lastKey in parent) || typeof parent[lastKey] !== 'object') {
456
- parent[lastKey] = {};
512
+ // Not a method - proceed with normal assignment using evaluated target
513
+ const lastKey = result.lastKey;
514
+ const parent = result.target;
515
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
516
+ // Check if property exists and is readonly OR is a class instance
517
+ if (lastKey in parent && (isReadonlyProperty(parent, lastKey) || isClassInstance(parent[lastKey]))) {
518
+ const currentValue = parent[lastKey];
519
+ if (typeof currentValue !== 'object' || currentValue === null) {
520
+ throw new Error(`Cannot merge object into ${isReadonlyProperty(parent, lastKey) ? 'readonly ' : ''}primitive property '${String(lastKey)}'`);
521
+ }
522
+ assignGingerly(currentValue, value, options);
523
+ }
524
+ else {
525
+ // Property is writable and not a class instance - replace it
526
+ parent[lastKey] = value;
457
527
  }
458
- assignGingerly(parent[lastKey], value, options);
528
+ }
529
+ else {
530
+ parent[lastKey] = value;
459
531
  }
460
532
  }
461
533
  else {
462
- parent[lastKey] = value;
534
+ // No withMethods - use original logic
535
+ const lastKey = pathParts[pathParts.length - 1];
536
+ const parent = ensureNestedPath(target, pathParts);
537
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
538
+ // Check if property exists and is readonly OR is a class instance
539
+ if (lastKey in parent && (isReadonlyProperty(parent, lastKey) || isClassInstance(parent[lastKey]))) {
540
+ // Property is readonly or a class instance - check if current value is an object
541
+ const currentValue = parent[lastKey];
542
+ if (typeof currentValue !== 'object' || currentValue === null) {
543
+ throw new Error(`Cannot merge object into ${isReadonlyProperty(parent, lastKey) ? 'readonly ' : ''}primitive property '${String(lastKey)}'`);
544
+ }
545
+ // Recursively apply assignGingerly to the readonly object or class instance
546
+ assignGingerly(currentValue, value, options);
547
+ }
548
+ else {
549
+ // Property is writable and not a class instance - replace it
550
+ parent[lastKey] = value;
551
+ }
552
+ }
553
+ else {
554
+ parent[lastKey] = value;
555
+ }
463
556
  }
464
557
  }
465
558
  else {
559
+ // Non-nested path
560
+ // Check if this is a method call
561
+ if (withMethodsSet && withMethodsSet.has(key)) {
562
+ const method = target[key];
563
+ if (typeof method === 'function') {
564
+ if (Array.isArray(value)) {
565
+ method.apply(target, value);
566
+ }
567
+ else {
568
+ method.call(target, value);
569
+ }
570
+ }
571
+ // Silently skip if not a function
572
+ continue;
573
+ }
574
+ // Normal assignment
466
575
  if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
467
576
  // Check if property exists and is readonly OR is a class instance
468
577
  if (key in target && (isReadonlyProperty(target, key) || isClassInstance(target[key]))) {
@@ -475,11 +584,8 @@ export function assignGingerly(target, source, options) {
475
584
  assignGingerly(currentValue, value, options);
476
585
  }
477
586
  else {
478
- // Property is writable and not a class instance - normal recursive merge
479
- if (!(key in target) || typeof target[key] !== 'object') {
480
- target[key] = {};
481
- }
482
- assignGingerly(target[key], value, options);
587
+ // Property is writable and not a class instance - replace it
588
+ target[key] = value;
483
589
  }
484
590
  }
485
591
  else {
package/assignGingerly.ts CHANGED
@@ -29,28 +29,37 @@ export interface ItemscopeManagerConfig<T = any> {
29
29
  };
30
30
  }
31
31
 
32
- // Polyfill for WeakMap.prototype.getOrInsert
33
-
34
- // if (typeof WeakMap.prototype.getOrInsertComputed !== 'function') {
35
- // WeakMap.prototype.getOrInsertComputed = function(key, insert) {
36
- // if (this.has(key)) return this.get(key);
37
- // const value = insert();
38
- // this.set(key, value);
39
- // return value;
40
- // };
41
- // }
42
-
43
- // /**
44
- // * @deprecated Use EnhancementConfig instead
45
- // */
46
- // export type IBaseRegistryItem<T = any> = EnhancementConfig<T>;
47
-
48
32
  /**
49
33
  * Interface for the options passed to assignGingerly
50
34
  */
51
35
  export interface IAssignGingerlyOptions {
52
36
  registry?: typeof EnhancementRegistry | EnhancementRegistry;
53
37
  bypassChecks?: boolean;
38
+
39
+ /**
40
+ * List of property names that should be treated as methods to call
41
+ * rather than properties to assign.
42
+ *
43
+ * When a path segment matches a name in this array/set:
44
+ * - If the property is a function, call it with appropriate arguments
45
+ * - For the last segment: use RHS value as argument (spread if array)
46
+ * - For middle segments: use next segment as string argument (if next is not also a method)
47
+ * - If consecutive segments are both methods, first is called with no arguments
48
+ * - If the property is not a function, silently skip
49
+ *
50
+ * Example:
51
+ * assignGingerly(element, {
52
+ * '?.classList?.add': 'myClass'
53
+ * }, { withMethods: ['add'] });
54
+ * // Calls: element.classList.add('myClass')
55
+ *
56
+ * Chained methods:
57
+ * assignGingerly(elementRef, {
58
+ * '?.deref?.querySelector?.myElement?.classList?.add': 'active'
59
+ * }, { withMethods: ['deref', 'querySelector', 'add'] });
60
+ * // Calls: elementRef.deref().querySelector('myElement').classList.add('active')
61
+ */
62
+ withMethods?: string[] | Set<string>;
54
63
  }
55
64
 
56
65
  /**
@@ -356,6 +365,62 @@ function isClassInstance(value: any): boolean {
356
365
  return proto !== Object.prototype && proto !== null;
357
366
  }
358
367
 
368
+ /**
369
+ * Helper function to evaluate a nested path with method calls
370
+ * Handles chained method calls where path segments can be methods
371
+ */
372
+ function evaluatePathWithMethods(
373
+ target: any,
374
+ pathParts: string[],
375
+ value: any,
376
+ withMethods: Set<string>
377
+ ): { target: any; lastKey: string; isMethod: boolean } {
378
+ let current = target;
379
+ let i = 0;
380
+
381
+ // Process all segments except the last one
382
+ while (i < pathParts.length - 1) {
383
+ const part = pathParts[i];
384
+ const nextPart = pathParts[i + 1];
385
+
386
+ if (withMethods.has(part)) {
387
+ const method = current[part];
388
+ if (typeof method === 'function') {
389
+ // Check if next part is also a method
390
+ if (withMethods.has(nextPart)) {
391
+ // Both are methods - call first with no args
392
+ current = method.call(current);
393
+ } else {
394
+ // Only current is method - call with next part as string arg
395
+ current = method.call(current, nextPart);
396
+ i++; // Skip next part since we consumed it as argument
397
+ }
398
+ } else {
399
+ // Not a function - just access property (create if needed)
400
+ if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {
401
+ current[part] = {};
402
+ }
403
+ current = current[part];
404
+ }
405
+ } else {
406
+ // Not a method - normal property access (create if needed)
407
+ if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {
408
+ current[part] = {};
409
+ }
410
+ current = current[part];
411
+ }
412
+
413
+ i++;
414
+ }
415
+
416
+ const lastKey = pathParts[pathParts.length - 1];
417
+ return {
418
+ target: current,
419
+ lastKey,
420
+ isMethod: withMethods.has(lastKey)
421
+ };
422
+ }
423
+
359
424
  /**
360
425
  * Main assignGingerly function
361
426
  */
@@ -368,6 +433,13 @@ export function assignGingerly(
368
433
  return target;
369
434
  }
370
435
 
436
+ // Convert withMethods array to Set for O(1) lookup
437
+ const withMethodsSet = options?.withMethods
438
+ ? options.withMethods instanceof Set
439
+ ? options.withMethods
440
+ : new Set(options.withMethods)
441
+ : undefined;
442
+
371
443
  const registry = options?.registry instanceof EnhancementRegistry
372
444
  ? options.registry
373
445
  : options?.registry
@@ -540,30 +612,85 @@ export function assignGingerly(
540
612
 
541
613
  if (isNestedPath(key)) {
542
614
  const pathParts = parsePath(key);
543
- const lastKey = pathParts[pathParts.length - 1];
544
- const parent = ensureNestedPath(target, pathParts);
545
-
546
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
547
- // Check if property exists and is readonly OR is a class instance
548
- if (lastKey in parent && (isReadonlyProperty(parent, lastKey) || isClassInstance(parent[lastKey]))) {
549
- // Property is readonly or a class instance - check if current value is an object
550
- const currentValue = parent[lastKey];
551
- if (typeof currentValue !== 'object' || currentValue === null) {
552
- throw new Error(`Cannot merge object into ${isReadonlyProperty(parent, lastKey) ? 'readonly ' : ''}primitive property '${String(lastKey)}'`);
615
+
616
+ // Check if we need to handle methods
617
+ if (withMethodsSet) {
618
+ const result = evaluatePathWithMethods(target, pathParts, value, withMethodsSet);
619
+
620
+ if (result.isMethod) {
621
+ // Last segment is a method - call it
622
+ const method = result.target[result.lastKey];
623
+ if (typeof method === 'function') {
624
+ if (Array.isArray(value)) {
625
+ method.apply(result.target, value);
626
+ } else {
627
+ method.call(result.target, value);
628
+ }
553
629
  }
554
- // Recursively apply assignGingerly to the readonly object or class instance
555
- assignGingerly(currentValue, value, options);
556
- } else {
557
- // Property is writable and not a class instance - normal recursive merge
558
- if (!(lastKey in parent) || typeof parent[lastKey] !== 'object') {
559
- parent[lastKey] = {};
630
+ // Silently skip if not a function
631
+ continue;
632
+ }
633
+
634
+ // Not a method - proceed with normal assignment using evaluated target
635
+ const lastKey = result.lastKey;
636
+ const parent = result.target;
637
+
638
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
639
+ // Check if property exists and is readonly OR is a class instance
640
+ if (lastKey in parent && (isReadonlyProperty(parent, lastKey) || isClassInstance(parent[lastKey]))) {
641
+ const currentValue = parent[lastKey];
642
+ if (typeof currentValue !== 'object' || currentValue === null) {
643
+ throw new Error(`Cannot merge object into ${isReadonlyProperty(parent, lastKey) ? 'readonly ' : ''}primitive property '${String(lastKey)}'`);
644
+ }
645
+ assignGingerly(currentValue, value, options);
646
+ } else {
647
+ // Property is writable and not a class instance - replace it
648
+ parent[lastKey] = value;
560
649
  }
561
- assignGingerly(parent[lastKey], value, options);
650
+ } else {
651
+ parent[lastKey] = value;
562
652
  }
563
653
  } else {
564
- parent[lastKey] = value;
654
+ // No withMethods - use original logic
655
+ const lastKey = pathParts[pathParts.length - 1];
656
+ const parent = ensureNestedPath(target, pathParts);
657
+
658
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
659
+ // Check if property exists and is readonly OR is a class instance
660
+ if (lastKey in parent && (isReadonlyProperty(parent, lastKey) || isClassInstance(parent[lastKey]))) {
661
+ // Property is readonly or a class instance - check if current value is an object
662
+ const currentValue = parent[lastKey];
663
+ if (typeof currentValue !== 'object' || currentValue === null) {
664
+ throw new Error(`Cannot merge object into ${isReadonlyProperty(parent, lastKey) ? 'readonly ' : ''}primitive property '${String(lastKey)}'`);
665
+ }
666
+ // Recursively apply assignGingerly to the readonly object or class instance
667
+ assignGingerly(currentValue, value, options);
668
+ } else {
669
+ // Property is writable and not a class instance - replace it
670
+ parent[lastKey] = value;
671
+ }
672
+ } else {
673
+ parent[lastKey] = value;
674
+ }
565
675
  }
566
676
  } else {
677
+ // Non-nested path
678
+
679
+ // Check if this is a method call
680
+ if (withMethodsSet && withMethodsSet.has(key)) {
681
+ const method = target[key];
682
+ if (typeof method === 'function') {
683
+ if (Array.isArray(value)) {
684
+ method.apply(target, value);
685
+ } else {
686
+ method.call(target, value);
687
+ }
688
+ }
689
+ // Silently skip if not a function
690
+ continue;
691
+ }
692
+
693
+ // Normal assignment
567
694
  if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
568
695
  // Check if property exists and is readonly OR is a class instance
569
696
  if (key in target && (isReadonlyProperty(target, key) || isClassInstance(target[key]))) {
@@ -575,11 +702,8 @@ export function assignGingerly(
575
702
  // Recursively apply assignGingerly to the readonly object or class instance
576
703
  assignGingerly(currentValue, value, options);
577
704
  } else {
578
- // Property is writable and not a class instance - normal recursive merge
579
- if (!(key in target) || typeof target[key] !== 'object') {
580
- target[key] = {};
581
- }
582
- assignGingerly(target[key], value, options);
705
+ // Property is writable and not a class instance - replace it
706
+ target[key] = value;
583
707
  }
584
708
  } else {
585
709
  target[key] = value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
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": {
@@ -63,9 +63,9 @@
63
63
  "chrome": "npx playwright cr http://localhost:8000"
64
64
  },
65
65
  "devDependencies": {
66
- "@playwright/test": "1.59.0-alpha-2026-02-28",
66
+ "@playwright/test": "1.59.1",
67
67
  "spa-ssi": "0.0.27",
68
- "@types/node": "25.5.0",
68
+ "@types/node": "25.5.2",
69
69
  "typescript": "6.0.2"
70
70
  }
71
71
  }
@@ -34,15 +34,15 @@ export default defineConfig({
34
34
  use: { ...devices['Desktop Chrome'] },
35
35
  },
36
36
 
37
- // {
38
- // name: 'firefox',
39
- // use: { ...devices['Desktop Firefox'] },
40
- // },
37
+ {
38
+ name: 'firefox',
39
+ use: { ...devices['Desktop Firefox'] },
40
+ },
41
41
 
42
- // {
43
- // name: 'webkit',
44
- // use: { ...devices['Desktop Safari'] },
45
- // },
42
+ {
43
+ name: 'webkit',
44
+ use: { ...devices['Desktop Safari'] },
45
+ },
46
46
  ],
47
47
 
48
48
  /* Run your local dev server before starting the tests */
@@ -169,6 +169,7 @@ export type IEnhancementRegistryItem<T = any> = EnhancementConfig<T>;
169
169
  export interface IAssignGingerlyOptions {
170
170
  registry?: typeof EnhancementRegistry | EnhancementRegistry;
171
171
  bypassChecks?: boolean;
172
+ withMethods?: string[] | Set<string>;
172
173
  }
173
174
 
174
175
  /**