@justeattakeaway/pie-tooltip 0.5.1 → 0.6.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 +1 -12
- package/custom-elements.json +0 -96
- package/dist/index.d.ts +1 -8
- package/dist/index.js +227 -317
- package/dist/react.d.ts +1 -8
- package/package.json +1 -1
- package/src/defs.ts +1 -1
- package/src/index.ts +2 -238
- package/src/tooltip.scss +6 -0
package/dist/react.d.ts
CHANGED
|
@@ -55,11 +55,8 @@ declare class PieTooltip_2 extends PieElement implements TooltipProps {
|
|
|
55
55
|
private _hasActionContent;
|
|
56
56
|
private _isPositioned;
|
|
57
57
|
private _isAnchorVisible;
|
|
58
|
-
private _resolvedPosition;
|
|
59
58
|
private _clippedTriggerElement;
|
|
60
59
|
private _triggerClippers;
|
|
61
|
-
private _overlayClippers;
|
|
62
|
-
private _collisionSignature;
|
|
63
60
|
private _overlayModeDirty;
|
|
64
61
|
private _triggerTrackingController;
|
|
65
62
|
private _interactionController;
|
|
@@ -81,10 +78,6 @@ declare class PieTooltip_2 extends PieElement implements TooltipProps {
|
|
|
81
78
|
private resolveMode;
|
|
82
79
|
private resolveOverlayMode;
|
|
83
80
|
private projectOverTrigger;
|
|
84
|
-
private resolveCollision;
|
|
85
|
-
private _setResolvedPosition;
|
|
86
|
-
private _getCollisionBoundary;
|
|
87
|
-
private _getCandidatePositions;
|
|
88
81
|
private _refreshTriggerClippers;
|
|
89
82
|
private handleCloseButtonClick;
|
|
90
83
|
private _getTriggerElement;
|
|
@@ -117,7 +110,7 @@ export declare interface TooltipProps {
|
|
|
117
110
|
trigger?: string;
|
|
118
111
|
/** When true, the panel is visible. The component never writes to this property. */
|
|
119
112
|
isOpen?: boolean;
|
|
120
|
-
/** The
|
|
113
|
+
/** The side of the trigger the panel sits on, and its alignment along the cross axis. */
|
|
121
114
|
position?: typeof positions[number];
|
|
122
115
|
/** How the panel sizes itself. Not applied when `type` is `icon`. */
|
|
123
116
|
size?: typeof sizes[number];
|
package/package.json
CHANGED
package/src/defs.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface TooltipProps {
|
|
|
42
42
|
/** When true, the panel is visible. The component never writes to this property. */
|
|
43
43
|
isOpen?: boolean;
|
|
44
44
|
|
|
45
|
-
/** The
|
|
45
|
+
/** The side of the trigger the panel sits on, and its alignment along the cross axis. */
|
|
46
46
|
position?: typeof positions[number];
|
|
47
47
|
|
|
48
48
|
/** How the panel sizes itself. Not applied when `type` is `icon`. */
|
package/src/index.ts
CHANGED
|
@@ -125,115 +125,6 @@ const intersectRects = (a: DOMRect, b: DOMRect): DOMRect | null => {
|
|
|
125
125
|
return new DOMRect(left, top, right - left, bottom - top);
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
-
type TooltipSide = 'top' | 'bottom' | 'left' | 'right';
|
|
129
|
-
type TooltipAlignment = '' | '-start' | '-end';
|
|
130
|
-
|
|
131
|
-
interface CandidateRect {
|
|
132
|
-
left: number;
|
|
133
|
-
top: number;
|
|
134
|
-
right: number;
|
|
135
|
-
bottom: number;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const oppositeSide: Record<TooltipSide, TooltipSide> = {
|
|
139
|
-
top: 'bottom',
|
|
140
|
-
bottom: 'top',
|
|
141
|
-
left: 'right',
|
|
142
|
-
right: 'left',
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const crossSides: Record<TooltipSide, Array<TooltipSide>> = {
|
|
146
|
-
top: ['left', 'right'],
|
|
147
|
-
bottom: ['left', 'right'],
|
|
148
|
-
left: ['top', 'bottom'],
|
|
149
|
-
right: ['top', 'bottom'],
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const parsePosition = (position: TooltipProps['position']): { side: TooltipSide; alignment: TooltipAlignment } => {
|
|
153
|
-
const match = /^(top|bottom|left|right)(-start|-end)?$/.exec(position ?? '');
|
|
154
|
-
|
|
155
|
-
if (!match) {
|
|
156
|
-
return { side: 'top', alignment: '' };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return { side: match[1] as TooltipSide, alignment: (match[2] ?? '') as TooltipAlignment };
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const toPhysicalSide = (side: TooltipSide, isRtl: boolean): TooltipSide => {
|
|
163
|
-
if (!isRtl) {
|
|
164
|
-
return side;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (side === 'left') {
|
|
168
|
-
return 'right';
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (side === 'right') {
|
|
172
|
-
return 'left';
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return side;
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const getCandidateRect = (
|
|
179
|
-
side: TooltipSide,
|
|
180
|
-
alignment: TooltipAlignment,
|
|
181
|
-
anchor: DOMRect,
|
|
182
|
-
panelWidth: number,
|
|
183
|
-
panelHeight: number,
|
|
184
|
-
offset: number,
|
|
185
|
-
isRtl: boolean,
|
|
186
|
-
): CandidateRect => {
|
|
187
|
-
const physicalSide = toPhysicalSide(side, isRtl);
|
|
188
|
-
const isVerticalSide = physicalSide === 'top' || physicalSide === 'bottom';
|
|
189
|
-
|
|
190
|
-
let left = 0;
|
|
191
|
-
let top = 0;
|
|
192
|
-
|
|
193
|
-
if (physicalSide === 'top') {
|
|
194
|
-
top = anchor.top - offset - panelHeight;
|
|
195
|
-
} else if (physicalSide === 'bottom') {
|
|
196
|
-
top = anchor.bottom + offset;
|
|
197
|
-
} else if (physicalSide === 'left') {
|
|
198
|
-
left = anchor.left - offset - panelWidth;
|
|
199
|
-
} else {
|
|
200
|
-
left = anchor.right + offset;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (isVerticalSide) {
|
|
204
|
-
if (alignment === '') {
|
|
205
|
-
left = anchor.left + (anchor.width / 2) - (panelWidth / 2);
|
|
206
|
-
} else if (alignment === '-start') {
|
|
207
|
-
left = isRtl ? anchor.right - panelWidth : anchor.left;
|
|
208
|
-
} else {
|
|
209
|
-
left = isRtl ? anchor.left : anchor.right - panelWidth;
|
|
210
|
-
}
|
|
211
|
-
} else if (alignment === '') {
|
|
212
|
-
top = anchor.top + (anchor.height / 2) - (panelHeight / 2);
|
|
213
|
-
} else {
|
|
214
|
-
top = alignment === '-start' ? anchor.top : anchor.bottom - panelHeight;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
left,
|
|
219
|
-
top,
|
|
220
|
-
right: left + panelWidth,
|
|
221
|
-
bottom: top + panelHeight,
|
|
222
|
-
};
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const containsRect = (boundary: DOMRect, rect: CandidateRect): boolean => rect.left >= boundary.left &&
|
|
226
|
-
rect.right <= boundary.right &&
|
|
227
|
-
rect.top >= boundary.top &&
|
|
228
|
-
rect.bottom <= boundary.bottom;
|
|
229
|
-
|
|
230
|
-
const getVisibleArea = (boundary: DOMRect, rect: CandidateRect): number => {
|
|
231
|
-
const width = Math.min(boundary.right, rect.right) - Math.max(boundary.left, rect.left);
|
|
232
|
-
const height = Math.min(boundary.bottom, rect.bottom) - Math.max(boundary.top, rect.top);
|
|
233
|
-
|
|
234
|
-
return Math.max(0, width) * Math.max(0, height);
|
|
235
|
-
};
|
|
236
|
-
|
|
237
128
|
/**
|
|
238
129
|
* @tagname pie-tooltip
|
|
239
130
|
* @event {Event} pie-tooltip-open - When a configured trigger asks for the panel. Set `isOpen` to `true` in response.
|
|
@@ -291,16 +182,10 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
291
182
|
|
|
292
183
|
@state() private _isAnchorVisible = true;
|
|
293
184
|
|
|
294
|
-
@state() private _resolvedPosition: TooltipProps['position'] | undefined;
|
|
295
|
-
|
|
296
185
|
private _clippedTriggerElement: Element | null = null;
|
|
297
186
|
|
|
298
187
|
private _triggerClippers: Array<Element> = [];
|
|
299
188
|
|
|
300
|
-
private _overlayClippers: Array<Element> = [];
|
|
301
|
-
|
|
302
|
-
private _collisionSignature: string | null = null;
|
|
303
|
-
|
|
304
189
|
private _overlayModeDirty = true;
|
|
305
190
|
|
|
306
191
|
private _triggerTrackingController: AbortController | undefined;
|
|
@@ -355,15 +240,9 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
355
240
|
this._overlayModeDirty = true;
|
|
356
241
|
}
|
|
357
242
|
|
|
358
|
-
const isAnchoringChange = anchoringProperties.some((prop) => changedProperties.has(prop));
|
|
359
|
-
|
|
360
|
-
if (isAnchoringChange) {
|
|
361
|
-
this._collisionSignature = null;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
243
|
this.resolveOverlayMode();
|
|
365
244
|
|
|
366
|
-
if (
|
|
245
|
+
if (anchoringProperties.some((prop) => changedProperties.has(prop))) {
|
|
367
246
|
this.projectOverTrigger();
|
|
368
247
|
}
|
|
369
248
|
|
|
@@ -552,7 +431,6 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
552
431
|
const useFixed = fixedClippingAncestors.length < absoluteClippingAncestors.length;
|
|
553
432
|
|
|
554
433
|
this.style.position = useFixed ? 'fixed' : '';
|
|
555
|
-
this._overlayClippers = useFixed ? fixedClippingAncestors : absoluteClippingAncestors;
|
|
556
434
|
}
|
|
557
435
|
|
|
558
436
|
private projectOverTrigger (): void {
|
|
@@ -603,121 +481,9 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
603
481
|
|
|
604
482
|
this.style.setProperty('--tooltip-container-inline-size', `${containerInlineSize}px`);
|
|
605
483
|
|
|
606
|
-
this.resolveCollision(visibleRect);
|
|
607
|
-
|
|
608
484
|
this._isPositioned = true;
|
|
609
485
|
}
|
|
610
486
|
|
|
611
|
-
private resolveCollision (anchorRect: DOMRect): void {
|
|
612
|
-
const panel = this.renderRoot.querySelector<HTMLElement>(`.${componentClass}`);
|
|
613
|
-
|
|
614
|
-
if (!panel) {
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
const boundary = this._getCollisionBoundary();
|
|
619
|
-
|
|
620
|
-
if (!boundary) {
|
|
621
|
-
this._collisionSignature = null;
|
|
622
|
-
this._setResolvedPosition(this.position ?? defaultProps.position);
|
|
623
|
-
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
const isIconType = this.type === 'icon';
|
|
628
|
-
const hostStyles = getComputedStyle(this);
|
|
629
|
-
const isRtl = hostStyles.direction === 'rtl';
|
|
630
|
-
|
|
631
|
-
const panelWidth = panel.offsetWidth;
|
|
632
|
-
const panelHeight = panel.offsetHeight;
|
|
633
|
-
const signature = [
|
|
634
|
-
anchorRect.left, anchorRect.top, anchorRect.width, anchorRect.height,
|
|
635
|
-
boundary.left, boundary.top, boundary.width, boundary.height,
|
|
636
|
-
panelWidth, panelHeight,
|
|
637
|
-
isRtl,
|
|
638
|
-
].join(':');
|
|
639
|
-
|
|
640
|
-
if (this._collisionSignature === signature) {
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
this._collisionSignature = signature;
|
|
645
|
-
|
|
646
|
-
const offset = parseFloat(hostStyles.getPropertyValue('--tooltip-offset')) || 0;
|
|
647
|
-
const arrowSize = parseFloat(hostStyles.getPropertyValue('--tooltip-arrow-size')) || 0;
|
|
648
|
-
const layerOffset = isIconType ? offset : offset + arrowSize;
|
|
649
|
-
|
|
650
|
-
const { side: preferredSide, alignment: preferredAlignment } = parsePosition(this.position ?? defaultProps.position);
|
|
651
|
-
|
|
652
|
-
let fitting: { side: TooltipSide; alignment: TooltipAlignment } | undefined;
|
|
653
|
-
let bestFallback: { side: TooltipSide; alignment: TooltipAlignment } | undefined;
|
|
654
|
-
let bestArea = -1;
|
|
655
|
-
|
|
656
|
-
this._getCandidatePositions(preferredSide, preferredAlignment).some(({ side, alignment }) => {
|
|
657
|
-
const rect = getCandidateRect(
|
|
658
|
-
side,
|
|
659
|
-
alignment,
|
|
660
|
-
anchorRect,
|
|
661
|
-
panelWidth,
|
|
662
|
-
panelHeight,
|
|
663
|
-
layerOffset,
|
|
664
|
-
isRtl,
|
|
665
|
-
);
|
|
666
|
-
|
|
667
|
-
if (containsRect(boundary, rect)) {
|
|
668
|
-
fitting = { side, alignment };
|
|
669
|
-
|
|
670
|
-
return true;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const area = getVisibleArea(boundary, rect);
|
|
674
|
-
|
|
675
|
-
if (area > bestArea) {
|
|
676
|
-
bestArea = area;
|
|
677
|
-
bestFallback = { side, alignment };
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
return false;
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
const resolved = fitting ?? bestFallback;
|
|
684
|
-
const resolvedPosition = resolved
|
|
685
|
-
? `${resolved.side}${resolved.alignment}` as TooltipProps['position']
|
|
686
|
-
: this.position ?? defaultProps.position;
|
|
687
|
-
|
|
688
|
-
this._setResolvedPosition(resolvedPosition);
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
private _setResolvedPosition (position: TooltipProps['position']): void {
|
|
692
|
-
if (this._resolvedPosition !== position) {
|
|
693
|
-
this._resolvedPosition = position;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
private _getCollisionBoundary (): DOMRect | null {
|
|
698
|
-
const { documentElement } = this.ownerDocument;
|
|
699
|
-
const viewport = new DOMRect(0, 0, documentElement.clientWidth, documentElement.clientHeight);
|
|
700
|
-
|
|
701
|
-
return this._overlayClippers.reduce<DOMRect | null>(
|
|
702
|
-
(rect, clipper) => (rect ? intersectRects(rect, getClipRect(clipper)) : null),
|
|
703
|
-
viewport,
|
|
704
|
-
);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
private _getCandidatePositions (
|
|
708
|
-
preferredSide: TooltipSide,
|
|
709
|
-
preferredAlignment: TooltipAlignment,
|
|
710
|
-
): Array<{ side: TooltipSide; alignment: TooltipAlignment }> {
|
|
711
|
-
const sideOrder: Array<TooltipSide> = [preferredSide, oppositeSide[preferredSide], ...crossSides[preferredSide]];
|
|
712
|
-
const allAlignments: Array<TooltipAlignment> = ['', '-start', '-end'];
|
|
713
|
-
|
|
714
|
-
return sideOrder.flatMap((side) => {
|
|
715
|
-
const alignments = [preferredAlignment, ...allAlignments.filter((alignment) => alignment !== preferredAlignment)];
|
|
716
|
-
|
|
717
|
-
return alignments.map((alignment) => ({ side, alignment }));
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
|
|
721
487
|
private _refreshTriggerClippers (): void {
|
|
722
488
|
const triggerElement = this._getTriggerElement();
|
|
723
489
|
|
|
@@ -886,15 +652,13 @@ export class PieTooltip extends PieElement implements TooltipProps {
|
|
|
886
652
|
heading,
|
|
887
653
|
isDismissible,
|
|
888
654
|
isOpen,
|
|
889
|
-
position
|
|
655
|
+
position,
|
|
890
656
|
size,
|
|
891
657
|
type,
|
|
892
658
|
variant,
|
|
893
|
-
_resolvedPosition,
|
|
894
659
|
_mode: mode,
|
|
895
660
|
} = this;
|
|
896
661
|
|
|
897
|
-
const position = _resolvedPosition ?? preferredPosition;
|
|
898
662
|
const isIconType = type === 'icon';
|
|
899
663
|
|
|
900
664
|
const layerClasses = {
|
package/src/tooltip.scss
CHANGED
|
@@ -153,6 +153,12 @@ $tooltip-alignments: ('', '-start', '-end');
|
|
|
153
153
|
|
|
154
154
|
position: relative;
|
|
155
155
|
|
|
156
|
+
// Reset the inherited text layout: a host page that sets `white-space` or `text-wrap` to
|
|
157
|
+
// `nowrap` (typical of header action bars) would otherwise reach the slotted content and
|
|
158
|
+
// inflate the panel's min-content size past its own inline-size.
|
|
159
|
+
white-space: normal;
|
|
160
|
+
text-wrap: wrap;
|
|
161
|
+
|
|
156
162
|
inline-size: var(--tooltip-width, var(--tooltip-inline-size));
|
|
157
163
|
max-inline-size: var(--tooltip-max-inline-size);
|
|
158
164
|
padding-block-start: var(--tooltip-padding-block);
|