@shival99/z-ui 2.2.6 → 2.3.1

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 (56) hide show
  1. package/assets/css/base.css +4 -0
  2. package/assets/css/overlay.css +82 -0
  3. package/assets/css/tailwind.css +1 -0
  4. package/fesm2022/shival99-z-ui-components-z-button-group.mjs +15 -13
  5. package/fesm2022/shival99-z-ui-components-z-button-group.mjs.map +1 -1
  6. package/fesm2022/shival99-z-ui-components-z-button.mjs +2 -2
  7. package/fesm2022/shival99-z-ui-components-z-button.mjs.map +1 -1
  8. package/fesm2022/shival99-z-ui-components-z-calendar.mjs +23 -17
  9. package/fesm2022/shival99-z-ui-components-z-calendar.mjs.map +1 -1
  10. package/fesm2022/shival99-z-ui-components-z-command.mjs +10 -24
  11. package/fesm2022/shival99-z-ui-components-z-command.mjs.map +1 -1
  12. package/fesm2022/shival99-z-ui-components-z-drawer.mjs +42 -60
  13. package/fesm2022/shival99-z-ui-components-z-drawer.mjs.map +1 -1
  14. package/fesm2022/shival99-z-ui-components-z-gallery.mjs +6 -8
  15. package/fesm2022/shival99-z-ui-components-z-gallery.mjs.map +1 -1
  16. package/fesm2022/shival99-z-ui-components-z-icon.mjs +147 -3
  17. package/fesm2022/shival99-z-ui-components-z-icon.mjs.map +1 -1
  18. package/fesm2022/shival99-z-ui-components-z-marquee.mjs +19 -4
  19. package/fesm2022/shival99-z-ui-components-z-marquee.mjs.map +1 -1
  20. package/fesm2022/shival99-z-ui-components-z-menu.mjs +2 -2
  21. package/fesm2022/shival99-z-ui-components-z-menu.mjs.map +1 -1
  22. package/fesm2022/shival99-z-ui-components-z-modal.mjs +45 -62
  23. package/fesm2022/shival99-z-ui-components-z-modal.mjs.map +1 -1
  24. package/fesm2022/shival99-z-ui-components-z-overlay.mjs +363 -0
  25. package/fesm2022/shival99-z-ui-components-z-overlay.mjs.map +1 -0
  26. package/fesm2022/shival99-z-ui-components-z-popover.mjs +94 -378
  27. package/fesm2022/shival99-z-ui-components-z-popover.mjs.map +1 -1
  28. package/fesm2022/shival99-z-ui-components-z-skeleton.mjs +14 -3
  29. package/fesm2022/shival99-z-ui-components-z-skeleton.mjs.map +1 -1
  30. package/fesm2022/shival99-z-ui-components-z-table.mjs +42 -16
  31. package/fesm2022/shival99-z-ui-components-z-table.mjs.map +1 -1
  32. package/fesm2022/shival99-z-ui-components-z-tabs.mjs +85 -7
  33. package/fesm2022/shival99-z-ui-components-z-tabs.mjs.map +1 -1
  34. package/fesm2022/shival99-z-ui-components-z-toast.mjs +32 -25
  35. package/fesm2022/shival99-z-ui-components-z-toast.mjs.map +1 -1
  36. package/fesm2022/shival99-z-ui-components-z-tooltip.mjs +52 -222
  37. package/fesm2022/shival99-z-ui-components-z-tooltip.mjs.map +1 -1
  38. package/fesm2022/shival99-z-ui-services.mjs +42 -25
  39. package/fesm2022/shival99-z-ui-services.mjs.map +1 -1
  40. package/package.json +9 -1
  41. package/types/shival99-z-ui-components-z-button-group.d.ts +12 -11
  42. package/types/shival99-z-ui-components-z-button.d.ts +2 -1
  43. package/types/shival99-z-ui-components-z-command.d.ts +2 -4
  44. package/types/shival99-z-ui-components-z-drawer.d.ts +11 -13
  45. package/types/shival99-z-ui-components-z-gallery.d.ts +7 -7
  46. package/types/shival99-z-ui-components-z-icon.d.ts +21 -1
  47. package/types/shival99-z-ui-components-z-marquee.d.ts +7 -1
  48. package/types/shival99-z-ui-components-z-modal.d.ts +52 -52
  49. package/types/shival99-z-ui-components-z-overlay.d.ts +145 -0
  50. package/types/shival99-z-ui-components-z-popover.d.ts +7 -15
  51. package/types/shival99-z-ui-components-z-skeleton.d.ts +2 -0
  52. package/types/shival99-z-ui-components-z-table.d.ts +6 -2
  53. package/types/shival99-z-ui-components-z-tabs.d.ts +13 -3
  54. package/types/shival99-z-ui-components-z-toast.d.ts +9 -3
  55. package/types/shival99-z-ui-components-z-tooltip.d.ts +9 -7
  56. package/types/shival99-z-ui-services.d.ts +52 -31
