@jsenv/navi 0.29.470 → 0.29.472

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
  };
@@ -52832,10 +52857,16 @@ const LinkWithRoute = ({
52832
52857
  currentExcept,
52833
52858
  currentAlso,
52834
52859
  children,
52860
+ // Kept out of `rest`: spread after the route's url, an `href={undefined}`
52861
+ // riding along would erase it.
52862
+ href,
52835
52863
  ...rest
52836
52864
  }) => {
52837
52865
  {
52838
52866
  assertRoute(route);
52867
+ if (href !== undefined) {
52868
+ console.warn(`A link with a "route" ignores its "href" (${href}): the route builds the address.`);
52869
+ }
52839
52870
  }
52840
52871
  const url = route.buildUrl(routeParams);
52841
52872
  {
@@ -58014,15 +58045,21 @@ const ButtonRouteResolver = props => {
58014
58045
  };
58015
58046
  const ButtonWithRoute = props => {
58016
58047
  const Next = useNextResolver();
58048
+ // `href` is kept out of `rest`: spread after the route's url, an
58049
+ // `href={undefined}` riding along would erase it.
58017
58050
  const {
58018
58051
  route,
58019
58052
  routeParams,
58020
58053
  children,
58021
58054
  pseudoState,
58055
+ href,
58022
58056
  ...rest
58023
58057
  } = props;
58024
58058
  {
58025
58059
  assertRoute(route);
58060
+ if (href !== undefined) {
58061
+ console.warn(`A button with a "route" ignores its "href" (${href}): the route builds the address.`);
58062
+ }
58026
58063
  }
58027
58064
  const url = route.buildUrl(routeParams);
58028
58065
  const linkMatching = useRouteCurrent(route, routeParams);
@@ -59404,6 +59441,10 @@ const Label = props => {
59404
59441
  requiredIndicator
59405
59442
  } = props;
59406
59443
  const controlId = useContext(ControlIdContext);
59444
+ // What a popup opened from the control is named by (see getTriggerLabelIds
59445
+ // in picker_custom.jsx): aria-labelledby can only point at an id.
59446
+ const idDefault = useId();
59447
+ props.id = props.id || `label_${idDefault}`;
59407
59448
  const [disabled, setDisabled] = useState(false);
59408
59449
  const [readOnly, setReadOnly] = useState(false);
59409
59450
  const [required, setRequired] = useState(false);
@@ -69182,9 +69223,297 @@ const PopupClose = ({
69182
69223
  });
69183
69224
  };
69184
69225
 
69226
+ const SuccessSvg = () => {
69227
+ return jsx("svg", {
69228
+ viewBox: "0 0 16 16",
69229
+ fill: "currentColor",
69230
+ children: jsx("path", {
69231
+ 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"
69232
+ })
69233
+ });
69234
+ };
69235
+ const ErrorSvg = () => {
69236
+ return jsx("svg", {
69237
+ viewBox: "0 0 16 16",
69238
+ fill: "currentColor",
69239
+ children: jsx("path", {
69240
+ 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"
69241
+ })
69242
+ });
69243
+ };
69244
+ const InfoSvg$1 = () => {
69245
+ return jsx("svg", {
69246
+ viewBox: "0 0 16 16",
69247
+ fill: "currentColor",
69248
+ children: jsx("path", {
69249
+ 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"
69250
+ })
69251
+ });
69252
+ };
69253
+ const WarningSvg = () => {
69254
+ return jsx("svg", {
69255
+ viewBox: "0 0 16 16",
69256
+ fill: "currentColor",
69257
+ children: jsx("path", {
69258
+ 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"
69259
+ })
69260
+ });
69261
+ };
69262
+
69263
+ installImportMetaCssBuild(import.meta);
69264
+ const css$E = /* css */`@layer navi {
69265
+ .navi_message_box {
69266
+ --background-color-info: var(--navi-info-color-light);
69267
+ --color-info: var(--navi-info-color);
69268
+ --background-color-success: var(--navi-success-color-light);
69269
+ --color-success: var(--navi-success-color);
69270
+ --background-color-warning: var(--navi-warning-color-light);
69271
+ --color-warning: var(--navi-warning-color);
69272
+ --background-color-error: var(--navi-error-color-light);
69273
+ --color-error: var(--navi-error-color);
69274
+ }
69275
+ }
69276
+
69277
+ .navi_message_box {
69278
+ --x-message-background-color: var(--background-color-info);
69279
+ --x-message-color: var(--color-info);
69280
+ background-color: var(--x-message-background-color);
69281
+
69282
+ & > .navi_icon {
69283
+ flex-shrink: 0;
69284
+ }
69285
+ }
69286
+
69287
+ .navi_message_box[data-status-info] {
69288
+ --x-message-background-color: var(--background-color-info);
69289
+ --x-message-color: var(--color-info);
69290
+ }
69291
+
69292
+ .navi_message_box[data-status-success] {
69293
+ --x-message-background-color: var(--background-color-success);
69294
+ --x-message-color: var(--color-success);
69295
+ }
69296
+
69297
+ .navi_message_box[data-status-warning] {
69298
+ --x-message-background-color: var(--background-color-warning);
69299
+ --x-message-color: var(--color-warning);
69300
+ }
69301
+
69302
+ .navi_message_box[data-status-error] {
69303
+ --x-message-background-color: var(--background-color-error);
69304
+ --x-message-color: var(--color-error);
69305
+ }
69306
+
69307
+ .navi_message_box[data-left-stripe] {
69308
+ border-left: 6px solid var(--x-message-color);
69309
+ border-top-left-radius: 6px;
69310
+ border-bottom-left-radius: 6px;
69311
+ }
69312
+ `;
69313
+ const MessageBox = ({
69314
+ status = "info",
69315
+ padding = "sm",
69316
+ icon,
69317
+ leftStripe,
69318
+ children,
69319
+ onClose,
69320
+ ...rest
69321
+ }) => {
69322
+ import.meta.css = [css$E, "@jsenv/navi/src/text/message_box.jsx"];
69323
+ const [hasTitleChild, setHasTitleChild] = useState(false);
69324
+ const innerLeftStripe = leftStripe === undefined ? hasTitleChild : leftStripe;
69325
+ if (icon === true) {
69326
+ icon = status === "info" ? jsx(InfoSvg$1, {}) : status === "success" ? jsx(SuccessSvg, {}) : status === "warning" ? jsx(WarningSvg, {}) : status === "error" ? jsx(ErrorSvg, {}) : null;
69327
+ } else if (typeof icon === "function") {
69328
+ const Comp = icon;
69329
+ icon = jsx(Comp, {});
69330
+ }
69331
+ return jsx(Box, {
69332
+ as: "div",
69333
+ role: status === "info" ? "status" : "alert",
69334
+ "data-left-stripe": innerLeftStripe ? "" : undefined,
69335
+ inline: true,
69336
+ column: true,
69337
+ alignY: "center",
69338
+ spacing: "s",
69339
+ ...rest,
69340
+ className: withPropsClassName("navi_message_box", rest.className),
69341
+ padding: padding,
69342
+ pseudoClasses: MessageBoxPseudoClasses,
69343
+ basePseudoState: {
69344
+ ":-navi-status-info": status === "info",
69345
+ ":-navi-status-success": status === "success",
69346
+ ":-navi-status-warning": status === "warning",
69347
+ ":-navi-status-error": status === "error"
69348
+ },
69349
+ children: jsx(MessageBoxStatusContext.Provider, {
69350
+ value: status,
69351
+ children: jsxs(MessageBoxReportTitleChildContext.Provider, {
69352
+ value: setHasTitleChild,
69353
+ children: [icon && jsx(Icon, {
69354
+ color: "var(--x-message-color)",
69355
+ height: "1lh",
69356
+ maxHeight: "auto",
69357
+ selfAlignY: "start",
69358
+ aspectRatio: "auto",
69359
+ children: icon
69360
+ }), jsx(Text, {
69361
+ children: children
69362
+ }), onClose &&
69363
+ // A column as tall as the first line of the message, pinned to the
69364
+ // top, the button centered in it: the close button stays level
69365
+ // with the first line however many lines the message takes (same
69366
+ // layout as Callout).
69367
+ jsx(Box, {
69368
+ flex: true,
69369
+ alignY: "center",
69370
+ height: "1lh",
69371
+ selfAlignY: "start",
69372
+ shrink: false,
69373
+ children: jsx(Button, {
69374
+ action: onClose,
69375
+ icon: true,
69376
+ border: "none",
69377
+ style: {
69378
+ ":hover": {
69379
+ backgroundColor: "rgba(0, 0, 0, 0.1)"
69380
+ }
69381
+ },
69382
+ children: jsx(Icon, {
69383
+ children: jsx(CloseSvg, {})
69384
+ })
69385
+ })
69386
+ })]
69387
+ })
69388
+ })
69389
+ });
69390
+ };
69391
+ const MessageBoxPseudoClasses = [":-navi-status-info", ":-navi-status-success", ":-navi-status-warning", ":-navi-status-error"];
69392
+ const MessageBoxStatusContext = createContext();
69393
+ const MessageBoxReportTitleChildContext = createContext();
69394
+
69395
+ installImportMetaCssBuild(import.meta);
69396
+ const css$D = /* css */`.navi_message_box {
69397
+ & .navi_title {
69398
+ margin-top: 0;
69399
+ margin-bottom: var(--navi-s);
69400
+ color: var(--x-message-color);
69401
+ }
69402
+ }
69403
+ `;
69404
+ const Title = props => {
69405
+ import.meta.css = [css$D, "@jsenv/navi/src/text/title.jsx"];
69406
+ const messageBoxStatus = useContext(MessageBoxStatusContext);
69407
+ const innerAs = props.as || (messageBoxStatus ? "h4" : "h1");
69408
+ const titleLevel = parseInt(innerAs.slice(1));
69409
+ const reportTitleToMessageBox = useContext(MessageBoxReportTitleChildContext);
69410
+ reportTitleToMessageBox?.(true);
69411
+ return jsx(TitleLevelContext.Provider, {
69412
+ value: titleLevel,
69413
+ children: jsx(Text, {
69414
+ bold: true,
69415
+ className: withPropsClassName("navi_title"),
69416
+ as: messageBoxStatus ? "h4" : "h1",
69417
+ ...props,
69418
+ pseudoClasses: TitlePseudoClasses,
69419
+ children: props.children
69420
+ })
69421
+ });
69422
+ };
69423
+ const TitleLevelContext = createContext();
69424
+ const useTitleLevel = () => {
69425
+ return useContext(TitleLevelContext);
69426
+ };
69427
+ const TitlePseudoClasses = [":hover"];
69428
+
69429
+ /**
69430
+ * What a popup is called, decided by Dialog and Popover alike: the name the
69431
+ * caller writes on it (aria-label/aria-labelledby), else the title it holds
69432
+ * (`<Popup.Title>`), else the name of what opened it (`openerAriaLabelledBy` /
69433
+ * `openerAriaLabel`, which a Picker fills with its trigger's). A popup no one
69434
+ * names is announced as "dialog" and nothing else.
69435
+ *
69436
+ * The title tells the popup it exists rather than the popup looking for it:
69437
+ * the content is built after the popup and thrown away before it (see
69438
+ * popup_content_mount.js), and a title may sit anywhere in it — a header, a
69439
+ * step of a form — so only the title knows when it is there.
69440
+ */
69441
+
69442
+ const PopupTitleContext = createContext(null);
69443
+
69444
+ /**
69445
+ * The title of the popup it sits in — reached as `<Popup.Title>`,
69446
+ * `<Dialog.Title>` or `<Popover.Title>`. A heading (`h2` unless `as` says
69447
+ * otherwise) that is also the popup's accessible name: a screen reader
69448
+ * announces the popup by it, and `getByRole("dialog", { name })` finds it.
69449
+ * An `aria-label`/`aria-labelledby` written on the popup itself wins over it.
69450
+ *
69451
+ * @type {import("ignore:preact").FunctionComponent<{
69452
+ * as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6",
69453
+ * id?: string,
69454
+ * children?: import("ignore:preact").ComponentChildren,
69455
+ * } & Record<string, any>>}
69456
+ */
69457
+ const PopupTitle = props => {
69458
+ const registerTitle = useContext(PopupTitleContext);
69459
+ const idDefault = useId();
69460
+ const id = props.id || `popup_title_${idDefault}`;
69461
+ useLayoutEffect(() => {
69462
+ if (!registerTitle) {
69463
+ return undefined;
69464
+ }
69465
+ return registerTitle(id);
69466
+ }, [registerTitle, id]);
69467
+ return jsx(Title, {
69468
+ as: "h2",
69469
+ ...props,
69470
+ id: id
69471
+ });
69472
+ };
69473
+
69474
+ /**
69475
+ * The name props of a popup element, and what its titles register with —
69476
+ * handed to its children through PopupTitleContext.
69477
+ */
69478
+ const usePopupName = ({
69479
+ ariaLabel,
69480
+ ariaLabelledBy,
69481
+ openerAriaLabel,
69482
+ openerAriaLabelledBy
69483
+ }) => {
69484
+ // Several titles is a content of the caller's own making (two steps both
69485
+ // mounted): the first one registered names the popup.
69486
+ const [titleIds, setTitleIds] = useState(NO_TITLE);
69487
+ const registerTitle = useMemo(() => {
69488
+ return id => {
69489
+ setTitleIds(ids => [...ids, id]);
69490
+ return () => {
69491
+ setTitleIds(ids => ids.filter(each => each !== id));
69492
+ };
69493
+ };
69494
+ }, []);
69495
+ let labelledBy;
69496
+ let label;
69497
+ if (ariaLabelledBy !== undefined || ariaLabel !== undefined) {
69498
+ labelledBy = ariaLabelledBy;
69499
+ label = ariaLabel;
69500
+ } else if (titleIds.length > 0) {
69501
+ labelledBy = titleIds[0];
69502
+ } else if (openerAriaLabelledBy !== undefined) {
69503
+ labelledBy = openerAriaLabelledBy;
69504
+ } else {
69505
+ label = openerAriaLabel;
69506
+ }
69507
+ return [{
69508
+ "aria-labelledby": labelledBy,
69509
+ "aria-label": label
69510
+ }, registerTitle];
69511
+ };
69512
+ const NO_TITLE = [];
69513
+
69185
69514
  installImportMetaCssBuild(import.meta);
69186
69515
  let openLocalDialogCount = 0;
69187
- const css$E = /* css */`
69516
+ const css$C = /* css */`
69188
69517
  /* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
69189
69518
  order depending on which popup renders first. */
69190
69519
 
@@ -70253,10 +70582,14 @@ const css$E = /* css */`
70253
70582
  * under `"while-opened"`, only a press on what opens the popup does (a
70254
70583
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
70255
70584
  * press that does not open the popup throws it away.
70585
+ * @param {string} [props.openerAriaLabelledBy] - The id(s) naming what opened
70586
+ * the dialog: its accessible name when neither `aria-label`/`aria-labelledby`
70587
+ * nor a `<Dialog.Title>` names it. A Picker passes its trigger's.
70588
+ * @param {string} [props.openerAriaLabel] - The same, as a string.
70256
70589
  * @param {import("ignore:preact").ComponentChildren} props.children
70257
70590
  */
70258
70591
  const Dialog = props => {
70259
- import.meta.css = [css$E, "@jsenv/navi/src/layout/dialog.jsx"];
70592
+ import.meta.css = [css$C, "@jsenv/navi/src/layout/dialog.jsx"];
70260
70593
  if (props.openController) {
70261
70594
  return jsx(ControlledDialog, {
70262
70595
  ...props
@@ -70632,6 +70965,9 @@ const useDialogProps = props => {
70632
70965
  onOpen,
70633
70966
  children: childrenProp,
70634
70967
  mount,
70968
+ // Read by usePopupName below, never written on the element.
70969
+ openerAriaLabel,
70970
+ openerAriaLabelledBy,
70635
70971
  ...rest
70636
70972
  } = props;
70637
70973
  // Assigned on every render, like openEffect below, so it always closes over
@@ -70642,7 +70978,16 @@ const useDialogProps = props => {
70642
70978
  mount,
70643
70979
  anchor
70644
70980
  });
70645
- const children = contentMounted ? childrenProp : null;
70981
+ const [nameProps, registerTitle] = usePopupName({
70982
+ ariaLabel: rest["aria-label"],
70983
+ ariaLabelledBy: rest["aria-labelledby"],
70984
+ openerAriaLabel,
70985
+ openerAriaLabelledBy
70986
+ });
70987
+ const children = contentMounted ? jsx(PopupTitleContext.Provider, {
70988
+ value: registerTitle,
70989
+ children: childrenProp
70990
+ }) : null;
70646
70991
  // Where it is placed, and whether there is a wall — the two questions that
70647
70992
  // pick the show call (see this file's top comment).
70648
70993
  const isTopLayer = layer === "top";
@@ -71492,6 +71837,7 @@ const useDialogProps = props => {
71492
71837
  "backdropFilter": backdrop ? backdropFilter : undefined,
71493
71838
  "styleCSSVars": DIALOG_STYLE_CSS_VARS,
71494
71839
  ...rest,
71840
+ ...nameProps,
71495
71841
  ...autoFocusProps,
71496
71842
  "as": "dialog",
71497
71843
  ref,
@@ -71692,10 +72038,11 @@ const DIALOG_STYLE_CSS_VARS = {
71692
72038
  maxHeight: "--dialog-max-height"
71693
72039
  };
71694
72040
  Dialog.Close = PopupClose;
72041
+ Dialog.Title = PopupTitle;
71695
72042
 
71696
72043
  installImportMetaCssBuild(import.meta);
71697
72044
  let openLocalPopoverCount = 0;
71698
- const css$D = /* css */`
72045
+ const css$B = /* css */`
71699
72046
  /* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
71700
72047
  order depending on which popup renders first. */
71701
72048
 
@@ -72294,10 +72641,17 @@ const css$D = /* css */`
72294
72641
  * under `"while-opened"`, only a press on what opens the popup does (a
72295
72642
  * `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
72296
72643
  * press that does not open the popup throws it away.
72644
+ * @param {string} [props.openerAriaLabelledBy] - The id(s) naming what opened
72645
+ * the popover: its accessible name when neither `aria-label`/`aria-labelledby`
72646
+ * nor a `<Popover.Title>` names it. A Picker passes its trigger's.
72647
+ * @param {string} [props.openerAriaLabel] - The same, as a string. A popover
72648
+ * something names — these, `aria-label`/`aria-labelledby`, a
72649
+ * `<Popover.Title>` — is a `dialog`, unless given another `role`: a name on
72650
+ * an element with no role is read by nobody.
72297
72651
  * @param {import("ignore:preact").ComponentChildren} props.children
72298
72652
  */
72299
72653
  const Popover = props => {
72300
- import.meta.css = [css$D, "@jsenv/navi/src/layout/popover.jsx"];
72654
+ import.meta.css = [css$B, "@jsenv/navi/src/layout/popover.jsx"];
72301
72655
  if (props.openController) {
72302
72656
  return jsx(ControlledPopover, {
72303
72657
  ...props
@@ -72536,6 +72890,9 @@ const usePopoverProps = props => {
72536
72890
  onOpen,
72537
72891
  children: childrenProp,
72538
72892
  mount,
72893
+ // Read by usePopupName below, never written on the element.
72894
+ openerAriaLabel,
72895
+ openerAriaLabelledBy,
72539
72896
  ...rest
72540
72897
  } = props;
72541
72898
  // Assigned on every render, like openEffect below, so it always closes over
@@ -72546,7 +72903,16 @@ const usePopoverProps = props => {
72546
72903
  mount,
72547
72904
  anchor
72548
72905
  });
72549
- const children = contentMounted ? childrenProp : null;
72906
+ const [nameProps, registerTitle] = usePopupName({
72907
+ ariaLabel: rest["aria-label"],
72908
+ ariaLabelledBy: rest["aria-labelledby"],
72909
+ openerAriaLabel,
72910
+ openerAriaLabelledBy
72911
+ });
72912
+ const children = contentMounted ? jsx(PopupTitleContext.Provider, {
72913
+ value: registerTitle,
72914
+ children: childrenProp
72915
+ }) : null;
72550
72916
  const isTopLayer = layer === "top";
72551
72917
  const ref = props.ref;
72552
72918
  const backdropRef = useRef();
@@ -73361,6 +73727,10 @@ const usePopoverProps = props => {
73361
73727
  "navi-hidden": openController.openedInDom ? undefined : "",
73362
73728
  "styleCSSVars": POPUP_STYLE_CSS_VARS,
73363
73729
  ...rest,
73730
+ ...nameProps,
73731
+ // A <div> has no role, and a name on an element without one is read by
73732
+ // nobody: a popover something names is a dialog, unless told what else.
73733
+ "role": rest.role ?? (nameProps["aria-labelledby"] === undefined && nameProps["aria-label"] === undefined ? undefined : "dialog"),
73364
73734
  ...autoFocusProps,
73365
73735
  ref,
73366
73736
  // Not a control, but still something the rest of navi has to be able to
@@ -73476,9 +73846,10 @@ const resolvePositionAreaAndAnimationKind = ({
73476
73846
  };
73477
73847
  };
73478
73848
  Popover.Close = PopupClose;
73849
+ Popover.Title = PopupTitle;
73479
73850
 
73480
73851
  installImportMetaCssBuild(import.meta);
73481
- const css$C = /* css */`@layer navi {
73852
+ const css$A = /* css */`@layer navi {
73482
73853
  .navi_popup {
73483
73854
  --popup-border-radius: var(--navi-popup-border-radius);
73484
73855
  --popup-border-width: 1px;
@@ -73619,6 +73990,10 @@ const css$C = /* css */`@layer navi {
73619
73990
  * @param {string} [props.className] - Merged with the shared
73620
73991
  * `"navi_popup"` class (see this file's own CSS) rather than replacing
73621
73992
  * it.
73993
+ * @param {string} [props.openerAriaLabelledBy] - Forwarded as-is: the name the
73994
+ * popup takes when neither `aria-label`/`aria-labelledby` nor a
73995
+ * `<Popup.Title>` in `children` names it (see `Dialog`/`Popover`).
73996
+ * @param {string} [props.openerAriaLabel] - Forwarded as-is, same.
73622
73997
  * @param {"always"|"idle"|"from-first-open"|"while-opened"} [props.mount] - When
73623
73998
  * `children` are built and thrown away (see popup_content_mount.js).
73624
73999
  * `"from-first-open"` (the default) builds them on the first open and keeps
@@ -73634,7 +74009,7 @@ const css$C = /* css */`@layer navi {
73634
74009
  * @param {import("ignore:preact").ComponentChildren} props.children
73635
74010
  */
73636
74011
  const Popup = props => {
73637
- import.meta.css = [css$C, "@jsenv/navi/src/layout/popup.jsx"];
74012
+ import.meta.css = [css$A, "@jsenv/navi/src/layout/popup.jsx"];
73638
74013
  const {
73639
74014
  mode: modeProp,
73640
74015
  maxWidth,
@@ -73717,6 +74092,7 @@ const Popup = props => {
73717
74092
  });
73718
74093
  };
73719
74094
  Popup.Close = PopupClose;
74095
+ Popup.Title = PopupTitle;
73720
74096
 
73721
74097
  // What Dialog's own animation values mean to a popover, which has no lift
73722
74098
  // (see the `animation` doc above).
@@ -73765,6 +74141,54 @@ const pickerUIIsNaviOwn = ui => {
73765
74141
  return Boolean(ui) && typeof ui === "object" && Boolean(ui.type?.isPickerOwnUI);
73766
74142
  };
73767
74143
 
74144
+ /*
74145
+ * What the picker really is, said on the element that holds the name and takes
74146
+ * the focus (the host input in picker.jsx), which is both what a screen reader
74147
+ * announces and what getByRole(role, { name }) finds:
74148
+ * - a picker that picks nothing is a door — its popup is a question
74149
+ * (type="confirm") or something to read (mode="callout", which says it for
74150
+ * itself) — and a door is pressed: "button";
74151
+ * - a picker holding a value and opening a popup of navi's is a select-only
74152
+ * combobox: it shows a value the user does not type, and opens what changes
74153
+ * it. A textbox could not say the rest — aria-expanded is not allowed on one,
74154
+ * so "open" and "closed" would never be announced;
74155
+ * - a picker showing the browser's own picker (no popup of navi's) stays the
74156
+ * textbox its <input> is.
74157
+ */
74158
+ const getPickerHostRole = props => {
74159
+ if (props.role !== undefined) {
74160
+ return props.role;
74161
+ }
74162
+ if (props.picksNothing) {
74163
+ return "button";
74164
+ }
74165
+ if (props["aria-haspopup"] !== undefined) {
74166
+ return "combobox";
74167
+ }
74168
+ return undefined;
74169
+ };
74170
+
74171
+ /*
74172
+ * And what that button is called, when the caller wrote it as a drawing rather
74173
+ * than as a word: a door draws no value, so what sits in its value slot IS its
74174
+ * label — the `ui` a caller gave it, or its placeholder. Undefined the moment
74175
+ * the caller names it themselves (aria-label/aria-labelledby), which is the
74176
+ * only way to name a door drawn as an icon. Read by the popup too: what the
74177
+ * user pressed is a fair name for what it opened.
74178
+ */
74179
+ const getPickerHostLabelId = (id, props) => {
74180
+ if (getPickerHostRole(props) !== "button") {
74181
+ return undefined;
74182
+ }
74183
+ if (props["aria-label"] !== undefined || props["aria-labelledby"] !== undefined) {
74184
+ return undefined;
74185
+ }
74186
+ if (props.variant === "headless" || props.ui === "default") {
74187
+ return undefined;
74188
+ }
74189
+ return `${id}_picker_label`;
74190
+ };
74191
+
73768
74192
  /**
73769
74193
  * What the picker holds, read from inside its `ui` — for a drawing given as
73770
74194
  * an element (`ui={<MyCard game={game} />}`), which the picker cannot hand
@@ -73794,7 +74218,7 @@ const usePickerState = () => {
73794
74218
  };
73795
74219
 
73796
74220
  installImportMetaCssBuild(import.meta);
73797
- const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
74221
+ const css$z = /* css */`.navi_picker_callout_dock > [data-picker-content] {
73798
74222
  display: none;
73799
74223
  }
73800
74224
 
@@ -73823,7 +74247,7 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
73823
74247
  }
73824
74248
  }
73825
74249
 
73826
- .navi_picker[aria-haspopup="listbox"][aria-expanded="true"][navi-popover-mode="overlay"] {
74250
+ .navi_picker[navi-popover-mode="overlay"][aria-expanded="true"] {
73827
74251
  border-color: #0000;
73828
74252
  }
73829
74253
 
@@ -73851,7 +74275,7 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
73851
74275
  }
73852
74276
  `;
73853
74277
  const PickerCustomResolver = props => {
73854
- import.meta.css = [css$B, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
74278
+ import.meta.css = [css$z, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
73855
74279
  if (props.children === undefined) {
73856
74280
  return renderResolver(PickerNative, props);
73857
74281
  }
@@ -74022,8 +74446,9 @@ const PickerCustom = props => {
74022
74446
  props.id = props.id || controlId || idDefault;
74023
74447
  // Given no value, the picker holds what the control in its popup holds (see
74024
74448
  // the `mount` decision below) — and a group around it collects that from the
74025
- // moment both exist.
74026
- const valueInPopup = !isControlValueGivenByProps(props);
74449
+ // moment both exist. A picker that picks nothing holds none: its popup's
74450
+ // controls are never adopted (see canRegisterAsFacadeChild).
74451
+ const valueInPopup = !props.picksNothing && !isControlValueGivenByProps(props);
74027
74452
  const parentController = useContext(ParentUIStateControllerContext);
74028
74453
  const answersToGroup = Boolean(parentController) && !props.standalone;
74029
74454
  // Same narrow-container/maxWidth-compact heuristic Popup itself uses (see
@@ -74674,6 +75099,24 @@ const isPressDisputedByHold = pickerEl => Boolean(pickerEl?.parentElement?.close
74674
75099
  const getPickerInput = pickerEl => {
74675
75100
  return pickerEl.querySelector(".navi_picker_input");
74676
75101
  };
75102
+
75103
+ // The <label>s naming a picker's trigger, by id. Read off the document: it is
75104
+ // the one place a label[for] and its control meet (navi's own Label carries an
75105
+ // id for this). A label wrapping the picker is left out — it holds the trigger
75106
+ // and the popup with it, and the popup would be named after its own content.
75107
+ const getTriggerLabelIds = pickerEl => {
75108
+ const labels = pickerEl && getPickerInput(pickerEl)?.labels;
75109
+ if (!labels) {
75110
+ return undefined;
75111
+ }
75112
+ const ids = [];
75113
+ for (const label of labels) {
75114
+ if (label.id && !label.contains(pickerEl)) {
75115
+ ids.push(label.id);
75116
+ }
75117
+ }
75118
+ return ids.length === 0 ? undefined : ids.join(" ");
75119
+ };
74677
75120
  const getPickerInputUIState = pickerEl => {
74678
75121
  const pickerInput = getPickerInput(pickerEl);
74679
75122
  return getUIStateFromElement(pickerInput);
@@ -74696,6 +75139,11 @@ const PickerContentInsidePopup = props => {
74696
75139
  // Same reason: a `data-testid` on the picker names the trigger (see
74697
75140
  // docs/testid.md) — this one names the popup.
74698
75141
  popupTestId,
75142
+ // Same again for the accessible name: aria-label/aria-labelledby on the
75143
+ // picker name the trigger (see control_context.js), these the popup.
75144
+ popupAriaLabel,
75145
+ popupAriaLabelledBy,
75146
+ popupAriaDescribedBy,
74699
75147
  positionArea,
74700
75148
  popoverMode = "nearby",
74701
75149
  popoverSpacing = popoverMode === "nearby" ? 5 : 0,
@@ -74757,8 +75205,25 @@ const PickerContentInsidePopup = props => {
74757
75205
  } = props;
74758
75206
  const isPopover = mode === "popover";
74759
75207
  const isCallout = mode === "callout";
74760
- return jsx(Next, {
74761
- "aria-haspopup": isPopover ? "listbox" : "dialog",
75208
+ // The trigger's name, in the order the browser reads it, for the popup to
75209
+ // take when neither the caller nor a <Popup.Title> names it: "Report this
75210
+ // book" is a fair name for the sheet it opens, "Start date" for a calendar.
75211
+ let openerAriaLabelledBy = rest["aria-labelledby"] ?? getPickerHostLabelId(props.id, props);
75212
+ let openerAriaLabel;
75213
+ if (openerAriaLabelledBy === undefined) {
75214
+ openerAriaLabel = rest["aria-label"];
75215
+ // Read only while open: the labels are a question for the document, and
75216
+ // a closed popup has nobody to announce it.
75217
+ if (openerAriaLabel === undefined && rest["aria-expanded"]) {
75218
+ openerAriaLabelledBy = getTriggerLabelIds(props.ref.current);
75219
+ }
75220
+ }
75221
+ return jsx(Next
75222
+ // A dialog in either mode: what a picker opens is content of the
75223
+ // caller's, never a listbox (a selectable List is a group of radios or
75224
+ // checkboxes). The popover says so with its role, below.
75225
+ , {
75226
+ "aria-haspopup": "dialog",
74762
75227
  "navi-popover-mode": isPopover ? popoverMode : undefined,
74763
75228
  ...rest,
74764
75229
  // On popupProps already (see the picker's popup assembly); they mean
@@ -74803,11 +75268,23 @@ const PickerContentInsidePopup = props => {
74803
75268
  pressOutside: pressOutside,
74804
75269
  children: jsx(PopupModeContext.Provider, {
74805
75270
  value: mode,
74806
- children: children
75271
+ children: jsx(PopupTitleContext.Provider, {
75272
+ value: null,
75273
+ children: children
75274
+ })
74807
75275
  })
74808
75276
  }) : jsx(Popup, {
74809
75277
  ...popupProps,
74810
75278
  "data-testid": popupTestId
75279
+ // What the trigger's aria-haspopup announces; a <dialog> says it
75280
+ // for itself.
75281
+ ,
75282
+ role: isPopover ? "dialog" : undefined,
75283
+ "aria-label": popupAriaLabel,
75284
+ "aria-labelledby": popupAriaLabelledBy,
75285
+ "aria-describedby": popupAriaDescribedBy,
75286
+ openerAriaLabel: openerAriaLabel,
75287
+ openerAriaLabelledBy: openerAriaLabelledBy
74811
75288
  // What the CSS above keys on: this picker's own popup, and no other
74812
75289
  // popup below the picker.
74813
75290
  ,
@@ -75077,7 +75554,7 @@ const showNativePicker = (pickerInput, event) => {
75077
75554
  };
75078
75555
 
75079
75556
  installImportMetaCssBuild(import.meta);
75080
- const css$A = /* css */`.navi_picker_confirm_body {
75557
+ const css$y = /* css */`.navi_picker_confirm_body {
75081
75558
  min-width: 180px;
75082
75559
  max-width: 320px;
75083
75560
  padding: var(--navi-m);
@@ -75093,7 +75570,7 @@ const css$A = /* css */`.navi_picker_confirm_body {
75093
75570
  }
75094
75571
  `;
75095
75572
  const PickerConfirmResolver = props => {
75096
- import.meta.css = [css$A, "@jsenv/navi/src/control/picker/picker_confirm.jsx"];
75573
+ import.meta.css = [css$y, "@jsenv/navi/src/control/picker/picker_confirm.jsx"];
75097
75574
  const Next = useNextResolver();
75098
75575
  if (props.type !== "confirm") {
75099
75576
  return jsx(Next, {
@@ -75167,12 +75644,7 @@ const PickerConfirmResolver = props => {
75167
75644
  mode: mode,
75168
75645
  focusCapture: focusCapture,
75169
75646
  openWhileReadOnly: openWhileReadOnly,
75170
- onConfirm: onConfirm
75171
- // A question holds no value to read before it is asked: the popup is
75172
- // built on the first open, like that of a picker told its value (see
75173
- // the `mount` resolution in picker_custom.jsx).
75174
- ,
75175
- mount: MOUNT_DEFAULT,
75647
+ onConfirm: onConfirm,
75176
75648
  message: undefined,
75177
75649
  confirmLabel: undefined,
75178
75650
  cancelLabel: undefined,
@@ -75619,7 +76091,7 @@ const LoadingIndicator = ({
75619
76091
  };
75620
76092
 
75621
76093
  installImportMetaCssBuild(import.meta);
75622
- const css$z = /* css */`@layer navi {
76094
+ const css$x = /* css */`@layer navi {
75623
76095
  .navi_separator {
75624
76096
  --size: 1px;
75625
76097
  --color: var(--navi-separator-color-default);
@@ -75693,7 +76165,7 @@ const Separator = ({
75693
76165
  style,
75694
76166
  ...props
75695
76167
  }) => {
75696
- import.meta.css = [css$z, "@jsenv/navi/src/layout/separator.jsx"];
76168
+ import.meta.css = [css$x, "@jsenv/navi/src/layout/separator.jsx"];
75697
76169
  return jsx(Box, {
75698
76170
  as: vertical ? "span" : "hr",
75699
76171
  ...props,
@@ -76487,7 +76959,7 @@ const listItemBlockedMessage = (loading, props) => {
76487
76959
  };
76488
76960
 
76489
76961
  installImportMetaCssBuild(import.meta);
76490
- const css$y = /* css */`@layer navi {
76962
+ const css$w = /* css */`@layer navi {
76491
76963
  .navi_list_container[navi-selectable] {
76492
76964
  --list-item-outline-width: var(--navi-focus-outline-width);
76493
76965
  --list-item-outline-offset: calc(-1 * var(--list-item-outline-width));
@@ -76660,7 +77132,7 @@ const ListSelectableResolver = props => {
76660
77132
  };
76661
77133
  const ListSelectable = props => {
76662
77134
  const Next = useNextResolver();
76663
- import.meta.css = [css$y, "@jsenv/navi/src/control/list/list_selectable.jsx"];
77135
+ import.meta.css = [css$w, "@jsenv/navi/src/control/list/list_selectable.jsx"];
76664
77136
  // we allow ourselves to auto-generate a name
76665
77137
  const defaultName = useId();
76666
77138
  props.name = props.name || `listbox_${defaultName}`;
@@ -77329,7 +77801,7 @@ const ListRunItemContext = createContext(null);
77329
77801
  // item takes its place in the collection by slot: the place is then the list's
77330
77802
  // to move, and the item's to follow — see list_items.js.
77331
77803
  const ListSlotContext = createContext(null);
77332
- const css$x = /* css */`@property --list-group-label-height {
77804
+ const css$v = /* css */`@property --list-group-label-height {
77333
77805
  syntax: "<length>";
77334
77806
  inherits: false;
77335
77807
  initial-value: 0;
@@ -77857,7 +78329,7 @@ const resolveRenderBudget = renderBudget => {
77857
78329
  };
77858
78330
  };
77859
78331
  const ListUI = props => {
77860
- import.meta.css = [css$x, "@jsenv/navi/src/control/list/list.jsx"];
78332
+ import.meta.css = [css$v, "@jsenv/navi/src/control/list/list.jsx"];
77861
78333
  const {
77862
78334
  ref,
77863
78335
  renderBudget: renderBudgetProp,
@@ -84147,7 +84619,7 @@ const createBadgeRegistry = () => {
84147
84619
  };
84148
84620
 
84149
84621
  installImportMetaCssBuild(import.meta);
84150
- const css$w = /* css */`@layer navi;
84622
+ const css$u = /* css */`@layer navi;
84151
84623
 
84152
84624
  .navi_badge {
84153
84625
  --font-size: .7em;
@@ -84250,7 +84722,7 @@ const BadgeUI = ({
84250
84722
  className,
84251
84723
  ...props
84252
84724
  }) => {
84253
- import.meta.css = [css$w, "@jsenv/navi/src/text/badge.jsx"];
84725
+ import.meta.css = [css$u, "@jsenv/navi/src/text/badge.jsx"];
84254
84726
  const defaultRef = useRef();
84255
84727
  props.ref = props.ref || defaultRef;
84256
84728
  const {
@@ -84358,7 +84830,7 @@ const wrapLabels = childArray => {
84358
84830
  };
84359
84831
 
84360
84832
  installImportMetaCssBuild(import.meta);
84361
- const css$v = /* css */`@layer navi;
84833
+ const css$t = /* css */`@layer navi;
84362
84834
 
84363
84835
  .navi_badge_list {
84364
84836
  flex-wrap: wrap;
@@ -84493,7 +84965,7 @@ const useBadgeRegistry = (children, enabled) => {
84493
84965
  * component grants (a <Picker>, see max_lines_context.js).
84494
84966
  */
84495
84967
  const BadgeList = props => {
84496
- import.meta.css = [css$v, "@jsenv/navi/src/text/badge_list.jsx"];
84968
+ import.meta.css = [css$t, "@jsenv/navi/src/text/badge_list.jsx"];
84497
84969
  const {
84498
84970
  maxLines,
84499
84971
  max,
@@ -84837,7 +85309,7 @@ const BadgeListContent = ({
84837
85309
  };
84838
85310
 
84839
85311
  installImportMetaCssBuild(import.meta);
84840
- const css$u = /* css */`.navi_color {
85312
+ const css$s = /* css */`.navi_color {
84841
85313
  aspect-ratio: 1;
84842
85314
  background-color: currentColor;
84843
85315
  border: 1px solid #0003;
@@ -84857,7 +85329,7 @@ const Color = ({
84857
85329
  children,
84858
85330
  ...rest
84859
85331
  }) => {
84860
- import.meta.css = [css$u, "@jsenv/navi/src/text/color.jsx"];
85332
+ import.meta.css = [css$s, "@jsenv/navi/src/text/color.jsx"];
84861
85333
  const color = children || undefined;
84862
85334
  return jsx(Box, {
84863
85335
  as: "span",
@@ -85344,7 +85816,7 @@ const PickerFileUI = /*#__PURE__*/asPickerOwnUI(() => {
85344
85816
  });
85345
85817
 
85346
85818
  installImportMetaCssBuild(import.meta);
85347
- const css$t = /* css */`@layer navi {
85819
+ const css$r = /* css */`@layer navi {
85348
85820
  .navi_picker {
85349
85821
  --picker-border-radius: var(--navi-control-border-radius);
85350
85822
  --picker-border-width: var(--navi-control-border-width);
@@ -85813,7 +86285,7 @@ const css$t = /* css */`@layer navi {
85813
86285
  }
85814
86286
  `;
85815
86287
  const PickerButton = props => {
85816
- import.meta.css = [css$t, "@jsenv/navi/src/control/picker/picker.jsx"];
86288
+ import.meta.css = [css$r, "@jsenv/navi/src/control/picker/picker.jsx"];
85817
86289
  warnOnUnknownPickerType(props);
85818
86290
  warnOnUIDrawnByNobody(props);
85819
86291
  if (typeof props.maxLines === "string") {
@@ -85856,11 +86328,6 @@ const PickerButton = props => {
85856
86328
  // confirm resolver (see picker_confirm.jsx), which is the picker that is
85857
86329
  // one — a caller whose popup is a menu of actions says it for themselves.
85858
86330
  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
86331
  readOnly,
85865
86332
  error,
85866
86333
  // Who draws the loading outline. Left unsaid, the picker does, around its
@@ -85943,21 +86410,8 @@ const PickerButton = props => {
85943
86410
  // clearing being a modification like any other.
85944
86411
  const interactive = !basePseudoState[":disabled"] && !basePseudoState[":read-only"] && !loading;
85945
86412
  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;
86413
+ const hostRole = getPickerHostRole(props);
86414
+ const hostLabelId = getPickerHostLabelId(inputProps.id, props);
85961
86415
  // What the picker knows about itself, for the pieces it does not place: the
85962
86416
  // drawings of its value (Picker.UI.*), and the affordances a caller may put
85963
86417
  // in their own `ui` (Picker.Clear) as much as the ones it puts in its slot.
@@ -86026,8 +86480,16 @@ const PickerButton = props => {
86026
86480
  basePseudoState: basePseudoState,
86027
86481
  styleCSSVars: PickerStyleCSSVars,
86028
86482
  variant: undefined,
86029
- icon: undefined,
86483
+ icon: undefined
86484
+ // The caller's own role goes onto the host, beside the name (see
86485
+ // control_context.js): a role and a name on two different elements
86486
+ // name nothing at all. What the trigger opens is said there too, and
86487
+ // aria-haspopup with it. aria-expanded stays here as well: commands.js
86488
+ // and the CSS read the open state off the root (see
86489
+ // expandableRepeatedBy in commands.js).
86490
+ ,
86030
86491
  role: undefined,
86492
+ "aria-haspopup": undefined,
86031
86493
  rightSlotIcon: undefined,
86032
86494
  rightSlotIconSize: undefined,
86033
86495
  rightSlot: undefined,
@@ -86067,6 +86529,8 @@ const PickerButton = props => {
86067
86529
  inset: loadingOutlineInset
86068
86530
  }), jsx(PickerInput, {
86069
86531
  role: hostRole,
86532
+ "aria-haspopup": props["aria-haspopup"],
86533
+ "aria-expanded": props["aria-expanded"],
86070
86534
  "aria-labelledby": hostLabelId,
86071
86535
  tabIndex: variant === "headless" ? -1 : undefined,
86072
86536
  "aria-hidden": variant === "headless" ? "true" : undefined,
@@ -86574,6 +87038,10 @@ const PickerFirstResolver = props => {
86574
87038
  * Without `children`, opens the browser-native picker for the given `type`.
86575
87039
  * With `children`, opens a popover (desktop) or dialog (mobile) containing the children.
86576
87040
  * Pass `mode="popover"` or `mode="dialog"` to override the automatic choice.
87041
+ * Either way the popup is a `dialog`, named by a `<Popup.Title>` in the
87042
+ * children (see `popupAriaLabelledBy`), and the trigger answers to `combobox` —
87043
+ * `button` under `picksNothing` — with `aria-haspopup="dialog"` and
87044
+ * `aria-expanded` (see docs/testid.md).
86577
87045
  *
86578
87046
  * `type="confirm"` is the picker that asks "are you sure?" before doing what it
86579
87047
  * stands for: its popup is the question, `ui` its label, and yes runs its
@@ -86647,6 +87115,9 @@ const PickerFirstResolver = props => {
86647
87115
  * standalone?: boolean,
86648
87116
  * popupLayer?: "top" | "local",
86649
87117
  * popupTestId?: string,
87118
+ * popupAriaLabel?: string,
87119
+ * popupAriaLabelledBy?: string,
87120
+ * popupAriaDescribedBy?: string,
86650
87121
  * confirmTestId?: string,
86651
87122
  * cancelTestId?: string,
86652
87123
  * mount?: "always" | "idle" | "from-first-open" | "while-opened",
@@ -87106,6 +87577,19 @@ const PickerFirstResolver = props => {
87106
87577
  * not only what it holds, which a testid inside the children already names.
87107
87578
  * Under `mode="callout"` it names the callout element — the control's own,
87108
87579
  * shared with its constraint messages — while the children are shown in it.
87580
+ * @param {string} [popupAriaLabelledBy] The id(s) of what names the popup:
87581
+ * `aria-labelledby` on the popup element, which is a dialog in either mode
87582
+ * (the trigger says `aria-haspopup="dialog"`). `aria-label`/`aria-labelledby`
87583
+ * on the picker itself name the trigger. Rarely needed: a `<Popup.Title>`
87584
+ * in the children names the popup with nothing to wire, and without one
87585
+ * the popup takes the trigger's own name — the picker's `aria-labelledby`,
87586
+ * the label a picker that picks nothing draws, its `aria-label`, or the
87587
+ * `<Label>`/`<label for>` pointing at it (one that has an id, and does not
87588
+ * wrap the picker). A `mode="callout"` popup is read as its message.
87589
+ * @param {string} [popupAriaLabel] The same, as a string, for a popup whose
87590
+ * title is not an element.
87591
+ * @param {string} [popupAriaDescribedBy] The id(s) of what describes the
87592
+ * popup: `aria-describedby` on the popup element.
87109
87593
  * @param {string} [confirmTestId] `type="confirm"`, default body: the
87110
87594
  * `data-testid` of the yes button; `cancelTestId` that of the no button.
87111
87595
  * Both are elements navi builds, which a test may not name by class (see
@@ -87118,8 +87602,9 @@ const PickerFirstResolver = props => {
87118
87602
  * it (no `ui`, a `Picker.UI.*`, a component `ui`), when there is a clear
87119
87603
  * cross, or when a form or group around the picker collects it; when an
87120
87604
  * element `ui` calls `usePickerState()`. Read by nothing, it waits for the
87121
- * first open, like a picker told a value. A `type="confirm"` picker never
87122
- * builds them early: its popup is a question, there is nothing to read.
87605
+ * first open, like a picker told a value. A `picksNothing` picker (and so
87606
+ * `type="confirm"`) never builds them early: its popup holds no value, there
87607
+ * is nothing to read.
87123
87608
  */
87124
87609
  // Every picker type is resolved into one of these before it gets here (see
87125
87610
  // PickerTypeResolver and resolveInputProps): a native input type, or navi_js
@@ -87243,7 +87728,7 @@ const Picker = /*#__PURE__*/Object.assign(PickerResolved, {
87243
87728
  });
87244
87729
 
87245
87730
  installImportMetaCssBuild(import.meta);
87246
- const css$s = /* css */`@layer navi {
87731
+ const css$q = /* css */`@layer navi {
87247
87732
  .navi_picker_spin {
87248
87733
  --picker-spin-padding-x-default: var(--navi-picker-padding-x-default);
87249
87734
  --picker-spin-padding-y-default: var(--navi-picker-padding-y-default);
@@ -87593,7 +88078,7 @@ const Spin = ({
87593
88078
  nextLabel,
87594
88079
  ...rest
87595
88080
  }) => {
87596
- import.meta.css = [css$s, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
88081
+ import.meta.css = [css$q, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
87597
88082
  const id = useId();
87598
88083
  // What the group around it says, when there is one: how big the whole thing
87599
88084
  // is written is said once, on the group, and every spin in it follows.
@@ -88304,7 +88789,7 @@ const renderValueDefault = value => String(value ?? "");
88304
88789
  * are passed on to the spins sitting in them.
88305
88790
  */
88306
88791
  const SpinGroup = props => {
88307
- import.meta.css = [css$s, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
88792
+ import.meta.css = [css$q, "@jsenv/navi/src/control/picker/picker_spin.jsx"];
88308
88793
  const {
88309
88794
  size
88310
88795
  } = props;
@@ -88813,7 +89298,7 @@ const TimeRangeSpin = ({
88813
89298
  };
88814
89299
 
88815
89300
  installImportMetaCssBuild(import.meta);
88816
- const css$r = /* css */`.navi_checkbox_group {
89301
+ const css$p = /* css */`.navi_checkbox_group {
88817
89302
  border-style: solid;
88818
89303
 
88819
89304
  &[data-callout] {
@@ -88873,7 +89358,7 @@ const CheckboxGroup = props => {
88873
89358
  return checkboxGroup;
88874
89359
  };
88875
89360
  const CheckboxGroupInterface = props => {
88876
- import.meta.css = [css$r, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
89361
+ import.meta.css = [css$p, "@jsenv/navi/src/control/input/checkbox_group.jsx"];
88877
89362
  const {
88878
89363
  ref
88879
89364
  } = props;
@@ -88991,7 +89476,7 @@ const formatIntlUnit = (unit, {
88991
89476
  };
88992
89477
 
88993
89478
  installImportMetaCssBuild(import.meta);
88994
- const css$q = /* css */`.navi_input_duration {
89479
+ const css$o = /* css */`.navi_input_duration {
88995
89480
  --duration-separator-spacing: 4px;
88996
89481
  --loader-color: var(--navi-loader-color);
88997
89482
  position: relative;
@@ -89056,7 +89541,7 @@ const css$q = /* css */`.navi_input_duration {
89056
89541
  * "auto" aligns each field toward its neighbouring separator (first→right, last→left, middle/solo→center).
89057
89542
  */
89058
89543
  const InputDuration = props => {
89059
- import.meta.css = [css$q, "@jsenv/navi/src/control/input/input_duration.jsx"];
89544
+ import.meta.css = [css$o, "@jsenv/navi/src/control/input/input_duration.jsx"];
89060
89545
  const defaultRef = useRef();
89061
89546
  props.ref = props.ref || defaultRef;
89062
89547
  props.max = props.max || "23h59";
@@ -89557,7 +90042,7 @@ const InputDurationPart = ({
89557
90042
  };
89558
90043
 
89559
90044
  installImportMetaCssBuild(import.meta);
89560
- const css$p = /* css */`.navi_radio_group {
90045
+ const css$n = /* css */`.navi_radio_group {
89561
90046
  border-style: solid;
89562
90047
 
89563
90048
  &[data-callout] {
@@ -89597,7 +90082,7 @@ const RadioGroup = props => {
89597
90082
  return radioGroup;
89598
90083
  };
89599
90084
  const RadioGroupInterface = props => {
89600
- import.meta.css = [css$p, "@jsenv/navi/src/control/input/radio_group.jsx"];
90085
+ import.meta.css = [css$n, "@jsenv/navi/src/control/input/radio_group.jsx"];
89601
90086
  const {
89602
90087
  ref
89603
90088
  } = props;
@@ -89627,7 +90112,7 @@ const RadioGroupInterface = props => {
89627
90112
  };
89628
90113
 
89629
90114
  installImportMetaCssBuild(import.meta);
89630
- const css$o = /* css */`.navi_input.navi_select {
90115
+ const css$m = /* css */`.navi_input.navi_select {
89631
90116
  & .navi_control_input {
89632
90117
  padding-right: calc(var(--x-padding-right) + 1em);
89633
90118
  line-height: var(--navi-control-line-height);
@@ -89674,7 +90159,7 @@ const Select = ({
89674
90159
  ...props
89675
90160
  }) => {
89676
90161
  installInputCss();
89677
- import.meta.css = [css$o, "@jsenv/navi/src/control/input/select.jsx"];
90162
+ import.meta.css = [css$m, "@jsenv/navi/src/control/input/select.jsx"];
89678
90163
  const defaultRef = useRef(null);
89679
90164
  props.ref = props.ref || defaultRef;
89680
90165
  seedDefaultValueFromSignal(props);
@@ -89727,7 +90212,7 @@ const Select = ({
89727
90212
  };
89728
90213
 
89729
90214
  installImportMetaCssBuild(import.meta);
89730
- const css$n = /* css */`.navi_split_button {
90215
+ const css$l = /* css */`.navi_split_button {
89731
90216
  border-radius: var(--navi-control-border-radius);
89732
90217
  display: inline-flex;
89733
90218
  position: relative;
@@ -89849,7 +90334,7 @@ const css$n = /* css */`.navi_split_button {
89849
90334
  * on the split button's own box.
89850
90335
  */
89851
90336
  const SplitButton = props => {
89852
- import.meta.css = [css$n, "@jsenv/navi/src/control/input/split_button.jsx"];
90337
+ import.meta.css = [css$l, "@jsenv/navi/src/control/input/split_button.jsx"];
89853
90338
  const {
89854
90339
  options = [],
89855
90340
  value,
@@ -90868,7 +91353,7 @@ const createItemTracker = (onChange) => {
90868
91353
  };
90869
91354
 
90870
91355
  installImportMetaCssBuild(import.meta);
90871
- const css$m = /* css */`.navi_wheel_container {
91356
+ const css$k = /* css */`.navi_wheel_container {
90872
91357
  --wheel-item-height: round(1.8em, 1px);
90873
91358
  --wheel-emphasis-size: round(1.5em, 1px);
90874
91359
  --wheel-item-width: 3.5ch;
@@ -91817,7 +92302,7 @@ const useWheelKeyboard = ({
91817
92302
  }, [isHorizontal, interactive, isLoop]);
91818
92303
  };
91819
92304
  function WheelUI(props) {
91820
- import.meta.css = [css$m, "@jsenv/navi/src/control/wheel/wheel.jsx"];
92305
+ import.meta.css = [css$k, "@jsenv/navi/src/control/wheel/wheel.jsx"];
91821
92306
  const {
91822
92307
  ref,
91823
92308
  visibleCount = 3,
@@ -92814,7 +93299,7 @@ Wheel.Item = WheelItem;
92814
93299
  * @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
93300
  */
92816
93301
  const WheelGroup = props => {
92817
- import.meta.css = [css$m, "@jsenv/navi/src/control/wheel/wheel.jsx"];
93302
+ import.meta.css = [css$k, "@jsenv/navi/src/control/wheel/wheel.jsx"];
92818
93303
  // WheelGroup IS a control group: it aggregates its named wheels ("hours",
92819
93304
  // "minutes"…) into one object value, so it can sit directly inside a Form or a
92820
93305
  // Picker with no extra <ControlGroup> wrapper. The wheel-specific presentation
@@ -92987,7 +93472,7 @@ const WheelColon = props => {
92987
93472
  Wheel.Colon = WheelColon;
92988
93473
 
92989
93474
  installImportMetaCssBuild(import.meta);
92990
- const css$l = /* css */`.navi_time_range_label {
93475
+ const css$j = /* css */`.navi_time_range_label {
92991
93476
  --wheel-item-height: round(1.8em, 1px);
92992
93477
  color: var(--wheel-color, light-dark(#111, #eee));
92993
93478
  line-height: var(--wheel-item-height);
@@ -93211,7 +93696,7 @@ const TimeRangeWheel = ({
93211
93696
  endTimeProps,
93212
93697
  ...rest
93213
93698
  }) => {
93214
- import.meta.css = [css$l, "@jsenv/navi/src/control/wheel/wheel_time.jsx"];
93699
+ import.meta.css = [css$j, "@jsenv/navi/src/control/wheel/wheel_time.jsx"];
93215
93700
  const startId = useId();
93216
93701
  const startRef = useRef(null);
93217
93702
  const endRef = useRef(null);
@@ -93799,7 +94284,7 @@ const Z_INDEX_DROP_PREVIEW = Z_INDEX_STICKY_CORNER + 1;
93799
94284
  const Z_INDEX_TABLE_UI = Z_INDEX_STICKY_CORNER + 1;
93800
94285
 
93801
94286
  installImportMetaCssBuild(import.meta);
93802
- const css$k = /* css */`.navi_table_drag_clone_container {
94287
+ const css$i = /* css */`.navi_table_drag_clone_container {
93803
94288
  top: var(--table-visual-top);
93804
94289
  left: var(--table-visual-left);
93805
94290
  width: var(--table-visual-width);
@@ -93954,7 +94439,7 @@ const useTableDragContextValue = ({
93954
94439
  }, [grabTarget, canChangeColumnOrder]);
93955
94440
  };
93956
94441
  const TableDragCloneContainer = /*#__PURE__*/forwardRef((props, ref) => {
93957
- import.meta.css = [css$k, "@jsenv/navi/src/control/table/drag/table_drag.jsx"];
94442
+ import.meta.css = [css$i, "@jsenv/navi/src/control/table/drag/table_drag.jsx"];
93958
94443
  const {
93959
94444
  tableId
93960
94445
  } = props;
@@ -94253,7 +94738,7 @@ const ROW_MIN_HEIGHT = 30;
94253
94738
  const ROW_MAX_HEIGHT = 100;
94254
94739
  const COLUMN_MIN_WIDTH = 50;
94255
94740
  const COLUMN_MAX_WIDTH = 500;
94256
- const css$j = /* css */`@layer navi {
94741
+ const css$h = /* css */`@layer navi {
94257
94742
  .navi_table {
94258
94743
  --table-resizer-handle-color: #063b7c;
94259
94744
  --table-resizer-color: #387ec9;
@@ -94422,7 +94907,7 @@ const css$j = /* css */`@layer navi {
94422
94907
 
94423
94908
  // Column resize components
94424
94909
  const TableColumnResizer = props => {
94425
- import.meta.css = [css$j, "@jsenv/navi/src/control/table/resize/table_resize.jsx"];
94910
+ import.meta.css = [css$h, "@jsenv/navi/src/control/table/resize/table_resize.jsx"];
94426
94911
  const defaultRef = useRef();
94427
94912
  const ref = props.ref || defaultRef;
94428
94913
  return jsxs("div", {
@@ -94894,7 +95379,7 @@ const findPreviousTableRow = currentRow => {
94894
95379
  };
94895
95380
 
94896
95381
  installImportMetaCssBuild(import.meta);
94897
- const css$i = /* css */`@layer navi {
95382
+ const css$g = /* css */`@layer navi {
94898
95383
  .navi_table {
94899
95384
  --selection-border-color: var(--navi-selection-border-color, #0078d4);
94900
95385
  --selection-background-color: var(--navi-selection-background-color, #eaf1fd);
@@ -94988,7 +95473,7 @@ const useTableSelectionController = ({
94988
95473
  onSelectionChange,
94989
95474
  selectionColor
94990
95475
  }) => {
94991
- import.meta.css = [css$i, "@jsenv/navi/src/control/table/selection/table_selection.jsx"];
95476
+ import.meta.css = [css$g, "@jsenv/navi/src/control/table/selection/table_selection.jsx"];
94992
95477
  const selectionController = useSelectionController({
94993
95478
  elementRef: tableRef,
94994
95479
  layout: "grid",
@@ -95459,7 +95944,7 @@ const useTableStickyContextValue = ({
95459
95944
  };
95460
95945
 
95461
95946
  installImportMetaCssBuild(import.meta);
95462
- const css$h = /* css */`@layer navi {
95947
+ const css$f = /* css */`@layer navi {
95463
95948
  .navi_table {
95464
95949
  --sticky-frontier-color: silver;
95465
95950
  --sticky-frontier-size: 12px;
@@ -95632,7 +96117,7 @@ const css$h = /* css */`@layer navi {
95632
96117
  const TableStickyFrontier = ({
95633
96118
  tableRef
95634
96119
  }) => {
95635
- import.meta.css = [css$h, "@jsenv/navi/src/control/table/sticky/table_sticky.jsx"];
96120
+ import.meta.css = [css$f, "@jsenv/navi/src/control/table/sticky/table_sticky.jsx"];
95636
96121
  const stickyLeftFrontierGhostRef = useRef();
95637
96122
  const stickyLeftFrontierPreviewRef = useRef();
95638
96123
  const stickyTopFrontierGhostRef = useRef();
@@ -95863,7 +96348,7 @@ installImportMetaCssBuild(import.meta);
95863
96348
  * inset 0 -1px 0 0 color; // Bottom border
95864
96349
  */
95865
96350
 
95866
- const css$g = /* css */ `.navi_table_root {
96351
+ const css$e = /* css */ `.navi_table_root {
95867
96352
  max-width: var(--table-max-width, none);
95868
96353
  max-height: var(--table-max-height, none);
95869
96354
  position: relative;
@@ -96048,11 +96533,11 @@ const css$g = /* css */ `.navi_table_root {
96048
96533
  // table must not carry this sheet, and a build that sees no caller drops the
96049
96534
  // css with the function.
96050
96535
  const installTableCss = () => {
96051
- import.meta.css = [css$g, "@jsenv/navi/src/control/table/table_css.js"];
96536
+ import.meta.css = [css$e, "@jsenv/navi/src/control/table/table_css.js"];
96052
96537
  };
96053
96538
 
96054
96539
  installImportMetaCssBuild(import.meta);
96055
- const css$f = /* css */`.navi_table_ui {
96540
+ const css$d = /* css */`.navi_table_ui {
96056
96541
  z-index: ${Z_INDEX_TABLE_UI};
96057
96542
  pointer-events: none;
96058
96543
  position: fixed;
@@ -96061,7 +96546,7 @@ const css$f = /* css */`.navi_table_ui {
96061
96546
  }
96062
96547
  `;
96063
96548
  const TableUI = /*#__PURE__*/forwardRef((props, ref) => {
96064
- import.meta.css = [css$f, "@jsenv/navi/src/control/table/table_ui.jsx"];
96549
+ import.meta.css = [css$d, "@jsenv/navi/src/control/table/table_ui.jsx"];
96065
96550
  const {
96066
96551
  tableRef,
96067
96552
  tableId,
@@ -96983,7 +97468,7 @@ const normalizeKey = (key) => {
96983
97468
  };
96984
97469
 
96985
97470
  installImportMetaCssBuild(import.meta);
96986
- const css$e = /* css */`.navi_shortcut_container[data-visually-hidden], .navi_shortcut_button[data-visually-hidden] {
97471
+ const css$c = /* css */`.navi_shortcut_container[data-visually-hidden], .navi_shortcut_button[data-visually-hidden] {
96987
97472
  clip: rect(0, 0, 0, 0);
96988
97473
  white-space: nowrap;
96989
97474
  opacity: 0;
@@ -97000,7 +97485,7 @@ const css$e = /* css */`.navi_shortcut_container[data-visually-hidden], .navi_sh
97000
97485
  const ActiveKeyboardShortcuts = ({
97001
97486
  visible
97002
97487
  }) => {
97003
- import.meta.css = [css$e, "@jsenv/navi/src/keyboard/active_keyboard_shortcuts.jsx"];
97488
+ import.meta.css = [css$c, "@jsenv/navi/src/keyboard/active_keyboard_shortcuts.jsx"];
97004
97489
  const activeShortcuts = activeShortcutsSignal.value;
97005
97490
  return jsx("div", {
97006
97491
  className: "navi_shortcut_container",
@@ -97040,7 +97525,7 @@ const KeyboardShortcutAriaElement = ({
97040
97525
  };
97041
97526
 
97042
97527
  installImportMetaCssBuild(import.meta);
97043
- const css$d = /* css */`@layer navi {
97528
+ const css$b = /* css */`@layer navi {
97044
97529
  .navi_clipboard_container {
97045
97530
  --height: 1.5em;
97046
97531
  --notif-spacing: .5em;
@@ -97071,7 +97556,7 @@ const ButtonCopyToClipboard = ({
97071
97556
  children,
97072
97557
  ...props
97073
97558
  }) => {
97074
- import.meta.css = [css$d, "@jsenv/navi/src/control/input/button_copy_to_clipboard.jsx"];
97559
+ import.meta.css = [css$b, "@jsenv/navi/src/control/input/button_copy_to_clipboard.jsx"];
97075
97560
  const [copied, setCopied] = useState(false);
97076
97561
  const renderedRef = useRef();
97077
97562
  useEffect(() => {
@@ -97151,7 +97636,7 @@ const Address = ({
97151
97636
  };
97152
97637
 
97153
97638
  installImportMetaCssBuild(import.meta);
97154
- const css$c = /* css */`@layer navi;
97639
+ const css$a = /* css */`@layer navi;
97155
97640
 
97156
97641
  .navi_text.navi_badge_count {
97157
97642
  white-space: nowrap;
@@ -97280,7 +97765,7 @@ const BadgeCount = ({
97280
97765
  lineLayout,
97281
97766
  ...props
97282
97767
  }) => {
97283
- import.meta.css = [css$c, "@jsenv/navi/src/text/badge_count.jsx"];
97768
+ import.meta.css = [css$a, "@jsenv/navi/src/text/badge_count.jsx"];
97284
97769
  const defaultRef = useRef();
97285
97770
  props.ref = props.ref || defaultRef;
97286
97771
  const {
@@ -97423,7 +97908,7 @@ const BadgeCountCircle = ({
97423
97908
  };
97424
97909
 
97425
97910
  installImportMetaCssBuild(import.meta);
97426
- const css$b = /* css */`@layer navi {
97911
+ const css$9 = /* css */`@layer navi {
97427
97912
  .navi_caption {
97428
97913
  --color: #6b7280;
97429
97914
  }
@@ -97443,7 +97928,7 @@ const Caption = ({
97443
97928
  className,
97444
97929
  ...rest
97445
97930
  }) => {
97446
- import.meta.css = [css$b, "@jsenv/navi/src/text/caption.jsx"];
97931
+ import.meta.css = [css$9, "@jsenv/navi/src/text/caption.jsx"];
97447
97932
  return jsx(Text, {
97448
97933
  as: "small",
97449
97934
  size: "0.8em" // We use em to be relative to the parent (we want to be smaller than the surrounding text)
@@ -97839,177 +98324,8 @@ const Interpolate = ({
97839
98324
  });
97840
98325
  };
97841
98326
 
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
- 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
98327
  installImportMetaCssBuild(import.meta);
98012
- const css$9 = /* css */`@layer navi;
98328
+ const css$8 = /* css */`@layer navi;
98013
98329
 
98014
98330
  .navi_quantity {
98015
98331
  flex-direction: column;
@@ -98110,7 +98426,7 @@ const Quantity = ({
98110
98426
  bold = true,
98111
98427
  ...props
98112
98428
  }) => {
98113
- import.meta.css = [css$9, "@jsenv/navi/src/text/quantity.jsx"];
98429
+ import.meta.css = [css$8, "@jsenv/navi/src/text/quantity.jsx"];
98114
98430
  const value = parseQuantityValue(children);
98115
98431
  const valueRounded = integer && typeof value === "number" ? Math.round(value) : value;
98116
98432
  const valueFormatted = typeof valueRounded === "number" ? formatNumber(valueRounded, {
@@ -98169,7 +98485,7 @@ const parseQuantityValue = children => {
98169
98485
  };
98170
98486
 
98171
98487
  installImportMetaCssBuild(import.meta);
98172
- const css$8 = /* css */`@layer navi {
98488
+ const css$7 = /* css */`@layer navi {
98173
98489
  .navi_meter {
98174
98490
  --loader-color: var(--navi-loader-color);
98175
98491
  --track-color: #efefef;
@@ -98308,7 +98624,7 @@ const Meter = ({
98308
98624
  style,
98309
98625
  ...rest
98310
98626
  }) => {
98311
- import.meta.css = [css$8, "@jsenv/navi/src/text/meter.jsx"];
98627
+ import.meta.css = [css$7, "@jsenv/navi/src/text/meter.jsx"];
98312
98628
  const defaultRef = useRef();
98313
98629
  const ref = rest.ref || defaultRef;
98314
98630
  value = Number(value);
@@ -98423,7 +98739,7 @@ const Paragraph = props => {
98423
98739
  };
98424
98740
 
98425
98741
  installImportMetaCssBuild(import.meta);
98426
- const css$7 = /* css */`.navi_text_box {
98742
+ const css$6 = /* css */`.navi_text_box {
98427
98743
  align-items: flex-start;
98428
98744
  min-width: 0;
98429
98745
  }
@@ -98465,7 +98781,7 @@ const TextBox = ({
98465
98781
  children,
98466
98782
  ...rest
98467
98783
  }) => {
98468
- import.meta.css = [css$7, "@jsenv/navi/src/text/text_box.jsx"];
98784
+ import.meta.css = [css$6, "@jsenv/navi/src/text/text_box.jsx"];
98469
98785
  const boxRef = useRef(null);
98470
98786
  const contentRef = useRef(null);
98471
98787
  useLayoutEffect(() => {
@@ -98511,40 +98827,6 @@ const adjustWidth = (boxEl, contentEl) => {
98511
98827
  contentEl.style.width = `${Math.ceil(optimalWidth)}px`;
98512
98828
  };
98513
98829
 
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
98830
  installImportMetaCssBuild(import.meta);
98549
98831
  const css$5 = /* css */`@keyframes navi_image_shimmer {
98550
98832
  0% {