@jsenv/navi 0.29.154 → 0.29.156

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.
@@ -5432,6 +5432,11 @@ const CONTROL_ATTRIBUTE_SET = new Set([
5432
5432
  "spellcheck",
5433
5433
  "autoCorrect",
5434
5434
  "aria-controls",
5435
+ // The name goes where the role is: the root box has none, so an aria-label
5436
+ // left on it names nothing at all, while the host is what the user focuses
5437
+ // and what a screen reader announces (and what getByRole({ name }) reads).
5438
+ "aria-label",
5439
+ "aria-labelledby",
5435
5440
  "tabIndex",
5436
5441
  "command",
5437
5442
  "commandFor",
@@ -6419,6 +6424,11 @@ const css$12 = /* css */`
6419
6424
  * @param {Object} options - Configuration options
6420
6425
  * @param {HTMLElement} [options.anchorElement] - Element the callout should follow. If not provided or too big, callout will be centered in viewport
6421
6426
  * @param {string} [options.status=""] - Callout status: "info" | "warning" | "error" | "success"
6427
+ * @param {string} [options.testId] - `data-testid` on the callout element. The callout is
6428
+ * drawn by navi, so nothing the caller renders can carry the name a test needs — same
6429
+ * situation as a picker's `popupTestId` (see docs/testid.md). A role is already there
6430
+ * for free (`status`/`alert`); reach for this when the role is not enough to tell two
6431
+ * callouts apart.
6422
6432
  * @param {Function} [options.onClose] - Callback when callout is closed
6423
6433
  * @param {boolean} [options.closeOnClickOutside] - Whether to close on outside clicks (defaults to true for "info" status)
6424
6434
  * @param {boolean} [options.icon=true] - Whether the status icon is shown beside the message.
@@ -6465,6 +6475,7 @@ const openCallout = (message, {
6465
6475
  // "success" - positive feedback (e.g., "Changes saved successfully")
6466
6476
  // "" - neutral information
6467
6477
  status = "",
6478
+ testId,
6468
6479
  onClose,
6469
6480
  closeOnClickOutside = status === "info",
6470
6481
  closeOnFocusLeave = closeOnClickOutside,
@@ -6642,6 +6653,15 @@ const openCallout = (message, {
6642
6653
  calloutElement.removeAttribute("data-icon");
6643
6654
  }
6644
6655
  }
6656
+ if (Object.hasOwn(options, "testId")) {
6657
+ // Per message, like the cross below: a callout is a slot several things
6658
+ // speak through, and the one speaking is what a test is looking at.
6659
+ if (options.testId) {
6660
+ calloutElement.setAttribute("data-testid", options.testId);
6661
+ } else {
6662
+ calloutElement.removeAttribute("data-testid");
6663
+ }
6664
+ }
6645
6665
  if (Object.hasOwn(options, "closeButton")) {
6646
6666
  // Per message rather than per callout: what replaces a message (a
6647
6667
  // constraint taking over a tooltip's callout) brings its own cross back.
@@ -7042,6 +7062,7 @@ const openCallout = (message, {
7042
7062
  }
7043
7063
  update(message, {
7044
7064
  status,
7065
+ testId,
7045
7066
  icon,
7046
7067
  closeButton
7047
7068
  });
@@ -7073,12 +7094,19 @@ const ARROW_SPACING = 8;
7073
7094
  // (closest "[aria-expanded]", see commands.js): a button inside the callout
7074
7095
  // closes the callout, not the picker or dialog around it. Never "false" — a
7075
7096
  // closed callout is removed, not kept.
7097
+ //
7098
+ // role="status" is the floor, not the final value: a message appearing without
7099
+ // the user asking for it is a live region, and a callout carrying no status at
7100
+ // all still owes one. A status raises it (the addStatusEffect swapping in
7101
+ // "alert" for warning/error), and it is also the name a test reaches the
7102
+ // callout by — navi's classes and ids are not a contract (docs/testid.md).
7076
7103
  const calloutTemplate = /* html */`
7077
7104
  <div
7078
7105
  class="navi_callout"
7079
7106
  popover="manual"
7080
7107
  navi-out-of-flow=""
7081
7108
  aria-expanded="true"
7109
+ role="status"
7082
7110
  >
7083
7111
  <div class="navi_callout_box">
7084
7112
  <div class="navi_callout_frame"></div>
@@ -7691,7 +7719,7 @@ const generateSvgWithoutArrow = (width, height) => {
7691
7719
  *
7692
7720
  * Usage:
7693
7721
  * const myToken = createOpenToken();
7694
- * calloutManager.addOpenToken(myToken, { message, status, anchorElement, event, skipFocus, onClose });
7722
+ * calloutManager.addOpenToken(myToken, { message, status, testId, anchorElement, event, skipFocus, onClose });
7695
7723
  * calloutManager.removeOpenToken(myToken, event);
7696
7724
  * calloutManager.requestCloseCallout(event, debugReason); // force-close all
7697
7725
  * calloutManager.callout // current open callout or null
@@ -7737,6 +7765,7 @@ const createCalloutManager = (
7737
7765
  const [, remainingTokenData] = tokens.entries().next().value;
7738
7766
  callout.update(remainingTokenData.message, {
7739
7767
  status: remainingTokenData.status,
7768
+ testId: remainingTokenData.testId,
7740
7769
  icon: remainingTokenData.icon,
7741
7770
  closeButton: remainingTokenData.closeButton,
7742
7771
  });
@@ -7763,6 +7792,7 @@ const createCalloutManager = (
7763
7792
  {
7764
7793
  message,
7765
7794
  status,
7795
+ testId,
7766
7796
  icon,
7767
7797
  closeButton,
7768
7798
  anchorElement,
@@ -7777,12 +7807,13 @@ const createCalloutManager = (
7777
7807
  }
7778
7808
  const calloutOptions = {
7779
7809
  status,
7810
+ testId,
7780
7811
  icon,
7781
7812
  closeButton,
7782
7813
  closeOnClickOutside: status !== "error",
7783
7814
  };
7784
7815
 
7785
- tokens.set(token, { message, status, icon, closeButton, onClose });
7816
+ tokens.set(token, { message, status, testId, icon, closeButton, onClose });
7786
7817
  if (callout) {
7787
7818
  callout.update(message, calloutOptions);
7788
7819
  return;
@@ -59308,6 +59339,7 @@ const PickerContentInsidePopup = props => {
59308
59339
  children: isCallout ? jsx(PickerCalloutPopup, {
59309
59340
  ...popupProps,
59310
59341
  pickerRef: props.ref,
59342
+ testId: popupTestId,
59311
59343
  status: calloutStatus,
59312
59344
  icon: calloutIcon,
59313
59345
  closeButton: calloutCloseButton,
@@ -59372,6 +59404,7 @@ const PickerCalloutPopup = ({
59372
59404
  anchor,
59373
59405
  openController,
59374
59406
  pickerRef,
59407
+ testId,
59375
59408
  status,
59376
59409
  icon,
59377
59410
  closeButton,
@@ -59397,6 +59430,10 @@ const PickerCalloutPopup = ({
59397
59430
  const anchorElement = anchor === pickerRef ? undefined : anchor && "current" in anchor ? anchor.current : anchor;
59398
59431
  calloutManager.addOpenToken(PICKER_CALLOUT_CONTENT_TOKEN, {
59399
59432
  message: hostRef.current,
59433
+ // The popup a `popupTestId` names is this callout: it is the surface the
59434
+ // picker opens, drawn by navi, so it is the one thing the caller cannot
59435
+ // name from its own children.
59436
+ testId,
59400
59437
  // "none" is the picker's word for it; the callout's is no status at all.
59401
59438
  status: status === "none" ? undefined : status,
59402
59439
  icon,
@@ -66945,6 +66982,10 @@ installImportMetaCssBuild(import.meta);const css$t = /* css */`@layer navi {
66945
66982
  --x-picker-padding-right: var(--picker-padding-right, var(--picker-padding-x, var(--picker-padding, var(--picker-padding-x-default))));
66946
66983
  --x-picker-padding-left: var(--picker-padding-left, var(--picker-padding-x, var(--picker-padding, var(--picker-padding-x-default))));
66947
66984
  --x-picker-padding-bottom: var(--picker-padding-bottom, var(--picker-padding-y, var(--picker-padding, var(--picker-padding-y-default))));
66985
+ --x-picker-press-padding-top: var(--picker-press-padding-top, var(--picker-press-padding-y, var(--picker-press-padding, 0px)));
66986
+ --x-picker-press-padding-right: var(--picker-press-padding-right, var(--picker-press-padding-x, var(--picker-press-padding, 0px)));
66987
+ --x-picker-press-padding-bottom: var(--picker-press-padding-bottom, var(--picker-press-padding-y, var(--picker-press-padding, 0px)));
66988
+ --x-picker-press-padding-left: var(--picker-press-padding-left, var(--picker-press-padding-x, var(--picker-press-padding, 0px)));
66948
66989
  --x-picker-color: var(--picker-color);
66949
66990
  --x-picker-icon-color: var(--picker-icon-color);
66950
66991
  --x-picker-align-x: var(--picker-align-x, var(--picker-align-x-default));
@@ -67091,14 +67132,17 @@ installImportMetaCssBuild(import.meta);const css$t = /* css */`@layer navi {
67091
67132
 
67092
67133
  &[navi-ui-custom] {
67093
67134
  & .navi_picker_input {
67094
- top: calc(-1 * var(--picker-border-width));
67095
- right: calc(-1 * var(--picker-border-width));
67096
- bottom: calc(-1 * var(--picker-border-width));
67097
- left: calc(-1 * var(--picker-border-width));
67098
- opacity: 0;
67099
- appearance: none;
67135
+ top: calc(-1 * (var(--picker-border-width) + var(--x-picker-press-padding-top)));
67136
+ right: calc(-1 *
67137
+ (var(--picker-border-width) + var(--x-picker-press-padding-right)));
67138
+ bottom: calc(-1 *
67139
+ (var(--picker-border-width) + var(--x-picker-press-padding-bottom)));
67140
+ left: calc(-1 * (var(--picker-border-width) + var(--x-picker-press-padding-left)));
67100
67141
  width: auto;
67101
67142
  height: auto;
67143
+ font: inherit;
67144
+ opacity: 0;
67145
+ appearance: none;
67102
67146
  position: absolute;
67103
67147
  }
67104
67148
  }
@@ -67278,6 +67322,12 @@ const PickerButton = props => {
67278
67322
  // keywords have to become lengths here — "s" reaching CSS untouched makes
67279
67323
  // the declaration invalid, silently, and the gap just goes away.
67280
67324
  props.slotSpacing = resolveSpacingSize(props.slotSpacing);
67325
+ for (const pressPaddingPropName of PRESS_PADDING_PROP_NAMES) {
67326
+ const pressPadding = props[pressPaddingPropName];
67327
+ if (pressPadding !== undefined) {
67328
+ props[pressPaddingPropName] = stringifySpacingStyle(pressPadding);
67329
+ }
67330
+ }
67281
67331
  const {
67282
67332
  ref,
67283
67333
  variant,
@@ -67836,6 +67886,13 @@ const isOpeningKeyboardOnMobile = type => {
67836
67886
  const NON_MOBILE_KEYBOARD_TYPES = new Set(["date", "month", "week", "time", "datetime-local", "color"]);
67837
67887
  const PICKER_BUTTON_PSEUDO_CLASSES = [":hover", ":focus", ":focus-visible", ":focus-within", ":read-only", ":disabled", ":-navi-loading", ":-navi-expanded", ":-navi-has-value"];
67838
67888
  const PickerInputPseudoClasses = [":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":-navi-has-value", ":-navi-expanded"];
67889
+
67890
+ // Spacing props that are not the names of real CSS styles, so Box hands them to
67891
+ // the custom property untouched: a size keyword would reach CSS as the word "s"
67892
+ // and a number as a unitless one, either of which makes the calc() using it
67893
+ // invalid and drops the press area back to the box. Hence the pass in
67894
+ // PickerButton, which turns them into lengths.
67895
+ const PRESS_PADDING_PROP_NAMES = ["pressPadding", "pressPaddingX", "pressPaddingY", "pressPaddingTop", "pressPaddingRight", "pressPaddingBottom", "pressPaddingLeft"];
67839
67896
  const PickerStyleCSSVars = {
67840
67897
  "outlineWidth": "--picker-outline-width",
67841
67898
  "borderWidth": "--picker-border-width",
@@ -67849,6 +67906,13 @@ const PickerStyleCSSVars = {
67849
67906
  "popupBorderRadius": "--picker-popup-border-radius",
67850
67907
  "dialogBorderWidth": "--picker-dialog-border-width",
67851
67908
  "slotSpacing": "--picker-slot-spacing",
67909
+ "pressPadding": "--picker-press-padding",
67910
+ "pressPaddingX": "--picker-press-padding-x",
67911
+ "pressPaddingY": "--picker-press-padding-y",
67912
+ "pressPaddingTop": "--picker-press-padding-top",
67913
+ "pressPaddingRight": "--picker-press-padding-right",
67914
+ "pressPaddingBottom": "--picker-press-padding-bottom",
67915
+ "pressPaddingLeft": "--picker-press-padding-left",
67852
67916
  // alignX/alignY resolve to these two on a flex-x box; naming the CSS style
67853
67917
  // (not the prop) is what styleCSSVars matches, so justifyContent/alignItems
67854
67918
  // passed directly land in the same variables.