@fundamental-ngx/core 0.64.4-rc.2 → 0.64.4
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-popover.mjs +142 -55
- package/fesm2022/fundamental-ngx-core-popover.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-core-product-switch.mjs +31 -42
- package/fesm2022/fundamental-ngx-core-product-switch.mjs.map +1 -1
- package/package.json +3 -3
- package/schematics/package.json +1 -1
- package/types/fundamental-ngx-core-popover.d.ts +50 -15
- package/types/fundamental-ngx-core-product-switch.d.ts +10 -19
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, Directive, inject, TemplateRef, HostBinding, input, viewChild, linkedSignal, output, ElementRef, Renderer2, afterNextRender, ChangeDetectionStrategy, ViewEncapsulation, Component, DestroyRef, InjectionToken, effect, ViewChild, NgModule, EventEmitter, Injector, computed, isSignal, Injectable, Input, model, booleanAttribute, ViewContainerRef, contentChild } from '@angular/core';
|
|
2
|
+
import { signal, Directive, inject, TemplateRef, HostBinding, input, viewChild, linkedSignal, output, ElementRef, Renderer2, afterNextRender, ChangeDetectionStrategy, ViewEncapsulation, Component, DestroyRef, InjectionToken, effect, ViewChild, NgModule, EventEmitter, Injector, NgZone, computed, isSignal, Injectable, Input, model, booleanAttribute, ViewContainerRef, contentChild } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/cdk/a11y';
|
|
4
4
|
import { CdkTrapFocus, A11yModule } from '@angular/cdk/a11y';
|
|
5
5
|
import { ESCAPE, DOWN_ARROW } from '@angular/cdk/keycodes';
|
|
@@ -724,8 +724,16 @@ class PopoverService {
|
|
|
724
724
|
this._viewportRuler = inject(ViewportRuler);
|
|
725
725
|
/** @hidden */
|
|
726
726
|
this._injector = inject(Injector);
|
|
727
|
-
/** @hidden Watches
|
|
727
|
+
/** @hidden Watches document.body while open for mutations that may affect the overlay's position. */
|
|
728
728
|
this._ancestorMutationObserver = null;
|
|
729
|
+
/** @hidden Pending layout measurement frame, cancelled when its open session ends. */
|
|
730
|
+
this._layoutShiftAnimationFrame = null;
|
|
731
|
+
/** @hidden Session identity used to reject callbacks retained after close, deactivate or reopen. */
|
|
732
|
+
this._layoutShiftObserverSession = 0;
|
|
733
|
+
/** @hidden Geometry captured after positioning, used to suppress updates for unchanged layout. */
|
|
734
|
+
this._layoutShiftGeometry = null;
|
|
735
|
+
/** @hidden */
|
|
736
|
+
this._ngZone = inject(NgZone);
|
|
729
737
|
/** @hidden Flag to prevent infinite loop when programmatically opening/closing */
|
|
730
738
|
this._programmaticChange = false;
|
|
731
739
|
/** @hidden Computed signal for body-related config to track dependencies */
|
|
@@ -778,8 +786,7 @@ class PopoverService {
|
|
|
778
786
|
this._clearHoverCloseTimer();
|
|
779
787
|
this._removeOverlayHoverListeners();
|
|
780
788
|
this._removeTriggerListeners();
|
|
781
|
-
this.
|
|
782
|
-
this._ancestorMutationObserver = null;
|
|
789
|
+
this._stopLayoutShiftObserver();
|
|
783
790
|
if (this._overlayRef) {
|
|
784
791
|
this._overlayRef.detach();
|
|
785
792
|
this._overlayRef.dispose();
|
|
@@ -820,8 +827,9 @@ class PopoverService {
|
|
|
820
827
|
}
|
|
821
828
|
/** Closes the popover. */
|
|
822
829
|
close(focusActiveElement = true) {
|
|
823
|
-
|
|
824
|
-
this.
|
|
830
|
+
// Disposal emits a detachment; don't close again and override the focus decision.
|
|
831
|
+
this._stopCloseListening$.next();
|
|
832
|
+
this._stopLayoutShiftObserver();
|
|
825
833
|
this._clearHoverCloseTimer();
|
|
826
834
|
this._removeOverlayHoverListeners();
|
|
827
835
|
if (this._overlayRef) {
|
|
@@ -836,7 +844,6 @@ class PopoverService {
|
|
|
836
844
|
if ((!this._overlayRef || !this._overlayRef.hasAttached()) && !this.disabled() && this._triggerElement) {
|
|
837
845
|
const position = this._getPositionStrategy();
|
|
838
846
|
this._overlayRef = this._overlay.create(this._getOverlayConfig(position));
|
|
839
|
-
this._startAncestorResizeObserver();
|
|
840
847
|
if (this._placementContainerValue) {
|
|
841
848
|
const placementElement = this._placementContainerValue instanceof ElementRef
|
|
842
849
|
? this._placementContainerValue.nativeElement
|
|
@@ -861,6 +868,7 @@ class PopoverService {
|
|
|
861
868
|
this._onLoad.next(this._getPopoverBody()._elementRef);
|
|
862
869
|
// Update position - this will trigger the positionChanges observable
|
|
863
870
|
this._overlayRef.updatePosition();
|
|
871
|
+
this._startLayoutShiftObserver();
|
|
864
872
|
}
|
|
865
873
|
}
|
|
866
874
|
/** method updating template or text inside rendered PopoverBody */
|
|
@@ -906,10 +914,12 @@ class PopoverService {
|
|
|
906
914
|
const refPosition = this._getPositionStrategy(positions);
|
|
907
915
|
this._listenForPositionChange(refPosition.positionChanges);
|
|
908
916
|
this._overlayRef.updatePositionStrategy(refPosition);
|
|
917
|
+
this._resetLayoutShiftGeometry();
|
|
909
918
|
}
|
|
910
919
|
/** Method called to refresh position of opened popover */
|
|
911
920
|
refreshPosition() {
|
|
912
921
|
this._overlayRef?.updatePosition();
|
|
922
|
+
this._resetLayoutShiftGeometry();
|
|
913
923
|
}
|
|
914
924
|
/** Temporary sets the ignoring of the event triggers. */
|
|
915
925
|
setIgnoreTriggers(ignore) {
|
|
@@ -1029,6 +1039,7 @@ class PopoverService {
|
|
|
1029
1039
|
* to enable it back in the future by initialise()
|
|
1030
1040
|
*/
|
|
1031
1041
|
deactivate() {
|
|
1042
|
+
this._stopLayoutShiftObserver();
|
|
1032
1043
|
// Close the popover if it's open
|
|
1033
1044
|
if (this.isOpen()) {
|
|
1034
1045
|
this.close();
|
|
@@ -1352,64 +1363,140 @@ class PopoverService {
|
|
|
1352
1363
|
}
|
|
1353
1364
|
/**
|
|
1354
1365
|
* @hidden
|
|
1355
|
-
* Starts a MutationObserver on
|
|
1356
|
-
*
|
|
1357
|
-
*
|
|
1358
|
-
*
|
|
1359
|
-
*
|
|
1360
|
-
* - `
|
|
1361
|
-
*
|
|
1362
|
-
*
|
|
1363
|
-
*
|
|
1364
|
-
*
|
|
1366
|
+
* Starts a MutationObserver on document.body for DOM changes that may invalidate the
|
|
1367
|
+
* overlay position — including mutations in elements outside the trigger's
|
|
1368
|
+
* nearest scroll container (e.g. a collapsible header above a table pushing the trigger down).
|
|
1369
|
+
*
|
|
1370
|
+
* Observed mutation types:
|
|
1371
|
+
* - `childList`: elements added/removed from the DOM (Angular `@if`, ngIf, toast dismissed).
|
|
1372
|
+
* - `attributes` with `attributeFilter: ['style', 'class']`: visibility toggled via CSS.
|
|
1373
|
+
*
|
|
1374
|
+
* Starts after attachment and initial positioning to capture the initial geometry baseline.
|
|
1375
|
+
* The observer and its requestAnimationFrame pipeline are registered outside Angular so
|
|
1376
|
+
* their work does not schedule zone-driven change detection. Own-host mutations are ignored;
|
|
1377
|
+
* other mutations are coalesced into at most one geometry measurement per frame.
|
|
1378
|
+
*
|
|
1379
|
+
* Calls updatePosition() only when the captured origin, overlay size, viewport or fill-mode
|
|
1380
|
+
* trigger size changes, then captures the resulting geometry as the next baseline. This
|
|
1381
|
+
* lets unchanged-layout mutation feedback settle while preserving later genuine shifts.
|
|
1382
|
+
* There is no fixed update limit: a layout that keeps changing remains eligible for one
|
|
1383
|
+
* reposition per frame. Session and overlay identity checks reject stale callbacks.
|
|
1365
1384
|
*/
|
|
1366
|
-
|
|
1367
|
-
const
|
|
1368
|
-
if (!
|
|
1385
|
+
_startLayoutShiftObserver() {
|
|
1386
|
+
const overlayRef = this._overlayRef;
|
|
1387
|
+
if (!this._triggerElement || !overlayRef?.hasAttached()) {
|
|
1369
1388
|
return;
|
|
1370
1389
|
}
|
|
1371
|
-
|
|
1372
|
-
const
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1390
|
+
this._stopLayoutShiftObserver();
|
|
1391
|
+
const session = ++this._layoutShiftObserverSession;
|
|
1392
|
+
this._layoutShiftGeometry = this._getLayoutShiftGeometry(overlayRef);
|
|
1393
|
+
this._ngZone.runOutsideAngular(() => {
|
|
1394
|
+
const scheduleUpdate = (records) => {
|
|
1395
|
+
// Skip self-caused mutations: updatePosition() writes `style` on hostElement
|
|
1396
|
+
// (the CDK position bounding-box) and its descendants. Using hostElement as the
|
|
1397
|
+
// scope boundary covers both the default case (.cdk-overlay-container) and the
|
|
1398
|
+
// placementContainer case where the host is moved outside that container.
|
|
1399
|
+
if (records.every((record) => overlayRef.hostElement.contains(record.target))) {
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
if (this._layoutShiftAnimationFrame !== null) {
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
this._layoutShiftAnimationFrame = window.requestAnimationFrame(() => {
|
|
1406
|
+
if (session !== this._layoutShiftObserverSession || this._overlayRef !== overlayRef) {
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
this._layoutShiftAnimationFrame = null;
|
|
1410
|
+
if (!overlayRef.hasAttached()) {
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
const geometry = this._getLayoutShiftGeometry(overlayRef);
|
|
1414
|
+
if (this._sameLayoutShiftGeometry(this._layoutShiftGeometry, geometry)) {
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
overlayRef.updatePosition();
|
|
1418
|
+
this._layoutShiftGeometry = this._getLayoutShiftGeometry(overlayRef);
|
|
1419
|
+
});
|
|
1420
|
+
};
|
|
1421
|
+
this._ancestorMutationObserver = new MutationObserver(scheduleUpdate);
|
|
1422
|
+
this._ancestorMutationObserver.observe(document.body, {
|
|
1423
|
+
subtree: true,
|
|
1424
|
+
childList: true,
|
|
1425
|
+
attributes: true,
|
|
1426
|
+
attributeFilter: ['style', 'class']
|
|
1388
1427
|
});
|
|
1389
|
-
};
|
|
1390
|
-
this._ancestorMutationObserver = new MutationObserver(scheduleUpdate);
|
|
1391
|
-
const container = this._findScrollableAncestor(trigger);
|
|
1392
|
-
this._ancestorMutationObserver.observe(container, {
|
|
1393
|
-
subtree: true,
|
|
1394
|
-
childList: true,
|
|
1395
|
-
attributes: true,
|
|
1396
|
-
attributeFilter: ['style', 'class']
|
|
1397
1428
|
});
|
|
1398
1429
|
}
|
|
1399
1430
|
/**
|
|
1400
1431
|
* @hidden
|
|
1401
|
-
*
|
|
1432
|
+
* Ends the current observer session, disconnects observation and cancels its pending frame.
|
|
1433
|
+
* Invalidates session identity before clearing geometry so a retained callback cannot
|
|
1434
|
+
* reposition a replacement overlay or clear its pending frame.
|
|
1402
1435
|
*/
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1436
|
+
_stopLayoutShiftObserver() {
|
|
1437
|
+
this._layoutShiftObserverSession++;
|
|
1438
|
+
this._ancestorMutationObserver?.disconnect();
|
|
1439
|
+
this._ancestorMutationObserver = null;
|
|
1440
|
+
if (this._layoutShiftAnimationFrame !== null) {
|
|
1441
|
+
window.cancelAnimationFrame(this._layoutShiftAnimationFrame);
|
|
1442
|
+
this._layoutShiftAnimationFrame = null;
|
|
1443
|
+
}
|
|
1444
|
+
this._layoutShiftGeometry = null;
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* @hidden
|
|
1448
|
+
* Captures the attached overlay's geometry after refreshPosition() or applyNewPosition().
|
|
1449
|
+
* Subsequent mutations are compared against that updated baseline, avoiding a redundant
|
|
1450
|
+
* observer-driven reposition for layout already handled by the explicit operation.
|
|
1451
|
+
*/
|
|
1452
|
+
_resetLayoutShiftGeometry() {
|
|
1453
|
+
if (this._overlayRef?.hasAttached()) {
|
|
1454
|
+
this._layoutShiftGeometry = this._getLayoutShiftGeometry(this._overlayRef);
|
|
1411
1455
|
}
|
|
1412
|
-
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* @hidden
|
|
1459
|
+
* Captures the effective origin (appendTo or trigger), overlay size and viewport geometry.
|
|
1460
|
+
* Includes the trigger's size in fill mode, where it can differ from the positioning origin.
|
|
1461
|
+
* Overlay x/y are positioning outputs and are excluded to avoid treating position writes
|
|
1462
|
+
* as new layout inputs. The snapshot does not track placement-container geometry.
|
|
1463
|
+
*/
|
|
1464
|
+
_getLayoutShiftGeometry(overlayRef) {
|
|
1465
|
+
const origin = this.appendTo() ?? this._triggerHtmlElement;
|
|
1466
|
+
const originElement = origin instanceof ElementRef ? origin.nativeElement : origin;
|
|
1467
|
+
const originRect = originElement.getBoundingClientRect();
|
|
1468
|
+
const overlayRect = overlayRef.overlayElement.getBoundingClientRect();
|
|
1469
|
+
const viewportRect = this._viewportRuler.getViewportRect();
|
|
1470
|
+
const triggerRect = this.fillControlMode() ? this._triggerHtmlElement.getBoundingClientRect() : null;
|
|
1471
|
+
return {
|
|
1472
|
+
originX: originRect.x,
|
|
1473
|
+
originY: originRect.y,
|
|
1474
|
+
originWidth: originRect.width,
|
|
1475
|
+
originHeight: originRect.height,
|
|
1476
|
+
overlayWidth: overlayRect.width,
|
|
1477
|
+
overlayHeight: overlayRect.height,
|
|
1478
|
+
viewportTop: viewportRect.top,
|
|
1479
|
+
viewportLeft: viewportRect.left,
|
|
1480
|
+
viewportWidth: viewportRect.width,
|
|
1481
|
+
viewportHeight: viewportRect.height,
|
|
1482
|
+
triggerWidth: triggerRect?.width ?? null,
|
|
1483
|
+
triggerHeight: triggerRect?.height ?? null
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
/** @hidden Compares captured geometry; a missing previous snapshot requires positioning. */
|
|
1487
|
+
_sameLayoutShiftGeometry(previous, current) {
|
|
1488
|
+
return (previous?.originX === current.originX &&
|
|
1489
|
+
previous.originY === current.originY &&
|
|
1490
|
+
previous.originWidth === current.originWidth &&
|
|
1491
|
+
previous.originHeight === current.originHeight &&
|
|
1492
|
+
previous.overlayWidth === current.overlayWidth &&
|
|
1493
|
+
previous.overlayHeight === current.overlayHeight &&
|
|
1494
|
+
previous.viewportTop === current.viewportTop &&
|
|
1495
|
+
previous.viewportLeft === current.viewportLeft &&
|
|
1496
|
+
previous.viewportWidth === current.viewportWidth &&
|
|
1497
|
+
previous.viewportHeight === current.viewportHeight &&
|
|
1498
|
+
previous.triggerWidth === current.triggerWidth &&
|
|
1499
|
+
previous.triggerHeight === current.triggerHeight);
|
|
1413
1500
|
}
|
|
1414
1501
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: PopoverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1415
1502
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: PopoverService }); }
|