@jsenv/navi 0.29.469 → 0.29.471

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.
@@ -39055,8 +39055,29 @@ const resolveFirstChildControl = (el) => {
39055
39055
  }
39056
39056
  return startEl.querySelector("[navi-control-host]");
39057
39057
  };
39058
+ // The element a popup command is about: the closest one keeping an open state.
39058
39059
  const resolveClosestExpandable = (el) => {
39059
- return el.closest("[aria-expanded]");
39060
+ const expandable = el.closest("[aria-expanded]");
39061
+ if (!expandable) {
39062
+ return null;
39063
+ }
39064
+ return expandableRepeatedBy(expandable) || expandable;
39065
+ };
39066
+ // A control's host repeats the aria-expanded of its root, beside its role, for
39067
+ // assistive tech to read (a picker's <input>, a combobox or a button). The root
39068
+ // is the expandable: the host is a leaf the walk lands on only when it is the
39069
+ // source itself, and it carries the control's own `command`/`commandfor`, which
39070
+ // a command resolved again from it (a close after a clear) would follow.
39071
+ const expandableRepeatedBy = (el) => {
39072
+ const controlRoot = findControlRoot(el);
39073
+ if (
39074
+ controlRoot &&
39075
+ controlRoot !== el &&
39076
+ controlRoot.hasAttribute("aria-expanded")
39077
+ ) {
39078
+ return controlRoot;
39079
+ }
39080
+ return null;
39060
39081
  };
39061
39082
  // The surface a send is decided on: the expandable holding the CONTROL that
39062
39083
  // sends, never one that control is itself. A picker's own root carries
