@brightspace-ui/core 3.222.0 → 3.224.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.
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import { clearDismissible, setDismissible } from '../../helpers/dismissible.js';
|
|
2
1
|
import { css, html, LitElement } from 'lit';
|
|
3
|
-
import { cssEscape, elemIdListAdd, elemIdListRemove,
|
|
2
|
+
import { cssEscape, elemIdListAdd, elemIdListRemove, isComposedAncestor } from '../../helpers/dom.js';
|
|
4
3
|
import { getComposedActiveElement, isFocusable } from '../../helpers/focus.js';
|
|
5
4
|
import { interactiveElements, interactiveRoles, isInteractive } from '../../helpers/interactive.js';
|
|
6
5
|
import { announce } from '../../helpers/announce.js';
|
|
7
6
|
import { bodySmallStyles } from '../typography/styles.js';
|
|
8
|
-
import { classMap } from 'lit/directives/class-map.js';
|
|
9
|
-
import { getFlag } from '../../helpers/flags.js';
|
|
10
7
|
import { getUniqueId } from '../../helpers/uniqueId.js';
|
|
11
8
|
import { PopoverMixin } from '../popover/popover-mixin.js';
|
|
12
|
-
import { RtlMixin } from '../../mixins/rtl/rtl-mixin.js';
|
|
13
|
-
import { styleMap } from 'lit/directives/style-map.js';
|
|
14
|
-
|
|
15
|
-
export const usePopoverMixin = getFlag('GAUD-7355-tooltip-popover', true);
|
|
16
9
|
|
|
17
10
|
const contentBorderSize = 1;
|
|
18
11
|
const contentHorizontalPadding = 15;
|
|
@@ -57,1568 +50,553 @@ let logAccessibilityWarning = true;
|
|
|
57
50
|
// only one tooltip is to be shown at once - track the active tooltip so it can be hidden if necessary
|
|
58
51
|
let activeTooltip = null;
|
|
59
52
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
};
|
|
137
|
-
}
|
|
53
|
+
/**
|
|
54
|
+
* A component used to display additional information when users focus or hover on a point of interest.
|
|
55
|
+
* @slot - Default content placed inside of the tooltip
|
|
56
|
+
* @fires d2l-tooltip-show - Dispatched when the tooltip is opened
|
|
57
|
+
* @fires d2l-tooltip-hide - Dispatched when the tooltip is closed
|
|
58
|
+
*/
|
|
59
|
+
class Tooltip extends PopoverMixin(LitElement) {
|
|
60
|
+
|
|
61
|
+
static get properties() {
|
|
62
|
+
return {
|
|
63
|
+
/**
|
|
64
|
+
* Align the tooltip with either the start or end of its target. If not set, the tooltip will attempt be centered.
|
|
65
|
+
* @type {'start'|'end'}
|
|
66
|
+
*/
|
|
67
|
+
align: { type: String, reflect: true },
|
|
68
|
+
/**
|
|
69
|
+
* ADVANCED: Announce the tooltip innerText when applicable (for use with custom elements)
|
|
70
|
+
* @type {boolean}
|
|
71
|
+
*/
|
|
72
|
+
announced: { type: Boolean },
|
|
73
|
+
/**
|
|
74
|
+
* ADVANCED: Causes the tooltip to close when its target is clicked
|
|
75
|
+
* @type {boolean}
|
|
76
|
+
*/
|
|
77
|
+
closeOnClick: { type: Boolean, attribute: 'close-on-click' },
|
|
78
|
+
/**
|
|
79
|
+
* Provide a delay in milliseconds to prevent the tooltip from opening immediately when hovered. This delay will only apply to hover, not focus.
|
|
80
|
+
* @type {number}
|
|
81
|
+
*/
|
|
82
|
+
delay: { type: Number },
|
|
83
|
+
/**
|
|
84
|
+
* ADVANCED: Disables focus lock so the tooltip will automatically close when no longer hovered even if it still has focus
|
|
85
|
+
* @type {boolean}
|
|
86
|
+
*/
|
|
87
|
+
disableFocusLock: { type: Boolean, attribute: 'disable-focus-lock' },
|
|
88
|
+
/**
|
|
89
|
+
* REQUIRED: The "id" of the tooltip's target element. Both elements must be within the same shadow root. If not provided, the tooltip's parent element will be used as its target.
|
|
90
|
+
* @type {string}
|
|
91
|
+
*/
|
|
92
|
+
for: { type: String },
|
|
93
|
+
/**
|
|
94
|
+
* ADVANCED: Force the tooltip to stay open as long as it remains "true"
|
|
95
|
+
* @type {boolean}
|
|
96
|
+
*/
|
|
97
|
+
forceShow: { type: Boolean, attribute: 'force-show' },
|
|
98
|
+
/**
|
|
99
|
+
* ADVANCED: Accessibility type for the tooltip to specify whether it is the primary label for the target or a secondary descriptor.
|
|
100
|
+
* @type {'label'|'descriptor'}
|
|
101
|
+
*/
|
|
102
|
+
forType: { type: String, attribute: 'for-type' },
|
|
103
|
+
/**
|
|
104
|
+
* Adjust the size of the gap between the tooltip and its target (px)
|
|
105
|
+
* @type {number}
|
|
106
|
+
*/
|
|
107
|
+
offset: { type: Number },
|
|
108
|
+
/**
|
|
109
|
+
* ADVANCED: Force the tooltip to open in a certain direction. If no position is provided, the tooltip will open in the first position that has enough space for it in the order: bottom, top, right, left.
|
|
110
|
+
* @type {'top'|'bottom'|'left'|'right'}
|
|
111
|
+
*/
|
|
112
|
+
positionLocation: { type: String, attribute: 'position' },
|
|
113
|
+
/**
|
|
114
|
+
* @ignore
|
|
115
|
+
*/
|
|
116
|
+
showing: { type: Boolean, reflect: true },
|
|
117
|
+
/**
|
|
118
|
+
* ADVANCED: Only show the tooltip if we detect the target element is truncated
|
|
119
|
+
* @type {boolean}
|
|
120
|
+
*/
|
|
121
|
+
showTruncatedOnly: { type: Boolean, attribute: 'show-truncated-only' },
|
|
122
|
+
/**
|
|
123
|
+
* The style of the tooltip based on the type of information it displays
|
|
124
|
+
* @type {'info'|'error'}
|
|
125
|
+
*/
|
|
126
|
+
state: { type: String, reflect: true }
|
|
127
|
+
};
|
|
128
|
+
}
|
|
138
129
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
130
|
+
static get styles() {
|
|
131
|
+
return [super.styles, bodySmallStyles, css`
|
|
132
|
+
:host {
|
|
133
|
+
--d2l-tooltip-background-color: var(--d2l-color-ferrite); /* Deprecated, use state attribute instead */
|
|
134
|
+
--d2l-tooltip-border-color: var(--d2l-color-ferrite); /* Deprecated, use state attribute instead */
|
|
135
|
+
--d2l-tooltip-outline-color: rgba(255, 255, 255, 0.32);
|
|
136
|
+
--d2l-popover-background-color: var(--d2l-tooltip-background-color);
|
|
137
|
+
--d2l-popover-border-color: var(--d2l-tooltip-border-color);
|
|
138
|
+
--d2l-popover-border-radius: 0.3rem;
|
|
139
|
+
--d2l-popover-outline-color: var(--d2l-tooltip-outline-color);
|
|
140
|
+
--d2l-popover-outline-width: 1px;
|
|
141
|
+
}
|
|
142
|
+
:host([state="error"]) {
|
|
143
|
+
--d2l-tooltip-background-color: var(--d2l-color-cinnabar);
|
|
144
|
+
--d2l-tooltip-border-color: var(--d2l-color-cinnabar);
|
|
145
|
+
}
|
|
146
|
+
.d2l-tooltip-content {
|
|
147
|
+
box-sizing: border-box;
|
|
148
|
+
color: white;
|
|
149
|
+
max-width: 17.5rem;
|
|
150
|
+
min-height: 1.85rem;
|
|
151
|
+
min-width: 2.1rem;
|
|
152
|
+
overflow: hidden;
|
|
153
|
+
overflow-wrap: anywhere;
|
|
154
|
+
padding-block: ${10 - contentBorderSize}px ${11 - contentBorderSize}px;
|
|
155
|
+
padding-inline: ${contentHorizontalPadding - contentBorderSize}px;
|
|
156
|
+
white-space: normal;
|
|
157
|
+
}
|
|
158
|
+
::slotted(ul),
|
|
159
|
+
::slotted(ol) {
|
|
160
|
+
padding-inline-start: 1rem;
|
|
161
|
+
}
|
|
162
|
+
@media (max-width: 615px) {
|
|
155
163
|
.d2l-tooltip-content {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
max-width: 17.5rem;
|
|
159
|
-
min-height: 1.85rem;
|
|
160
|
-
min-width: 2.1rem;
|
|
161
|
-
overflow: hidden;
|
|
162
|
-
overflow-wrap: anywhere;
|
|
163
|
-
padding-block: ${10 - contentBorderSize}px ${11 - contentBorderSize}px;
|
|
164
|
-
padding-inline: ${contentHorizontalPadding - contentBorderSize}px;
|
|
165
|
-
white-space: normal;
|
|
166
|
-
}
|
|
167
|
-
::slotted(ul),
|
|
168
|
-
::slotted(ol) {
|
|
169
|
-
padding-inline-start: 1rem;
|
|
164
|
+
padding-bottom: ${12 - contentBorderSize}px;
|
|
165
|
+
padding-top: ${12 - contentBorderSize}px;
|
|
170
166
|
}
|
|
171
|
-
@media (max-width: 615px) {
|
|
172
|
-
.d2l-tooltip-content {
|
|
173
|
-
padding-bottom: ${12 - contentBorderSize}px;
|
|
174
|
-
padding-top: ${12 - contentBorderSize}px;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
`];
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
constructor() {
|
|
181
|
-
super();
|
|
182
|
-
this.announced = false;
|
|
183
|
-
this.closeOnClick = false;
|
|
184
|
-
this.delay = 300;
|
|
185
|
-
this.disableFocusLock = false;
|
|
186
|
-
this.forceShow = false;
|
|
187
|
-
this.forType = 'descriptor';
|
|
188
|
-
this.offset = 10;
|
|
189
|
-
this.showTruncatedOnly = false;
|
|
190
|
-
this.state = 'info';
|
|
191
|
-
|
|
192
|
-
this.#handleTargetBlurBound = this.#handleTargetBlur.bind(this);
|
|
193
|
-
this.#handleTargetClickBound = this.#handleTargetClick.bind(this);
|
|
194
|
-
this.#handleTargetFocusBound = this.#handleTargetFocus.bind(this);
|
|
195
|
-
this.#handleTargetMouseEnterBound = this.#handleTargetMouseEnter.bind(this);
|
|
196
|
-
this.#handleTargetMouseLeaveBound = this.#handleTargetMouseLeave.bind(this);
|
|
197
|
-
this.#handleTargetMutationBound = this.#handleTargetMutation.bind(this);
|
|
198
|
-
this.#handleTargetResizeBound = this.#handleTargetResize.bind(this);
|
|
199
|
-
this.#handleTargetTouchEndBound = this.#handleTargetTouchEnd.bind(this);
|
|
200
|
-
this.#handleTargetTouchStartBound = this.#handleTargetTouchStart.bind(this);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/** @ignore */
|
|
204
|
-
get showing() {
|
|
205
|
-
return this.#showing;
|
|
206
|
-
}
|
|
207
|
-
set showing(val) {
|
|
208
|
-
const oldVal = this.#showing;
|
|
209
|
-
if (oldVal !== val) {
|
|
210
|
-
this.#showing = val;
|
|
211
|
-
this.requestUpdate('showing', oldVal);
|
|
212
|
-
this.#showingChanged(val, oldVal !== undefined); // don't dispatch hide event when initializing
|
|
213
167
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
connectedCallback() {
|
|
217
|
-
super.connectedCallback();
|
|
218
|
-
this.showing = false;
|
|
219
|
-
window.addEventListener('resize', this.#handleTargetResizeBound);
|
|
220
|
-
|
|
221
|
-
requestAnimationFrame(() => this.#updateTarget());
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
disconnectedCallback() {
|
|
225
|
-
super.disconnectedCallback();
|
|
226
|
-
if (activeTooltip === this) activeTooltip = null;
|
|
227
|
-
|
|
228
|
-
this.#removeListeners();
|
|
229
|
-
window.removeEventListener('resize', this.#handleTargetResizeBound);
|
|
168
|
+
`];
|
|
169
|
+
}
|
|
230
170
|
|
|
231
|
-
|
|
171
|
+
constructor() {
|
|
172
|
+
super();
|
|
173
|
+
this.announced = false;
|
|
174
|
+
this.closeOnClick = false;
|
|
175
|
+
this.delay = 300;
|
|
176
|
+
this.disableFocusLock = false;
|
|
177
|
+
this.forceShow = false;
|
|
178
|
+
this.forType = 'descriptor';
|
|
179
|
+
this.offset = 10;
|
|
180
|
+
this.showTruncatedOnly = false;
|
|
181
|
+
this.state = 'info';
|
|
182
|
+
|
|
183
|
+
this.#handleTargetBlurBound = this.#handleTargetBlur.bind(this);
|
|
184
|
+
this.#handleTargetClickBound = this.#handleTargetClick.bind(this);
|
|
185
|
+
this.#handleTargetFocusBound = this.#handleTargetFocus.bind(this);
|
|
186
|
+
this.#handleTargetMouseEnterBound = this.#handleTargetMouseEnter.bind(this);
|
|
187
|
+
this.#handleTargetMouseLeaveBound = this.#handleTargetMouseLeave.bind(this);
|
|
188
|
+
this.#handleTargetMutationBound = this.#handleTargetMutation.bind(this);
|
|
189
|
+
this.#handleTargetResizeBound = this.#handleTargetResize.bind(this);
|
|
190
|
+
this.#handleTargetTouchEndBound = this.#handleTargetTouchEnd.bind(this);
|
|
191
|
+
this.#handleTargetTouchStartBound = this.#handleTargetTouchStart.bind(this);
|
|
192
|
+
}
|
|
232
193
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
194
|
+
/** @ignore */
|
|
195
|
+
get showing() {
|
|
196
|
+
return this.#showing;
|
|
197
|
+
}
|
|
198
|
+
set showing(val) {
|
|
199
|
+
const oldVal = this.#showing;
|
|
200
|
+
if (oldVal !== val) {
|
|
201
|
+
this.#showing = val;
|
|
202
|
+
this.requestUpdate('showing', oldVal);
|
|
203
|
+
this.#showingChanged(val, oldVal !== undefined); // don't dispatch hide event when initializing
|
|
237
204
|
}
|
|
205
|
+
}
|
|
238
206
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
207
|
+
connectedCallback() {
|
|
208
|
+
super.connectedCallback();
|
|
209
|
+
this.showing = false;
|
|
210
|
+
window.addEventListener('resize', this.#handleTargetResizeBound);
|
|
244
211
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
<div class="d2l-tooltip-content d2l-body-small" role="text">
|
|
248
|
-
<slot></slot>
|
|
249
|
-
</div>
|
|
250
|
-
`;
|
|
212
|
+
requestAnimationFrame(() => this.#updateTarget());
|
|
213
|
+
}
|
|
251
214
|
|
|
252
|
-
|
|
253
|
-
|
|
215
|
+
disconnectedCallback() {
|
|
216
|
+
super.disconnectedCallback();
|
|
217
|
+
if (activeTooltip === this) activeTooltip = null;
|
|
254
218
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (changedProperties.has('align') || changedProperties.has('forceShow') || changedProperties.has('offset') || changedProperties.has('positionLocation')) {
|
|
259
|
-
super.configure({
|
|
260
|
-
maxWidth: '350',
|
|
261
|
-
minWidth: '48',
|
|
262
|
-
noAutoClose: this.forceShow,
|
|
263
|
-
offset: (this.offset !== undefined ? Number.parseInt(this.offset) : undefined),
|
|
264
|
-
position: { location: this.#adaptPositionLocation(this.positionLocation), span: this.#adaptPositionSpan(this.align) },
|
|
265
|
-
});
|
|
266
|
-
}
|
|
219
|
+
this.#removeListeners();
|
|
220
|
+
window.removeEventListener('resize', this.#handleTargetResizeBound);
|
|
267
221
|
|
|
268
|
-
|
|
269
|
-
if (prop === 'for') this.#updateTarget();
|
|
270
|
-
else if (prop === 'forceShow') this.#updateShowing();
|
|
271
|
-
});
|
|
272
|
-
}
|
|
222
|
+
delayTimeoutId = null;
|
|
273
223
|
|
|
274
|
-
|
|
275
|
-
this.#
|
|
276
|
-
this.#
|
|
277
|
-
this.#updateShowing();
|
|
224
|
+
if (this.#target) {
|
|
225
|
+
elemIdListRemove(this.#target, 'aria-labelledby', this.id);
|
|
226
|
+
elemIdListRemove(this.#target, 'aria-describedby', this.id);
|
|
278
227
|
}
|
|
228
|
+
}
|
|
279
229
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
230
|
+
firstUpdated(changedProperties) {
|
|
231
|
+
super.firstUpdated(changedProperties);
|
|
232
|
+
this.addEventListener('mouseenter', this.#handleTooltipMouseEnter);
|
|
233
|
+
this.addEventListener('mouseleave', this.#handleTooltipMouseLeave);
|
|
234
|
+
}
|
|
283
235
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
236
|
+
render() {
|
|
237
|
+
const content = html`
|
|
238
|
+
<div class="d2l-tooltip-content d2l-body-small" role="text">
|
|
239
|
+
<slot></slot>
|
|
240
|
+
</div>
|
|
241
|
+
`;
|
|
287
242
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
#handleTargetFocusBound;
|
|
291
|
-
#handleTargetMouseEnterBound;
|
|
292
|
-
#handleTargetMouseLeaveBound;
|
|
293
|
-
#handleTargetMutationBound;
|
|
294
|
-
#handleTargetResizeBound;
|
|
295
|
-
#handleTargetTouchEndBound;
|
|
296
|
-
#handleTargetTouchStartBound;
|
|
297
|
-
#hoverTimeout;
|
|
298
|
-
#isFocusing = false;
|
|
299
|
-
#isHovering = false;
|
|
300
|
-
#isHoveringTooltip = false;
|
|
301
|
-
#isTruncating = false;
|
|
302
|
-
#longPressTimeout;
|
|
303
|
-
#mouseLeaveTimeout;
|
|
304
|
-
#mouseLeftTooltip = false;
|
|
305
|
-
#resizeRunSinceTruncationCheck = false;
|
|
306
|
-
#showing;
|
|
307
|
-
#target;
|
|
308
|
-
#targetSizeObserver;
|
|
309
|
-
#targetMutationObserver;
|
|
310
|
-
|
|
311
|
-
// for testing only!
|
|
312
|
-
_getTarget() {
|
|
313
|
-
return this.#target;
|
|
314
|
-
}
|
|
243
|
+
return this.renderPopover(content);
|
|
244
|
+
}
|
|
315
245
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
case 'bottom': return 'block-end';
|
|
319
|
-
case 'left': return 'inline-start';
|
|
320
|
-
case 'right': return 'inline-end';
|
|
321
|
-
case 'top': return 'block-start';
|
|
322
|
-
default: return 'block-end';
|
|
323
|
-
}
|
|
324
|
-
}
|
|
246
|
+
willUpdate(changedProperties) {
|
|
247
|
+
super.willUpdate(changedProperties);
|
|
325
248
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
249
|
+
if (changedProperties.has('align') || changedProperties.has('forceShow') || changedProperties.has('offset') || changedProperties.has('positionLocation')) {
|
|
250
|
+
super.configure({
|
|
251
|
+
maxWidth: '350',
|
|
252
|
+
minWidth: '48',
|
|
253
|
+
noAutoClose: this.forceShow,
|
|
254
|
+
offset: (this.offset !== undefined ? Number.parseInt(this.offset) : undefined),
|
|
255
|
+
position: { location: this.#adaptPositionLocation(this.positionLocation), span: this.#adaptPositionSpan(this.align) },
|
|
256
|
+
});
|
|
332
257
|
}
|
|
333
258
|
|
|
334
|
-
|
|
335
|
-
|
|
259
|
+
changedProperties.forEach((_, prop) => {
|
|
260
|
+
if (prop === 'for') this.#updateTarget();
|
|
261
|
+
else if (prop === 'forceShow') this.#updateShowing();
|
|
262
|
+
});
|
|
263
|
+
}
|
|
336
264
|
|
|
337
|
-
|
|
265
|
+
hide() {
|
|
266
|
+
this.#isHovering = false;
|
|
267
|
+
this.#isFocusing = false;
|
|
268
|
+
this.#updateShowing();
|
|
269
|
+
}
|
|
338
270
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
this.#target.addEventListener('blur', this.#handleTargetBlurBound);
|
|
343
|
-
this.#target.addEventListener('click', this.#handleTargetClickBound);
|
|
344
|
-
this.#target.addEventListener('touchstart', this.#handleTargetTouchStartBound, { passive: true });
|
|
345
|
-
this.#target.addEventListener('touchcancel', this.#handleTargetTouchEndBound);
|
|
346
|
-
this.#target.addEventListener('touchend', this.#handleTargetTouchEndBound);
|
|
271
|
+
show() {
|
|
272
|
+
this.showing = true;
|
|
273
|
+
}
|
|
347
274
|
|
|
348
|
-
|
|
349
|
-
|
|
275
|
+
updatePosition() {
|
|
276
|
+
return super.position();
|
|
277
|
+
}
|
|
350
278
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
279
|
+
#handleTargetBlurBound;
|
|
280
|
+
#handleTargetClickBound;
|
|
281
|
+
#handleTargetFocusBound;
|
|
282
|
+
#handleTargetMouseEnterBound;
|
|
283
|
+
#handleTargetMouseLeaveBound;
|
|
284
|
+
#handleTargetMutationBound;
|
|
285
|
+
#handleTargetResizeBound;
|
|
286
|
+
#handleTargetTouchEndBound;
|
|
287
|
+
#handleTargetTouchStartBound;
|
|
288
|
+
#hoverTimeout;
|
|
289
|
+
#isFocusing = false;
|
|
290
|
+
#isHovering = false;
|
|
291
|
+
#isHoveringTooltip = false;
|
|
292
|
+
#isTruncating = false;
|
|
293
|
+
#longPressTimeout;
|
|
294
|
+
#mouseLeaveTimeout;
|
|
295
|
+
#mouseLeftTooltip = false;
|
|
296
|
+
#resizeRunSinceTruncationCheck = false;
|
|
297
|
+
#showing;
|
|
298
|
+
#target;
|
|
299
|
+
#targetSizeObserver;
|
|
300
|
+
#targetMutationObserver;
|
|
301
|
+
|
|
302
|
+
// for testing only!
|
|
303
|
+
_getTarget() {
|
|
304
|
+
return this.#target;
|
|
305
|
+
}
|
|
355
306
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
target = (target || ownerRoot?.host?.querySelector(targetSelector)) ?? null;
|
|
364
|
-
} else {
|
|
365
|
-
const parentNode = this.parentNode;
|
|
366
|
-
target = parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE ? ownerRoot.host : parentNode;
|
|
367
|
-
|
|
368
|
-
// reduce console pollution since Safari + VO prevents inadequate SR experience for tooltips during form validation when using 'for'
|
|
369
|
-
if (!(target.tagName === 'D2L-INPUT-TEXT' && target?.invalid)) {
|
|
370
|
-
console.warn('<d2l-tooltip>: missing required attribute "for"');
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return target;
|
|
307
|
+
#adaptPositionLocation(val) {
|
|
308
|
+
switch (val) {
|
|
309
|
+
case 'bottom': return 'block-end';
|
|
310
|
+
case 'left': return 'inline-start';
|
|
311
|
+
case 'right': return 'inline-end';
|
|
312
|
+
case 'top': return 'block-start';
|
|
313
|
+
default: return 'block-end';
|
|
374
314
|
}
|
|
315
|
+
}
|
|
375
316
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
317
|
+
#adaptPositionSpan(val) {
|
|
318
|
+
switch (val) {
|
|
319
|
+
case 'start': return 'end';
|
|
320
|
+
case 'end': return 'start';
|
|
321
|
+
default: return 'all';
|
|
379
322
|
}
|
|
323
|
+
}
|
|
380
324
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
325
|
+
#addListeners() {
|
|
326
|
+
this.addEventListener('d2l-popover-close', this.hide);
|
|
384
327
|
|
|
385
|
-
|
|
386
|
-
if (this.showTruncatedOnly) {
|
|
387
|
-
await this.#updateTruncating();
|
|
388
|
-
if (!this.#isTruncating) return;
|
|
389
|
-
}
|
|
328
|
+
if (!this.#target) return;
|
|
390
329
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
330
|
+
this.#target.addEventListener('mouseenter', this.#handleTargetMouseEnterBound);
|
|
331
|
+
this.#target.addEventListener('mouseleave', this.#handleTargetMouseLeaveBound);
|
|
332
|
+
this.#target.addEventListener('focus', this.#handleTargetFocusBound);
|
|
333
|
+
this.#target.addEventListener('blur', this.#handleTargetBlurBound);
|
|
334
|
+
this.#target.addEventListener('click', this.#handleTargetClickBound);
|
|
335
|
+
this.#target.addEventListener('touchstart', this.#handleTargetTouchStartBound, { passive: true });
|
|
336
|
+
this.#target.addEventListener('touchcancel', this.#handleTargetTouchEndBound);
|
|
337
|
+
this.#target.addEventListener('touchend', this.#handleTargetTouchEndBound);
|
|
398
338
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (this.#mouseLeftTooltip) {
|
|
402
|
-
this.#isHovering = true;
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
339
|
+
this.#targetSizeObserver = new ResizeObserver(this.#handleTargetResizeBound);
|
|
340
|
+
this.#targetSizeObserver.observe(this.#target);
|
|
405
341
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
342
|
+
this.#targetMutationObserver = new MutationObserver(this.#handleTargetMutationBound);
|
|
343
|
+
this.#targetMutationObserver.observe(this.#target, { attributes: true, attributeFilter: ['id'] });
|
|
344
|
+
this.#targetMutationObserver.observe(this.#target.parentNode, { childList: true });
|
|
345
|
+
}
|
|
411
346
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}, getDelay(this.delay));
|
|
415
|
-
}
|
|
347
|
+
#findTarget() {
|
|
348
|
+
const ownerRoot = this.getRootNode();
|
|
416
349
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
350
|
+
let target;
|
|
351
|
+
if (this.for) {
|
|
352
|
+
const targetSelector = `#${cssEscape(this.for)}`;
|
|
353
|
+
target = ownerRoot.querySelector(targetSelector);
|
|
354
|
+
target = (target || ownerRoot?.host?.querySelector(targetSelector)) ?? null;
|
|
355
|
+
} else {
|
|
356
|
+
const parentNode = this.parentNode;
|
|
357
|
+
target = parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE ? ownerRoot.host : parentNode;
|
|
423
358
|
|
|
424
|
-
|
|
425
|
-
if (!
|
|
426
|
-
|
|
427
|
-
this.#updateTarget();
|
|
359
|
+
// reduce console pollution since Safari + VO prevents inadequate SR experience for tooltips during form validation when using 'for'
|
|
360
|
+
if (!(target.tagName === 'D2L-INPUT-TEXT' && target?.invalid)) {
|
|
361
|
+
console.warn('<d2l-tooltip>: missing required attribute "for"');
|
|
428
362
|
}
|
|
429
363
|
}
|
|
364
|
+
return target;
|
|
365
|
+
}
|
|
430
366
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
367
|
+
#handleTargetBlur() {
|
|
368
|
+
this.#isFocusing = false;
|
|
369
|
+
this.#updateShowing();
|
|
370
|
+
}
|
|
436
371
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
372
|
+
#handleTargetClick() {
|
|
373
|
+
if (this.closeOnClick) this.hide();
|
|
374
|
+
}
|
|
440
375
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
376
|
+
async #handleTargetFocus() {
|
|
377
|
+
if (this.showTruncatedOnly) {
|
|
378
|
+
await this.#updateTruncating();
|
|
379
|
+
if (!this.#isTruncating) return;
|
|
445
380
|
}
|
|
446
381
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
382
|
+
if (this.disableFocusLock) {
|
|
383
|
+
this.showing = true;
|
|
384
|
+
} else {
|
|
385
|
+
this.#isFocusing = true;
|
|
450
386
|
this.#updateShowing();
|
|
451
387
|
}
|
|
388
|
+
}
|
|
452
389
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
this.#
|
|
457
|
-
|
|
458
|
-
resetDelayTimeout();
|
|
459
|
-
|
|
460
|
-
this.#mouseLeaveTimeout = setTimeout(() => {
|
|
461
|
-
this.#mouseLeftTooltip = false;
|
|
462
|
-
this.#updateShowing();
|
|
463
|
-
}, 100); // delay to allow for mouseenter to fire if hovering on target
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
#removeListeners() {
|
|
467
|
-
this.removeEventListener('d2l-popover-close', this.hide);
|
|
468
|
-
|
|
469
|
-
if (!this.#target) return;
|
|
470
|
-
|
|
471
|
-
this.#target.removeEventListener('mouseenter', this.#handleTargetMouseEnterBound);
|
|
472
|
-
this.#target.removeEventListener('mouseleave', this.#handleTargetMouseLeaveBound);
|
|
473
|
-
this.#target.removeEventListener('focus', this.#handleTargetFocusBound);
|
|
474
|
-
this.#target.removeEventListener('blur', this.#handleTargetBlurBound);
|
|
475
|
-
this.#target.removeEventListener('click', this.#handleTargetClickBound);
|
|
476
|
-
this.#target.removeEventListener('touchstart', this.#handleTargetTouchStartBound);
|
|
477
|
-
this.#target.removeEventListener('touchcancel', this.#handleTargetTouchEndBound);
|
|
478
|
-
this.#target.removeEventListener('touchend', this.#handleTargetTouchEndBound);
|
|
479
|
-
|
|
480
|
-
if (this.#targetSizeObserver) {
|
|
481
|
-
this.#targetSizeObserver.disconnect();
|
|
482
|
-
this.#targetSizeObserver = null;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if (this.#targetMutationObserver) {
|
|
486
|
-
this.#targetMutationObserver.disconnect();
|
|
487
|
-
this.#targetMutationObserver = null;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
async #showingChanged(newValue, dispatch) {
|
|
492
|
-
clearTimeout(this.#hoverTimeout);
|
|
493
|
-
clearTimeout(this.#longPressTimeout);
|
|
494
|
-
|
|
495
|
-
if (newValue) {
|
|
496
|
-
if (!this.forceShow) {
|
|
497
|
-
if (activeTooltip) activeTooltip.hide();
|
|
498
|
-
activeTooltip = this;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
this.setAttribute('aria-hidden', 'false');
|
|
502
|
-
await this.updateComplete;
|
|
503
|
-
|
|
504
|
-
// wait for popover before dispatching (ex. otherwise visual-diff won't capture complete target area)
|
|
505
|
-
await super.open(this.#target, false);
|
|
506
|
-
|
|
507
|
-
if (dispatch) {
|
|
508
|
-
this.dispatchEvent(new CustomEvent('d2l-tooltip-show', { bubbles: true, composed: true }));
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (this.announced && !isInteractiveTarget(this.#target)) announce(this.innerText);
|
|
512
|
-
} else {
|
|
513
|
-
if (activeTooltip === this) activeTooltip = null;
|
|
514
|
-
|
|
515
|
-
this.setAttribute('aria-hidden', 'true');
|
|
516
|
-
|
|
517
|
-
super.close();
|
|
518
|
-
|
|
519
|
-
if (dispatch) {
|
|
520
|
-
this.dispatchEvent(new CustomEvent('d2l-tooltip-hide', { bubbles: true, composed: true }));
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
#updateShowing() {
|
|
526
|
-
this.showing = this.#isFocusing || this.#isHovering || this.forceShow || this.#isHoveringTooltip;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
#updateTarget() {
|
|
530
|
-
|
|
531
|
-
if (!this.isConnected) return;
|
|
532
|
-
|
|
533
|
-
const newTarget = this.#findTarget();
|
|
534
|
-
if (this.#target === newTarget) return;
|
|
535
|
-
|
|
536
|
-
this.#removeListeners();
|
|
537
|
-
this.#target = newTarget;
|
|
538
|
-
|
|
539
|
-
if (this.#target) {
|
|
540
|
-
const targetDisabled = this.#target.hasAttribute('disabled') || this.#target.getAttribute('aria-disabled') === 'true';
|
|
541
|
-
|
|
542
|
-
const isTargetInteractive = isInteractiveTarget(this.#target);
|
|
543
|
-
this.id = this.id || getUniqueId();
|
|
544
|
-
this.setAttribute('role', 'tooltip');
|
|
545
|
-
|
|
546
|
-
if (this.forType === 'label') {
|
|
547
|
-
elemIdListAdd(this.#target, 'aria-labelledby', this.id);
|
|
548
|
-
} else if (!this.announced || isTargetInteractive) {
|
|
549
|
-
elemIdListAdd(this.#target, 'aria-describedby', this.id);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (logAccessibilityWarning && !isTargetInteractive && !this.announced) {
|
|
553
|
-
console.warn(
|
|
554
|
-
'd2l-tooltip may be being used in a non-accessible manner; it should be attached to interactive elements like \'a\', \'button\',' +
|
|
555
|
-
'\'input\'', '\'select\', \'textarea\' or static / custom elements if a role has been set and the element is focusable.',
|
|
556
|
-
this.#target
|
|
557
|
-
);
|
|
558
|
-
logAccessibilityWarning = false;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
if (this.showing) {
|
|
562
|
-
if (!super._opened) super.open(this.#target, false);
|
|
563
|
-
else super.position();
|
|
564
|
-
} else if (!targetDisabled && isComposedAncestor(this.#target, getComposedActiveElement())) {
|
|
565
|
-
this.#handleTargetFocusBound();
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
this.#addListeners();
|
|
390
|
+
#handleTargetMouseEnter() {
|
|
391
|
+
// came from tooltip so keep showing
|
|
392
|
+
if (this.#mouseLeftTooltip) {
|
|
393
|
+
this.#isHovering = true;
|
|
394
|
+
return;
|
|
569
395
|
}
|
|
570
396
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
* and get the expected styles through getComputedStyle.
|
|
576
|
-
*/
|
|
577
|
-
async #updateTruncating() {
|
|
578
|
-
// if no resize has happened since truncation was previously calculated the result will not have changed
|
|
579
|
-
if (!this.#resizeRunSinceTruncationCheck || !this.showTruncatedOnly) return;
|
|
580
|
-
|
|
581
|
-
const target = this.#target;
|
|
582
|
-
const cloneContainer = document.createElement('div');
|
|
583
|
-
cloneContainer.style.position = 'absolute';
|
|
584
|
-
cloneContainer.style.overflow = 'hidden';
|
|
585
|
-
cloneContainer.style.whiteSpace = 'nowrap';
|
|
586
|
-
cloneContainer.style.width = '1px';
|
|
587
|
-
cloneContainer.style.insetInlineStart = '-10000px';
|
|
588
|
-
|
|
589
|
-
const clone = target.cloneNode(true);
|
|
590
|
-
clone.removeAttribute('id');
|
|
591
|
-
clone.style.maxWidth = 'none';
|
|
592
|
-
clone.style.display = 'inline-block';
|
|
593
|
-
|
|
594
|
-
cloneContainer.appendChild(clone);
|
|
595
|
-
target.appendChild(cloneContainer);
|
|
596
|
-
await this.updateComplete;
|
|
597
|
-
|
|
598
|
-
// if the clone is a web component it needs to update to fill in any slots
|
|
599
|
-
const customElem = customElements.get(clone.localName);
|
|
600
|
-
if (customElem !== undefined) {
|
|
601
|
-
clone.requestUpdate();
|
|
602
|
-
await clone.updateComplete;
|
|
397
|
+
this.#hoverTimeout = setTimeout(async() => {
|
|
398
|
+
if (this.showTruncatedOnly) {
|
|
399
|
+
await this.#updateTruncating();
|
|
400
|
+
if (!this.#isTruncating) return;
|
|
603
401
|
}
|
|
604
402
|
|
|
605
|
-
this.#
|
|
606
|
-
this.#
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
|
|
403
|
+
this.#isHovering = true;
|
|
404
|
+
this.#updateShowing();
|
|
405
|
+
}, getDelay(this.delay));
|
|
610
406
|
}
|
|
611
|
-
customElements.define('d2l-tooltip', Tooltip);
|
|
612
|
-
|
|
613
|
-
} else {
|
|
614
|
-
|
|
615
|
-
// Cleanup: GAUD-7355-tooltip-popover - remove this entire block and unused imports
|
|
616
407
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const pointerGap = 0; /* spacing between pointer and target */
|
|
625
|
-
const defaultViewportMargin = 18;
|
|
626
|
-
const contentBorderRadius = 6;
|
|
627
|
-
const outlineSize = 1;
|
|
628
|
-
|
|
629
|
-
const computeTooltipShift = (centerDelta, spaceLeft, spaceRight) => {
|
|
630
|
-
|
|
631
|
-
const contentXAdjustment = centerDelta / 2;
|
|
632
|
-
if (centerDelta <= 0) {
|
|
633
|
-
return contentXAdjustment * -1;
|
|
634
|
-
}
|
|
635
|
-
if (spaceLeft >= contentXAdjustment && spaceRight >= contentXAdjustment) {
|
|
636
|
-
// center with target
|
|
637
|
-
return contentXAdjustment * -1;
|
|
638
|
-
}
|
|
639
|
-
if (spaceLeft <= contentXAdjustment) {
|
|
640
|
-
// shift content right (not enough space to center)
|
|
641
|
-
return spaceLeft * -1;
|
|
642
|
-
} else {
|
|
643
|
-
// shift content left (not enough space to center)
|
|
644
|
-
return (centerDelta * -1) + spaceRight;
|
|
645
|
-
}
|
|
646
|
-
};
|
|
408
|
+
#handleTargetMouseLeave() {
|
|
409
|
+
clearTimeout(this.#hoverTimeout);
|
|
410
|
+
this.#isHovering = false;
|
|
411
|
+
if (this.showing) resetDelayTimeout();
|
|
412
|
+
setTimeout(() => this.#updateShowing(), 100); // delay to allow for mouseenter to fire if hovering on tooltip
|
|
413
|
+
}
|
|
647
414
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
* @fires d2l-tooltip-hide - Dispatched when the tooltip is closed
|
|
653
|
-
*/
|
|
654
|
-
class Tooltip extends RtlMixin(LitElement) {
|
|
655
|
-
|
|
656
|
-
static get properties() {
|
|
657
|
-
return {
|
|
658
|
-
/**
|
|
659
|
-
* Align the tooltip with either the start or end of its target. If not set, the tooltip will attempt be centered.
|
|
660
|
-
* @type {'start'|'end'}
|
|
661
|
-
*/
|
|
662
|
-
align: { type: String, reflect: true },
|
|
663
|
-
/**
|
|
664
|
-
* ADVANCED: Announce the tooltip innerText when applicable (for use with custom elements)
|
|
665
|
-
* @type {boolean}
|
|
666
|
-
*/
|
|
667
|
-
announced: { type: Boolean },
|
|
668
|
-
/**
|
|
669
|
-
* @ignore
|
|
670
|
-
*/
|
|
671
|
-
boundary: { type: Object },
|
|
672
|
-
/**
|
|
673
|
-
* ADVANCED: Causes the tooltip to close when its target is clicked
|
|
674
|
-
* @type {boolean}
|
|
675
|
-
*/
|
|
676
|
-
closeOnClick: { type: Boolean, attribute: 'close-on-click' },
|
|
677
|
-
/**
|
|
678
|
-
* Provide a delay in milliseconds to prevent the tooltip from opening immediately when hovered. This delay will only apply to hover, not focus.
|
|
679
|
-
* @type {number}
|
|
680
|
-
*/
|
|
681
|
-
delay: { type: Number },
|
|
682
|
-
/**
|
|
683
|
-
* ADVANCED: Disables focus lock so the tooltip will automatically close when no longer hovered even if it still has focus
|
|
684
|
-
* @type {boolean}
|
|
685
|
-
*/
|
|
686
|
-
disableFocusLock: { type: Boolean, attribute: 'disable-focus-lock' },
|
|
687
|
-
/**
|
|
688
|
-
* REQUIRED: The "id" of the tooltip's target element. Both elements must be within the same shadow root. If not provided, the tooltip's parent element will be used as its target.
|
|
689
|
-
* @type {string}
|
|
690
|
-
*/
|
|
691
|
-
for: { type: String },
|
|
692
|
-
/**
|
|
693
|
-
* ADVANCED: Force the tooltip to stay open as long as it remains "true"
|
|
694
|
-
* @type {boolean}
|
|
695
|
-
*/
|
|
696
|
-
forceShow: { type: Boolean, attribute: 'force-show' },
|
|
697
|
-
/**
|
|
698
|
-
* ADVANCED: Accessibility type for the tooltip to specify whether it is the primary label for the target or a secondary descriptor.
|
|
699
|
-
* @type {'label'|'descriptor'}
|
|
700
|
-
*/
|
|
701
|
-
forType: { type: String, attribute: 'for-type' },
|
|
702
|
-
/**
|
|
703
|
-
* Adjust the size of the gap between the tooltip and its target (px)
|
|
704
|
-
* @type {number}
|
|
705
|
-
*/
|
|
706
|
-
offset: { type: Number }, /* tooltipOffset */
|
|
707
|
-
/**
|
|
708
|
-
* ADVANCED: Only show the tooltip if we detect the target element is truncated
|
|
709
|
-
* @type {boolean}
|
|
710
|
-
*/
|
|
711
|
-
showTruncatedOnly: { type: Boolean, attribute: 'show-truncated-only' },
|
|
712
|
-
/**
|
|
713
|
-
* ADVANCED: Force the tooltip to open in a certain direction. If no position is provided, the tooltip will open in the first position that has enough space for it in the order: bottom, top, right, left.
|
|
714
|
-
* @type {'top'|'bottom'|'left'|'right'}
|
|
715
|
-
*/
|
|
716
|
-
position: { type: String },
|
|
717
|
-
/**
|
|
718
|
-
* @ignore
|
|
719
|
-
*/
|
|
720
|
-
showing: { type: Boolean, reflect: true },
|
|
721
|
-
/**
|
|
722
|
-
* The style of the tooltip based on the type of information it displays
|
|
723
|
-
* @type {'info'|'error'}
|
|
724
|
-
*/
|
|
725
|
-
state: { type: String, reflect: true },
|
|
726
|
-
_maxWidth: { type: Number },
|
|
727
|
-
_openDir: { type: String, reflect: true, attribute: '_open-dir' },
|
|
728
|
-
_tooltipShift: { type: Number },
|
|
729
|
-
_viewportMargin: { type: Number }
|
|
730
|
-
};
|
|
415
|
+
#handleTargetMutation([m]) {
|
|
416
|
+
if (!this.#target.isConnected || (m.target === this.#target && m.attributeName === 'id')) {
|
|
417
|
+
this.#targetMutationObserver.disconnect();
|
|
418
|
+
this.#updateTarget();
|
|
731
419
|
}
|
|
420
|
+
}
|
|
732
421
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
--d2l-tooltip-outline-color: rgba(255, 255, 255, 0.32);
|
|
739
|
-
box-sizing: border-box;
|
|
740
|
-
color: white;
|
|
741
|
-
display: inline-block;
|
|
742
|
-
height: 0;
|
|
743
|
-
overflow: hidden;
|
|
744
|
-
position: absolute;
|
|
745
|
-
text-align: left;
|
|
746
|
-
visibility: hidden;
|
|
747
|
-
white-space: normal;
|
|
748
|
-
width: 0;
|
|
749
|
-
z-index: 1001; /* position on top of floating buttons */
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
:host([state="error"]) {
|
|
753
|
-
--d2l-tooltip-background-color: var(--d2l-color-cinnabar);
|
|
754
|
-
--d2l-tooltip-border-color: var(--d2l-color-cinnabar);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
:host([dir="rtl"]) {
|
|
758
|
-
text-align: right;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
:host([force-show]),
|
|
762
|
-
:host([showing]) {
|
|
763
|
-
height: auto;
|
|
764
|
-
overflow: visible;
|
|
765
|
-
visibility: visible;
|
|
766
|
-
width: auto;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
.d2l-tooltip-pointer {
|
|
770
|
-
border: 1px solid transparent; /* fixes a webket clipping defect */
|
|
771
|
-
box-sizing: border-box;
|
|
772
|
-
display: inline-block;
|
|
773
|
-
height: ${pointerLength}px;
|
|
774
|
-
position: absolute;
|
|
775
|
-
width: ${pointerLength}px;
|
|
776
|
-
z-index: 1;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
:host([_open-dir="top"]) .d2l-tooltip-pointer,
|
|
780
|
-
:host([_open-dir="bottom"]) .d2l-tooltip-pointer {
|
|
781
|
-
left: calc(50% - ${pointerLength / 2}px);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
:host([_open-dir="top"][align="start"]) .d2l-tooltip-pointer,
|
|
785
|
-
:host([_open-dir="bottom"][align="start"]) .d2l-tooltip-pointer,
|
|
786
|
-
:host([_open-dir="top"][align="end"][dir="rtl"]) .d2l-tooltip-pointer,
|
|
787
|
-
:host([_open-dir="bottom"][align="end"][dir="rtl"]) .d2l-tooltip-pointer {
|
|
788
|
-
left: ${contentHorizontalPadding + (pointerRotatedLength - pointerLength) / 2}px; /* needed for browsers that don't support min like Legacy-Edge */
|
|
789
|
-
left: min(${contentHorizontalPadding + (pointerRotatedLength - pointerLength) / 2}px, calc(50% - ${pointerLength / 2}px));
|
|
790
|
-
right: auto;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
:host([_open-dir="top"][align="end"]) .d2l-tooltip-pointer,
|
|
794
|
-
:host([_open-dir="bottom"][align="end"]) .d2l-tooltip-pointer,
|
|
795
|
-
:host([_open-dir="top"][align="start"][dir="rtl"]) .d2l-tooltip-pointer,
|
|
796
|
-
:host([_open-dir="bottom"][align="start"][dir="rtl"]) .d2l-tooltip-pointer {
|
|
797
|
-
left: auto;
|
|
798
|
-
right: ${contentHorizontalPadding + (pointerRotatedLength - pointerLength) / 2}px; /* needed for browsers that don't support min like Legacy-Edge */
|
|
799
|
-
right: min(${contentHorizontalPadding + (pointerRotatedLength - pointerLength) / 2}px, calc(50% - ${pointerLength / 2}px));
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
:host([_open-dir="top"]) .d2l-tooltip-pointer {
|
|
803
|
-
bottom: -${pointerOverhang}px;
|
|
804
|
-
clip: rect(${pointerOverhang + contentBorderSize}px, 21px, 22px, -3px);
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
:host([_open-dir="bottom"]) .d2l-tooltip-pointer {
|
|
808
|
-
clip: rect(-5px, 21px, ${pointerOverhang + contentBorderSize}px, -7px);
|
|
809
|
-
top: -${pointerOverhang}px;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
:host([_open-dir="left"]) .d2l-tooltip-pointer,
|
|
813
|
-
:host([_open-dir="right"]) .d2l-tooltip-pointer {
|
|
814
|
-
top: calc(50% - ${pointerLength / 2}px);
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
:host([_open-dir="left"]) .d2l-tooltip-pointer {
|
|
818
|
-
clip: rect(-3px, 21px, 21px, ${pointerOverhang + contentBorderSize}px);
|
|
819
|
-
right: -${pointerOverhang}px;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
:host([_open-dir="right"]) .d2l-tooltip-pointer {
|
|
823
|
-
clip: rect(-3px, ${pointerOverhang + contentBorderSize}px, 21px, -3px);
|
|
824
|
-
left: -${pointerOverhang}px;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
.d2l-tooltip-pointer > div {
|
|
828
|
-
background-color: var(--d2l-tooltip-background-color);
|
|
829
|
-
border: ${contentBorderSize}px solid var(--d2l-tooltip-border-color);
|
|
830
|
-
border-radius: 0.1rem;
|
|
831
|
-
box-sizing: border-box;
|
|
832
|
-
height: ${pointerLength}px;
|
|
833
|
-
left: -1px;
|
|
834
|
-
position: absolute;
|
|
835
|
-
top: -1px;
|
|
836
|
-
-webkit-transform: rotate(45deg);
|
|
837
|
-
transform: rotate(45deg);
|
|
838
|
-
width: ${pointerLength}px;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
:host([_open-dir="top"]) .d2l-tooltip-pointer-outline {
|
|
842
|
-
clip: rect(${pointerOverhang + contentBorderSize + outlineSize * 2}px, 21px, 22px, -3px);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
:host([_open-dir="bottom"]) .d2l-tooltip-pointer-outline {
|
|
846
|
-
clip: rect(-4px, 21px, ${pointerOverhang + contentBorderSize - outlineSize * 2}px, -7px);
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
:host([_open-dir="left"]) .d2l-tooltip-pointer-outline {
|
|
850
|
-
clip: rect(-3px, 21px, 21px, ${pointerOverhang + contentBorderSize + outlineSize * 2}px);
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
:host([_open-dir="right"]) .d2l-tooltip-pointer-outline {
|
|
854
|
-
clip: rect(-3px, ${pointerOverhang + contentBorderSize - outlineSize * 2}px, 21px, -4px);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
.d2l-tooltip-pointer-outline > div {
|
|
858
|
-
outline: ${outlineSize}px solid var(--d2l-tooltip-outline-color);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
.d2l-tooltip-position {
|
|
862
|
-
display: inline-block;
|
|
863
|
-
height: 0;
|
|
864
|
-
position: absolute;
|
|
865
|
-
width: 17.5rem;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
:host([_open-dir="left"]) .d2l-tooltip-position {
|
|
869
|
-
right: 100%;
|
|
870
|
-
}
|
|
871
|
-
:host([_open-dir="right"][dir="rtl"]) .d2l-tooltip-position {
|
|
872
|
-
left: 100%;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
.d2l-tooltip-content {
|
|
876
|
-
background-color: var(--d2l-tooltip-background-color);
|
|
877
|
-
border: ${contentBorderSize}px solid var(--d2l-tooltip-border-color);
|
|
878
|
-
border-radius: ${contentBorderRadius}px;
|
|
879
|
-
box-sizing: border-box;
|
|
880
|
-
max-width: 17.5rem;
|
|
881
|
-
min-height: 1.95rem;
|
|
882
|
-
min-width: 2.1rem;
|
|
883
|
-
outline: ${outlineSize}px solid var(--d2l-tooltip-outline-color);
|
|
884
|
-
overflow: hidden;
|
|
885
|
-
overflow-wrap: anywhere;
|
|
886
|
-
padding-block: ${10 - contentBorderSize}px ${11 - contentBorderSize}px;
|
|
887
|
-
padding-inline: ${contentHorizontalPadding - contentBorderSize}px;
|
|
888
|
-
position: absolute;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
/* increase specificty for Legacy-Edge so the d2l-body-small color doesn't override it */
|
|
892
|
-
.d2l-tooltip-content.d2l-tooltip-content {
|
|
893
|
-
color: inherit;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
:host([_open-dir="top"]) .d2l-tooltip-content {
|
|
897
|
-
bottom: 100%;
|
|
898
|
-
}
|
|
899
|
-
:host([_open-dir="left"]) .d2l-tooltip-content {
|
|
900
|
-
right: 0;
|
|
901
|
-
}
|
|
902
|
-
:host([_open-dir="right"][dir="rtl"]) .d2l-tooltip-content {
|
|
903
|
-
left: 0;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
.d2l-tooltip-container {
|
|
907
|
-
height: 100%;
|
|
908
|
-
width: 100%;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
:host([_open-dir="bottom"][showing]) .d2l-tooltip-container {
|
|
912
|
-
-webkit-animation: d2l-tooltip-bottom-animation 200ms ease;
|
|
913
|
-
animation: d2l-tooltip-bottom-animation 200ms ease;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
:host([_open-dir="top"][showing]) .d2l-tooltip-container {
|
|
917
|
-
-webkit-animation: d2l-tooltip-top-animation 200ms ease;
|
|
918
|
-
animation: d2l-tooltip-top-animation 200ms ease;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
:host([_open-dir="left"][showing]) .d2l-tooltip-container {
|
|
922
|
-
-webkit-animation: d2l-tooltip-left-animation 200ms ease;
|
|
923
|
-
animation: d2l-tooltip-left-animation 200ms ease;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
:host([_open-dir="right"][showing]) .d2l-tooltip-container {
|
|
927
|
-
-webkit-animation: d2l-tooltip-right-animation 200ms ease;
|
|
928
|
-
animation: d2l-tooltip-right-animation 200ms ease;
|
|
929
|
-
}
|
|
422
|
+
#handleTargetResize() {
|
|
423
|
+
this.#resizeRunSinceTruncationCheck = true;
|
|
424
|
+
if (!this.showing) return;
|
|
425
|
+
super.position();
|
|
426
|
+
}
|
|
930
427
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
428
|
+
#handleTargetTouchEnd() {
|
|
429
|
+
clearTimeout(this.#longPressTimeout);
|
|
430
|
+
}
|
|
935
431
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
432
|
+
#handleTargetTouchStart() {
|
|
433
|
+
this.#longPressTimeout = setTimeout(() => {
|
|
434
|
+
this.#target.focus();
|
|
435
|
+
}, 500);
|
|
436
|
+
}
|
|
941
437
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
-webkit-animation: none;
|
|
948
|
-
animation: none;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
438
|
+
#handleTooltipMouseEnter() {
|
|
439
|
+
if (!this.showing) return;
|
|
440
|
+
this.#isHoveringTooltip = true;
|
|
441
|
+
this.#updateShowing();
|
|
442
|
+
}
|
|
951
443
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
100% { opacity: 1; transform: translate(0, 0); }
|
|
955
|
-
}
|
|
956
|
-
@keyframes d2l-tooltip-bottom-animation {
|
|
957
|
-
0% { opacity: 0; transform: translate(0, 10px); }
|
|
958
|
-
100% { opacity: 1; transform: translate(0, 0); }
|
|
959
|
-
}
|
|
960
|
-
@keyframes d2l-tooltip-left-animation {
|
|
961
|
-
0% { opacity: 0; transform: translate(-10px, 0); }
|
|
962
|
-
100% { opacity: 1; transform: translate(0, 0); }
|
|
963
|
-
}
|
|
964
|
-
@keyframes d2l-tooltip-right-animation {
|
|
965
|
-
0% { opacity: 0; transform: translate(10px, 0); }
|
|
966
|
-
100% { opacity: 1; transform: translate(0, 0); }
|
|
967
|
-
}
|
|
444
|
+
#handleTooltipMouseLeave() {
|
|
445
|
+
clearTimeout(this.#mouseLeaveTimeout);
|
|
968
446
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
padding-top: ${12 - contentBorderSize}px;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
`];
|
|
976
|
-
}
|
|
447
|
+
this.#isHoveringTooltip = false;
|
|
448
|
+
this.#mouseLeftTooltip = true;
|
|
449
|
+
resetDelayTimeout();
|
|
977
450
|
|
|
978
|
-
|
|
979
|
-
super();
|
|
980
|
-
|
|
981
|
-
this._onTargetBlur = this._onTargetBlur.bind(this);
|
|
982
|
-
this._onTargetFocus = this._onTargetFocus.bind(this);
|
|
983
|
-
this._onTargetMouseEnter = this._onTargetMouseEnter.bind(this);
|
|
984
|
-
this._onTargetMouseLeave = this._onTargetMouseLeave.bind(this);
|
|
985
|
-
this._onTargetResize = this._onTargetResize.bind(this);
|
|
986
|
-
this._onTargetMutation = this._onTargetMutation.bind(this);
|
|
987
|
-
this._onTargetClick = this._onTargetClick.bind(this);
|
|
988
|
-
this._onTargetTouchStart = this._onTargetTouchStart.bind(this);
|
|
989
|
-
this._onTargetTouchEnd = this._onTargetTouchEnd.bind(this);
|
|
990
|
-
|
|
991
|
-
this.announced = false;
|
|
992
|
-
this.closeOnClick = false;
|
|
993
|
-
this.delay = 300;
|
|
994
|
-
this.disableFocusLock = false;
|
|
995
|
-
this.forceShow = false;
|
|
996
|
-
this.forType = 'descriptor';
|
|
997
|
-
this.offset = pointerRotatedOverhang + pointerGap;
|
|
998
|
-
this.showTruncatedOnly = false;
|
|
999
|
-
this.state = 'info';
|
|
1000
|
-
|
|
1001
|
-
this._dismissibleId = null;
|
|
1002
|
-
this._isFocusing = false;
|
|
1003
|
-
this._isHovering = false;
|
|
1004
|
-
this._resizeRunSinceTruncationCheck = false;
|
|
1005
|
-
this._viewportMargin = defaultViewportMargin;
|
|
1006
|
-
|
|
1007
|
-
this.#isHoveringTooltip = false;
|
|
451
|
+
this.#mouseLeaveTimeout = setTimeout(() => {
|
|
1008
452
|
this.#mouseLeftTooltip = false;
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
get showing() {
|
|
1013
|
-
return this._showing;
|
|
1014
|
-
}
|
|
1015
|
-
set showing(val) {
|
|
1016
|
-
const oldVal = this._showing;
|
|
1017
|
-
if (oldVal !== val) {
|
|
1018
|
-
this._showing = val;
|
|
1019
|
-
this.requestUpdate('showing', oldVal);
|
|
1020
|
-
this._showingChanged(val, oldVal !== undefined); // don't dispatch hide event when initializing
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
connectedCallback() {
|
|
1025
|
-
super.connectedCallback();
|
|
1026
|
-
this.showing = false;
|
|
1027
|
-
window.addEventListener('resize', this._onTargetResize);
|
|
1028
|
-
|
|
1029
|
-
requestAnimationFrame(() => {
|
|
1030
|
-
if (this.isConnected) {
|
|
1031
|
-
this._updateTarget();
|
|
1032
|
-
}
|
|
1033
|
-
});
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
disconnectedCallback() {
|
|
1037
|
-
super.disconnectedCallback();
|
|
1038
|
-
if (activeTooltip === this) activeTooltip = null;
|
|
1039
|
-
this._removeListeners();
|
|
1040
|
-
window.removeEventListener('resize', this._onTargetResize);
|
|
1041
|
-
clearDismissible(this._dismissibleId);
|
|
1042
|
-
delayTimeoutId = null;
|
|
1043
|
-
this._dismissibleId = null;
|
|
1044
|
-
if (this._target) {
|
|
1045
|
-
elemIdListRemove(this._target, 'aria-labelledby', this.id);
|
|
1046
|
-
elemIdListRemove(this._target, 'aria-describedby', this.id);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
async getUpdateComplete() {
|
|
1051
|
-
const fontsPromise = document.fonts ? document.fonts.ready : Promise.resolve();
|
|
1052
|
-
const ret = await super.getUpdateComplete();
|
|
1053
|
-
/* wait for the fonts to load because browsers have a font block period
|
|
1054
|
-
where they will render an invisible fallback font face that may result in
|
|
1055
|
-
improper width calculations before the real font is loaded */
|
|
1056
|
-
await fontsPromise;
|
|
1057
|
-
return ret;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
render() {
|
|
1061
|
-
const tooltipPositionStyle = {
|
|
1062
|
-
maxWidth: this._maxWidth ? `${this._maxWidth}px` : null
|
|
1063
|
-
};
|
|
1064
|
-
if (this._tooltipShift !== undefined) {
|
|
1065
|
-
if (this._isAboveOrBelow()) {
|
|
1066
|
-
const isRtl = this.getAttribute('dir') === 'rtl';
|
|
1067
|
-
tooltipPositionStyle.left = !isRtl ? `${this._tooltipShift}px` : null;
|
|
1068
|
-
tooltipPositionStyle.right = !isRtl ? null : `${this._tooltipShift}px`;
|
|
1069
|
-
} else {
|
|
1070
|
-
tooltipPositionStyle.top = `${this._tooltipShift}px`;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
const contentClasses = {
|
|
1075
|
-
'd2l-tooltip-content': true,
|
|
1076
|
-
'd2l-body-small': true,
|
|
1077
|
-
'vdiff-target': this.showing
|
|
1078
|
-
};
|
|
1079
|
-
|
|
1080
|
-
// Note: role="text" is a workaround for Safari. Otherwise, list-item content is not announced with VoiceOver
|
|
1081
|
-
return html`
|
|
1082
|
-
<div class="d2l-tooltip-container">
|
|
1083
|
-
<div class="d2l-tooltip-position" style=${styleMap(tooltipPositionStyle)}>
|
|
1084
|
-
<div class="${classMap(contentClasses)}" @mouseenter="${this.#onTooltipMouseEnter}" @mouseleave="${this.#onTooltipMouseLeave}">
|
|
1085
|
-
<div role="text">
|
|
1086
|
-
<slot></slot>
|
|
1087
|
-
</div>
|
|
1088
|
-
</div>
|
|
1089
|
-
</div>
|
|
1090
|
-
<div class="d2l-tooltip-pointer d2l-tooltip-pointer-outline">
|
|
1091
|
-
<div></div>
|
|
1092
|
-
</div>
|
|
1093
|
-
<div class="d2l-tooltip-pointer" @mouseenter="${this.#onTooltipMouseEnter}" @mouseleave="${this.#onTooltipMouseLeave}">
|
|
1094
|
-
<div></div>
|
|
1095
|
-
</div>
|
|
1096
|
-
</div>`
|
|
1097
|
-
;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
willUpdate(changedProperties) {
|
|
1101
|
-
super.willUpdate(changedProperties);
|
|
1102
|
-
|
|
1103
|
-
if (changedProperties.has('for')) {
|
|
1104
|
-
this._updateTarget();
|
|
1105
|
-
}
|
|
1106
|
-
if (changedProperties.has('forceShow')) {
|
|
1107
|
-
this._updateShowing();
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
hide() {
|
|
1112
|
-
this._isHovering = false;
|
|
1113
|
-
this._isFocusing = false;
|
|
1114
|
-
this._updateShowing();
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
show() {
|
|
1118
|
-
this.showing = true;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
async updatePosition() {
|
|
1122
|
-
|
|
1123
|
-
if (!this._target) {
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
453
|
+
this.#updateShowing();
|
|
454
|
+
}, 100); // delay to allow for mouseenter to fire if hovering on target
|
|
455
|
+
}
|
|
1126
456
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
const spaceAround = this._computeSpaceAround(offsetParent, targetRect);
|
|
1130
|
-
|
|
1131
|
-
// Compute the size of the spaces above, below, left and right and find which space to fit the tooltip in
|
|
1132
|
-
const content = this._getContent();
|
|
1133
|
-
if (content === null) return;
|
|
1134
|
-
|
|
1135
|
-
const spaces = this._computeAvailableSpaces(targetRect, spaceAround);
|
|
1136
|
-
const space = await this._fitContentToSpace(content, spaces);
|
|
1137
|
-
|
|
1138
|
-
const contentRect = content.getBoundingClientRect();
|
|
1139
|
-
// + 1 because scrollWidth does not give sub-pixel measurements and half a pixel may cause text to unexpectedly wrap
|
|
1140
|
-
this._maxWidth = Math.min(content.scrollWidth + 2 * contentBorderSize, 350) + 1;
|
|
1141
|
-
this._openDir = space.dir;
|
|
1142
|
-
|
|
1143
|
-
// Compute the x and y position of the tooltip relative to its target
|
|
1144
|
-
let offsetTop, offsetLeft;
|
|
1145
|
-
if (offsetParent && offsetParent.tagName !== 'BODY') {
|
|
1146
|
-
const offsetRect = offsetParent.getBoundingClientRect();
|
|
1147
|
-
offsetTop = offsetRect.top + offsetParent.clientTop - offsetParent.scrollTop;
|
|
1148
|
-
offsetLeft = offsetRect.left + offsetParent.clientLeft - offsetParent.scrollLeft;
|
|
1149
|
-
} else {
|
|
1150
|
-
offsetTop = -document.documentElement.scrollTop;
|
|
1151
|
-
offsetLeft = -document.documentElement.scrollLeft;
|
|
1152
|
-
}
|
|
1153
|
-
const top = targetRect.top - offsetTop;
|
|
1154
|
-
const left = targetRect.left - offsetLeft;
|
|
1155
|
-
|
|
1156
|
-
let positionRect;
|
|
1157
|
-
if (this._isAboveOrBelow()) {
|
|
1158
|
-
positionRect = {
|
|
1159
|
-
left,
|
|
1160
|
-
top: this._openDir === 'top' ? top - this.offset : top + targetRect.height + this.offset,
|
|
1161
|
-
width: targetRect.width,
|
|
1162
|
-
height: 0,
|
|
1163
|
-
};
|
|
1164
|
-
} else {
|
|
1165
|
-
positionRect = {
|
|
1166
|
-
left: this._openDir === 'left' ? left - this.offset : left + targetRect.width + this.offset,
|
|
1167
|
-
top,
|
|
1168
|
-
height: targetRect.height,
|
|
1169
|
-
width: 0,
|
|
1170
|
-
};
|
|
1171
|
-
}
|
|
457
|
+
#removeListeners() {
|
|
458
|
+
this.removeEventListener('d2l-popover-close', this.hide);
|
|
1172
459
|
|
|
1173
|
-
|
|
1174
|
-
if (this._isAboveOrBelow() && (this.align === 'start' || this.align === 'end')) {
|
|
1175
|
-
const shift = Math.min((targetRect.width / 2) - (contentHorizontalPadding + pointerRotatedLength / 2), 0);
|
|
1176
|
-
if (this.align === 'start') {
|
|
1177
|
-
this._tooltipShift = shift;
|
|
1178
|
-
} else {
|
|
1179
|
-
this._tooltipShift = targetRect.width - this._maxWidth - shift;
|
|
1180
|
-
}
|
|
1181
|
-
} else {
|
|
1182
|
-
let spaceLeft, spaceRight, centerDelta, maxShift, minShift;
|
|
1183
|
-
if (this._isAboveOrBelow()) {
|
|
1184
|
-
const isRtl = this.getAttribute('dir') === 'rtl';
|
|
1185
|
-
spaceLeft = !isRtl ? spaceAround.left : spaceAround.right;
|
|
1186
|
-
spaceRight = !isRtl ? spaceAround.right : spaceAround.left;
|
|
1187
|
-
centerDelta = this._maxWidth - targetRect.width;
|
|
1188
|
-
maxShift = targetRect.width / 2;
|
|
1189
|
-
minShift = maxShift - this._maxWidth;
|
|
1190
|
-
} else {
|
|
1191
|
-
spaceLeft = spaceAround.above;
|
|
1192
|
-
spaceRight = spaceAround.below;
|
|
1193
|
-
centerDelta = contentRect.height - targetRect.height;
|
|
1194
|
-
maxShift = targetRect.height / 2;
|
|
1195
|
-
minShift = maxShift - contentRect.height;
|
|
1196
|
-
}
|
|
1197
|
-
const shift = computeTooltipShift(centerDelta, spaceLeft, spaceRight);
|
|
1198
|
-
const shiftMargin = (pointerRotatedLength / 2) + contentBorderRadius;
|
|
1199
|
-
this._tooltipShift = Math.min(Math.max(shift, minShift + shiftMargin), maxShift - shiftMargin);
|
|
1200
|
-
}
|
|
1201
|
-
this.style.left = `${positionRect.left}px`;
|
|
1202
|
-
this.style.top = `${positionRect.top}px`;
|
|
1203
|
-
this.style.width = `${positionRect.width}px`;
|
|
1204
|
-
this.style.height = `${positionRect.height}px`;
|
|
1205
|
-
}
|
|
460
|
+
if (!this.#target) return;
|
|
1206
461
|
|
|
1207
|
-
|
|
1208
|
-
|
|
462
|
+
this.#target.removeEventListener('mouseenter', this.#handleTargetMouseEnterBound);
|
|
463
|
+
this.#target.removeEventListener('mouseleave', this.#handleTargetMouseLeaveBound);
|
|
464
|
+
this.#target.removeEventListener('focus', this.#handleTargetFocusBound);
|
|
465
|
+
this.#target.removeEventListener('blur', this.#handleTargetBlurBound);
|
|
466
|
+
this.#target.removeEventListener('click', this.#handleTargetClickBound);
|
|
467
|
+
this.#target.removeEventListener('touchstart', this.#handleTargetTouchStartBound);
|
|
468
|
+
this.#target.removeEventListener('touchcancel', this.#handleTargetTouchEndBound);
|
|
469
|
+
this.#target.removeEventListener('touchend', this.#handleTargetTouchEndBound);
|
|
1209
470
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
}
|
|
1214
|
-
this._target.addEventListener('mouseenter', this._onTargetMouseEnter);
|
|
1215
|
-
this._target.addEventListener('mouseleave', this._onTargetMouseLeave);
|
|
1216
|
-
this._target.addEventListener('focus', this._onTargetFocus);
|
|
1217
|
-
this._target.addEventListener('blur', this._onTargetBlur);
|
|
1218
|
-
this._target.addEventListener('click', this._onTargetClick);
|
|
1219
|
-
this._target.addEventListener('touchstart', this._onTargetTouchStart, { passive: true });
|
|
1220
|
-
this._target.addEventListener('touchcancel', this._onTargetTouchEnd);
|
|
1221
|
-
this._target.addEventListener('touchend', this._onTargetTouchEnd);
|
|
1222
|
-
|
|
1223
|
-
this._targetSizeObserver = new ResizeObserver(this._onTargetResize);
|
|
1224
|
-
this._targetSizeObserver.observe(this._target);
|
|
1225
|
-
|
|
1226
|
-
this._targetMutationObserver = new MutationObserver(this._onTargetMutation);
|
|
1227
|
-
this._targetMutationObserver.observe(this._target, { attributes: true, attributeFilter: ['id'] });
|
|
1228
|
-
this._targetMutationObserver.observe(this._target.parentNode, { childList: true });
|
|
471
|
+
if (this.#targetSizeObserver) {
|
|
472
|
+
this.#targetSizeObserver.disconnect();
|
|
473
|
+
this.#targetSizeObserver = null;
|
|
1229
474
|
}
|
|
1230
475
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
const spaces = [
|
|
1235
|
-
{ dir: 'bottom', width: verticalWidth, height: Math.max(spaceAround.below - this.offset, 0) },
|
|
1236
|
-
{ dir: 'top', width: verticalWidth, height: Math.max(spaceAround.above - this.offset, 0) },
|
|
1237
|
-
{ dir: 'right', width: Math.max(spaceAround.right - this.offset, 0), height: horizontalHeight },
|
|
1238
|
-
{ dir: 'left', width: Math.max(spaceAround.left - this.offset, 0), height: horizontalHeight }
|
|
1239
|
-
];
|
|
1240
|
-
if (this.getAttribute('dir') === 'rtl') {
|
|
1241
|
-
const tmp = spaces[2];
|
|
1242
|
-
spaces[2] = spaces[3];
|
|
1243
|
-
spaces[3] = tmp;
|
|
1244
|
-
}
|
|
1245
|
-
return spaces;
|
|
476
|
+
if (this.#targetMutationObserver) {
|
|
477
|
+
this.#targetMutationObserver.disconnect();
|
|
478
|
+
this.#targetMutationObserver = null;
|
|
1246
479
|
}
|
|
480
|
+
}
|
|
1247
481
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
const bounded = (boundingContainer !== document.documentElement);
|
|
1252
|
-
const boundingContainerRect = boundingContainer.getBoundingClientRect();
|
|
1253
|
-
|
|
1254
|
-
const spaceAround = (bounded ? {
|
|
1255
|
-
above: targetRect.top - boundingContainerRect.top - this._viewportMargin,
|
|
1256
|
-
below: boundingContainerRect.bottom - targetRect.bottom - this._viewportMargin,
|
|
1257
|
-
left: targetRect.left - boundingContainerRect.left - this._viewportMargin,
|
|
1258
|
-
right: boundingContainerRect.right - targetRect.right - this._viewportMargin
|
|
1259
|
-
} : {
|
|
1260
|
-
above: targetRect.top - this._viewportMargin,
|
|
1261
|
-
below: window.innerHeight - targetRect.bottom - this._viewportMargin,
|
|
1262
|
-
left: targetRect.left - this._viewportMargin,
|
|
1263
|
-
right: document.documentElement.clientWidth - targetRect.right - this._viewportMargin
|
|
1264
|
-
});
|
|
1265
|
-
|
|
1266
|
-
if (this.boundary && offsetParent) {
|
|
1267
|
-
const offsetRect = offsetParent.getBoundingClientRect();
|
|
1268
|
-
if (!isNaN(this.boundary.left)) {
|
|
1269
|
-
spaceAround.left = Math.min(targetRect.left - offsetRect.left - this.boundary.left, spaceAround.left);
|
|
1270
|
-
}
|
|
1271
|
-
if (!isNaN(this.boundary.right)) {
|
|
1272
|
-
spaceAround.right = Math.min(offsetRect.right - targetRect.right - this.boundary.right, spaceAround.right);
|
|
1273
|
-
}
|
|
1274
|
-
if (!isNaN(this.boundary.top)) {
|
|
1275
|
-
spaceAround.above = Math.min(targetRect.top - offsetRect.top - this.boundary.top, spaceAround.above);
|
|
1276
|
-
}
|
|
1277
|
-
if (!isNaN(this.boundary.bottom)) {
|
|
1278
|
-
spaceAround.below = Math.min(offsetRect.bottom - targetRect.bottom - this.boundary.bottom, spaceAround.below);
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
const isRTL = this.getAttribute('dir') === 'rtl';
|
|
1282
|
-
if ((this.align === 'start' && !isRTL) || (this.align === 'end' && isRTL)) {
|
|
1283
|
-
spaceAround.left = 0;
|
|
1284
|
-
} else if ((this.align === 'start' && isRTL) || (this.align === 'end' && !isRTL)) {
|
|
1285
|
-
spaceAround.right = 0;
|
|
1286
|
-
}
|
|
1287
|
-
return spaceAround;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
_findTarget() {
|
|
1291
|
-
const ownerRoot = this.getRootNode();
|
|
1292
|
-
|
|
1293
|
-
let target;
|
|
1294
|
-
if (this.for) {
|
|
1295
|
-
const targetSelector = `#${cssEscape(this.for)}`;
|
|
1296
|
-
target = ownerRoot.querySelector(targetSelector);
|
|
1297
|
-
target = (target || ownerRoot?.host?.querySelector(targetSelector)) ?? null;
|
|
1298
|
-
} else {
|
|
1299
|
-
const parentNode = this.parentNode;
|
|
1300
|
-
target = parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE ? ownerRoot.host : parentNode;
|
|
1301
|
-
|
|
1302
|
-
// reduce console pollution since Safari + VO prevents inadequate SR experience for tooltips during form validation when using 'for'
|
|
1303
|
-
if (!(target.tagName === 'D2L-INPUT-TEXT' && target?.invalid)) {
|
|
1304
|
-
console.warn('<d2l-tooltip>: missing required attribute "for"');
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
return target;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
async _fitByBestFit(content, spaces) {
|
|
1311
|
-
for (let i = 0; i < spaces.length; ++i) {
|
|
1312
|
-
const space = spaces[i];
|
|
1313
|
-
this._maxWidth = space.width;
|
|
1314
|
-
await this.updateComplete;
|
|
1315
|
-
|
|
1316
|
-
if (content.scrollWidth + 2 * contentBorderSize <= Math.ceil(space.width) && content.scrollHeight + 2 * contentBorderSize <= Math.ceil(space.height)) {
|
|
1317
|
-
return space;
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
return undefined;
|
|
1321
|
-
}
|
|
482
|
+
async #showingChanged(newValue, dispatch) {
|
|
483
|
+
clearTimeout(this.#hoverTimeout);
|
|
484
|
+
clearTimeout(this.#longPressTimeout);
|
|
1322
485
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
if (space.width * space.height > largest.width * largest.height) {
|
|
1328
|
-
largest = space;
|
|
1329
|
-
}
|
|
486
|
+
if (newValue) {
|
|
487
|
+
if (!this.forceShow) {
|
|
488
|
+
if (activeTooltip) activeTooltip.hide();
|
|
489
|
+
activeTooltip = this;
|
|
1330
490
|
}
|
|
1331
|
-
this._maxWidth = largest.width;
|
|
1332
|
-
await this.updateComplete;
|
|
1333
|
-
return largest;
|
|
1334
|
-
}
|
|
1335
491
|
|
|
1336
|
-
|
|
1337
|
-
const space = spaces.filter(space => space.dir === this.position)[0];
|
|
1338
|
-
if (!space) {
|
|
1339
|
-
return undefined;
|
|
1340
|
-
}
|
|
1341
|
-
this._maxWidth = space.width;
|
|
492
|
+
this.setAttribute('aria-hidden', 'false');
|
|
1342
493
|
await this.updateComplete;
|
|
1343
|
-
return space;
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
async _fitContentToSpace(content, spaces) {
|
|
1347
|
-
// Legacy manual positioning based on the position attribute to allow for backwards compatibility
|
|
1348
|
-
let space = await this._fitByManualPosition(spaces);
|
|
1349
|
-
if (space) {
|
|
1350
|
-
return space;
|
|
1351
|
-
}
|
|
1352
|
-
space = await this._fitByBestFit(content, spaces);
|
|
1353
|
-
if (space) {
|
|
1354
|
-
return space;
|
|
1355
|
-
}
|
|
1356
|
-
return this._fitByLargestSpace(spaces);
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
_getContent() {
|
|
1360
|
-
return this.shadowRoot && this.shadowRoot.querySelector('.d2l-tooltip-content');
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// for testing only!
|
|
1364
|
-
_getTarget() {
|
|
1365
|
-
return this._target;
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
_isAboveOrBelow() {
|
|
1369
|
-
return this._openDir === 'bottom' || this._openDir === 'top';
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
_isInteractive(ele) {
|
|
1373
|
-
if (!isFocusable(ele, true, false, true)) {
|
|
1374
|
-
return false;
|
|
1375
|
-
}
|
|
1376
|
-
if (ele.nodeType !== Node.ELEMENT_NODE) {
|
|
1377
|
-
return false;
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
return isInteractive(ele, tooltipInteractiveElements, tooltipInteractiveRoles);
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
_onTargetBlur() {
|
|
1384
|
-
this._isFocusing = false;
|
|
1385
|
-
this._updateShowing();
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
_onTargetClick() {
|
|
1389
|
-
if (this.closeOnClick) {
|
|
1390
|
-
this.hide();
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
494
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
await this._updateTruncating();
|
|
1397
|
-
if (!this._truncating) return;
|
|
1398
|
-
}
|
|
495
|
+
// wait for popover before dispatching (ex. otherwise visual-diff won't capture complete target area)
|
|
496
|
+
await super.open(this.#target, false);
|
|
1399
497
|
|
|
1400
|
-
if (
|
|
1401
|
-
this.
|
|
1402
|
-
} else {
|
|
1403
|
-
this._isFocusing = true;
|
|
1404
|
-
this._updateShowing();
|
|
498
|
+
if (dispatch) {
|
|
499
|
+
this.dispatchEvent(new CustomEvent('d2l-tooltip-show', { bubbles: true, composed: true }));
|
|
1405
500
|
}
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
_onTargetMouseEnter() {
|
|
1409
|
-
// came from tooltip so keep showing
|
|
1410
|
-
if (this.#mouseLeftTooltip) {
|
|
1411
|
-
this._isHovering = true;
|
|
1412
|
-
return;
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
this._hoverTimeout = setTimeout(async() => {
|
|
1416
|
-
if (this.showTruncatedOnly) {
|
|
1417
|
-
await this._updateTruncating();
|
|
1418
|
-
if (!this._truncating) return;
|
|
1419
|
-
}
|
|
1420
501
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
}
|
|
502
|
+
if (this.announced && !isInteractiveTarget(this.#target)) announce(this.innerText);
|
|
503
|
+
} else {
|
|
504
|
+
if (activeTooltip === this) activeTooltip = null;
|
|
1425
505
|
|
|
1426
|
-
|
|
1427
|
-
clearTimeout(this._hoverTimeout);
|
|
1428
|
-
this._isHovering = false;
|
|
1429
|
-
if (this.showing) resetDelayTimeout();
|
|
1430
|
-
setTimeout(() => this._updateShowing(), 100); // delay to allow for mouseenter to fire if hovering on tooltip
|
|
1431
|
-
}
|
|
506
|
+
this.setAttribute('aria-hidden', 'true');
|
|
1432
507
|
|
|
1433
|
-
|
|
1434
|
-
if (!this._target?.isConnected || (m.target === this._target && m.attributeName === 'id')) {
|
|
1435
|
-
this._targetMutationObserver?.disconnect();
|
|
1436
|
-
this._updateTarget();
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
508
|
+
super.close();
|
|
1439
509
|
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
if (!this.showing) {
|
|
1443
|
-
return;
|
|
510
|
+
if (dispatch) {
|
|
511
|
+
this.dispatchEvent(new CustomEvent('d2l-tooltip-hide', { bubbles: true, composed: true }));
|
|
1444
512
|
}
|
|
1445
|
-
this.updatePosition();
|
|
1446
513
|
}
|
|
514
|
+
}
|
|
1447
515
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
516
|
+
#updateShowing() {
|
|
517
|
+
this.showing = this.#isFocusing || this.#isHovering || this.forceShow || this.#isHoveringTooltip;
|
|
518
|
+
}
|
|
1451
519
|
|
|
1452
|
-
|
|
1453
|
-
this._longPressTimeout = setTimeout(() => {
|
|
1454
|
-
this._target.focus();
|
|
1455
|
-
}, 500);
|
|
1456
|
-
}
|
|
520
|
+
#updateTarget() {
|
|
1457
521
|
|
|
1458
|
-
|
|
1459
|
-
if (!this._target) {
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
this._target.removeEventListener('mouseenter', this._onTargetMouseEnter);
|
|
1463
|
-
this._target.removeEventListener('mouseleave', this._onTargetMouseLeave);
|
|
1464
|
-
this._target.removeEventListener('focus', this._onTargetFocus);
|
|
1465
|
-
this._target.removeEventListener('blur', this._onTargetBlur);
|
|
1466
|
-
this._target.removeEventListener('click', this._onTargetClick);
|
|
1467
|
-
this._target.removeEventListener('touchstart', this._onTargetTouchStart);
|
|
1468
|
-
this._target.removeEventListener('touchcancel', this._onTargetTouchEnd);
|
|
1469
|
-
this._target.removeEventListener('touchend', this._onTargetTouchEnd);
|
|
1470
|
-
|
|
1471
|
-
if (this._targetSizeObserver) {
|
|
1472
|
-
this._targetSizeObserver.disconnect();
|
|
1473
|
-
this._targetSizeObserver = null;
|
|
1474
|
-
}
|
|
522
|
+
if (!this.isConnected) return;
|
|
1475
523
|
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
this._targetMutationObserver = null;
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
524
|
+
const newTarget = this.#findTarget();
|
|
525
|
+
if (this.#target === newTarget) return;
|
|
1481
526
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
clearTimeout(this._longPressTimeout);
|
|
1485
|
-
if (newValue) {
|
|
1486
|
-
if (!this.forceShow) {
|
|
1487
|
-
if (activeTooltip) activeTooltip.hide();
|
|
1488
|
-
activeTooltip = this;
|
|
1489
|
-
}
|
|
527
|
+
this.#removeListeners();
|
|
528
|
+
this.#target = newTarget;
|
|
1490
529
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
await this.updateComplete;
|
|
1494
|
-
await this.updatePosition();
|
|
1495
|
-
if (dispatch) {
|
|
1496
|
-
this.dispatchEvent(new CustomEvent(
|
|
1497
|
-
'd2l-tooltip-show', { bubbles: true, composed: true }
|
|
1498
|
-
));
|
|
1499
|
-
}
|
|
530
|
+
if (this.#target) {
|
|
531
|
+
const targetDisabled = this.#target.hasAttribute('disabled') || this.#target.getAttribute('aria-disabled') === 'true';
|
|
1500
532
|
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
533
|
+
const isTargetInteractive = isInteractiveTarget(this.#target);
|
|
534
|
+
this.id = this.id || getUniqueId();
|
|
535
|
+
this.setAttribute('role', 'tooltip');
|
|
1504
536
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
}
|
|
1510
|
-
if (dispatch) {
|
|
1511
|
-
this.dispatchEvent(new CustomEvent(
|
|
1512
|
-
'd2l-tooltip-hide', { bubbles: true, composed: true }
|
|
1513
|
-
));
|
|
1514
|
-
}
|
|
537
|
+
if (this.forType === 'label') {
|
|
538
|
+
elemIdListAdd(this.#target, 'aria-labelledby', this.id);
|
|
539
|
+
} else if (!this.announced || isTargetInteractive) {
|
|
540
|
+
elemIdListAdd(this.#target, 'aria-describedby', this.id);
|
|
1515
541
|
}
|
|
1516
|
-
}
|
|
1517
542
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
return;
|
|
543
|
+
if (logAccessibilityWarning && !isTargetInteractive && !this.announced) {
|
|
544
|
+
console.warn(
|
|
545
|
+
'd2l-tooltip may be being used in a non-accessible manner; it should be attached to interactive elements like \'a\', \'button\',' +
|
|
546
|
+
'\'input\'', '\'select\', \'textarea\' or static / custom elements if a role has been set and the element is focusable.',
|
|
547
|
+
this.#target
|
|
548
|
+
);
|
|
549
|
+
logAccessibilityWarning = false;
|
|
1526
550
|
}
|
|
1527
551
|
|
|
1528
|
-
this.
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
if (this
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
const isTargetInteractive = isInteractiveTarget(this._target);
|
|
1535
|
-
this.id = this.id || getUniqueId();
|
|
1536
|
-
this.setAttribute('role', 'tooltip');
|
|
1537
|
-
if (this.forType === 'label') {
|
|
1538
|
-
elemIdListAdd(this._target, 'aria-labelledby', this.id);
|
|
1539
|
-
} else if (!this.announced || isTargetInteractive) {
|
|
1540
|
-
elemIdListAdd(this._target, 'aria-describedby', this.id);
|
|
1541
|
-
}
|
|
1542
|
-
if (logAccessibilityWarning && !isTargetInteractive && !this.announced) {
|
|
1543
|
-
console.warn(
|
|
1544
|
-
'd2l-tooltip may be being used in a non-accessible manner; it should be attached to interactive elements like \'a\', \'button\',' +
|
|
1545
|
-
'\'input\'', '\'select\', \'textarea\' or static / custom elements if a role has been set and the element is focusable.',
|
|
1546
|
-
this._target
|
|
1547
|
-
);
|
|
1548
|
-
logAccessibilityWarning = false;
|
|
1549
|
-
}
|
|
1550
|
-
if (this.showing) {
|
|
1551
|
-
this.updatePosition();
|
|
1552
|
-
} else if (!targetDisabled && isComposedAncestor(this._target, getComposedActiveElement())) {
|
|
1553
|
-
this._onTargetFocus();
|
|
1554
|
-
}
|
|
552
|
+
if (this.showing) {
|
|
553
|
+
if (!super._opened) super.open(this.#target, false);
|
|
554
|
+
else super.position();
|
|
555
|
+
} else if (!targetDisabled && isComposedAncestor(this.#target, getComposedActiveElement())) {
|
|
556
|
+
this.#handleTargetFocusBound();
|
|
1555
557
|
}
|
|
1556
|
-
this._addListeners();
|
|
1557
558
|
}
|
|
559
|
+
this.#addListeners();
|
|
560
|
+
}
|
|
1558
561
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
this._truncating = (clone.scrollWidth - target.offsetWidth) > 2; // Safari adds 1px to scrollWidth necessitating a subtraction comparison.
|
|
1599
|
-
this._resizeRunSinceTruncationCheck = false;
|
|
1600
|
-
target.removeChild(cloneContainer);
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
#onTooltipMouseEnter() {
|
|
1604
|
-
if (!this.showing) return;
|
|
1605
|
-
this.#isHoveringTooltip = true;
|
|
1606
|
-
this._updateShowing();
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
#onTooltipMouseLeave() {
|
|
1610
|
-
clearTimeout(this._mouseLeaveTimeout);
|
|
1611
|
-
|
|
1612
|
-
this.#isHoveringTooltip = false;
|
|
1613
|
-
this.#mouseLeftTooltip = true;
|
|
1614
|
-
resetDelayTimeout();
|
|
1615
|
-
|
|
1616
|
-
this._mouseLeaveTimeout = setTimeout(() => {
|
|
1617
|
-
this.#mouseLeftTooltip = false;
|
|
1618
|
-
this._updateShowing();
|
|
1619
|
-
}, 100); // delay to allow for mouseenter to fire if hovering on target
|
|
1620
|
-
}
|
|
562
|
+
/**
|
|
563
|
+
* This solution appends a clone of the target to the target in order to retain target styles.
|
|
564
|
+
* A possible consequence of this is unexpected behaviours for web components that have slots.
|
|
565
|
+
* If this becomes an issue, it would also likely be possible to append the clone to document.body
|
|
566
|
+
* and get the expected styles through getComputedStyle.
|
|
567
|
+
*/
|
|
568
|
+
async #updateTruncating() {
|
|
569
|
+
// if no resize has happened since truncation was previously calculated the result will not have changed
|
|
570
|
+
if (!this.#resizeRunSinceTruncationCheck || !this.showTruncatedOnly) return;
|
|
571
|
+
|
|
572
|
+
const target = this.#target;
|
|
573
|
+
const cloneContainer = document.createElement('div');
|
|
574
|
+
cloneContainer.style.position = 'absolute';
|
|
575
|
+
cloneContainer.style.overflow = 'hidden';
|
|
576
|
+
cloneContainer.style.whiteSpace = 'nowrap';
|
|
577
|
+
cloneContainer.style.width = '1px';
|
|
578
|
+
cloneContainer.style.insetInlineStart = '-10000px';
|
|
579
|
+
|
|
580
|
+
const clone = target.cloneNode(true);
|
|
581
|
+
clone.removeAttribute('id');
|
|
582
|
+
clone.style.maxWidth = 'none';
|
|
583
|
+
clone.style.display = 'inline-block';
|
|
584
|
+
|
|
585
|
+
cloneContainer.appendChild(clone);
|
|
586
|
+
target.appendChild(cloneContainer);
|
|
587
|
+
await this.updateComplete;
|
|
588
|
+
|
|
589
|
+
// if the clone is a web component it needs to update to fill in any slots
|
|
590
|
+
const customElem = customElements.get(clone.localName);
|
|
591
|
+
if (customElem !== undefined) {
|
|
592
|
+
clone.requestUpdate();
|
|
593
|
+
await clone.updateComplete;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
this.#isTruncating = (clone.scrollWidth - target.offsetWidth) > 2; // Safari adds 1px to scrollWidth necessitating a subtraction comparison.
|
|
597
|
+
this.#resizeRunSinceTruncationCheck = false;
|
|
598
|
+
target.removeChild(cloneContainer);
|
|
1621
599
|
}
|
|
1622
|
-
customElements.define('d2l-tooltip', Tooltip);
|
|
1623
600
|
|
|
1624
601
|
}
|
|
602
|
+
customElements.define('d2l-tooltip', Tooltip);
|