assign-gingerly 0.0.43 → 0.0.44

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
@@ -4790,6 +4790,7 @@ await customElements.assignFeatures(MyElement, { feature: { spawn: SyncFeature }
4790
4790
  | [truth-sourcer](https://www.npmjs.com/package/truth-sourcer) | Attribute/property binding and truth-sourcing for custom elements | [GitHub](https://github.com/bahrus/truth-sourcer) |
4791
4791
  | [be-reflective](https://www.npmjs.com/package/be-reflective) | CSS custom state reflection from computed styles | [GitHub](https://github.com/bahrus/be-reflective) |
4792
4792
  | [face-up](https://www.npmjs.com/package/face-up) | Form Associated Custom Element behavior via ElementInternals | [GitHub](https://github.com/bahrus/face-up) |
4793
+ | [roundabout](https://www.npmjs.com/package/roundabout) | Reactive view-model binding with template rendering and computed property orchestration | [GitHub](https://github.com/bahrus/roundabout#using-roundaboutfeature-with-assignfeatures) |
4793
4794
  | [time-ticker](https://www.npmjs.com/package/time-ticker) | Web component that fires events periodically (example of a feature-based component with no code in the class) | [GitHub](https://github.com/bahrus/time-ticker) |
4794
4795
 
4795
4796
  </details>
package/assignFeatures.js CHANGED
@@ -479,6 +479,86 @@ export function captureFeatureInitVals(instance) {
479
479
  }
480
480
  }
481
481
  }
482
+ /**
483
+ * Global store for inter-feature suggestions.
484
+ * Structure: Map<targetSymbol, Map<targetClass, Array<FeatureInfoSuggestion>>>
485
+ * Scoped per target class to prevent leaking between different custom elements.
486
+ */
487
+ const featureInfoSuggestions = new Map();
488
+ /**
489
+ * Suggest configuration to another feature during registration.
490
+ *
491
+ * Call this in a feature's `static onAssigned` to provide config fragments
492
+ * (withAttrs, customData) that another feature should merge into its own config.
493
+ *
494
+ * The target feature is identified by a Symbol (stable across versions and mocks).
495
+ * Suggestions are scoped per target class to prevent leaking between different
496
+ * custom elements that use the same features.
497
+ *
498
+ * @param fromFeatureCtr - The feature class making the suggestion (for tracing)
499
+ * @param toFeatureSymbol - Symbol identifying the target feature
500
+ * @param featureInfo - Config fragments to suggest (withAttrs, customData)
501
+ * @param targetClass - The custom element class being configured
502
+ *
503
+ * @example
504
+ * import { suggestFeatureInfo } from 'assign-gingerly/assignFeatures.js';
505
+ * import { ROUNDABOUT_FEATURE } from 'roundabout/symbols.js';
506
+ *
507
+ * class FaceUp {
508
+ * static onAssigned(ctr, featureConfig) {
509
+ * suggestFeatureInfo(FaceUp, ROUNDABOUT_FEATURE, {
510
+ * customData: { formBindings: { value: 'value' } }
511
+ * }, ctr);
512
+ * }
513
+ * }
514
+ */
515
+ export function suggestFeatureInfo(fromFeatureCtr, toFeatureSymbol, featureInfo, targetClass) {
516
+ let symbolMap = featureInfoSuggestions.get(toFeatureSymbol);
517
+ if (!symbolMap) {
518
+ symbolMap = new Map();
519
+ featureInfoSuggestions.set(toFeatureSymbol, symbolMap);
520
+ }
521
+ let suggestions = symbolMap.get(targetClass);
522
+ if (!suggestions) {
523
+ suggestions = [];
524
+ symbolMap.set(targetClass, suggestions);
525
+ }
526
+ suggestions.push({
527
+ from: fromFeatureCtr,
528
+ ...featureInfo
529
+ });
530
+ }
531
+ /**
532
+ * Retrieve suggestions made to a feature by other features.
533
+ *
534
+ * Call this in a feature's `static onAssigned` to read config fragments
535
+ * suggested by other features that were processed earlier.
536
+ *
537
+ * @param toFeatureSymbol - Symbol identifying this feature (the target)
538
+ * @param targetClass - The custom element class being configured
539
+ * @returns Array of suggestions (empty if none)
540
+ *
541
+ * @example
542
+ * import { getFeatureInfoSuggestions } from 'assign-gingerly/assignFeatures.js';
543
+ * import { ROUNDABOUT_FEATURE } from './symbols.js';
544
+ *
545
+ * class RoundaboutFeature {
546
+ * static onAssigned(ctr, featureConfig) {
547
+ * const suggestions = getFeatureInfoSuggestions(ROUNDABOUT_FEATURE, ctr);
548
+ * for (const suggestion of suggestions) {
549
+ * if (suggestion.customData) {
550
+ * featureConfig.customData = { ...featureConfig.customData, ...suggestion.customData };
551
+ * }
552
+ * }
553
+ * }
554
+ * }
555
+ */
556
+ export function getFeatureInfoSuggestions(toFeatureSymbol, targetClass) {
557
+ const symbolMap = featureInfoSuggestions.get(toFeatureSymbol);
558
+ if (!symbolMap)
559
+ return [];
560
+ return symbolMap.get(targetClass) || [];
561
+ }
482
562
  // =============================================================================
483
563
  // PropertyBag — base class for nested feature containers
484
564
  // =============================================================================
package/assignFeatures.ts CHANGED
@@ -731,6 +731,114 @@ export function captureFeatureInitVals(instance: any): void {
731
731
  }
732
732
  }
733
733
 
734
+ // =============================================================================
735
+ // Inter-feature communication: suggestFeatureInfo / getFeatureInfoSuggestions
736
+ // =============================================================================
737
+
738
+ /**
739
+ * A suggestion from one feature to another, containing config fragments to merge.
740
+ */
741
+ export interface FeatureInfoSuggestion {
742
+ /** The feature class that made the suggestion (for debugging/tracing) */
743
+ from: Function;
744
+ /** Attribute patterns to merge into the target feature's withAttrs */
745
+ withAttrs?: any;
746
+ /** Custom data to merge into the target feature's customData */
747
+ customData?: any;
748
+ }
749
+
750
+ /**
751
+ * Global store for inter-feature suggestions.
752
+ * Structure: Map<targetSymbol, Map<targetClass, Array<FeatureInfoSuggestion>>>
753
+ * Scoped per target class to prevent leaking between different custom elements.
754
+ */
755
+ const featureInfoSuggestions = new Map<symbol, Map<Function, FeatureInfoSuggestion[]>>();
756
+
757
+ /**
758
+ * Suggest configuration to another feature during registration.
759
+ *
760
+ * Call this in a feature's `static onAssigned` to provide config fragments
761
+ * (withAttrs, customData) that another feature should merge into its own config.
762
+ *
763
+ * The target feature is identified by a Symbol (stable across versions and mocks).
764
+ * Suggestions are scoped per target class to prevent leaking between different
765
+ * custom elements that use the same features.
766
+ *
767
+ * @param fromFeatureCtr - The feature class making the suggestion (for tracing)
768
+ * @param toFeatureSymbol - Symbol identifying the target feature
769
+ * @param featureInfo - Config fragments to suggest (withAttrs, customData)
770
+ * @param targetClass - The custom element class being configured
771
+ *
772
+ * @example
773
+ * import { suggestFeatureInfo } from 'assign-gingerly/assignFeatures.js';
774
+ * import { ROUNDABOUT_FEATURE } from 'roundabout/symbols.js';
775
+ *
776
+ * class FaceUp {
777
+ * static onAssigned(ctr, featureConfig) {
778
+ * suggestFeatureInfo(FaceUp, ROUNDABOUT_FEATURE, {
779
+ * customData: { formBindings: { value: 'value' } }
780
+ * }, ctr);
781
+ * }
782
+ * }
783
+ */
784
+ export function suggestFeatureInfo(
785
+ fromFeatureCtr: Function,
786
+ toFeatureSymbol: symbol,
787
+ featureInfo: { withAttrs?: any; customData?: any },
788
+ targetClass: Function
789
+ ): void {
790
+ let symbolMap = featureInfoSuggestions.get(toFeatureSymbol);
791
+ if (!symbolMap) {
792
+ symbolMap = new Map();
793
+ featureInfoSuggestions.set(toFeatureSymbol, symbolMap);
794
+ }
795
+
796
+ let suggestions = symbolMap.get(targetClass);
797
+ if (!suggestions) {
798
+ suggestions = [];
799
+ symbolMap.set(targetClass, suggestions);
800
+ }
801
+
802
+ suggestions.push({
803
+ from: fromFeatureCtr,
804
+ ...featureInfo
805
+ });
806
+ }
807
+
808
+ /**
809
+ * Retrieve suggestions made to a feature by other features.
810
+ *
811
+ * Call this in a feature's `static onAssigned` to read config fragments
812
+ * suggested by other features that were processed earlier.
813
+ *
814
+ * @param toFeatureSymbol - Symbol identifying this feature (the target)
815
+ * @param targetClass - The custom element class being configured
816
+ * @returns Array of suggestions (empty if none)
817
+ *
818
+ * @example
819
+ * import { getFeatureInfoSuggestions } from 'assign-gingerly/assignFeatures.js';
820
+ * import { ROUNDABOUT_FEATURE } from './symbols.js';
821
+ *
822
+ * class RoundaboutFeature {
823
+ * static onAssigned(ctr, featureConfig) {
824
+ * const suggestions = getFeatureInfoSuggestions(ROUNDABOUT_FEATURE, ctr);
825
+ * for (const suggestion of suggestions) {
826
+ * if (suggestion.customData) {
827
+ * featureConfig.customData = { ...featureConfig.customData, ...suggestion.customData };
828
+ * }
829
+ * }
830
+ * }
831
+ * }
832
+ */
833
+ export function getFeatureInfoSuggestions(
834
+ toFeatureSymbol: symbol,
835
+ targetClass: Function
836
+ ): FeatureInfoSuggestion[] {
837
+ const symbolMap = featureInfoSuggestions.get(toFeatureSymbol);
838
+ if (!symbolMap) return [];
839
+ return symbolMap.get(targetClass) || [];
840
+ }
841
+
734
842
  // =============================================================================
735
843
  // PropertyBag — base class for nested feature containers
736
844
  // =============================================================================
package/index.js CHANGED
@@ -9,6 +9,6 @@ export { resolveTemplate } from './resolveTemplate.js';
9
9
  export { getHost } from './getHost.js';
10
10
  export { resolveValues, resolveValue } from './resolveValues.js';
11
11
  export { assignFrom } from './assignFrom.js';
12
- export { assignFeatures, FeaturesRegistry, captureFeatureInitVals, PropertyBag } from './assignFeatures.js';
12
+ export { assignFeatures, FeaturesRegistry, captureFeatureInitVals, PropertyBag, suggestFeatureInfo, getFeatureInfoSuggestions } from './assignFeatures.js';
13
13
  export { installForwarding } from './installForwarding.js';
14
14
  import './object-extension.js';
package/index.ts CHANGED
@@ -9,6 +9,6 @@ export {resolveTemplate} from './resolveTemplate.js';
9
9
  export {getHost} from './getHost.js';
10
10
  export {resolveValues, resolveValue} from './resolveValues.js';
11
11
  export {assignFrom} from './assignFrom.js';
12
- export {assignFeatures, FeaturesRegistry, captureFeatureInitVals, PropertyBag} from './assignFeatures.js';
12
+ export {assignFeatures, FeaturesRegistry, captureFeatureInitVals, PropertyBag, suggestFeatureInfo, getFeatureInfoSuggestions} from './assignFeatures.js';
13
13
  export {installForwarding} from './installForwarding.js';
14
14
  import './object-extension.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.43",
3
+ "version": "0.0.44",
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": {
@@ -89,7 +89,7 @@
89
89
  "devDependencies": {
90
90
  "@playwright/test": "1.60.0",
91
91
  "spa-ssi": "0.0.27",
92
- "@types/node": "25.7.0",
92
+ "@types/node": "25.8.0",
93
93
  "typescript": "6.0.3"
94
94
  }
95
95
  }