@justeattakeaway/pie-tooltip 0.3.0 → 0.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/README.md +12 -1
- package/custom-elements.json +112 -7
- package/dist/index.d.ts +13 -2
- package/dist/index.js +299 -196
- package/dist/react.d.ts +13 -2
- package/package.json +1 -1
- package/src/defs.ts +4 -1
- package/src/index.ts +286 -22
package/dist/react.d.ts
CHANGED
|
@@ -62,15 +62,19 @@ declare class PieTooltip_2 extends PieElement implements TooltipProps {
|
|
|
62
62
|
private _hasActionContent;
|
|
63
63
|
private _isPositioned;
|
|
64
64
|
private _isAnchorVisible;
|
|
65
|
+
private _resolvedPosition;
|
|
65
66
|
private _clippedTriggerElement;
|
|
66
67
|
private _triggerClippers;
|
|
68
|
+
private _overlayClippers;
|
|
69
|
+
private _collisionSignature;
|
|
70
|
+
private _overlayModeDirty;
|
|
67
71
|
private _triggerTrackingController;
|
|
68
72
|
private _interactionController;
|
|
69
73
|
private _directionObserver;
|
|
70
74
|
private _triggerObserver;
|
|
71
75
|
private _reanchorFrame;
|
|
72
|
-
private _shouldResolveOverlayMode;
|
|
73
76
|
private _hoverCloseTimer;
|
|
77
|
+
private _openedByClick;
|
|
74
78
|
static styles: CSSResult;
|
|
75
79
|
private get _mode();
|
|
76
80
|
protected firstUpdated(): void;
|
|
@@ -81,6 +85,10 @@ declare class PieTooltip_2 extends PieElement implements TooltipProps {
|
|
|
81
85
|
private resolveMode;
|
|
82
86
|
private resolveOverlayMode;
|
|
83
87
|
private projectOverTrigger;
|
|
88
|
+
private resolveCollision;
|
|
89
|
+
private _setResolvedPosition;
|
|
90
|
+
private _getCollisionBoundary;
|
|
91
|
+
private _getCandidatePositions;
|
|
84
92
|
private _refreshTriggerClippers;
|
|
85
93
|
private handleCloseButtonClick;
|
|
86
94
|
private _getTriggerElement;
|
|
@@ -120,7 +128,10 @@ export declare interface TooltipProps {
|
|
|
120
128
|
*/
|
|
121
129
|
isOpen?: boolean;
|
|
122
130
|
/**
|
|
123
|
-
* The side of the trigger the panel sits on, and its alignment along the cross axis.
|
|
131
|
+
* The preferred side of the trigger the panel sits on, and its alignment along the cross axis.
|
|
132
|
+
* The component repositions the panel automatically — flipping to the opposite side and/or
|
|
133
|
+
* shifting its alignment — when the preferred position would collide with the viewport or with
|
|
134
|
+
* a clipping scroll container. This behaviour cannot be disabled.
|
|
124
135
|
*/
|
|
125
136
|
position?: typeof positions[number];
|
|
126
137
|
/**
|
package/package.json
CHANGED
package/src/defs.ts
CHANGED
|
@@ -56,7 +56,10 @@ export interface TooltipProps {
|
|
|
56
56
|
isOpen?: boolean;
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* The side of the trigger the panel sits on, and its alignment along the cross axis.
|
|
59
|
+
* The preferred side of the trigger the panel sits on, and its alignment along the cross axis.
|
|
60
|
+
* The component repositions the panel automatically — flipping to the opposite side and/or
|
|
61
|
+
* shifting its alignment — when the preferred position would collide with the viewport or with
|
|
62
|
+
* a clipping scroll container. This behaviour cannot be disabled.
|
|
60
63
|
*/
|
|
61
64
|
position?: typeof positions[number];
|
|
62
65
|
|
package/src/index.ts
CHANGED
|
@@ -133,6 +133,115 @@ const intersectRects = (a: DOMRect, b: DOMRect): DOMRect | null => {
|
|
|
133
133
|
return new DOMRect(left, top, right - left, bottom - top);
|
|
134
134
|
};
|
|
135
135
|
|
|
136
|
+
type TooltipSide = 'top' | 'bottom' | 'left' | 'right';
|
|
137
|
+
type TooltipAlignment = '' | '-start' | '-end';
|
|
138
|
+
|
|
139
|
+
interface CandidateRect {
|
|
140
|
+
left: number;
|
|
141
|
+
top: number;
|
|
142
|
+
right: number;
|
|
143
|
+
bottom: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const oppositeSide: Record<TooltipSide, TooltipSide> = {
|
|
147
|
+
top: 'bottom',
|
|
148
|
+
bottom: 'top',
|
|
149
|
+
left: 'right',
|
|
150
|
+
right: 'left',
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const crossSides: Record<TooltipSide, Array<TooltipSide>> = {
|
|
154
|
+
top: ['left', 'right'],
|
|
155
|
+
bottom: ['left', 'right'],
|
|
156
|
+
left: ['top', 'bottom'],
|
|
157
|
+
right: ['top', 'bottom'],
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const parsePosition = (position: TooltipProps['position']): { side: TooltipSide; alignment: TooltipAlignment } => {
|
|
161
|
+
const match = /^(top|bottom|left|right)(-start|-end)?$/.exec(position ?? '');
|
|
162
|
+
|
|
163
|
+
if (!match) {
|
|
164
|
+
return { side: 'top', alignment: '' };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { side: match[1] as TooltipSide, alignment: (match[2] ?? '') as TooltipAlignment };
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const toPhysicalSide = (side: TooltipSide, isRtl: boolean): TooltipSide => {
|
|
171
|
+
if (!isRtl) {
|
|
172
|
+
return side;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (side === 'left') {
|
|
176
|
+
return 'right';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (side === 'right') {
|
|
180
|
+
return 'left';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return side;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const getCandidateRect = (
|
|
187
|
+
side: TooltipSide,
|
|
188
|
+
alignment: TooltipAlignment,
|
|
189
|
+
anchor: DOMRect,
|
|
190
|
+
panelWidth: number,
|
|
191
|
+
panelHeight: number,
|
|
192
|
+
offset: number,
|
|
193
|
+
isRtl: boolean,
|
|
194
|
+
): CandidateRect => {
|
|
195
|
+
const physicalSide = toPhysicalSide(side, isRtl);
|
|
196
|
+
const isVerticalSide = physicalSide === 'top' || physicalSide === 'bottom';
|
|
197
|
+
|
|
198
|
+
let left = 0;
|
|
199
|
+
let top = 0;
|
|
200
|
+
|
|
201
|
+
if (physicalSide === 'top') {
|
|
202
|
+
top = anchor.top - offset - panelHeight;
|
|
203
|
+
} else if (physicalSide === 'bottom') {
|
|
204
|
+
top = anchor.bottom + offset;
|
|
205
|
+
} else if (physicalSide === 'left') {
|
|
206
|
+
left = anchor.left - offset - panelWidth;
|
|
207
|
+
} else {
|
|
208
|
+
left = anchor.right + offset;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (isVerticalSide) {
|
|
212
|
+
if (alignment === '') {
|
|
213
|
+
left = anchor.left + (anchor.width / 2) - (panelWidth / 2);
|
|
214
|
+
} else if (alignment === '-start') {
|
|
215
|
+
left = isRtl ? anchor.right - panelWidth : anchor.left;
|
|
216
|
+
} else {
|
|
217
|
+
left = isRtl ? anchor.left : anchor.right - panelWidth;
|
|
218
|
+
}
|
|
219
|
+
} else if (alignment === '') {
|
|
220
|
+
top = anchor.top + (anchor.height / 2) - (panelHeight / 2);
|
|
221
|
+
} else {
|
|
222
|
+
top = alignment === '-start' ? anchor.top : anchor.bottom - panelHeight;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
left,
|
|
227
|
+
top,
|
|
228
|
+
right: left + panelWidth,
|
|
229
|
+
bottom: top + panelHeight,
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const containsRect = (boundary: DOMRect, rect: CandidateRect): boolean => rect.left >= boundary.left &&
|
|
234
|
+
rect.right <= boundary.right &&
|
|
235
|
+
rect.top >= boundary.top &&
|
|
236
|
+
rect.bottom <= boundary.bottom;
|
|
237
|
+
|
|
238
|
+
const getVisibleArea = (boundary: DOMRect, rect: CandidateRect): number => {
|
|
239
|
+
const width = Math.min(boundary.right, rect.right) - Math.max(boundary.left, rect.left);
|
|
240
|
+
const height = Math.min(boundary.bottom, rect.bottom) - Math.max(boundary.top, rect.top);
|
|
241
|
+
|
|
242
|
+
return Math.max(0, width) * Math.max(0, height);
|
|
243
|
+
};
|
|
244
|
+
|
|
136
245
|
/**
|
|
137
246
|
* @tagname pie-tooltip
|
|
138
247
|
* @event {Event} pie-tooltip-open - When a configured trigger asks for the panel. Set `isOpen` to `true` in response.
|
|
@@ -190,10 +299,18 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
190
299
|
|
|
191
300
|
@state() private _isAnchorVisible = true;
|
|
192
301
|
|
|
302
|
+
@state() private _resolvedPosition: TooltipProps['position'] | undefined;
|
|
303
|
+
|
|
193
304
|
private _clippedTriggerElement: Element | null = null;
|
|
194
305
|
|
|
195
306
|
private _triggerClippers: Array<Element> = [];
|
|
196
307
|
|
|
308
|
+
private _overlayClippers: Array<Element> = [];
|
|
309
|
+
|
|
310
|
+
private _collisionSignature: string | null = null;
|
|
311
|
+
|
|
312
|
+
private _overlayModeDirty = true;
|
|
313
|
+
|
|
197
314
|
private _triggerTrackingController: AbortController | undefined;
|
|
198
315
|
private _interactionController: AbortController | undefined;
|
|
199
316
|
|
|
@@ -203,10 +320,10 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
203
320
|
|
|
204
321
|
private _reanchorFrame = 0;
|
|
205
322
|
|
|
206
|
-
private _shouldResolveOverlayMode = false;
|
|
207
|
-
|
|
208
323
|
private _hoverCloseTimer: ReturnType<typeof setTimeout> | undefined;
|
|
209
324
|
|
|
325
|
+
private _openedByClick = false;
|
|
326
|
+
|
|
210
327
|
static styles = unsafeCSS(styles);
|
|
211
328
|
|
|
212
329
|
private get _mode (): TooltipMode | undefined {
|
|
@@ -219,6 +336,8 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
219
336
|
|
|
220
337
|
protected firstUpdated (): void {
|
|
221
338
|
this.resolveMode();
|
|
339
|
+
this._overlayModeDirty = true;
|
|
340
|
+
this.resolveOverlayMode();
|
|
222
341
|
this.projectOverTrigger();
|
|
223
342
|
this._rebuildInteractionListeners();
|
|
224
343
|
}
|
|
@@ -227,10 +346,26 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
227
346
|
const anchoringProperties: Array<keyof PieTooltip> = ['trigger', 'isOpen', 'position', 'size'];
|
|
228
347
|
|
|
229
348
|
if (this.isOpen && changedProperties.has('isOpen')) {
|
|
230
|
-
this.
|
|
349
|
+
this._overlayModeDirty = true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (!this.isOpen) {
|
|
353
|
+
this._openedByClick = false;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (changedProperties.has('trigger')) {
|
|
357
|
+
this._overlayModeDirty = true;
|
|
231
358
|
}
|
|
232
359
|
|
|
233
|
-
|
|
360
|
+
const isAnchoringChange = anchoringProperties.some((prop) => changedProperties.has(prop));
|
|
361
|
+
|
|
362
|
+
if (isAnchoringChange) {
|
|
363
|
+
this._collisionSignature = null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
this.resolveOverlayMode();
|
|
367
|
+
|
|
368
|
+
if (isAnchoringChange) {
|
|
234
369
|
this.projectOverTrigger();
|
|
235
370
|
}
|
|
236
371
|
|
|
@@ -276,18 +411,14 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
276
411
|
this._reanchorFrame = requestAnimationFrame(() => {
|
|
277
412
|
this._reanchorFrame = 0;
|
|
278
413
|
|
|
279
|
-
|
|
280
|
-
this._shouldResolveOverlayMode = false;
|
|
281
|
-
this.resolveOverlayMode();
|
|
282
|
-
}
|
|
283
|
-
|
|
414
|
+
this.resolveOverlayMode();
|
|
284
415
|
this.projectOverTrigger();
|
|
285
416
|
});
|
|
286
417
|
};
|
|
287
418
|
|
|
288
419
|
// Flagged rather than resolved immediately so window-drag cannot walk ancestors more than once per frame.
|
|
289
420
|
const handleResize = () => {
|
|
290
|
-
this.
|
|
421
|
+
this._overlayModeDirty = true;
|
|
291
422
|
handleViewportChange();
|
|
292
423
|
};
|
|
293
424
|
|
|
@@ -318,7 +449,7 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
318
449
|
this._triggerObserver = new ResizeObserver(() => {
|
|
319
450
|
// Geometry changed, so the ancestor chain may have too. Flagged rather than resolved
|
|
320
451
|
// here so the walk runs at most once per frame.
|
|
321
|
-
this.
|
|
452
|
+
this._overlayModeDirty = true;
|
|
322
453
|
handleViewportChange();
|
|
323
454
|
});
|
|
324
455
|
|
|
@@ -347,8 +478,6 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
347
478
|
this._triggerObserver?.disconnect();
|
|
348
479
|
this._triggerObserver = undefined;
|
|
349
480
|
|
|
350
|
-
this._shouldResolveOverlayMode = false;
|
|
351
|
-
|
|
352
481
|
if (this._reanchorFrame) {
|
|
353
482
|
cancelAnimationFrame(this._reanchorFrame);
|
|
354
483
|
this._reanchorFrame = 0;
|
|
@@ -367,10 +496,16 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
367
496
|
// ancestor of it; a clipper strictly inside the containing block does not clip. So each
|
|
368
497
|
// clipper is counted against a mode only once that mode's containing block has been reached.
|
|
369
498
|
private resolveOverlayMode (): void {
|
|
499
|
+
if (!this._overlayModeDirty) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
this._overlayModeDirty = false;
|
|
504
|
+
|
|
370
505
|
let isAtOrAboveAbsoluteContainingBlock = false;
|
|
371
506
|
let isAtOrAboveFixedContainingBlock = false;
|
|
372
|
-
|
|
373
|
-
|
|
507
|
+
const absoluteClippingAncestors: Array<Element> = [];
|
|
508
|
+
const fixedClippingAncestors: Array<Element> = [];
|
|
374
509
|
|
|
375
510
|
const { documentElement, body } = this.ownerDocument;
|
|
376
511
|
|
|
@@ -410,11 +545,11 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
410
545
|
// block and the clipper is counted correctly.
|
|
411
546
|
if (clips && !propagatesOverflowToViewport) {
|
|
412
547
|
if (isAtOrAboveAbsoluteContainingBlock) {
|
|
413
|
-
|
|
548
|
+
absoluteClippingAncestors.push(element);
|
|
414
549
|
}
|
|
415
550
|
|
|
416
551
|
if (isAtOrAboveFixedContainingBlock) {
|
|
417
|
-
|
|
552
|
+
fixedClippingAncestors.push(element);
|
|
418
553
|
}
|
|
419
554
|
}
|
|
420
555
|
});
|
|
@@ -422,7 +557,10 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
422
557
|
// The fixed containing block is always at or above the absolute one, so the clippers that
|
|
423
558
|
// apply to a fixed box are a subset of those that apply to an absolute one. A lower count
|
|
424
559
|
// therefore always means a strictly better escape, never a worse one.
|
|
425
|
-
|
|
560
|
+
const useFixed = fixedClippingAncestors.length < absoluteClippingAncestors.length;
|
|
561
|
+
|
|
562
|
+
this.style.position = useFixed ? 'fixed' : '';
|
|
563
|
+
this._overlayClippers = useFixed ? fixedClippingAncestors : absoluteClippingAncestors;
|
|
426
564
|
}
|
|
427
565
|
|
|
428
566
|
// Measures the trigger relative to the origin marker (which sits at the containing block's
|
|
@@ -480,9 +618,122 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
480
618
|
const containerInlineSize = container ? container.getBoundingClientRect().width : width;
|
|
481
619
|
|
|
482
620
|
this.style.setProperty('--tooltip-container-inline-size', `${containerInlineSize}px`);
|
|
621
|
+
|
|
622
|
+
this.resolveCollision(visibleRect);
|
|
623
|
+
|
|
483
624
|
this._isPositioned = true;
|
|
484
625
|
}
|
|
485
626
|
|
|
627
|
+
private resolveCollision (anchorRect: DOMRect): void {
|
|
628
|
+
const panel = this.renderRoot.querySelector<HTMLElement>(`.${componentClass}`);
|
|
629
|
+
|
|
630
|
+
if (!panel) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const boundary = this._getCollisionBoundary();
|
|
635
|
+
|
|
636
|
+
if (!boundary) {
|
|
637
|
+
this._collisionSignature = null;
|
|
638
|
+
this._setResolvedPosition(this.position ?? defaultProps.position);
|
|
639
|
+
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const isIconType = this.type === 'icon';
|
|
644
|
+
const hostStyles = getComputedStyle(this);
|
|
645
|
+
const isRtl = hostStyles.direction === 'rtl';
|
|
646
|
+
|
|
647
|
+
const panelWidth = panel.offsetWidth;
|
|
648
|
+
const panelHeight = panel.offsetHeight;
|
|
649
|
+
const signature = [
|
|
650
|
+
anchorRect.left, anchorRect.top, anchorRect.width, anchorRect.height,
|
|
651
|
+
boundary.left, boundary.top, boundary.width, boundary.height,
|
|
652
|
+
panelWidth, panelHeight,
|
|
653
|
+
isRtl,
|
|
654
|
+
].join(':');
|
|
655
|
+
|
|
656
|
+
if (this._collisionSignature === signature) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
this._collisionSignature = signature;
|
|
661
|
+
|
|
662
|
+
const offset = parseFloat(hostStyles.getPropertyValue('--tooltip-offset')) || 0;
|
|
663
|
+
const arrowSize = parseFloat(hostStyles.getPropertyValue('--tooltip-arrow-size')) || 0;
|
|
664
|
+
const layerOffset = isIconType ? offset : offset + arrowSize;
|
|
665
|
+
|
|
666
|
+
const { side: preferredSide, alignment: preferredAlignment } = parsePosition(this.position ?? defaultProps.position);
|
|
667
|
+
|
|
668
|
+
let fitting: { side: TooltipSide; alignment: TooltipAlignment } | undefined;
|
|
669
|
+
let bestFallback: { side: TooltipSide; alignment: TooltipAlignment } | undefined;
|
|
670
|
+
let bestArea = -1;
|
|
671
|
+
|
|
672
|
+
this._getCandidatePositions(preferredSide, preferredAlignment).some(({ side, alignment }) => {
|
|
673
|
+
const rect = getCandidateRect(
|
|
674
|
+
side,
|
|
675
|
+
alignment,
|
|
676
|
+
anchorRect,
|
|
677
|
+
panelWidth,
|
|
678
|
+
panelHeight,
|
|
679
|
+
layerOffset,
|
|
680
|
+
isRtl,
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
if (containsRect(boundary, rect)) {
|
|
684
|
+
fitting = { side, alignment };
|
|
685
|
+
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const area = getVisibleArea(boundary, rect);
|
|
690
|
+
|
|
691
|
+
if (area > bestArea) {
|
|
692
|
+
bestArea = area;
|
|
693
|
+
bestFallback = { side, alignment };
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return false;
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
const resolved = fitting ?? bestFallback;
|
|
700
|
+
const resolvedPosition = resolved
|
|
701
|
+
? `${resolved.side}${resolved.alignment}` as TooltipProps['position']
|
|
702
|
+
: this.position ?? defaultProps.position;
|
|
703
|
+
|
|
704
|
+
this._setResolvedPosition(resolvedPosition);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
private _setResolvedPosition (position: TooltipProps['position']): void {
|
|
708
|
+
if (this._resolvedPosition !== position) {
|
|
709
|
+
this._resolvedPosition = position;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
private _getCollisionBoundary (): DOMRect | null {
|
|
714
|
+
const { documentElement } = this.ownerDocument;
|
|
715
|
+
const viewport = new DOMRect(0, 0, documentElement.clientWidth, documentElement.clientHeight);
|
|
716
|
+
|
|
717
|
+
return this._overlayClippers.reduce<DOMRect | null>(
|
|
718
|
+
(rect, clipper) => (rect ? intersectRects(rect, getClipRect(clipper)) : null),
|
|
719
|
+
viewport,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
private _getCandidatePositions (
|
|
724
|
+
preferredSide: TooltipSide,
|
|
725
|
+
preferredAlignment: TooltipAlignment,
|
|
726
|
+
): Array<{ side: TooltipSide; alignment: TooltipAlignment }> {
|
|
727
|
+
const sideOrder: Array<TooltipSide> = [preferredSide, oppositeSide[preferredSide], ...crossSides[preferredSide]];
|
|
728
|
+
const allAlignments: Array<TooltipAlignment> = ['', '-start', '-end'];
|
|
729
|
+
|
|
730
|
+
return sideOrder.flatMap((side) => {
|
|
731
|
+
const alignments = [preferredAlignment, ...allAlignments.filter((alignment) => alignment !== preferredAlignment)];
|
|
732
|
+
|
|
733
|
+
return alignments.map((alignment) => ({ side, alignment }));
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
|
|
486
737
|
// Cached because `projectOverTrigger` runs on every re-anchoring frame, and collecting these
|
|
487
738
|
// costs a computed style per ancestor. Refreshed when the trigger changes and whenever the
|
|
488
739
|
// overlay mode is resolved, which is the same cadence as an ancestor chain actually changing.
|
|
@@ -595,11 +846,22 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
595
846
|
if (this.triggers.includes('click')) {
|
|
596
847
|
triggerEl.addEventListener('click', (e: Event) => {
|
|
597
848
|
e.stopPropagation();
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
849
|
+
|
|
850
|
+
if (!this.isOpen) {
|
|
851
|
+
// Only click-opens toggle
|
|
852
|
+
this._openedByClick = true;
|
|
601
853
|
this._requestOpen();
|
|
854
|
+
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (!this._openedByClick) {
|
|
859
|
+
this._openedByClick = true;
|
|
860
|
+
|
|
861
|
+
return;
|
|
602
862
|
}
|
|
863
|
+
|
|
864
|
+
this._requestClose();
|
|
603
865
|
}, { signal });
|
|
604
866
|
|
|
605
867
|
// Light-dismiss: click anywhere outside the panel and trigger
|
|
@@ -648,13 +910,15 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
648
910
|
heading,
|
|
649
911
|
isDismissible,
|
|
650
912
|
isOpen,
|
|
651
|
-
position,
|
|
913
|
+
position: preferredPosition,
|
|
652
914
|
size,
|
|
653
915
|
type,
|
|
654
916
|
variant,
|
|
917
|
+
_resolvedPosition,
|
|
655
918
|
_mode: mode,
|
|
656
919
|
} = this;
|
|
657
920
|
|
|
921
|
+
const position = _resolvedPosition ?? preferredPosition;
|
|
658
922
|
const isIconType = type === 'icon';
|
|
659
923
|
|
|
660
924
|
const layerClasses = {
|