@apliteni/apliteni-ui 0.34.1 → 0.34.2
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/package.json +1 -1
- package/src/components/tooltip.js +115 -8
package/package.json
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
// wireTooltip(container);
|
|
10
10
|
//
|
|
11
11
|
// A mark carries `data-tip-value`, with `data-tip-label` and `data-tip-detail`;
|
|
12
|
-
// a `[data-tip-anchor]` inside it is where the readout opens.
|
|
12
|
+
// a `[data-tip-anchor]` inside it is where the readout opens. Under a finger
|
|
13
|
+
// there is no hover, so a tap is the switch: see Pointer kinds below.
|
|
13
14
|
// why: docs/specification.md#the-hover-readout
|
|
14
15
|
import { esc } from './index.js';
|
|
15
16
|
|
|
@@ -157,7 +158,7 @@ export function showTooltip(host, mark) {
|
|
|
157
158
|
host.__tipDismissed = null;
|
|
158
159
|
tip.__tipHost = host;
|
|
159
160
|
tip.classList.add('is-open');
|
|
160
|
-
|
|
161
|
+
wireDocument(host.ownerDocument);
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
function releaseMark(host) {
|
|
@@ -177,15 +178,60 @@ export function hideTooltip(host) {
|
|
|
177
178
|
host.__tipDismissed = null;
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
// A tap that closes a readout dismisses that mark the way Escape does, so a
|
|
182
|
+
// chart sampling its own marks does not bring it straight back. Which is true
|
|
183
|
+
// of the tap that lands on the mark and of the tap that lands on the host's
|
|
184
|
+
// ground beside it: both are the reader putting the readout away.
|
|
185
|
+
function dismiss(host) {
|
|
186
|
+
const mark = host.__tipMark;
|
|
187
|
+
close(host);
|
|
188
|
+
host.__tipDismissed = mark;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---- Pointer kinds -------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
// A finger has no hover. It arrives already pressing, so the pointerover under
|
|
194
|
+
// a tap is the tap itself: a readout opened on it would open on the way to
|
|
195
|
+
// whatever the mark does when pressed, and go again the moment the finger
|
|
196
|
+
// lifts. Under a coarse pointer the tap is the switch instead — one tap opens
|
|
197
|
+
// the readout, the next closes it, and a tap anywhere else closes it too.
|
|
198
|
+
|
|
199
|
+
/** Whether the device reports no fine pointer at all: a phone, a tablet. */
|
|
200
|
+
function coarse(doc) {
|
|
201
|
+
try {
|
|
202
|
+
return doc.defaultView?.matchMedia?.('(pointer: coarse)')?.matches === true;
|
|
203
|
+
} catch { return false; }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// A pointer event says which kind of pointer it is; a click and a focus do not,
|
|
207
|
+
// so the last kind seen is remembered for them. A keystroke clears it, because
|
|
208
|
+
// a keyboard on a tablet is a third way in and focus has to open the readout
|
|
209
|
+
// again after a tap. Before any of that, the media query answers.
|
|
210
|
+
//
|
|
211
|
+
// A pen is on the tap's side of that line with the finger. It presses a screen
|
|
212
|
+
// rather than resting over one, the tablets it comes with report `(pointer:
|
|
213
|
+
// coarse)`, and a hover a pen does offer is a few millimetres of one nothing
|
|
214
|
+
// here is placed against — so a stylus that fell to the hover path would get
|
|
215
|
+
// the flash this component exists to remove. A mouse is the fine pointer.
|
|
216
|
+
function touching(doc, e) {
|
|
217
|
+
if (e?.pointerType) doc.__tipTouch = e.pointerType === 'touch' || e.pointerType === 'pen';
|
|
218
|
+
return doc.__tipTouch ?? coarse(doc);
|
|
219
|
+
}
|
|
220
|
+
|
|
180
221
|
// Escape dismisses every readout the kit is showing without the pointer having
|
|
181
222
|
// to move — the reader it covers something for. One rendered open and never
|
|
182
223
|
// shown is a picture, and stays. The dismissed mark is remembered until another
|
|
183
224
|
// mark shows or the pointer leaves, so crossing the gap between marks and coming
|
|
184
225
|
// back does not return the readout to the mark it was dismissed from.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
226
|
+
//
|
|
227
|
+
// The same document is where the kind of pointer in play is tracked, and where
|
|
228
|
+
// a tap that lands outside a readout's host takes that readout down.
|
|
229
|
+
function wireDocument(doc) {
|
|
230
|
+
if (doc.__tipDocWired) return;
|
|
231
|
+
doc.__tipDocWired = true;
|
|
188
232
|
doc.addEventListener('keydown', (e) => {
|
|
233
|
+
doc.__tipTouch = false;
|
|
234
|
+
doc.__tipTapping = false;
|
|
189
235
|
if (e.key !== 'Escape') return;
|
|
190
236
|
doc.querySelectorAll('[data-tip].is-open').forEach((tip) => {
|
|
191
237
|
const host = tip.__tipHost;
|
|
@@ -195,6 +241,38 @@ function wireEscape(doc) {
|
|
|
195
241
|
host.__tipDismissed = mark;
|
|
196
242
|
});
|
|
197
243
|
});
|
|
244
|
+
// A tap lands focus on the mark on its way to the click that decides, so the
|
|
245
|
+
// focus between a pointerdown and its click is the tap's own. Focus that no
|
|
246
|
+
// tap brought is not, and opens the readout under a coarse pointer as it
|
|
247
|
+
// always did — a reader swiping through the marks with a screen reader gets
|
|
248
|
+
// the value and the `aria-describedby` that announces it.
|
|
249
|
+
doc.addEventListener('pointerdown', (e) => { touching(doc, e); doc.__tipTapping = true; }, true);
|
|
250
|
+
doc.addEventListener('pointercancel', () => { doc.__tipTapping = false; }, true);
|
|
251
|
+
// Captured, so a host the tap lands in has already lost every other host's
|
|
252
|
+
// readout by the time it opens its own.
|
|
253
|
+
doc.addEventListener('click', (e) => {
|
|
254
|
+
if (e.__tipDismissal) return;
|
|
255
|
+
doc.__tipTapping = false;
|
|
256
|
+
if (!touching(doc)) return;
|
|
257
|
+
doc.querySelectorAll('[data-tip].is-open').forEach((tip) => {
|
|
258
|
+
const host = tip.__tipHost;
|
|
259
|
+
if (host && !host.contains(e.target)) hideTooltip(host);
|
|
260
|
+
});
|
|
261
|
+
}, true);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// An opening tap is stopped on its way to the mark, and stopping it takes it
|
|
265
|
+
// away from the document as well — where other overlays keep the click listener
|
|
266
|
+
// that dismisses them, so a dropdown panel would stand open behind the readout
|
|
267
|
+
// the tap just opened. The tap is handed to the document itself instead. Its
|
|
268
|
+
// target is the root element, which every dismissal listener hears and no
|
|
269
|
+
// delegated trigger matches, since no trigger's attribute is on the root.
|
|
270
|
+
function dismissOverlays(doc) {
|
|
271
|
+
const view = doc.defaultView;
|
|
272
|
+
if (!view || !doc.documentElement) return;
|
|
273
|
+
const passed = new view.MouseEvent('click', { bubbles: true, cancelable: true });
|
|
274
|
+
passed.__tipDismissal = true;
|
|
275
|
+
doc.documentElement.dispatchEvent(passed);
|
|
198
276
|
}
|
|
199
277
|
|
|
200
278
|
// The readout is placed in px from its host, so the host has to be the box it is
|
|
@@ -208,8 +286,10 @@ function anchorHost(host) {
|
|
|
208
286
|
|
|
209
287
|
/**
|
|
210
288
|
* Wire every `[data-tip-host]` under root: a pointer resting on a mark, or focus
|
|
211
|
-
* landing on one, shows the readout; leaving the marks hides it.
|
|
212
|
-
*
|
|
289
|
+
* landing on one, shows the readout; leaving the marks hides it. Under a coarse
|
|
290
|
+
* pointer nothing rests, so a tap on a mark opens the readout and a second tap —
|
|
291
|
+
* or a tap anywhere else — closes it. A host with no readout of its own is given
|
|
292
|
+
* one here, once, never on hover. Safe to call again.
|
|
213
293
|
*/
|
|
214
294
|
export function wireTooltip(root = document) {
|
|
215
295
|
const hosts = [...root.querySelectorAll('[data-tip-host]')];
|
|
@@ -217,6 +297,8 @@ export function wireTooltip(root = document) {
|
|
|
217
297
|
hosts.forEach((host) => {
|
|
218
298
|
if (host.__tipWired) return;
|
|
219
299
|
host.__tipWired = true;
|
|
300
|
+
const doc = host.ownerDocument;
|
|
301
|
+
wireDocument(doc);
|
|
220
302
|
anchorHost(host);
|
|
221
303
|
if (!tipOf(host)) host.insertAdjacentHTML('beforeend', tooltip());
|
|
222
304
|
const markOf = (t) => {
|
|
@@ -225,16 +307,41 @@ export function wireTooltip(root = document) {
|
|
|
225
307
|
};
|
|
226
308
|
host.addEventListener('pointerover', (e) => {
|
|
227
309
|
anchorHost(host);
|
|
310
|
+
if (touching(doc, e)) return;
|
|
228
311
|
const mark = markOf(e.target);
|
|
229
312
|
if (!mark) close(host);
|
|
230
313
|
else if (mark !== host.__tipMark) showTooltip(host, mark);
|
|
231
314
|
});
|
|
232
|
-
host.addEventListener('pointerleave', () => hideTooltip(host));
|
|
315
|
+
host.addEventListener('pointerleave', (e) => { if (!touching(doc, e)) hideTooltip(host); });
|
|
233
316
|
host.addEventListener('focusin', (e) => {
|
|
234
317
|
anchorHost(host);
|
|
318
|
+
// A tap lands focus on its way to the click that decides, so under a
|
|
319
|
+
// finger the readout waits for that tap rather than opening twice. Focus
|
|
320
|
+
// arriving on its own under the same pointer is a reader reaching the
|
|
321
|
+
// mark another way, and opens it.
|
|
322
|
+
if (touching(doc) && doc.__tipTapping) return;
|
|
235
323
|
const mark = markOf(e.target);
|
|
236
324
|
if (mark) showTooltip(host, mark);
|
|
237
325
|
});
|
|
238
326
|
host.addEventListener('focusout', (e) => { if (!markOf(e.relatedTarget)) hideTooltip(host); });
|
|
327
|
+
// The tap. Captured, so the tap that opens the readout is spent opening it
|
|
328
|
+
// and never reaches the mark's own click — a chart drilling down on a bar
|
|
329
|
+
// does not drill down on the tap that asked what the bar says. The tap that
|
|
330
|
+
// closes the readout is let through, so the drill-down is one tap further.
|
|
331
|
+
// What the opening tap is stopped from reaching, it is handed to: see
|
|
332
|
+
// dismissOverlays(), so an open overlay closes under it as under any tap.
|
|
333
|
+
host.addEventListener('click', (e) => {
|
|
334
|
+
if (!touching(doc)) return;
|
|
335
|
+
anchorHost(host);
|
|
336
|
+
const mark = markOf(e.target);
|
|
337
|
+
if (!mark || mark === host.__tipMark) { dismiss(host); return; }
|
|
338
|
+
e.preventDefault();
|
|
339
|
+
e.stopPropagation();
|
|
340
|
+
dismissOverlays(doc);
|
|
341
|
+
// A tap is deliberate, so it ends a dismissal rather than being refused by
|
|
342
|
+
// one. What a dismissal stops is the readout coming back on its own.
|
|
343
|
+
host.__tipDismissed = null;
|
|
344
|
+
showTooltip(host, mark);
|
|
345
|
+
}, true);
|
|
239
346
|
});
|
|
240
347
|
}
|