@jsenv/navi 0.29.470 → 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
  };
@@ -59404,6 +59429,10 @@ const Label = props => {
59404
59429
  requiredIndicator
59405
59430
  } = props;
59406
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}`;
59407
59436
  const [disabled, setDisabled] = useState(false);
59408
59437
  const [readOnly, setReadOnly] = useState(false);
59409
59438
  const [required, setRequired] = useState(false);
@@ -69182,9 +69211,297 @@ const PopupClose = ({
69182
69211
  });
69183
69212
  };
69184
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
+
69185
69502
  installImportMetaCssBuild(import.meta);
69186
69503
  let openLocalDialogCount = 0;
69187
- const css$E = /* css */`
69504
+ const css$C = /* css */`
69188
69505
  /* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
69189
69506
  order depending on which popup renders first. */
69190
69507
 
@@ -70253,10 +70570,14 @@ const css$E = /* css */`
70253
70570
  * under `"while-opened"`, only a press on what opens the popup does (a
70254
70571
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
70255
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.
70256
70577
  * @param {import("ignore:preact").ComponentChildren} props.children
70257
70578
  */
70258
70579
  const Dialog = props => {
70259
- import.meta.css = [css$E, "@jsenv/navi/src/layout/dialog.jsx"];
70580
+ import.meta.css = [css$C, "@jsenv/navi/src/layout/dialog.jsx"];
70260
70581
  if (props.openController) {
70261
70582
  return jsx(ControlledDialog, {
70262
70583
  ...props
@@ -70632,6 +70953,9 @@ const useDialogProps = props => {
70632
70953
  onOpen,
70633
70954
  children: childrenProp,
70634
70955
  mount,
70956
+ // Read by usePopupName below, never written on the element.
70957
+ openerAriaLabel,
70958
+ openerAriaLabelledBy,
70635
70959
  ...rest
70636
70960
  } = props;
70637
70961
  // Assigned on every render, like openEffect below, so it always closes over
@@ -70642,7 +70966,16 @@ const useDialogProps = props => {
70642
70966
  mount,
70643
70967
  anchor
70644
70968
  });
70645
- 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;
70646
70979
  // Where it is placed, and whether there is a wall — the two questions that
70647
70980
  // pick the show call (see this file's top comment).
70648
70981
  const isTopLayer = layer === "top";
@@ -71492,6 +71825,7 @@ const useDialogProps = props => {
71492
71825
  "backdropFilter": backdrop ? backdropFilter : undefined,
71493
71826
  "styleCSSVars": DIALOG_STYLE_CSS_VARS,
71494
71827
  ...rest,
71828
+ ...nameProps,
71495
71829
  ...autoFocusProps,
71496
71830
  "as": "dialog",
71497
71831
  ref,
@@ -71692,10 +72026,11 @@ const DIALOG_STYLE_CSS_VARS = {
71692
72026
  maxHeight: "--dialog-max-height"
71693
72027
  };
71694
72028
  Dialog.Close = PopupClose;
72029
+ Dialog.Title = PopupTitle;
71695
72030
 
71696
72031
  installImportMetaCssBuild(import.meta);
71697
72032
  let openLocalPopoverCount = 0;
71698
- const css$D = /* css */`
72033
+ const css$B = /* css */`
71699
72034
  /* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
71700
72035
  order depending on which popup renders first. */
71701
72036
 
@@ -72294,10 +72629,17 @@ const css$D = /* css */`
72294
72629
  * under `"while-opened"`, only a press on what opens the popup does (a
72295
72630
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
72296
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.
72297
72639
  * @param {import("ignore:preact").ComponentChildren} props.children
72298
72640
  */
72299
72641
  const Popover = props => {
72300
- import.meta.css = [css$D, "@jsenv/navi/src/layout/popover.jsx"];
72642
+ import.meta.css = [css$B, "@jsenv/navi/src/layout/popover.jsx"];
72301
72643
  if (props.openController) {
72302
72644
  return jsx(ControlledPopover, {
72303
72645
  ...props
@@ -72536,6 +72878,9 @@ const usePopoverProps = props => {
72536
72878
  onOpen,
72537
72879
  children: childrenProp,
72538
72880
  mount,
72881
+ // Read by usePopupName below, never written on the element.
72882
+ openerAriaLabel,
72883
+ openerAriaLabelledBy,
72539
72884
  ...rest
72540
72885
  } = props;
72541
72886
  // Assigned on every render, like openEffect below, so it always closes over
@@ -72546,7 +72891,16 @@ const usePopoverProps = props => {
72546
72891
  mount,
72547
72892
  anchor
72548
72893
  });
72549
- 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;
72550
72904
  const isTopLayer = layer === "top";
72551
72905
  const ref = props.ref;
72552
72906
  const backdropRef = useRef();
@@ -73361,6 +73715,10 @@ const usePopoverProps = props => {
73361
73715
  "navi-hidden": openController.openedInDom ? undefined : "",
73362
73716
  "styleCSSVars": POPUP_STYLE_CSS_VARS,
73363
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"),
73364
73722
  ...autoFocusProps,
73365
73723
  ref,
73366
73724
  // Not a control, but still something the rest of navi has to be able to
@@ -73476,9 +73834,10 @@ const resolvePositionAreaAndAnimationKind = ({
73476
73834
  };
73477
73835
  };
73478
73836
  Popover.Close = PopupClose;
73837
+ Popover.Title = PopupTitle;
73479
73838
 
73480
73839
  installImportMetaCssBuild(import.meta);
73481
- const css$C = /* css */`@layer navi {
73840
+ const css$A = /* css */`@layer navi {
73482
73841
  .navi_popup {
73483
73842
  --popup-border-radius: var(--navi-popup-border-radius);
73484
73843
  --popup-border-width: 1px;
@@ -73619,6 +73978,10 @@ const css$C = /* css */`@layer navi {
73619
73978
  * @param {string} [props.className] - Merged with the shared
73620
73979
  * `"navi_popup"` class (see this file's own CSS) rather than replacing
73621
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.
73622
73985
  * @param {"always"|"idle"|"from-first-open"|"while-opened"} [props.mount] - When
73623
73986
  * `children` are built and thrown away (see popup_content_mount.js).
73624
73987
  * `"from-first-open"` (the default) builds them on the first open and keeps
@@ -73634,7 +73997,7 @@ const css$C = /* css */`@layer navi {
73634
73997
  * @param {import("ignore:preact").ComponentChildren} props.children
73635
73998
  */
73636
73999
  const Popup = props => {
73637
- import.meta.css = [css$C, "@jsenv/navi/src/layout/popup.jsx"];
74000
+ import.meta.css = [css$A, "@jsenv/navi/src/layout/popup.jsx"];
73638
74001
  const {
73639
74002
  mode: modeProp,
73640
74003
  maxWidth,
@@ -73717,6 +74080,7 @@ const Popup = props => {
73717
74080
  });
73718
74081
  };
73719
74082
  Popup.Close = PopupClose;
74083
+ Popup.Title = PopupTitle;
73720
74084
 
73721
74085
  // What Dialog's own animation values mean to a popover, which has no lift
73722
74086
  // (see the `animation` doc above).
@@ -73765,6 +74129,54 @@ const pickerUIIsNaviOwn = ui => {
73765
74129
  return Boolean(ui) && typeof ui === "object" && Boolean(ui.type?.isPickerOwnUI);
73766
74130
  };
73767
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
+
73768
74180
  /**
73769
74181
  * What the picker holds, read from inside its `ui` — for a drawing given as
73770
74182
  * an element (`ui={<MyCard game={game} />}`), which the picker cannot hand
@@ -73794,7 +74206,7 @@ const usePickerState = () => {
73794
74206
  };
73795
74207
 
73796
74208
  installImportMetaCssBuild(import.meta);
73797
- const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
74209
+ const css$z = /* css */`.navi_picker_callout_dock > [data-picker-content] {
73798
74210
  display: none;
73799
74211
  }
73800
74212
 
@@ -73823,7 +74235,7 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
73823
74235
  }
73824
74236
  }
73825
74237
 
73826
- .navi_picker[aria-haspopup="listbox"][aria-expanded="true"][navi-popover-mode="overlay"] {
74238
+ .navi_picker[navi-popover-mode="overlay"][aria-expanded="true"] {
73827
74239
  border-color: #0000;
73828
74240
  }
73829
74241
 
@@ -73851,7 +74263,7 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
73851
74263
  }
73852
74264
  `;
73853
74265
  const PickerCustomResolver = props => {
73854
- 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"];
73855
74267
  if (props.children === undefined) {
73856
74268
  return renderResolver(PickerNative, props);
73857
74269
  }
@@ -74674,6 +75086,24 @@ const isPressDisputedByHold = pickerEl => Boolean(pickerEl?.parentElement?.close
74674
75086
  const getPickerInput = pickerEl => {
74675
75087
  return pickerEl.querySelector(".navi_picker_input");
74676
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
+ };
74677
75107
  const getPickerInputUIState = pickerEl => {
74678
75108
  const pickerInput = getPickerInput(pickerEl);
74679
75109
  return getUIStateFromElement(pickerInput);
@@ -74696,6 +75126,11 @@ const PickerContentInsidePopup = props => {
74696
75126
  // Same reason: a `data-testid` on the picker names the trigger (see
74697
75127
  // docs/testid.md) — this one names the popup.
74698
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,
74699
75134
  positionArea,
74700
75135
  popoverMode = "nearby",
74701
75136
  popoverSpacing = popoverMode === "nearby" ? 5 : 0,
@@ -74757,8 +75192,25 @@ const PickerContentInsidePopup = props => {
74757
75192
  } = props;
74758
75193
  const isPopover = mode === "popover";
74759
75194
  const isCallout = mode === "callout";
74760
- return jsx(Next, {
74761
- "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",
74762
75214
  "navi-popover-mode": isPopover ? popoverMode : undefined,
74763
75215
  ...rest,
74764
75216
  // On popupProps already (see the picker's popup assembly); they mean
@@ -74803,11 +75255,23 @@ const PickerContentInsidePopup = props => {
74803
75255
  pressOutside: pressOutside,
74804
75256
  children: jsx(PopupModeContext.Provider, {
74805
75257
  value: mode,
74806
- children: children
75258
+ children: jsx(PopupTitleContext.Provider, {
75259
+ value: null,
75260
+ children: children
75261
+ })
74807
75262
  })
74808
75263
  }) : jsx(Popup, {
74809
75264
  ...popupProps,
74810
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
74811
75275
  // What the CSS above keys on: this picker's own popup, and no other
74812
75276
  // popup below the picker.
74813
75277
  ,
@@ -75077,7 +75541,7 @@ const showNativePicker = (pickerInput, event) => {
75077
75541
  };
75078
75542
 
75079
75543
  installImportMetaCssBuild(import.meta);
75080
- const css$A = /* css */`.navi_picker_confirm_body {
75544
+ const css$y = /* css */`.navi_picker_confirm_body {
75081
75545
  min-width: 180px;
75082
75546
  max-width: 320px;
75083
75547
  padding: var(--navi-m);
@@ -75093,7 +75557,7 @@ const css$A = /* css */`.navi_picker_confirm_body {
75093
75557
  }
75094
75558
  `;
75095
75559
  const PickerConfirmResolver = props => {
75096
- 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"];
75097
75561
  const Next = useNextResolver();
75098
75562
  if (props.type !== "confirm") {
75099
75563
  return jsx(Next, {
@@ -75619,7 +76083,7 @@ const LoadingIndicator = ({
75619
76083
  };
75620
76084
 
75621
76085
  installImportMetaCssBuild(import.meta);
75622
- const css$z = /* css */`@layer navi {
76086
+ const css$x = /* css */`@layer navi {
75623
76087
  .navi_separator {
75624
76088
  --size: 1px;
75625
76089
  --color: var(--navi-separator-color-default);
@@ -75693,7 +76157,7 @@ const Separator = ({
75693
76157
  style,
75694
76158
  ...props
75695
76159
  }) => {
75696
- import.meta.css = [css$z, "@jsenv/navi/src/layout/separator.jsx"];
76160
+ import.meta.css = [css$x, "@jsenv/navi/src/layout/separator.jsx"];
75697
76161
  return jsx(Box, {
75698
76162
  as: vertical ? "span" : "hr",
75699
76163
  ...props,
@@ -76487,7 +76951,7 @@ const listItemBlockedMessage = (loading, props) => {
76487
76951
  };
76488
76952
 
76489
76953
  installImportMetaCssBuild(import.meta);
76490
- const css$y = /* css */`@layer navi {
76954
+ const css$w = /* css */`@layer navi {
76491
76955
  .navi_list_container[navi-selectable] {
76492
76956
  --list-item-outline-width: var(--navi-focus-outline-width);
76493
76957
  --list-item-outline-offset: calc(-1 * var(--list-item-outline-width));
@@ -76660,7 +77124,7 @@ const ListSelectableResolver = props => {
76660
77124
  };
76661
77125
  const ListSelectable = props => {
76662
77126
  const Next = useNextResolver();
76663
- 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"];
76664
77128
  // we allow ourselves to auto-generate a name
76665
77129
  const defaultName = useId();
76666
77130
  props.name = props.name || `listbox_${defaultName}`;
@@ -77329,7 +77793,7 @@ const ListRunItemContext = createContext(null);
77329
77793
  // item takes its place in the collection by slot: the place is then the list's
77330
77794
  // to move, and the item's to follow — see list_items.js.
77331
77795
  const ListSlotContext = createContext(null);
77332
- const css$x = /* css */`@property --list-group-label-height {
77796
+ const css$v = /* css */`@property --list-group-label-height {
77333
77797
  syntax: "<length>";
77334
77798
  inherits: false;
77335
77799
  initial-value: 0;
@@ -77857,7 +78321,7 @@ const resolveRenderBudget = renderBudget => {
77857
78321
  };
77858
78322
  };
77859
78323
  const ListUI = props => {
77860
- 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"];
77861
78325
  const {
77862
78326
  ref,
77863
78327
  renderBudget: renderBudgetProp,
@@ -84147,7 +84611,7 @@ const createBadgeRegistry = () => {
84147
84611
  };
84148
84612
 
84149
84613
  installImportMetaCssBuild(import.meta);
84150
- const css$w = /* css */`@layer navi;
84614
+ const css$u = /* css */`@layer navi;
84151
84615
 
84152
84616
  .navi_badge {
84153
84617
  --font-size: .7em;
@@ -84250,7 +84714,7 @@ const BadgeUI = ({
84250
84714
  className,
84251
84715
  ...props
84252
84716
  }) => {
84253
- import.meta.css = [css$w, "@jsenv/navi/src/text/badge.jsx"];
84717
+ import.meta.css = [css$u, "@jsenv/navi/src/text/badge.jsx"];
84254
84718
  const defaultRef = useRef();
84255
84719
  props.ref = props.ref || defaultRef;
84256
84720
  const {
@@ -84358,7 +84822,7 @@ const wrapLabels = childArray => {
84358
84822
  };
84359
84823
 
84360
84824
  installImportMetaCssBuild(import.meta);
84361
- const css$v = /* css */`@layer navi;
84825
+ const css$t = /* css */`@layer navi;
84362
84826
 
84363
84827
  .navi_badge_list {
84364
84828
  flex-wrap: wrap;
@@ -84493,7 +84957,7 @@ const useBadgeRegistry = (children, enabled) => {
84493
84957
  * component grants (a <Picker>, see max_lines_context.js).
84494
84958
  */
84495
84959
  const BadgeList = props => {
84496
- 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"];
84497
84961
  const {
84498
84962
  maxLines,
84499
84963
  max,
@@ -84837,7 +85301,7 @@ const BadgeListContent = ({
84837
85301
  };
84838
85302
 
84839
85303
  installImportMetaCssBuild(import.meta);
84840
- const css$u = /* css */`.navi_color {
85304
+ const css$s = /* css */`.navi_color {
84841
85305
  aspect-ratio: 1;
84842
85306
  background-color: currentColor;
84843
85307
  border: 1px solid #0003;
@@ -84857,7 +85321,7 @@ const Color = ({
84857
85321
  children,
84858
85322
  ...rest
84859
85323
  }) => {
84860
- import.meta.css = [css$u, "@jsenv/navi/src/text/color.jsx"];
85324
+ import.meta.css = [css$s, "@jsenv/navi/src/text/color.jsx"];
84861
85325
  const color = children || undefined;
84862
85326
  return jsx(Box, {
84863
85327
  as: "span",
@@ -85344,7 +85808,7 @@ const PickerFileUI = /*#__PURE__*/asPickerOwnUI(() => {
85344
85808
  });
85345
85809
 
85346
85810
  installImportMetaCssBuild(import.meta);
85347
- const css$t = /* css */`@layer navi {
85811
+ const css$r = /* css */`@layer navi {
85348
85812
  .navi_picker {
85349
85813
  --picker-border-radius: var(--navi-control-border-radius);
85350
85814
  --picker-border-width: var(--navi-control-border-width);
@@ -85813,7 +86277,7 @@ const css$t = /* css */`@layer navi {
85813
86277
  }
85814
86278
  `;
85815
86279
  const PickerButton = props => {
85816
- 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"];
85817
86281
  warnOnUnknownPickerType(props);
85818
86282
  warnOnUIDrawnByNobody(props);
85819
86283
  if (typeof props.maxLines === "string") {
@@ -85856,11 +86320,6 @@ const PickerButton = props => {
85856
86320
  // confirm resolver (see picker_confirm.jsx), which is the picker that is
85857
86321
  // one — a caller whose popup is a menu of actions says it for themselves.
85858
86322
  picksNothing,
85859
- // Where the caller's own role goes: onto the host, beside the name (see
85860
- // control_context.js) — a role and a name on two different elements name
85861
- // nothing at all. Left unsaid, a picker that picks nothing takes "button"
85862
- // (see PickerInput below).
85863
- role,
85864
86323
  readOnly,
85865
86324
  error,
85866
86325
  // Who draws the loading outline. Left unsaid, the picker does, around its
@@ -85943,21 +86402,8 @@ const PickerButton = props => {
85943
86402
  // clearing being a modification like any other.
85944
86403
  const interactive = !basePseudoState[":disabled"] && !basePseudoState[":read-only"] && !loading;
85945
86404
  usePickerErrorCallout(uiStateController, error);
85946
- // What the picker really is, said on the element that holds the name and
85947
- // takes the focus (the host input below): a picker that picks nothing is a
85948
- // door — its popup is a question (type="confirm") or something to read
85949
- // (mode="callout", which says it for itself) — and a door is pressed. So it
85950
- // answers to "button" rather than to the "textbox" its <input> would
85951
- // otherwise be, which is both what a screen reader announces and what
85952
- // getByRole("button", { name }) finds. A picker holding a value stays a
85953
- // textbox: it does hold text.
85954
- const hostRole = role ?? (picksNothing ? "button" : undefined);
85955
- // And what that button is called, when the caller wrote it as a drawing
85956
- // rather than as a word: a door draws no value, so what sits in its value
85957
- // slot IS its label — the `ui` a caller gave it, or its placeholder. Skipped
85958
- // the moment the caller names it themselves (aria-label/aria-labelledby),
85959
- // which is the only way to name a door drawn as an icon.
85960
- 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);
85961
86407
  // What the picker knows about itself, for the pieces it does not place: the
85962
86408
  // drawings of its value (Picker.UI.*), and the affordances a caller may put
85963
86409
  // in their own `ui` (Picker.Clear) as much as the ones it puts in its slot.
@@ -86026,8 +86472,16 @@ const PickerButton = props => {
86026
86472
  basePseudoState: basePseudoState,
86027
86473
  styleCSSVars: PickerStyleCSSVars,
86028
86474
  variant: undefined,
86029
- 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
+ ,
86030
86483
  role: undefined,
86484
+ "aria-haspopup": undefined,
86031
86485
  rightSlotIcon: undefined,
86032
86486
  rightSlotIconSize: undefined,
86033
86487
  rightSlot: undefined,
@@ -86067,6 +86521,8 @@ const PickerButton = props => {
86067
86521
  inset: loadingOutlineInset
86068
86522
  }), jsx(PickerInput, {
86069
86523
  role: hostRole,
86524
+ "aria-haspopup": props["aria-haspopup"],
86525
+ "aria-expanded": props["aria-expanded"],
86070
86526
  "aria-labelledby": hostLabelId,
86071
86527
  tabIndex: variant === "headless" ? -1 : undefined,
86072
86528
  "aria-hidden": variant === "headless" ? "true" : undefined,
@@ -86574,6 +87030,10 @@ const PickerFirstResolver = props => {
86574
87030
  * Without `children`, opens the browser-native picker for the given `type`.
86575
87031
  * With `children`, opens a popover (desktop) or dialog (mobile) containing the children.
86576
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).
86577
87037
  *
86578
87038
  * `type="confirm"` is the picker that asks "are you sure?" before doing what it
86579
87039
  * stands for: its popup is the question, `ui` its label, and yes runs its
@@ -86647,6 +87107,9 @@ const PickerFirstResolver = props => {
86647
87107
  * standalone?: boolean,
86648
87108
  * popupLayer?: "top" | "local",
86649
87109
  * popupTestId?: string,
87110
+ * popupAriaLabel?: string,
87111
+ * popupAriaLabelledBy?: string,
87112
+ * popupAriaDescribedBy?: string,
86650
87113
  * confirmTestId?: string,
86651
87114
  * cancelTestId?: string,
86652
87115
  * mount?: "always" | "idle" | "from-first-open" | "while-opened",
@@ -87106,6 +87569,19 @@ const PickerFirstResolver = props => {
87106
87569
  * not only what it holds, which a testid inside the children already names.
87107
87570
  * Under `mode="callout"` it names the callout element — the control's own,
87108
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.
87109
87585
  * @param {string} [confirmTestId] `type="confirm"`, default body: the
87110
87586
  * `data-testid` of the yes button; `cancelTestId` that of the no button.
87111
87587
  * Both are elements navi builds, which a test may not name by class (see
@@ -87243,7 +87719,7 @@ const Picker = /*#__PURE__*/Object.assign(PickerResolved, {
87243
87719
  });
87244
87720
 
87245
87721
  installImportMetaCssBuild(import.meta);
87246
- const css$s = /* css */`@layer navi {
87722
+ const css$q = /* css */`@layer navi {
87247
87723
  .navi_picker_spin {
87248
87724
  --picker-spin-padding-x-default: var(--navi-picker-padding-x-default);
87249
87725
  --picker-spin-padding-y-default: var(--navi-picker-padding-y-default);
@@ -87593,7 +88069,7 @@ const Spin = ({
87593
88069
  nextLabel,
87594
88070
  ...rest
87595
88071
  }) => {
87596
- 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"];
87597
88073
  const id = useId();
87598
88074
  // What the group around it says, when there is one: how big the whole thing
87599
88075
  // is written is said once, on the group, and every spin in it follows.
@@ -88304,7 +88780,7 @@ const renderValueDefault = value => String(value ?? "");
88304
88780
  * are passed on to the spins sitting in them.
88305
88781
  */
88306
88782
  const SpinGroup = props => {
88307
- 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"];
88308
88784
  const {
88309
88785
  size
88310
88786
  } = props;
@@ -88813,7 +89289,7 @@ const TimeRangeSpin = ({
88813
89289
  };
88814
89290
 
88815
89291
  installImportMetaCssBuild(import.meta);
88816
- const css$r = /* css */`.navi_checkbox_group {
89292
+ const css$p = /* css */`.navi_checkbox_group {
88817
89293
  border-style: solid;
88818
89294
 
88819
89295
  &[data-callout] {
@@ -88873,7 +89349,7 @@ const CheckboxGroup = props => {
88873
89349
  return checkboxGroup;
88874
89350
  };
88875
89351
  const CheckboxGroupInterface = props => {
88876
- 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"];
88877
89353
  const {
88878
89354
  ref
88879
89355
  } = props;
@@ -88991,7 +89467,7 @@ const formatIntlUnit = (unit, {
88991
89467
  };
88992
89468
 
88993
89469
  installImportMetaCssBuild(import.meta);
88994
- const css$q = /* css */`.navi_input_duration {
89470
+ const css$o = /* css */`.navi_input_duration {
88995
89471
  --duration-separator-spacing: 4px;
88996
89472
  --loader-color: var(--navi-loader-color);
88997
89473
  position: relative;
@@ -89056,7 +89532,7 @@ const css$q = /* css */`.navi_input_duration {
89056
89532
  * "auto" aligns each field toward its neighbouring separator (first→right, last→left, middle/solo→center).
89057
89533
  */
89058
89534
  const InputDuration = props => {
89059
- 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"];
89060
89536
  const defaultRef = useRef();
89061
89537
  props.ref = props.ref || defaultRef;
89062
89538
  props.max = props.max || "23h59";
@@ -89557,7 +90033,7 @@ const InputDurationPart = ({
89557
90033
  };
89558
90034
 
89559
90035
  installImportMetaCssBuild(import.meta);
89560
- const css$p = /* css */`.navi_radio_group {
90036
+ const css$n = /* css */`.navi_radio_group {
89561
90037
  border-style: solid;
89562
90038
 
89563
90039
  &[data-callout] {
@@ -89597,7 +90073,7 @@ const RadioGroup = props => {
89597
90073
  return radioGroup;
89598
90074
  };
89599
90075
  const RadioGroupInterface = props => {
89600
- 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"];
89601
90077
  const {
89602
90078
  ref
89603
90079
  } = props;
@@ -89627,7 +90103,7 @@ const RadioGroupInterface = props => {
89627
90103
  };
89628
90104
 
89629
90105
  installImportMetaCssBuild(import.meta);
89630
- const css$o = /* css */`.navi_input.navi_select {
90106
+ const css$m = /* css */`.navi_input.navi_select {
89631
90107
  & .navi_control_input {
89632
90108
  padding-right: calc(var(--x-padding-right) + 1em);
89633
90109
  line-height: var(--navi-control-line-height);
@@ -89674,7 +90150,7 @@ const Select = ({
89674
90150
  ...props
89675
90151
  }) => {
89676
90152
  installInputCss();
89677
- 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"];
89678
90154
  const defaultRef = useRef(null);
89679
90155
  props.ref = props.ref || defaultRef;
89680
90156
  seedDefaultValueFromSignal(props);
@@ -89727,7 +90203,7 @@ const Select = ({
89727
90203
  };
89728
90204
 
89729
90205
  installImportMetaCssBuild(import.meta);
89730
- const css$n = /* css */`.navi_split_button {
90206
+ const css$l = /* css */`.navi_split_button {
89731
90207
  border-radius: var(--navi-control-border-radius);
89732
90208
  display: inline-flex;
89733
90209
  position: relative;
@@ -89849,7 +90325,7 @@ const css$n = /* css */`.navi_split_button {
89849
90325
  * on the split button's own box.
89850
90326
  */
89851
90327
  const SplitButton = props => {
89852
- 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"];
89853
90329
  const {
89854
90330
  options = [],
89855
90331
  value,
@@ -90868,7 +91344,7 @@ const createItemTracker = (onChange) => {
90868
91344
  };
90869
91345
 
90870
91346
  installImportMetaCssBuild(import.meta);
90871
- const css$m = /* css */`.navi_wheel_container {
91347
+ const css$k = /* css */`.navi_wheel_container {
90872
91348
  --wheel-item-height: round(1.8em, 1px);
90873
91349
  --wheel-emphasis-size: round(1.5em, 1px);
90874
91350
  --wheel-item-width: 3.5ch;
@@ -91817,7 +92293,7 @@ const useWheelKeyboard = ({
91817
92293
  }, [isHorizontal, interactive, isLoop]);
91818
92294
  };
91819
92295
  function WheelUI(props) {
91820
- 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"];
91821
92297
  const {
91822
92298
  ref,
91823
92299
  visibleCount = 3,
@@ -92814,7 +93290,7 @@ Wheel.Item = WheelItem;
92814
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.
92815
93291
  */
92816
93292
  const WheelGroup = props => {
92817
- 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"];
92818
93294
  // WheelGroup IS a control group: it aggregates its named wheels ("hours",
92819
93295
  // "minutes"…) into one object value, so it can sit directly inside a Form or a
92820
93296
  // Picker with no extra <ControlGroup> wrapper. The wheel-specific presentation
@@ -92987,7 +93463,7 @@ const WheelColon = props => {
92987
93463
  Wheel.Colon = WheelColon;
92988
93464
 
92989
93465
  installImportMetaCssBuild(import.meta);
92990
- const css$l = /* css */`.navi_time_range_label {
93466
+ const css$j = /* css */`.navi_time_range_label {
92991
93467
  --wheel-item-height: round(1.8em, 1px);
92992
93468
  color: var(--wheel-color, light-dark(#111, #eee));
92993
93469
  line-height: var(--wheel-item-height);
@@ -93211,7 +93687,7 @@ const TimeRangeWheel = ({
93211
93687
  endTimeProps,
93212
93688
  ...rest
93213
93689
  }) => {
93214
- 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"];
93215
93691
  const startId = useId();
93216
93692
  const startRef = useRef(null);
93217
93693
  const endRef = useRef(null);
@@ -93799,7 +94275,7 @@ const Z_INDEX_DROP_PREVIEW = Z_INDEX_STICKY_CORNER + 1;
93799
94275
  const Z_INDEX_TABLE_UI = Z_INDEX_STICKY_CORNER + 1;
93800
94276
 
93801
94277
  installImportMetaCssBuild(import.meta);
93802
- const css$k = /* css */`.navi_table_drag_clone_container {
94278
+ const css$i = /* css */`.navi_table_drag_clone_container {
93803
94279
  top: var(--table-visual-top);
93804
94280
  left: var(--table-visual-left);
93805
94281
  width: var(--table-visual-width);
@@ -93954,7 +94430,7 @@ const useTableDragContextValue = ({
93954
94430
  }, [grabTarget, canChangeColumnOrder]);
93955
94431
  };
93956
94432
  const TableDragCloneContainer = /*#__PURE__*/forwardRef((props, ref) => {
93957
- 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"];
93958
94434
  const {
93959
94435
  tableId
93960
94436
  } = props;
@@ -94253,7 +94729,7 @@ const ROW_MIN_HEIGHT = 30;
94253
94729
  const ROW_MAX_HEIGHT = 100;
94254
94730
  const COLUMN_MIN_WIDTH = 50;
94255
94731
  const COLUMN_MAX_WIDTH = 500;
94256
- const css$j = /* css */`@layer navi {
94732
+ const css$h = /* css */`@layer navi {
94257
94733
  .navi_table {
94258
94734
  --table-resizer-handle-color: #063b7c;
94259
94735
  --table-resizer-color: #387ec9;
@@ -94422,7 +94898,7 @@ const css$j = /* css */`@layer navi {
94422
94898
 
94423
94899
  // Column resize components
94424
94900
  const TableColumnResizer = props => {
94425
- 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"];
94426
94902
  const defaultRef = useRef();
94427
94903
  const ref = props.ref || defaultRef;
94428
94904
  return jsxs("div", {
@@ -94894,7 +95370,7 @@ const findPreviousTableRow = currentRow => {
94894
95370
  };
94895
95371
 
94896
95372
  installImportMetaCssBuild(import.meta);
94897
- const css$i = /* css */`@layer navi {
95373
+ const css$g = /* css */`@layer navi {
94898
95374
  .navi_table {
94899
95375
  --selection-border-color: var(--navi-selection-border-color, #0078d4);
94900
95376
  --selection-background-color: var(--navi-selection-background-color, #eaf1fd);
@@ -94988,7 +95464,7 @@ const useTableSelectionController = ({
94988
95464
  onSelectionChange,
94989
95465
  selectionColor
94990
95466
  }) => {
94991
- 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"];
94992
95468
  const selectionController = useSelectionController({
94993
95469
  elementRef: tableRef,
94994
95470
  layout: "grid",
@@ -95459,7 +95935,7 @@ const useTableStickyContextValue = ({
95459
95935
  };
95460
95936
 
95461
95937
  installImportMetaCssBuild(import.meta);
95462
- const css$h = /* css */`@layer navi {
95938
+ const css$f = /* css */`@layer navi {
95463
95939
  .navi_table {
95464
95940
  --sticky-frontier-color: silver;
95465
95941
  --sticky-frontier-size: 12px;
@@ -95632,7 +96108,7 @@ const css$h = /* css */`@layer navi {
95632
96108
  const TableStickyFrontier = ({
95633
96109
  tableRef
95634
96110
  }) => {
95635
- 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"];
95636
96112
  const stickyLeftFrontierGhostRef = useRef();
95637
96113
  const stickyLeftFrontierPreviewRef = useRef();
95638
96114
  const stickyTopFrontierGhostRef = useRef();
@@ -95863,7 +96339,7 @@ installImportMetaCssBuild(import.meta);
95863
96339
  * inset 0 -1px 0 0 color; // Bottom border
95864
96340
  */
95865
96341
 
95866
- const css$g = /* css */ `.navi_table_root {
96342
+ const css$e = /* css */ `.navi_table_root {
95867
96343
  max-width: var(--table-max-width, none);
95868
96344
  max-height: var(--table-max-height, none);
95869
96345
  position: relative;
@@ -96048,11 +96524,11 @@ const css$g = /* css */ `.navi_table_root {
96048
96524
  // table must not carry this sheet, and a build that sees no caller drops the
96049
96525
  // css with the function.
96050
96526
  const installTableCss = () => {
96051
- 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"];
96052
96528
  };
96053
96529
 
96054
96530
  installImportMetaCssBuild(import.meta);
96055
- const css$f = /* css */`.navi_table_ui {
96531
+ const css$d = /* css */`.navi_table_ui {
96056
96532
  z-index: ${Z_INDEX_TABLE_UI};
96057
96533
  pointer-events: none;
96058
96534
  position: fixed;
@@ -96061,7 +96537,7 @@ const css$f = /* css */`.navi_table_ui {
96061
96537
  }
96062
96538
  `;
96063
96539
  const TableUI = /*#__PURE__*/forwardRef((props, ref) => {
96064
- 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"];
96065
96541
  const {
96066
96542
  tableRef,
96067
96543
  tableId,
@@ -96983,7 +97459,7 @@ const normalizeKey = (key) => {
96983
97459
  };
96984
97460
 
96985
97461
  installImportMetaCssBuild(import.meta);
96986
- 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] {
96987
97463
  clip: rect(0, 0, 0, 0);
96988
97464
  white-space: nowrap;
96989
97465
  opacity: 0;
@@ -97000,7 +97476,7 @@ const css$e = /* css */`.navi_shortcut_container[data-visually-hidden], .navi_sh
97000
97476
  const ActiveKeyboardShortcuts = ({
97001
97477
  visible
97002
97478
  }) => {
97003
- 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"];
97004
97480
  const activeShortcuts = activeShortcutsSignal.value;
97005
97481
  return jsx("div", {
97006
97482
  className: "navi_shortcut_container",
@@ -97040,7 +97516,7 @@ const KeyboardShortcutAriaElement = ({
97040
97516
  };
97041
97517
 
97042
97518
  installImportMetaCssBuild(import.meta);
97043
- const css$d = /* css */`@layer navi {
97519
+ const css$b = /* css */`@layer navi {
97044
97520
  .navi_clipboard_container {
97045
97521
  --height: 1.5em;
97046
97522
  --notif-spacing: .5em;
@@ -97071,7 +97547,7 @@ const ButtonCopyToClipboard = ({
97071
97547
  children,
97072
97548
  ...props
97073
97549
  }) => {
97074
- 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"];
97075
97551
  const [copied, setCopied] = useState(false);
97076
97552
  const renderedRef = useRef();
97077
97553
  useEffect(() => {
@@ -97151,7 +97627,7 @@ const Address = ({
97151
97627
  };
97152
97628
 
97153
97629
  installImportMetaCssBuild(import.meta);
97154
- const css$c = /* css */`@layer navi;
97630
+ const css$a = /* css */`@layer navi;
97155
97631
 
97156
97632
  .navi_text.navi_badge_count {
97157
97633
  white-space: nowrap;
@@ -97280,7 +97756,7 @@ const BadgeCount = ({
97280
97756
  lineLayout,
97281
97757
  ...props
97282
97758
  }) => {
97283
- 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"];
97284
97760
  const defaultRef = useRef();
97285
97761
  props.ref = props.ref || defaultRef;
97286
97762
  const {
@@ -97423,7 +97899,7 @@ const BadgeCountCircle = ({
97423
97899
  };
97424
97900
 
97425
97901
  installImportMetaCssBuild(import.meta);
97426
- const css$b = /* css */`@layer navi {
97902
+ const css$9 = /* css */`@layer navi {
97427
97903
  .navi_caption {
97428
97904
  --color: #6b7280;
97429
97905
  }
@@ -97443,7 +97919,7 @@ const Caption = ({
97443
97919
  className,
97444
97920
  ...rest
97445
97921
  }) => {
97446
- import.meta.css = [css$b, "@jsenv/navi/src/text/caption.jsx"];
97922
+ import.meta.css = [css$9, "@jsenv/navi/src/text/caption.jsx"];
97447
97923
  return jsx(Text, {
97448
97924
  as: "small",
97449
97925
  size: "0.8em" // We use em to be relative to the parent (we want to be smaller than the surrounding text)
@@ -97839,177 +98315,8 @@ const Interpolate = ({
97839
98315
  });
97840
98316
  };
97841
98317
 
97842
- const SuccessSvg = () => {
97843
- return jsx("svg", {
97844
- viewBox: "0 0 16 16",
97845
- fill: "currentColor",
97846
- children: jsx("path", {
97847
- 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"
97848
- })
97849
- });
97850
- };
97851
- const ErrorSvg = () => {
97852
- return jsx("svg", {
97853
- viewBox: "0 0 16 16",
97854
- fill: "currentColor",
97855
- children: jsx("path", {
97856
- 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"
97857
- })
97858
- });
97859
- };
97860
- const InfoSvg$1 = () => {
97861
- return jsx("svg", {
97862
- viewBox: "0 0 16 16",
97863
- fill: "currentColor",
97864
- children: jsx("path", {
97865
- 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"
97866
- })
97867
- });
97868
- };
97869
- const WarningSvg = () => {
97870
- return jsx("svg", {
97871
- viewBox: "0 0 16 16",
97872
- fill: "currentColor",
97873
- children: jsx("path", {
97874
- 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"
97875
- })
97876
- });
97877
- };
97878
-
97879
98318
  installImportMetaCssBuild(import.meta);
97880
- const css$a = /* css */`@layer navi {
97881
- .navi_message_box {
97882
- --background-color-info: var(--navi-info-color-light);
97883
- --color-info: var(--navi-info-color);
97884
- --background-color-success: var(--navi-success-color-light);
97885
- --color-success: var(--navi-success-color);
97886
- --background-color-warning: var(--navi-warning-color-light);
97887
- --color-warning: var(--navi-warning-color);
97888
- --background-color-error: var(--navi-error-color-light);
97889
- --color-error: var(--navi-error-color);
97890
- }
97891
- }
97892
-
97893
- .navi_message_box {
97894
- --x-message-background-color: var(--background-color-info);
97895
- --x-message-color: var(--color-info);
97896
- background-color: var(--x-message-background-color);
97897
-
97898
- & > .navi_icon {
97899
- flex-shrink: 0;
97900
- }
97901
- }
97902
-
97903
- .navi_message_box[data-status-info] {
97904
- --x-message-background-color: var(--background-color-info);
97905
- --x-message-color: var(--color-info);
97906
- }
97907
-
97908
- .navi_message_box[data-status-success] {
97909
- --x-message-background-color: var(--background-color-success);
97910
- --x-message-color: var(--color-success);
97911
- }
97912
-
97913
- .navi_message_box[data-status-warning] {
97914
- --x-message-background-color: var(--background-color-warning);
97915
- --x-message-color: var(--color-warning);
97916
- }
97917
-
97918
- .navi_message_box[data-status-error] {
97919
- --x-message-background-color: var(--background-color-error);
97920
- --x-message-color: var(--color-error);
97921
- }
97922
-
97923
- .navi_message_box[data-left-stripe] {
97924
- border-left: 6px solid var(--x-message-color);
97925
- border-top-left-radius: 6px;
97926
- border-bottom-left-radius: 6px;
97927
- }
97928
- `;
97929
- const MessageBox = ({
97930
- status = "info",
97931
- padding = "sm",
97932
- icon,
97933
- leftStripe,
97934
- children,
97935
- onClose,
97936
- ...rest
97937
- }) => {
97938
- import.meta.css = [css$a, "@jsenv/navi/src/text/message_box.jsx"];
97939
- const [hasTitleChild, setHasTitleChild] = useState(false);
97940
- const innerLeftStripe = leftStripe === undefined ? hasTitleChild : leftStripe;
97941
- if (icon === true) {
97942
- icon = status === "info" ? jsx(InfoSvg$1, {}) : status === "success" ? jsx(SuccessSvg, {}) : status === "warning" ? jsx(WarningSvg, {}) : status === "error" ? jsx(ErrorSvg, {}) : null;
97943
- } else if (typeof icon === "function") {
97944
- const Comp = icon;
97945
- icon = jsx(Comp, {});
97946
- }
97947
- return jsx(Box, {
97948
- as: "div",
97949
- role: status === "info" ? "status" : "alert",
97950
- "data-left-stripe": innerLeftStripe ? "" : undefined,
97951
- inline: true,
97952
- column: true,
97953
- alignY: "center",
97954
- spacing: "s",
97955
- ...rest,
97956
- className: withPropsClassName("navi_message_box", rest.className),
97957
- padding: padding,
97958
- pseudoClasses: MessageBoxPseudoClasses,
97959
- basePseudoState: {
97960
- ":-navi-status-info": status === "info",
97961
- ":-navi-status-success": status === "success",
97962
- ":-navi-status-warning": status === "warning",
97963
- ":-navi-status-error": status === "error"
97964
- },
97965
- children: jsx(MessageBoxStatusContext.Provider, {
97966
- value: status,
97967
- children: jsxs(MessageBoxReportTitleChildContext.Provider, {
97968
- value: setHasTitleChild,
97969
- children: [icon && jsx(Icon, {
97970
- color: "var(--x-message-color)",
97971
- height: "1lh",
97972
- maxHeight: "auto",
97973
- selfAlignY: "start",
97974
- aspectRatio: "auto",
97975
- children: icon
97976
- }), jsx(Text, {
97977
- children: children
97978
- }), onClose &&
97979
- // A column as tall as the first line of the message, pinned to the
97980
- // top, the button centered in it: the close button stays level
97981
- // with the first line however many lines the message takes (same
97982
- // layout as Callout).
97983
- jsx(Box, {
97984
- flex: true,
97985
- alignY: "center",
97986
- height: "1lh",
97987
- selfAlignY: "start",
97988
- shrink: false,
97989
- children: jsx(Button, {
97990
- action: onClose,
97991
- icon: true,
97992
- border: "none",
97993
- style: {
97994
- ":hover": {
97995
- backgroundColor: "rgba(0, 0, 0, 0.1)"
97996
- }
97997
- },
97998
- children: jsx(Icon, {
97999
- children: jsx(CloseSvg, {})
98000
- })
98001
- })
98002
- })]
98003
- })
98004
- })
98005
- });
98006
- };
98007
- const MessageBoxPseudoClasses = [":-navi-status-info", ":-navi-status-success", ":-navi-status-warning", ":-navi-status-error"];
98008
- const MessageBoxStatusContext = createContext();
98009
- const MessageBoxReportTitleChildContext = createContext();
98010
-
98011
- installImportMetaCssBuild(import.meta);
98012
- const css$9 = /* css */`@layer navi;
98319
+ const css$8 = /* css */`@layer navi;
98013
98320
 
98014
98321
  .navi_quantity {
98015
98322
  flex-direction: column;
@@ -98110,7 +98417,7 @@ const Quantity = ({
98110
98417
  bold = true,
98111
98418
  ...props
98112
98419
  }) => {
98113
- import.meta.css = [css$9, "@jsenv/navi/src/text/quantity.jsx"];
98420
+ import.meta.css = [css$8, "@jsenv/navi/src/text/quantity.jsx"];
98114
98421
  const value = parseQuantityValue(children);
98115
98422
  const valueRounded = integer && typeof value === "number" ? Math.round(value) : value;
98116
98423
  const valueFormatted = typeof valueRounded === "number" ? formatNumber(valueRounded, {
@@ -98169,7 +98476,7 @@ const parseQuantityValue = children => {
98169
98476
  };
98170
98477
 
98171
98478
  installImportMetaCssBuild(import.meta);
98172
- const css$8 = /* css */`@layer navi {
98479
+ const css$7 = /* css */`@layer navi {
98173
98480
  .navi_meter {
98174
98481
  --loader-color: var(--navi-loader-color);
98175
98482
  --track-color: #efefef;
@@ -98308,7 +98615,7 @@ const Meter = ({
98308
98615
  style,
98309
98616
  ...rest
98310
98617
  }) => {
98311
- import.meta.css = [css$8, "@jsenv/navi/src/text/meter.jsx"];
98618
+ import.meta.css = [css$7, "@jsenv/navi/src/text/meter.jsx"];
98312
98619
  const defaultRef = useRef();
98313
98620
  const ref = rest.ref || defaultRef;
98314
98621
  value = Number(value);
@@ -98423,7 +98730,7 @@ const Paragraph = props => {
98423
98730
  };
98424
98731
 
98425
98732
  installImportMetaCssBuild(import.meta);
98426
- const css$7 = /* css */`.navi_text_box {
98733
+ const css$6 = /* css */`.navi_text_box {
98427
98734
  align-items: flex-start;
98428
98735
  min-width: 0;
98429
98736
  }
@@ -98465,7 +98772,7 @@ const TextBox = ({
98465
98772
  children,
98466
98773
  ...rest
98467
98774
  }) => {
98468
- 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"];
98469
98776
  const boxRef = useRef(null);
98470
98777
  const contentRef = useRef(null);
98471
98778
  useLayoutEffect(() => {
@@ -98511,40 +98818,6 @@ const adjustWidth = (boxEl, contentEl) => {
98511
98818
  contentEl.style.width = `${Math.ceil(optimalWidth)}px`;
98512
98819
  };
98513
98820
 
98514
- installImportMetaCssBuild(import.meta);
98515
- const css$6 = /* css */`.navi_message_box {
98516
- & .navi_title {
98517
- margin-top: 0;
98518
- margin-bottom: var(--navi-s);
98519
- color: var(--x-message-color);
98520
- }
98521
- }
98522
- `;
98523
- const Title = props => {
98524
- import.meta.css = [css$6, "@jsenv/navi/src/text/title.jsx"];
98525
- const messageBoxStatus = useContext(MessageBoxStatusContext);
98526
- const innerAs = props.as || (messageBoxStatus ? "h4" : "h1");
98527
- const titleLevel = parseInt(innerAs.slice(1));
98528
- const reportTitleToMessageBox = useContext(MessageBoxReportTitleChildContext);
98529
- reportTitleToMessageBox?.(true);
98530
- return jsx(TitleLevelContext.Provider, {
98531
- value: titleLevel,
98532
- children: jsx(Text, {
98533
- bold: true,
98534
- className: withPropsClassName("navi_title"),
98535
- as: messageBoxStatus ? "h4" : "h1",
98536
- ...props,
98537
- pseudoClasses: TitlePseudoClasses,
98538
- children: props.children
98539
- })
98540
- });
98541
- };
98542
- const TitleLevelContext = createContext();
98543
- const useTitleLevel = () => {
98544
- return useContext(TitleLevelContext);
98545
- };
98546
- const TitlePseudoClasses = [":hover"];
98547
-
98548
98821
  installImportMetaCssBuild(import.meta);
98549
98822
  const css$5 = /* css */`@keyframes navi_image_shimmer {
98550
98823
  0% {