@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 +140 -80
- package/icon/generated-icon-type.d.ts +1 -1
- package/package.json +1 -1
- package/pop/index.d.ts +3 -0
- package/pop/index.js +93 -76
- package/pop/types.d.ts +3 -0
- package/tooltip/index.d.ts +1 -0
- package/tooltip/index.js +23 -4
- package/tooltip/tooltip-state.d.ts +1 -0
- package/tooltip/tooltip-state.js +9 -1
- package/tooltip/types.d.ts +1 -0
- package/utils/dom.d.ts +1 -0
- package/utils/dom.js +17 -0
- package/utils/index.js +2 -1
package/pop/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Context, subscribeContext } from "../utils/context.js";
|
|
2
|
-
import { getBooleanAttribute, updateBooleanAttribute, getLiteralAttribute, updateLiteralAttribute, updateIntegerAttribute, getIntegerAttribute, isAttrEqual, isAttrTrue } from "../utils/dom.js";
|
|
2
|
+
import { getBooleanAttribute, updateBooleanAttribute, getLiteralAttribute, updateLiteralAttribute, updateIntegerAttribute, getIntegerAttribute, isAttrEqual, getScrollableParents, isAttrTrue } from "../utils/dom.js";
|
|
3
3
|
import { defineCustomElement, NectaryElement } from "../utils/element.js";
|
|
4
4
|
import { getRect } from "../utils/rect.js";
|
|
5
5
|
import { getFirstSlotElement, getFirstFocusableElement, isElementFocused } from "../utils/slot.js";
|
|
@@ -15,6 +15,7 @@ class Pop extends NectaryElement {
|
|
|
15
15
|
#$focus;
|
|
16
16
|
#$dialog;
|
|
17
17
|
#resizeThrottle;
|
|
18
|
+
#resizeObserver;
|
|
18
19
|
#$targetSlot;
|
|
19
20
|
#$targetOpenSlot;
|
|
20
21
|
#$contentSlot;
|
|
@@ -26,6 +27,7 @@ class Pop extends NectaryElement {
|
|
|
26
27
|
#targetStyleValue = null;
|
|
27
28
|
#modalWidth = 0;
|
|
28
29
|
#modalHeight = 0;
|
|
30
|
+
#scrollableParents = [];
|
|
29
31
|
constructor() {
|
|
30
32
|
super();
|
|
31
33
|
const shadowRoot = this.attachShadow();
|
|
@@ -38,6 +40,9 @@ class Pop extends NectaryElement {
|
|
|
38
40
|
this.#$contentSlot = shadowRoot.querySelector('slot[name="content"]');
|
|
39
41
|
this.#$targetOpenWrapper = shadowRoot.querySelector("#target-open");
|
|
40
42
|
this.#resizeThrottle = throttleAnimationFrame(this.#updateOrientation);
|
|
43
|
+
this.#resizeObserver = new ResizeObserver(() => {
|
|
44
|
+
this.#resizeThrottle.fn();
|
|
45
|
+
});
|
|
41
46
|
this.#keydownContext = new Context(this.#$contentSlot, "keydown");
|
|
42
47
|
this.#visibilityContext = new Context(this.#$contentSlot, "visibility");
|
|
43
48
|
this.#controller = new AbortController();
|
|
@@ -64,6 +69,7 @@ class Pop extends NectaryElement {
|
|
|
64
69
|
this.#controller.abort();
|
|
65
70
|
this.#controller = null;
|
|
66
71
|
this.#resizeThrottle.cancel();
|
|
72
|
+
this.#resizeObserver.disconnect();
|
|
67
73
|
this.#onCollapse();
|
|
68
74
|
}
|
|
69
75
|
static get observedAttributes() {
|
|
@@ -72,6 +78,12 @@ class Pop extends NectaryElement {
|
|
|
72
78
|
"open"
|
|
73
79
|
];
|
|
74
80
|
}
|
|
81
|
+
get allowScroll() {
|
|
82
|
+
return getBooleanAttribute(this, "allow-scroll");
|
|
83
|
+
}
|
|
84
|
+
get hideOutsideViewport() {
|
|
85
|
+
return getBooleanAttribute(this, "hide-outside-viewport");
|
|
86
|
+
}
|
|
75
87
|
set modal(isModal) {
|
|
76
88
|
updateBooleanAttribute(this, "modal", isModal);
|
|
77
89
|
}
|
|
@@ -102,6 +114,9 @@ class Pop extends NectaryElement {
|
|
|
102
114
|
get popoverRect() {
|
|
103
115
|
return getRect(this.#$dialog);
|
|
104
116
|
}
|
|
117
|
+
get shouldCloseOnBackdropClick() {
|
|
118
|
+
return !getBooleanAttribute(this, "disable-backdrop-close");
|
|
119
|
+
}
|
|
105
120
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
106
121
|
if (isAttrEqual(oldVal, newVal)) {
|
|
107
122
|
return;
|
|
@@ -162,74 +177,91 @@ class Pop extends NectaryElement {
|
|
|
162
177
|
this.#$targetSlot.removeEventListener("blur", this.#stopEventPropagation, true);
|
|
163
178
|
this.#$focus.removeAttribute("tabindex");
|
|
164
179
|
this.#$focus.removeAttribute("style");
|
|
165
|
-
this
|
|
180
|
+
if (this.modal || !this.allowScroll) {
|
|
181
|
+
this.#$dialog.showModal();
|
|
182
|
+
} else {
|
|
183
|
+
this.#$dialog.show();
|
|
184
|
+
}
|
|
166
185
|
this.#$targetWrapper.setAttribute("aria-expanded", "true");
|
|
167
186
|
this.#updateOrientation();
|
|
187
|
+
this.#resizeObserver.observe(this.#$dialog);
|
|
168
188
|
if (this.modal) {
|
|
169
189
|
getFirstFocusableElement(this.#$contentSlot)?.focus();
|
|
170
190
|
} else {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
191
|
+
if (!this.allowScroll) {
|
|
192
|
+
const $targetEl = this.#getFirstTargetElement(this.#$targetSlot);
|
|
193
|
+
const targetElComputedStyle = getComputedStyle($targetEl);
|
|
194
|
+
const marginLeft = parseInt(targetElComputedStyle.marginLeft);
|
|
195
|
+
const marginRight = parseInt(targetElComputedStyle.marginRight);
|
|
196
|
+
const marginTop = parseInt(targetElComputedStyle.marginTop);
|
|
197
|
+
const marginBottom = parseInt(targetElComputedStyle.marginBottom);
|
|
198
|
+
const targetRect = this.#getTargetRect();
|
|
199
|
+
this.#$targetWrapper.style.setProperty("display", "block");
|
|
200
|
+
this.#$targetWrapper.style.setProperty("width", `${targetRect.width + marginLeft + marginRight}px`);
|
|
201
|
+
this.#$targetWrapper.style.setProperty("height", `${targetRect.height + marginTop + marginBottom}px`);
|
|
202
|
+
this.#$targetOpenWrapper.style.setProperty("width", `${targetRect.width}px`);
|
|
203
|
+
this.#$targetOpenWrapper.style.setProperty("height", `${targetRect.height}px`);
|
|
204
|
+
this.#targetStyleValue = $targetEl.getAttribute("style");
|
|
205
|
+
$targetEl.style.setProperty("margin", "0");
|
|
206
|
+
$targetEl.style.setProperty("position", "static");
|
|
207
|
+
if (targetElComputedStyle.transform !== "none") {
|
|
208
|
+
const matrix = new DOMMatrixReadOnly(targetElComputedStyle.transform);
|
|
209
|
+
$targetEl.style.setProperty("transform", matrix.translate(-matrix.e, -matrix.f).toString());
|
|
210
|
+
}
|
|
211
|
+
getFirstSlotElement(this.#$targetSlot)?.setAttribute("slot", "target-open");
|
|
189
212
|
}
|
|
190
|
-
|
|
191
|
-
|
|
213
|
+
const activeSlot = this.allowScroll ? this.#$targetSlot : this.#$targetOpenSlot;
|
|
214
|
+
activeSlot.addEventListener("keydown", this.#onTargetKeydown);
|
|
192
215
|
if (this.#targetActiveElement !== null) {
|
|
193
|
-
|
|
216
|
+
activeSlot.addEventListener("focus", this.#stopEventPropagation, true);
|
|
194
217
|
this.#targetActiveElement.focus();
|
|
195
|
-
|
|
218
|
+
activeSlot.removeEventListener("focus", this.#stopEventPropagation, true);
|
|
196
219
|
if (!isElementFocused(this.#targetActiveElement)) {
|
|
197
220
|
requestAnimationFrame(() => {
|
|
198
221
|
if (this.isDomConnected && this.#$dialog.open) {
|
|
199
|
-
|
|
222
|
+
activeSlot.addEventListener("focus", this.#stopEventPropagation, true);
|
|
200
223
|
this.#targetActiveElement.focus();
|
|
201
|
-
|
|
224
|
+
activeSlot.removeEventListener("focus", this.#stopEventPropagation, true);
|
|
202
225
|
}
|
|
203
226
|
});
|
|
204
227
|
}
|
|
205
228
|
}
|
|
206
229
|
}
|
|
207
|
-
|
|
208
|
-
|
|
230
|
+
if (!this.allowScroll) {
|
|
231
|
+
disableOverscroll();
|
|
232
|
+
} else {
|
|
233
|
+
this.#scrollableParents = getScrollableParents(this.#getFirstTargetElement(this.#$targetSlot));
|
|
234
|
+
this.#scrollableParents.forEach((el) => {
|
|
235
|
+
el.addEventListener("scroll", () => this.#updatePosition(false), { passive: true, capture: true });
|
|
236
|
+
});
|
|
237
|
+
}
|
|
209
238
|
window.addEventListener("resize", this.#onResize);
|
|
210
239
|
requestAnimationFrame(() => {
|
|
211
240
|
if (this.isDomConnected && this.#$dialog.open) {
|
|
212
241
|
this.#$contentSlot.addEventListener("slotchange", this.#onContentSlotChange);
|
|
213
242
|
}
|
|
214
243
|
});
|
|
244
|
+
requestAnimationFrame(() => this.#updatePosition());
|
|
215
245
|
this.#dispatchContentVisibility(true);
|
|
216
246
|
}
|
|
217
247
|
#onCollapse() {
|
|
218
248
|
if (!this.#$dialog.open) {
|
|
219
249
|
return;
|
|
220
250
|
}
|
|
251
|
+
this.#resizeObserver.disconnect();
|
|
221
252
|
const isNonModal = !this.modal;
|
|
253
|
+
const activeSlot = this.allowScroll ? this.#$targetSlot : this.#$targetOpenSlot;
|
|
222
254
|
this.#dispatchContentVisibility(false);
|
|
223
|
-
|
|
255
|
+
activeSlot.removeEventListener("keydown", this.#onTargetKeydown);
|
|
224
256
|
if (isNonModal) {
|
|
225
|
-
|
|
257
|
+
activeSlot.addEventListener("blur", this.#captureActiveElement, true);
|
|
226
258
|
}
|
|
227
259
|
this.#$dialog.close();
|
|
228
260
|
this.#$targetWrapper.setAttribute("aria-expanded", "false");
|
|
229
261
|
if (isNonModal) {
|
|
230
|
-
|
|
262
|
+
activeSlot.removeEventListener("blur", this.#captureActiveElement, true);
|
|
231
263
|
}
|
|
232
|
-
if (isNonModal) {
|
|
264
|
+
if (isNonModal && !this.allowScroll) {
|
|
233
265
|
const targetEl = this.#getFirstTargetElement(this.#$targetOpenSlot);
|
|
234
266
|
targetEl.style.removeProperty("margin");
|
|
235
267
|
targetEl.style.removeProperty("position");
|
|
@@ -261,17 +293,23 @@ class Pop extends NectaryElement {
|
|
|
261
293
|
this.#targetActiveElement = null;
|
|
262
294
|
}
|
|
263
295
|
}
|
|
264
|
-
|
|
296
|
+
if (!this.allowScroll) {
|
|
297
|
+
enableOverscroll();
|
|
298
|
+
} else {
|
|
299
|
+
this.#scrollableParents.forEach((el) => {
|
|
300
|
+
el.removeEventListener("scroll", () => this.#updatePosition(false), { capture: true });
|
|
301
|
+
});
|
|
302
|
+
}
|
|
265
303
|
this.#resizeThrottle.cancel();
|
|
266
304
|
window.removeEventListener("resize", this.#onResize);
|
|
267
|
-
|
|
305
|
+
this.#scrollableParents = [];
|
|
268
306
|
this.#$contentSlot.removeEventListener("slotchange", this.#onContentSlotChange);
|
|
269
307
|
}
|
|
270
308
|
#onResize = () => {
|
|
271
309
|
this.#resizeThrottle.fn();
|
|
272
310
|
};
|
|
273
|
-
#updatePosition = () => {
|
|
274
|
-
const targetRect = this.modal ? this.#getTargetRect() : this.#$targetWrapper.getBoundingClientRect();
|
|
311
|
+
#updatePosition = (updateWidth) => {
|
|
312
|
+
const targetRect = this.modal || this.allowScroll ? this.#getTargetRect() : this.#$targetWrapper.getBoundingClientRect();
|
|
275
313
|
const orient = this.orientation;
|
|
276
314
|
const modalWidth = this.#modalWidth;
|
|
277
315
|
const modalHeight = this.#modalHeight;
|
|
@@ -304,9 +342,17 @@ class Pop extends NectaryElement {
|
|
|
304
342
|
}
|
|
305
343
|
const clampedXPos = Math.max(inset, Math.min(xPos, window.innerWidth - modalWidth - inset));
|
|
306
344
|
const clampedYPos = Math.max(inset, Math.min(yPos, window.innerHeight - modalHeight - inset));
|
|
345
|
+
if (this.hideOutsideViewport && this.#isPopPointInViewport(xPos, yPos)) {
|
|
346
|
+
this.#$dialog.style.setProperty("visibility", "hidden");
|
|
347
|
+
} else {
|
|
348
|
+
this.#$dialog.style.removeProperty("visibility");
|
|
349
|
+
}
|
|
307
350
|
this.#$dialog.style.setProperty("left", `${clampedXPos}px`);
|
|
308
351
|
this.#$dialog.style.setProperty("top", `${clampedYPos}px`);
|
|
309
|
-
if (
|
|
352
|
+
if (updateWidth === true) {
|
|
353
|
+
this.#$dialog.style.setProperty("width", `${modalWidth}px`);
|
|
354
|
+
}
|
|
355
|
+
if (!this.modal && !this.allowScroll) {
|
|
310
356
|
const targetLeftPos = targetRect.x - clampedXPos;
|
|
311
357
|
const targetTopPos = targetRect.y - clampedYPos;
|
|
312
358
|
this.#$targetOpenWrapper.style.setProperty("left", `${targetLeftPos}px`);
|
|
@@ -321,49 +367,12 @@ class Pop extends NectaryElement {
|
|
|
321
367
|
const shouldSetWidthToTarget = orient === "top-stretch" || orient === "bottom-stretch";
|
|
322
368
|
const modalHeight = modalRect.height;
|
|
323
369
|
const modalWidth = shouldSetWidthToTarget ? targetRect.width : modalRect.width;
|
|
324
|
-
const inset = this.inset;
|
|
325
|
-
let xPos = 0;
|
|
326
|
-
let yPos = 0;
|
|
327
370
|
this.#modalHeight = modalHeight;
|
|
328
371
|
this.#modalWidth = modalWidth;
|
|
329
|
-
|
|
330
|
-
xPos = targetRect.x;
|
|
331
|
-
}
|
|
332
|
-
if (orient === "bottom-left" || orient === "top-left") {
|
|
333
|
-
xPos = targetRect.x + targetRect.width - modalWidth;
|
|
334
|
-
}
|
|
335
|
-
if (orient === "bottom-center" || orient === "top-center") {
|
|
336
|
-
xPos = targetRect.x + targetRect.width / 2 - modalWidth / 2;
|
|
337
|
-
}
|
|
338
|
-
if (orient === "center-right") {
|
|
339
|
-
xPos = targetRect.x + targetRect.width;
|
|
340
|
-
}
|
|
341
|
-
if (orient === "center-left") {
|
|
342
|
-
xPos = targetRect.x - modalWidth;
|
|
343
|
-
}
|
|
344
|
-
if (orient === "bottom-left" || orient === "bottom-right" || orient === "bottom-stretch" || orient === "bottom-center") {
|
|
345
|
-
yPos = targetRect.y + targetRect.height;
|
|
346
|
-
}
|
|
347
|
-
if (orient === "top-left" || orient === "top-right" || orient === "top-stretch" || orient === "top-center") {
|
|
348
|
-
yPos = targetRect.y - modalHeight;
|
|
349
|
-
}
|
|
350
|
-
if (orient === "center-left" || orient === "center-right") {
|
|
351
|
-
yPos = targetRect.y + targetRect.height / 2 - modalHeight / 2;
|
|
352
|
-
}
|
|
353
|
-
xPos = Math.round(Math.max(inset, Math.min(xPos, window.innerWidth - modalWidth - inset)));
|
|
354
|
-
yPos = Math.round(Math.max(inset, Math.min(yPos, window.innerHeight - modalHeight - inset)));
|
|
355
|
-
this.#$dialog.style.setProperty("left", `${xPos}px`);
|
|
356
|
-
this.#$dialog.style.setProperty("top", `${yPos}px`);
|
|
357
|
-
this.#$dialog.style.setProperty("width", `${modalWidth}px`);
|
|
358
|
-
if (!this.modal) {
|
|
359
|
-
const targetLeftPos = targetRect.x - xPos;
|
|
360
|
-
const targetTopPos = targetRect.y - yPos;
|
|
361
|
-
this.#$targetOpenWrapper.style.setProperty("left", `${targetLeftPos}px`);
|
|
362
|
-
this.#$targetOpenWrapper.style.setProperty("top", `${targetTopPos}px`);
|
|
363
|
-
}
|
|
372
|
+
this.#updatePosition(true);
|
|
364
373
|
};
|
|
365
374
|
#onBackdropMouseDown = (e) => {
|
|
366
|
-
if (isTargetEqual(e, this.#$dialog)) {
|
|
375
|
+
if (this.shouldCloseOnBackdropClick && isTargetEqual(e, this.#$dialog)) {
|
|
367
376
|
const rect = this.popoverRect;
|
|
368
377
|
const isInside = e.x >= rect.x && e.x < rect.x + rect.width && e.y >= rect.y && e.y < rect.y + rect.height;
|
|
369
378
|
if (!isInside) {
|
|
@@ -419,6 +428,14 @@ class Pop extends NectaryElement {
|
|
|
419
428
|
this.#updateOrientation();
|
|
420
429
|
}
|
|
421
430
|
};
|
|
431
|
+
#isPopPointInViewport(x, y) {
|
|
432
|
+
const inset = this.inset;
|
|
433
|
+
const modalWidth = this.#modalWidth;
|
|
434
|
+
const modalHeight = this.#modalHeight;
|
|
435
|
+
const clampedX = Math.max(inset, Math.min(x, window.innerWidth - modalWidth - inset));
|
|
436
|
+
const clampedY = Math.max(inset, Math.min(y, window.innerHeight - modalHeight - inset));
|
|
437
|
+
return Math.abs(clampedX - x) > 2 || Math.abs(clampedY - y) > 2;
|
|
438
|
+
}
|
|
422
439
|
}
|
|
423
440
|
defineCustomElement("sinch-pop", Pop);
|
|
424
441
|
export {
|
package/pop/types.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { NectaryComponentReactByType, NectaryComponentVanillaByType, TRect, NectaryComponentReact, NectaryComponentVanilla } from '../types';
|
|
2
2
|
export type TSinchPopOrientation = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'bottom-center' | 'bottom-stretch' | 'top-center' | 'top-stretch' | 'center-right' | 'center-left';
|
|
3
3
|
export type TSinchPopProps = {
|
|
4
|
+
/** Allow scrolling of the page when pop is open */
|
|
5
|
+
'allow-scroll'?: boolean;
|
|
4
6
|
/** Open/close state */
|
|
5
7
|
open: boolean;
|
|
6
8
|
/** Orientation, where it *points to* from origin */
|
|
7
9
|
orientation: TSinchPopOrientation;
|
|
10
|
+
'hide-outside-viewport'?: boolean;
|
|
8
11
|
/** Modal/non-modal mode */
|
|
9
12
|
modal?: boolean;
|
|
10
13
|
inset?: number;
|
package/tooltip/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare class Tooltip extends NectaryElement {
|
|
|
11
11
|
disconnectedCallback(): void;
|
|
12
12
|
static get observedAttributes(): string[];
|
|
13
13
|
attributeChangedCallback(name: string, _: string | null, newVal: string | null): void;
|
|
14
|
+
get isOpenedControlled(): boolean | undefined;
|
|
14
15
|
get text(): string;
|
|
15
16
|
set text(value: string);
|
|
16
17
|
get orientation(): TSinchTooltipOrientation;
|
package/tooltip/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import "../text/index.js";
|
|
2
2
|
import "../pop/index.js";
|
|
3
|
-
import { shouldReduceMotion, updateAttribute, getAttribute, getLiteralAttribute, updateLiteralAttribute,
|
|
3
|
+
import { shouldReduceMotion, updateAttribute, updateBooleanAttribute, getAttribute, getLiteralAttribute, updateLiteralAttribute, setClass } from "../utils/dom.js";
|
|
4
4
|
import { defineCustomElement, NectaryElement } from "../utils/element.js";
|
|
5
5
|
import { rectOverlap } from "../utils/rect.js";
|
|
6
6
|
import { getReactEventHandler } from "../utils/get-react-event-handler.js";
|
|
7
7
|
import { TooltipState } from "./tooltip-state.js";
|
|
8
8
|
import { getPopOrientation, orientationValues, textAlignValues, typeValues } from "./utils.js";
|
|
9
|
-
const templateHTML = '<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>';
|
|
9
|
+
const templateHTML = '<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>';
|
|
10
10
|
const TIP_SIZE = 8;
|
|
11
11
|
const SHOW_DELAY_SLOW = 1e3;
|
|
12
12
|
const SHOW_DELAY_FAST = 250;
|
|
@@ -68,6 +68,7 @@ class Tooltip extends NectaryElement {
|
|
|
68
68
|
}
|
|
69
69
|
static get observedAttributes() {
|
|
70
70
|
return [
|
|
71
|
+
"is-opened",
|
|
71
72
|
"text",
|
|
72
73
|
"orientation",
|
|
73
74
|
"text-align",
|
|
@@ -105,8 +106,24 @@ class Tooltip extends NectaryElement {
|
|
|
105
106
|
updateAttribute(this.#$pop, name, newVal);
|
|
106
107
|
break;
|
|
107
108
|
}
|
|
109
|
+
case "is-opened": {
|
|
110
|
+
this.#tooltipState.updateOptions({
|
|
111
|
+
isOpened: this.isOpenedControlled
|
|
112
|
+
});
|
|
113
|
+
if (this.isOpenedControlled === true) {
|
|
114
|
+
updateBooleanAttribute(this.#$pop, "disable-backdrop-close", true);
|
|
115
|
+
this.#tooltipState.show();
|
|
116
|
+
} else if (this.isOpenedControlled === false) {
|
|
117
|
+
updateBooleanAttribute(this.#$pop, "disable-backdrop-close", false);
|
|
118
|
+
this.#tooltipState.hide();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
108
121
|
}
|
|
109
122
|
}
|
|
123
|
+
get isOpenedControlled() {
|
|
124
|
+
const isOpenedAttr = getAttribute(this, "is-opened");
|
|
125
|
+
return isOpenedAttr === null ? void 0 : isOpenedAttr !== "false";
|
|
126
|
+
}
|
|
110
127
|
get text() {
|
|
111
128
|
return getAttribute(this, "text", "");
|
|
112
129
|
}
|
|
@@ -158,8 +175,10 @@ class Tooltip extends NectaryElement {
|
|
|
158
175
|
};
|
|
159
176
|
// Tooltip begins to wait for SHOW_DELAY on mouseenter
|
|
160
177
|
#onStateShowStart = () => {
|
|
161
|
-
this
|
|
162
|
-
|
|
178
|
+
if (this.isOpenedControlled === void 0) {
|
|
179
|
+
this.#subscribeScroll();
|
|
180
|
+
this.#subscribeMouseLeaveEvents();
|
|
181
|
+
}
|
|
163
182
|
};
|
|
164
183
|
// SHOW_DELAY ended, tooltip can be shown with animation
|
|
165
184
|
#onStateShowEnd = () => {
|
package/tooltip/tooltip-state.js
CHANGED
|
@@ -12,6 +12,9 @@ class TooltipState {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
show() {
|
|
15
|
+
if (this.#options.isOpened === false) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
15
18
|
switch (this.#state) {
|
|
16
19
|
case "hide": {
|
|
17
20
|
this.#switchToHideToShow();
|
|
@@ -24,6 +27,9 @@ class TooltipState {
|
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
hide() {
|
|
30
|
+
if (this.#options.isOpened === true) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
27
33
|
switch (this.#state) {
|
|
28
34
|
case "hide-to-show": {
|
|
29
35
|
this.#onHideAnimationEnd();
|
|
@@ -76,13 +82,15 @@ class TooltipState {
|
|
|
76
82
|
this.#options.onShowStart();
|
|
77
83
|
if (this.#options.showDelay === 0) {
|
|
78
84
|
this.#onSwitchToShow();
|
|
85
|
+
} else if (this.#options.isOpened !== void 0) {
|
|
86
|
+
this.#timerId = window.setTimeout(this.#onSwitchToShow, 100);
|
|
79
87
|
} else {
|
|
80
88
|
this.#timerId = window.setTimeout(this.#onSwitchToShow, this.#options.showDelay);
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
#switchToShowToHide(skipDelay, skipHideAnimation) {
|
|
84
92
|
this.#switchToState("show-to-hide");
|
|
85
|
-
if (skipDelay === true || this.#options.hideDelay === 0) {
|
|
93
|
+
if (skipDelay === true || this.#options.hideDelay === 0 || this.#options.isOpened !== void 0) {
|
|
86
94
|
this.#onShowToHideEnd(skipHideAnimation);
|
|
87
95
|
} else {
|
|
88
96
|
this.#timerId = window.setTimeout(this.#onShowToHideEnd, this.#options.hideDelay);
|
package/tooltip/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type TSinchTooltipOrientation = 'top' | 'bottom' | 'left' | 'right' | 'to
|
|
|
3
3
|
export type TSinchTooltipTextAlign = 'center' | 'right' | 'left';
|
|
4
4
|
export type TSinchTooltipType = 'slow' | 'fast';
|
|
5
5
|
export type TSinchTooltipProps = {
|
|
6
|
+
'is-opened'?: string;
|
|
6
7
|
/** Text */
|
|
7
8
|
text: string;
|
|
8
9
|
/** Orientation, where it *points to* from origin */
|
package/utils/dom.d.ts
CHANGED
|
@@ -32,4 +32,5 @@ export declare const getCssVars: (element: Element, variableNames: string[]) =>
|
|
|
32
32
|
export declare const cloneNode: (el: Element, deep: boolean) => Element;
|
|
33
33
|
export declare const shouldReduceMotion: () => boolean;
|
|
34
34
|
export declare const isAttrEqual: (oldVal: string | null, newVal: string | null) => boolean;
|
|
35
|
+
export declare const getScrollableParents: (node: HTMLElement | null) => (HTMLElement | Document)[];
|
|
35
36
|
export {};
|
package/utils/dom.js
CHANGED
|
@@ -137,6 +137,22 @@ const shouldReduceMotion = () => window.matchMedia("(prefers-reduced-motion: red
|
|
|
137
137
|
const isAttrEqual = (oldVal, newVal) => {
|
|
138
138
|
return oldVal === newVal || newVal === null && oldVal === "false" || newVal === "" && oldVal === "true";
|
|
139
139
|
};
|
|
140
|
+
const getScrollableParents = (node) => {
|
|
141
|
+
const scrollableParents = [];
|
|
142
|
+
if (node == null) {
|
|
143
|
+
return scrollableParents;
|
|
144
|
+
}
|
|
145
|
+
let parent = node.parentElement;
|
|
146
|
+
while (parent != null) {
|
|
147
|
+
const computedStyle = getComputedStyle(parent);
|
|
148
|
+
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")) {
|
|
149
|
+
scrollableParents.push(parent);
|
|
150
|
+
}
|
|
151
|
+
parent = parent.parentElement;
|
|
152
|
+
}
|
|
153
|
+
scrollableParents.push(document);
|
|
154
|
+
return scrollableParents;
|
|
155
|
+
};
|
|
140
156
|
export {
|
|
141
157
|
attrValueToInteger,
|
|
142
158
|
attrValueToPixels,
|
|
@@ -148,6 +164,7 @@ export {
|
|
|
148
164
|
getCssVars,
|
|
149
165
|
getIntegerAttribute,
|
|
150
166
|
getLiteralAttribute,
|
|
167
|
+
getScrollableParents,
|
|
151
168
|
hasClass,
|
|
152
169
|
isAttrEqual,
|
|
153
170
|
isAttrTrue,
|
package/utils/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Context, subscribeContext } from "./context.js";
|
|
2
2
|
import { CSV_DELIMITER, getFirstCsvValue, packCsv, unpackCsv, updateCsv } from "./csv.js";
|
|
3
|
-
import { attrValueToInteger, attrValueToPixels, clampNumber, cloneNode, getAttribute, getBooleanAttribute, getCssVar, getCssVars, getIntegerAttribute, getLiteralAttribute, hasClass, isAttrEqual, isAttrTrue, isLiteralValue, setClass, shouldReduceMotion, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute, updateIntegerAttribute, updateLiteralAttribute } from "./dom.js";
|
|
3
|
+
import { attrValueToInteger, attrValueToPixels, clampNumber, cloneNode, getAttribute, getBooleanAttribute, getCssVar, getCssVars, getIntegerAttribute, getLiteralAttribute, getScrollableParents, hasClass, isAttrEqual, isAttrTrue, isLiteralValue, setClass, shouldReduceMotion, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute, updateIntegerAttribute, updateLiteralAttribute } from "./dom.js";
|
|
4
4
|
import { NectaryElement, defineCustomElement, pascalToKebabCase, registerComponent, resetNectaryRegistry, setNectaryRegistry } from "./element.js";
|
|
5
5
|
import { getRect, getTargetRect, rectOverlap } from "./rect.js";
|
|
6
6
|
import { getFirstFocusableElement, getFirstSlotElement, isElementFocused } from "./slot.js";
|
|
@@ -32,6 +32,7 @@ export {
|
|
|
32
32
|
getLiteralAttribute,
|
|
33
33
|
getReactEventHandler,
|
|
34
34
|
getRect,
|
|
35
|
+
getScrollableParents,
|
|
35
36
|
getTargetAttribute,
|
|
36
37
|
getTargetByAttribute,
|
|
37
38
|
getTargetIndexInParent,
|