@fundamental-ngx/core 0.61.4 → 0.61.5

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.
Files changed (31) hide show
  1. package/fesm2022/fundamental-ngx-core-breadcrumb.mjs +1 -1
  2. package/fesm2022/fundamental-ngx-core-breadcrumb.mjs.map +1 -1
  3. package/fesm2022/fundamental-ngx-core-carousel.mjs +7 -8
  4. package/fesm2022/fundamental-ngx-core-carousel.mjs.map +1 -1
  5. package/fesm2022/fundamental-ngx-core-form.mjs +10 -16
  6. package/fesm2022/fundamental-ngx-core-form.mjs.map +1 -1
  7. package/fesm2022/fundamental-ngx-core-icon.mjs +2 -2
  8. package/fesm2022/fundamental-ngx-core-icon.mjs.map +1 -1
  9. package/fesm2022/fundamental-ngx-core-inline-help.mjs +34 -15
  10. package/fesm2022/fundamental-ngx-core-inline-help.mjs.map +1 -1
  11. package/fesm2022/fundamental-ngx-core-menu.mjs +40 -28
  12. package/fesm2022/fundamental-ngx-core-menu.mjs.map +1 -1
  13. package/fesm2022/fundamental-ngx-core-multi-combobox.mjs +75 -31
  14. package/fesm2022/fundamental-ngx-core-multi-combobox.mjs.map +1 -1
  15. package/fesm2022/fundamental-ngx-core-popover.mjs +152 -16
  16. package/fesm2022/fundamental-ngx-core-popover.mjs.map +1 -1
  17. package/fesm2022/fundamental-ngx-core-product-switch.mjs +23 -27
  18. package/fesm2022/fundamental-ngx-core-product-switch.mjs.map +1 -1
  19. package/fesm2022/fundamental-ngx-core-shellbar.mjs +2 -2
  20. package/fesm2022/fundamental-ngx-core-shellbar.mjs.map +1 -1
  21. package/fesm2022/fundamental-ngx-core-tabs.mjs +19 -25
  22. package/fesm2022/fundamental-ngx-core-tabs.mjs.map +1 -1
  23. package/package.json +3 -3
  24. package/types/fundamental-ngx-core-carousel.d.ts +2 -3
  25. package/types/fundamental-ngx-core-form.d.ts +7 -10
  26. package/types/fundamental-ngx-core-inline-help.d.ts +20 -15
  27. package/types/fundamental-ngx-core-menu.d.ts +17 -9
  28. package/types/fundamental-ngx-core-multi-combobox.d.ts +18 -1
  29. package/types/fundamental-ngx-core-popover.d.ts +48 -3
  30. package/types/fundamental-ngx-core-product-switch.d.ts +20 -24
  31. package/types/fundamental-ngx-core-tabs.d.ts +5 -10
@@ -608,6 +608,10 @@ class PopoverService {
608
608
  this._placementRefresh$ = new Subject();
609
609
  /** @hidden */
610
610
  this._ignoreTriggers = false;
611
+ /** @hidden Timer for delayed hover close, allows mouse to travel from trigger to overlay body. */
612
+ this._hoverCloseTimer = null;
613
+ /** @hidden Listeners for mouseenter/mouseleave on the overlay element. */
614
+ this._overlayHoverListeners = [];
611
615
  /** @hidden */
612
616
  this._modalBodyClass = 'fd-overlay-active';
613
617
  /** @hidden */
@@ -676,6 +680,8 @@ class PopoverService {
676
680
  }
677
681
  });
