@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.
@@ -40048,8 +40048,29 @@ const resolveFirstChildControl = (el) => {
40048
40048
  }
40049
40049
  return startEl.querySelector("[navi-control-host]");
40050
40050
  };
40051
+ // The element a popup command is about: the closest one keeping an open state.
40051
40052
  const resolveClosestExpandable = (el) => {
40052
- return el.closest("[aria-expanded]");
40053
+ const expandable = el.closest("[aria-expanded]");
40054
+ if (!expandable) {
40055
+ return null;
40056
+ }
40057
+ return expandableRepeatedBy(expandable) || expandable;
40058
+ };
40059
+ // A control's host repeats the aria-expanded of its root, beside its role, for
40060
+ // assistive tech to read (a picker's <input>, a combobox or a button). The root
40061
+ // is the expandable: the host is a leaf the walk lands on only when it is the
40062
+ // source itself, and it carries the control's own `command`/`commandfor`, which
40063
+ // a command resolved again from it (a close after a clear) would follow.
40064
+ const expandableRepeatedBy = (el) => {
40065
+ const controlRoot = findControlRoot(el);
40066
+ if (
40067
+ controlRoot &&
40068
+ controlRoot !== el &&
40069
+ controlRoot.hasAttribute("aria-expanded")
40070
+ ) {
40071
+ return controlRoot;
40072
+ }
40073
+ return null;
40053
40074
  };
40054
40075
  // The surface a send is decided on: the expandable holding the CONTROL that
40055
40076
  // sends, never one that control is itself. A picker's own root carries