@@ -1,9 +1,8 @@
1
- import { Overlay, OverlayPositionBuilder } from '@angular/cdk/overlay';
2
- import { ComponentPortal } from '@angular/cdk/portal';
3
1
  import { NgTemplateOutlet, DOCUMENT, isPlatformBrowser } from '@angular/common';
4
2
  import * as i0 from '@angular/core';
5
3
  import { signal, computed, ChangeDetectionStrategy, Component, inject, Renderer2, DestroyRef, PLATFORM_ID, viewChild, output, input, ElementRef, Directive } from '@angular/core';
6
4
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { Z_OVERLAY_FALLBACK_ORDER, ZOverlayService, startZOverlayAutoUpdate, computeZOverlayPlacement } from '@shival99/z-ui/components/z-overlay';
7
6
  import { ZOverlayContainerService, ZOverlayZIndexService } from '@shival99/z-ui/services';
8
7
  import { zMergeClasses, zIsMobileViewport, zTransform } from '@shival99/z-ui/utils';
9
8
  import { Subject, switchMap, of, timer, map, filter } from 'rxjs';
@@ -196,85 +195,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
196
195
  }]
197
196
  }] });
198
197
 
199
- const Z_POPOVER_MOBILE_VIEWPORT_MARGIN = 10;
200
198
  function isMobilePopoverViewport() {
201
199
  return zIsMobileViewport();
202
200
  }
