@nectary/components 5.2.1 → 5.4.0

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/bundle.js CHANGED
@@ -127,6 +127,22 @@ const shouldReduceMotion = () => window.matchMedia("(prefers-reduced-motion: red
127
127
  const isAttrEqual = (oldVal, newVal) => {
128
128
  return oldVal === newVal || newVal === null && oldVal === "false" || newVal === "" && oldVal === "true";
129
129
  };
130
+ const getScrollableParents = (node) => {
131
+ const scrollableParents = [];
132
+ if (node == null) {
133
+ return scrollableParents;
134
+ }
135
+ let parent = node.parentElement;
136
+ while (parent != null) {
137
+ const computedStyle = getComputedStyle(parent);
138
+ if ((parent.scrollHeight > parent.clientHeight || parent.scrollWidth > parent.clientWidth) && (computedStyle.overflow === "auto" || computedStyle.overflow === "scroll" || computedStyle.overflowY === "auto" || computedStyle.overflowY === "scroll" || computedStyle.overflowX === "auto" || computedStyle.overflowX === "scroll")) {
139
+ scrollableParents.push(parent);
140
+ }
141
+ parent = parent.parentElement;
142
+ }
143
+ scrollableParents.push(document);
144
+ return scrollableParents;
145
+ };
130
146
  class NectaryElementBase extends HTMLElement {
131
147
  static _isGlobal = false;
132
148
  static get elementName() {
@@ -2834,6 +2850,7 @@ class Pop extends NectaryElement {
2834
2850
  #$focus;
2835
2851
  #$dialog;
2836
2852
  #resizeThrottle;
2853
+ #resizeObserver;
2837
2854
  #$targetSlot;
2838
2855
  #$targetOpenSlot;
2839
2856
  #$contentSlot;
@@ -2845,6 +2862,7 @@ class Pop extends NectaryElement {
2845
2862
  #targetStyleValue = null;
2846
2863
  #modalWidth = 0;
2847
2864
  #modalHeight = 0;
2865
+ #scrollableParents = [];
2848
2866
  constructor() {
2849
2867
  super();
2850
2868
  const shadowRoot = this.attachShadow();
@@ -2857,6 +2875,9 @@ class Pop extends NectaryElement {
2857
2875
  this.#$contentSlot = shadowRoot.querySelector('slot[name="content"]');
2858
2876
  this.#$targetOpenWrapper = shadowRoot.querySelector("#target-open");
2859
2877
  this.#resizeThrottle = throttleAnimationFrame(this.#updateOrientation);
2878
+ this.#resizeObserver = new ResizeObserver(() => {
2879
+ this.#resizeThrottle.fn();
2880
+ });
2860
2881
  this.#keydownContext = new Context(this.#$contentSlot, "keydown");
2861
2882
  this.#visibilityContext = new Context(this.#$contentSlot, "visibility");
2862
2883
  this.#controller = new AbortController();
@@ -2883,6 +2904,7 @@ class Pop extends NectaryElement {
2883
2904
  this.#controller.abort();
2884
2905
  this.#controller = null;
2885
2906
  this.#resizeThrottle.cancel();
2907
+ this.#resizeObserver.disconnect();
2886
2908
  this.#onCollapse();
2887
2909
  }
2888
2910
  static get observedAttributes() {
@@ -2891,6 +2913,12 @@ class Pop extends NectaryElement {
2891
2913
  "open"
2892
2914
  ];
2893
2915
  }
2916
+ get allowScroll() {
2917
+ return getBooleanAttribute(this, "allow-scroll");
2918
+ }
2919
+ get hideOutsideViewport() {
2920
+ return getBooleanAttribute(this, "hide-outside-viewport");
2921
+ }
2894
2922
  set modal(isModal) {
2895
2923
  updateBooleanAttribute(this, "modal", isModal);
2896
2924
  }
@@ -2921,6 +2949,9 @@ class Pop extends NectaryElement {
2921
2949
  get popoverRect() {
2922
2950
  return getRect(this.#$dialog);
2923
2951
  }
2952
+ get shouldCloseOnBackdropClick() {
2953
+ return !getBooleanAttribute(this, "disable-backdrop-close");
2954
+ }
2924
2955
  attributeChangedCallback(name, oldVal, newVal) {
2925
2956
  if (isAttrEqual(oldVal, newVal)) {
2926
2957
  return;
@@ -2981,74 +3012,91 @@ class Pop extends NectaryElement {
2981
3012
  this.#$targetSlot.removeEventListener("blur", this.#stopEventPropagation, true);
2982
3013
  this.#$focus.removeAttribute("tabindex");
2983
3014
  this.#$focus.removeAttribute("style");
2984
- this.#$dialog.showModal();
3015
+ if (this.modal || !this.allowScroll) {
3016
+ this.#$dialog.showModal();
3017
+ } else {
3018
+ this.#$dialog.show();
3019
+ }
2985
3020
  this.#$targetWrapper.setAttribute("aria-expanded", "true");
2986
3021
  this.#updateOrientation();
3022
+ this.#resizeObserver.observe(this.#$dialog);
2987
3023
  if (this.modal) {
2988
3024
  getFirstFocusableElement(this.#$contentSlot)?.focus();
2989
3025
  } else {
2990
- const $targetEl = this.#getFirstTargetElement(this.#$targetSlot);
2991
- const targetElComputedStyle = getComputedStyle($targetEl);
2992
- const marginLeft = parseInt(targetElComputedStyle.marginLeft);
2993
- const marginRight = parseInt(targetElComputedStyle.marginRight);
2994
- const marginTop = parseInt(targetElComputedStyle.marginTop);
2995
- const marginBottom = parseInt(targetElComputedStyle.marginBottom);
2996
- const targetRect = this.#getTargetRect();
2997
- this.#$targetWrapper.style.setProperty("display", "block");
2998
- this.#$targetWrapper.style.setProperty("width", `${targetRect.width + marginLeft + marginRight}px`);
2999
- this.#$targetWrapper.style.setProperty("height", `${targetRect.height + marginTop + marginBottom}px`);
3000
- this.#$targetOpenWrapper.style.setProperty("width", `${targetRect.width}px`);
3001
- this.#$targetOpenWrapper.style.setProperty("height", `${targetRect.height}px`);
3002
- this.#targetStyleValue = $targetEl.getAttribute("style");
3003
- $targetEl.style.setProperty("margin", "0");
3004
- $targetEl.style.setProperty("position", "static");
3005
- if (targetElComputedStyle.transform !== "none") {
3006
- const matrix = new DOMMatrixReadOnly(targetElComputedStyle.transform);
3007
- $targetEl.style.setProperty("transform", matrix.translate(-matrix.e, -matrix.f).toString());
3008
- }
3009
- getFirstSlotElement(this.#$targetSlot)?.setAttribute("slot", "target-open");
3010
- this.#$targetOpenSlot.addEventListener("keydown", this.#onTargetKeydown);
3026
+ if (!this.allowScroll) {
3027
+ const $targetEl = this.#getFirstTargetElement(this.#$targetSlot);
3028
+ const targetElComputedStyle = getComputedStyle($targetEl);
3029
+ const marginLeft = parseInt(targetElComputedStyle.marginLeft);
3030
+ const marginRight = parseInt(targetElComputedStyle.marginRight);
3031
+ const marginTop = parseInt(targetElComputedStyle.marginTop);
3032
+ const marginBottom = parseInt(targetElComputedStyle.marginBottom);
3033
+ const targetRect = this.#getTargetRect();
3034
+ this.#$targetWrapper.style.setProperty("display", "block");
3035
+ this.#$targetWrapper.style.setProperty("width", `${targetRect.width + marginLeft + marginRight}px`);
3036
+ this.#$targetWrapper.style.setProperty("height", `${targetRect.height + marginTop + marginBottom}px`);
3037
+ this.#$targetOpenWrapper.style.setProperty("width", `${targetRect.width}px`);
3038
+ this.#$targetOpenWrapper.style.setProperty("height", `${targetRect.height}px`);
3039
+ this.#targetStyleValue = $targetEl.getAttribute("style");
3040
+ $targetEl.style.setProperty("margin", "0");
3041
+ $targetEl.style.setProperty("position", "static");
3042
+ if (targetElComputedStyle.transform !== "none") {
3043
+ const matrix = new DOMMatrixReadOnly(targetElComputedStyle.transform);
3044
+ $targetEl.style.setProperty("transform", matrix.translate(-matrix.e, -matrix.f).toString());
3045
+ }
3046
+ getFirstSlotElement(this.#$targetSlot)?.setAttribute("slot", "target-open");
3047
+ }
3048
+ const activeSlot = this.allowScroll ? this.#$targetSlot : this.#$targetOpenSlot;
3049
+ activeSlot.addEventListener("keydown", this.#onTargetKeydown);
3011
3050
  if (this.#targetActiveElement !== null) {
3012
- this.#$targetOpenSlot.addEventListener("focus", this.#stopEventPropagation, true);
3051
+ activeSlot.addEventListener("focus", this.#stopEventPropagation, true);
3013
3052
  this.#targetActiveElement.focus();
3014
- this.#$targetOpenSlot.removeEventListener("focus", this.#stopEventPropagation, true);
3053
+ activeSlot.removeEventListener("focus", this.#stopEventPropagation, true);
3015
3054
  if (!isElementFocused(this.#targetActiveElement)) {
3016
3055
  requestAnimationFrame(() => {
3017
3056
  if (this.isDomConnected && this.#$dialog.open) {
3018
- this.#$targetOpenSlot.addEventListener("focus", this.#stopEventPropagation, true);
3057
+ activeSlot.addEventListener("focus", this.#stopEventPropagation, true);
3019
3058
  this.#targetActiveElement.focus();
3020
- this.#$targetOpenSlot.removeEventListener("focus", this.#stopEventPropagation, true);
3059
+ activeSlot.removeEventListener("focus", this.#stopEventPropagation, true);
3021
3060
  }
3022
3061
  });
3023
3062
  }
3024
3063
  }
3025
3064
  }
3026
- disableOverscroll();
3027
- window.addEventListener("scroll", this.#updatePosition, { passive: false });
3065
+ if (!this.allowScroll) {
3066
+ disableOverscroll();
3067
+ } else {
3068
+ this.#scrollableParents = getScrollableParents(this.#getFirstTargetElement(this.#$targetSlot));
3069
+ this.#scrollableParents.forEach((el) => {
3070
+ el.addEventListener("scroll", () => this.#updatePosition(false), { passive: true, capture: true });
3071
+ });
3072
+ }
3028
3073
  window.addEventListener("resize", this.#onResize);
3029
3074
  requestAnimationFrame(() => {
3030
3075
  if (this.isDomConnected && this.#$dialog.open) {
3031
3076
  this.#$contentSlot.addEventListener("slotchange", this.#onContentSlotChange);
3032
3077
  }
3033
3078
  });
3079
+ requestAnimationFrame(() => this.#updatePosition());
3034
3080
  this.#dispatchContentVisibility(true);
3035
3081
  }
3036
3082
  #onCollapse() {
3037
3083
  if (!this.#$dialog.open) {
3038
3084
  return;
3039
3085
  }
3086
+ this.#resizeObserver.disconnect();
3040
3087
  const isNonModal = !this.modal;
3088
+ const activeSlot = this.allowScroll ? this.#$targetSlot : this.#$targetOpenSlot;
3041
3089
  this.#dispatchContentVisibility(false);
3042
- this.#$targetOpenSlot.removeEventListener("keydown", this.#onTargetKeydown);
3090
+ activeSlot.removeEventListener("keydown", this.#onTargetKeydown);
3043
3091
  if (isNonModal) {
3044
- this.#$targetOpenSlot.addEventListener("blur", this.#captureActiveElement, true);
3092
+ activeSlot.addEventListener("blur", this.#captureActiveElement, true);
3045
3093
  }
3046
3094
  this.#$dialog.close();
3047
3095
  this.#$targetWrapper.setAttribute("aria-expanded", "false");
3048
3096
  if (isNonModal) {
3049
- this.#$targetOpenSlot.removeEventListener("blur", this.#captureActiveElement, true);
3097
+ activeSlot.removeEventListener("blur", this.#captureActiveElement, true);
3050
3098
  }
3051
- if (isNonModal) {
3099
+ if (isNonModal && !this.allowScroll) {
3052
3100
  const targetEl = this.#getFirstTargetElement(this.#$targetOpenSlot);
3053
3101
  targetEl.style.removeProperty("margin");
3054
3102
  targetEl.style.removeProperty("position");
@@ -3080,17 +3128,23 @@ class Pop extends NectaryElement {
3080
3128
  this.#targetActiveElement = null;
3081
3129
  }
3082
3130
  }
3083
- enableOverscroll();
3131
+ if (!this.allowScroll) {
3132
+ enableOverscroll();
3133
+ } else {
3134
+ this.#scrollableParents.forEach((el) => {
3135
+ el.removeEventListener("scroll", () => this.#updatePosition(false), { capture: true });
3136
+ });
3137
+ }
3084
3138
  this.#resizeThrottle.cancel();
3085
3139
  window.removeEventListener("resize", this.#onResize);
3086
- window.removeEventListener("scroll", this.#updatePosition);
3140
+ this.#scrollableParents = [];
3087
3141
  this.#$contentSlot.removeEventListener("slotchange", this.#onContentSlotChange);
3088
3142
  }
3089
3143
  #onResize = () => {
3090
3144
  this.#resizeThrottle.fn();
3091
3145
  };
3092
- #updatePosition = () => {
3093
- const targetRect = this.modal ? this.#getTargetRect() : this.#$targetWrapper.getBoundingClientRect();
3146
+ #updatePosition = (updateWidth) => {
3147
+ const targetRect = this.modal || this.allowScroll ? this.#getTargetRect() : this.#$targetWrapper.getBoundingClientRect();
3094
3148
  const orient = this.orientation;
3095
3149
  const modalWidth = this.#modalWidth;
3096
3150
  const modalHeight = this.#modalHeight;
@@ -3123,9 +3177,17 @@ class Pop extends NectaryElement {
3123
3177
  }
3124
3178
  const clampedXPos = Math.max(inset, Math.min(xPos, window.innerWidth - modalWidth - inset));
3125
3179
  const clampedYPos = Math.max(inset, Math.min(yPos, window.innerHeight - modalHeight - inset));
3180
+ if (this.hideOutsideViewport && this.#isPopPointInViewport(xPos, yPos)) {
3181
+ this.#$dialog.style.setProperty("visibility", "hidden");
3182
+ } else {
3183
+ this.#$dialog.style.removeProperty("visibility");
3184
+ }
3126
3185
  this.#$dialog.style.setProperty("left", `${clampedXPos}px`);
3127
3186
  this.#$dialog.style.setProperty("top", `${clampedYPos}px`);
3128
- if (!this.modal) {
3187
+ if (updateWidth === true) {
3188
+ this.#$dialog.style.setProperty("width", `${modalWidth}px`);
3189
+ }
3190
+ if (!this.modal && !this.allowScroll) {
3129
3191
  const targetLeftPos = targetRect.x - clampedXPos;
3130
3192
  const targetTopPos = targetRect.y - clampedYPos;
3131
3193
  this.#$targetOpenWrapper.style.setProperty("left", `${targetLeftPos}px`);
@@ -3140,49 +3202,12 @@ class Pop extends NectaryElement {
3140
3202
  const shouldSetWidthToTarget = orient === "top-stretch" || orient === "bottom-stretch";
3141
3203
  const modalHeight = modalRect.height;
3142
3204
  const modalWidth = shouldSetWidthToTarget ? targetRect.width : modalRect.width;
3143
- const inset = this.inset;
3144
- let xPos = 0;
3145
- let yPos = 0;
3146
3205
  this.#modalHeight = modalHeight;
3147
3206
  this.#modalWidth = modalWidth;
3148
- if (orient === "bottom-right" || orient === "top-right" || orient === "top-stretch" || orient === "bottom-stretch") {
3149
- xPos = targetRect.x;
3150
- }
3151
- if (orient === "bottom-left" || orient === "top-left") {
3152
- xPos = targetRect.x + targetRect.width - modalWidth;
3153
- }
3154
- if (orient === "bottom-center" || orient === "top-center") {
3155
- xPos = targetRect.x + targetRect.width / 2 - modalWidth / 2;
3156
- }
3157
- if (orient === "center-right") {
3158
- xPos = targetRect.x + targetRect.width;
3159
- }
3160
- if (orient === "center-left") {
3161
- xPos = targetRect.x - modalWidth;
3162
- }
3163
- if (orient === "bottom-left" || orient === "bottom-right" || orient === "bottom-stretch" || orient === "bottom-center") {
3164
- yPos = targetRect.y + targetRect.height;
3165
- }
3166
- if (orient === "top-left" || orient === "top-right" || orient === "top-stretch" || orient === "top-center") {
3167
- yPos = targetRect.y - modalHeight;
3168
- }
3169
- if (orient === "center-left" || orient === "center-right") {
3170
- yPos = targetRect.y + targetRect.height / 2 - modalHeight / 2;
3171
- }
3172
- xPos = Math.round(Math.max(inset, Math.min(xPos, window.innerWidth - modalWidth - inset)));
3173
- yPos = Math.round(Math.max(inset, Math.min(yPos, window.innerHeight - modalHeight - inset)));
3174
- this.#$dialog.style.setProperty("left", `${xPos}px`);
3175
- this.#$dialog.style.setProperty("top", `${yPos}px`);
3176
- this.#$dialog.style.setProperty("width", `${modalWidth}px`);
3177
- if (!this.modal) {
3178
- const targetLeftPos = targetRect.x - xPos;
3179
- const targetTopPos = targetRect.y - yPos;
3180
- this.#$targetOpenWrapper.style.setProperty("left", `${targetLeftPos}px`);
3181
- this.#$targetOpenWrapper.style.setProperty("top", `${targetTopPos}px`);
3182
- }
3207
+ this.#updatePosition(true);
3183
3208
  };
3184
3209
  #onBackdropMouseDown = (e) => {
3185
- if (isTargetEqual(e, this.#$dialog)) {
3210
+ if (this.shouldCloseOnBackdropClick && isTargetEqual(e, this.#$dialog)) {
3186
3211
  const rect = this.popoverRect;
3187
3212
  const isInside = e.x >= rect.x && e.x < rect.x + rect.width && e.y >= rect.y && e.y < rect.y + rect.height;
3188
3213
  if (!isInside) {
@@ -3238,6 +3263,14 @@ class Pop extends NectaryElement {
3238
3263
  this.#updateOrientation();
3239
3264
  }
3240
3265
  };
3266
+ #isPopPointInViewport(x, y) {
3267
+ const inset = this.inset;
3268
+ const modalWidth = this.#modalWidth;
3269
+ const modalHeight = this.#modalHeight;
3270
+ const clampedX = Math.max(inset, Math.min(x, window.innerWidth - modalWidth - inset));
3271
+ const clampedY = Math.max(inset, Math.min(y, window.innerHeight - modalHeight - inset));
3272
+ return Math.abs(clampedX - x) > 2 || Math.abs(clampedY - y) > 2;
3273
+ }
3241
3274
  }
3242
3275
  defineCustomElement("sinch-pop", Pop);
3243
3276
  class TooltipState {
@@ -3254,6 +3287,9 @@ class TooltipState {
3254
3287
  };
3255
3288
  }
3256
3289
  show() {
3290
+ if (this.#options.isOpened === false) {
3291
+ return;
3292
+ }
3257
3293
  switch (this.#state) {
3258
3294
  case "hide": {
3259
3295
  this.#switchToHideToShow();
@@ -3266,6 +3302,9 @@ class TooltipState {
3266
3302
  }
3267
3303
  }
3268
3304
  hide() {
3305
+ if (this.#options.isOpened === true) {
3306
+ return;
3307
+ }
3269
3308
  switch (this.#state) {
3270
3309
  case "hide-to-show": {
3271
3310
  this.#onHideAnimationEnd();
@@ -3318,13 +3357,15 @@ class TooltipState {
3318
3357
  this.#options.onShowStart();
3319
3358
  if (this.#options.showDelay === 0) {
3320
3359
  this.#onSwitchToShow();
3360
+ } else if (this.#options.isOpened !== void 0) {
3361
+ this.#timerId = window.setTimeout(this.#onSwitchToShow, 100);
3321
3362
  } else {
3322
3363
  this.#timerId = window.setTimeout(this.#onSwitchToShow, this.#options.showDelay);
3323
3364
  }
3324
3365
  }
3325
3366
  #switchToShowToHide(skipDelay, skipHideAnimation) {
3326
3367
  this.#switchToState("show-to-hide");
3327
- if (skipDelay === true || this.#options.hideDelay === 0) {
3368
+ if (skipDelay === true || this.#options.hideDelay === 0 || this.#options.isOpened !== void 0) {
3328
3369
  this.#onShowToHideEnd(skipHideAnimation);
3329
3370
  } else {
3330
3371
  this.#timerId = window.setTimeout(this.#onShowToHideEnd, this.#options.hideDelay);
@@ -3386,7 +3427,7 @@ const getPopOrientation$1 = (orientation) => {
3386
3427
  }
3387
3428
  return orientation;
3388
3429
  };
3389
- const templateHTML$Q = '<style>:host{display:contents}#content-wrapper{padding-bottom:8px;filter:drop-shadow(var(--sinch-comp-tooltip-shadow))}:host([orientation=left]) #content-wrapper{padding-bottom:0;padding-right:8px}:host([orientation=right]) #content-wrapper{padding-bottom:0;padding-left:8px}:host([orientation^=bottom]) #content-wrapper{padding-bottom:0;padding-top:8px}#content{position:relative;display:block;max-width:300px;padding:2px 6px;box-sizing:border-box;background-color:var(--sinch-local-color-background);border-radius:var(--sinch-comp-tooltip-shape-radius);pointer-events:none;opacity:0;--sinch-local-color-background:var(--sinch-comp-tooltip-color-background);--sinch-global-color-text:var(--sinch-comp-tooltip-color-text)}#text{word-break:break-word;pointer-events:none;--sinch-comp-text-font:var(--sinch-comp-tooltip-font-body)}#tip{position:absolute;left:50%;top:100%;transform:translateX(-50%) rotate(0);transform-origin:top center;fill:var(--sinch-local-color-background);pointer-events:none}#tip.hidden{display:none}:host([orientation=left]) #tip{transform:translateX(-50%) rotate(270deg);top:50%;left:100%}:host([orientation=right]) #tip{transform:translateX(-50%) rotate(90deg);top:50%;left:0}:host([orientation^=bottom]) #tip{transform:translateX(-50%) rotate(180deg);top:0}:host([text-align=right]) #text{--sinch-comp-text-align:right}:host([text-align=center]) #text{--sinch-comp-text-align:center}:host([text-align=left]) #text{--sinch-comp-text-align:left}</style><sinch-pop id="pop"><slot id="target" slot="target"></slot><div id="content-wrapper" slot="content"><div id="content"><sinch-text id="text" type="s"></sinch-text><svg id="tip" width="8" height="4" aria-hidden="true"><path d="m4 4 4-4h-8l4 4Z"/></svg></div></div></sinch-pop>';
3430
+ const templateHTML$Q = '<style>:host{display:contents}#content-wrapper{padding-bottom:8px;filter:drop-shadow(var(--sinch-comp-tooltip-shadow))}:host([orientation=left]) #content-wrapper{padding-bottom:0;padding-right:8px}:host([orientation=right]) #content-wrapper{padding-bottom:0;padding-left:8px}:host([orientation^=bottom]) #content-wrapper{padding-bottom:0;padding-top:8px}#content{position:relative;display:block;max-width:300px;padding:2px 6px;box-sizing:border-box;background-color:var(--sinch-local-color-background);border-radius:var(--sinch-comp-tooltip-shape-radius);pointer-events:none;opacity:0;--sinch-local-color-background:var(--sinch-comp-tooltip-color-background);--sinch-global-color-text:var(--sinch-comp-tooltip-color-text)}#text{word-break:break-word;pointer-events:none;--sinch-comp-text-font:var(--sinch-comp-tooltip-font-body)}#tip{position:absolute;left:50%;top:100%;transform:translateX(-50%) rotate(0);transform-origin:top center;fill:var(--sinch-local-color-background);pointer-events:none}#tip.hidden{display:none}:host([orientation=left]) #tip{transform:translateX(-50%) rotate(270deg);top:50%;left:100%}:host([orientation=right]) #tip{transform:translateX(-50%) rotate(90deg);top:50%;left:0}:host([orientation^=bottom]) #tip{transform:translateX(-50%) rotate(180deg);top:0}:host([text-align=right]) #text{--sinch-comp-text-align:right}:host([text-align=center]) #text{--sinch-comp-text-align:center}:host([text-align=left]) #text{--sinch-comp-text-align:left}</style><sinch-pop id="pop" allow-scroll hide-outside-viewport><slot id="target" slot="target"></slot><div id="content-wrapper" slot="content"><div id="content"><sinch-text id="text" type="s"></sinch-text><svg id="tip" width="8" height="4" aria-hidden="true"><path d="m4 4 4-4h-8l4 4Z"/></svg></div></div></sinch-pop>';
3390
3431
  const TIP_SIZE$1 = 8;
3391
3432
  const SHOW_DELAY_SLOW = 1e3;
3392
3433
  const SHOW_DELAY_FAST = 250;
@@ -3448,6 +3489,7 @@ class Tooltip extends NectaryElement {
3448
3489
  }
3449
3490
  static get observedAttributes() {
3450
3491
  return [
3492
+ "is-opened",
3451
3493
  "text",
3452
3494
  "orientation",
3453
3495
  "text-align",
@@ -3485,8 +3527,24 @@ class Tooltip extends NectaryElement {
3485
3527
  updateAttribute(this.#$pop, name, newVal);
3486
3528
  break;
3487
3529
  }
3530
+ case "is-opened": {
3531
+ this.#tooltipState.updateOptions({
3532
+ isOpened: this.isOpenedControlled
3533
+ });
3534
+ if (this.isOpenedControlled === true) {
3535
+ updateBooleanAttribute(this.#$pop, "disable-backdrop-close", true);
3536
+ this.#tooltipState.show();
3537
+ } else if (this.isOpenedControlled === false) {
3538
+ updateBooleanAttribute(this.#$pop, "disable-backdrop-close", false);
3539
+ this.#tooltipState.hide();
3540
+ }
3541
+ }
3488
3542
  }
3489
3543
  }
3544
+ get isOpenedControlled() {
3545
+ const isOpenedAttr = getAttribute(this, "is-opened");
3546
+ return isOpenedAttr === null ? void 0 : isOpenedAttr !== "false";
3547
+ }
3490
3548
  get text() {
3491
3549
  return getAttribute(this, "text", "");
3492
3550
  }
@@ -3538,8 +3596,10 @@ class Tooltip extends NectaryElement {
3538
3596
  };
3539
3597
  // Tooltip begins to wait for SHOW_DELAY on mouseenter
3540
3598
  #onStateShowStart = () => {
3541
- this.#subscribeScroll();
3542
- this.#subscribeMouseLeaveEvents();
3599
+ if (this.isOpenedControlled === void 0) {
3600
+ this.#subscribeScroll();
3601
+ this.#subscribeMouseLeaveEvents();
3602
+ }
3543
3603
  };
3544
3604
  // SHOW_DELAY ended, tooltip can be shown with animation
3545
3605
  #onStateShowEnd = () => {