@jsenv/navi 0.28.1 → 0.28.3
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 +111 -50
- package/dist/jsenv_navi.js.map +8 -8
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -21724,8 +21724,12 @@ const createDisplayedEvent = (ancestor) => {
|
|
|
21724
21724
|
*
|
|
21725
21725
|
* @param {import("preact/hooks").Ref<HTMLElement>} focusableElementRef
|
|
21726
21726
|
* Ref to the element to focus.
|
|
21727
|
-
* @param {boolean} autoFocus
|
|
21728
|
-
* When false the hook is a no-op.
|
|
21727
|
+
* @param {boolean|"fallback"|"restore"} autoFocus
|
|
21728
|
+
* When false the hook is a no-op. `"fallback"` claims focus only when nothing
|
|
21729
|
+
* more specific already did. `"restore"` never claims focus on open; it only
|
|
21730
|
+
* gets focus back from an ancestor that closed while it was focused (see
|
|
21731
|
+
* focus_transfer.js) — typically a text input that must not pop the mobile
|
|
21732
|
+
* keyboard open every time, but should stay where the user left it.
|
|
21729
21733
|
* @param {object} [options]
|
|
21730
21734
|
* @param {boolean} [options.preventScroll]
|
|
21731
21735
|
* Passed as `preventScroll` to `element.focus()`. Defaults to true to suppress
|
|
@@ -21748,6 +21752,11 @@ const useAutoFocus = (
|
|
|
21748
21752
|
if (!autoFocus) {
|
|
21749
21753
|
return () => {};
|
|
21750
21754
|
}
|
|
21755
|
+
if (autoFocus === "restore") {
|
|
21756
|
+
// "restore" never claims focus on its own; the only way it gets focus is
|
|
21757
|
+
// an ancestor reopening and handing it back (see focus_transfer.js).
|
|
21758
|
+
return () => {};
|
|
21759
|
+
}
|
|
21751
21760
|
const focusableElement = focusableElementRef.current;
|
|
21752
21761
|
if (!focusableElement) {
|
|
21753
21762
|
return () => {};
|
|
@@ -36015,22 +36024,61 @@ const renderSafe = (value) => {
|
|
|
36015
36024
|
const PickerContext = createContext();
|
|
36016
36025
|
|
|
36017
36026
|
/**
|
|
36018
|
-
*
|
|
36019
|
-
*
|
|
36020
|
-
*
|
|
36021
|
-
*
|
|
36022
|
-
*
|
|
36027
|
+
* Decides which element receives focus when a container (popover, dialog, …)
|
|
36028
|
+
* opens, and gives it back to where it came from when the container closes.
|
|
36029
|
+
*
|
|
36030
|
+
* The [navi-autofocus] attribute (written by use_auto_focus.js) tunes where
|
|
36031
|
+
* focus lands. Candidates are tried in this order:
|
|
36032
|
+
* 1. The element that held focus when the container was last closed, if it
|
|
36033
|
+
* opted into that with "fallback" or "restore"
|
|
36034
|
+
* 2. [navi-autofocus] with any other value ("" for a plain `autoFocus`)
|
|
36035
|
+
* 3. The first focusable element
|
|
36036
|
+
* 4. [navi-autofocus="fallback"], the container itself included
|
|
36037
|
+
* 5. The element focused before the container opened
|
|
36038
|
+
*
|
|
36039
|
+
* [navi-autofocus="restore"] appears in step 1 only: it never claims focus on
|
|
36040
|
+
* a fresh open, it only gets it back.
|
|
36023
36041
|
*/
|
|
36042
|
+
|
|
36043
|
+
// The element that held focus when a container closed is marked with
|
|
36044
|
+
// [navi-autofocus-last-focused], and its container with
|
|
36045
|
+
// [navi-autofocus-restore]. Both carry the same generated id: containers can
|
|
36046
|
+
// nest (a popover inside a dialog), so the id is what tells a reopening
|
|
36047
|
+
// container which mark among its descendants is its own.
|
|
36048
|
+
let restoreIdCounter = 0;
|
|
36049
|
+
|
|
36050
|
+
const isRestorableAutofocus = (el) => {
|
|
36051
|
+
const value = el.getAttribute("navi-autofocus");
|
|
36052
|
+
return value === "fallback" || value === "restore";
|
|
36053
|
+
};
|
|
36054
|
+
|
|
36055
|
+
const clearAutofocusRestore = (containerEl) => {
|
|
36056
|
+
const restoreId = containerEl.getAttribute("navi-autofocus-restore");
|
|
36057
|
+
if (restoreId === null) {
|
|
36058
|
+
return null;
|
|
36059
|
+
}
|
|
36060
|
+
containerEl.removeAttribute("navi-autofocus-restore");
|
|
36061
|
+
const selector = `[navi-autofocus-last-focused="${restoreId}"]`;
|
|
36062
|
+
const lastFocused = containerEl.matches(selector)
|
|
36063
|
+
? containerEl
|
|
36064
|
+
: containerEl.querySelector(selector);
|
|
36065
|
+
if (lastFocused) {
|
|
36066
|
+
lastFocused.removeAttribute("navi-autofocus-last-focused");
|
|
36067
|
+
}
|
|
36068
|
+
return lastFocused;
|
|
36069
|
+
};
|
|
36070
|
+
|
|
36024
36071
|
const markAutofocusRestoreOnClose = (containerEl) => {
|
|
36072
|
+
clearAutofocusRestore(containerEl);
|
|
36025
36073
|
const focused = document.activeElement;
|
|
36026
36074
|
if (
|
|
36027
36075
|
focused &&
|
|
36028
|
-
containerEl.contains(focused) &&
|
|
36029
|
-
focused
|
|
36076
|
+
(containerEl === focused || containerEl.contains(focused)) &&
|
|
36077
|
+
isRestorableAutofocus(focused)
|
|
36030
36078
|
) {
|
|
36031
|
-
|
|
36032
|
-
|
|
36033
|
-
|
|
36079
|
+
const restoreId = `${++restoreIdCounter}`;
|
|
36080
|
+
containerEl.setAttribute("navi-autofocus-restore", restoreId);
|
|
36081
|
+
focused.setAttribute("navi-autofocus-last-focused", restoreId);
|
|
36034
36082
|
}
|
|
36035
36083
|
};
|
|
36036
36084
|
|
|
@@ -36052,19 +36100,14 @@ const prepareFocusTransfer = (prepareEvent, debugFocus) => {
|
|
|
36052
36100
|
transferFocus: (transferEvent, containerEl) => {
|
|
36053
36101
|
let target;
|
|
36054
36102
|
let reason;
|
|
36055
|
-
|
|
36056
|
-
|
|
36057
|
-
|
|
36058
|
-
|
|
36059
|
-
);
|
|
36060
|
-
if (naviAutoFocusFallback) {
|
|
36061
|
-
reason = "navi-autofocus fallback (restore)";
|
|
36062
|
-
target = naviAutoFocusFallback;
|
|
36063
|
-
}
|
|
36103
|
+
const lastFocused = clearAutofocusRestore(containerEl);
|
|
36104
|
+
if (lastFocused) {
|
|
36105
|
+
reason = "element focused when closed (restore)";
|
|
36106
|
+
target = lastFocused;
|
|
36064
36107
|
}
|
|
36065
36108
|
if (!target) {
|
|
36066
36109
|
const naviAutoFocus = containerEl.querySelector(
|
|
36067
|
-
|
|
36110
|
+
`[navi-autofocus]:not([navi-autofocus="fallback"]):not([navi-autofocus="restore"])`,
|
|
36068
36111
|
);
|
|
36069
36112
|
if (naviAutoFocus) {
|
|
36070
36113
|
reason = "navi-autofocus";
|
|
@@ -36073,7 +36116,7 @@ const prepareFocusTransfer = (prepareEvent, debugFocus) => {
|
|
|
36073
36116
|
}
|
|
36074
36117
|
if (!target) {
|
|
36075
36118
|
const focusable = findFocusable(containerEl, {
|
|
36076
|
-
exclude:
|
|
36119
|
+
exclude: isRestorableAutofocus,
|
|
36077
36120
|
});
|
|
36078
36121
|
if (focusable) {
|
|
36079
36122
|
reason = "first focusable element";
|
|
@@ -37329,9 +37372,10 @@ const css$v = /* css */`
|
|
|
37329
37372
|
* @param {number} [props.tabIndex=-1] - Set on the dialog element itself so
|
|
37330
37373
|
* `autoFocus="fallback"` below has somewhere to land when the dialog has
|
|
37331
37374
|
* no other focusable descendant of its own.
|
|
37332
|
-
* @param {boolean|"fallback"} [props.autoFocus="fallback"] - See
|
|
37333
|
-
* `
|
|
37334
|
-
* no other focusable descendant
|
|
37375
|
+
* @param {boolean|"fallback"|"restore"} [props.autoFocus="fallback"] - See
|
|
37376
|
+
* `focus_transfer.js` — `"fallback"` focuses the dialog itself if it has
|
|
37377
|
+
* no other focusable descendant, `"restore"` keeps it out of the opening
|
|
37378
|
+
* focus chain unless it held focus when the dialog closed.
|
|
37335
37379
|
* @param {boolean} [props.open] - Controlled open state.
|
|
37336
37380
|
* @param {boolean} [props.defaultOpen] - Uncontrolled, mount-only initial
|
|
37337
37381
|
* open state — plays no entrance animation (nothing was ever shown as
|
|
@@ -38317,9 +38361,10 @@ const css$u = /* css */`
|
|
|
38317
38361
|
* @param {number} [props.tabIndex=-1] - Set on the popover element itself
|
|
38318
38362
|
* so `autoFocus="fallback"` below has somewhere to land when the popover
|
|
38319
38363
|
* has no other focusable descendant of its own.
|
|
38320
|
-
* @param {boolean|"fallback"} [props.autoFocus="fallback"] - See
|
|
38321
|
-
* `
|
|
38322
|
-
* no other focusable descendant
|
|
38364
|
+
* @param {boolean|"fallback"|"restore"} [props.autoFocus="fallback"] - See
|
|
38365
|
+
* `focus_transfer.js` — `"fallback"` focuses the popover itself if it has
|
|
38366
|
+
* no other focusable descendant, `"restore"` keeps it out of the opening
|
|
38367
|
+
* focus chain unless it held focus when the popover closed.
|
|
38323
38368
|
* @param {boolean} [props.open] - Controlled open state.
|
|
38324
38369
|
* @param {boolean} [props.defaultOpen] - Uncontrolled, mount-only initial
|
|
38325
38370
|
* open state — plays no entrance animation (nothing was ever shown as
|
|
@@ -42233,9 +42278,10 @@ const css$p = /* css */`
|
|
|
42233
42278
|
user-select: none;
|
|
42234
42279
|
}
|
|
42235
42280
|
}
|
|
42236
|
-
/* Loading placeholders (see List's loading /
|
|
42281
|
+
/* Loading placeholders (see List's loading / loadingFallback / loadingSkeletonTemplate).
|
|
42237
42282
|
A skeleton row reuses <Text loading> for the shimmer bar; the loader row
|
|
42238
|
-
centers a spinner
|
|
42283
|
+
centers a spinner; a custom loadingFallback is only given a row to live in,
|
|
42284
|
+
its own markup does the layout. */
|
|
42239
42285
|
.navi_list_item_skeleton {
|
|
42240
42286
|
pointer-events: none;
|
|
42241
42287
|
}
|
|
@@ -42246,6 +42292,9 @@ const css$p = /* css */`
|
|
|
42246
42292
|
justify-content: center;
|
|
42247
42293
|
color: light-dark(#888, #aaa);
|
|
42248
42294
|
}
|
|
42295
|
+
.navi_list_loading_fallback {
|
|
42296
|
+
display: flex;
|
|
42297
|
+
}
|
|
42249
42298
|
/* Error state (List error prop): an inline callout describing why the list
|
|
42250
42299
|
failed to load, shown in place of the items. */
|
|
42251
42300
|
.navi_list_error {
|
|
@@ -42357,9 +42406,9 @@ const ListUI = props => {
|
|
|
42357
42406
|
searchText,
|
|
42358
42407
|
searchNoMatchMode = "remove",
|
|
42359
42408
|
loading,
|
|
42360
|
-
|
|
42409
|
+
loadingFallback = "skeleton",
|
|
42361
42410
|
loadingSkeletonCount = 3,
|
|
42362
|
-
|
|
42411
|
+
loadingSkeletonTemplate,
|
|
42363
42412
|
error,
|
|
42364
42413
|
horizontal,
|
|
42365
42414
|
spacing,
|
|
@@ -42450,9 +42499,8 @@ const ListUI = props => {
|
|
|
42450
42499
|
const nothingToDisplay = !loading && !error && noVisibleItems && !searchFallbackShown && !emptyFallbackShown;
|
|
42451
42500
|
|
|
42452
42501
|
// Placeholder content replaces the real children: an error message when the
|
|
42453
|
-
// load failed (takes precedence), otherwise — while loading —
|
|
42454
|
-
//
|
|
42455
|
-
// centered loader when loadingIndicator="loader".
|
|
42502
|
+
// load failed (takes precedence), otherwise — while loading — whatever
|
|
42503
|
+
// loadingFallback asks for.
|
|
42456
42504
|
let content = children;
|
|
42457
42505
|
if (error) {
|
|
42458
42506
|
content = jsxs(ListItem, {
|
|
@@ -42466,16 +42514,9 @@ const ListUI = props => {
|
|
|
42466
42514
|
children: error === true ? "Something went wrong." : error
|
|
42467
42515
|
})]
|
|
42468
42516
|
});
|
|
42469
|
-
} else if (loading) {
|
|
42470
|
-
if (
|
|
42471
|
-
|
|
42472
|
-
role: "presentation",
|
|
42473
|
-
"aria-hidden": "true",
|
|
42474
|
-
baseClassName: "navi_list_item navi_list_loader",
|
|
42475
|
-
children: jsx(LoadingIndicator, {})
|
|
42476
|
-
});
|
|
42477
|
-
} else {
|
|
42478
|
-
const template = skeletonTemplate ?? jsx(ListItem, {
|
|
42517
|
+
} else if (loading && loadingFallback) {
|
|
42518
|
+
if (loadingFallback === "skeleton") {
|
|
42519
|
+
const template = loadingSkeletonTemplate ?? jsx(ListItem, {
|
|
42479
42520
|
skeleton: true
|
|
42480
42521
|
});
|
|
42481
42522
|
const skeletons = [];
|
|
@@ -42487,6 +42528,21 @@ const ListUI = props => {
|
|
|
42487
42528
|
skeletonIndex++;
|
|
42488
42529
|
}
|
|
42489
42530
|
content = skeletons;
|
|
42531
|
+
} else if (loadingFallback === "loader") {
|
|
42532
|
+
content = jsx(ListItem, {
|
|
42533
|
+
role: "presentation",
|
|
42534
|
+
"aria-hidden": "true",
|
|
42535
|
+
baseClassName: "navi_list_item navi_list_loader",
|
|
42536
|
+
children: jsx(LoadingIndicator, {})
|
|
42537
|
+
});
|
|
42538
|
+
} else {
|
|
42539
|
+
// Custom content is not aria-hidden (unlike the bare spinner): it usually
|
|
42540
|
+
// carries a message worth announcing.
|
|
42541
|
+
content = jsx(ListItem, {
|
|
42542
|
+
role: "presentation",
|
|
42543
|
+
baseClassName: "navi_list_item navi_list_loading_fallback",
|
|
42544
|
+
children: loadingFallback
|
|
42545
|
+
});
|
|
42490
42546
|
}
|
|
42491
42547
|
}
|
|
42492
42548
|
return jsx(Box, {
|
|
@@ -42569,9 +42625,9 @@ const ListFirstResolver = props => {
|
|
|
42569
42625
|
* searchText?: string,
|
|
42570
42626
|
* searchNoMatchMode?: "remove" | "invisible_and_inert" | "muted" | "below",
|
|
42571
42627
|
* loading?: boolean,
|
|
42572
|
-
*
|
|
42628
|
+
* loadingFallback?: "skeleton" | "loader" | import("ignore:preact").ComponentChildren,
|
|
42573
42629
|
* loadingSkeletonCount?: number,
|
|
42574
|
-
*
|
|
42630
|
+
* loadingSkeletonTemplate?: import("ignore:preact").ComponentChildren,
|
|
42575
42631
|
* error?: boolean | import("ignore:preact").ComponentChildren,
|
|
42576
42632
|
* separator?: boolean | import("ignore:preact").ComponentChildren,
|
|
42577
42633
|
* lockSize?: boolean,
|
|
@@ -42584,6 +42640,11 @@ const ListFirstResolver = props => {
|
|
|
42584
42640
|
* children?: import("ignore:preact").ComponentChildren,
|
|
42585
42641
|
* [key: string]: any,
|
|
42586
42642
|
* }>}
|
|
42643
|
+
* @param {"skeleton"|"loader"|import("ignore:preact").ComponentChildren} [props.loadingFallback="skeleton"]
|
|
42644
|
+
* What to display in place of the items while `loading`: `"skeleton"` renders
|
|
42645
|
+
* `loadingSkeletonCount` placeholder rows (look: `loadingSkeletonTemplate`),
|
|
42646
|
+
* `"loader"` a single centered spinner, and anything else is rendered as-is
|
|
42647
|
+
* in a row of its own. A falsy value displays nothing.
|
|
42587
42648
|
*/
|
|
42588
42649
|
const List = createComponentResolver([ListFirstResolver, ListSelectableResolver, ListUI]);
|
|
42589
42650
|
const ListContent = ({
|
|
@@ -43331,7 +43392,7 @@ const ListItemPresentation = props => {
|
|
|
43331
43392
|
// A <List.Item skeleton> — a non-interactive placeholder row shown while a list
|
|
43332
43393
|
// is loading. It is presentation-only (not tracked, not selectable, aria-hidden)
|
|
43333
43394
|
// and reuses <Text loading> for the shimmer. Box layout props (padding, spacing…)
|
|
43334
|
-
// pass through so a
|
|
43395
|
+
// pass through so a loadingSkeletonTemplate can match the real items' metrics; and when
|
|
43335
43396
|
// children are provided they render as-is, so a template can reproduce a
|
|
43336
43397
|
// multi-part item (e.g. title + subtitle) out of several <Text loading> bars.
|
|
43337
43398
|
const ListItemSkeletonResolver = props => {
|
|
@@ -43572,7 +43633,7 @@ const LIST_ITEM_PSEUDO_ELEMENTS = ["::highlight"];
|
|
|
43572
43633
|
* depending on whether the parent List has `multiple`). Requires
|
|
43573
43634
|
* `value` and typically a <SelectableInput /> child.
|
|
43574
43635
|
* skeleton — render a non-interactive placeholder row (a shimmering bar)
|
|
43575
|
-
* instead of a real item. Used as the List `
|
|
43636
|
+
* instead of a real item. Used as the List `loadingSkeletonTemplate`
|
|
43576
43637
|
* while `loading`; Box layout props (padding…) pass through so the
|
|
43577
43638
|
* placeholder can match the real items' metrics.
|
|
43578
43639
|
* value — the JS value emitted by the list's action/uiAction when this item
|