@arraypress/extension-kit 1.11.0 → 1.11.1
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 +7 -2
- package/package.json +1 -1
- package/src/styles/chrome-ui.css +3 -0
- package/src/ui/menu.js +18 -1
- package/src/ui/toast.d.ts +3 -0
- package/src/ui/toast.js +21 -1
package/README.md
CHANGED
|
@@ -511,7 +511,10 @@ button (or `focusTo`); an item runs once the menu has closed. `createMenu({id, p
|
|
|
511
511
|
opens it below the button instead. `menuPosition` and `isInside` are the pure geometry. In a
|
|
512
512
|
content script, give it the overlay's shadow root (`createMenu({root})`, a closed one too): the
|
|
513
513
|
keys it handles don't reach the page's own shortcuts, and `destroy()` takes it off the page with
|
|
514
|
-
its window listener. On a right-to-left page it lines up with its button's other edge.
|
|
514
|
+
its window listener. On a right-to-left page it lines up with its button's other edge. The
|
|
515
|
+
button's box is read before the menu opens (opening takes the focus, and a button shown only
|
|
516
|
+
while its row holds the focus has no box after); a button that isn't drawn opens it from its
|
|
517
|
+
nearest drawn ancestor, the row.
|
|
515
518
|
|
|
516
519
|
`moreActionsButton(name, options)` is the 32px ⋮ icon button, labelled "More actions for “name”"
|
|
517
520
|
(`label` for a translation), with `aria-haspopup="menu"`; its icon is the stylesheet's
|
|
@@ -570,7 +573,9 @@ toast.show('Copied'); // no Undo
|
|
|
570
573
|
`#toast` (`role="status"`), bottom left, a manual `popover` so it's in the top layer, over
|
|
571
574
|
anything the page stacks (`:popover-open` while it shows). A new message replaces the last and
|
|
572
575
|
restarts the wait: 10 seconds, as chrome://bookmarks's (`TOAST_MS`), or `duration` /
|
|
573
|
-
`undoDuration`. Undo hides it and runs once. `undoLabel` translates the button.
|
|
576
|
+
`undoDuration`. Undo hides it and runs once. `undoLabel` translates the button. While the keyboard
|
|
577
|
+
focus is on Undo, or the pointer is over the toast, it stays; the wait starts again in full once
|
|
578
|
+
they leave (as cr-toast, which stops its timer on `focusin`).
|
|
574
579
|
|
|
575
580
|
### Site list
|
|
576
581
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arraypress/extension-kit",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.1",
|
|
4
4
|
"description": "Shared pieces for Chrome extensions: Chrome's own page styles and components, a Chrome Sync settings store, the light/dark toolbar glyph, Chrome's on-device model, content-script helpers, About links, and build and test helpers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/styles/chrome-ui.css
CHANGED
|
@@ -1085,7 +1085,10 @@ label.row, .row[actionable] { cursor: pointer; }
|
|
|
1085
1085
|
margin-inline: 8px -4px;
|
|
1086
1086
|
}
|
|
1087
1087
|
|
|
1088
|
+
/* Shown too while one of its buttons has its menu open: the menu is modal and takes the focus and
|
|
1089
|
+
the pointer, and the button it opened from (where the focus goes back) would vanish under it. */
|
|
1088
1090
|
.sp-item:is(:hover, :focus-within) .sp-suffix,
|
|
1091
|
+
.sp-suffix:has([aria-expanded="true"]),
|
|
1089
1092
|
.sp-suffix.always { display: flex; }
|
|
1090
1093
|
|
|
1091
1094
|
/* .sp-notice (ui/notice.js): a card saying what a list leaves out or can't do; a warning's icon in
|
package/src/ui/menu.js
CHANGED
|
@@ -101,6 +101,20 @@ export function isInside(box, x, y) {
|
|
|
101
101
|
return x >= box.left && x <= box.right && y >= box.top && y <= box.bottom;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* The box a menu opens from: the anchor's, or, when the anchor has none (it isn't drawn: a row's
|
|
106
|
+
* buttons shown only while the row is hovered or holds the focus), its nearest ancestor's that has.
|
|
107
|
+
*
|
|
108
|
+
* @param {Element} anchor - The button that opened the menu.
|
|
109
|
+
* @returns {DOMRect | undefined} The box; undefined when nothing up to the root is drawn.
|
|
110
|
+
*/
|
|
111
|
+
function anchorBox(anchor) {
|
|
112
|
+
for (let at = /** @type {Element | null} */ (anchor); at; at = at.parentElement) {
|
|
113
|
+
if (at.getClientRects().length) return at.getBoundingClientRect();
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
104
118
|
/**
|
|
105
119
|
* What {@link createMenu} takes.
|
|
106
120
|
*
|
|
@@ -240,10 +254,13 @@ export function createMenu({ id, placement = 'over', root, doc = rootDocument(ro
|
|
|
240
254
|
anchor.setAttribute('aria-expanded', 'true');
|
|
241
255
|
expanded = anchor;
|
|
242
256
|
}
|
|
257
|
+
// Measured before the menu opens: it takes the focus, and a button shown only while its row
|
|
258
|
+
// holds the focus (`.sp-suffix`) has no box after that, so the menu would open in the corner.
|
|
259
|
+
const from = anchor && anchorBox(anchor);
|
|
243
260
|
menu.showModal();
|
|
244
261
|
const { width, height } = menu.getBoundingClientRect();
|
|
245
262
|
const { left, top } = menuPosition({ width, height }, { width: view.innerWidth, height: view.innerHeight }, {
|
|
246
|
-
anchor:
|
|
263
|
+
anchor: from, x, y, placement, rtl: view.getComputedStyle(menu).direction === 'rtl',
|
|
247
264
|
});
|
|
248
265
|
menu.style.left = `${left}px`;
|
|
249
266
|
menu.style.top = `${top}px`;
|
package/src/ui/toast.d.ts
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Chrome's toast (cr-toast), bottom left, as chrome://bookmarks shows one
|
|
3
3
|
* after a delete: "Deleted “Research” Undo". One per page, made by
|
|
4
4
|
* {@link createToast}; a new message replaces the last and restarts the wait.
|
|
5
|
+
* While the keyboard focus is on Undo, or the pointer is over the toast, it
|
|
6
|
+
* stays, and the wait starts again in full once they leave (cr-toast stops
|
|
7
|
+
* its timer on `focusin` and restarts it on `focusout`).
|
|
5
8
|
*
|
|
6
9
|
* It's a manual `popover`, so it shows in the top layer: over anything the
|
|
7
10
|
* page stacks (a content script's toast over a site's own fixed bars), and
|
package/src/ui/toast.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Chrome's toast (cr-toast), bottom left, as chrome://bookmarks shows one
|
|
3
3
|
* after a delete: "Deleted “Research” Undo". One per page, made by
|
|
4
4
|
* {@link createToast}; a new message replaces the last and restarts the wait.
|
|
5
|
+
* While the keyboard focus is on Undo, or the pointer is over the toast, it
|
|
6
|
+
* stays, and the wait starts again in full once they leave (cr-toast stops
|
|
7
|
+
* its timer on `focusin` and restarts it on `focusout`).
|
|
5
8
|
*
|
|
6
9
|
* It's a manual `popover`, so it shows in the top layer: over anything the
|
|
7
10
|
* page stacks (a content script's toast over a site's own fixed bars), and
|
|
@@ -75,11 +78,27 @@ export function createToast({ undoLabel = 'Undo', duration = TOAST_MS, undoDurat
|
|
|
75
78
|
/** @type {(() => unknown) | null} */
|
|
76
79
|
let undoLast = null;
|
|
77
80
|
|
|
81
|
+
/** How long the toast showing now stays, once nothing holds it. */
|
|
82
|
+
let stay = duration;
|
|
83
|
+
|
|
78
84
|
const hide = () => {
|
|
79
85
|
clearTimeout(timer);
|
|
80
86
|
toast.togglePopover(false);
|
|
81
87
|
undoLast = null;
|
|
82
88
|
};
|
|
89
|
+
/** Whether the keyboard focus is in the toast (on Undo) or the pointer over it: it stays while either is. */
|
|
90
|
+
const held = () => toast.matches(':focus-within, :hover');
|
|
91
|
+
/** The wait starts again, in full, unless the toast is held: as cr-toast's, whose focusout restarts it. */
|
|
92
|
+
const wait = () => {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
if (!held() && toast.matches(':popover-open')) timer = setTimeout(hide, stay);
|
|
95
|
+
};
|
|
96
|
+
// cr-toast stops its timer on focusin and restarts it on focusout, so Undo can't go from under
|
|
97
|
+
// the keyboard; the pointer over it holds it the same way (WCAG 2.2.1, Timing Adjustable).
|
|
98
|
+
toast.addEventListener('focusin', () => clearTimeout(timer));
|
|
99
|
+
toast.addEventListener('pointerenter', () => clearTimeout(timer));
|
|
100
|
+
toast.addEventListener('focusout', () => queueMicrotask(wait));
|
|
101
|
+
toast.addEventListener('pointerleave', wait);
|
|
83
102
|
undoButton.addEventListener('click', () => {
|
|
84
103
|
const undo = undoLast;
|
|
85
104
|
hide();
|
|
@@ -98,7 +117,8 @@ export function createToast({ undoLabel = 'Undo', duration = TOAST_MS, undoDurat
|
|
|
98
117
|
// Hidden and shown again, so it's the newest in the top layer (over a popover since).
|
|
99
118
|
toast.togglePopover(false);
|
|
100
119
|
toast.togglePopover(true);
|
|
101
|
-
|
|
120
|
+
stay = undo ? undoDuration : duration;
|
|
121
|
+
wait();
|
|
102
122
|
},
|
|
103
123
|
hide,
|
|
104
124
|
};
|