678
682
  this._destroyRef.onDestroy(() => {
683
+ this._clearHoverCloseTimer();
684
+ this._removeOverlayHoverListeners();
679
685
  this._removeTriggerListeners();
680
686
  if (this._overlayRef) {
681
687
  this._overlayRef.detach();
@@ -717,12 +723,12 @@ class PopoverService {
717
723
  }
718
724
  /** Closes the popover. */
719
725
  close(focusActiveElement = true) {
726
+ this._clearHoverCloseTimer();
727
+ this._removeOverlayHoverListeners();
720
728
  if (this._overlayRef) {
721
729
  this._overlayRef.dispose();
722
730
  }
723
- this._programmaticChange = true;
724
- this.isOpen.set(false);
725
- this._programmaticChange = false;
731
+ this._safeSetIsOpen(false);
726
732
  this.checkModalBackground();
727
733
  this._focusLastActiveElementBeforeOpen(focusActiveElement);
728
734
  }
@@ -748,9 +754,8 @@ class PopoverService {
748
754
  if (this.fillControlMode()) {
749
755
  this._listenOnResize();
750
756
  }
751
- this._programmaticChange = true;
752
- this.isOpen.set(true);
753
- this._programmaticChange = false;
757
+ this._safeSetIsOpen(true);
758
+ this._setupOverlayHoverListeners();
754
759
  this._listenOnClose();
755
760
  this._focusFirstTabbableElement();
756
761
  this._onLoad.next(this._getPopoverBody()._elementRef);
@@ -923,6 +928,7 @@ class PopoverService {
923
928
  }
924
929
  this._removeTriggerListeners();
925
930
  if (this.triggers()?.length) {
931
+ const hasHoverTrigger = this._hasHoverTriggers();
926
932
  this._normalizeTriggers().forEach((trigger) => {
927
933
  this._eventRef.push(this._renderer.listen(this._triggerHtmlElement, trigger.trigger, (event) => {
928
934
  if (this._ignoreTriggers || this.disabled()) {
@@ -930,7 +936,20 @@ class PopoverService {
930
936
  }
931
937
  const closeAction = !!trigger.closeAction;
932
938
  const openAction = !!trigger.openAction;
933
- this.toggle(openAction, closeAction);
939
+ // For hover triggers, use delayed close to prevent flickering
940
+ // when the mouse travels between the trigger and the popover body.
941
+ if (hasHoverTrigger && trigger.trigger === 'mouseenter' && openAction) {
942
+ this._clearHoverCloseTimer();
943
+ if (!this.isOpen()) {
944
+ this.toggle(openAction, closeAction);
945
+ }
946
+ }
947
+ else if (hasHoverTrigger && trigger.trigger === 'mouseleave' && closeAction) {
948
+ this._scheduleHoverClose();
949
+ }
950
+ else {
951
+ this.toggle(openAction, closeAction);
952
+ }
934
953
  if (trigger.stopPropagation) {
935
954
  event.stopImmediatePropagation();
936
955
  }
@@ -946,6 +965,73 @@ class PopoverService {
946
965
  this._triggerElement = trigger;
947
966
  this._refreshTriggerListeners();
948
967
  }
968
+ /**
969
+ * @hidden
970
+ * Safely sets isOpen signal, deferring if called during Angular's render cycle.
971
+ * This prevents NG0600 errors when focus events fire during template evaluation.
972
+ */
973
+ _safeSetIsOpen(value) {
974
+ this._programmaticChange = true;
975
+ try {
976
+ this.isOpen.set(value);
977
+ this._programmaticChange = false;
978
+ }
979
+ catch (e) {
980
+ this._programmaticChange = false;
981
+ // NG0600: Writing to signals during render - defer to next macrotask
982
+ if (e instanceof Error && e.message?.includes('NG0600')) {
983
+ setTimeout(() => {
984
+ this._programmaticChange = true;
985
+ this.isOpen.set(value);
986
+ this._programmaticChange = false;
987
+ });
988
+ }
989
+ else {
990
+ throw e;
991
+ }
992
+ }
993
+ }
994
+ /** @hidden Whether the current triggers include mouseenter or mouseleave. */
995
+ _hasHoverTriggers() {
996
+ return this.triggers().some((t) => {
997
+ const name = typeof t === 'string' ? t : t.trigger;
998
+ return name === 'mouseenter' || name === 'mouseleave';
999
+ });
1000
+ }
1001
+ /** @hidden Clears any pending delayed hover close. */
1002
+ _clearHoverCloseTimer() {
1003
+ if (this._hoverCloseTimer !== null) {
1004
+ clearTimeout(this._hoverCloseTimer);
1005
+ this._hoverCloseTimer = null;
1006
+ }
1007
+ }
1008
+ /** @hidden Schedules a delayed close to allow mouse to travel from trigger to overlay body. */
1009
+ _scheduleHoverClose() {
1010
+ this._clearHoverCloseTimer();
1011
+ this._hoverCloseTimer = setTimeout(() => {
1012
+ this._hoverCloseTimer = null;
1013
+ this.close();
1014
+ }, 50);
1015
+ }
1016
+ /** @hidden Adds mouseenter/mouseleave listeners on the overlay element when hover triggers are active. */
1017
+ _setupOverlayHoverListeners() {
1018
+ this._removeOverlayHoverListeners();
1019
+ if (!this._hasHoverTriggers() || !this._overlayRef) {
1020
+ return;
1021
+ }
1022
+ const overlayElement = this._overlayRef.overlayElement;
1023
+ this._overlayHoverListeners.push(this._renderer.listen(overlayElement, 'mouseenter', () => {
1024
+ this._clearHoverCloseTimer();
1025
+ }));
1026
+ this._overlayHoverListeners.push(this._renderer.listen(overlayElement, 'mouseleave', () => {
1027
+ this._scheduleHoverClose();
1028
+ }));
1029
+ }
1030
+ /** @hidden Removes overlay hover listeners. */
1031
+ _removeOverlayHoverListeners() {
1032
+ this._overlayHoverListeners.forEach((fn) => fn());
1033
+ this._overlayHoverListeners = [];
1034
+ }
949
1035
  /** @hidden */
950
1036
  _normalizeTriggers() {
951
1037
  return this.triggers().map((trigger, index) => {
@@ -1034,7 +1120,7 @@ class PopoverService {
1034
1120
  /** Subscribe to close events from CDK Overlay, to throw proper events, change values */
1035
1121
  _listenOnClose() {
1036
1122
  const body = this._getPopoverBody();
1037
- const closeEvents$ = merge(this._overlayRef.detachments(), outputToObservable(body.onClose), this._outsideClicks$());
1123
+ const closeEvents$ = merge(this._overlayRef.detachments(), outputToObservable(body.onClose), this._outsideClicks$(), this._escapeKeydowns$());
1038
1124
  // Only use _stopCloseListening$ to stop listening, not _refresh$
1039
1125
  // _refresh$ can emit due to signal effects and would complete the subscription prematurely
1040
1126
  closeEvents$.pipe(takeUntil(this._stopCloseListening$), takeUntilDestroyed(this._destroyRef)).subscribe(() => {
@@ -1045,6 +1131,12 @@ class PopoverService {
1045
1131
  _outsideClicks$() {
1046
1132
  return merge(this._overlayRef.backdropClick(), this._overlayRef._outsidePointerEvents).pipe(filter((event) => this._shouldClose(event)));
1047
1133
  }
1134
+ /** Listener for Escape keydown via CDK overlay's document-level keyboard dispatcher. */
1135
+ _escapeKeydowns$() {
1136
+ return this._overlayRef
1137
+ .keydownEvents()
1138
+ .pipe(filter((event) => this.closeOnEscapeKey() && event.key === 'Escape'));
1139
+ }
1048
1140
  /** @hidden */
1049
1141
  _shouldClose(event) {
1050
1142
  return (this.isOpen() &&
@@ -1381,8 +1473,9 @@ class PopoverComponent {
1381
1473
  if (currentIsOpen !== previousIsOpen) {
1382
1474
  this.isOpenChange.emit(currentIsOpen);
1383
1475
  }
1384
- // Sync to service only if not already syncing (prevents loop)
1385
- if (!this._syncingIsOpen) {
1476
+ // Sync to service only if not already syncing and not in mobile mode (prevents loop).
1477
+ // In mobile mode, the dialog handles open/close — the popover service should not be involved.
1478
+ if (!this._syncingIsOpen && !this.mobile()) {
1386
1479
  this._syncingIsOpen = true;
1387
1480
  this._popoverService.isOpen.set(currentIsOpen);
1388
1481
  this._syncingIsOpen = false;
@@ -1396,6 +1489,10 @@ class PopoverComponent {
1396
1489
  this._popoverService.isOpenChange
1397
1490
  .pipe(takeUntilDestroyed(this._destroyRef))
1398
1491
  .subscribe((serviceIsOpen) => {
1492
+ // In mobile mode, the dialog handles open/close — ignore service changes.
1493
+ if (this.mobile()) {
1494
+ return;
1495
+ }
1399
1496
  // Only update if different and not already syncing (prevents loop)
1400
1497
  if (!this._syncingIsOpen && this.isOpen() !== serviceIsOpen) {
1401
1498
  this._syncingIsOpen = true;
@@ -1411,8 +1508,9 @@ class PopoverComponent {
1411
1508
  const triggerValue = this.trigger();
1412
1509
  // Always sync disabled state to service (for both trigger directive and control usage)
1413
1510
  this._popoverService.disabled.set(effectiveConfig.disabled);
1414
- // Full sync only when trigger is set (for fdPopoverTrigger directive)
1415
- if (triggerValue) {
1511
+ // Full sync only when trigger is set and not in mobile mode (for fdPopoverTrigger directive).
1512
+ // In mobile mode, the dialog handles open/close — the popover service should not be involved.
1513
+ if (triggerValue && !this.mobile()) {
1416
1514
  this._syncToService(effectiveConfig, triggerValue);
1417
1515
  }
1418
1516
  });
@@ -1444,11 +1542,23 @@ class PopoverComponent {
1444
1542
  }
1445
1543
  /** Opens the popover. */
1446
1544
  open() {
1447
- this._popoverService.open();
1545
+ // In mobile mode, just update the signal — the mobile component's effect handles the dialog.
1546
+ if (this.mobile()) {
1547
+ this.isOpen.set(true);
1548
+ }
1549
+ else {
1550
+ this._popoverService.open();
1551
+ }
1448
1552
  }
1449
1553
  /** Closes the popover. */
1450
1554
  close(focusActiveElement = true) {
1451
- this._popoverService.close(focusActiveElement);
1555
+ // In mobile mode, just update the signal — the mobile component's effect handles the dialog.
1556
+ if (this.mobile()) {
1557
+ this.isOpen.set(false);
1558
+ }
1559
+ else {
1560
+ this._popoverService.close(focusActiveElement);
1561
+ }
1452
1562
  }
1453
1563
  /** Temporary sets the ignoring of the event triggers. */
1454
1564
  setIgnoreTriggers(ignore) {
@@ -1496,7 +1606,7 @@ class PopoverComponent {
1496
1606
  !activeElement?.classList.contains(SELECT_CLASS_NAMES.selectControl)) {
1497
1607
  // prevent page scrolling on Space keydown
1498
1608
  event.preventDefault();
1499
- this._popoverService.toggle();
1609
+ this.toggle();
1500
1610
  }
1501
1611
  }
1502
1612
  /** @hidden - Sync all input signals to the service */
@@ -1689,9 +1799,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
1689
1799
  }]
1690
1800
  }] });
1691
1801
 
1802
+ /**
1803
+ * Builds a computed PopoverConfig from individual signal accessors.
1804
+ * Reads all provided accessors reactively and assembles a config object.
1805
+ *
1806
+ * @example
1807
+ * ```typescript
1808
+ * readonly popoverConfig = buildPopoverConfig({
1809
+ * placement: () => this.placement() ?? 'bottom',
1810
+ * triggers: this.triggers,
1811
+ * disabled: this.disabled,
1812
+ * });
1813
+ * ```
1814
+ */
1815
+ function buildPopoverConfig(signals) {
1816
+ return computed(() => {
1817
+ const config = {};
1818
+ for (const key of Object.keys(signals)) {
1819
+ const accessor = signals[key];
1820
+ if (accessor) {
1821
+ config[key] = accessor();
1822
+ }
1823
+ }
1824
+ return config;
1825
+ });
1826
+ }
1827
+
1692
1828
  /**
1693
1829
  * Generated bundle index. Do not edit.
1694
1830
  */
1695
1831
 
1696
- export { BasePopoverClass, FD_POPOVER_COMPONENT, POPOVER_COMPONENT, PopoverBodyComponent, PopoverBodyDirective, PopoverBodyFooterDirective, PopoverBodyHeaderDirective, PopoverComponent, PopoverContainerDirective, PopoverControlComponent, PopoverMobileComponent, PopoverMobileModule, PopoverModule, PopoverService, PopoverTriggerDirective, SELECT_CLASS_NAMES };
1832
+ export { BasePopoverClass, FD_POPOVER_COMPONENT, POPOVER_COMPONENT, PopoverBodyComponent, PopoverBodyDirective, PopoverBodyFooterDirective, PopoverBodyHeaderDirective, PopoverComponent, PopoverContainerDirective, PopoverControlComponent, PopoverMobileComponent, PopoverMobileModule, PopoverModule, PopoverService, PopoverTriggerDirective, SELECT_CLASS_NAMES, buildPopoverConfig };
1697
1833
  //# sourceMappingURL=fundamental-ngx-core-popover.mjs.map