@@ -39067,7 +39088,7 @@ const resolveClosestExpandable = (el) => {
39067
39088
  const resolveExpandableAround = (source) => {
39068
39089
  const sender = findControlRoot(source) || source;
39069
39090
  const parent = sender.parentElement;
39070
- return parent ? parent.closest("[aria-expanded]") : null;
39091
+ return parent ? resolveClosestExpandable(parent) : null;
39071
39092
  };
39072
39093
  // An element carrying navi-command-proxy-for="anchorId" stands in for that
39073
39094
  // other element as the command's source when the source is forwarded to
@@ -39368,8 +39389,12 @@ const sendCommand = (source, event, { requester }) => {
39368
39389
  // looking at, and a decision just taken there is a reason to leave it. The
39369
39390
  // NEAREST surface answers, and only that one — a form inside a slide inside a
39370
39391
  // dialog goes back a slide, it does not also close the dialog.
39371
- // send inside expandable
39372
- if (target.getAttribute("aria-expanded") === "true") {
39392
+ // send inside expandable — the control itself when it sends from its host,
39393
+ // whose aria-expanded is its root's (see expandableRepeatedBy)
39394
+ if (
39395
+ target.getAttribute("aria-expanded") === "true" &&
39396
+ !expandableRepeatedBy(target)
39397
+ ) {
39373
39398
  return {
39374
39399
  target,
39375
39400
  implementation: () => {
@@ -39804,7 +39829,7 @@ const requestCloseUpward = (target, detail) => {
39804
39829
  return false;
39805
39830
  }
39806
39831
  const parent = expandable.parentElement;
39807
- expandable = parent ? parent.closest("[aria-expanded]") : null;
39832
+ expandable = parent ? resolveClosestExpandable(parent) : null;
39808
39833
  }
39809
39834
  return true;
39810
39835
  };
@@ -46639,8 +46664,10 @@ const createControlInfo = (props, {
46639
46664
  } else if (controlType === "details") {
46640
46665
  statePropName = "open";
46641
46666
  defaultStatePropName = "defaultOpen";
46642
- stateInitial = props.open || props.defaultOpen;
46643
46667
  value = props.value || "open";
46668
+ // Held like a checkbox: the value while open, nothing while closed — the
46669
+ // shape the toggle writes back (see details.jsx).
46670
+ stateInitial = props.open || props.defaultOpen ? value : undefined;
46644
46671
  } else if (controlType === "picker" || controlType === "select") {
46645
46672
  statePropName = "value";
46646
46673
  defaultStatePropName = "defaultValue";
@@ -54674,6 +54701,9 @@ const DetailsField = props => {
54674
54701
  value,
54675
54702
  children
54676
54703
  } = detailsProps;
54704
+ const {
54705
+ uiStateController
54706
+ } = controlChildrenWrapperProps;
54677
54707
  const open = Boolean(value);
54678
54708
  useFocusGroup(ref, {
54679
54709
  enabled: focusGroup,
@@ -54755,6 +54785,10 @@ const DetailsField = props => {
54755
54785
  return;
54756
54786
  }
54757
54787
  const details = ref.current;
54788
+ // The browser has already toggled the element (a summary press, the
54789
+ // arrow shortcuts): the state follows it whatever the gate says, or the
54790
+ // marker and whoever reads the value keep the old one.
54791
+ uiStateController.setUIState(details.open ? uiStateController.value : undefined, e);
54758
54792
  dispatchRequestInteraction(details, {
54759
54793
  event: e,
54760
54794
  wantAction: true,
@@ -58175,6 +58209,10 @@ const Label = props => {
58175
58209
  requiredIndicator
58176
58210
  } = props;
58177
58211
  const controlId = useContext(ControlIdContext);
58212
+ // What a popup opened from the control is named by (see getTriggerLabelIds
58213
+ // in picker_custom.jsx): aria-labelledby can only point at an id.
58214
+ const idDefault = useId();
58215
+ props.id = props.id || `label_${idDefault}`;
58178
58216
  const [disabled, setDisabled] = useState(false);
58179
58217
  const [readOnly, setReadOnly] = useState(false);
58180
58218
  const [required, setRequired] = useState(false);
@@ -67868,9 +67906,297 @@ const PopupClose = ({
67868
67906
  });
67869
67907
  };
67870
67908
 
67909
+ const SuccessSvg = () => {
67910
+ return jsx("svg", {
67911
+ viewBox: "0 0 16 16",
67912
+ fill: "currentColor",
67913
+ children: jsx("path", {
67914
+ d: "M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm1.5 0a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm10.28-1.72-4.5 4.5a.75.75 0 0 1-1.06 0l-2-2a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018l1.47 1.47 3.97-3.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"
67915
+ })
67916
+ });
67917
+ };
67918
+ const ErrorSvg = () => {
67919
+ return jsx("svg", {
67920
+ viewBox: "0 0 16 16",
67921
+ fill: "currentColor",
67922
+ children: jsx("path", {
67923
+ d: "M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"
67924
+ })
67925
+ });
67926
+ };
67927
+ const InfoSvg$1 = () => {
67928
+ return jsx("svg", {
67929
+ viewBox: "0 0 16 16",
67930
+ fill: "currentColor",
67931
+ children: jsx("path", {
67932
+ d: "M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"
67933
+ })
67934
+ });
67935
+ };
67936
+ const WarningSvg = () => {
67937
+ return jsx("svg", {
67938
+ viewBox: "0 0 16 16",
67939
+ fill: "currentColor",
67940
+ children: jsx("path", {
67941
+ d: "M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
67942
+ })
67943
+ });
67944
+ };
67945
+
67946
+ installImportMetaCssBuild(import.meta);
67947
+ const css$E = /* css */`@layer navi {
67948
+ .navi_message_box {
67949
+ --background-color-info: var(--navi-info-color-light);
67950
+ --color-info: var(--navi-info-color);
67951
+ --background-color-success: var(--navi-success-color-light);
67952
+ --color-success: var(--navi-success-color);
67953
+ --background-color-warning: var(--navi-warning-color-light);
67954
+ --color-warning: var(--navi-warning-color);
67955
+ --background-color-error: var(--navi-error-color-light);
67956
+ --color-error: var(--navi-error-color);
67957
+ }
67958
+ }
67959
+
67960
+ .navi_message_box {
67961
+ --x-message-background-color: var(--background-color-info);
67962
+ --x-message-color: var(--color-info);
67963
+ background-color: var(--x-message-background-color);
67964
+
67965
+ & > .navi_icon {
67966
+ flex-shrink: 0;
67967
+ }
67968
+ }
67969
+
67970
+ .navi_message_box[data-status-info] {
67971
+ --x-message-background-color: var(--background-color-info);
67972
+ --x-message-color: var(--color-info);
67973
+ }
67974
+
67975
+ .navi_message_box[data-status-success] {
67976
+ --x-message-background-color: var(--background-color-success);
67977
+ --x-message-color: var(--color-success);
67978
+ }
67979
+
67980
+ .navi_message_box[data-status-warning] {
67981
+ --x-message-background-color: var(--background-color-warning);
67982
+ --x-message-color: var(--color-warning);
67983
+ }
67984
+
67985
+ .navi_message_box[data-status-error] {
67986
+ --x-message-background-color: var(--background-color-error);
67987
+ --x-message-color: var(--color-error);
67988
+ }
67989
+
67990
+ .navi_message_box[data-left-stripe] {
67991
+ border-left: 6px solid var(--x-message-color);
67992
+ border-top-left-radius: 6px;
67993
+ border-bottom-left-radius: 6px;
67994
+ }
67995
+ `;
67996
+ const MessageBox = ({
67997
+ status = "info",
67998
+ padding = "sm",
67999
+ icon,
68000
+ leftStripe,
68001
+ children,
68002
+ onClose,
68003
+ ...rest
68004
+ }) => {
68005
+ import.meta.css = [css$E, "@jsenv/navi/src/text/message_box.jsx"];
68006
+ const [hasTitleChild, setHasTitleChild] = useState(false);
68007
+ const innerLeftStripe = leftStripe === undefined ? hasTitleChild : leftStripe;
68008
+ if (icon === true) {
68009
+ icon = status === "info" ? jsx(InfoSvg$1, {}) : status === "success" ? jsx(SuccessSvg, {}) : status === "warning" ? jsx(WarningSvg, {}) : status === "error" ? jsx(ErrorSvg, {}) : null;
68010
+ } else if (typeof icon === "function") {
68011
+ const Comp = icon;
68012
+ icon = jsx(Comp, {});
68013
+ }
68014
+ return jsx(Box, {
68015
+ as: "div",
68016
+ role: status === "info" ? "status" : "alert",
68017
+ "data-left-stripe": innerLeftStripe ? "" : undefined,
68018
+ inline: true,
68019
+ column: true,
68020
+ alignY: "center",
68021
+ spacing: "s",
68022
+ ...rest,
68023
+ className: withPropsClassName("navi_message_box", rest.className),
68024
+ padding: padding,
68025
+ pseudoClasses: MessageBoxPseudoClasses,
68026
+ basePseudoState: {
68027
+ ":-navi-status-info": status === "info",
68028
+ ":-navi-status-success": status === "success",
68029
+ ":-navi-status-warning": status === "warning",
68030
+ ":-navi-status-error": status === "error"
68031
+ },
68032
+ children: jsx(MessageBoxStatusContext.Provider, {
68033
+ value: status,
68034
+ children: jsxs(MessageBoxReportTitleChildContext.Provider, {
68035
+ value: setHasTitleChild,
68036
+ children: [icon && jsx(Icon, {
68037
+ color: "var(--x-message-color)",
68038
+ height: "1lh",
68039
+ maxHeight: "auto",
68040
+ selfAlignY: "start",
68041
+ aspectRatio: "auto",
68042
+ children: icon
68043
+ }), jsx(Text, {
68044
+ children: children
68045
+ }), onClose &&
68046
+ // A column as tall as the first line of the message, pinned to the
68047
+ // top, the button centered in it: the close button stays level
68048
+ // with the first line however many lines the message takes (same
68049
+ // layout as Callout).
68050
+ jsx(Box, {
68051
+ flex: true,
68052
+ alignY: "center",
68053
+ height: "1lh",
68054
+ selfAlignY: "start",
68055
+ shrink: false,
68056
+ children: jsx(Button, {
68057
+ action: onClose,
68058
+ icon: true,
68059
+ border: "none",
68060
+ style: {
68061
+ ":hover": {
68062
+ backgroundColor: "rgba(0, 0, 0, 0.1)"
68063
+ }
68064
+ },
68065
+ children: jsx(Icon, {
68066
+ children: jsx(CloseSvg, {})
68067
+ })
68068
+ })
68069
+ })]
68070
+ })
68071
+ })
68072
+ });
68073
+ };
68074
+ const MessageBoxPseudoClasses = [":-navi-status-info", ":-navi-status-success", ":-navi-status-warning", ":-navi-status-error"];
68075
+ const MessageBoxStatusContext = createContext();
68076
+ const MessageBoxReportTitleChildContext = createContext();
68077
+
68078
+ installImportMetaCssBuild(import.meta);
68079
+ const css$D = /* css */`.navi_message_box {
68080
+ & .navi_title {
68081
+ margin-top: 0;
68082
+ margin-bottom: var(--navi-s);
68083
+ color: var(--x-message-color);
68084
+ }
68085
+ }
68086
+ `;
68087
+ const Title = props => {
68088
+ import.meta.css = [css$D, "@jsenv/navi/src/text/title.jsx"];
68089
+ const messageBoxStatus = useContext(MessageBoxStatusContext);
68090
+ const innerAs = props.as || (messageBoxStatus ? "h4" : "h1");
68091
+ const titleLevel = parseInt(innerAs.slice(1));
68092
+ const reportTitleToMessageBox = useContext(MessageBoxReportTitleChildContext);
68093
+ reportTitleToMessageBox?.(true);
68094
+ return jsx(TitleLevelContext.Provider, {
68095
+ value: titleLevel,
68096
+ children: jsx(Text, {
68097
+ bold: true,
68098
+ className: withPropsClassName("navi_title"),
68099
+ as: messageBoxStatus ? "h4" : "h1",
68100
+ ...props,
68101
+ pseudoClasses: TitlePseudoClasses,
68102
+ children: props.children
68103
+ })
68104
+ });
68105
+ };
68106
+ const TitleLevelContext = createContext();
68107
+ const useTitleLevel = () => {
68108
+ return useContext(TitleLevelContext);
68109
+ };
68110
+ const TitlePseudoClasses = [":hover"];
68111
+
68112
+ /**
68113
+ * What a popup is called, decided by Dialog and Popover alike: the name the
68114
+ * caller writes on it (aria-label/aria-labelledby), else the title it holds
68115
+ * (`<Popup.Title>`), else the name of what opened it (`openerAriaLabelledBy` /
68116
+ * `openerAriaLabel`, which a Picker fills with its trigger's). A popup no one
68117
+ * names is announced as "dialog" and nothing else.
68118
+ *
68119
+ * The title tells the popup it exists rather than the popup looking for it:
68120
+ * the content is built after the popup and thrown away before it (see
68121
+ * popup_content_mount.js), and a title may sit anywhere in it — a header, a
68122
+ * step of a form — so only the title knows when it is there.
68123
+ */
68124
+
68125
+ const PopupTitleContext = createContext(null);
68126
+
68127
+ /**
68128
+ * The title of the popup it sits in — reached as `<Popup.Title>`,
68129
+ * `<Dialog.Title>` or `<Popover.Title>`. A heading (`h2` unless `as` says
68130
+ * otherwise) that is also the popup's accessible name: a screen reader
68131
+ * announces the popup by it, and `getByRole("dialog", { name })` finds it.
68132
+ * An `aria-label`/`aria-labelledby` written on the popup itself wins over it.
68133
+ *
68134
+ * @type {import("ignore:preact").FunctionComponent<{
68135
+ * as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6",
68136
+ * id?: string,
68137
+ * children?: import("ignore:preact").ComponentChildren,
68138
+ * } & Record<string, any>>}
68139
+ */
68140
+ const PopupTitle = props => {
68141
+ const registerTitle = useContext(PopupTitleContext);
68142
+ const idDefault = useId();
68143
+ const id = props.id || `popup_title_${idDefault}`;
68144
+ useLayoutEffect(() => {
68145
+ if (!registerTitle) {
68146
+ return undefined;
68147
+ }
68148
+ return registerTitle(id);
68149
+ }, [registerTitle, id]);
68150
+ return jsx(Title, {
68151
+ as: "h2",
68152
+ ...props,
68153
+ id: id
68154
+ });
68155
+ };
68156
+
68157
+ /**
68158
+ * The name props of a popup element, and what its titles register with —
68159
+ * handed to its children through PopupTitleContext.
68160
+ */
68161
+ const usePopupName = ({
68162
+ ariaLabel,
68163
+ ariaLabelledBy,
68164
+ openerAriaLabel,
68165
+ openerAriaLabelledBy
68166
+ }) => {
68167
+ // Several titles is a content of the caller's own making (two steps both
68168
+ // mounted): the first one registered names the popup.
68169
+ const [titleIds, setTitleIds] = useState(NO_TITLE);
68170
+ const registerTitle = useMemo(() => {
68171
+ return id => {
68172
+ setTitleIds(ids => [...ids, id]);
68173
+ return () => {
68174
+ setTitleIds(ids => ids.filter(each => each !== id));
68175
+ };
68176
+ };
68177
+ }, []);
68178
+ let labelledBy;
68179
+ let label;
68180
+ if (ariaLabelledBy !== undefined || ariaLabel !== undefined) {
68181
+ labelledBy = ariaLabelledBy;
68182
+ label = ariaLabel;
68183
+ } else if (titleIds.length > 0) {
68184
+ labelledBy = titleIds[0];
68185
+ } else if (openerAriaLabelledBy !== undefined) {
68186
+ labelledBy = openerAriaLabelledBy;
68187
+ } else {
68188
+ label = openerAriaLabel;
68189
+ }
68190
+ return [{
68191
+ "aria-labelledby": labelledBy,
68192
+ "aria-label": label
68193
+ }, registerTitle];
68194
+ };
68195
+ const NO_TITLE = [];
68196
+
67871
68197
  installImportMetaCssBuild(import.meta);
67872
68198
  let openLocalDialogCount = 0;
67873
- const css$E = /* css */`
68199
+ const css$C = /* css */`
67874
68200
  /* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
67875
68201
  order depending on which popup renders first. */
67876
68202
 
@@ -68939,10 +69265,14 @@ const css$E = /* css */`
68939
69265
  * under `"while-opened"`, only a press on what opens the popup does (a
68940
69266
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
68941
69267
  * press that does not open the popup throws it away.
69268
+ * @param {string} [props.openerAriaLabelledBy] - The id(s) naming what opened
69269
+ * the dialog: its accessible name when neither `aria-label`/`aria-labelledby`
69270
+ * nor a `<Dialog.Title>` names it. A Picker passes its trigger's.
69271
+ * @param {string} [props.openerAriaLabel] - The same, as a string.
68942
69272
  * @param {import("ignore:preact").ComponentChildren} props.children
68943
69273
  */
68944
69274
  const Dialog = props => {
68945
- import.meta.css = [css$E, "@jsenv/navi/src/layout/dialog.jsx"];
69275
+ import.meta.css = [css$C, "@jsenv/navi/src/layout/dialog.jsx"];
68946
69276
  if (props.openController) {
68947
69277
  return jsx(ControlledDialog, {
68948
69278
  ...props
@@ -69316,6 +69646,9 @@ const useDialogProps = props => {
69316
69646
  onOpen,
69317
69647
  children: childrenProp,
69318
69648
  mount,
69649
+ // Read by usePopupName below, never written on the element.
69650
+ openerAriaLabel,
69651
+ openerAriaLabelledBy,
69319
69652
  ...rest
69320
69653
  } = props;
69321
69654
  // Assigned on every render, like openEffect below, so it always closes over
@@ -69326,7 +69659,16 @@ const useDialogProps = props => {
69326
69659
  mount,
69327
69660
  anchor
69328
69661
  });
69329
- const children = contentMounted ? childrenProp : null;
69662
+ const [nameProps, registerTitle] = usePopupName({
69663
+ ariaLabel: rest["aria-label"],
69664
+ ariaLabelledBy: rest["aria-labelledby"],
69665
+ openerAriaLabel,
69666
+ openerAriaLabelledBy
69667
+ });
69668
+ const children = contentMounted ? jsx(PopupTitleContext.Provider, {
69669
+ value: registerTitle,
69670
+ children: childrenProp
69671
+ }) : null;
69330
69672
  // Where it is placed, and whether there is a wall — the two questions that
69331
69673
  // pick the show call (see this file's top comment).
69332
69674
  const isTopLayer = layer === "top";
@@ -70116,6 +70458,7 @@ const useDialogProps = props => {
70116
70458
  "backdropFilter": backdrop ? backdropFilter : undefined,
70117
70459
  "styleCSSVars": DIALOG_STYLE_CSS_VARS,
70118
70460
  ...rest,
70461
+ ...nameProps,
70119
70462
  ...autoFocusProps,
70120
70463
  "as": "dialog",
70121
70464
  ref,
@@ -70289,10 +70632,11 @@ const DIALOG_STYLE_CSS_VARS = {
70289
70632
  maxHeight: "--dialog-max-height"
70290
70633
  };
70291
70634
  Dialog.Close = PopupClose;
70635
+ Dialog.Title = PopupTitle;
70292
70636
 
70293
70637
  installImportMetaCssBuild(import.meta);
70294
70638
  let openLocalPopoverCount = 0;
70295
- const css$D = /* css */`
70639
+ const css$B = /* css */`
70296
70640
  /* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
70297
70641
  order depending on which popup renders first. */
70298
70642
 
@@ -70891,10 +71235,17 @@ const css$D = /* css */`
70891
71235
  * under `"while-opened"`, only a press on what opens the popup does (a
70892
71236
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
70893
71237
  * press that does not open the popup throws it away.
71238
+ * @param {string} [props.openerAriaLabelledBy] - The id(s) naming what opened
71239
+ * the popover: its accessible name when neither `aria-label`/`aria-labelledby`
71240
+ * nor a `<Popover.Title>` names it. A Picker passes its trigger's.
71241
+ * @param {string} [props.openerAriaLabel] - The same, as a string. A popover
71242
+ * something names — these, `aria-label`/`aria-labelledby`, a
71243
+ * `<Popover.Title>` — is a `dialog`, unless given another `role`: a name on
71244
+ * an element with no role is read by nobody.
70894
71245
  * @param {import("ignore:preact").ComponentChildren} props.children
70895
71246
  */
70896
71247
  const Popover = props => {
70897
- import.meta.css = [css$D, "@jsenv/navi/src/layout/popover.jsx"];
71248
+ import.meta.css = [css$B, "@jsenv/navi/src/layout/popover.jsx"];
70898
71249
  if (props.openController) {
70899
71250
  return jsx(ControlledPopover, {
70900
71251
  ...props
@@ -71131,6 +71482,9 @@ const usePopoverProps = props => {
71131
71482
  onOpen,
71132
71483
  children: childrenProp,
71133
71484
  mount,
71485
+ // Read by usePopupName below, never written on the element.
71486
+ openerAriaLabel,
71487
+ openerAriaLabelledBy,
71134
71488
  ...rest
71135
71489
  } = props;
71136
71490
  // Assigned on every render, like openEffect below, so it always closes over
@@ -71141,7 +71495,16 @@ const usePopoverProps = props => {
71141
71495
  mount,
71142
71496
  anchor
71143
71497
  });
71144
- const children = contentMounted ? childrenProp : null;
71498
+ const [nameProps, registerTitle] = usePopupName({
71499
+ ariaLabel: rest["aria-label"],
71500
+ ariaLabelledBy: rest["aria-labelledby"],
71501
+ openerAriaLabel,
71502
+ openerAriaLabelledBy
71503
+ });
71504
+ const children = contentMounted ? jsx(PopupTitleContext.Provider, {
71505
+ value: registerTitle,
71506
+ children: childrenProp
71507
+ }) : null;
71145
71508
  const isTopLayer = layer === "top";
71146
71509
  const ref = props.ref;
71147
71510
  const backdropRef = useRef();
@@ -71946,6 +72309,10 @@ const usePopoverProps = props => {
71946
72309
  "navi-hidden": openController.openedInDom ? undefined : "",
71947
72310
  "styleCSSVars": POPUP_STYLE_CSS_VARS,
71948
72311
  ...rest,
72312
+ ...nameProps,
72313
+ // A <div> has no role, and a name on an element without one is read by
72314
+ // nobody: a popover something names is a dialog, unless told what else.
72315
+ "role": rest.role ?? (nameProps["aria-labelledby"] === undefined && nameProps["aria-label"] === undefined ? undefined : "dialog"),
71949
72316
  ...autoFocusProps,
71950
72317
  ref,
71951
72318
  // Not a control, but still something the rest of navi has to be able to
@@ -72061,9 +72428,10 @@ const resolvePositionAreaAndAnimationKind = ({
72061
72428
  };
72062
72429
  };
72063
72430
  Popover.Close = PopupClose;
72431
+ Popover.Title = PopupTitle;
72064
72432
 
72065
72433
  installImportMetaCssBuild(import.meta);
72066
- const css$C = /* css */`@layer navi {
72434
+ const css$A = /* css */`@layer navi {
72067
72435
  .navi_popup {
72068
72436
  --popup-border-radius: var(--navi-popup-border-radius);
72069
72437
  --popup-border-width: 1px;
@@ -72204,6 +72572,10 @@ const css$C = /* css */`@layer navi {
72204
72572
  * @param {string} [props.className] - Merged with the shared
72205
72573
  * `"navi_popup"` class (see this file's own CSS) rather than replacing
72206
72574
  * it.
72575
+ * @param {string} [props.openerAriaLabelledBy] - Forwarded as-is: the name the
72576
+ * popup takes when neither `aria-label`/`aria-labelledby` nor a
72577
+ * `<Popup.Title>` in `children` names it (see `Dialog`/`Popover`).
72578
+ * @param {string} [props.openerAriaLabel] - Forwarded as-is, same.
72207
72579
  * @param {"always"|"idle"|"from-first-open"|"while-opened"} [props.mount] - When
72208
72580
  * `children` are built and thrown away (see popup_content_mount.js).
72209
72581
  * `"from-first-open"` (the default) builds them on the first open and keeps
@@ -72219,7 +72591,7 @@ const css$C = /* css */`@layer navi {
72219
72591
  * @param {import("ignore:preact").ComponentChildren} props.children
72220
72592
  */
72221
72593
  const Popup = props => {
72222
- import.meta.css = [css$C, "@jsenv/navi/src/layout/popup.jsx"];
72594
+ import.meta.css = [css$A, "@jsenv/navi/src/layout/popup.jsx"];
72223
72595
  const {
72224
72596
  mode: modeProp,
72225
72597
  maxWidth,
@@ -72302,6 +72674,7 @@ const Popup = props => {
72302
72674
  });
72303
72675
  };
72304
72676
  Popup.Close = PopupClose;
72677
+ Popup.Title = PopupTitle;
72305
72678
 
72306
72679
  // What Dialog's own animation values mean to a popover, which has no lift
72307
72680
  // (see the `animation` doc above).
@@ -72350,6 +72723,54 @@ const pickerUIIsNaviOwn = ui => {
72350
72723
  return Boolean(ui) && typeof ui === "object" && Boolean(ui.type?.isPickerOwnUI);
72351
72724
  };
72352
72725
 
72726
+ /*
72727
+ * What the picker really is, said on the element that holds the name and takes
72728
+ * the focus (the host input in picker.jsx), which is both what a screen reader
72729
+ * announces and what getByRole(role, { name }) finds:
72730
+ * - a picker that picks nothing is a door — its popup is a question
72731
+ * (type="confirm") or something to read (mode="callout", which says it for
72732
+ * itself) — and a door is pressed: "button";
72733
+ * - a picker holding a value and opening a popup of navi's is a select-only
72734
+ * combobox: it shows a value the user does not type, and opens what changes
72735
+ * it. A textbox could not say the rest — aria-expanded is not allowed on one,
72736
+ * so "open" and "closed" would never be announced;
72737
+ * - a picker showing the browser's own picker (no popup of navi's) stays the
72738
+ * textbox its <input> is.
72739
+ */
72740
+ const getPickerHostRole = props => {
72741
+ if (props.role !== undefined) {
72742
+ return props.role;
72743
+ }
72744
+ if (props.picksNothing) {
72745
+ return "button";
72746
+ }
72747
+ if (props["aria-haspopup"] !== undefined) {
72748
+ return "combobox";
72749
+ }
72750
+ return undefined;
72751
+ };
72752
+
72753
+ /*
72754
+ * And what that button is called, when the caller wrote it as a drawing rather
72755
+ * than as a word: a door draws no value, so what sits in its value slot IS its
72756
+ * label — the `ui` a caller gave it, or its placeholder. Undefined the moment
72757
+ * the caller names it themselves (aria-label/aria-labelledby), which is the
72758
+ * only way to name a door drawn as an icon. Read by the popup too: what the
72759
+ * user pressed is a fair name for what it opened.
72760
+ */
72761
+ const getPickerHostLabelId = (id, props) => {
72762
+ if (getPickerHostRole(props) !== "button") {
72763
+ return undefined;
72764
+ }
72765
+ if (props["aria-label"] !== undefined || props["aria-labelledby"] !== undefined) {
72766
+ return undefined;
72767
+ }
72768
+ if (props.variant === "headless" || props.ui === "default") {
72769
+ return undefined;
72770
+ }
72771
+ return `${id}_picker_label`;
72772
+ };
72773
+
72353
72774
  /**
72354
72775
  * What the picker holds, read from inside its `ui` — for a drawing given as
72355
72776
  * an element (`ui={<MyCard game={game} />}`), which the picker cannot hand
@@ -72379,7 +72800,7 @@ const usePickerState = () => {
72379
72800
  };
72380
72801
 
72381
72802
  installImportMetaCssBuild(import.meta);
72382
- const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
72803
+ const css$z = /* css */`.navi_picker_callout_dock > [data-picker-content] {
72383
72804
  display: none;
72384
72805
  }
72385
72806
 
@@ -72408,7 +72829,7 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
72408
72829
  }
72409
72830
  }
72410
72831
 
72411
- .navi_picker[aria-haspopup="listbox"][aria-expanded="true"][navi-popover-mode="overlay"] {
72832
+ .navi_picker[navi-popover-mode="overlay"][aria-expanded="true"] {
72412
72833
  border-color: #0000;
72413
72834
  }
72414
72835
 
@@ -72436,7 +72857,7 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
72436
72857
  }
72437
72858
  `;
72438
72859
  const PickerCustomResolver = props => {
72439
- import.meta.css = [css$B, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
72860
+ import.meta.css = [css$z, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
72440
72861
  if (props.children === undefined) {
72441
72862
  return renderResolver(PickerNative, props);
72442
72863
  }
@@ -73245,6 +73666,24 @@ const isPressDisputedByHold = pickerEl => Boolean(pickerEl?.parentElement?.close
73245
73666
  const getPickerInput = pickerEl => {
73246
73667
  return pickerEl.querySelector(".navi_picker_input");
73247
73668
  };
73669
+
73670
+ // The <label>s naming a picker's trigger, by id. Read off the document: it is
73671
+ // the one place a label[for] and its control meet (navi's own Label carries an
73672
+ // id for this). A label wrapping the picker is left out — it holds the trigger
73673
+ // and the popup with it, and the popup would be named after its own content.
73674
+ const getTriggerLabelIds = pickerEl => {
73675
+ const labels = pickerEl && getPickerInput(pickerEl)?.labels;
73676
+ if (!labels) {
73677
+ return undefined;
73678
+ }
73679
+ const ids = [];
73680
+ for (const label of labels) {
73681
+ if (label.id && !label.contains(pickerEl)) {
73682
+ ids.push(label.id);
73683
+ }
73684
+ }
73685
+ return ids.length === 0 ? undefined : ids.join(" ");
73686
+ };
73248
73687
  const getPickerInputUIState = pickerEl => {
73249
73688
  const pickerInput = getPickerInput(pickerEl);
73250
73689
  return getUIStateFromElement(pickerInput);
@@ -73267,6 +73706,11 @@ const PickerContentInsidePopup = props => {
73267
73706
  // Same reason: a `data-testid` on the picker names the trigger (see
73268
73707
  // docs/testid.md) — this one names the popup.
73269
73708
  popupTestId,
73709
+ // Same again for the accessible name: aria-label/aria-labelledby on the
73710
+ // picker name the trigger (see control_context.js), these the popup.
73711
+ popupAriaLabel,
73712
+ popupAriaLabelledBy,
73713
+ popupAriaDescribedBy,
73270
73714
  positionArea,
73271
73715
  popoverMode = "nearby",
73272
73716
  popoverSpacing = popoverMode === "nearby" ? 5 : 0,
@@ -73328,8 +73772,25 @@ const PickerContentInsidePopup = props => {
73328
73772
  } = props;
73329
73773
  const isPopover = mode === "popover";
73330
73774
  const isCallout = mode === "callout";
73331
- return jsx(Next, {
73332
- "aria-haspopup": isPopover ? "listbox" : "dialog",
73775
+ // The trigger's name, in the order the browser reads it, for the popup to
73776
+ // take when neither the caller nor a <Popup.Title> names it: "Report this
73777
+ // book" is a fair name for the sheet it opens, "Start date" for a calendar.
73778
+ let openerAriaLabelledBy = rest["aria-labelledby"] ?? getPickerHostLabelId(props.id, props);
73779
+ let openerAriaLabel;
73780
+ if (openerAriaLabelledBy === undefined) {
73781
+ openerAriaLabel = rest["aria-label"];
73782
+ // Read only while open: the labels are a question for the document, and
73783
+ // a closed popup has nobody to announce it.
73784
+ if (openerAriaLabel === undefined && rest["aria-expanded"]) {
73785
+ openerAriaLabelledBy = getTriggerLabelIds(props.ref.current);
73786
+ }
73787
+ }
73788
+ return jsx(Next
73789
+ // A dialog in either mode: what a picker opens is content of the
73790
+ // caller's, never a listbox (a selectable List is a group of radios or
73791
+ // checkboxes). The popover says so with its role, below.
73792
+ , {
73793
+ "aria-haspopup": "dialog",
73333
73794
  "navi-popover-mode": isPopover ? popoverMode : undefined,
73334
73795
  ...rest,
73335
73796
  // On popupProps already (see the picker's popup assembly); they mean
@@ -73374,11 +73835,23 @@ const PickerContentInsidePopup = props => {
73374
73835
  pressOutside: pressOutside,
73375
73836
  children: jsx(PopupModeContext.Provider, {
73376
73837
  value: mode,
73377
- children: children
73838
+ children: jsx(PopupTitleContext.Provider, {
73839
+ value: null,
73840
+ children: children
73841
+ })
73378
73842
  })
73379
73843
  }) : jsx(Popup, {
73380
73844
  ...popupProps,
73381
73845
  "data-testid": popupTestId
73846
+ // What the trigger's aria-haspopup announces; a <dialog> says it
73847
+ // for itself.
73848
+ ,
73849
+ role: isPopover ? "dialog" : undefined,
73850
+ "aria-label": popupAriaLabel,
73851
+ "aria-labelledby": popupAriaLabelledBy,
73852
+ "aria-describedby": popupAriaDescribedBy,
73853
+ openerAriaLabel: openerAriaLabel,
73854
+ openerAriaLabelledBy: openerAriaLabelledBy
73382
73855
  // What the CSS above keys on: this picker's own popup, and no other
73383
73856
  // popup below the picker.
73384
73857
  ,
@@ -73648,7 +74121,7 @@ const showNativePicker = (pickerInput, event) => {
73648
74121
  };
73649
74122
 
73650
74123
  installImportMetaCssBuild(import.meta);
73651
- const css$A = /* css */`.navi_picker_confirm_body {
74124
+ const css$y = /* css */`.navi_picker_confirm_body {
73652
74125
  min-width: 180px;
73653
74126
  max-width: 320px;
73654
74127
  padding: var(--navi-m);
@@ -73664,7 +74137,7 @@ const css$A = /* css */`.navi_picker_confirm_body {
73664
74137
  }
73665
74138
  `;
73666
74139
  const PickerConfirmResolver = props => {
73667
- import.meta.css = [css$A, "@jsenv/navi/src/control/picker/picker_confirm.jsx"];
74140
+ import.meta.css = [css$y, "@jsenv/navi/src/control/picker/picker_confirm.jsx"];
73668
74141
  const Next = useNextResolver();
73669
74142
  if (props.type !== "confirm") {
73670
74143
  return jsx(Next, {
@@ -74190,7 +74663,7 @@ const LoadingIndicator = ({
74190
74663
  };
74191
74664
 
74192
74665
  installImportMetaCssBuild(import.meta);
74193
- const css$z = /* css */`@layer navi {
74666
+ const css$x = /* css */`@layer navi {
74194
74667
  .navi_separator {
74195
74668
  --size: 1px;
74196
74669
  --color: var(--navi-separator-color-default);
@@ -74264,7 +74737,7 @@ const Separator = ({
74264
74737
  style,
74265
74738
  ...props
74266
74739
  }) => {
74267
- import.meta.css = [css$z, "@jsenv/navi/src/layout/separator.jsx"];
74740
+ import.meta.css = [css$x, "@jsenv/navi/src/layout/separator.jsx"];
74268
74741
  return jsx(Box, {
74269
74742
  as: vertical ? "span" : "hr",
74270
74743
  ...props,
@@ -75058,7 +75531,7 @@ const listItemBlockedMessage = (loading, props) => {
75058
75531
  };
75059
75532
 
75060
75533
  installImportMetaCssBuild(import.meta);
75061
- const css$y = /* css */`@layer navi {
75534
+ const css$w = /* css */`@layer navi {
75062
75535
  .navi_list_container[navi-selectable] {
75063
75536
  --list-item-outline-width: var(--navi-focus-outline-width);
75064
75537
  --list-item-outline-offset: calc(-1 * var(--list-item-outline-width));
@@ -75231,7 +75704,7 @@ const ListSelectableResolver = props => {
75231
75704
  };
75232
75705
  const ListSelectable = props => {
75233
75706
  const Next = useNextResolver();
75234
- import.meta.css = [css$y, "@jsenv/navi/src/control/list/list_selectable.jsx"];
75707
+ import.meta.css = [css$w, "@jsenv/navi/src/control/list/list_selectable.jsx"];
75235
75708
  // we allow ourselves to auto-generate a name
75236
75709
  const defaultName = useId();
75237
75710
  props.name = props.name || `listbox_${defaultName}`;
@@ -75900,7 +76373,7 @@ const ListRunItemContext = createContext(null);
75900
76373
  // item takes its place in the collection by slot: the place is then the list's
75901
76374
  // to move, and the item's to follow — see list_items.js.
75902
76375
  const ListSlotContext = createContext(null);
75903
- const css$x = /* css */`@property --list-group-label-height {
76376
+ const css$v = /* css */`@property --list-group-label-height {
75904
76377
  syntax: "<length>";
75905
76378
  inherits: false;
75906
76379
  initial-value: 0;
@@ -76428,7 +76901,7 @@ const resolveRenderBudget = renderBudget => {
76428
76901
  };
76429
76902
  };
76430
76903
  const ListUI = props => {
76431
- import.meta.css = [css$x, "@jsenv/navi/src/control/list/list.jsx"];
76904
+ import.meta.css = [css$v, "@jsenv/navi/src/control/list/list.jsx"];
76432
76905
  const {
76433
76906
  ref,
76434
76907
  renderBudget: renderBudgetProp,
@@ -82543,7 +83016,7 @@ const createBadgeRegistry = () => {
82543
83016
  };
82544
83017
 
82545
83018
  installImportMetaCssBuild(import.meta);
82546
- const css$w = /* css */`@layer navi;
83019
+ const css$u = /* css */`@layer navi;
82547
83020
 
82548
83021
  .navi_badge {
82549
83022
  --font-size: .7em;
@@ -82646,7 +83119,7 @@ const BadgeUI = ({
82646
83119
  className,
82647
83120
  ...props
82648
83121
  }) => {
82649
- import.meta.css = [css$w, "@jsenv/navi/src/text/badge.jsx"];
83122
+ import.meta.css = [css$u, "@jsenv/navi/src/text/badge.jsx"];
82650
83123
  const defaultRef = useRef();
82651
83124
  props.ref = props.ref || defaultRef;
82652
83125
  const {
@@ -82754,7 +83227,7 @@ const wrapLabels = childArray => {
82754
83227
  };
82755
83228
 
82756
83229
  installImportMetaCssBuild(import.meta);
82757
- const css$v = /* css */`@layer navi;
83230
+ const css$t = /* css */`@layer navi;
82758
83231
 
82759
83232
  .navi_badge_list {
82760
83233
  flex-wrap: wrap;
@@ -82889,7 +83362,7 @@ const useBadgeRegistry = (children, enabled) => {
82889
83362
  * component grants (a <Picker>, see max_lines_context.js).
82890
83363
  */
82891
83364
  const BadgeList = props => {
82892
- import.meta.css = [css$v, "@jsenv/navi/src/text/badge_list.jsx"];
83365
+ import.meta.css = [css$t, "@jsenv/navi/src/text/badge_list.jsx"];
82893
83366
  const {
82894
83367
  maxLines,
82895
83368
  max,
@@ -83233,7 +83706,7 @@ const BadgeListContent = ({
83233
83706
  };
83234
83707
 
83235
83708
  installImportMetaCssBuild(import.meta);
83236
- const css$u = /* css */`.navi_color {
83709
+ const css$s = /* css */`.navi_color {
83237
83710
  aspect-ratio: 1;
83238
83711
  background-color: currentColor;
83239
83712
  border: 1px solid #0003;
@@ -83253,7 +83726,7 @@ const Color = ({
83253
83726
  children,
83254
83727
  ...rest
83255
83728
  }) => {
83256
- import.meta.css = [css$u, "@jsenv/navi/src/text/color.jsx"];
83729
+ import.meta.css = [css$s, "@jsenv/navi/src/text/color.jsx"];
83257
83730
  const color = children || undefined;
83258
83731
  return jsx(Box, {
83259
83732
  as: "span",
@@ -83740,7 +84213,7 @@ const PickerFileUI = /*#__PURE__*/asPickerOwnUI(() => {
83740
84213
  });
83741
84214
 
83742
84215
  installImportMetaCssBuild(import.meta);
83743
- const css$t = /* css */`@layer navi {
84216
+ const css$r = /* css */`@layer navi {
83744
84217
  .navi_picker {
83745
84218
  --picker-border-radius: var(--navi-control-border-radius);
83746
84219
  --picker-border-width: var(--navi-control-border-width);
@@ -84209,7 +84682,7 @@ const css$t = /* css */`@layer navi {
84209
84682
  }
84210
84683
  `;
84211
84684
  const PickerButton = props => {
84212
- import.meta.css = [css$t, "@jsenv/navi/src/control/picker/picker.jsx"];
84685
+ import.meta.css = [css$r, "@jsenv/navi/src/control/picker/picker.jsx"];
84213
84686
  if (typeof props.maxLines === "string") {
84214
84687
  props.maxLines = parseInt(props.maxLines);
84215
84688
  }
@@ -84250,11 +84723,6 @@ const PickerButton = props => {
84250
84723
  // confirm resolver (see picker_confirm.jsx), which is the picker that is
84251
84724
  // one — a caller whose popup is a menu of actions says it for themselves.
84252
84725
  picksNothing,
84253
- // Where the caller's own role goes: onto the host, beside the name (see
84254
- // control_context.js) — a role and a name on two different elements name
84255
- // nothing at all. Left unsaid, a picker that picks nothing takes "button"
84256
- // (see PickerInput below).
84257
- role,
84258
84726
  readOnly,
84259
84727
  error,
84260
84728
  // Who draws the loading outline. Left unsaid, the picker does, around its
@@ -84335,21 +84803,8 @@ const PickerButton = props => {
84335
84803
  // clearing being a modification like any other.
84336
84804
  const interactive = !basePseudoState[":disabled"] && !basePseudoState[":read-only"] && !loading;
84337
84805
  usePickerErrorCallout(uiStateController, error);
84338
- // What the picker really is, said on the element that holds the name and
84339
- // takes the focus (the host input below): a picker that picks nothing is a
84340
- // door — its popup is a question (type="confirm") or something to read
84341
- // (mode="callout", which says it for itself) — and a door is pressed. So it
84342
- // answers to "button" rather than to the "textbox" its <input> would
84343
- // otherwise be, which is both what a screen reader announces and what
84344
- // getByRole("button", { name }) finds. A picker holding a value stays a
84345
- // textbox: it does hold text.
84346
- const hostRole = role ?? (picksNothing ? "button" : undefined);
84347
- // And what that button is called, when the caller wrote it as a drawing
84348
- // rather than as a word: a door draws no value, so what sits in its value
84349
- // slot IS its label — the `ui` a caller gave it, or its placeholder. Skipped
84350
- // the moment the caller names it themselves (aria-label/aria-labelledby),
84351
- // which is the only way to name a door drawn as an icon.
84352
- const hostLabelId = hostRole === "button" && inputProps["aria-label"] === undefined && inputProps["aria-labelledby"] === undefined && variant !== "headless" && ui !== "default" ? `${inputProps.id}_picker_label` : undefined;
84806
+ const hostRole = getPickerHostRole(props);
84807
+ const hostLabelId = getPickerHostLabelId(inputProps.id, props);
84353
84808
  // What the picker knows about itself, for the pieces it does not place: the
84354
84809
  // drawings of its value (Picker.UI.*), and the affordances a caller may put
84355
84810
  // in their own `ui` (Picker.Clear) as much as the ones it puts in its slot.
@@ -84418,8 +84873,16 @@ const PickerButton = props => {
84418
84873
  basePseudoState: basePseudoState,
84419
84874
  styleCSSVars: PickerStyleCSSVars,
84420
84875
  variant: undefined,
84421
- icon: undefined,
84876
+ icon: undefined
84877
+ // The caller's own role goes onto the host, beside the name (see
84878
+ // control_context.js): a role and a name on two different elements
84879
+ // name nothing at all. What the trigger opens is said there too, and
84880
+ // aria-haspopup with it. aria-expanded stays here as well: commands.js
84881
+ // and the CSS read the open state off the root (see
84882
+ // expandableRepeatedBy in commands.js).
84883
+ ,
84422
84884
  role: undefined,
84885
+ "aria-haspopup": undefined,
84423
84886
  rightSlotIcon: undefined,
84424
84887
  rightSlotIconSize: undefined,
84425
84888
  rightSlot: undefined,
@@ -84459,6 +84922,8 @@ const PickerButton = props => {
84459
84922
  inset: loadingOutlineInset
84460
84923
  }), jsx(PickerInput, {
84461
84924
  role: hostRole,
84925
+ "aria-haspopup": props["aria-haspopup"],
84926
+ "aria-expanded": props["aria-expanded"],
84462
84927
  "aria-labelledby": hostLabelId,
84463
84928
  tabIndex: variant === "headless" ? -1 : undefined,
84464
84929
  "aria-hidden": variant === "headless" ? "true" : undefined,
@@ -84984,7 +85449,7 @@ const Picker = /*#__PURE__*/Object.assign(PickerResolved, {
84984
85449
  });
84985
85450
 
84986
85451
  installImportMetaCssBuild(import.meta);
84987
- const css$s = /* css */`@layer navi {
85452
+ const css$q = /* css */`@layer navi {
84988
85453
  .navi_picker_spin {
84989
85454
  --picker-spin-padding-x-default: var(--navi-picker-padding-x-default);
84990
85455
  --picker-spin-padding-y-default: var(--navi-picker-padding-y-default);
@@ -85334,7 +85799,7 @@ const Spin = ({
85334
85799
  nextLabel,
85335
85800
  ...rest
85336
85801
  }) => {
85337
- import.meta.css = [css$s, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
85802
+ import.meta.css = [css$q, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
85338
85803
  const id = useId();
85339
85804
  // What the group around it says, when there is one: how big the whole thing
85340
85805
  // is written is said once, on the group, and every spin in it follows.
@@ -85708,7 +86173,42 @@ const Spin = ({
85708
86173
  variant: "discrete",
85709
86174
  outlineWidth: group ? undefined : "0",
85710
86175
  textAlign: "center",
85711
- expandX: true,
86176
+ expandX: true
86177
+ // The arrow keys are the chevrons at the keyboard: one step the way
86178
+ // the spin steps (`valueAtStep`, round at the ends when it loops),
86179
+ // refused at a bound in the chevron's words. The field's own
86180
+ // stepping (input_resolver_mode) stands down on a prevented keydown.
86181
+ ,
86182
+ onKeyDown: e => {
86183
+ controlProps?.onKeyDown?.(e);
86184
+ if (e.defaultPrevented) {
86185
+ return;
86186
+ }
86187
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown") {
86188
+ return;
86189
+ }
86190
+ // Up is towards the bigger values however the spin lies: what a
86191
+ // field means by it, and what ▲ means on a NumberSpin.
86192
+ const isNext = e.key === "ArrowUp";
86193
+ if (!(isNext ? nextAllowed : previousAllowed)) {
86194
+ e.preventDefault();
86195
+ openCallout(boundMessage(isNext), {
86196
+ anchorElement: middleRef.current,
86197
+ status: "info",
86198
+ openingEvent: e
86199
+ });
86200
+ return;
86201
+ }
86202
+ dispatchRequestInteraction(e.currentTarget, {
86203
+ event: e,
86204
+ name: "--navi-arrow-up-down",
86205
+ prevented: () => e.preventDefault(),
86206
+ allowed: () => {
86207
+ setValue(isNext ? valueNext : valuePrevious, e);
86208
+ e.preventDefault();
86209
+ }
86210
+ });
86211
+ },
85712
86212
  uiAction: (valueNext, event) => {
85713
86213
  uiAction?.(valueNext, stepEventRef.current ?? event);
85714
86214
  }
@@ -86010,7 +86510,7 @@ const renderValueDefault = value => String(value ?? "");
86010
86510
  * are passed on to the spins sitting in them.
86011
86511
  */
86012
86512
  const SpinGroup = props => {
86013
- import.meta.css = [css$s, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
86513
+ import.meta.css = [css$q, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
86014
86514
  const {
86015
86515
  size
86016
86516
  } = props;
@@ -86519,7 +87019,7 @@ const TimeRangeSpin = ({
86519
87019
  };
86520
87020
 
86521
87021
  installImportMetaCssBuild(import.meta);
86522
- const css$r = /* css */`.navi_checkbox_group {
87022
+ const css$p = /* css */`.navi_checkbox_group {
86523
87023
  border-style: solid;
86524
87024
 
86525
87025
  &[data-callout] {
@@ -86579,7 +87079,7 @@ const CheckboxGroup = props => {
86579
87079
  return checkboxGroup;
86580
87080
  };
86581
87081
  const CheckboxGroupInterface = props => {
86582
- import.meta.css = [css$r, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
87082
+ import.meta.css = [css$p, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
86583
87083
  const {
86584
87084
  ref
86585
87085
  } = props;
@@ -86697,7 +87197,7 @@ const formatIntlUnit = (unit, {
86697
87197
  };
86698
87198
 
86699
87199
  installImportMetaCssBuild(import.meta);
86700
- const css$q = /* css */`.navi_input_duration {
87200
+ const css$o = /* css */`.navi_input_duration {
86701
87201
  --duration-separator-spacing: 4px;
86702
87202
  --loader-color: var(--navi-loader-color);
86703
87203
  position: relative;
@@ -86762,7 +87262,7 @@ const css$q = /* css */`.navi_input_duration {
86762
87262
  * "auto" aligns each field toward its neighbouring separator (first→right, last→left, middle/solo→center).
86763
87263
  */
86764
87264
  const InputDuration = props => {
86765
- import.meta.css = [css$q, "@jsenv/navi/src/control/input/input_duration.jsx"];
87265
+ import.meta.css = [css$o, "@jsenv/navi/src/control/input/input_duration.jsx"];
86766
87266
  const defaultRef = useRef();
86767
87267
  props.ref = props.ref || defaultRef;
86768
87268
  props.max = props.max || "23h59";
@@ -87263,7 +87763,7 @@ const InputDurationPart = ({
87263
87763
  };
87264
87764
 
87265
87765
  installImportMetaCssBuild(import.meta);
87266
- const css$p = /* css */`.navi_radio_group {
87766
+ const css$n = /* css */`.navi_radio_group {
87267
87767
  border-style: solid;
87268
87768
 
87269
87769
  &[data-callout] {
@@ -87303,7 +87803,7 @@ const RadioGroup = props => {
87303
87803
  return radioGroup;
87304
87804
  };
87305
87805
  const RadioGroupInterface = props => {
87306
- import.meta.css = [css$p, "@jsenv/navi/src/control/input/radio_group.jsx"];
87806
+ import.meta.css = [css$n, "@jsenv/navi/src/control/input/radio_group.jsx"];
87307
87807
  const {
87308
87808
  ref
87309
87809
  } = props;
@@ -87333,7 +87833,7 @@ const RadioGroupInterface = props => {
87333
87833
  };
87334
87834
 
87335
87835
  installImportMetaCssBuild(import.meta);
87336
- const css$o = /* css */`.navi_input.navi_select {
87836
+ const css$m = /* css */`.navi_input.navi_select {
87337
87837
  & .navi_control_input {
87338
87838
  padding-right: calc(var(--x-padding-right) + 1em);
87339
87839
  line-height: var(--navi-control-line-height);
@@ -87380,7 +87880,7 @@ const Select = ({
87380
87880
  ...props
87381
87881
  }) => {
87382
87882
  installInputCss();
87383
- import.meta.css = [css$o, "@jsenv/navi/src/control/input/select.jsx"];
87883
+ import.meta.css = [css$m, "@jsenv/navi/src/control/input/select.jsx"];
87384
87884
  const defaultRef = useRef(null);
87385
87885
  props.ref = props.ref || defaultRef;
87386
87886
  seedDefaultValueFromSignal(props);
@@ -87430,7 +87930,7 @@ const Select = ({
87430
87930
  };
87431
87931
 
87432
87932
  installImportMetaCssBuild(import.meta);
87433
- const css$n = /* css */`.navi_split_button {
87933
+ const css$l = /* css */`.navi_split_button {
87434
87934
  border-radius: var(--navi-control-border-radius);
87435
87935
  display: inline-flex;
87436
87936
  position: relative;
@@ -87552,7 +88052,7 @@ const css$n = /* css */`.navi_split_button {
87552
88052
  * on the split button's own box.
87553
88053
  */
87554
88054
  const SplitButton = props => {
87555
- import.meta.css = [css$n, "@jsenv/navi/src/control/input/split_button.jsx"];
88055
+ import.meta.css = [css$l, "@jsenv/navi/src/control/input/split_button.jsx"];
87556
88056
  const {
87557
88057
  options = [],
87558
88058
  value,
@@ -88571,7 +89071,7 @@ const createItemTracker = (onChange) => {
88571
89071
  };
88572
89072
 
88573
89073
  installImportMetaCssBuild(import.meta);
88574
- const css$m = /* css */`.navi_wheel_container {
89074
+ const css$k = /* css */`.navi_wheel_container {
88575
89075
  --wheel-item-height: round(1.8em, 1px);
88576
89076
  --wheel-emphasis-size: round(1.5em, 1px);
88577
89077
  --wheel-item-width: 3.5ch;
@@ -89519,7 +90019,7 @@ const useWheelKeyboard = ({
89519
90019
  }, [isHorizontal, interactive, isLoop]);
89520
90020
  };
89521
90021
  function WheelUI(props) {
89522
- import.meta.css = [css$m, "@jsenv/navi/src/control/wheel/wheel.jsx"];
90022
+ import.meta.css = [css$k, "@jsenv/navi/src/control/wheel/wheel.jsx"];
89523
90023
  const {
89524
90024
  ref,
89525
90025
  visibleCount = 3,
@@ -90509,7 +91009,7 @@ Wheel.Item = WheelItem;
90509
91009
  * @param {boolean|number} [props.zoom] - Enlarge the centered value of every wheel (see Wheel's zoom prop) with one prop for the whole group.
90510
91010
  */
90511
91011
  const WheelGroup = props => {
90512
- import.meta.css = [css$m, "@jsenv/navi/src/control/wheel/wheel.jsx"];
91012
+ import.meta.css = [css$k, "@jsenv/navi/src/control/wheel/wheel.jsx"];
90513
91013
  // WheelGroup IS a control group: it aggregates its named wheels ("hours",
90514
91014
  // "minutes"…) into one object value, so it can sit directly inside a Form or a
90515
91015
  // Picker with no extra <ControlGroup> wrapper. The wheel-specific presentation
@@ -90682,7 +91182,7 @@ const WheelColon = props => {
90682
91182
  Wheel.Colon = WheelColon;
90683
91183
 
90684
91184
  installImportMetaCssBuild(import.meta);
90685
- const css$l = /* css */`.navi_time_range_label {
91185
+ const css$j = /* css */`.navi_time_range_label {
90686
91186
  --wheel-item-height: round(1.8em, 1px);
90687
91187
  color: var(--wheel-color, light-dark(#111, #eee));
90688
91188
  line-height: var(--wheel-item-height);
@@ -90906,7 +91406,7 @@ const TimeRangeWheel = ({
90906
91406
  endTimeProps,
90907
91407
  ...rest
90908
91408
  }) => {
90909
- import.meta.css = [css$l, "@jsenv/navi/src/control/wheel/wheel_time.jsx"];
91409
+ import.meta.css = [css$j, "@jsenv/navi/src/control/wheel/wheel_time.jsx"];
90910
91410
  const startId = useId();
90911
91411
  const startRef = useRef(null);
90912
91412
  const endRef = useRef(null);
@@ -91494,7 +91994,7 @@ const Z_INDEX_DROP_PREVIEW = Z_INDEX_STICKY_CORNER + 1;
91494
91994
  const Z_INDEX_TABLE_UI = Z_INDEX_STICKY_CORNER + 1;
91495
91995
 
91496
91996
  installImportMetaCssBuild(import.meta);
91497
- const css$k = /* css */`.navi_table_drag_clone_container {
91997
+ const css$i = /* css */`.navi_table_drag_clone_container {
91498
91998
  top: var(--table-visual-top);
91499
91999
  left: var(--table-visual-left);
91500
92000
  width: var(--table-visual-width);
@@ -91649,7 +92149,7 @@ const useTableDragContextValue = ({
91649
92149
  }, [grabTarget, canChangeColumnOrder]);
91650
92150
  };
91651
92151
  const TableDragCloneContainer = /*#__PURE__*/forwardRef((props, ref) => {
91652
- import.meta.css = [css$k, "@jsenv/navi/src/control/table/drag/table_drag.jsx"];
92152
+ import.meta.css = [css$i, "@jsenv/navi/src/control/table/drag/table_drag.jsx"];
91653
92153
  const {
91654
92154
  tableId
91655
92155
  } = props;
@@ -91948,7 +92448,7 @@ const ROW_MIN_HEIGHT = 30;
91948
92448
  const ROW_MAX_HEIGHT = 100;
91949
92449
  const COLUMN_MIN_WIDTH = 50;
91950
92450
  const COLUMN_MAX_WIDTH = 500;
91951
- const css$j = /* css */`@layer navi {
92451
+ const css$h = /* css */`@layer navi {
91952
92452
  .navi_table {
91953
92453
  --table-resizer-handle-color: #063b7c;
91954
92454
  --table-resizer-color: #387ec9;
@@ -92117,7 +92617,7 @@ const css$j = /* css */`@layer navi {
92117
92617
 
92118
92618
  // Column resize components
92119
92619
  const TableColumnResizer = props => {
92120
- import.meta.css = [css$j, "@jsenv/navi/src/control/table/resize/table_resize.jsx"];
92620
+ import.meta.css = [css$h, "@jsenv/navi/src/control/table/resize/table_resize.jsx"];
92121
92621
  const defaultRef = useRef();
92122
92622
  const ref = props.ref || defaultRef;
92123
92623
  return jsxs("div", {
@@ -92589,7 +93089,7 @@ const findPreviousTableRow = currentRow => {
92589
93089
  };
92590
93090
 
92591
93091
  installImportMetaCssBuild(import.meta);
92592
- const css$i = /* css */`@layer navi {
93092
+ const css$g = /* css */`@layer navi {
92593
93093
  .navi_table {
92594
93094
  --selection-border-color: var(--navi-selection-border-color, #0078d4);
92595
93095
  --selection-background-color: var(--navi-selection-background-color, #eaf1fd);
@@ -92683,7 +93183,7 @@ const useTableSelectionController = ({
92683
93183
  onSelectionChange,
92684
93184
  selectionColor
92685
93185
  }) => {
92686
- import.meta.css = [css$i, "@jsenv/navi/src/control/table/selection/table_selection.jsx"];
93186
+ import.meta.css = [css$g, "@jsenv/navi/src/control/table/selection/table_selection.jsx"];
92687
93187
  const selectionController = useSelectionController({
92688
93188
  elementRef: tableRef,
92689
93189
  layout: "grid",
@@ -93154,7 +93654,7 @@ const useTableStickyContextValue = ({
93154
93654
  };
93155
93655
 
93156
93656
  installImportMetaCssBuild(import.meta);
93157
- const css$h = /* css */`@layer navi {
93657
+ const css$f = /* css */`@layer navi {
93158
93658
  .navi_table {
93159
93659
  --sticky-frontier-color: silver;
93160
93660
  --sticky-frontier-size: 12px;
@@ -93327,7 +93827,7 @@ const css$h = /* css */`@layer navi {
93327
93827
  const TableStickyFrontier = ({
93328
93828
  tableRef
93329
93829
  }) => {
93330
- import.meta.css = [css$h, "@jsenv/navi/src/control/table/sticky/table_sticky.jsx"];
93830
+ import.meta.css = [css$f, "@jsenv/navi/src/control/table/sticky/table_sticky.jsx"];
93331
93831
  const stickyLeftFrontierGhostRef = useRef();
93332
93832
  const stickyLeftFrontierPreviewRef = useRef();
93333
93833
  const stickyTopFrontierGhostRef = useRef();
@@ -93558,7 +94058,7 @@ installImportMetaCssBuild(import.meta);
93558
94058
  * inset 0 -1px 0 0 color; // Bottom border
93559
94059
  */
93560
94060
 
93561
- const css$g = /* css */ `.navi_table_root {
94061
+ const css$e = /* css */ `.navi_table_root {
93562
94062
  max-width: var(--table-max-width, none);
93563
94063
  max-height: var(--table-max-height, none);
93564
94064
  position: relative;
@@ -93743,11 +94243,11 @@ const css$g = /* css */ `.navi_table_root {
93743
94243
  // table must not carry this sheet, and a build that sees no caller drops the
93744
94244
  // css with the function.
93745
94245
  const installTableCss = () => {
93746
- import.meta.css = [css$g, "@jsenv/navi/src/control/table/table_css.js"];
94246
+ import.meta.css = [css$e, "@jsenv/navi/src/control/table/table_css.js"];
93747
94247
  };
93748
94248
 
93749
94249
  installImportMetaCssBuild(import.meta);
93750
- const css$f = /* css */`.navi_table_ui {
94250
+ const css$d = /* css */`.navi_table_ui {
93751
94251
  z-index: ${Z_INDEX_TABLE_UI};
93752
94252
  pointer-events: none;
93753
94253
  position: fixed;
@@ -93756,7 +94256,7 @@ const css$f = /* css */`.navi_table_ui {
93756
94256
  }
93757
94257
  `;
93758
94258
  const TableUI = /*#__PURE__*/forwardRef((props, ref) => {
93759
- import.meta.css = [css$f, "@jsenv/navi/src/control/table/table_ui.jsx"];
94259
+ import.meta.css = [css$d, "@jsenv/navi/src/control/table/table_ui.jsx"];
93760
94260
  const {
93761
94261
  tableRef,
93762
94262
  tableId,
@@ -94678,7 +95178,7 @@ const normalizeKey = (key) => {
94678
95178
  };
94679
95179
 
94680
95180
  installImportMetaCssBuild(import.meta);
94681
- const css$e = /* css */`.navi_shortcut_container[data-visually-hidden], .navi_shortcut_button[data-visually-hidden] {
95181
+ const css$c = /* css */`.navi_shortcut_container[data-visually-hidden], .navi_shortcut_button[data-visually-hidden] {
94682
95182
  clip: rect(0, 0, 0, 0);
94683
95183
  white-space: nowrap;
94684
95184
  opacity: 0;
@@ -94695,7 +95195,7 @@ const css$e = /* css */`.navi_shortcut_container[data-visually-hidden], .navi_sh
94695
95195
  const ActiveKeyboardShortcuts = ({
94696
95196
  visible
94697
95197
  }) => {
94698
- import.meta.css = [css$e, "@jsenv/navi/src/keyboard/active_keyboard_shortcuts.jsx"];
95198
+ import.meta.css = [css$c, "@jsenv/navi/src/keyboard/active_keyboard_shortcuts.jsx"];
94699
95199
  const activeShortcuts = activeShortcutsSignal.value;
94700
95200
  return jsx("div", {
94701
95201
  className: "navi_shortcut_container",
@@ -94735,7 +95235,7 @@ const KeyboardShortcutAriaElement = ({
94735
95235
  };
94736
95236
 
94737
95237
  installImportMetaCssBuild(import.meta);
94738
- const css$d = /* css */`@layer navi {
95238
+ const css$b = /* css */`@layer navi {
94739
95239
  .navi_clipboard_container {
94740
95240
  --height: 1.5em;
94741
95241
  --notif-spacing: .5em;
@@ -94766,7 +95266,7 @@ const ButtonCopyToClipboard = ({
94766
95266
  children,
94767
95267
  ...props
94768
95268
  }) => {
94769
- import.meta.css = [css$d, "@jsenv/navi/src/control/input/button_copy_to_clipboard.jsx"];
95269
+ import.meta.css = [css$b, "@jsenv/navi/src/control/input/button_copy_to_clipboard.jsx"];
94770
95270
  const [copied, setCopied] = useState(false);
94771
95271
  const renderedRef = useRef();
94772
95272
  useEffect(() => {
@@ -94846,7 +95346,7 @@ const Address = ({
94846
95346
  };
94847
95347
 
94848
95348
  installImportMetaCssBuild(import.meta);
94849
- const css$c = /* css */`@layer navi;
95349
+ const css$a = /* css */`@layer navi;
94850
95350
 
94851
95351
  .navi_text.navi_badge_count {
94852
95352
  white-space: nowrap;
@@ -94975,7 +95475,7 @@ const BadgeCount = ({
94975
95475
  lineLayout,
94976
95476
  ...props
94977
95477
  }) => {
94978
- import.meta.css = [css$c, "@jsenv/navi/src/text/badge_count.jsx"];
95478
+ import.meta.css = [css$a, "@jsenv/navi/src/text/badge_count.jsx"];
94979
95479
  const defaultRef = useRef();
94980
95480
  props.ref = props.ref || defaultRef;
94981
95481
  const {
@@ -95118,7 +95618,7 @@ const BadgeCountCircle = ({
95118
95618
  };
95119
95619
 
95120
95620
  installImportMetaCssBuild(import.meta);
95121
- const css$b = /* css */`@layer navi {
95621
+ const css$9 = /* css */`@layer navi {
95122
95622
  .navi_caption {
95123
95623
  --color: #6b7280;
95124
95624
  }
@@ -95138,7 +95638,7 @@ const Caption = ({
95138
95638
  className,
95139
95639
  ...rest
95140
95640
  }) => {
95141
- import.meta.css = [css$b, "@jsenv/navi/src/text/caption.jsx"];
95641
+ import.meta.css = [css$9, "@jsenv/navi/src/text/caption.jsx"];
95142
95642
  return jsx(Text, {
95143
95643
  as: "small",
95144
95644
  size: "0.8em" // We use em to be relative to the parent (we want to be smaller than the surrounding text)
@@ -95534,177 +96034,8 @@ const Interpolate = ({
95534
96034
  });
95535
96035
  };
95536
96036
 
95537
- const SuccessSvg = () => {
95538
- return jsx("svg", {
95539
- viewBox: "0 0 16 16",
95540
- fill: "currentColor",
95541
- children: jsx("path", {
95542
- d: "M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm1.5 0a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm10.28-1.72-4.5 4.5a.75.75 0 0 1-1.06 0l-2-2a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018l1.47 1.47 3.97-3.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"
95543
- })
95544
- });
95545
- };
95546
- const ErrorSvg = () => {
95547
- return jsx("svg", {
95548
- viewBox: "0 0 16 16",
95549
- fill: "currentColor",
95550
- children: jsx("path", {
95551
- d: "M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"
95552
- })
95553
- });
95554
- };
95555
- const InfoSvg$1 = () => {
95556
- return jsx("svg", {
95557
- viewBox: "0 0 16 16",
95558
- fill: "currentColor",
95559
- children: jsx("path", {
95560
- d: "M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"
95561
- })
95562
- });
95563
- };
95564
- const WarningSvg = () => {
95565
- return jsx("svg", {
95566
- viewBox: "0 0 16 16",
95567
- fill: "currentColor",
95568
- children: jsx("path", {
95569
- d: "M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
95570
- })
95571
- });
95572
- };
95573
-
95574
96037
  installImportMetaCssBuild(import.meta);
95575
- const css$a = /* css */`@layer navi {
95576
- .navi_message_box {
95577
- --background-color-info: var(--navi-info-color-light);
95578
- --color-info: var(--navi-info-color);
95579
- --background-color-success: var(--navi-success-color-light);
95580
- --color-success: var(--navi-success-color);
95581
- --background-color-warning: var(--navi-warning-color-light);
95582
- --color-warning: var(--navi-warning-color);
95583
- --background-color-error: var(--navi-error-color-light);
95584
- --color-error: var(--navi-error-color);
95585
- }
95586
- }
95587
-
95588
- .navi_message_box {
95589
- --x-message-background-color: var(--background-color-info);
95590
- --x-message-color: var(--color-info);
95591
- background-color: var(--x-message-background-color);
95592
-
95593
- & > .navi_icon {
95594
- flex-shrink: 0;
95595
- }
95596
- }
95597
-
95598
- .navi_message_box[data-status-info] {
95599
- --x-message-background-color: var(--background-color-info);
95600
- --x-message-color: var(--color-info);
95601
- }
95602
-
95603
- .navi_message_box[data-status-success] {
95604
- --x-message-background-color: var(--background-color-success);
95605
- --x-message-color: var(--color-success);
95606
- }
95607
-
95608
- .navi_message_box[data-status-warning] {
95609
- --x-message-background-color: var(--background-color-warning);
95610
- --x-message-color: var(--color-warning);
95611
- }
95612
-
95613
- .navi_message_box[data-status-error] {
95614
- --x-message-background-color: var(--background-color-error);
95615
- --x-message-color: var(--color-error);
95616
- }
95617
-
95618
- .navi_message_box[data-left-stripe] {
95619
- border-left: 6px solid var(--x-message-color);
95620
- border-top-left-radius: 6px;
95621
- border-bottom-left-radius: 6px;
95622
- }
95623
- `;
95624
- const MessageBox = ({
95625
- status = "info",
95626
- padding = "sm",
95627
- icon,
95628
- leftStripe,
95629
- children,
95630
- onClose,
95631
- ...rest
95632
- }) => {
95633
- import.meta.css = [css$a, "@jsenv/navi/src/text/message_box.jsx"];
95634
- const [hasTitleChild, setHasTitleChild] = useState(false);
95635
- const innerLeftStripe = leftStripe === undefined ? hasTitleChild : leftStripe;
95636
- if (icon === true) {
95637
- icon = status === "info" ? jsx(InfoSvg$1, {}) : status === "success" ? jsx(SuccessSvg, {}) : status === "warning" ? jsx(WarningSvg, {}) : status === "error" ? jsx(ErrorSvg, {}) : null;
95638
- } else if (typeof icon === "function") {
95639
- const Comp = icon;
95640
- icon = jsx(Comp, {});
95641
- }
95642
- return jsx(Box, {
95643
- as: "div",
95644
- role: status === "info" ? "status" : "alert",
95645
- "data-left-stripe": innerLeftStripe ? "" : undefined,
95646
- inline: true,
95647
- column: true,
95648
- alignY: "center",
95649
- spacing: "s",
95650
- ...rest,
95651
- className: withPropsClassName("navi_message_box", rest.className),
95652
- padding: padding,
95653
- pseudoClasses: MessageBoxPseudoClasses,
95654
- basePseudoState: {
95655
- ":-navi-status-info": status === "info",
95656
- ":-navi-status-success": status === "success",
95657
- ":-navi-status-warning": status === "warning",
95658
- ":-navi-status-error": status === "error"
95659
- },
95660
- children: jsx(MessageBoxStatusContext.Provider, {
95661
- value: status,
95662
- children: jsxs(MessageBoxReportTitleChildContext.Provider, {
95663
- value: setHasTitleChild,
95664
- children: [icon && jsx(Icon, {
95665
- color: "var(--x-message-color)",
95666
- height: "1lh",
95667
- maxHeight: "auto",
95668
- selfAlignY: "start",
95669
- aspectRatio: "auto",
95670
- children: icon
95671
- }), jsx(Text, {
95672
- children: children
95673
- }), onClose &&
95674
- // A column as tall as the first line of the message, pinned to the
95675
- // top, the button centered in it: the close button stays level
95676
- // with the first line however many lines the message takes (same
95677
- // layout as Callout).
95678
- jsx(Box, {
95679
- flex: true,
95680
- alignY: "center",
95681
- height: "1lh",
95682
- selfAlignY: "start",
95683
- shrink: false,
95684
- children: jsx(Button, {
95685
- action: onClose,
95686
- icon: true,
95687
- border: "none",
95688
- style: {
95689
- ":hover": {
95690
- backgroundColor: "rgba(0, 0, 0, 0.1)"
95691
- }
95692
- },
95693
- children: jsx(Icon, {
95694
- children: jsx(CloseSvg, {})
95695
- })
95696
- })
95697
- })]
95698
- })
95699
- })
95700
- });
95701
- };
95702
- const MessageBoxPseudoClasses = [":-navi-status-info", ":-navi-status-success", ":-navi-status-warning", ":-navi-status-error"];
95703
- const MessageBoxStatusContext = createContext();
95704
- const MessageBoxReportTitleChildContext = createContext();
95705
-
95706
- installImportMetaCssBuild(import.meta);
95707
- const css$9 = /* css */`@layer navi;
96038
+ const css$8 = /* css */`@layer navi;
95708
96039
 
95709
96040
  .navi_quantity {
95710
96041
  flex-direction: column;
@@ -95805,7 +96136,7 @@ const Quantity = ({
95805
96136
  bold = true,
95806
96137
  ...props
95807
96138
  }) => {
95808
- import.meta.css = [css$9, "@jsenv/navi/src/text/quantity.jsx"];
96139
+ import.meta.css = [css$8, "@jsenv/navi/src/text/quantity.jsx"];
95809
96140
  const value = parseQuantityValue(children);
95810
96141
  const valueRounded = integer && typeof value === "number" ? Math.round(value) : value;
95811
96142
  const valueFormatted = typeof valueRounded === "number" ? formatNumber(valueRounded, {
@@ -95864,7 +96195,7 @@ const parseQuantityValue = children => {
95864
96195
  };
95865
96196
 
95866
96197
  installImportMetaCssBuild(import.meta);
95867
- const css$8 = /* css */`@layer navi {
96198
+ const css$7 = /* css */`@layer navi {
95868
96199
  .navi_meter {
95869
96200
  --loader-color: var(--navi-loader-color);
95870
96201
  --track-color: #efefef;
@@ -96003,7 +96334,7 @@ const Meter = ({
96003
96334
  style,
96004
96335
  ...rest
96005
96336
  }) => {
96006
- import.meta.css = [css$8, "@jsenv/navi/src/text/meter.jsx"];
96337
+ import.meta.css = [css$7, "@jsenv/navi/src/text/meter.jsx"];
96007
96338
  const defaultRef = useRef();
96008
96339
  const ref = rest.ref || defaultRef;
96009
96340
  value = Number(value);
@@ -96118,7 +96449,7 @@ const Paragraph = props => {
96118
96449
  };
96119
96450
 
96120
96451
  installImportMetaCssBuild(import.meta);
96121
- const css$7 = /* css */`.navi_text_box {
96452
+ const css$6 = /* css */`.navi_text_box {
96122
96453
  align-items: flex-start;
96123
96454
  min-width: 0;
96124
96455
  }
@@ -96160,7 +96491,7 @@ const TextBox = ({
96160
96491
  children,
96161
96492
  ...rest
96162
96493
  }) => {
96163
- import.meta.css = [css$7, "@jsenv/navi/src/text/text_box.jsx"];
96494
+ import.meta.css = [css$6, "@jsenv/navi/src/text/text_box.jsx"];
96164
96495
  const boxRef = useRef(null);
96165
96496
  const contentRef = useRef(null);
96166
96497
  useLayoutEffect(() => {
@@ -96206,40 +96537,6 @@ const adjustWidth = (boxEl, contentEl) => {
96206
96537
  contentEl.style.width = `${Math.ceil(optimalWidth)}px`;
96207
96538
  };
96208
96539
 
96209
- installImportMetaCssBuild(import.meta);
96210
- const css$6 = /* css */`.navi_message_box {
96211
- & .navi_title {
96212
- margin-top: 0;
96213
- margin-bottom: var(--navi-s);
96214
- color: var(--x-message-color);
96215
- }
96216
- }
96217
- `;
96218
- const Title = props => {
96219
- import.meta.css = [css$6, "@jsenv/navi/src/text/title.jsx"];
96220
- const messageBoxStatus = useContext(MessageBoxStatusContext);
96221
- const innerAs = props.as || (messageBoxStatus ? "h4" : "h1");
96222
- const titleLevel = parseInt(innerAs.slice(1));
96223
- const reportTitleToMessageBox = useContext(MessageBoxReportTitleChildContext);
96224
- reportTitleToMessageBox?.(true);
96225
- return jsx(TitleLevelContext.Provider, {
96226
- value: titleLevel,
96227
- children: jsx(Text, {
96228
- bold: true,
96229
- className: withPropsClassName("navi_title"),
96230
- as: messageBoxStatus ? "h4" : "h1",
96231
- ...props,
96232
- pseudoClasses: TitlePseudoClasses,
96233
- children: props.children
96234
- })
96235
- });
96236
- };
96237
- const TitleLevelContext = createContext();
96238
- const useTitleLevel = () => {
96239
- return useContext(TitleLevelContext);
96240
- };
96241
- const TitlePseudoClasses = [":hover"];
96242
-
96243
96540
  installImportMetaCssBuild(import.meta);
96244
96541
  const css$5 = /* css */`@keyframes navi_image_shimmer {
96245
96542
  0% {