@jsenv/navi 0.29.147 → 0.29.148
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/dist/jsenv_navi.js +68 -18
- package/dist/jsenv_navi.js.map +13 -11
- package/docs/popup_open.md +13 -5
- package/docs/testid.md +5 -0
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -31192,7 +31192,9 @@ const createDisplayedEvent = (ancestor, becauseAncestorOpened) => {
|
|
|
31192
31192
|
* 5. The element focused before the container opened
|
|
31193
31193
|
*
|
|
31194
31194
|
* [navi-autofocus="restore"] appears in step 1 only: it never claims focus on
|
|
31195
|
-
* a fresh open, it only gets it back.
|
|
31195
|
+
* a fresh open, it only gets it back. Unless a caller says the arrival is not a
|
|
31196
|
+
* fresh open but the answer to a press aimed at that very control — see
|
|
31197
|
+
* findFocusTarget's `restoreMayClaim`.
|
|
31196
31198
|
*
|
|
31197
31199
|
* A ladder that comes back empty — a container holding nothing focusable yet —
|
|
31198
31200
|
* places no focus, and says so on the container ([navi-autofocus-unplaced]),
|
|
@@ -31340,9 +31342,19 @@ const markAutofocusRestoreOnClose = (
|
|
|
31340
31342
|
* it, so landing there scrolls whatever comes before it out of sight.
|
|
31341
31343
|
* transferFocus turns this on by itself wherever the keyboard is a virtual
|
|
31342
31344
|
* one — see the reasoning there.
|
|
31345
|
+
* @param {boolean} [options.restoreMayClaim]
|
|
31346
|
+
* Lets `navi-autofocus="restore"` claim the focus like a plain `autoFocus`.
|
|
31347
|
+
* Its "never on a fresh open" is about a surface APPEARING — a sheet whose
|
|
31348
|
+
* field must not raise a phone's keyboard by opening. A press aimed at the
|
|
31349
|
+
* very control being handed the focus is not that: it IS the ask, and
|
|
31350
|
+
* refusing it leaves the focus on the button that was pressed. Only
|
|
31351
|
+
* "restore" is freed; "last-resort" still waits its turn.
|
|
31343
31352
|
* @returns {{target: HTMLElement, reason: string}|undefined}
|
|
31344
31353
|
*/
|
|
31345
|
-
const findFocusTarget = (
|
|
31354
|
+
const findFocusTarget = (
|
|
31355
|
+
containerEl,
|
|
31356
|
+
{ skipFirstFocusable, restoreMayClaim } = {},
|
|
31357
|
+
) => {
|
|
31346
31358
|
// Not while there is anything else: what takes the focus only for want of
|
|
31347
31359
|
// anything better ("last-resort") and what only takes it back ("restore").
|
|
31348
31360
|
// Neither is dropped, both are simply tried later — step 3 below for the
|
|
@@ -31359,8 +31371,15 @@ const findFocusTarget = (containerEl, { skipFirstFocusable } = {}) => {
|
|
|
31359
31371
|
const isHiddenFromAssistiveTech = (element) =>
|
|
31360
31372
|
Boolean(element.closest?.(`[aria-hidden="true"]`));
|
|
31361
31373
|
|
|
31362
|
-
const skip = (element) =>
|
|
31363
|
-
|
|
31374
|
+
const skip = (element) => {
|
|
31375
|
+
if (isHiddenFromAssistiveTech(element)) {
|
|
31376
|
+
return true;
|
|
31377
|
+
}
|
|
31378
|
+
if (restoreMayClaim) {
|
|
31379
|
+
return element.getAttribute("navi-autofocus") === "last-resort";
|
|
31380
|
+
}
|
|
31381
|
+
return isRestorableAutofocus(element);
|
|
31382
|
+
};
|
|
31364
31383
|
|
|
31365
31384
|
// Every mark, not just the first: a mark is only worth stopping at if it
|
|
31366
31385
|
// leads somewhere focusable. One inside a screen waiting its turn (an inert
|
|
@@ -44619,6 +44638,7 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
44619
44638
|
* make the row thinner or thicker.
|
|
44620
44639
|
*/
|
|
44621
44640
|
const css$O = /* css */`.navi_control_swap {
|
|
44641
|
+
font-size: var(--navi-control-font-size);
|
|
44622
44642
|
--x-control-swap-size: var(--navi-control-swap-size, calc(var(--navi-control-line-height) + 2 * var(--navi-s) + 2 *
|
|
44623
44643
|
var(--navi-control-border-width)));
|
|
44624
44644
|
--x-control-swap-gap: var(--navi-control-swap-gap, var(--navi-xs));
|
|
@@ -44627,6 +44647,7 @@ const css$O = /* css */`.navi_control_swap {
|
|
|
44627
44647
|
gap: var(--x-control-swap-gap);
|
|
44628
44648
|
|
|
44629
44649
|
& > .navi_control_swap_cap {
|
|
44650
|
+
--button-background-color: var(--navi-surface-color);
|
|
44630
44651
|
aspect-ratio: 1;
|
|
44631
44652
|
flex: none;
|
|
44632
44653
|
justify-content: center;
|
|
@@ -44817,7 +44838,7 @@ const ControlSwap = props => {
|
|
|
44817
44838
|
id: `${slotIdPrefix}_${index}`,
|
|
44818
44839
|
className: "navi_control_swap_slot",
|
|
44819
44840
|
inert: index === activeIndex ? undefined : true,
|
|
44820
|
-
children: side.
|
|
44841
|
+
children: side.control
|
|
44821
44842
|
}, side.name))
|
|
44822
44843
|
})
|
|
44823
44844
|
}), jsx(ControlSwapCap, {
|
|
@@ -44832,7 +44853,8 @@ const ControlSwap = props => {
|
|
|
44832
44853
|
|
|
44833
44854
|
/**
|
|
44834
44855
|
* One of the two controls, and the cap that speaks for it. Declarative: the row
|
|
44835
|
-
* reads these and draws the caps at its ends, the controls between them.
|
|
44856
|
+
* reads these and draws the caps at its ends, the controls between them. The
|
|
44857
|
+
* props below say what the side IS; every other prop describes its cap.
|
|
44836
44858
|
*
|
|
44837
44859
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
44838
44860
|
* name?: string,
|
|
@@ -44841,6 +44863,7 @@ const ControlSwap = props => {
|
|
|
44841
44863
|
* badge?: boolean | import("ignore:preact").ComponentChildren,
|
|
44842
44864
|
* autoFocus?: boolean,
|
|
44843
44865
|
* children?: import("ignore:preact").ComponentChildren,
|
|
44866
|
+
* [key: string]: any,
|
|
44844
44867
|
* }>}
|
|
44845
44868
|
* @param name - How `value`/`signal`/`onChange` name this side. Its position
|
|
44846
44869
|
* ("0" or "1") by default.
|
|
@@ -44857,6 +44880,12 @@ const ControlSwap = props => {
|
|
|
44857
44880
|
* that was pressed, for a control one reads before writing in (and, on a
|
|
44858
44881
|
* phone, for a keyboard that must not rise). Never on mount, whatever the
|
|
44859
44882
|
* setting.
|
|
44883
|
+
*
|
|
44884
|
+
* Anything else — `data-testid`, `variant`, `backgroundColor`, `color`, an
|
|
44885
|
+
* `aria-describedby` — goes to the cap, which is a `<Button>`. It is the one
|
|
44886
|
+
* element of the row an application does not render, so nothing else can name
|
|
44887
|
+
* it or dress it; the control it stands for is a vnode of the caller's own and
|
|
44888
|
+
* takes its props directly.
|
|
44860
44889
|
*/
|
|
44861
44890
|
const ControlSwapSide = () => null;
|
|
44862
44891
|
const ControlSwapCap = ({
|
|
@@ -44869,14 +44898,20 @@ const ControlSwapCap = ({
|
|
|
44869
44898
|
const {
|
|
44870
44899
|
icon,
|
|
44871
44900
|
label,
|
|
44872
|
-
badge
|
|
44873
|
-
|
|
44901
|
+
badge,
|
|
44902
|
+
...capProps
|
|
44903
|
+
} = side.capProps;
|
|
44874
44904
|
return jsxs(Button, {
|
|
44875
|
-
ref: ref,
|
|
44876
|
-
className: "navi_control_swap_cap",
|
|
44877
44905
|
icon: true,
|
|
44878
44906
|
pressEffect: "none",
|
|
44879
44907
|
"aria-label": label,
|
|
44908
|
+
...capProps,
|
|
44909
|
+
ref: ref
|
|
44910
|
+
// After the caller's props, all of them: the class is what the row's own
|
|
44911
|
+
// CSS reaches for, and the rest is the wiring that makes the cap a cap.
|
|
44912
|
+
,
|
|
44913
|
+
|
|
44914
|
+
className: capProps.className ? `navi_control_swap_cap ${capProps.className}` : "navi_control_swap_cap",
|
|
44880
44915
|
"aria-expanded": active,
|
|
44881
44916
|
"aria-controls": slotId,
|
|
44882
44917
|
onClick: onPress,
|
|
@@ -44898,13 +44933,20 @@ const readSides = children => {
|
|
|
44898
44933
|
if (!child || child.type !== ControlSwapSide) {
|
|
44899
44934
|
continue;
|
|
44900
44935
|
}
|
|
44936
|
+
// What is left over once the side's own vocabulary is taken out describes
|
|
44937
|
+
// the cap: the control is a vnode the caller wrote and dresses itself, so
|
|
44938
|
+
// the cap is the only thing in the row left to describe.
|
|
44901
44939
|
const {
|
|
44902
44940
|
name,
|
|
44903
|
-
|
|
44941
|
+
autoFocus,
|
|
44942
|
+
children: control,
|
|
44943
|
+
...capProps
|
|
44904
44944
|
} = child.props;
|
|
44905
44945
|
sides.push({
|
|
44906
44946
|
name: name === undefined ? String(sides.length) : name,
|
|
44907
|
-
|
|
44947
|
+
autoFocus,
|
|
44948
|
+
control,
|
|
44949
|
+
capProps
|
|
44908
44950
|
});
|
|
44909
44951
|
}
|
|
44910
44952
|
return sides;
|
|
@@ -44916,14 +44958,22 @@ const readSides = children => {
|
|
|
44916
44958
|
//
|
|
44917
44959
|
// WHERE inside is navi's own ladder (findFocusTarget): an `autoFocus` in the
|
|
44918
44960
|
// control's own content first, the first focusable otherwise, a last resort
|
|
44919
|
-
// after that.
|
|
44920
|
-
//
|
|
44921
|
-
//
|
|
44922
|
-
//
|
|
44923
|
-
//
|
|
44961
|
+
// after that. Two of the ladder's reflexes are turned off here, and it is the
|
|
44962
|
+
// same reason both times — they are about a surface APPEARING, and this is a
|
|
44963
|
+
// press aimed at the control being handed the focus:
|
|
44964
|
+
// - the first focusable is kept even where the pointer is coarse (an arriving
|
|
44965
|
+
// popup drops it so a virtual keyboard does not rise over what it just
|
|
44966
|
+
// showed, see docs/autofocus.md); pressing a cap to reach a field is asking
|
|
44967
|
+
// for that keyboard;
|
|
44968
|
+
// - `autoFocus="restore"` may claim it, though it means "never on a fresh
|
|
44969
|
+
// open". A field marked that way so its sheet opens quietly would otherwise
|
|
44970
|
+
// leave the focus on the magnifier that was pressed to reach it.
|
|
44971
|
+
// A side one reads before writing in says `autoFocus={false}`.
|
|
44924
44972
|
const focusWithTheFloor = (arrivingSlot, arrivingSide, arrivingCap) => {
|
|
44925
44973
|
if (arrivingSide.autoFocus !== false) {
|
|
44926
|
-
const found = findFocusTarget(arrivingSlot
|
|
44974
|
+
const found = findFocusTarget(arrivingSlot, {
|
|
44975
|
+
restoreMayClaim: true
|
|
44976
|
+
});
|
|
44927
44977
|
if (found) {
|
|
44928
44978
|
moveFocusTo(found.target);
|
|
44929
44979
|
return;
|