@nectary/components 5.29.1 → 5.30.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 +497 -107
- package/button/index.js +37 -5
- package/button/types.d.ts +4 -0
- package/package.json +1 -1
- package/pop/index.d.ts +6 -0
- package/pop/index.js +140 -93
- package/pop/utils.d.ts +20 -0
- package/pop/utils.js +46 -0
- package/tooltip/index.js +176 -12
- package/utils/dom.d.ts +2 -0
- package/utils/dom.js +34 -0
- package/utils/index.js +3 -1
- package/utils/placement.d.ts +13 -0
- package/utils/placement.js +78 -0
package/bundle.js
CHANGED
|
@@ -143,6 +143,38 @@ const getScrollableParents = (node) => {
|
|
|
143
143
|
scrollableParents.push(document);
|
|
144
144
|
return scrollableParents;
|
|
145
145
|
};
|
|
146
|
+
const isTransformedElement = (element) => {
|
|
147
|
+
if (element == null) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const style = getComputedStyle(element);
|
|
151
|
+
const backdropFilter = style.getPropertyValue("backdrop-filter");
|
|
152
|
+
const hasTransform = style.transform !== "none" || style.perspective !== "none" || style.filter !== "none" || backdropFilter !== "" && backdropFilter !== "none";
|
|
153
|
+
const hasWillChange = style.willChange.split(",").map((value) => value.trim().toLowerCase()).some((value) => value === "transform" || value === "perspective" || value === "filter" || value === "backdrop-filter");
|
|
154
|
+
return hasTransform || hasWillChange;
|
|
155
|
+
};
|
|
156
|
+
const getTransformedAncestor = (node) => {
|
|
157
|
+
let current = node;
|
|
158
|
+
while (current != null) {
|
|
159
|
+
if (current !== node) {
|
|
160
|
+
if (isTransformedElement(current)) {
|
|
161
|
+
return current;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const parent = current.parentElement;
|
|
165
|
+
if (parent != null) {
|
|
166
|
+
current = parent;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const root = current.getRootNode();
|
|
170
|
+
if (root instanceof ShadowRoot && root.host instanceof HTMLElement) {
|
|
171
|
+
current = root.host;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
};
|
|
146
178
|
class NectaryElementBase extends HTMLElement {
|
|
147
179
|
static _isGlobal = false;
|
|
148
180
|
static get elementName() {
|
|
@@ -2316,9 +2348,12 @@ class Button extends NectaryElement {
|
|
|
2316
2348
|
super.connectedCallback();
|
|
2317
2349
|
this.#controller = new AbortController();
|
|
2318
2350
|
const { signal } = this.#controller;
|
|
2319
|
-
this.
|
|
2320
|
-
|
|
2321
|
-
|
|
2351
|
+
if (!this.hasAttribute("tabindex")) {
|
|
2352
|
+
this.setAttribute("tabindex", "0");
|
|
2353
|
+
}
|
|
2354
|
+
if (!this.hasAttribute("role")) {
|
|
2355
|
+
this.setAttribute("role", "button");
|
|
2356
|
+
}
|
|
2322
2357
|
this.addEventListener("click", this.#onButtonClick, { signal });
|
|
2323
2358
|
this.addEventListener("focus", this.#onButtonFocus, { signal });
|
|
2324
2359
|
this.addEventListener("blur", this.#onButtonBlur, { signal });
|
|
@@ -2342,7 +2377,9 @@ class Button extends NectaryElement {
|
|
|
2342
2377
|
"toggled",
|
|
2343
2378
|
"size",
|
|
2344
2379
|
"data-size",
|
|
2345
|
-
"data-managed-aria-disabled"
|
|
2380
|
+
"data-managed-aria-disabled",
|
|
2381
|
+
"role",
|
|
2382
|
+
"tabindex"
|
|
2346
2383
|
];
|
|
2347
2384
|
}
|
|
2348
2385
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
@@ -2380,6 +2417,33 @@ class Button extends NectaryElement {
|
|
|
2380
2417
|
this.#onSizeUpdate();
|
|
2381
2418
|
break;
|
|
2382
2419
|
}
|
|
2420
|
+
case "role": {
|
|
2421
|
+
if (isAttrEqual(oldVal, newVal)) {
|
|
2422
|
+
break;
|
|
2423
|
+
}
|
|
2424
|
+
const effectiveRole = newVal !== null && newVal !== "" ? newVal : "button";
|
|
2425
|
+
this.#internals.role = effectiveRole;
|
|
2426
|
+
if (newVal === null || newVal === "") {
|
|
2427
|
+
this.setAttribute("role", "button");
|
|
2428
|
+
}
|
|
2429
|
+
break;
|
|
2430
|
+
}
|
|
2431
|
+
case "tabindex": {
|
|
2432
|
+
if (newVal === null) {
|
|
2433
|
+
if (this.isDomConnected) {
|
|
2434
|
+
this.setAttribute("tabindex", "0");
|
|
2435
|
+
}
|
|
2436
|
+
break;
|
|
2437
|
+
}
|
|
2438
|
+
if (isAttrEqual(oldVal, newVal)) {
|
|
2439
|
+
break;
|
|
2440
|
+
}
|
|
2441
|
+
const parsed = Number.parseInt(newVal, 10);
|
|
2442
|
+
if (Number.isNaN(parsed)) {
|
|
2443
|
+
this.setAttribute("tabindex", "0");
|
|
2444
|
+
}
|
|
2445
|
+
break;
|
|
2446
|
+
}
|
|
2383
2447
|
}
|
|
2384
2448
|
}
|
|
2385
2449
|
set type(value) {
|
|
@@ -3138,6 +3202,79 @@ const createThrottle = (delayFn, cancelFn) => (cb) => {
|
|
|
3138
3202
|
};
|
|
3139
3203
|
};
|
|
3140
3204
|
const throttleAnimationFrame = createThrottle(globalThis.requestAnimationFrame, globalThis.cancelAnimationFrame);
|
|
3205
|
+
const resolveTransformedAncestor = (node, ancestorHint) => {
|
|
3206
|
+
if (node != null && ancestorHint != null && ancestorHint.isConnected && isTransformedElement(ancestorHint) && (ancestorHint === node || ancestorHint.contains(node))) {
|
|
3207
|
+
return ancestorHint;
|
|
3208
|
+
}
|
|
3209
|
+
return getTransformedAncestor(node);
|
|
3210
|
+
};
|
|
3211
|
+
const getTransformedAncestorScale = (ancestor) => {
|
|
3212
|
+
if (ancestor == null) {
|
|
3213
|
+
return { scaleX: 1, scaleY: 1 };
|
|
3214
|
+
}
|
|
3215
|
+
const transform = getComputedStyle(ancestor).transform;
|
|
3216
|
+
let matrixScaleX = null;
|
|
3217
|
+
let matrixScaleY = null;
|
|
3218
|
+
if (transform !== "none") {
|
|
3219
|
+
try {
|
|
3220
|
+
const matrix = new DOMMatrixReadOnly(transform);
|
|
3221
|
+
matrixScaleX = Math.hypot(matrix.a, matrix.b);
|
|
3222
|
+
matrixScaleY = Math.hypot(matrix.c, matrix.d);
|
|
3223
|
+
} catch {
|
|
3224
|
+
matrixScaleX = null;
|
|
3225
|
+
matrixScaleY = null;
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
if (matrixScaleX !== null && matrixScaleY !== null) {
|
|
3229
|
+
return {
|
|
3230
|
+
scaleX: Number.isFinite(matrixScaleX) && matrixScaleX > 0 ? matrixScaleX : 1,
|
|
3231
|
+
scaleY: Number.isFinite(matrixScaleY) && matrixScaleY > 0 ? matrixScaleY : 1
|
|
3232
|
+
};
|
|
3233
|
+
}
|
|
3234
|
+
const rect = ancestor.getBoundingClientRect();
|
|
3235
|
+
const baseWidth = ancestor.offsetWidth;
|
|
3236
|
+
const baseHeight = ancestor.offsetHeight;
|
|
3237
|
+
const scaleX = baseWidth > 0 ? rect.width / baseWidth : 1;
|
|
3238
|
+
const scaleY = baseHeight > 0 ? rect.height / baseHeight : 1;
|
|
3239
|
+
return {
|
|
3240
|
+
scaleX: Number.isFinite(scaleX) && scaleX > 0 ? scaleX : 1,
|
|
3241
|
+
scaleY: Number.isFinite(scaleY) && scaleY > 0 ? scaleY : 1
|
|
3242
|
+
};
|
|
3243
|
+
};
|
|
3244
|
+
const getPlacementContext = (node, ancestorHint) => {
|
|
3245
|
+
const transformedAncestor = resolveTransformedAncestor(node, ancestorHint);
|
|
3246
|
+
const ancestorRect = transformedAncestor?.getBoundingClientRect() ?? null;
|
|
3247
|
+
const ancestorClientLeft = transformedAncestor?.clientLeft ?? 0;
|
|
3248
|
+
const ancestorClientTop = transformedAncestor?.clientTop ?? 0;
|
|
3249
|
+
const ancestorClientWidth = transformedAncestor?.clientWidth ?? 0;
|
|
3250
|
+
const ancestorClientHeight = transformedAncestor?.clientHeight ?? 0;
|
|
3251
|
+
const { scaleX, scaleY } = getTransformedAncestorScale(transformedAncestor);
|
|
3252
|
+
const boundsLeft = ancestorRect != null ? ancestorRect.x + ancestorClientLeft * scaleX : 0;
|
|
3253
|
+
const boundsTop = ancestorRect != null ? ancestorRect.y + ancestorClientTop * scaleY : 0;
|
|
3254
|
+
const boundsWidth = ancestorRect != null && ancestorClientWidth > 0 ? ancestorClientWidth : ancestorRect != null ? ancestorRect.width / scaleX : window.innerWidth;
|
|
3255
|
+
const boundsHeight = ancestorRect != null && ancestorClientHeight > 0 ? ancestorClientHeight : ancestorRect != null ? ancestorRect.height / scaleY : window.innerHeight;
|
|
3256
|
+
return {
|
|
3257
|
+
transformedAncestor,
|
|
3258
|
+
ancestorRect,
|
|
3259
|
+
scaleX,
|
|
3260
|
+
scaleY,
|
|
3261
|
+
boundsLeft,
|
|
3262
|
+
boundsTop,
|
|
3263
|
+
boundsWidth,
|
|
3264
|
+
boundsHeight
|
|
3265
|
+
};
|
|
3266
|
+
};
|
|
3267
|
+
const toLocalRect = (rect, placementContext) => {
|
|
3268
|
+
if (placementContext.transformedAncestor == null) {
|
|
3269
|
+
return rect;
|
|
3270
|
+
}
|
|
3271
|
+
return {
|
|
3272
|
+
x: (rect.x - placementContext.boundsLeft) / placementContext.scaleX,
|
|
3273
|
+
y: (rect.y - placementContext.boundsTop) / placementContext.scaleY,
|
|
3274
|
+
width: rect.width / placementContext.scaleX,
|
|
3275
|
+
height: rect.height / placementContext.scaleY
|
|
3276
|
+
};
|
|
3277
|
+
};
|
|
3141
3278
|
const orientationValues$2 = [
|
|
3142
3279
|
"top-left",
|
|
3143
3280
|
"top-right",
|
|
@@ -3150,6 +3287,50 @@ const orientationValues$2 = [
|
|
|
3150
3287
|
"center-left",
|
|
3151
3288
|
"center-right"
|
|
3152
3289
|
];
|
|
3290
|
+
const getAnchorPosition = (rect, width, height, orient) => {
|
|
3291
|
+
let x = 0;
|
|
3292
|
+
let y = 0;
|
|
3293
|
+
if (orient === "bottom-right" || orient === "top-right" || orient === "top-stretch" || orient === "bottom-stretch") {
|
|
3294
|
+
x = rect.x;
|
|
3295
|
+
}
|
|
3296
|
+
if (orient === "bottom-left" || orient === "top-left") {
|
|
3297
|
+
x = rect.x + rect.width - width;
|
|
3298
|
+
}
|
|
3299
|
+
if (orient === "bottom-center" || orient === "top-center") {
|
|
3300
|
+
x = rect.x + rect.width / 2 - width / 2;
|
|
3301
|
+
}
|
|
3302
|
+
if (orient === "center-right") {
|
|
3303
|
+
x = rect.x + rect.width;
|
|
3304
|
+
}
|
|
3305
|
+
if (orient === "center-left") {
|
|
3306
|
+
x = rect.x - width;
|
|
3307
|
+
}
|
|
3308
|
+
if (orient === "bottom-left" || orient === "bottom-right" || orient === "bottom-stretch" || orient === "bottom-center") {
|
|
3309
|
+
y = rect.y + rect.height;
|
|
3310
|
+
}
|
|
3311
|
+
if (orient === "top-left" || orient === "top-right" || orient === "top-stretch" || orient === "top-center") {
|
|
3312
|
+
y = rect.y - height;
|
|
3313
|
+
}
|
|
3314
|
+
if (orient === "center-left" || orient === "center-right") {
|
|
3315
|
+
y = rect.y + rect.height / 2 - height / 2;
|
|
3316
|
+
}
|
|
3317
|
+
return { x, y };
|
|
3318
|
+
};
|
|
3319
|
+
const clampPosition = ({
|
|
3320
|
+
x,
|
|
3321
|
+
y,
|
|
3322
|
+
boundsWidth,
|
|
3323
|
+
boundsHeight,
|
|
3324
|
+
insetX,
|
|
3325
|
+
insetY,
|
|
3326
|
+
modalWidth,
|
|
3327
|
+
modalHeight
|
|
3328
|
+
}) => {
|
|
3329
|
+
return {
|
|
3330
|
+
x: Math.max(insetX, Math.min(x, boundsWidth - modalWidth - insetX)),
|
|
3331
|
+
y: Math.max(insetY, Math.min(y, boundsHeight - modalHeight - insetY))
|
|
3332
|
+
};
|
|
3333
|
+
};
|
|
3153
3334
|
const bodyEl$1 = document.body;
|
|
3154
3335
|
const disableOverscroll = () => {
|
|
3155
3336
|
bodyEl$1.__pop_counter__ = (bodyEl$1.__pop_counter__ ?? 0) + 1;
|
|
@@ -3165,7 +3346,7 @@ const enableOverscroll = () => {
|
|
|
3165
3346
|
document.documentElement.style.removeProperty("overscroll-behavior");
|
|
3166
3347
|
}
|
|
3167
3348
|
};
|
|
3168
|
-
const templateHTML$U = '<style>:host{display:contents;position:relative}dialog{position:fixed;left:0;top:0;margin:0;outline:0;padding:0;border:none;box-sizing:border-box;max-width:unset;max-height:unset;z-index:1;background:0 0;overflow:visible}dialog:not([open]){display:none}dialog::backdrop{background-color:transparent}#content{position:relative;z-index:1}#target-open{display:flex;flex-direction:column;position:absolute;left:0;top:0}#focus{display:none;position:absolute;width:0;height:0}</style><slot id="target" name="target" aria-haspopup="dialog" aria-expanded="false"></slot><div id="focus"></div><dialog id="dialog"><div id="target-open"><slot name="target-open"></slot></div><div id="content"><slot name="content"></slot></div></dialog>';
|
|
3349
|
+
const templateHTML$U = '<style>:host{display:contents;position:relative}dialog{position:fixed;left:0;top:0;transform:translate(var(--sinch-pop-offset-x),var(--sinch-pop-offset-y));margin:0;outline:0;padding:0;border:none;box-sizing:border-box;max-width:unset;max-height:unset;z-index:1;background:0 0;overflow:visible}dialog:not([open]){display:none}dialog::backdrop{background-color:transparent}#content{position:relative;z-index:1}#target-open{display:flex;flex-direction:column;position:absolute;left:0;top:0}#focus{display:none;position:absolute;width:0;height:0}</style><slot id="target" name="target" aria-haspopup="dialog" aria-expanded="false"></slot><div id="focus"></div><dialog id="dialog"><div id="target-open"><slot name="target-open"></slot></div><div id="content"><slot name="content"></slot></div></dialog>';
|
|
3169
3350
|
const template$U = document.createElement("template");
|
|
3170
3351
|
template$U.innerHTML = templateHTML$U;
|
|
3171
3352
|
class Pop extends NectaryElement {
|
|
@@ -3173,6 +3354,7 @@ class Pop extends NectaryElement {
|
|
|
3173
3354
|
#$focus;
|
|
3174
3355
|
#$dialog;
|
|
3175
3356
|
#resizeThrottle;
|
|
3357
|
+
#scrollPositionThrottle;
|
|
3176
3358
|
#resizeObserver;
|
|
3177
3359
|
#$targetSlot;
|
|
3178
3360
|
#$targetOpenSlot;
|
|
@@ -3182,10 +3364,15 @@ class Pop extends NectaryElement {
|
|
|
3182
3364
|
#controller;
|
|
3183
3365
|
#keydownContext;
|
|
3184
3366
|
#visibilityContext;
|
|
3185
|
-
#targetStyleValue = null;
|
|
3186
3367
|
#modalWidth = 0;
|
|
3187
3368
|
#modalHeight = 0;
|
|
3188
3369
|
#scrollableParents = [];
|
|
3370
|
+
#openSession = {
|
|
3371
|
+
effectiveAllowScroll: false,
|
|
3372
|
+
modalSemantics: false,
|
|
3373
|
+
targetStyleValue: null,
|
|
3374
|
+
transformedAncestor: null
|
|
3375
|
+
};
|
|
3189
3376
|
constructor() {
|
|
3190
3377
|
super();
|
|
3191
3378
|
const shadowRoot = this.attachShadow();
|
|
@@ -3198,6 +3385,9 @@ class Pop extends NectaryElement {
|
|
|
3198
3385
|
this.#$contentSlot = shadowRoot.querySelector('slot[name="content"]');
|
|
3199
3386
|
this.#$targetOpenWrapper = shadowRoot.querySelector("#target-open");
|
|
3200
3387
|
this.#resizeThrottle = throttleAnimationFrame(this.#updateOrientation);
|
|
3388
|
+
this.#scrollPositionThrottle = throttleAnimationFrame(() => {
|
|
3389
|
+
this.#updatePosition(false);
|
|
3390
|
+
});
|
|
3201
3391
|
this.#resizeObserver = new ResizeObserver(() => {
|
|
3202
3392
|
this.#resizeThrottle.fn();
|
|
3203
3393
|
});
|
|
@@ -3227,6 +3417,7 @@ class Pop extends NectaryElement {
|
|
|
3227
3417
|
this.#controller.abort();
|
|
3228
3418
|
this.#controller = null;
|
|
3229
3419
|
this.#resizeThrottle.cancel();
|
|
3420
|
+
this.#scrollPositionThrottle.cancel();
|
|
3230
3421
|
this.#resizeObserver.disconnect();
|
|
3231
3422
|
this.#onCollapse();
|
|
3232
3423
|
}
|
|
@@ -3322,10 +3513,68 @@ class Pop extends NectaryElement {
|
|
|
3322
3513
|
}
|
|
3323
3514
|
return item;
|
|
3324
3515
|
}
|
|
3516
|
+
#prepareTransferredTarget() {
|
|
3517
|
+
const targetEl = this.#getFirstTargetElement(this.#$targetSlot);
|
|
3518
|
+
const targetElComputedStyle = getComputedStyle(targetEl);
|
|
3519
|
+
const marginLeft = parseInt(targetElComputedStyle.marginLeft, 10);
|
|
3520
|
+
const marginRight = parseInt(targetElComputedStyle.marginRight, 10);
|
|
3521
|
+
const marginTop = parseInt(targetElComputedStyle.marginTop, 10);
|
|
3522
|
+
const marginBottom = parseInt(targetElComputedStyle.marginBottom, 10);
|
|
3523
|
+
const targetRect = this.#getTargetRect();
|
|
3524
|
+
this.#$targetWrapper.style.setProperty("display", "block");
|
|
3525
|
+
this.#$targetWrapper.style.setProperty("width", `${targetRect.width + marginLeft + marginRight}px`);
|
|
3526
|
+
this.#$targetWrapper.style.setProperty("height", `${targetRect.height + marginTop + marginBottom}px`);
|
|
3527
|
+
this.#$targetOpenWrapper.style.setProperty("width", `${targetRect.width}px`);
|
|
3528
|
+
this.#$targetOpenWrapper.style.setProperty("height", `${targetRect.height}px`);
|
|
3529
|
+
this.#openSession.targetStyleValue = targetEl.getAttribute("style");
|
|
3530
|
+
targetEl.style.setProperty("margin", "0");
|
|
3531
|
+
targetEl.style.setProperty("position", "static");
|
|
3532
|
+
if (targetElComputedStyle.transform !== "none") {
|
|
3533
|
+
const matrix = new DOMMatrixReadOnly(targetElComputedStyle.transform);
|
|
3534
|
+
targetEl.style.setProperty("transform", matrix.translate(-matrix.e, -matrix.f).toString());
|
|
3535
|
+
}
|
|
3536
|
+
getFirstSlotElement(this.#$targetSlot)?.setAttribute("slot", "target-open");
|
|
3537
|
+
}
|
|
3538
|
+
#restoreTransferredTarget() {
|
|
3539
|
+
const targetEl = this.#getFirstTargetElement(this.#$targetOpenSlot);
|
|
3540
|
+
const { targetStyleValue } = this.#openSession;
|
|
3541
|
+
if (targetStyleValue === null) {
|
|
3542
|
+
targetEl.style.removeProperty("margin");
|
|
3543
|
+
targetEl.style.removeProperty("position");
|
|
3544
|
+
targetEl.style.removeProperty("transform");
|
|
3545
|
+
} else {
|
|
3546
|
+
targetEl.setAttribute("style", targetStyleValue);
|
|
3547
|
+
}
|
|
3548
|
+
getFirstSlotElement(this.#$targetOpenSlot)?.setAttribute("slot", "target");
|
|
3549
|
+
this.#$targetWrapper.style.removeProperty("display");
|
|
3550
|
+
this.#$targetWrapper.style.removeProperty("width");
|
|
3551
|
+
this.#$targetWrapper.style.removeProperty("height");
|
|
3552
|
+
}
|
|
3553
|
+
#subscribeScrollTracking() {
|
|
3554
|
+
this.#scrollableParents = getScrollableParents(this.#getFirstTargetElement(this.#$targetSlot));
|
|
3555
|
+
this.#scrollableParents.forEach((el) => {
|
|
3556
|
+
el.addEventListener("scroll", this.#onScrollableParentScroll, { passive: true, capture: true });
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
3559
|
+
#unsubscribeScrollTracking() {
|
|
3560
|
+
this.#scrollableParents.forEach((el) => {
|
|
3561
|
+
el.removeEventListener("scroll", this.#onScrollableParentScroll, { capture: true });
|
|
3562
|
+
});
|
|
3563
|
+
}
|
|
3325
3564
|
#onExpand() {
|
|
3326
3565
|
if (!this.isDomConnected || this.#$dialog.open) {
|
|
3327
3566
|
return;
|
|
3328
3567
|
}
|
|
3568
|
+
const transformedAncestor = getTransformedAncestor(this);
|
|
3569
|
+
const effectiveAllowScroll = this.allowScroll || transformedAncestor != null;
|
|
3570
|
+
const shouldUseModal = this.modal && transformedAncestor == null;
|
|
3571
|
+
const openAsModal = shouldUseModal || !effectiveAllowScroll;
|
|
3572
|
+
this.#openSession = {
|
|
3573
|
+
effectiveAllowScroll,
|
|
3574
|
+
modalSemantics: shouldUseModal,
|
|
3575
|
+
targetStyleValue: null,
|
|
3576
|
+
transformedAncestor
|
|
3577
|
+
};
|
|
3329
3578
|
this.#$targetSlot.addEventListener("blur", this.#stopEventPropagation, true);
|
|
3330
3579
|
this.#$focus.setAttribute("tabindex", "-1");
|
|
3331
3580
|
this.#$focus.style.display = "block";
|
|
@@ -3335,7 +3584,7 @@ class Pop extends NectaryElement {
|
|
|
3335
3584
|
this.#$targetSlot.removeEventListener("blur", this.#stopEventPropagation, true);
|
|
3336
3585
|
this.#$focus.removeAttribute("tabindex");
|
|
3337
3586
|
this.#$focus.removeAttribute("style");
|
|
3338
|
-
if (
|
|
3587
|
+
if (openAsModal) {
|
|
3339
3588
|
this.#$dialog.showModal();
|
|
3340
3589
|
} else {
|
|
3341
3590
|
this.#$dialog.show();
|
|
@@ -3343,32 +3592,13 @@ class Pop extends NectaryElement {
|
|
|
3343
3592
|
this.#$targetWrapper.setAttribute("aria-expanded", "true");
|
|
3344
3593
|
this.#updateOrientation();
|
|
3345
3594
|
this.#resizeObserver.observe(this.#$dialog);
|
|
3346
|
-
if (
|
|
3595
|
+
if (shouldUseModal) {
|
|
3347
3596
|
getFirstFocusableElement(this.#$contentSlot)?.focus();
|
|
3348
3597
|
} else {
|
|
3349
|
-
if (!
|
|
3350
|
-
|
|
3351
|
-
const targetElComputedStyle = getComputedStyle($targetEl);
|
|
3352
|
-
const marginLeft = parseInt(targetElComputedStyle.marginLeft);
|
|
3353
|
-
const marginRight = parseInt(targetElComputedStyle.marginRight);
|
|
3354
|
-
const marginTop = parseInt(targetElComputedStyle.marginTop);
|
|
3355
|
-
const marginBottom = parseInt(targetElComputedStyle.marginBottom);
|
|
3356
|
-
const targetRect = this.#getTargetRect();
|
|
3357
|
-
this.#$targetWrapper.style.setProperty("display", "block");
|
|
3358
|
-
this.#$targetWrapper.style.setProperty("width", `${targetRect.width + marginLeft + marginRight}px`);
|
|
3359
|
-
this.#$targetWrapper.style.setProperty("height", `${targetRect.height + marginTop + marginBottom}px`);
|
|
3360
|
-
this.#$targetOpenWrapper.style.setProperty("width", `${targetRect.width}px`);
|
|
3361
|
-
this.#$targetOpenWrapper.style.setProperty("height", `${targetRect.height}px`);
|
|
3362
|
-
this.#targetStyleValue = $targetEl.getAttribute("style");
|
|
3363
|
-
$targetEl.style.setProperty("margin", "0");
|
|
3364
|
-
$targetEl.style.setProperty("position", "static");
|
|
3365
|
-
if (targetElComputedStyle.transform !== "none") {
|
|
3366
|
-
const matrix = new DOMMatrixReadOnly(targetElComputedStyle.transform);
|
|
3367
|
-
$targetEl.style.setProperty("transform", matrix.translate(-matrix.e, -matrix.f).toString());
|
|
3368
|
-
}
|
|
3369
|
-
getFirstSlotElement(this.#$targetSlot)?.setAttribute("slot", "target-open");
|
|
3598
|
+
if (!effectiveAllowScroll) {
|
|
3599
|
+
this.#prepareTransferredTarget();
|
|
3370
3600
|
}
|
|
3371
|
-
const activeSlot =
|
|
3601
|
+
const activeSlot = effectiveAllowScroll ? this.#$targetSlot : this.#$targetOpenSlot;
|
|
3372
3602
|
activeSlot.addEventListener("keydown", this.#onTargetKeydown);
|
|
3373
3603
|
if (this.#targetActiveElement !== null) {
|
|
3374
3604
|
activeSlot.addEventListener("focus", this.#stopEventPropagation, true);
|
|
@@ -3385,13 +3615,10 @@ class Pop extends NectaryElement {
|
|
|
3385
3615
|
}
|
|
3386
3616
|
}
|
|
3387
3617
|
}
|
|
3388
|
-
if (!
|
|
3618
|
+
if (!effectiveAllowScroll) {
|
|
3389
3619
|
disableOverscroll();
|
|
3390
3620
|
} else {
|
|
3391
|
-
this.#
|
|
3392
|
-
this.#scrollableParents.forEach((el) => {
|
|
3393
|
-
el.addEventListener("scroll", () => this.#updatePosition(false), { passive: true, capture: true });
|
|
3394
|
-
});
|
|
3621
|
+
this.#subscribeScrollTracking();
|
|
3395
3622
|
}
|
|
3396
3623
|
window.addEventListener("resize", this.#onResize);
|
|
3397
3624
|
requestAnimationFrame(() => {
|
|
@@ -3406,9 +3633,11 @@ class Pop extends NectaryElement {
|
|
|
3406
3633
|
if (!this.#$dialog.open) {
|
|
3407
3634
|
return;
|
|
3408
3635
|
}
|
|
3636
|
+
const openSession = this.#openSession;
|
|
3637
|
+
const effectiveAllowScroll = openSession.effectiveAllowScroll;
|
|
3409
3638
|
this.#resizeObserver.disconnect();
|
|
3410
|
-
const isNonModal = !
|
|
3411
|
-
const activeSlot =
|
|
3639
|
+
const isNonModal = !openSession.modalSemantics;
|
|
3640
|
+
const activeSlot = effectiveAllowScroll ? this.#$targetSlot : this.#$targetOpenSlot;
|
|
3412
3641
|
this.#dispatchContentVisibility(false);
|
|
3413
3642
|
activeSlot.removeEventListener("keydown", this.#onTargetKeydown);
|
|
3414
3643
|
if (isNonModal) {
|
|
@@ -3419,19 +3648,8 @@ class Pop extends NectaryElement {
|
|
|
3419
3648
|
if (isNonModal) {
|
|
3420
3649
|
activeSlot.removeEventListener("blur", this.#captureActiveElement, true);
|
|
3421
3650
|
}
|
|
3422
|
-
if (isNonModal && !
|
|
3423
|
-
|
|
3424
|
-
targetEl.style.removeProperty("margin");
|
|
3425
|
-
targetEl.style.removeProperty("position");
|
|
3426
|
-
targetEl.style.removeProperty("transform");
|
|
3427
|
-
if (this.#targetStyleValue !== null) {
|
|
3428
|
-
targetEl.setAttribute("style", this.#targetStyleValue);
|
|
3429
|
-
this.#targetStyleValue = null;
|
|
3430
|
-
}
|
|
3431
|
-
getFirstSlotElement(this.#$targetOpenSlot)?.setAttribute("slot", "target");
|
|
3432
|
-
this.#$targetWrapper.style.removeProperty("display");
|
|
3433
|
-
this.#$targetWrapper.style.removeProperty("width");
|
|
3434
|
-
this.#$targetWrapper.style.removeProperty("height");
|
|
3651
|
+
if (isNonModal && !effectiveAllowScroll) {
|
|
3652
|
+
this.#restoreTransferredTarget();
|
|
3435
3653
|
}
|
|
3436
3654
|
if (this.#targetActiveElement !== null) {
|
|
3437
3655
|
if (!isElementFocused(this.#targetActiveElement)) {
|
|
@@ -3451,57 +3669,69 @@ class Pop extends NectaryElement {
|
|
|
3451
3669
|
this.#targetActiveElement = null;
|
|
3452
3670
|
}
|
|
3453
3671
|
}
|
|
3454
|
-
if (!
|
|
3672
|
+
if (!effectiveAllowScroll) {
|
|
3455
3673
|
enableOverscroll();
|
|
3456
3674
|
} else {
|
|
3457
|
-
this.#
|
|
3458
|
-
el.removeEventListener("scroll", () => this.#updatePosition(false), { capture: true });
|
|
3459
|
-
});
|
|
3675
|
+
this.#unsubscribeScrollTracking();
|
|
3460
3676
|
}
|
|
3461
3677
|
this.#resizeThrottle.cancel();
|
|
3678
|
+
this.#scrollPositionThrottle.cancel();
|
|
3462
3679
|
window.removeEventListener("resize", this.#onResize);
|
|
3463
3680
|
this.#scrollableParents = [];
|
|
3464
3681
|
this.#$contentSlot.removeEventListener("slotchange", this.#onContentSlotChange);
|
|
3682
|
+
this.#openSession = {
|
|
3683
|
+
effectiveAllowScroll: false,
|
|
3684
|
+
modalSemantics: false,
|
|
3685
|
+
targetStyleValue: null,
|
|
3686
|
+
transformedAncestor: null
|
|
3687
|
+
};
|
|
3465
3688
|
}
|
|
3466
3689
|
#onResize = () => {
|
|
3467
3690
|
this.#resizeThrottle.fn();
|
|
3468
3691
|
};
|
|
3692
|
+
#onScrollableParentScroll = () => {
|
|
3693
|
+
this.#scrollPositionThrottle.fn();
|
|
3694
|
+
};
|
|
3469
3695
|
#updatePosition = (updateWidth) => {
|
|
3470
|
-
const
|
|
3696
|
+
const placementContext = getPlacementContext(this, this.#openSession.transformedAncestor);
|
|
3697
|
+
const { scaleX, scaleY, boundsWidth, boundsHeight } = placementContext;
|
|
3698
|
+
const { modalSemantics, effectiveAllowScroll } = this.#openSession;
|
|
3699
|
+
const shouldClamp = !effectiveAllowScroll;
|
|
3700
|
+
const targetRectViewport = modalSemantics || effectiveAllowScroll ? this.#getTargetRect() : this.#$targetWrapper.getBoundingClientRect();
|
|
3471
3701
|
const orient = this.orientation;
|
|
3472
|
-
const modalWidth = this.#modalWidth;
|
|
3473
|
-
const modalHeight = this.#modalHeight;
|
|
3474
3702
|
const inset = this.inset;
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
}
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3703
|
+
const insetX = inset / scaleX;
|
|
3704
|
+
const insetY = inset / scaleY;
|
|
3705
|
+
const targetRect = toLocalRect(targetRectViewport, placementContext);
|
|
3706
|
+
const liveRect = this.#$dialog.open ? this.#$dialog.getBoundingClientRect() : null;
|
|
3707
|
+
const isStretch = orient === "top-stretch" || orient === "bottom-stretch";
|
|
3708
|
+
const modalWidthViewport = isStretch ? this.#modalWidth : liveRect?.width ?? this.#modalWidth;
|
|
3709
|
+
const modalHeightViewport = liveRect?.height ?? this.#modalHeight;
|
|
3710
|
+
const modalWidth = modalWidthViewport / scaleX;
|
|
3711
|
+
const modalHeight = modalHeightViewport / scaleY;
|
|
3712
|
+
const localPos = getAnchorPosition(targetRect, modalWidth, modalHeight, orient);
|
|
3713
|
+
const xPos = localPos.x;
|
|
3714
|
+
const yPos = localPos.y;
|
|
3715
|
+
const localViewportInfos = { boundsWidth, boundsHeight, insetX, insetY, modalWidth, modalHeight };
|
|
3716
|
+
const clampedPosition = shouldClamp ? clampPosition({ x: xPos, y: yPos, ...localViewportInfos }) : { x: xPos, y: yPos };
|
|
3717
|
+
const clampedXPos = clampedPosition.x;
|
|
3718
|
+
const clampedYPos = clampedPosition.y;
|
|
3719
|
+
if (this.hideOutsideViewport) {
|
|
3720
|
+
const viewportPosition = getAnchorPosition(targetRectViewport, modalWidthViewport, modalHeightViewport, orient);
|
|
3721
|
+
const visibilityViewportInfos = {
|
|
3722
|
+
boundsWidth: window.innerWidth,
|
|
3723
|
+
boundsHeight: window.innerHeight,
|
|
3724
|
+
insetX: inset,
|
|
3725
|
+
insetY: inset,
|
|
3726
|
+
modalWidth: modalWidthViewport,
|
|
3727
|
+
modalHeight: modalHeightViewport
|
|
3728
|
+
};
|
|
3729
|
+
const isOutOfViewport = this.#isOutsideViewport(viewportPosition.x, viewportPosition.y, visibilityViewportInfos);
|
|
3730
|
+
if (isOutOfViewport) {
|
|
3731
|
+
this.#$dialog.style.setProperty("visibility", "hidden");
|
|
3732
|
+
} else {
|
|
3733
|
+
this.#$dialog.style.removeProperty("visibility");
|
|
3734
|
+
}
|
|
3505
3735
|
} else {
|
|
3506
3736
|
this.#$dialog.style.removeProperty("visibility");
|
|
3507
3737
|
}
|
|
@@ -3510,7 +3740,7 @@ class Pop extends NectaryElement {
|
|
|
3510
3740
|
if (updateWidth === true) {
|
|
3511
3741
|
this.#$dialog.style.setProperty("width", `${modalWidth}px`);
|
|
3512
3742
|
}
|
|
3513
|
-
if (!
|
|
3743
|
+
if (!modalSemantics && !effectiveAllowScroll) {
|
|
3514
3744
|
const targetLeftPos = targetRect.x - clampedXPos;
|
|
3515
3745
|
const targetTopPos = targetRect.y - clampedYPos;
|
|
3516
3746
|
this.#$targetOpenWrapper.style.setProperty("left", `${targetLeftPos}px`);
|
|
@@ -3586,13 +3816,10 @@ class Pop extends NectaryElement {
|
|
|
3586
3816
|
this.#updateOrientation();
|
|
3587
3817
|
}
|
|
3588
3818
|
};
|
|
3589
|
-
#
|
|
3590
|
-
const
|
|
3591
|
-
const
|
|
3592
|
-
|
|
3593
|
-
const clampedX = Math.max(inset, Math.min(x, window.innerWidth - modalWidth - inset));
|
|
3594
|
-
const clampedY = Math.max(inset, Math.min(y, window.innerHeight - modalHeight - inset));
|
|
3595
|
-
return Math.abs(clampedX - x) > 2 || Math.abs(clampedY - y) > 2;
|
|
3819
|
+
#isOutsideViewport(x, y, viewportInfos) {
|
|
3820
|
+
const { boundsWidth, boundsHeight, insetX, insetY, modalWidth, modalHeight } = viewportInfos;
|
|
3821
|
+
const clampedPosition = clampPosition({ x, y, boundsWidth, boundsHeight, insetX, insetY, modalWidth, modalHeight });
|
|
3822
|
+
return Math.abs(clampedPosition.x - x) > 2 || Math.abs(clampedPosition.y - y) > 2;
|
|
3596
3823
|
}
|
|
3597
3824
|
}
|
|
3598
3825
|
defineCustomElement("sinch-pop", Pop);
|
|
@@ -3756,6 +3983,10 @@ const SHOW_DELAY_SLOW = 1e3;
|
|
|
3756
3983
|
const SHOW_DELAY_FAST = 250;
|
|
3757
3984
|
const HIDE_DELAY = 0;
|
|
3758
3985
|
const ANIMATION_DURATION = 100;
|
|
3986
|
+
const OVERLAP_TOLERANCE = 1;
|
|
3987
|
+
const MAX_ZERO_DIMENSION_PLACEMENT_RETRIES = 8;
|
|
3988
|
+
const MIN_FIRST_REVEAL_STABILITY_FRAMES = 3;
|
|
3989
|
+
const MAX_FIRST_REVEAL_STABILITY_FRAMES = 6;
|
|
3759
3990
|
const template$T = document.createElement("template");
|
|
3760
3991
|
template$T.innerHTML = templateHTML$T;
|
|
3761
3992
|
class Tooltip extends NectaryElement {
|
|
@@ -3765,11 +3996,16 @@ class Tooltip extends NectaryElement {
|
|
|
3765
3996
|
#$contentWrapper;
|
|
3766
3997
|
#$tip;
|
|
3767
3998
|
#$target;
|
|
3768
|
-
#
|
|
3999
|
+
#resizeObserver = null;
|
|
3769
4000
|
#tooltipState;
|
|
3770
4001
|
#animation = null;
|
|
3771
4002
|
#shouldReduceMotion = false;
|
|
3772
4003
|
#isSubscribed = false;
|
|
4004
|
+
#controller;
|
|
4005
|
+
#placementScheduled = false;
|
|
4006
|
+
#zeroDimensionPlacementRetries = 0;
|
|
4007
|
+
#revealRequestId = 0;
|
|
4008
|
+
#hasCompletedFirstReveal = false;
|
|
3773
4009
|
constructor() {
|
|
3774
4010
|
super();
|
|
3775
4011
|
const shadowRoot = this.attachShadow();
|
|
@@ -3781,6 +4017,7 @@ class Tooltip extends NectaryElement {
|
|
|
3781
4017
|
this.#$tip = shadowRoot.querySelector("#tip");
|
|
3782
4018
|
this.#$target = shadowRoot.querySelector("#target");
|
|
3783
4019
|
this.#shouldReduceMotion = shouldReduceMotion();
|
|
4020
|
+
this.#controller = null;
|
|
3784
4021
|
this.#tooltipState = new TooltipState({
|
|
3785
4022
|
showDelay: SHOW_DELAY_SLOW,
|
|
3786
4023
|
hideDelay: this.#shouldReduceMotion ? HIDE_DELAY + ANIMATION_DURATION : HIDE_DELAY,
|
|
@@ -3801,6 +4038,10 @@ class Tooltip extends NectaryElement {
|
|
|
3801
4038
|
this.#$pop.addEventListener("-close", this.#onPopClose, options);
|
|
3802
4039
|
this.addEventListener("-show", this.#onShowReactHandler, options);
|
|
3803
4040
|
this.addEventListener("-hide", this.#onHideReactHandler, options);
|
|
4041
|
+
this.#resizeObserver = new ResizeObserver(() => {
|
|
4042
|
+
this.#schedulePlacement();
|
|
4043
|
+
});
|
|
4044
|
+
this.#resizeObserver.observe(this.#$content);
|
|
3804
4045
|
updateAttribute(this.#$pop, "orientation", getPopOrientation$1(this.orientation));
|
|
3805
4046
|
updateBooleanAttribute(this.#$pop, "hide-outside-viewport", !this.showOutsideViewport);
|
|
3806
4047
|
this.#updateText();
|
|
@@ -3810,6 +4051,8 @@ class Tooltip extends NectaryElement {
|
|
|
3810
4051
|
this.#tooltipState.destroy();
|
|
3811
4052
|
this.#controller.abort();
|
|
3812
4053
|
this.#controller = null;
|
|
4054
|
+
this.#resizeObserver?.disconnect();
|
|
4055
|
+
this.#resizeObserver = null;
|
|
3813
4056
|
}
|
|
3814
4057
|
static get observedAttributes() {
|
|
3815
4058
|
return [
|
|
@@ -3941,9 +4184,13 @@ class Tooltip extends NectaryElement {
|
|
|
3941
4184
|
};
|
|
3942
4185
|
// SHOW_DELAY ended, tooltip can be shown with animation
|
|
3943
4186
|
#onStateShowEnd = () => {
|
|
4187
|
+
const revealRequestId = ++this.#revealRequestId;
|
|
3944
4188
|
this.#dispatchShowEvent();
|
|
3945
4189
|
updateBooleanAttribute(this.#$pop, "open", true);
|
|
3946
|
-
|
|
4190
|
+
this.#schedulePlacement();
|
|
4191
|
+
this.#scheduleReveal(revealRequestId);
|
|
4192
|
+
};
|
|
4193
|
+
#playShowAnimation() {
|
|
3947
4194
|
if (this.#animation !== null) {
|
|
3948
4195
|
this.#animation.updatePlaybackRate(1);
|
|
3949
4196
|
this.#animation.play();
|
|
@@ -3956,20 +4203,64 @@ class Tooltip extends NectaryElement {
|
|
|
3956
4203
|
fill: "forwards"
|
|
3957
4204
|
});
|
|
3958
4205
|
}
|
|
3959
|
-
}
|
|
4206
|
+
}
|
|
4207
|
+
#isRectStable(previousRect, nextRect) {
|
|
4208
|
+
return Math.abs(previousRect.x - nextRect.x) < 0.5 && Math.abs(previousRect.y - nextRect.y) < 0.5 && Math.abs(previousRect.width - nextRect.width) < 0.5 && Math.abs(previousRect.height - nextRect.height) < 0.5;
|
|
4209
|
+
}
|
|
4210
|
+
#scheduleReveal(revealRequestId) {
|
|
4211
|
+
const reveal = () => {
|
|
4212
|
+
if (!this.isDomConnected || !this.#isOpen() || this.#revealRequestId !== revealRequestId) {
|
|
4213
|
+
return;
|
|
4214
|
+
}
|
|
4215
|
+
this.#playShowAnimation();
|
|
4216
|
+
this.#hasCompletedFirstReveal = true;
|
|
4217
|
+
};
|
|
4218
|
+
if (this.#hasCompletedFirstReveal) {
|
|
4219
|
+
reveal();
|
|
4220
|
+
return;
|
|
4221
|
+
}
|
|
4222
|
+
let previousRect = null;
|
|
4223
|
+
let observedFrames = 0;
|
|
4224
|
+
let remainingFrames = MAX_FIRST_REVEAL_STABILITY_FRAMES;
|
|
4225
|
+
const waitForStableRect = () => {
|
|
4226
|
+
if (!this.isDomConnected || !this.#isOpen() || this.#revealRequestId !== revealRequestId) {
|
|
4227
|
+
return;
|
|
4228
|
+
}
|
|
4229
|
+
const nextRect = this.#$pop.popoverRect;
|
|
4230
|
+
if (observedFrames >= MIN_FIRST_REVEAL_STABILITY_FRAMES && previousRect !== null && this.#isRectStable(previousRect, nextRect)) {
|
|
4231
|
+
reveal();
|
|
4232
|
+
return;
|
|
4233
|
+
}
|
|
4234
|
+
if (remainingFrames === 0) {
|
|
4235
|
+
reveal();
|
|
4236
|
+
return;
|
|
4237
|
+
}
|
|
4238
|
+
previousRect = nextRect;
|
|
4239
|
+
observedFrames += 1;
|
|
4240
|
+
remainingFrames -= 1;
|
|
4241
|
+
requestAnimationFrame(waitForStableRect);
|
|
4242
|
+
};
|
|
4243
|
+
requestAnimationFrame(waitForStableRect);
|
|
4244
|
+
}
|
|
3960
4245
|
// HIDE_DELAY ended, begin tooltip hide animation
|
|
3961
4246
|
#onStateHideStart = () => {
|
|
4247
|
+
this.#revealRequestId += 1;
|
|
4248
|
+
if (this.#animation === null) {
|
|
4249
|
+
return;
|
|
4250
|
+
}
|
|
3962
4251
|
this.#animation.updatePlaybackRate(-1);
|
|
3963
4252
|
this.#animation.play();
|
|
3964
4253
|
};
|
|
3965
4254
|
// Hide animation ended, tooltip can be hidden
|
|
3966
4255
|
#onStateHideEnd = () => {
|
|
3967
4256
|
if (this.#isOpen()) {
|
|
3968
|
-
this.#animation
|
|
4257
|
+
this.#animation?.finish();
|
|
3969
4258
|
this.#dispatchHideEvent();
|
|
3970
4259
|
updateBooleanAttribute(this.#$pop, "open", false);
|
|
3971
4260
|
}
|
|
3972
4261
|
this.#resetTipOrientation();
|
|
4262
|
+
this.#resetContentOffset();
|
|
4263
|
+
this.#zeroDimensionPlacementRetries = 0;
|
|
3973
4264
|
this.#unsubscribeMouseLeaveEvents();
|
|
3974
4265
|
this.#unsubscribeScroll();
|
|
3975
4266
|
};
|
|
@@ -3977,26 +4268,125 @@ class Tooltip extends NectaryElement {
|
|
|
3977
4268
|
this.#$tip.style.top = "";
|
|
3978
4269
|
this.#$tip.style.left = "";
|
|
3979
4270
|
}
|
|
3980
|
-
#
|
|
4271
|
+
#resetContentOffset() {
|
|
4272
|
+
this.#$pop.style.removeProperty("--sinch-pop-offset-x");
|
|
4273
|
+
this.#$pop.style.removeProperty("--sinch-pop-offset-y");
|
|
4274
|
+
}
|
|
4275
|
+
#schedulePlacement(resetZeroDimensionRetries = true) {
|
|
4276
|
+
if (!this.#isOpen() || this.#placementScheduled) {
|
|
4277
|
+
return;
|
|
4278
|
+
}
|
|
4279
|
+
if (resetZeroDimensionRetries) {
|
|
4280
|
+
this.#zeroDimensionPlacementRetries = 0;
|
|
4281
|
+
}
|
|
4282
|
+
this.#placementScheduled = true;
|
|
4283
|
+
requestAnimationFrame(() => {
|
|
4284
|
+
this.#placementScheduled = false;
|
|
4285
|
+
if (!this.isDomConnected || !this.#isOpen()) {
|
|
4286
|
+
return;
|
|
4287
|
+
}
|
|
4288
|
+
this.#updatePlacement();
|
|
4289
|
+
});
|
|
4290
|
+
}
|
|
4291
|
+
#applyContentOffset(offsetX, offsetY) {
|
|
4292
|
+
if (offsetX === 0 && offsetY === 0) {
|
|
4293
|
+
this.#$pop.style.removeProperty("--sinch-pop-offset-x");
|
|
4294
|
+
this.#$pop.style.removeProperty("--sinch-pop-offset-y");
|
|
4295
|
+
return;
|
|
4296
|
+
}
|
|
4297
|
+
this.#$pop.style.setProperty("--sinch-pop-offset-x", `${offsetX}px`);
|
|
4298
|
+
this.#$pop.style.setProperty("--sinch-pop-offset-y", `${offsetY}px`);
|
|
4299
|
+
}
|
|
4300
|
+
#updatePlacement = () => {
|
|
4301
|
+
if (!this.isDomConnected || !this.#isOpen()) {
|
|
4302
|
+
return;
|
|
4303
|
+
}
|
|
4304
|
+
const popRect = this.#$pop.popoverRect;
|
|
4305
|
+
if (popRect.width === 0 || popRect.height === 0) {
|
|
4306
|
+
if (this.#zeroDimensionPlacementRetries >= MAX_ZERO_DIMENSION_PLACEMENT_RETRIES) {
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4309
|
+
this.#zeroDimensionPlacementRetries += 1;
|
|
4310
|
+
this.#schedulePlacement(false);
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
this.#zeroDimensionPlacementRetries = 0;
|
|
4314
|
+
const placementContext = getPlacementContext(this.#$pop);
|
|
4315
|
+
this.#resetContentOffset();
|
|
4316
|
+
this.#updateTipOrientation(placementContext);
|
|
4317
|
+
const didOffset = this.#resolveOverlap(placementContext);
|
|
4318
|
+
if (didOffset) {
|
|
4319
|
+
requestAnimationFrame(() => {
|
|
4320
|
+
if (!this.isDomConnected || !this.#isOpen()) {
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4323
|
+
this.#updateTipOrientation(placementContext);
|
|
4324
|
+
});
|
|
4325
|
+
}
|
|
4326
|
+
};
|
|
4327
|
+
#resolveOverlap(placementContext) {
|
|
4328
|
+
const orientation = this.orientation;
|
|
4329
|
+
const targetRect = toLocalRect(this.#$pop.footprintRect, placementContext);
|
|
4330
|
+
const contentRect = toLocalRect(this.#$content.getBoundingClientRect(), placementContext);
|
|
4331
|
+
const tipRect = toLocalRect(this.#$tip.getBoundingClientRect(), placementContext);
|
|
4332
|
+
const targetBottom = targetRect.y + targetRect.height;
|
|
4333
|
+
const targetRight = targetRect.x + targetRect.width;
|
|
4334
|
+
const bottomEdge = Math.max(contentRect.y + contentRect.height, tipRect.y + tipRect.height);
|
|
4335
|
+
const topEdge = Math.min(contentRect.y, tipRect.y);
|
|
4336
|
+
const rightEdge = Math.max(contentRect.x + contentRect.width, tipRect.x + tipRect.width);
|
|
4337
|
+
const leftEdge = Math.min(contentRect.x, tipRect.x);
|
|
4338
|
+
let offsetX = 0;
|
|
4339
|
+
let offsetY = 0;
|
|
4340
|
+
if (orientation.startsWith("top")) {
|
|
4341
|
+
if (bottomEdge > targetRect.y + OVERLAP_TOLERANCE) {
|
|
4342
|
+
offsetY = targetRect.y - bottomEdge;
|
|
4343
|
+
}
|
|
4344
|
+
} else if (orientation.startsWith("bottom")) {
|
|
4345
|
+
if (topEdge < targetBottom - OVERLAP_TOLERANCE) {
|
|
4346
|
+
offsetY = targetBottom - topEdge;
|
|
4347
|
+
}
|
|
4348
|
+
} else if (orientation === "left") {
|
|
4349
|
+
if (rightEdge > targetRect.x + OVERLAP_TOLERANCE) {
|
|
4350
|
+
offsetX = targetRect.x - rightEdge;
|
|
4351
|
+
}
|
|
4352
|
+
} else if (orientation === "right") {
|
|
4353
|
+
if (leftEdge < targetRight - OVERLAP_TOLERANCE) {
|
|
4354
|
+
offsetX = targetRight - leftEdge;
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
this.#applyContentOffset(offsetX, offsetY);
|
|
4358
|
+
return offsetX !== 0 || offsetY !== 0;
|
|
4359
|
+
}
|
|
4360
|
+
#updateTipOrientation = (placementContext) => {
|
|
3981
4361
|
const orient = this.orientation;
|
|
3982
4362
|
if (!("footprintRect" in this.#$pop)) {
|
|
3983
|
-
requestAnimationFrame(
|
|
4363
|
+
requestAnimationFrame(() => {
|
|
4364
|
+
if (!this.isDomConnected || !this.#isOpen()) {
|
|
4365
|
+
return;
|
|
4366
|
+
}
|
|
4367
|
+
this.#updateTipOrientation();
|
|
4368
|
+
});
|
|
3984
4369
|
return;
|
|
3985
4370
|
}
|
|
3986
|
-
const
|
|
3987
|
-
const
|
|
4371
|
+
const ctx = placementContext ?? getPlacementContext(this.#$pop);
|
|
4372
|
+
const targetRect = toLocalRect(this.#$pop.footprintRect, ctx);
|
|
4373
|
+
const contentRect = toLocalRect(this.#$content.getBoundingClientRect(), ctx);
|
|
3988
4374
|
const diffX = targetRect.x - contentRect.x;
|
|
3989
4375
|
const diffY = targetRect.y - contentRect.y;
|
|
4376
|
+
const targetWidth = targetRect.width;
|
|
4377
|
+
const targetHeight = targetRect.height;
|
|
4378
|
+
const contentWidth = contentRect.width;
|
|
4379
|
+
const contentHeight = contentRect.height;
|
|
3990
4380
|
if (orient === "left" || orient === "right") {
|
|
3991
|
-
const yPos = Math.max(TIP_SIZE$1, Math.min(diffY +
|
|
4381
|
+
const yPos = Math.max(TIP_SIZE$1, Math.min(diffY + targetHeight / 2, contentHeight - TIP_SIZE$1));
|
|
3992
4382
|
this.#$tip.style.top = `${yPos}px`;
|
|
3993
4383
|
} else {
|
|
3994
|
-
let xPos = Math.max(TIP_SIZE$1, Math.min(diffX +
|
|
4384
|
+
let xPos = Math.max(TIP_SIZE$1, Math.min(diffX + targetWidth / 2, contentWidth - TIP_SIZE$1));
|
|
3995
4385
|
if (orient === "bottom-left" || orient === "top-left") {
|
|
3996
|
-
xPos = Math.max(xPos,
|
|
4386
|
+
xPos = Math.max(xPos, contentWidth * 0.75);
|
|
3997
4387
|
}
|
|
3998
4388
|
if (orient === "bottom-right" || orient === "top-right") {
|
|
3999
|
-
xPos = Math.min(xPos,
|
|
4389
|
+
xPos = Math.min(xPos, contentWidth * 0.25);
|
|
4000
4390
|
}
|
|
4001
4391
|
this.#$tip.style.left = `${xPos}px`;
|
|
4002
4392
|
}
|