@@ -40060,7 +40081,7 @@ const resolveClosestExpandable = (el) => {
40060
40081
  const resolveExpandableAround = (source) => {
40061
40082
  const sender = findControlRoot(source) || source;
40062
40083
  const parent = sender.parentElement;
40063
- return parent ? parent.closest("[aria-expanded]") : null;
40084
+ return parent ? resolveClosestExpandable(parent) : null;
40064
40085
  };
40065
40086
  // An element carrying navi-command-proxy-for="anchorId" stands in for that
40066
40087
  // other element as the command's source when the source is forwarded to
@@ -40377,8 +40398,12 @@ const sendCommand = (source, event, { requester }) => {
40377
40398
  // looking at, and a decision just taken there is a reason to leave it. The
40378
40399
  // NEAREST surface answers, and only that one — a form inside a slide inside a
40379
40400
  // dialog goes back a slide, it does not also close the dialog.
40380
- // send inside expandable
40381
- if (target.getAttribute("aria-expanded") === "true") {
40401
+ // send inside expandable — the control itself when it sends from its host,
40402
+ // whose aria-expanded is its root's (see expandableRepeatedBy)
40403
+ if (
40404
+ target.getAttribute("aria-expanded") === "true" &&
40405
+ !expandableRepeatedBy(target)
40406
+ ) {
40382
40407
  return {
40383
40408
  target,
40384
40409
  implementation: () => {
@@ -40813,7 +40838,7 @@ const requestCloseUpward = (target, detail) => {
40813
40838
  return false;
40814
40839
  }
40815
40840
  const parent = expandable.parentElement;
40816
- expandable = parent ? parent.closest("[aria-expanded]") : null;
40841
+ expandable = parent ? resolveClosestExpandable(parent) : null;
40817
40842
  }
40818
40843
  return true;
40819
40844
  };
@@ -47811,8 +47836,10 @@ const createControlInfo = (props, {
47811
47836
  } else if (controlType === "details") {
47812
47837
  statePropName = "open";
47813
47838
  defaultStatePropName = "defaultOpen";
47814
- stateInitial = props.open || props.defaultOpen;
47815
47839
  value = props.value || "open";
47840
+ // Held like a checkbox: the value while open, nothing while closed — the
47841
+ // shape the toggle writes back (see details.jsx).
47842
+ stateInitial = props.open || props.defaultOpen ? value : undefined;
47816
47843
  } else if (controlType === "picker" || controlType === "select") {
47817
47844
  statePropName = "value";
47818
47845
  defaultStatePropName = "defaultValue";
@@ -55877,6 +55904,9 @@ const DetailsField = props => {
55877
55904
  value,
55878
55905
  children
55879
55906
  } = detailsProps;
55907
+ const {
55908
+ uiStateController
55909
+ } = controlChildrenWrapperProps;
55880
55910
  const open = Boolean(value);
55881
55911
  useFocusGroup(ref, {
55882
55912
  enabled: focusGroup,
@@ -55958,6 +55988,10 @@ const DetailsField = props => {
55958
55988
  return;
55959
55989
  }
55960
55990
  const details = ref.current;
55991
+ // The browser has already toggled the element (a summary press, the
55992
+ // arrow shortcuts): the state follows it whatever the gate says, or the
55993
+ // marker and whoever reads the value keep the old one.
55994
+ uiStateController.setUIState(details.open ? uiStateController.value : undefined, e);
55961
55995
  dispatchRequestInteraction(details, {
55962
55996
  event: e,
55963
55997
  wantAction: true,
@@ -59395,6 +59429,10 @@ const Label = props => {
59395
59429
  requiredIndicator
59396
59430
  } = props;
59397
59431
  const controlId = useContext(ControlIdContext);
59432
+ // What a popup opened from the control is named by (see getTriggerLabelIds
59433
+ // in picker_custom.jsx): aria-labelledby can only point at an id.
59434
+ const idDefault = useId();
59435
+ props.id = props.id || `label_${idDefault}`;
59398
59436
  const [disabled, setDisabled] = useState(false);
59399
59437
  const [readOnly, setReadOnly] = useState(false);
59400
59438
  const [required, setRequired] = useState(false);
@@ -69173,9 +69211,297 @@ const PopupClose = ({
69173
69211
  });
69174
69212
  };
69175
69213
 
69214
+ const SuccessSvg = () => {
69215
+ return jsx("svg", {
69216
+ viewBox: "0 0 16 16",
69217
+ fill: "currentColor",
69218
+ children: jsx("path", {
69219
+ 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"
69220
+ })
69221
+ });
69222
+ };
69223
+ const ErrorSvg = () => {
69224
+ return jsx("svg", {
69225
+ viewBox: "0 0 16 16",
69226
+ fill: "currentColor",
69227
+ children: jsx("path", {
69228
+ 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"
69229
+ })
69230
+ });
69231
+ };
69232
+ const InfoSvg$1 = () => {
69233
+ return jsx("svg", {
69234
+ viewBox: "0 0 16 16",
69235
+ fill: "currentColor",
69236
+ children: jsx("path", {
69237
+ 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"
69238
+ })
69239
+ });
69240
+ };
69241
+ const WarningSvg = () => {
69242
+ return jsx("svg", {
69243
+ viewBox: "0 0 16 16",
69244
+ fill: "currentColor",
69245
+ children: jsx("path", {
69246
+ 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"
69247
+ })
69248
+ });
69249
+ };
69250
+
69251
+ installImportMetaCssBuild(import.meta);
69252
+ const css$E = /* css */`@layer navi {
69253
+ .navi_message_box {
69254
+ --background-color-info: var(--navi-info-color-light);
69255
+ --color-info: var(--navi-info-color);
69256
+ --background-color-success: var(--navi-success-color-light);
69257
+ --color-success: var(--navi-success-color);
69258
+ --background-color-warning: var(--navi-warning-color-light);
69259
+ --color-warning: var(--navi-warning-color);
69260
+ --background-color-error: var(--navi-error-color-light);
69261
+ --color-error: var(--navi-error-color);
69262
+ }
69263
+ }
69264
+
69265
+ .navi_message_box {
69266
+ --x-message-background-color: var(--background-color-info);
69267
+ --x-message-color: var(--color-info);
69268
+ background-color: var(--x-message-background-color);
69269
+
69270
+ & > .navi_icon {
69271
+ flex-shrink: 0;
69272
+ }
69273
+ }
69274
+
69275
+ .navi_message_box[data-status-info] {
69276
+ --x-message-background-color: var(--background-color-info);
69277
+ --x-message-color: var(--color-info);
69278
+ }
69279
+
69280
+ .navi_message_box[data-status-success] {
69281
+ --x-message-background-color: var(--background-color-success);
69282
+ --x-message-color: var(--color-success);
69283
+ }
69284
+
69285
+ .navi_message_box[data-status-warning] {
69286
+ --x-message-background-color: var(--background-color-warning);
69287
+ --x-message-color: var(--color-warning);
69288
+ }
69289
+
69290
+ .navi_message_box[data-status-error] {
69291
+ --x-message-background-color: var(--background-color-error);
69292
+ --x-message-color: var(--color-error);
69293
+ }
69294
+
69295
+ .navi_message_box[data-left-stripe] {
69296
+ border-left: 6px solid var(--x-message-color);
69297
+ border-top-left-radius: 6px;
69298
+ border-bottom-left-radius: 6px;
69299
+ }
69300
+ `;
69301
+ const MessageBox = ({
69302
+ status = "info",
69303
+ padding = "sm",
69304
+ icon,
69305
+ leftStripe,
69306
+ children,
69307
+ onClose,
69308
+ ...rest
69309
+ }) => {
69310
+ import.meta.css = [css$E, "@jsenv/navi/src/text/message_box.jsx"];
69311
+ const [hasTitleChild, setHasTitleChild] = useState(false);
69312
+ const innerLeftStripe = leftStripe === undefined ? hasTitleChild : leftStripe;
69313
+ if (icon === true) {
69314
+ icon = status === "info" ? jsx(InfoSvg$1, {}) : status === "success" ? jsx(SuccessSvg, {}) : status === "warning" ? jsx(WarningSvg, {}) : status === "error" ? jsx(ErrorSvg, {}) : null;
69315
+ } else if (typeof icon === "function") {
69316
+ const Comp = icon;
69317
+ icon = jsx(Comp, {});
69318
+ }
69319
+ return jsx(Box, {
69320
+ as: "div",
69321
+ role: status === "info" ? "status" : "alert",
69322
+ "data-left-stripe": innerLeftStripe ? "" : undefined,
69323
+ inline: true,
69324
+ column: true,
69325
+ alignY: "center",
69326
+ spacing: "s",
69327
+ ...rest,
69328
+ className: withPropsClassName("navi_message_box", rest.className),
69329
+ padding: padding,
69330
+ pseudoClasses: MessageBoxPseudoClasses,
69331
+ basePseudoState: {
69332
+ ":-navi-status-info": status === "info",
69333
+ ":-navi-status-success": status === "success",
69334
+ ":-navi-status-warning": status === "warning",
69335
+ ":-navi-status-error": status === "error"
69336
+ },
69337
+ children: jsx(MessageBoxStatusContext.Provider, {
69338
+ value: status,
69339
+ children: jsxs(MessageBoxReportTitleChildContext.Provider, {
69340
+ value: setHasTitleChild,
69341
+ children: [icon && jsx(Icon, {
69342
+ color: "var(--x-message-color)",
69343
+ height: "1lh",
69344
+ maxHeight: "auto",
69345
+ selfAlignY: "start",
69346
+ aspectRatio: "auto",
69347
+ children: icon
69348
+ }), jsx(Text, {
69349
+ children: children
69350
+ }), onClose &&
69351
+ // A column as tall as the first line of the message, pinned to the
69352
+ // top, the button centered in it: the close button stays level
69353
+ // with the first line however many lines the message takes (same
69354
+ // layout as Callout).
69355
+ jsx(Box, {
69356
+ flex: true,
69357
+ alignY: "center",
69358
+ height: "1lh",
69359
+ selfAlignY: "start",
69360
+ shrink: false,
69361
+ children: jsx(Button, {
69362
+ action: onClose,
69363
+ icon: true,
69364
+ border: "none",
69365
+ style: {
69366
+ ":hover": {
69367
+ backgroundColor: "rgba(0, 0, 0, 0.1)"
69368
+ }
69369
+ },
69370
+ children: jsx(Icon, {
69371
+ children: jsx(CloseSvg, {})
69372
+ })
69373
+ })
69374
+ })]
69375
+ })
69376
+ })
69377
+ });
69378
+ };
69379
+ const MessageBoxPseudoClasses = [":-navi-status-info", ":-navi-status-success", ":-navi-status-warning", ":-navi-status-error"];
69380
+ const MessageBoxStatusContext = createContext();
69381
+ const MessageBoxReportTitleChildContext = createContext();
69382
+
69383
+ installImportMetaCssBuild(import.meta);
69384
+ const css$D = /* css */`.navi_message_box {
69385
+ & .navi_title {
69386
+ margin-top: 0;
69387
+ margin-bottom: var(--navi-s);
69388
+ color: var(--x-message-color);
69389
+ }
69390
+ }
69391
+ `;
69392
+ const Title = props => {
69393
+ import.meta.css = [css$D, "@jsenv/navi/src/text/title.jsx"];
69394
+ const messageBoxStatus = useContext(MessageBoxStatusContext);
69395
+ const innerAs = props.as || (messageBoxStatus ? "h4" : "h1");
69396
+ const titleLevel = parseInt(innerAs.slice(1));
69397
+ const reportTitleToMessageBox = useContext(MessageBoxReportTitleChildContext);
69398
+ reportTitleToMessageBox?.(true);
69399
+ return jsx(TitleLevelContext.Provider, {
69400
+ value: titleLevel,
69401
+ children: jsx(Text, {
69402
+ bold: true,
69403
+ className: withPropsClassName("navi_title"),
69404
+ as: messageBoxStatus ? "h4" : "h1",
69405
+ ...props,
69406
+ pseudoClasses: TitlePseudoClasses,
69407
+ children: props.children
69408
+ })
69409
+ });
69410
+ };
69411
+ const TitleLevelContext = createContext();
69412
+ const useTitleLevel = () => {
69413
+ return useContext(TitleLevelContext);
69414
+ };
69415
+ const TitlePseudoClasses = [":hover"];
69416
+
69417
+ /**
69418
+ * What a popup is called, decided by Dialog and Popover alike: the name the
69419
+ * caller writes on it (aria-label/aria-labelledby), else the title it holds
69420
+ * (`<Popup.Title>`), else the name of what opened it (`openerAriaLabelledBy` /
69421
+ * `openerAriaLabel`, which a Picker fills with its trigger's). A popup no one
69422
+ * names is announced as "dialog" and nothing else.
69423
+ *
69424
+ * The title tells the popup it exists rather than the popup looking for it:
69425
+ * the content is built after the popup and thrown away before it (see
69426
+ * popup_content_mount.js), and a title may sit anywhere in it — a header, a
69427
+ * step of a form — so only the title knows when it is there.
69428
+ */
69429
+
69430
+ const PopupTitleContext = createContext(null);
69431
+
69432
+ /**
69433
+ * The title of the popup it sits in — reached as `<Popup.Title>`,
69434
+ * `<Dialog.Title>` or `<Popover.Title>`. A heading (`h2` unless `as` says
69435
+ * otherwise) that is also the popup's accessible name: a screen reader
69436
+ * announces the popup by it, and `getByRole("dialog", { name })` finds it.
69437
+ * An `aria-label`/`aria-labelledby` written on the popup itself wins over it.
69438
+ *
69439
+ * @type {import("ignore:preact").FunctionComponent<{
69440
+ * as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6",
69441
+ * id?: string,
69442
+ * children?: import("ignore:preact").ComponentChildren,
69443
+ * } & Record<string, any>>}
69444
+ */
69445
+ const PopupTitle = props => {
69446
+ const registerTitle = useContext(PopupTitleContext);
69447
+ const idDefault = useId();
69448
+ const id = props.id || `popup_title_${idDefault}`;
69449
+ useLayoutEffect(() => {
69450
+ if (!registerTitle) {
69451
+ return undefined;
69452
+ }
69453
+ return registerTitle(id);
69454
+ }, [registerTitle, id]);
69455
+ return jsx(Title, {
69456
+ as: "h2",
69457
+ ...props,
69458
+ id: id
69459
+ });
69460
+ };
69461
+
69462
+ /**
69463
+ * The name props of a popup element, and what its titles register with —
69464
+ * handed to its children through PopupTitleContext.
69465
+ */
69466
+ const usePopupName = ({
69467
+ ariaLabel,
69468
+ ariaLabelledBy,
69469
+ openerAriaLabel,
69470
+ openerAriaLabelledBy
69471
+ }) => {
69472
+ // Several titles is a content of the caller's own making (two steps both
69473
+ // mounted): the first one registered names the popup.
69474
+ const [titleIds, setTitleIds] = useState(NO_TITLE);
69475
+ const registerTitle = useMemo(() => {
69476
+ return id => {
69477
+ setTitleIds(ids => [...ids, id]);
69478
+ return () => {
69479
+ setTitleIds(ids => ids.filter(each => each !== id));
69480
+ };
69481
+ };
69482
+ }, []);
69483
+ let labelledBy;
69484
+ let label;
69485
+ if (ariaLabelledBy !== undefined || ariaLabel !== undefined) {
69486
+ labelledBy = ariaLabelledBy;
69487
+ label = ariaLabel;
69488
+ } else if (titleIds.length > 0) {
69489
+ labelledBy = titleIds[0];
69490
+ } else if (openerAriaLabelledBy !== undefined) {
69491
+ labelledBy = openerAriaLabelledBy;
69492
+ } else {
69493
+ label = openerAriaLabel;
69494
+ }
69495
+ return [{
69496
+ "aria-labelledby": labelledBy,
69497
+ "aria-label": label
69498
+ }, registerTitle];
69499
+ };
69500
+ const NO_TITLE = [];
69501
+
69176
69502
  installImportMetaCssBuild(import.meta);
69177
69503
  let openLocalDialogCount = 0;
69178
- const css$E = /* css */`
69504
+ const css$C = /* css */`
69179
69505
  /* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
69180
69506
  order depending on which popup renders first. */
69181
69507
 
@@ -70244,10 +70570,14 @@ const css$E = /* css */`
70244
70570
  * under `"while-opened"`, only a press on what opens the popup does (a
70245
70571
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
70246
70572
  * press that does not open the popup throws it away.
70573
+ * @param {string} [props.openerAriaLabelledBy] - The id(s) naming what opened
70574
+ * the dialog: its accessible name when neither `aria-label`/`aria-labelledby`
70575
+ * nor a `<Dialog.Title>` names it. A Picker passes its trigger's.
70576
+ * @param {string} [props.openerAriaLabel] - The same, as a string.
70247
70577
  * @param {import("ignore:preact").ComponentChildren} props.children
70248
70578
  */
70249
70579
  const Dialog = props => {
70250
- import.meta.css = [css$E, "@jsenv/navi/src/layout/dialog.jsx"];
70580
+ import.meta.css = [css$C, "@jsenv/navi/src/layout/dialog.jsx"];
70251
70581
  if (props.openController) {
70252
70582
  return jsx(ControlledDialog, {
70253
70583
  ...props
@@ -70623,6 +70953,9 @@ const useDialogProps = props => {
70623
70953
  onOpen,
70624
70954
  children: childrenProp,
70625
70955
  mount,
70956
+ // Read by usePopupName below, never written on the element.
70957
+ openerAriaLabel,
70958
+ openerAriaLabelledBy,
70626
70959
  ...rest
70627
70960
  } = props;
70628
70961
  // Assigned on every render, like openEffect below, so it always closes over
@@ -70633,7 +70966,16 @@ const useDialogProps = props => {
70633
70966
  mount,
70634
70967
  anchor
70635
70968
  });
70636
- const children = contentMounted ? childrenProp : null;
70969
+ const [nameProps, registerTitle] = usePopupName({
70970
+ ariaLabel: rest["aria-label"],
70971
+ ariaLabelledBy: rest["aria-labelledby"],
70972
+ openerAriaLabel,
70973
+ openerAriaLabelledBy
70974
+ });
70975
+ const children = contentMounted ? jsx(PopupTitleContext.Provider, {
70976
+ value: registerTitle,
70977
+ children: childrenProp
70978
+ }) : null;
70637
70979
  // Where it is placed, and whether there is a wall — the two questions that
70638
70980
  // pick the show call (see this file's top comment).
70639
70981
  const isTopLayer = layer === "top";
@@ -71483,6 +71825,7 @@ const useDialogProps = props => {
71483
71825
  "backdropFilter": backdrop ? backdropFilter : undefined,
71484
71826
  "styleCSSVars": DIALOG_STYLE_CSS_VARS,
71485
71827
  ...rest,
71828
+ ...nameProps,
71486
71829
  ...autoFocusProps,
71487
71830
  "as": "dialog",
71488
71831
  ref,
@@ -71683,10 +72026,11 @@ const DIALOG_STYLE_CSS_VARS = {
71683
72026
  maxHeight: "--dialog-max-height"
71684
72027
  };
71685
72028
  Dialog.Close = PopupClose;
72029
+ Dialog.Title = PopupTitle;
71686
72030
 
71687
72031
  installImportMetaCssBuild(import.meta);
71688
72032
  let openLocalPopoverCount = 0;
71689
- const css$D = /* css */`
72033
+ const css$B = /* css */`
71690
72034
  /* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
71691
72035
  order depending on which popup renders first. */
71692
72036
 
@@ -72285,10 +72629,17 @@ const css$D = /* css */`
72285
72629
  * under `"while-opened"`, only a press on what opens the popup does (a
72286
72630
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
72287
72631
  * press that does not open the popup throws it away.
72632
+ * @param {string} [props.openerAriaLabelledBy] - The id(s) naming what opened
72633
+ * the popover: its accessible name when neither `aria-label`/`aria-labelledby`
72634
+ * nor a `<Popover.Title>` names it. A Picker passes its trigger's.
72635
+ * @param {string} [props.openerAriaLabel] - The same, as a string. A popover
72636
+ * something names — these, `aria-label`/`aria-labelledby`, a
72637
+ * `<Popover.Title>` — is a `dialog`, unless given another `role`: a name on
72638
+ * an element with no role is read by nobody.
72288
72639
  * @param {import("ignore:preact").ComponentChildren} props.children
72289
72640
  */
72290
72641
  const Popover = props => {
72291
- import.meta.css = [css$D, "@jsenv/navi/src/layout/popover.jsx"];
72642
+ import.meta.css = [css$B, "@jsenv/navi/src/layout/popover.jsx"];
72292
72643
  if (props.openController) {
72293
72644
  return jsx(ControlledPopover, {
72294
72645
  ...props
@@ -72527,6 +72878,9 @@ const usePopoverProps = props => {
72527
72878
  onOpen,
72528
72879
  children: childrenProp,
72529
72880
  mount,
72881
+ // Read by usePopupName below, never written on the element.
72882
+ openerAriaLabel,
72883
+ openerAriaLabelledBy,
72530
72884
  ...rest
72531
72885
  } = props;
72532
72886
  // Assigned on every render, like openEffect below, so it always closes over
@@ -72537,7 +72891,16 @@ const usePopoverProps = props => {
72537
72891
  mount,
72538
72892
  anchor
72539
72893
  });
72540
- const children = contentMounted ? childrenProp : null;
72894
+ const [nameProps, registerTitle] = usePopupName({
72895
+ ariaLabel: rest["aria-label"],
72896
+ ariaLabelledBy: rest["aria-labelledby"],
72897
+ openerAriaLabel,
72898
+ openerAriaLabelledBy
72899
+ });
72900
+ const children = contentMounted ? jsx(PopupTitleContext.Provider, {
72901
+ value: registerTitle,
72902
+ children: childrenProp
72903
+ }) : null;
72541
72904
  const isTopLayer = layer === "top";
72542
72905
  const ref = props.ref;
72543
72906
  const backdropRef = useRef();
@@ -73352,6 +73715,10 @@ const usePopoverProps = props => {
73352
73715
  "navi-hidden": openController.openedInDom ? undefined : "",
73353
73716
  "styleCSSVars": POPUP_STYLE_CSS_VARS,
73354
73717
  ...rest,
73718
+ ...nameProps,
73719
+ // A <div> has no role, and a name on an element without one is read by
73720
+ // nobody: a popover something names is a dialog, unless told what else.
73721
+ "role": rest.role ?? (nameProps["aria-labelledby"] === undefined && nameProps["aria-label"] === undefined ? undefined : "dialog"),
73355
73722
  ...autoFocusProps,
73356
73723
  ref,
73357
73724
  // Not a control, but still something the rest of navi has to be able to
@@ -73467,9 +73834,10 @@ const resolvePositionAreaAndAnimationKind = ({
73467
73834
  };
73468
73835
  };
73469
73836
  Popover.Close = PopupClose;
73837
+ Popover.Title = PopupTitle;
73470
73838
 
73471
73839
  installImportMetaCssBuild(import.meta);
73472
- const css$C = /* css */`@layer navi {
73840
+ const css$A = /* css */`@layer navi {
73473
73841
  .navi_popup {
73474
73842
  --popup-border-radius: var(--navi-popup-border-radius);
73475
73843
  --popup-border-width: 1px;
@@ -73610,6 +73978,10 @@ const css$C = /* css */`@layer navi {
73610
73978
  * @param {string} [props.className] - Merged with the shared
73611
73979
  * `"navi_popup"` class (see this file's own CSS) rather than replacing
73612
73980
  * it.
73981
+ * @param {string} [props.openerAriaLabelledBy] - Forwarded as-is: the name the
73982
+ * popup takes when neither `aria-label`/`aria-labelledby` nor a
73983
+ * `<Popup.Title>` in `children` names it (see `Dialog`/`Popover`).
73984
+ * @param {string} [props.openerAriaLabel] - Forwarded as-is, same.
73613
73985
  * @param {"always"|"idle"|"from-first-open"|"while-opened"} [props.mount] - When
73614
73986
  * `children` are built and thrown away (see popup_content_mount.js).
73615
73987
  * `"from-first-open"` (the default) builds them on the first open and keeps
@@ -73625,7 +73997,7 @@ const css$C = /* css */`@layer navi {
73625
73997
  * @param {import("ignore:preact").ComponentChildren} props.children
73626
73998
  */
73627
73999
  const Popup = props => {
73628
- import.meta.css = [css$C, "@jsenv/navi/src/layout/popup.jsx"];
74000
+ import.meta.css = [css$A, "@jsenv/navi/src/layout/popup.jsx"];
73629
74001
  const {
73630
74002
  mode: modeProp,
73631
74003
  maxWidth,
@@ -73708,6 +74080,7 @@ const Popup = props => {
73708
74080
  });
73709
74081
  };
73710
74082
  Popup.Close = PopupClose;
74083
+ Popup.Title = PopupTitle;
73711
74084
 
73712
74085
  // What Dialog's own animation values mean to a popover, which has no lift
73713
74086
  // (see the `animation` doc above).
@@ -73756,6 +74129,54 @@ const pickerUIIsNaviOwn = ui => {
73756
74129
  return Boolean(ui) && typeof ui === "object" && Boolean(ui.type?.isPickerOwnUI);
73757
74130
  };
73758
74131
 
74132
+ /*
74133
+ * What the picker really is, said on the element that holds the name and takes
74134
+ * the focus (the host input in picker.jsx), which is both what a screen reader
74135
+ * announces and what getByRole(role, { name }) finds:
74136
+ * - a picker that picks nothing is a door — its popup is a question
74137
+ * (type="confirm") or something to read (mode="callout", which says it for
74138
+ * itself) — and a door is pressed: "button";
74139
+ * - a picker holding a value and opening a popup of navi's is a select-only
74140
+ * combobox: it shows a value the user does not type, and opens what changes
74141
+ * it. A textbox could not say the rest — aria-expanded is not allowed on one,
74142
+ * so "open" and "closed" would never be announced;
74143
+ * - a picker showing the browser's own picker (no popup of navi's) stays the
74144
+ * textbox its <input> is.
74145
+ */
74146
+ const getPickerHostRole = props => {
74147
+ if (props.role !== undefined) {
74148
+ return props.role;
74149
+ }
74150
+ if (props.picksNothing) {
74151
+ return "button";
74152
+ }
74153
+ if (props["aria-haspopup"] !== undefined) {
74154
+ return "combobox";
74155
+ }
74156
+ return undefined;
74157
+ };
74158
+
74159
+ /*
74160
+ * And what that button is called, when the caller wrote it as a drawing rather
74161
+ * than as a word: a door draws no value, so what sits in its value slot IS its
74162
+ * label — the `ui` a caller gave it, or its placeholder. Undefined the moment
74163
+ * the caller names it themselves (aria-label/aria-labelledby), which is the
74164
+ * only way to name a door drawn as an icon. Read by the popup too: what the
74165
+ * user pressed is a fair name for what it opened.
74166
+ */
74167
+ const getPickerHostLabelId = (id, props) => {
74168
+ if (getPickerHostRole(props) !== "button") {
74169
+ return undefined;
74170
+ }
74171
+ if (props["aria-label"] !== undefined || props["aria-labelledby"] !== undefined) {
74172
+ return undefined;
74173
+ }
74174
+ if (props.variant === "headless" || props.ui === "default") {
74175
+ return undefined;
74176
+ }
74177
+ return `${id}_picker_label`;
74178
+ };
74179
+
73759
74180
  /**
73760
74181
  * What the picker holds, read from inside its `ui` — for a drawing given as
73761
74182
  * an element (`ui={<MyCard game={game} />}`), which the picker cannot hand
@@ -73785,7 +74206,7 @@ const usePickerState = () => {
73785
74206
  };
73786
74207
 
73787
74208
  installImportMetaCssBuild(import.meta);
73788
- const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
74209
+ const css$z = /* css */`.navi_picker_callout_dock > [data-picker-content] {
73789
74210
  display: none;
73790
74211
  }
73791
74212
 
@@ -73814,7 +74235,7 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
73814
74235
  }
73815
74236
  }
73816
74237
 
73817
- .navi_picker[aria-haspopup="listbox"][aria-expanded="true"][navi-popover-mode="overlay"] {
74238
+ .navi_picker[navi-popover-mode="overlay"][aria-expanded="true"] {
73818
74239
  border-color: #0000;
73819
74240
  }
73820
74241
 
@@ -73842,7 +74263,7 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
73842
74263
  }
73843
74264
  `;
73844
74265
  const PickerCustomResolver = props => {
73845
- import.meta.css = [css$B, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
74266
+ import.meta.css = [css$z, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
73846
74267
  if (props.children === undefined) {
73847
74268
  return renderResolver(PickerNative, props);
73848
74269
  }
@@ -74665,6 +75086,24 @@ const isPressDisputedByHold = pickerEl => Boolean(pickerEl?.parentElement?.close
74665
75086
  const getPickerInput = pickerEl => {
74666
75087
  return pickerEl.querySelector(".navi_picker_input");
74667
75088
  };
75089
+
75090
+ // The <label>s naming a picker's trigger, by id. Read off the document: it is
75091
+ // the one place a label[for] and its control meet (navi's own Label carries an
75092
+ // id for this). A label wrapping the picker is left out — it holds the trigger
75093
+ // and the popup with it, and the popup would be named after its own content.
75094
+ const getTriggerLabelIds = pickerEl => {
75095
+ const labels = pickerEl && getPickerInput(pickerEl)?.labels;
75096
+ if (!labels) {
75097
+ return undefined;
75098
+ }
75099
+ const ids = [];
75100
+ for (const label of labels) {
75101
+ if (label.id && !label.contains(pickerEl)) {
75102
+ ids.push(label.id);
75103
+ }
75104
+ }
75105
+ return ids.length === 0 ? undefined : ids.join(" ");
75106
+ };
74668
75107
  const getPickerInputUIState = pickerEl => {
74669
75108
  const pickerInput = getPickerInput(pickerEl);
74670
75109
  return getUIStateFromElement(pickerInput);
@@ -74687,6 +75126,11 @@ const PickerContentInsidePopup = props => {
74687
75126
  // Same reason: a `data-testid` on the picker names the trigger (see
74688
75127
  // docs/testid.md) — this one names the popup.
74689
75128
  popupTestId,
75129
+ // Same again for the accessible name: aria-label/aria-labelledby on the
75130
+ // picker name the trigger (see control_context.js), these the popup.
75131
+ popupAriaLabel,
75132
+ popupAriaLabelledBy,
75133
+ popupAriaDescribedBy,
74690
75134
  positionArea,
74691
75135
  popoverMode = "nearby",
74692
75136
  popoverSpacing = popoverMode === "nearby" ? 5 : 0,
@@ -74748,8 +75192,25 @@ const PickerContentInsidePopup = props => {
74748
75192
  } = props;
74749
75193
  const isPopover = mode === "popover";
74750
75194
  const isCallout = mode === "callout";
74751
- return jsx(Next, {
74752
- "aria-haspopup": isPopover ? "listbox" : "dialog",
75195
+ // The trigger's name, in the order the browser reads it, for the popup to
75196
+ // take when neither the caller nor a <Popup.Title> names it: "Report this
75197
+ // book" is a fair name for the sheet it opens, "Start date" for a calendar.
75198
+ let openerAriaLabelledBy = rest["aria-labelledby"] ?? getPickerHostLabelId(props.id, props);
75199
+ let openerAriaLabel;
75200
+ if (openerAriaLabelledBy === undefined) {
75201
+ openerAriaLabel = rest["aria-label"];
75202
+ // Read only while open: the labels are a question for the document, and
75203
+ // a closed popup has nobody to announce it.
75204
+ if (openerAriaLabel === undefined && rest["aria-expanded"]) {
75205
+ openerAriaLabelledBy = getTriggerLabelIds(props.ref.current);
75206
+ }
75207
+ }
75208
+ return jsx(Next
75209
+ // A dialog in either mode: what a picker opens is content of the
75210
+ // caller's, never a listbox (a selectable List is a group of radios or
75211
+ // checkboxes). The popover says so with its role, below.
75212
+ , {
75213
+ "aria-haspopup": "dialog",
74753
75214
  "navi-popover-mode": isPopover ? popoverMode : undefined,
74754
75215
  ...rest,
74755
75216
  // On popupProps already (see the picker's popup assembly); they mean
@@ -74794,11 +75255,23 @@ const PickerContentInsidePopup = props => {
74794
75255
  pressOutside: pressOutside,
74795
75256
  children: jsx(PopupModeContext.Provider, {
74796
75257
  value: mode,
74797
- children: children
75258
+ children: jsx(PopupTitleContext.Provider, {
75259
+ value: null,
75260
+ children: children
75261
+ })
74798
75262
  })
74799
75263
  }) : jsx(Popup, {
74800
75264
  ...popupProps,
74801
75265
  "data-testid": popupTestId
75266
+ // What the trigger's aria-haspopup announces; a <dialog> says it
75267
+ // for itself.
75268
+ ,
75269
+ role: isPopover ? "dialog" : undefined,
75270
+ "aria-label": popupAriaLabel,
75271
+ "aria-labelledby": popupAriaLabelledBy,
75272
+ "aria-describedby": popupAriaDescribedBy,
75273
+ openerAriaLabel: openerAriaLabel,
75274
+ openerAriaLabelledBy: openerAriaLabelledBy
74802
75275
  // What the CSS above keys on: this picker's own popup, and no other
74803
75276
  // popup below the picker.
74804
75277
  ,
@@ -75068,7 +75541,7 @@ const showNativePicker = (pickerInput, event) => {
75068
75541
  };
75069
75542
 
75070
75543
  installImportMetaCssBuild(import.meta);
75071
- const css$A = /* css */`.navi_picker_confirm_body {
75544
+ const css$y = /* css */`.navi_picker_confirm_body {
75072
75545
  min-width: 180px;
75073
75546
  max-width: 320px;
75074
75547
  padding: var(--navi-m);
@@ -75084,7 +75557,7 @@ const css$A = /* css */`.navi_picker_confirm_body {
75084
75557
  }
75085
75558
  `;
75086
75559
  const PickerConfirmResolver = props => {
75087
- import.meta.css = [css$A, "@jsenv/navi/src/control/picker/picker_confirm.jsx"];
75560
+ import.meta.css = [css$y, "@jsenv/navi/src/control/picker/picker_confirm.jsx"];
75088
75561
  const Next = useNextResolver();
75089
75562
  if (props.type !== "confirm") {
75090
75563
  return jsx(Next, {
@@ -75610,7 +76083,7 @@ const LoadingIndicator = ({
75610
76083
  };
75611
76084
 
75612
76085
  installImportMetaCssBuild(import.meta);
75613
- const css$z = /* css */`@layer navi {
76086
+ const css$x = /* css */`@layer navi {
75614
76087
  .navi_separator {
75615
76088
  --size: 1px;
75616
76089
  --color: var(--navi-separator-color-default);
@@ -75684,7 +76157,7 @@ const Separator = ({
75684
76157
  style,
75685
76158
  ...props
75686
76159
  }) => {
75687
- import.meta.css = [css$z, "@jsenv/navi/src/layout/separator.jsx"];
76160
+ import.meta.css = [css$x, "@jsenv/navi/src/layout/separator.jsx"];
75688
76161
  return jsx(Box, {
75689
76162
  as: vertical ? "span" : "hr",
75690
76163
  ...props,
@@ -76478,7 +76951,7 @@ const listItemBlockedMessage = (loading, props) => {
76478
76951
  };
76479
76952
 
76480
76953
  installImportMetaCssBuild(import.meta);
76481
- const css$y = /* css */`@layer navi {
76954
+ const css$w = /* css */`@layer navi {
76482
76955
  .navi_list_container[navi-selectable] {
76483
76956
  --list-item-outline-width: var(--navi-focus-outline-width);
76484
76957
  --list-item-outline-offset: calc(-1 * var(--list-item-outline-width));
@@ -76651,7 +77124,7 @@ const ListSelectableResolver = props => {
76651
77124
  };
76652
77125
  const ListSelectable = props => {
76653
77126
  const Next = useNextResolver();
76654
- import.meta.css = [css$y, "@jsenv/navi/src/control/list/list_selectable.jsx"];
77127
+ import.meta.css = [css$w, "@jsenv/navi/src/control/list/list_selectable.jsx"];
76655
77128
  // we allow ourselves to auto-generate a name
76656
77129
  const defaultName = useId();
76657
77130
  props.name = props.name || `listbox_${defaultName}`;
@@ -77320,7 +77793,7 @@ const ListRunItemContext = createContext(null);
77320
77793
  // item takes its place in the collection by slot: the place is then the list's
77321
77794
  // to move, and the item's to follow — see list_items.js.
77322
77795
  const ListSlotContext = createContext(null);
77323
- const css$x = /* css */`@property --list-group-label-height {
77796
+ const css$v = /* css */`@property --list-group-label-height {
77324
77797
  syntax: "<length>";
77325
77798
  inherits: false;
77326
77799
  initial-value: 0;
@@ -77848,7 +78321,7 @@ const resolveRenderBudget = renderBudget => {
77848
78321
  };
77849
78322
  };
77850
78323
  const ListUI = props => {
77851
- import.meta.css = [css$x, "@jsenv/navi/src/control/list/list.jsx"];
78324
+ import.meta.css = [css$v, "@jsenv/navi/src/control/list/list.jsx"];
77852
78325
  const {
77853
78326
  ref,
77854
78327
  renderBudget: renderBudgetProp,
@@ -84138,7 +84611,7 @@ const createBadgeRegistry = () => {
84138
84611
  };
84139
84612
 
84140
84613
  installImportMetaCssBuild(import.meta);
84141
- const css$w = /* css */`@layer navi;
84614
+ const css$u = /* css */`@layer navi;
84142
84615
 
84143
84616
  .navi_badge {
84144
84617
  --font-size: .7em;
@@ -84241,7 +84714,7 @@ const BadgeUI = ({
84241
84714
  className,
84242
84715
  ...props
84243
84716
  }) => {
84244
- import.meta.css = [css$w, "@jsenv/navi/src/text/badge.jsx"];
84717
+ import.meta.css = [css$u, "@jsenv/navi/src/text/badge.jsx"];
84245
84718
  const defaultRef = useRef();
84246
84719
  props.ref = props.ref || defaultRef;
84247
84720
  const {
@@ -84349,7 +84822,7 @@ const wrapLabels = childArray => {
84349
84822
  };
84350
84823
 
84351
84824
  installImportMetaCssBuild(import.meta);
84352
- const css$v = /* css */`@layer navi;
84825
+ const css$t = /* css */`@layer navi;
84353
84826
 
84354
84827
  .navi_badge_list {
84355
84828
  flex-wrap: wrap;
@@ -84484,7 +84957,7 @@ const useBadgeRegistry = (children, enabled) => {
84484
84957
  * component grants (a <Picker>, see max_lines_context.js).
84485
84958
  */
84486
84959
  const BadgeList = props => {
84487
- import.meta.css = [css$v, "@jsenv/navi/src/text/badge_list.jsx"];
84960
+ import.meta.css = [css$t, "@jsenv/navi/src/text/badge_list.jsx"];
84488
84961
  const {
84489
84962
  maxLines,
84490
84963
  max,
@@ -84828,7 +85301,7 @@ const BadgeListContent = ({
84828
85301
  };
84829
85302
 
84830
85303
  installImportMetaCssBuild(import.meta);
84831
- const css$u = /* css */`.navi_color {
85304
+ const css$s = /* css */`.navi_color {
84832
85305
  aspect-ratio: 1;
84833
85306
  background-color: currentColor;
84834
85307
  border: 1px solid #0003;
@@ -84848,7 +85321,7 @@ const Color = ({
84848
85321
  children,
84849
85322
  ...rest
84850
85323
  }) => {
84851
- import.meta.css = [css$u, "@jsenv/navi/src/text/color.jsx"];
85324
+ import.meta.css = [css$s, "@jsenv/navi/src/text/color.jsx"];
84852
85325
  const color = children || undefined;
84853
85326
  return jsx(Box, {
84854
85327
  as: "span",
@@ -85335,7 +85808,7 @@ const PickerFileUI = /*#__PURE__*/asPickerOwnUI(() => {
85335
85808
  });
85336
85809
 
85337
85810
  installImportMetaCssBuild(import.meta);
85338
- const css$t = /* css */`@layer navi {
85811
+ const css$r = /* css */`@layer navi {
85339
85812
  .navi_picker {
85340
85813
  --picker-border-radius: var(--navi-control-border-radius);
85341
85814
  --picker-border-width: var(--navi-control-border-width);
@@ -85804,7 +86277,7 @@ const css$t = /* css */`@layer navi {
85804
86277
  }
85805
86278
  `;
85806
86279
  const PickerButton = props => {
85807
- import.meta.css = [css$t, "@jsenv/navi/src/control/picker/picker.jsx"];
86280
+ import.meta.css = [css$r, "@jsenv/navi/src/control/picker/picker.jsx"];
85808
86281
  warnOnUnknownPickerType(props);
85809
86282
  warnOnUIDrawnByNobody(props);
85810
86283
  if (typeof props.maxLines === "string") {
@@ -85847,11 +86320,6 @@ const PickerButton = props => {
85847
86320
  // confirm resolver (see picker_confirm.jsx), which is the picker that is
85848
86321
  // one — a caller whose popup is a menu of actions says it for themselves.
85849
86322
  picksNothing,
85850
- // Where the caller's own role goes: onto the host, beside the name (see
85851
- // control_context.js) — a role and a name on two different elements name
85852
- // nothing at all. Left unsaid, a picker that picks nothing takes "button"
85853
- // (see PickerInput below).
85854
- role,
85855
86323
  readOnly,
85856
86324
  error,
85857
86325
  // Who draws the loading outline. Left unsaid, the picker does, around its
@@ -85934,21 +86402,8 @@ const PickerButton = props => {
85934
86402
  // clearing being a modification like any other.
85935
86403
  const interactive = !basePseudoState[":disabled"] && !basePseudoState[":read-only"] && !loading;
85936
86404
  usePickerErrorCallout(uiStateController, error);
85937
- // What the picker really is, said on the element that holds the name and
85938
- // takes the focus (the host input below): a picker that picks nothing is a
85939
- // door — its popup is a question (type="confirm") or something to read
85940
- // (mode="callout", which says it for itself) — and a door is pressed. So it
85941
- // answers to "button" rather than to the "textbox" its <input> would
85942
- // otherwise be, which is both what a screen reader announces and what
85943
- // getByRole("button", { name }) finds. A picker holding a value stays a
85944
- // textbox: it does hold text.
85945
- const hostRole = role ?? (picksNothing ? "button" : undefined);
85946
- // And what that button is called, when the caller wrote it as a drawing
85947
- // rather than as a word: a door draws no value, so what sits in its value
85948
- // slot IS its label — the `ui` a caller gave it, or its placeholder. Skipped
85949
- // the moment the caller names it themselves (aria-label/aria-labelledby),
85950
- // which is the only way to name a door drawn as an icon.
85951
- const hostLabelId = hostRole === "button" && inputProps["aria-label"] === undefined && inputProps["aria-labelledby"] === undefined && variant !== "headless" && ui !== "default" ? `${inputProps.id}_picker_label` : undefined;
86405
+ const hostRole = getPickerHostRole(props);
86406
+ const hostLabelId = getPickerHostLabelId(inputProps.id, props);
85952
86407
  // What the picker knows about itself, for the pieces it does not place: the
85953
86408
  // drawings of its value (Picker.UI.*), and the affordances a caller may put
85954
86409
  // in their own `ui` (Picker.Clear) as much as the ones it puts in its slot.
@@ -86017,8 +86472,16 @@ const PickerButton = props => {
86017
86472
  basePseudoState: basePseudoState,
86018
86473
  styleCSSVars: PickerStyleCSSVars,
86019
86474
  variant: undefined,
86020
- icon: undefined,
86475
+ icon: undefined
86476
+ // The caller's own role goes onto the host, beside the name (see
86477
+ // control_context.js): a role and a name on two different elements
86478
+ // name nothing at all. What the trigger opens is said there too, and
86479
+ // aria-haspopup with it. aria-expanded stays here as well: commands.js
86480
+ // and the CSS read the open state off the root (see
86481
+ // expandableRepeatedBy in commands.js).
86482
+ ,
86021
86483
  role: undefined,
86484
+ "aria-haspopup": undefined,
86022
86485
  rightSlotIcon: undefined,
86023
86486
  rightSlotIconSize: undefined,
86024
86487
  rightSlot: undefined,
@@ -86058,6 +86521,8 @@ const PickerButton = props => {
86058
86521
  inset: loadingOutlineInset
86059
86522
  }), jsx(PickerInput, {
86060
86523
  role: hostRole,
86524
+ "aria-haspopup": props["aria-haspopup"],
86525
+ "aria-expanded": props["aria-expanded"],
86061
86526
  "aria-labelledby": hostLabelId,
86062
86527
  tabIndex: variant === "headless" ? -1 : undefined,
86063
86528
  "aria-hidden": variant === "headless" ? "true" : undefined,
@@ -86565,6 +87030,10 @@ const PickerFirstResolver = props => {
86565
87030
  * Without `children`, opens the browser-native picker for the given `type`.
86566
87031
  * With `children`, opens a popover (desktop) or dialog (mobile) containing the children.
86567
87032
  * Pass `mode="popover"` or `mode="dialog"` to override the automatic choice.
87033
+ * Either way the popup is a `dialog`, named by a `<Popup.Title>` in the
87034
+ * children (see `popupAriaLabelledBy`), and the trigger answers to `combobox` —
87035
+ * `button` under `picksNothing` — with `aria-haspopup="dialog"` and
87036
+ * `aria-expanded` (see docs/testid.md).
86568
87037
  *
86569
87038
  * `type="confirm"` is the picker that asks "are you sure?" before doing what it
86570
87039
  * stands for: its popup is the question, `ui` its label, and yes runs its
@@ -86638,6 +87107,9 @@ const PickerFirstResolver = props => {
86638
87107
  * standalone?: boolean,
86639
87108
  * popupLayer?: "top" | "local",
86640
87109
  * popupTestId?: string,
87110
+ * popupAriaLabel?: string,
87111
+ * popupAriaLabelledBy?: string,
87112
+ * popupAriaDescribedBy?: string,
86641
87113
  * confirmTestId?: string,
86642
87114
  * cancelTestId?: string,
86643
87115
  * mount?: "always" | "idle" | "from-first-open" | "while-opened",
@@ -87097,6 +87569,19 @@ const PickerFirstResolver = props => {
87097
87569
  * not only what it holds, which a testid inside the children already names.
87098
87570
  * Under `mode="callout"` it names the callout element — the control's own,
87099
87571
  * shared with its constraint messages — while the children are shown in it.
87572
+ * @param {string} [popupAriaLabelledBy] The id(s) of what names the popup:
87573
+ * `aria-labelledby` on the popup element, which is a dialog in either mode
87574
+ * (the trigger says `aria-haspopup="dialog"`). `aria-label`/`aria-labelledby`
87575
+ * on the picker itself name the trigger. Rarely needed: a `<Popup.Title>`
87576
+ * in the children names the popup with nothing to wire, and without one
87577
+ * the popup takes the trigger's own name — the picker's `aria-labelledby`,
87578
+ * the label a picker that picks nothing draws, its `aria-label`, or the
87579
+ * `<Label>`/`<label for>` pointing at it (one that has an id, and does not
87580
+ * wrap the picker). A `mode="callout"` popup is read as its message.
87581
+ * @param {string} [popupAriaLabel] The same, as a string, for a popup whose
87582
+ * title is not an element.
87583
+ * @param {string} [popupAriaDescribedBy] The id(s) of what describes the
87584
+ * popup: `aria-describedby` on the popup element.
87100
87585
  * @param {string} [confirmTestId] `type="confirm"`, default body: the
87101
87586
  * `data-testid` of the yes button; `cancelTestId` that of the no button.
87102
87587
  * Both are elements navi builds, which a test may not name by class (see
@@ -87234,7 +87719,7 @@ const Picker = /*#__PURE__*/Object.assign(PickerResolved, {
87234
87719
  });
87235
87720
 
87236
87721
  installImportMetaCssBuild(import.meta);
87237
- const css$s = /* css */`@layer navi {
87722
+ const css$q = /* css */`@layer navi {
87238
87723
  .navi_picker_spin {
87239
87724
  --picker-spin-padding-x-default: var(--navi-picker-padding-x-default);
87240
87725
  --picker-spin-padding-y-default: var(--navi-picker-padding-y-default);
@@ -87584,7 +88069,7 @@ const Spin = ({
87584
88069
  nextLabel,
87585
88070
  ...rest
87586
88071
  }) => {
87587
- import.meta.css = [css$s, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
88072
+ import.meta.css = [css$q, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
87588
88073
  const id = useId();
87589
88074
  // What the group around it says, when there is one: how big the whole thing
87590
88075
  // is written is said once, on the group, and every spin in it follows.
@@ -87958,7 +88443,42 @@ const Spin = ({
87958
88443
  variant: "discrete",
87959
88444
  outlineWidth: group ? undefined : "0",
87960
88445
  textAlign: "center",
87961
- expandX: true,
88446
+ expandX: true
88447
+ // The arrow keys are the chevrons at the keyboard: one step the way
88448
+ // the spin steps (`valueAtStep`, round at the ends when it loops),
88449
+ // refused at a bound in the chevron's words. The field's own
88450
+ // stepping (input_resolver_mode) stands down on a prevented keydown.
88451
+ ,
88452
+ onKeyDown: e => {
88453
+ controlProps?.onKeyDown?.(e);
88454
+ if (e.defaultPrevented) {
88455
+ return;
88456
+ }
88457
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown") {
88458
+ return;
88459
+ }
88460
+ // Up is towards the bigger values however the spin lies: what a
88461
+ // field means by it, and what ▲ means on a NumberSpin.
88462
+ const isNext = e.key === "ArrowUp";
88463
+ if (!(isNext ? nextAllowed : previousAllowed)) {
88464
+ e.preventDefault();
88465
+ openCallout(boundMessage(isNext), {
88466
+ anchorElement: middleRef.current,
88467
+ status: "info",
88468
+ openingEvent: e
88469
+ });
88470
+ return;
88471
+ }
88472
+ dispatchRequestInteraction(e.currentTarget, {
88473
+ event: e,
88474
+ name: "--navi-arrow-up-down",
88475
+ prevented: () => e.preventDefault(),
88476
+ allowed: () => {
88477
+ setValue(isNext ? valueNext : valuePrevious, e);
88478
+ e.preventDefault();
88479
+ }
88480
+ });
88481
+ },
87962
88482
  uiAction: (valueNext, event) => {
87963
88483
  uiAction?.(valueNext, stepEventRef.current ?? event);
87964
88484
  }
@@ -88260,7 +88780,7 @@ const renderValueDefault = value => String(value ?? "");
88260
88780
  * are passed on to the spins sitting in them.
88261
88781
  */
88262
88782
  const SpinGroup = props => {
88263
- import.meta.css = [css$s, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
88783
+ import.meta.css = [css$q, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
88264
88784
  const {
88265
88785
  size
88266
88786
  } = props;
@@ -88769,7 +89289,7 @@ const TimeRangeSpin = ({
88769
89289
  };
88770
89290
 
88771
89291
  installImportMetaCssBuild(import.meta);
88772
- const css$r = /* css */`.navi_checkbox_group {
89292
+ const css$p = /* css */`.navi_checkbox_group {
88773
89293
  border-style: solid;
88774
89294
 
88775
89295
  &[data-callout] {
@@ -88829,7 +89349,7 @@ const CheckboxGroup = props => {
88829
89349
  return checkboxGroup;
88830
89350
  };
88831
89351
  const CheckboxGroupInterface = props => {
88832
- import.meta.css = [css$r, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
89352
+ import.meta.css = [css$p, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
88833
89353
  const {
88834
89354
  ref
88835
89355
  } = props;
@@ -88947,7 +89467,7 @@ const formatIntlUnit = (unit, {
88947
89467
  };
88948
89468
 
88949
89469
  installImportMetaCssBuild(import.meta);
88950
- const css$q = /* css */`.navi_input_duration {
89470
+ const css$o = /* css */`.navi_input_duration {
88951
89471
  --duration-separator-spacing: 4px;
88952
89472
  --loader-color: var(--navi-loader-color);
88953
89473
  position: relative;
@@ -89012,7 +89532,7 @@ const css$q = /* css */`.navi_input_duration {
89012
89532
  * "auto" aligns each field toward its neighbouring separator (first→right, last→left, middle/solo→center).
89013
89533
  */
89014
89534
  const InputDuration = props => {
89015
- import.meta.css = [css$q, "@jsenv/navi/src/control/input/input_duration.jsx"];
89535
+ import.meta.css = [css$o, "@jsenv/navi/src/control/input/input_duration.jsx"];
89016
89536
  const defaultRef = useRef();
89017
89537
  props.ref = props.ref || defaultRef;
89018
89538
  props.max = props.max || "23h59";
@@ -89513,7 +90033,7 @@ const InputDurationPart = ({
89513
90033
  };
89514
90034
 
89515
90035
  installImportMetaCssBuild(import.meta);
89516
- const css$p = /* css */`.navi_radio_group {
90036
+ const css$n = /* css */`.navi_radio_group {
89517
90037
  border-style: solid;
89518
90038
 
89519
90039
  &[data-callout] {
@@ -89553,7 +90073,7 @@ const RadioGroup = props => {
89553
90073
  return radioGroup;
89554
90074
  };
89555
90075
  const RadioGroupInterface = props => {
89556
- import.meta.css = [css$p, "@jsenv/navi/src/control/input/radio_group.jsx"];
90076
+ import.meta.css = [css$n, "@jsenv/navi/src/control/input/radio_group.jsx"];
89557
90077
  const {
89558
90078
  ref
89559
90079
  } = props;
@@ -89583,7 +90103,7 @@ const RadioGroupInterface = props => {
89583
90103
  };
89584
90104
 
89585
90105
  installImportMetaCssBuild(import.meta);
89586
- const css$o = /* css */`.navi_input.navi_select {
90106
+ const css$m = /* css */`.navi_input.navi_select {
89587
90107
  & .navi_control_input {
89588
90108
  padding-right: calc(var(--x-padding-right) + 1em);
89589
90109
  line-height: var(--navi-control-line-height);
@@ -89630,7 +90150,7 @@ const Select = ({
89630
90150
  ...props
89631
90151
  }) => {
89632
90152
  installInputCss();
89633
- import.meta.css = [css$o, "@jsenv/navi/src/control/input/select.jsx"];
90153
+ import.meta.css = [css$m, "@jsenv/navi/src/control/input/select.jsx"];
89634
90154
  const defaultRef = useRef(null);
89635
90155
  props.ref = props.ref || defaultRef;
89636
90156
  seedDefaultValueFromSignal(props);
@@ -89683,7 +90203,7 @@ const Select = ({
89683
90203
  };
89684
90204
 
89685
90205
  installImportMetaCssBuild(import.meta);
89686
- const css$n = /* css */`.navi_split_button {
90206
+ const css$l = /* css */`.navi_split_button {
89687
90207
  border-radius: var(--navi-control-border-radius);
89688
90208
  display: inline-flex;
89689
90209
  position: relative;
@@ -89805,7 +90325,7 @@ const css$n = /* css */`.navi_split_button {
89805
90325
  * on the split button's own box.
89806
90326
  */
89807
90327
  const SplitButton = props => {
89808
- import.meta.css = [css$n, "@jsenv/navi/src/control/input/split_button.jsx"];
90328
+ import.meta.css = [css$l, "@jsenv/navi/src/control/input/split_button.jsx"];
89809
90329
  const {
89810
90330
  options = [],
89811
90331
  value,
@@ -90824,7 +91344,7 @@ const createItemTracker = (onChange) => {
90824
91344
  };
90825
91345
 
90826
91346
  installImportMetaCssBuild(import.meta);
90827
- const css$m = /* css */`.navi_wheel_container {
91347
+ const css$k = /* css */`.navi_wheel_container {
90828
91348
  --wheel-item-height: round(1.8em, 1px);
90829
91349
  --wheel-emphasis-size: round(1.5em, 1px);
90830
91350
  --wheel-item-width: 3.5ch;
@@ -91773,7 +92293,7 @@ const useWheelKeyboard = ({
91773
92293
  }, [isHorizontal, interactive, isLoop]);
91774
92294
  };
91775
92295
  function WheelUI(props) {
91776
- import.meta.css = [css$m, "@jsenv/navi/src/control/wheel/wheel.jsx"];
92296
+ import.meta.css = [css$k, "@jsenv/navi/src/control/wheel/wheel.jsx"];
91777
92297
  const {
91778
92298
  ref,
91779
92299
  visibleCount = 3,
@@ -92770,7 +93290,7 @@ Wheel.Item = WheelItem;
92770
93290
  * @param {boolean|number} [props.zoom] - Enlarge the centered value of every wheel (see Wheel's zoom prop) with one prop for the whole group.
92771
93291
  */
92772
93292
  const WheelGroup = props => {
92773
- import.meta.css = [css$m, "@jsenv/navi/src/control/wheel/wheel.jsx"];
93293
+ import.meta.css = [css$k, "@jsenv/navi/src/control/wheel/wheel.jsx"];
92774
93294
  // WheelGroup IS a control group: it aggregates its named wheels ("hours",
92775
93295
  // "minutes"…) into one object value, so it can sit directly inside a Form or a
92776
93296
  // Picker with no extra <ControlGroup> wrapper. The wheel-specific presentation
@@ -92943,7 +93463,7 @@ const WheelColon = props => {
92943
93463
  Wheel.Colon = WheelColon;
92944
93464
 
92945
93465
  installImportMetaCssBuild(import.meta);
92946
- const css$l = /* css */`.navi_time_range_label {
93466
+ const css$j = /* css */`.navi_time_range_label {
92947
93467
  --wheel-item-height: round(1.8em, 1px);
92948
93468
  color: var(--wheel-color, light-dark(#111, #eee));
92949
93469
  line-height: var(--wheel-item-height);
@@ -93167,7 +93687,7 @@ const TimeRangeWheel = ({
93167
93687
  endTimeProps,
93168
93688
  ...rest
93169
93689
  }) => {
93170
- import.meta.css = [css$l, "@jsenv/navi/src/control/wheel/wheel_time.jsx"];
93690
+ import.meta.css = [css$j, "@jsenv/navi/src/control/wheel/wheel_time.jsx"];
93171
93691
  const startId = useId();
93172
93692
  const startRef = useRef(null);
93173
93693
  const endRef = useRef(null);
@@ -93755,7 +94275,7 @@ const Z_INDEX_DROP_PREVIEW = Z_INDEX_STICKY_CORNER + 1;
93755
94275
  const Z_INDEX_TABLE_UI = Z_INDEX_STICKY_CORNER + 1;
93756
94276
 
93757
94277
  installImportMetaCssBuild(import.meta);
93758
- const css$k = /* css */`.navi_table_drag_clone_container {
94278
+ const css$i = /* css */`.navi_table_drag_clone_container {
93759
94279
  top: var(--table-visual-top);
93760
94280
  left: var(--table-visual-left);
93761
94281
  width: var(--table-visual-width);
@@ -93910,7 +94430,7 @@ const useTableDragContextValue = ({
93910
94430
  }, [grabTarget, canChangeColumnOrder]);
93911
94431
  };
93912
94432
  const TableDragCloneContainer = /*#__PURE__*/forwardRef((props, ref) => {
93913
- import.meta.css = [css$k, "@jsenv/navi/src/control/table/drag/table_drag.jsx"];
94433
+ import.meta.css = [css$i, "@jsenv/navi/src/control/table/drag/table_drag.jsx"];
93914
94434
  const {
93915
94435
  tableId
93916
94436
  } = props;
@@ -94209,7 +94729,7 @@ const ROW_MIN_HEIGHT = 30;
94209
94729
  const ROW_MAX_HEIGHT = 100;
94210
94730
  const COLUMN_MIN_WIDTH = 50;
94211
94731
  const COLUMN_MAX_WIDTH = 500;
94212
- const css$j = /* css */`@layer navi {
94732
+ const css$h = /* css */`@layer navi {
94213
94733
  .navi_table {
94214
94734
  --table-resizer-handle-color: #063b7c;
94215
94735
  --table-resizer-color: #387ec9;
@@ -94378,7 +94898,7 @@ const css$j = /* css */`@layer navi {
94378
94898
 
94379
94899
  // Column resize components
94380
94900
  const TableColumnResizer = props => {
94381
- import.meta.css = [css$j, "@jsenv/navi/src/control/table/resize/table_resize.jsx"];
94901
+ import.meta.css = [css$h, "@jsenv/navi/src/control/table/resize/table_resize.jsx"];
94382
94902
  const defaultRef = useRef();
94383
94903
  const ref = props.ref || defaultRef;
94384
94904
  return jsxs("div", {
@@ -94850,7 +95370,7 @@ const findPreviousTableRow = currentRow => {
94850
95370
  };
94851
95371
 
94852
95372
  installImportMetaCssBuild(import.meta);
94853
- const css$i = /* css */`@layer navi {
95373
+ const css$g = /* css */`@layer navi {
94854
95374
  .navi_table {
94855
95375
  --selection-border-color: var(--navi-selection-border-color, #0078d4);
94856
95376
  --selection-background-color: var(--navi-selection-background-color, #eaf1fd);
@@ -94944,7 +95464,7 @@ const useTableSelectionController = ({
94944
95464
  onSelectionChange,
94945
95465
  selectionColor
94946
95466
  }) => {
94947
- import.meta.css = [css$i, "@jsenv/navi/src/control/table/selection/table_selection.jsx"];
95467
+ import.meta.css = [css$g, "@jsenv/navi/src/control/table/selection/table_selection.jsx"];
94948
95468
  const selectionController = useSelectionController({
94949
95469
  elementRef: tableRef,
94950
95470
  layout: "grid",
@@ -95415,7 +95935,7 @@ const useTableStickyContextValue = ({
95415
95935
  };
95416
95936
 
95417
95937
  installImportMetaCssBuild(import.meta);
95418
- const css$h = /* css */`@layer navi {
95938
+ const css$f = /* css */`@layer navi {
95419
95939
  .navi_table {
95420
95940
  --sticky-frontier-color: silver;
95421
95941
  --sticky-frontier-size: 12px;
@@ -95588,7 +96108,7 @@ const css$h = /* css */`@layer navi {
95588
96108
  const TableStickyFrontier = ({
95589
96109
  tableRef
95590
96110
  }) => {
95591
- import.meta.css = [css$h, "@jsenv/navi/src/control/table/sticky/table_sticky.jsx"];
96111
+ import.meta.css = [css$f, "@jsenv/navi/src/control/table/sticky/table_sticky.jsx"];
95592
96112
  const stickyLeftFrontierGhostRef = useRef();
95593
96113
  const stickyLeftFrontierPreviewRef = useRef();
95594
96114
  const stickyTopFrontierGhostRef = useRef();
@@ -95819,7 +96339,7 @@ installImportMetaCssBuild(import.meta);
95819
96339
  * inset 0 -1px 0 0 color; // Bottom border
95820
96340
  */
95821
96341
 
95822
- const css$g = /* css */ `.navi_table_root {
96342
+ const css$e = /* css */ `.navi_table_root {
95823
96343
  max-width: var(--table-max-width, none);
95824
96344
  max-height: var(--table-max-height, none);
95825
96345
  position: relative;
@@ -96004,11 +96524,11 @@ const css$g = /* css */ `.navi_table_root {
96004
96524
  // table must not carry this sheet, and a build that sees no caller drops the
96005
96525
  // css with the function.
96006
96526
  const installTableCss = () => {
96007
- import.meta.css = [css$g, "@jsenv/navi/src/control/table/table_css.js"];
96527
+ import.meta.css = [css$e, "@jsenv/navi/src/control/table/table_css.js"];
96008
96528
  };
96009
96529
 
96010
96530
  installImportMetaCssBuild(import.meta);
96011
- const css$f = /* css */`.navi_table_ui {
96531
+ const css$d = /* css */`.navi_table_ui {
96012
96532
  z-index: ${Z_INDEX_TABLE_UI};
96013
96533
  pointer-events: none;
96014
96534
  position: fixed;
@@ -96017,7 +96537,7 @@ const css$f = /* css */`.navi_table_ui {
96017
96537
  }
96018
96538
  `;
96019
96539
  const TableUI = /*#__PURE__*/forwardRef((props, ref) => {
96020
- import.meta.css = [css$f, "@jsenv/navi/src/control/table/table_ui.jsx"];
96540
+ import.meta.css = [css$d, "@jsenv/navi/src/control/table/table_ui.jsx"];
96021
96541
  const {
96022
96542
  tableRef,
96023
96543
  tableId,
@@ -96939,7 +97459,7 @@ const normalizeKey = (key) => {
96939
97459
  };
96940
97460
 
96941
97461
  installImportMetaCssBuild(import.meta);
96942
- const css$e = /* css */`.navi_shortcut_container[data-visually-hidden], .navi_shortcut_button[data-visually-hidden] {
97462
+ const css$c = /* css */`.navi_shortcut_container[data-visually-hidden], .navi_shortcut_button[data-visually-hidden] {
96943
97463
  clip: rect(0, 0, 0, 0);
96944
97464
  white-space: nowrap;
96945
97465
  opacity: 0;
@@ -96956,7 +97476,7 @@ const css$e = /* css */`.navi_shortcut_container[data-visually-hidden], .navi_sh
96956
97476
  const ActiveKeyboardShortcuts = ({
96957
97477
  visible
96958
97478
  }) => {
96959
- import.meta.css = [css$e, "@jsenv/navi/src/keyboard/active_keyboard_shortcuts.jsx"];
97479
+ import.meta.css = [css$c, "@jsenv/navi/src/keyboard/active_keyboard_shortcuts.jsx"];
96960
97480
  const activeShortcuts = activeShortcutsSignal.value;
96961
97481
  return jsx("div", {
96962
97482
  className: "navi_shortcut_container",
@@ -96996,7 +97516,7 @@ const KeyboardShortcutAriaElement = ({
96996
97516
  };
96997
97517
 
96998
97518
  installImportMetaCssBuild(import.meta);
96999
- const css$d = /* css */`@layer navi {
97519
+ const css$b = /* css */`@layer navi {
97000
97520
  .navi_clipboard_container {
97001
97521
  --height: 1.5em;
97002
97522
  --notif-spacing: .5em;
@@ -97027,7 +97547,7 @@ const ButtonCopyToClipboard = ({
97027
97547
  children,
97028
97548
  ...props
97029
97549
  }) => {
97030
- import.meta.css = [css$d, "@jsenv/navi/src/control/input/button_copy_to_clipboard.jsx"];
97550
+ import.meta.css = [css$b, "@jsenv/navi/src/control/input/button_copy_to_clipboard.jsx"];
97031
97551
  const [copied, setCopied] = useState(false);
97032
97552
  const renderedRef = useRef();
97033
97553
  useEffect(() => {
@@ -97107,7 +97627,7 @@ const Address = ({
97107
97627
  };
97108
97628
 
97109
97629
  installImportMetaCssBuild(import.meta);
97110
- const css$c = /* css */`@layer navi;
97630
+ const css$a = /* css */`@layer navi;
97111
97631
 
97112
97632
  .navi_text.navi_badge_count {
97113
97633
  white-space: nowrap;
@@ -97236,7 +97756,7 @@ const BadgeCount = ({
97236
97756
  lineLayout,
97237
97757
  ...props
97238
97758
  }) => {
97239
- import.meta.css = [css$c, "@jsenv/navi/src/text/badge_count.jsx"];
97759
+ import.meta.css = [css$a, "@jsenv/navi/src/text/badge_count.jsx"];
97240
97760
  const defaultRef = useRef();
97241
97761
  props.ref = props.ref || defaultRef;
97242
97762
  const {
@@ -97379,7 +97899,7 @@ const BadgeCountCircle = ({
97379
97899
  };
97380
97900
 
97381
97901
  installImportMetaCssBuild(import.meta);
97382
- const css$b = /* css */`@layer navi {
97902
+ const css$9 = /* css */`@layer navi {
97383
97903
  .navi_caption {
97384
97904
  --color: #6b7280;
97385
97905
  }
@@ -97399,7 +97919,7 @@ const Caption = ({
97399
97919
  className,
97400
97920
  ...rest
97401
97921
  }) => {
97402
- import.meta.css = [css$b, "@jsenv/navi/src/text/caption.jsx"];
97922
+ import.meta.css = [css$9, "@jsenv/navi/src/text/caption.jsx"];
97403
97923
  return jsx(Text, {
97404
97924
  as: "small",
97405
97925
  size: "0.8em" // We use em to be relative to the parent (we want to be smaller than the surrounding text)
@@ -97795,177 +98315,8 @@ const Interpolate = ({
97795
98315
  });
97796
98316
  };
97797
98317
 
97798
- const SuccessSvg = () => {
97799
- return jsx("svg", {
97800
- viewBox: "0 0 16 16",
97801
- fill: "currentColor",
97802
- children: jsx("path", {
97803
- 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"
97804
- })
97805
- });
97806
- };
97807
- const ErrorSvg = () => {
97808
- return jsx("svg", {
97809
- viewBox: "0 0 16 16",
97810
- fill: "currentColor",
97811
- children: jsx("path", {
97812
- 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"
97813
- })
97814
- });
97815
- };
97816
- const InfoSvg$1 = () => {
97817
- return jsx("svg", {
97818
- viewBox: "0 0 16 16",
97819
- fill: "currentColor",
97820
- children: jsx("path", {
97821
- 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"
97822
- })
97823
- });
97824
- };
97825
- const WarningSvg = () => {
97826
- return jsx("svg", {
97827
- viewBox: "0 0 16 16",
97828
- fill: "currentColor",
97829
- children: jsx("path", {
97830
- 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"
97831
- })
97832
- });
97833
- };
97834
-
97835
98318
  installImportMetaCssBuild(import.meta);
97836
- const css$a = /* css */`@layer navi {
97837
- .navi_message_box {
97838
- --background-color-info: var(--navi-info-color-light);
97839
- --color-info: var(--navi-info-color);
97840
- --background-color-success: var(--navi-success-color-light);
97841
- --color-success: var(--navi-success-color);
97842
- --background-color-warning: var(--navi-warning-color-light);
97843
- --color-warning: var(--navi-warning-color);
97844
- --background-color-error: var(--navi-error-color-light);
97845
- --color-error: var(--navi-error-color);
97846
- }
97847
- }
97848
-
97849
- .navi_message_box {
97850
- --x-message-background-color: var(--background-color-info);
97851
- --x-message-color: var(--color-info);
97852
- background-color: var(--x-message-background-color);
97853
-
97854
- & > .navi_icon {
97855
- flex-shrink: 0;
97856
- }
97857
- }
97858
-
97859
- .navi_message_box[data-status-info] {
97860
- --x-message-background-color: var(--background-color-info);
97861
- --x-message-color: var(--color-info);
97862
- }
97863
-
97864
- .navi_message_box[data-status-success] {
97865
- --x-message-background-color: var(--background-color-success);
97866
- --x-message-color: var(--color-success);
97867
- }
97868
-
97869
- .navi_message_box[data-status-warning] {
97870
- --x-message-background-color: var(--background-color-warning);
97871
- --x-message-color: var(--color-warning);
97872
- }
97873
-
97874
- .navi_message_box[data-status-error] {
97875
- --x-message-background-color: var(--background-color-error);
97876
- --x-message-color: var(--color-error);
97877
- }
97878
-
97879
- .navi_message_box[data-left-stripe] {
97880
- border-left: 6px solid var(--x-message-color);
97881
- border-top-left-radius: 6px;
97882
- border-bottom-left-radius: 6px;
97883
- }
97884
- `;
97885
- const MessageBox = ({
97886
- status = "info",
97887
- padding = "sm",
97888
- icon,
97889
- leftStripe,
97890
- children,
97891
- onClose,
97892
- ...rest
97893
- }) => {
97894
- import.meta.css = [css$a, "@jsenv/navi/src/text/message_box.jsx"];
97895
- const [hasTitleChild, setHasTitleChild] = useState(false);
97896
- const innerLeftStripe = leftStripe === undefined ? hasTitleChild : leftStripe;
97897
- if (icon === true) {
97898
- icon = status === "info" ? jsx(InfoSvg$1, {}) : status === "success" ? jsx(SuccessSvg, {}) : status === "warning" ? jsx(WarningSvg, {}) : status === "error" ? jsx(ErrorSvg, {}) : null;
97899
- } else if (typeof icon === "function") {
97900
- const Comp = icon;
97901
- icon = jsx(Comp, {});
97902
- }
97903
- return jsx(Box, {
97904
- as: "div",
97905
- role: status === "info" ? "status" : "alert",
97906
- "data-left-stripe": innerLeftStripe ? "" : undefined,
97907
- inline: true,
97908
- column: true,
97909
- alignY: "center",
97910
- spacing: "s",
97911
- ...rest,
97912
- className: withPropsClassName("navi_message_box", rest.className),
97913
- padding: padding,
97914
- pseudoClasses: MessageBoxPseudoClasses,
97915
- basePseudoState: {
97916
- ":-navi-status-info": status === "info",
97917
- ":-navi-status-success": status === "success",
97918
- ":-navi-status-warning": status === "warning",
97919
- ":-navi-status-error": status === "error"
97920
- },
97921
- children: jsx(MessageBoxStatusContext.Provider, {
97922
- value: status,
97923
- children: jsxs(MessageBoxReportTitleChildContext.Provider, {
97924
- value: setHasTitleChild,
97925
- children: [icon && jsx(Icon, {
97926
- color: "var(--x-message-color)",
97927
- height: "1lh",
97928
- maxHeight: "auto",
97929
- selfAlignY: "start",
97930
- aspectRatio: "auto",
97931
- children: icon
97932
- }), jsx(Text, {
97933
- children: children
97934
- }), onClose &&
97935
- // A column as tall as the first line of the message, pinned to the
97936
- // top, the button centered in it: the close button stays level
97937
- // with the first line however many lines the message takes (same
97938
- // layout as Callout).
97939
- jsx(Box, {
97940
- flex: true,
97941
- alignY: "center",
97942
- height: "1lh",
97943
- selfAlignY: "start",
97944
- shrink: false,
97945
- children: jsx(Button, {
97946
- action: onClose,
97947
- icon: true,
97948
- border: "none",
97949
- style: {
97950
- ":hover": {
97951
- backgroundColor: "rgba(0, 0, 0, 0.1)"
97952
- }
97953
- },
97954
- children: jsx(Icon, {
97955
- children: jsx(CloseSvg, {})
97956
- })
97957
- })
97958
- })]
97959
- })
97960
- })
97961
- });
97962
- };
97963
- const MessageBoxPseudoClasses = [":-navi-status-info", ":-navi-status-success", ":-navi-status-warning", ":-navi-status-error"];
97964
- const MessageBoxStatusContext = createContext();
97965
- const MessageBoxReportTitleChildContext = createContext();
97966
-
97967
- installImportMetaCssBuild(import.meta);
97968
- const css$9 = /* css */`@layer navi;
98319
+ const css$8 = /* css */`@layer navi;
97969
98320
 
97970
98321
  .navi_quantity {
97971
98322
  flex-direction: column;
@@ -98066,7 +98417,7 @@ const Quantity = ({
98066
98417
  bold = true,
98067
98418
  ...props
98068
98419
  }) => {
98069
- import.meta.css = [css$9, "@jsenv/navi/src/text/quantity.jsx"];
98420
+ import.meta.css = [css$8, "@jsenv/navi/src/text/quantity.jsx"];
98070
98421
  const value = parseQuantityValue(children);
98071
98422
  const valueRounded = integer && typeof value === "number" ? Math.round(value) : value;
98072
98423
  const valueFormatted = typeof valueRounded === "number" ? formatNumber(valueRounded, {
@@ -98125,7 +98476,7 @@ const parseQuantityValue = children => {
98125
98476
  };
98126
98477
 
98127
98478
  installImportMetaCssBuild(import.meta);
98128
- const css$8 = /* css */`@layer navi {
98479
+ const css$7 = /* css */`@layer navi {
98129
98480
  .navi_meter {
98130
98481
  --loader-color: var(--navi-loader-color);
98131
98482
  --track-color: #efefef;
@@ -98264,7 +98615,7 @@ const Meter = ({
98264
98615
  style,
98265
98616
  ...rest
98266
98617
  }) => {
98267
- import.meta.css = [css$8, "@jsenv/navi/src/text/meter.jsx"];
98618
+ import.meta.css = [css$7, "@jsenv/navi/src/text/meter.jsx"];
98268
98619
  const defaultRef = useRef();
98269
98620
  const ref = rest.ref || defaultRef;
98270
98621
  value = Number(value);
@@ -98379,7 +98730,7 @@ const Paragraph = props => {
98379
98730
  };
98380
98731
 
98381
98732
  installImportMetaCssBuild(import.meta);
98382
- const css$7 = /* css */`.navi_text_box {
98733
+ const css$6 = /* css */`.navi_text_box {
98383
98734
  align-items: flex-start;
98384
98735
  min-width: 0;
98385
98736
  }
@@ -98421,7 +98772,7 @@ const TextBox = ({
98421
98772
  children,
98422
98773
  ...rest
98423
98774
  }) => {
98424
- import.meta.css = [css$7, "@jsenv/navi/src/text/text_box.jsx"];
98775
+ import.meta.css = [css$6, "@jsenv/navi/src/text/text_box.jsx"];
98425
98776
  const boxRef = useRef(null);
98426
98777
  const contentRef = useRef(null);
98427
98778
  useLayoutEffect(() => {
@@ -98467,40 +98818,6 @@ const adjustWidth = (boxEl, contentEl) => {
98467
98818
  contentEl.style.width = `${Math.ceil(optimalWidth)}px`;
98468
98819
  };
98469
98820
 
98470
- installImportMetaCssBuild(import.meta);
98471
- const css$6 = /* css */`.navi_message_box {
98472
- & .navi_title {
98473
- margin-top: 0;
98474
- margin-bottom: var(--navi-s);
98475
- color: var(--x-message-color);
98476
- }
98477
- }
98478
- `;
98479
- const Title = props => {
98480
- import.meta.css = [css$6, "@jsenv/navi/src/text/title.jsx"];
98481
- const messageBoxStatus = useContext(MessageBoxStatusContext);
98482
- const innerAs = props.as || (messageBoxStatus ? "h4" : "h1");
98483
- const titleLevel = parseInt(innerAs.slice(1));
98484
- const reportTitleToMessageBox = useContext(MessageBoxReportTitleChildContext);
98485
- reportTitleToMessageBox?.(true);
98486
- return jsx(TitleLevelContext.Provider, {
98487
- value: titleLevel,
98488
- children: jsx(Text, {
98489
- bold: true,
98490
- className: withPropsClassName("navi_title"),
98491
- as: messageBoxStatus ? "h4" : "h1",
98492
- ...props,
98493
- pseudoClasses: TitlePseudoClasses,
98494
- children: props.children
98495
- })
98496
- });
98497
- };
98498
- const TitleLevelContext = createContext();
98499
- const useTitleLevel = () => {
98500
- return useContext(TitleLevelContext);
98501
- };
98502
- const TitlePseudoClasses = [":hover"];
98503
-
98504
98821
  installImportMetaCssBuild(import.meta);
98505
98822
  const css$5 = /* css */`@keyframes navi_image_shimmer {
98506
98823
  0% {