@ship-ui/core 0.17.16 → 0.17.18

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.
@@ -7014,31 +7014,56 @@ class ShipTooltipWrapper {
7014
7014
  close: () => this.close()(),
7015
7015
  };
7016
7016
  this.isTemplate = computed(() => this.content() instanceof TemplateRef, ...(ngDevMode ? [{ debugName: "isTemplate" }] : []));
7017
+ this.#document = inject(DOCUMENT);
7017
7018
  this.#selfRef = inject((ElementRef));
7018
7019
  this.#renderer = inject(Renderer2);
7019
7020
  this.#positionAbort = null;
7020
- this.SUPPORTS_ANCHOR = typeof CSS !== 'undefined' && CSS.supports('position-anchor', '--abc');
7021
+ this.SUPPORTS_ANCHOR = typeof CSS !== 'undefined' && CSS.supports('position-anchor', '--abc') && CSS.supports('anchor-name', '--abc');
7021
7022
  this.isBelow = signal(false, ...(ngDevMode ? [{ debugName: "isBelow" }] : []));
7022
7023
  this.openEffect = effect(() => {
7023
7024
  if (this.isOpen()) {
7024
- setTimeout(() => {
7025
- this.#selfRef.nativeElement.showPopover();
7026
- this.schedulePositionUpdate();
7025
+ queueMicrotask(() => {
7026
+ const tooltipEl = this.#selfRef.nativeElement;
7027
+ if (!tooltipEl || !tooltipEl.isConnected)
7028
+ return;
7029
+ if (this.#positionAbort) {
7030
+ this.#positionAbort.abort();
7031
+ }
7032
+ this.#positionAbort = new AbortController();
7033
+ tooltipEl.showPopover();
7034
+ if (!this.SUPPORTS_ANCHOR) {
7035
+ setTimeout(() => {
7036
+ const scrollableParent = this.#findScrollableParent(tooltipEl);
7037
+ scrollableParent.addEventListener('scroll', () => this.calculateTooltipPosition(), {
7038
+ signal: this.#positionAbort?.signal,
7039
+ passive: true,
7040
+ });
7041
+ window?.addEventListener('resize', () => this.calculateTooltipPosition(), {
7042
+ signal: this.#positionAbort?.signal,
7043
+ passive: true,
7044
+ });
7045
+ this.calculateTooltipPosition();
7046
+ });
7047
+ }
7027
7048
  });
7028
7049
  }
7029
7050
  else {
7030
- this.#selfRef.nativeElement.hidePopover();
7051
+ const tooltipEl = this.#selfRef.nativeElement;
7052
+ if (tooltipEl) {
7053
+ try {
7054
+ if (tooltipEl.matches(':popover-open')) {
7055
+ tooltipEl.hidePopover();
7056
+ }
7057
+ }
7058
+ catch (e) {
7059
+ // Ignore if already hidden or other errors
7060
+ }
7061
+ }
7062
+ this.#positionAbort?.abort();
7063
+ this.#positionAbort = null;
7031
7064
  }
7032
7065
  }, ...(ngDevMode ? [{ debugName: "openEffect" }] : []));
7033
- this.rafId = null;
7034
- this.schedulePositionUpdate = () => {
7035
- if (this.rafId !== null) {
7036
- cancelAnimationFrame(this.rafId);
7037
- }
7038
- this.rafId = requestAnimationFrame(this.calculateTooltipPosition);
7039
- };
7040
7066
  this.calculateTooltipPosition = () => {
7041
- this.rafId = null;
7042
7067
  if (!this.anchorEl())
7043
7068
  return;
7044
7069
  const hostRect = this.anchorEl().nativeElement.getBoundingClientRect();
@@ -7046,61 +7071,71 @@ class ShipTooltipWrapper {
7046
7071
  const tooltipRect = tooltipEl.getBoundingClientRect();
7047
7072
  if (tooltipRect.width === 0 && tooltipRect.height === 0)
7048
7073
  return;
7049
- if (this.SUPPORTS_ANCHOR) {
7050
- const isPlacedBelow = tooltipRect.top > hostRect.top;
7051
- if (this.isBelow() !== isPlacedBelow) {
7052
- this.isBelow.set(isPlacedBelow);
7074
+ const BASE_SPACE = 8;
7075
+ // Position generators
7076
+ const topCenter = (t, m) => ({
7077
+ left: t.left + t.width / 2 - m.width / 2,
7078
+ top: t.top - m.height - BASE_SPACE,
7079
+ });
7080
+ const bottomCenter = (t, m) => ({
7081
+ left: t.left + t.width / 2 - m.width / 2,
7082
+ top: t.bottom + BASE_SPACE,
7083
+ });
7084
+ const topSpanLeft = (t, m) => ({ left: t.right - m.width, top: t.top - m.height - BASE_SPACE });
7085
+ const topSpanRight = (t, m) => ({ left: t.left, top: t.top - m.height - BASE_SPACE });
7086
+ const bottomSpanLeft = (t, m) => ({ left: t.right - m.width, top: t.bottom + BASE_SPACE });
7087
+ const bottomSpanRight = (t, m) => ({ left: t.left, top: t.bottom + BASE_SPACE });
7088
+ const tryOrder = [topCenter, topSpanLeft, topSpanRight, bottomCenter, bottomSpanLeft, bottomSpanRight];
7089
+ for (const positionFn of tryOrder) {
7090
+ const pos = positionFn(hostRect, tooltipRect);
7091
+ if (this.#fitsInViewport(pos, tooltipRect)) {
7092
+ this.#applyPosition(pos, tooltipEl);
7093
+ this.isBelow.set(pos.top > hostRect.top);
7094
+ return;
7053
7095
  }
7054
- return;
7055
- }
7056
- const gap = 12;
7057
- const outOfBoundsTop = hostRect.top - tooltipRect.height - gap < 0;
7058
- if (this.isBelow() !== outOfBoundsTop) {
7059
- this.isBelow.set(outOfBoundsTop);
7060
- }
7061
- let newTop = hostRect.top - tooltipRect.height - gap;
7062
- let newLeft = hostRect.left + hostRect.width / 2 - tooltipRect.width / 2;
7063
- if (outOfBoundsTop) {
7064
- newTop = hostRect.top + hostRect.height;
7065
- }
7066
- if (newLeft + tooltipRect.width > window?.innerWidth) {
7067
- newLeft = hostRect.right - tooltipRect.width / 2;
7068
- }
7069
- if (newLeft < 0) {
7070
- newLeft = -(tooltipRect.width / 2);
7071
- }
7072
- const leftStyle = `${newLeft}px`;
7073
- const topStyle = `${newTop}px`;
7074
- if (tooltipEl.style.left !== leftStyle) {
7075
- this.#renderer.setStyle(tooltipEl, 'left', leftStyle);
7076
- }
7077
- if (tooltipEl.style.top !== topStyle) {
7078
- this.#renderer.setStyle(tooltipEl, 'top', topStyle);
7079
- }
7080
- if (tooltipEl.style.position !== 'fixed') {
7081
- this.#renderer.setStyle(tooltipEl, 'position', 'fixed');
7082
7096
  }
7097
+ const fallback = this.#clampToViewport(topCenter(hostRect, tooltipRect), tooltipRect);
7098
+ this.#applyPosition(fallback, tooltipEl);
7099
+ this.isBelow.set(fallback.top > hostRect.top);
7083
7100
  };
7084
7101
  }
7102
+ #document;
7085
7103
  #selfRef;
7086
7104
  #renderer;
7087
7105
  #positionAbort;
7088
- ngAfterViewInit() {
7089
- this.#positionAbort = new AbortController();
7090
- const options = { signal: this.#positionAbort.signal, capture: true, passive: true };
7091
- window?.addEventListener('scroll', this.schedulePositionUpdate, options);
7092
- window?.addEventListener('resize', this.schedulePositionUpdate, {
7093
- signal: this.#positionAbort.signal,
7094
- passive: true,
7095
- });
7096
- this.schedulePositionUpdate();
7097
- }
7098
7106
  ngOnDestroy() {
7099
7107
  this.#positionAbort?.abort();
7100
- if (this.rafId !== null) {
7101
- cancelAnimationFrame(this.rafId);
7102
- this.rafId = null;
7108
+ this.#positionAbort = null;
7109
+ }
7110
+ #findScrollableParent(element) {
7111
+ const SCROLLABLE_STYLES = ['scroll', 'auto'];
7112
+ let parent = element.parentElement;
7113
+ while (parent) {
7114
+ if (SCROLLABLE_STYLES.indexOf(window?.getComputedStyle(parent).overflowY) > -1 &&
7115
+ parent.scrollHeight > parent.clientHeight) {
7116
+ return parent;
7117
+ }
7118
+ parent = parent.parentElement;
7103
7119
  }
7120
+ return this.#document.documentElement;
7121
+ }
7122
+ #applyPosition(pos, element) {
7123
+ this.#renderer.setStyle(element, 'left', `${pos.left}px`);
7124
+ this.#renderer.setStyle(element, 'top', `${pos.top}px`);
7125
+ this.#renderer.setStyle(element, 'position', 'fixed');
7126
+ this.#renderer.setStyle(element, 'margin', '0');
7127
+ }
7128
+ #fitsInViewport(pos, m) {
7129
+ return (pos.left >= 0 &&
7130
+ pos.top >= 0 &&
7131
+ pos.left + m.width <= window.innerWidth &&
7132
+ pos.top + m.height <= window.innerHeight);
7133
+ }
7134
+ #clampToViewport(pos, m) {
7135
+ return {
7136
+ left: Math.max(0, Math.min(pos.left, window.innerWidth - m.width)),
7137
+ top: Math.max(0, Math.min(pos.top, window.innerHeight - m.height)),
7138
+ };
7104
7139
  }
7105
7140
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: ShipTooltipWrapper, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7106
7141
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", type: ShipTooltipWrapper, isStandalone: true, selector: "ship-tooltip-wrapper", inputs: { positionAnchorName: { classPropertyName: "positionAnchorName", publicName: "positionAnchorName", isSignal: true, isRequired: true, transformFunction: null }, anchorEl: { classPropertyName: "anchorEl", publicName: "anchorEl", isSignal: true, isRequired: true, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, close: { classPropertyName: "close", publicName: "close", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "tooltip" }, properties: { "attr.popover": "\"manual\"", "style.position-anchor": "positionAnchorName()", "class.below": "isBelow()" } }, ngImport: i0, template: `
@@ -7219,13 +7254,12 @@ class ShipTooltip {
7219
7254
  cleanupTooltip() {
7220
7255
  if (openRef?.wrapperComponentRef) {
7221
7256
  openRef.component.cancelCleanupTimer();
7222
- const nativeEl = openRef.wrapperComponentRef.location.nativeElement;
7223
- if (nativeEl && typeof nativeEl.matches === 'function' && nativeEl.matches(':popover-open')) {
7224
- nativeEl.hidePopover();
7225
- }
7226
- openRef.wrapperComponentRef.destroy();
7227
7257
  openRef.component.isOpen.set(false);
7228
- openRef = null;
7258
+ openRef.wrapperComponentRef.location.nativeElement.hidePopover();
7259
+ setTimeout(() => {
7260
+ openRef.wrapperComponentRef.destroy();
7261
+ openRef = null;
7262
+ });
7229
7263
  }
7230
7264
  }
7231
7265
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: ShipTooltip, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }