@fundamental-ngx/core 0.61.3 → 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.
- package/fesm2022/fundamental-ngx-core-breadcrumb.mjs +1 -1
- package/fesm2022/fundamental-ngx-core-breadcrumb.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-carousel.mjs +7 -8
- package/fesm2022/fundamental-ngx-core-carousel.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-form.mjs +10 -16
- package/fesm2022/fundamental-ngx-core-form.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-icon.mjs +2 -2
- package/fesm2022/fundamental-ngx-core-icon.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-inline-help.mjs +34 -15
- package/fesm2022/fundamental-ngx-core-inline-help.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-menu.mjs +40 -29
- package/fesm2022/fundamental-ngx-core-menu.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-multi-combobox.mjs +75 -31
- package/fesm2022/fundamental-ngx-core-multi-combobox.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-popover.mjs +152 -16
- package/fesm2022/fundamental-ngx-core-popover.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-product-switch.mjs +23 -27
- package/fesm2022/fundamental-ngx-core-product-switch.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-select.mjs +12 -3
- package/fesm2022/fundamental-ngx-core-select.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-shellbar.mjs +2 -2
- package/fesm2022/fundamental-ngx-core-shellbar.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-tabs.mjs +19 -25
- package/fesm2022/fundamental-ngx-core-tabs.mjs.map +1 -1
- package/package.json +3 -3
- package/types/fundamental-ngx-core-carousel.d.ts +2 -3
- package/types/fundamental-ngx-core-form.d.ts +7 -10
- package/types/fundamental-ngx-core-inline-help.d.ts +20 -15
- package/types/fundamental-ngx-core-menu.d.ts +17 -9
- package/types/fundamental-ngx-core-mobile-mode.d.ts +6 -2
- package/types/fundamental-ngx-core-multi-combobox.d.ts +18 -1
- package/types/fundamental-ngx-core-popover.d.ts +48 -3
- package/types/fundamental-ngx-core-product-switch.d.ts +20 -24
- package/types/fundamental-ngx-core-select.d.ts +5 -0
- 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.
|
|
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.
|
|
752
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|