203
- function getPopoverOverlaySize(widthConfig = {}) {
204
- return widthConfig;
205
- }
206
- function getPopoverRenderPosition(position, _mobile = isMobilePopoverViewport()) {
207
- return position;
208
- }
209
- function getMobilePopoverFallbackPositions(position, offset) {
210
- return getPopoverFallbackPositions(position, offset).map(pos => ({
211
- ...pos,
212
- offsetX: pos.offsetX ?? 0,
213
- offsetY: pos.offsetY ?? 0,
214
- }));
215
- }
216
- function clampPopoverPosition(value, min, max) {
217
- return Math.min(Math.max(value, min), max);
218
- }
219
- function getAlignedX(position, triggerRect, overlayRect) {
220
- if (position === 'left' || position.startsWith('left-')) {
221
- return triggerRect.left - overlayRect.width;
222
- }
223
- if (position === 'right' || position.startsWith('right-')) {
224
- return triggerRect.right;
225
- }
226
- if (position.endsWith('-right')) {
227
- return triggerRect.right - overlayRect.width;
228
- }
229
- if (position === 'top' || position === 'bottom') {
230
- return triggerRect.left + triggerRect.width / 2 - overlayRect.width / 2;
231
- }
232
- return triggerRect.left;
233
- }
234
- function getAlignedY(position, triggerRect, overlayRect) {
235
- if (position === 'top' || position.startsWith('top-')) {
236
- return triggerRect.top - overlayRect.height;
237
- }
238
- if (position === 'bottom' || position.startsWith('bottom-')) {
239
- return triggerRect.bottom;
240
- }
241
- if (position.endsWith('-bottom')) {
242
- return triggerRect.bottom - overlayRect.height;
243
- }
244
- if (position === 'left' || position === 'right') {
245
- return triggerRect.top + triggerRect.height / 2 - overlayRect.height / 2;
246
- }
247
- return triggerRect.top;
248
- }
249
- function getMobilePopoverPaneStyles(config) {
250
- const { position, triggerRect, overlayRect, offset, viewportWidth } = config;
251
- const viewportMargin = config.viewportMargin ?? Z_POPOVER_MOBILE_VIEWPORT_MARGIN;
252
- const maxLeft = Math.max(viewportMargin, viewportWidth - overlayRect.width - viewportMargin);
253
- let left = getAlignedX(position, triggerRect, overlayRect);
254
- let top = getAlignedY(position, triggerRect, overlayRect);
255
- if (position === 'top' || position.startsWith('top-')) {
256
- top -= offset;
257
- }
258
- if (position === 'bottom' || position.startsWith('bottom-')) {
259
- top += offset;
260
- }
261
- if (position === 'left' || position.startsWith('left-')) {
262
- left -= offset;
263
- }
264
- if (position === 'right' || position.startsWith('right-')) {
265
- left += offset;
266
- }
267
- left = clampPopoverPosition(left, viewportMargin, maxLeft);
268
- return {
269
- top: `${top}px`,
270
- left: `${left}px`,
271
- right: '',
272
- bottom: '',
273
- transform: 'none',
274
- };
275
- }
276
- function getPopoverPushEnabled(_mobile = isMobilePopoverViewport()) {
277
- return false;
201
+ /** Danh sách fallback (không gồm vị trí chính — Floating UI tự thử vị trí chính trước). */
202
+ function getPopoverFallbackPlacements(position) {
203
+ return Z_OVERLAY_FALLBACK_ORDER[position];
278
204
  }
279
205
  function isPopoverTriggerVisibleInViewport(config) {
280
206
  const { triggerRect, viewportWidth, viewportHeight } = config;
@@ -286,172 +212,12 @@ function isPopoverTriggerVisibleInViewport(config) {
286
212
  triggerRect.right > 0 &&
287
213
  triggerRect.left < viewportWidth);
288
214
  }
289
- function createPopoverPosition(position, offset) {
290
- const positions = {
291
- top: {
292
- originX: 'center',
293
- originY: 'top',
294
- overlayX: 'center',
295
- overlayY: 'bottom',
296
- offsetY: -offset,
297
- },
298
- 'top-left': {
299
- originX: 'start',
300
- originY: 'top',
301
- overlayX: 'start',
302
- overlayY: 'bottom',
303
- offsetY: -offset,
304
- },
305
- 'top-right': {
306
- originX: 'end',
307
- originY: 'top',
308
- overlayX: 'end',
309
- overlayY: 'bottom',
310
- offsetY: -offset,
311
- },
312
- bottom: {
313
- originX: 'center',
314
- originY: 'bottom',
315
- overlayX: 'center',
316
- overlayY: 'top',
317
- offsetY: offset,
318
- },
319
- 'bottom-left': {
320
- originX: 'start',
321
- originY: 'bottom',
322
- overlayX: 'start',
323
- overlayY: 'top',
324
- offsetY: offset,
325
- },
326
- 'bottom-right': {
327
- originX: 'end',
328
- originY: 'bottom',
329
- overlayX: 'end',
330
- overlayY: 'top',
331
- offsetY: offset,
332
- },
333
- left: {
334
- originX: 'start',
335
- originY: 'center',
336
- overlayX: 'end',
337
- overlayY: 'center',
338
- offsetX: -offset,
339
- },
340
- 'left-top': {
341
- originX: 'start',
342
- originY: 'top',
343
- overlayX: 'end',
344
- overlayY: 'top',
345
- offsetX: -offset,
346
- },
347
- 'left-bottom': {
348
- originX: 'start',
349
- originY: 'bottom',
350
- overlayX: 'end',
351
- overlayY: 'bottom',
352
- offsetX: -offset,
353
- },
354
- right: {
355
- originX: 'end',
356
- originY: 'center',
357
- overlayX: 'start',
358
- overlayY: 'center',
359
- offsetX: offset,
360
- },
361
- 'right-top': {
362
- originX: 'end',
363
- originY: 'top',
364
- overlayX: 'start',
365
- overlayY: 'top',
366
- offsetX: offset,
367
- },
368
- 'right-bottom': {
369
- originX: 'end',
370
- originY: 'bottom',
371
- overlayX: 'start',
372
- overlayY: 'bottom',
373
- offsetX: offset,
374
- },
375
- };
376
- return positions[position];
377
- }
378
- function getPopoverFallbackPositions(position, offset) {
379
- const fallbackOrder = {
380
- top: ['top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right'],
381
- 'top-left': ['top-left', 'bottom-left', 'top', 'bottom', 'top-right', 'bottom-right'],
382
- 'top-right': ['top-right', 'bottom-right', 'top', 'bottom', 'top-left', 'bottom-left'],
383
- bottom: ['bottom', 'top', 'bottom-left', 'bottom-right', 'top-left', 'top-right'],
384
- 'bottom-left': ['bottom-left', 'top-left', 'bottom', 'top', 'bottom-right', 'top-right'],
385
- 'bottom-right': ['bottom-right', 'top-right', 'bottom', 'top', 'bottom-left', 'top-left'],
386
- left: ['left', 'right', 'left-top', 'left-bottom', 'right-top', 'right-bottom'],
387
- 'left-top': ['left-top', 'right-top', 'left', 'right', 'left-bottom', 'right-bottom'],
388
- 'left-bottom': ['left-bottom', 'right-bottom', 'left', 'right', 'left-top', 'right-top'],
389
- right: ['right', 'left', 'right-top', 'right-bottom', 'left-top', 'left-bottom'],
390
- 'right-top': ['right-top', 'left-top', 'right', 'left', 'right-bottom', 'left-bottom'],
391
- 'right-bottom': ['right-bottom', 'left-bottom', 'right', 'left', 'right-top', 'left-top'],
392
- };
393
- const orderedPositions = fallbackOrder[position] ?? [position];
394
- return orderedPositions.map(pos => createPopoverPosition(pos, offset));
395
- }
396
- function createPopoverPositionStrategy(overlayPositionBuilder, origin, position, offset) {
397
- const mobile = isMobilePopoverViewport();
398
- const positions = mobile
399
- ? getMobilePopoverFallbackPositions(position, offset)
400
- : getPopoverFallbackPositions(position, offset);
401
- return overlayPositionBuilder
402
- .flexibleConnectedTo(origin)
403
- .withPositions(positions)
404
- .withFlexibleDimensions(false)
405
- .withPush(getPopoverPushEnabled(mobile))
406
- .withViewportMargin(10);
407
- }
408
- function getActualPopoverPosition(change) {
409
- const { originX, originY, overlayX, overlayY } = change.connectionPair;
410
- if (overlayY === 'bottom' && originY === 'top') {
411
- if (originX === 'start' && overlayX === 'start') {
412
- return 'top-left';
413
- }
414
- if (originX === 'end' && overlayX === 'end') {
415
- return 'top-right';
416
- }
417
- return 'top';
418
- }
419
- if (overlayY === 'top' && originY === 'bottom') {
420
- if (originX === 'start' && overlayX === 'start') {
421
- return 'bottom-left';
422
- }
423
- if (originX === 'end' && overlayX === 'end') {
424
- return 'bottom-right';
425
- }
426
- return 'bottom';
427
- }
428
- if (overlayX === 'end' && originX === 'start') {
429
- if (originY === 'top' && overlayY === 'top') {
430
- return 'left-top';
431
- }
432
- if (originY === 'bottom' && overlayY === 'bottom') {
433
- return 'left-bottom';
434
- }
435
- return 'left';
436
- }
437
- if (overlayX === 'start' && originX === 'end') {
438
- if (originY === 'top' && overlayY === 'top') {
439
- return 'right-top';
440
- }
441
- if (originY === 'bottom' && overlayY === 'bottom') {
442
- return 'right-bottom';
443
- }
444
- return 'right';
445
- }
446
- return 'top';
447
- }
448
215
 
449
216
  const Z_POPOVER_ANIMATION_DURATION = 200;
450
217
 
451
218
  class ZPopoverComponent {
452
219
  static _openPopovers = new Set();
453
- _overlay = inject(Overlay);
454
- _overlayPositionBuilder = inject(OverlayPositionBuilder);
220
+ _overlayService = inject(ZOverlayService);
455
221
  _renderer = inject(Renderer2);
456
222
  _destroyRef = inject(DestroyRef);
457
223
  _platformId = inject(PLATFORM_ID);
@@ -489,7 +255,7 @@ class ZPopoverComponent {
489
255
  _componentRef = null;
490
256
  _delaySubject = new Subject();
491
257
  _listenerCleanups = [];
492
- _positionSubscription = null;
258
+ _autoUpdateCleanup = null;
493
259
  _scrollSubscription = null;
494
260
  _viewportResizeSubscription = null;
495
261
  _resizeObserver = null;
@@ -570,28 +336,13 @@ class ZPopoverComponent {
570
336
  this._schedulePositionUpdate();
571
337
  }
572
338
  _createOverlay() {
573
- const positionStrategy = createPopoverPositionStrategy(this._overlayPositionBuilder, this._trigger(), this.zPosition(), this.zOffset());
574
- const overlayRef = this._overlay.create({
575
- positionStrategy,
576
- scrollStrategy: this._createScrollStrategy(),
577
- hasBackdrop: false,
578
- ...getPopoverOverlaySize(),
579
- });
339
+ const overlayRef = this._overlayService.createAnchored();
580
340
  this._zIndexService.applyToOverlay(overlayRef);
581
341
  return overlayRef;
582
342
  }
583
- _createScrollStrategy() {
584
- if (this.zScrollClose()) {
585
- return this._overlay.scrollStrategies.close();
586
- }
587
- if (isMobilePopoverViewport() && (this.zSticky() || this.zKeyboardSafe())) {
588
- return this._overlay.scrollStrategies.noop();
589
- }
590
- return this._overlay.scrollStrategies.reposition({ scrollThrottle: 10, autoClose: false });
591
- }
592
343
  _disposeOverlay() {
593
- this._positionSubscription?.unsubscribe();
594
- this._positionSubscription = null;
344
+ this._autoUpdateCleanup?.();
345
+ this._autoUpdateCleanup = null;
595
346
  this._scrollSubscription?.unsubscribe();
596
347
  this._scrollSubscription = null;
597
348
  this._viewportResizeSubscription?.unsubscribe();
@@ -611,7 +362,7 @@ class ZPopoverComponent {
611
362
  .pipe(switchMap(config => (config.delay <= 0 ? of(config) : timer(config.delay).pipe(map(() => config)))), takeUntilDestroyed(this._destroyRef))
612
363
  .subscribe(config => {
613
364
  if (config.show) {
614
- this._showPopover();
365
+ void this._showPopover();
615
366
  return;
616
367
  }
617
368
  this._hidePopover();
@@ -687,7 +438,7 @@ class ZPopoverComponent {
687
438
  ticking = false;
688
439
  return;
689
440
  }
690
- this._updatePositionNow();
441
+ void this._updatePositionNow();
691
442
  ticking = false;
692
443
  });
693
444
  };
@@ -722,7 +473,7 @@ class ZPopoverComponent {
722
473
  },
723
474
  };
724
475
  }
725
- _showPopover() {
476
+ async _showPopover() {
726
477
  const content = this.zContent();
727
478
  if (!content || this._componentRef) {
728
479
  return;
@@ -732,30 +483,24 @@ class ZPopoverComponent {
732
483
  this._setupOutsideClick();
733
484
  }
734
485
  this._setupScrollClose();
735
- const positionStrategy = createPopoverPositionStrategy(this._overlayPositionBuilder, this._trigger(), this.zPosition(), this.zOffset());
736
- this._overlayRef.updatePositionStrategy(positionStrategy);
737
- this._overlayRef.updateSize(getPopoverOverlaySize());
738
- const portal = new ComponentPortal(ZPopoverContentComponent);
739
- this._componentRef = this._overlayRef.attach(portal);
740
- const renderPosition = getPopoverRenderPosition(this.zPosition());
486
+ this._componentRef = this._overlayRef.attach(ZPopoverContentComponent);
741
487
  this._componentRef.instance.setProps({
742
488
  content,
743
- position: renderPosition,
489
+ position: this.zPosition(),
744
490
  customClass: this.zClass(),
745
491
  onClose: () => this.hide(),
746
492
  showArrow: this.zShowArrow(),
747
493
  });
748
- this._positionSubscription = positionStrategy.positionChanges.subscribe(change => {
749
- const actualPosition = getActualPopoverPosition(change);
750
- this._componentRef?.instance.position.set(actualPosition);
751
- this._applyMobilePopoverPosition(actualPosition);
752
- });
494
+ await this._applyPosition();
495
+ if (!this._overlayRef || !this._componentRef) {
496
+ return; // đã bị dispose (hide/destroy) trong lúc chờ computePosition
497
+ }
498
+ this._autoUpdateCleanup = startZOverlayAutoUpdate(this._trigger().nativeElement, this._overlayRef.overlayElement, () => void this._applyPosition(), { ancestorScroll: false });
753
499
  if (this.zTrigger() === 'hover') {
754
500
  this._setupPopoverHover();
755
501
  }
756
502
  this._setupResizeObserver();
757
503
  this._setupViewportResizeReposition();
758
- this._updatePositionAfterContentSettles();
759
504
  this._isVisible.set(true);
760
505
  ZPopoverComponent._openPopovers.add(this);
761
506
  this.zShow.emit();
@@ -792,16 +537,13 @@ class ZPopoverComponent {
792
537
  this._resizeObserver.observe(wrapper);
793
538
  }
794
539
  }
795
- _updatePositionAfterContentSettles() {
796
- this._schedulePositionUpdate();
797
- }
798
540
  _schedulePositionUpdate(force = false) {
799
541
  if (this._positionFrame !== null) {
800
542
  return;
801
543
  }
802
544
  this._positionFrame = requestAnimationFrame(() => {
803
545
  this._positionFrame = null;
804
- this._updatePositionNow(force);
546
+ void this._updatePositionNow(force);
805
547
  });
806
548
  }
807
549
  _scheduleSettledPositionUpdate() {
@@ -821,7 +563,7 @@ class ZPopoverComponent {
821
563
  cancelAnimationFrame(this._positionFrame);
822
564
  this._positionFrame = null;
823
565
  }
824
- _updatePositionNow(force = false) {
566
+ async _updatePositionNow(force = false) {
825
567
  if (!this._overlayRef?.hasAttached()) {
826
568
  return;
827
569
  }
@@ -831,8 +573,30 @@ class ZPopoverComponent {
831
573
  if (!force && this._shouldSkipMobileKeyboardUpdate()) {
832
574
  return;
833
575
  }
834
- this._overlayRef.updatePosition();
835
- this._applyMobilePopoverPosition(this._componentRef?.instance.position() ?? this.zPosition());
576
+ await this._applyPosition();
577
+ }
578
+ async _applyPosition() {
579
+ if (!this._overlayRef || !this._componentRef) {
580
+ return;
581
+ }
582
+ const anchorElement = this._trigger().nativeElement;
583
+ const floatingElement = this._overlayRef.overlayElement;
584
+ const position = this.zPosition();
585
+ const result = await computeZOverlayPlacement({
586
+ reference: anchorElement,
587
+ floating: floatingElement,
588
+ placement: position,
589
+ fallbackPlacements: getPopoverFallbackPlacements(position),
590
+ offset: this.zOffset(),
591
+ push: true,
592
+ viewportPadding: 10,
593
+ });
594
+ if (!this._componentRef) {
595
+ return;
596
+ }
597
+ floatingElement.style.left = `${result.x}px`;
598
+ floatingElement.style.top = `${result.y}px`;
599
+ this._componentRef.instance.position.set(result.placement);
836
600
  }
837
601
  _markViewportSettling() {
838
602
  this._viewportSettlingUntil = Date.now() + 260;
@@ -869,22 +633,6 @@ class ZPopoverComponent {
869
633
  }
870
634
  this._viewportResizeTimeouts = [];
871
635
  }
872
- _applyMobilePopoverPosition(position) {
873
- if (!isMobilePopoverViewport() || !this._overlayRef?.hasAttached()) {
874
- return;
875
- }
876
- const { overlayElement } = this._overlayRef;
877
- const { visualViewport } = window;
878
- const styles = getMobilePopoverPaneStyles({
879
- position,
880
- triggerRect: this._trigger().nativeElement.getBoundingClientRect(),
881
- overlayRect: overlayElement.getBoundingClientRect(),
882
- offset: this.zOffset(),
883
- viewportWidth: visualViewport?.width ?? window.innerWidth,
884
- viewportHeight: visualViewport?.height ?? window.innerHeight,
885
- });
886
- this._setOverlayPaneStyles(styles);
887
- }
888
636
  _isTriggerVisibleInViewport() {
889
637
  const { visualViewport } = window;
890
638
  return isPopoverTriggerVisibleInViewport({
@@ -893,11 +641,6 @@ class ZPopoverComponent {
893
641
  viewportHeight: visualViewport?.height ?? window.innerHeight,
894
642
  });
895
643
  }
896
- _setOverlayPaneStyles(styles) {
897
- for (const key of ['top', 'left', 'right', 'bottom', 'transform']) {
898
- this._renderer.setStyle(this._overlayRef?.overlayElement, key, styles[key]);
899
- }
900
- }
901
644
  _setupPopoverHover() {
902
645
  const overlayElement = this._overlayRef?.overlayElement;
903
646
  if (!overlayElement) {
@@ -916,9 +659,6 @@ class ZPopoverComponent {
916
659
  if (!this._componentRef) {
917
660
  return;
918
661
  }
919
- if (this._hideTimeout) {
920
- return;
921
- }
922
662
  this._componentRef.instance.state.set('closed');
923
663
  this._hideTimeout = setTimeout(() => {
924
664
  this._hideTimeout = null;
@@ -1001,8 +741,7 @@ class ZPopoverDirective {
1001
741
  zOutsideClick = output();
1002
742
  _trigger = computed(() => this.zPopoverTrigger() ?? this.zTrigger(), /* @ts-ignore */
1003
743
  ...(ngDevMode ? [{ debugName: "_trigger" }] : /* istanbul ignore next */ []));
1004
- _overlay = inject(Overlay);
1005
- _overlayPositionBuilder = inject(OverlayPositionBuilder);
744
+ _overlayService = inject(ZOverlayService);
1006
745
  _elementRef = inject((ElementRef));
1007
746
  _renderer = inject(Renderer2);
1008
747
  _destroyRef = inject(DestroyRef);
@@ -1014,7 +753,7 @@ class ZPopoverDirective {
1014
753
  _componentRef = null;
1015
754
  _delaySubject = new Subject();
1016
755
  _listenerCleanups = [];
1017
- _positionSubscription = null;
756
+ _autoUpdateCleanup = null;
1018
757
  _scrollSubscription = null;
1019
758
  _viewportResizeSubscription = null;
1020
759
  _resizeObserver = null;
@@ -1105,36 +844,24 @@ class ZPopoverDirective {
1105
844
  this._schedulePositionUpdate();
1106
845
  }
1107
846
  _createOverlay() {
1108
- const positionStrategy = createPopoverPositionStrategy(this._overlayPositionBuilder, this._getTriggerElement(), this.zPosition(), this.zOffset());
1109
- const widthConfig = this._getWidthConfig();
1110
- const overlayRef = this._overlay.create({
1111
- positionStrategy,
1112
- scrollStrategy: this._createScrollStrategy(),
1113
- hasBackdrop: false,
1114
- ...widthConfig,
1115
- });
847
+ const overlayRef = this._overlayService.createAnchored();
1116
848
  this._zIndexService.applyToOverlay(overlayRef);
1117
- return overlayRef;
1118
- }
1119
- _createScrollStrategy() {
1120
- if (this.zScrollClose()) {
1121
- return this._overlay.scrollStrategies.close();
1122
- }
1123
- if (isMobilePopoverViewport() && (this.zSticky() || this.zKeyboardSafe())) {
1124
- return this._overlay.scrollStrategies.noop();
849
+ const widthConfig = this._getWidthConfig();
850
+ if (widthConfig.width) {
851
+ overlayRef.updateSize(widthConfig);
1125
852
  }
1126
- return this._overlay.scrollStrategies.reposition();
853
+ return overlayRef;
1127
854
  }
1128
855
  _getWidthConfig() {
1129
856
  const width = this.zPopoverWidth();
1130
857
  if (width === 'auto') {
1131
- return getPopoverOverlaySize();
858
+ return {};
1132
859
  }
1133
860
  if (width === 'trigger') {
1134
861
  const triggerWidth = this._getTriggerElement().getBoundingClientRect().width;
1135
- return getPopoverOverlaySize({ width: `${triggerWidth}px`, minWidth: `${triggerWidth}px` });
862
+ return { width: `${triggerWidth}px`, minWidth: `${triggerWidth}px` };
1136
863
  }
1137
- return getPopoverOverlaySize({ width, minWidth: width });
864
+ return { width, minWidth: width };
1138
865
  }
1139
866
  _getTriggerElement() {
1140
867
  const triggerRef = this.zTriggerRef();
@@ -1144,8 +871,8 @@ class ZPopoverDirective {
1144
871
  return triggerRef ?? this._elementRef.nativeElement;
1145
872
  }
1146
873
  _disposeOverlay() {
1147
- this._positionSubscription?.unsubscribe();
1148
- this._positionSubscription = null;
874
+ this._autoUpdateCleanup?.();
875
+ this._autoUpdateCleanup = null;
1149
876
  this._scrollSubscription?.unsubscribe();
1150
877
  this._scrollSubscription = null;
1151
878
  this._viewportResizeSubscription?.unsubscribe();
@@ -1165,7 +892,7 @@ class ZPopoverDirective {
1165
892
  .pipe(switchMap(config => (config.delay <= 0 ? of(config) : timer(config.delay).pipe(map(() => config)))), takeUntilDestroyed(this._destroyRef))
1166
893
  .subscribe(config => {
1167
894
  if (config.show) {
1168
- this._showPopover();
895
+ void this._showPopover();
1169
896
  return;
1170
897
  }
1171
898
  this._hidePopover();
@@ -1256,7 +983,7 @@ class ZPopoverDirective {
1256
983
  ticking = false;
1257
984
  return;
1258
985
  }
1259
- this._updatePositionNow();
986
+ void this._updatePositionNow();
1260
987
  ticking = false;
1261
988
  });
1262
989
  };
@@ -1294,7 +1021,7 @@ class ZPopoverDirective {
1294
1021
  _delay(show, delay) {
1295
1022
  this._delaySubject.next({ show, delay });
1296
1023
  }
1297
- _showPopover() {
1024
+ async _showPopover() {
1298
1025
  const content = this.zContent();
1299
1026
  if (!content) {
1300
1027
  return;
@@ -1316,34 +1043,24 @@ class ZPopoverDirective {
1316
1043
  this._setupOutsideClick();
1317
1044
  }
1318
1045
  this._setupScrollClose();
1319
- const positionStrategy = createPopoverPositionStrategy(this._overlayPositionBuilder, this._getTriggerElement(), this.zPosition(), this.zOffset());
1320
- this._overlayRef.updatePositionStrategy(positionStrategy);
1321
- const widthConfig = this._getWidthConfig();
1322
- if (widthConfig.width) {
1323
- this._overlayRef.updateSize(widthConfig);
1324
- }
1325
- const portal = new ComponentPortal(ZPopoverContentComponent);
1326
- this._componentRef = this._overlayRef.attach(portal);
1327
- const renderPosition = getPopoverRenderPosition(this.zPosition());
1046
+ this._componentRef = this._overlayRef.attach(ZPopoverContentComponent);
1328
1047
  this._componentRef.instance.setProps({
1329
1048
  content,
1330
- position: renderPosition,
1049
+ position: this.zPosition(),
1331
1050
  customClass: this.zClass(),
1332
1051
  onClose: () => this.hide(),
1333
1052
  showArrow: this.zShowArrow(),
1334
1053
  });
1335
- this._positionSubscription = positionStrategy.positionChanges.subscribe(change => {
1336
- const actualPosition = getActualPopoverPosition(change);
1337
- this._componentRef?.instance.position.set(actualPosition);
1338
- this.zPositionChange.emit(actualPosition);
1339
- this._applyMobilePopoverPosition(actualPosition);
1340
- });
1054
+ await this._applyPosition();
1055
+ if (!this._overlayRef || !this._componentRef) {
1056
+ return; // đã bị dispose (hide/destroy) trong lúc chờ computePosition
1057
+ }
1058
+ this._autoUpdateCleanup = startZOverlayAutoUpdate(this._getTriggerElement(), this._overlayRef.overlayElement, () => void this._applyPosition(), { ancestorScroll: false });
1341
1059
  if (this._trigger() === 'hover') {
1342
1060
  this._setupPopoverHover();
1343
1061
  }
1344
1062
  this._setupResizeObserver();
1345
1063
  this._setupViewportResizeReposition();
1346
- this._updatePositionAfterContentSettles();
1347
1064
  this._isVisible.set(true);
1348
1065
  ZPopoverDirective._openPopovers.add(this);
1349
1066
  this._renderer.addClass(this._elementRef.nativeElement, 'z-popover-open');
@@ -1390,16 +1107,13 @@ class ZPopoverDirective {
1390
1107
  this._resizeObserver.observe(wrapper);
1391
1108
  }
1392
1109
  }
1393
- _updatePositionAfterContentSettles() {
1394
- this._schedulePositionUpdate();
1395
- }
1396
1110
  _schedulePositionUpdate(force = false) {
1397
1111
  if (this._positionFrame !== null) {
1398
1112
  return;
1399
1113
  }
1400
1114
  this._positionFrame = requestAnimationFrame(() => {
1401
1115
  this._positionFrame = null;
1402
- this._updatePositionNow(force);
1116
+ void this._updatePositionNow(force);
1403
1117
  });
1404
1118
  }
1405
1119
  _scheduleSettledPositionUpdate() {
@@ -1419,7 +1133,7 @@ class ZPopoverDirective {
1419
1133
  cancelAnimationFrame(this._positionFrame);
1420
1134
  this._positionFrame = null;
1421
1135
  }
1422
- _updatePositionNow(force = false) {
1136
+ async _updatePositionNow(force = false) {
1423
1137
  if (!this._overlayRef?.hasAttached()) {
1424
1138
  return;
1425
1139
  }
@@ -1433,8 +1147,31 @@ class ZPopoverDirective {
1433
1147
  if (widthConfig.width) {
1434
1148
  this._overlayRef.updateSize(widthConfig);
1435
1149
  }
1436
- this._overlayRef.updatePosition();
1437
- this._applyMobilePopoverPosition(this._componentRef?.instance.position() ?? this.zPosition());
1150
+ await this._applyPosition();
1151
+ }
1152
+ async _applyPosition() {
1153
+ if (!this._overlayRef || !this._componentRef) {
1154
+ return;
1155
+ }
1156
+ const anchorElement = this._getTriggerElement();
1157
+ const floatingElement = this._overlayRef.overlayElement;
1158
+ const position = this.zPosition();
1159
+ const result = await computeZOverlayPlacement({
1160
+ reference: anchorElement,
1161
+ floating: floatingElement,
1162
+ placement: position,
1163
+ fallbackPlacements: getPopoverFallbackPlacements(position),
1164
+ offset: this.zOffset(),
1165
+ push: true,
1166
+ viewportPadding: 10,
1167
+ });
1168
+ if (!this._componentRef) {
1169
+ return;
1170
+ }
1171
+ floatingElement.style.left = `${result.x}px`;
1172
+ floatingElement.style.top = `${result.y}px`;
1173
+ this._componentRef.instance.position.set(result.placement);
1174
+ this.zPositionChange.emit(result.placement);
1438
1175
  }
1439
1176
  _markViewportSettling() {
1440
1177
  this._viewportSettlingUntil = Date.now() + 260;
@@ -1471,22 +1208,6 @@ class ZPopoverDirective {
1471
1208
  }
1472
1209
  this._viewportResizeTimeouts = [];
1473
1210
  }
1474
- _applyMobilePopoverPosition(position) {
1475
- if (!isMobilePopoverViewport() || !this._overlayRef?.hasAttached()) {
1476
- return;
1477
- }
1478
- const { overlayElement } = this._overlayRef;
1479
- const { visualViewport } = window;
1480
- const styles = getMobilePopoverPaneStyles({
1481
- position,
1482
- triggerRect: this._getTriggerElement().getBoundingClientRect(),
1483
- overlayRect: overlayElement.getBoundingClientRect(),
1484
- offset: this.zOffset(),
1485
- viewportWidth: visualViewport?.width ?? window.innerWidth,
1486
- viewportHeight: visualViewport?.height ?? window.innerHeight,
1487
- });
1488
- this._setOverlayPaneStyles(styles);
1489
- }
1490
1211
  _isTriggerVisibleInViewport() {
1491
1212
  const { visualViewport } = window;
1492
1213
  return isPopoverTriggerVisibleInViewport({
@@ -1495,11 +1216,6 @@ class ZPopoverDirective {
1495
1216
  viewportHeight: visualViewport?.height ?? window.innerHeight,
1496
1217
  });
1497
1218
  }
1498
- _setOverlayPaneStyles(styles) {
1499
- for (const key of ['top', 'left', 'right', 'bottom', 'transform']) {
1500
- this._renderer.setStyle(this._overlayRef?.overlayElement, key, styles[key]);
1501
- }
1502
- }
1503
1219
  _setupPopoverHover() {
1504
1220
  const overlayElement = this._overlayRef?.overlayElement;
1505
1221
  if (!overlayElement) {