@jsenv/navi 0.29.306 → 0.29.307

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.
@@ -12,7 +12,7 @@ import { useErrorBoundary, useLayoutEffect, useContext, useCallback, useRef, use
12
12
  import { humanizeI18n, prefixFirstAndIndentRemainingLines, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay, formatTime, formatMinuteDuration, formatSecondDuration, formatHourDuration, formatTimeRelative, formatNumber, interpolateText, installInterpolateJsx } from "@jsenv/humanize";
13
13
  export { createI18n, formatDatetime, formatDay, formatDayRelative, formatDuration, formatHourDuration, formatMinuteDuration, formatMonth, formatNumber, formatSecondDuration, formatTime, formatTimeOfDay, formatTimeRange, formatTimeRelative, interpolateText } from "@jsenv/humanize";
14
14
  import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
15
- import { durationContainsNaN, compareTwoDurations, durationToSeconds, DISPLAYABLE_RULE, MAX_LINE_BREAKS_RULE, NO_EMOJI_RULE, SINGLE_SPACE_RULE, createValidity, resolveCharClass, getCharClassMessageKey, compileCharClassAnchored, compileCharClass, CHAR_CLASS_PRESETS, parseDuration, durationToISOString } from "@jsenv/validity";
15
+ import { TYPE_RULE, durationContainsNaN, compareTwoDurations, durationToSeconds, DISPLAYABLE_RULE, MAX_LINE_BREAKS_RULE, NO_EMOJI_RULE, SINGLE_SPACE_RULE, createValidity, resolveCharClass, getCharClassMessageKey, compileCharClassAnchored, compileCharClass, CHAR_CLASS_PRESETS, parseDuration, durationToISOString } from "@jsenv/validity";
16
16
  export { compareTwoDurations, durationContainsNaN, durationToHours, durationToISOString, durationToMinutes, durationToNumber, durationToSeconds, durationToString, parseDuration } from "@jsenv/validity";
17
17
  import { Suspense, createPortal, forwardRef } from "preact/compat";
18
18
 
@@ -8444,6 +8444,75 @@ const languagesSignal = computed(() => {
8444
8444
  // (nor the "languagechange" listener above).
8445
8445
  setRuntimeLangSource(() => languagesSignal.value);
8446
8446
 
8447
+ /**
8448
+ * Where navi meets @jsenv/validity.
8449
+ *
8450
+ * validity names a refusal with a key and its parameters rather than a
8451
+ * sentence, so that a field and a server can refuse in the same words in the
8452
+ * language of the person reading. navi is one of those two callers: it says the
8453
+ * sentence, in the browser, through `naviI18n`. The keys line up on purpose —
8454
+ * validity's `"single_space.start"` is navi's `"constraint.single_space.start"`
8455
+ * — so overriding a message is looking up one key, not going through a
8456
+ * translation table.
8457
+ *
8458
+ * The other direction is `constraintFromValidityRule`: a rule an app wrote for
8459
+ * its server, worn by a control as a constraint.
8460
+ */
8461
+
8462
+
8463
+ /**
8464
+ * Turns a @jsenv/validity rule into a constraint a control can wear, so an app
8465
+ * rule written once — in the package its server reads too — is checked on both
8466
+ * sides instead of being written twice.
8467
+ *
8468
+ * @param {object} rule
8469
+ * `{ name, applyOn(ruleValue, value, ruleConfig) }`, the same object passed
8470
+ * to `createValidity({ rules })`.
8471
+ * @param {object} [ruleConfig]
8472
+ * What parameterizes the rule, under its own name — `{ maxWords: 40 }` for a
8473
+ * rule named `maxWords`. Pass `formatMessage` here to say the refusal through
8474
+ * the app's own i18n; without it the key is looked up in `naviI18n` under
8475
+ * `constraint.<key>`, and a rule answering with a finished sentence is shown
8476
+ * as-is.
8477
+ *
8478
+ * Call it once, at module level: a constraint rebuilt on every render is a new
8479
+ * object on every check.
8480
+ */
8481
+ const constraintFromValidityRule = (rule, ruleConfig = {}) => {
8482
+ const { formatMessage, ...ruleParams } = ruleConfig;
8483
+ return {
8484
+ name: rule.name,
8485
+ check: (field) => {
8486
+ const result = rule.applyOn(
8487
+ ruleParams[rule.name],
8488
+ field.uiState,
8489
+ ruleParams,
8490
+ );
8491
+ if (!result) {
8492
+ return null;
8493
+ }
8494
+ if (typeof result === "string") {
8495
+ return result;
8496
+ }
8497
+ if (formatMessage) {
8498
+ return formatMessage(result.key, result.params);
8499
+ }
8500
+ return naviI18nFromValidityMessage(result);
8501
+ },
8502
+ };
8503
+ };
8504
+
8505
+ const naviI18nFromValidityMessage = ({ key, params }) => {
8506
+ if (params && typeof params.max === "number") {
8507
+ // Lets a template pluralize on the bound it names: "[max] retour[s]".
8508
+ return naviI18n(`constraint.${key}`, {
8509
+ ...params,
8510
+ s: params.max > 1 ? "s" : "",
8511
+ });
8512
+ }
8513
+ return naviI18n(`constraint.${key}`, params);
8514
+ };
8515
+
8447
8516
  /**
8448
8517
  * https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation
8449
8518
  */
@@ -8593,9 +8662,8 @@ const PATTERN_CONSTRAINT = {
8593
8662
  };
8594
8663
  CONSTRAINT_ATTRIBUTE_SET.add("pattern");
8595
8664
 
8596
- // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/email#validation
8597
- const emailregex =
8598
- /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
8665
+ // What an address is, is @jsenv/validity's `type: "email"`: a server checking
8666
+ // the value again refuses it for the same reason and in the same words.
8599
8667
  const TYPE_EMAIL_CONSTRAINT = {
8600
8668
  name: "type_email",
8601
8669
  messageAttribute: "data-type-message",
@@ -8609,14 +8677,11 @@ const TYPE_EMAIL_CONSTRAINT = {
8609
8677
  if (!valueAsString) {
8610
8678
  return null;
8611
8679
  }
8612
- if (emailregex.test(valueAsString)) {
8680
+ const result = TYPE_RULE.applyOn("email", valueAsString);
8681
+ if (!result) {
8613
8682
  return null;
8614
8683
  }
8615
-
8616
- if (!valueAsString.includes("@")) {
8617
- return naviI18n("constraint.type.email.at", { value: valueAsString });
8618
- }
8619
- return naviI18n("constraint.type.email.invalid");
8684
+ return naviI18nFromValidityMessage(result);
8620
8685
  },
8621
8686
  };
8622
8687
 
@@ -11917,75 +11982,6 @@ const isFunctionButNotAnActionFunction = (action) => {
11917
11982
  return typeof action === "function" && !action.isAction;
11918
11983
  };
11919
11984
 
11920
- /**
11921
- * Where navi meets @jsenv/validity.
11922
- *
11923
- * validity names a refusal with a key and its parameters rather than a
11924
- * sentence, so that a field and a server can refuse in the same words in the
11925
- * language of the person reading. navi is one of those two callers: it says the
11926
- * sentence, in the browser, through `naviI18n`. The keys line up on purpose —
11927
- * validity's `"single_space.start"` is navi's `"constraint.single_space.start"`
11928
- * — so overriding a message is looking up one key, not going through a
11929
- * translation table.
11930
- *
11931
- * The other direction is `constraintFromValidityRule`: a rule an app wrote for
11932
- * its server, worn by a control as a constraint.
11933
- */
11934
-
11935
-
11936
- /**
11937
- * Turns a @jsenv/validity rule into a constraint a control can wear, so an app
11938
- * rule written once — in the package its server reads too — is checked on both
11939
- * sides instead of being written twice.
11940
- *
11941
- * @param {object} rule
11942
- * `{ name, applyOn(ruleValue, value, ruleConfig) }`, the same object passed
11943
- * to `createValidity({ rules })`.
11944
- * @param {object} [ruleConfig]
11945
- * What parameterizes the rule, under its own name — `{ maxWords: 40 }` for a
11946
- * rule named `maxWords`. Pass `formatMessage` here to say the refusal through
11947
- * the app's own i18n; without it the key is looked up in `naviI18n` under
11948
- * `constraint.<key>`, and a rule answering with a finished sentence is shown
11949
- * as-is.
11950
- *
11951
- * Call it once, at module level: a constraint rebuilt on every render is a new
11952
- * object on every check.
11953
- */
11954
- const constraintFromValidityRule = (rule, ruleConfig = {}) => {
11955
- const { formatMessage, ...ruleParams } = ruleConfig;
11956
- return {
11957
- name: rule.name,
11958
- check: (field) => {
11959
- const result = rule.applyOn(
11960
- ruleParams[rule.name],
11961
- field.uiState,
11962
- ruleParams,
11963
- );
11964
- if (!result) {
11965
- return null;
11966
- }
11967
- if (typeof result === "string") {
11968
- return result;
11969
- }
11970
- if (formatMessage) {
11971
- return formatMessage(result.key, result.params);
11972
- }
11973
- return naviI18nFromValidityMessage(result);
11974
- },
11975
- };
11976
- };
11977
-
11978
- const naviI18nFromValidityMessage = ({ key, params }) => {
11979
- if (params && typeof params.max === "number") {
11980
- // Lets a template pluralize on the bound it names: "[max] retour[s]".
11981
- return naviI18n(`constraint.${key}`, {
11982
- ...params,
11983
- s: params.max > 1 ? "s" : "",
11984
- });
11985
- }
11986
- return naviI18n(`constraint.${key}`, params);
11987
- };
11988
-
11989
11985
  /**
11990
11986
  * `data-displayable` — the value must be something the layout can actually
11991
11987
  * draw. What it refuses and why lives in @jsenv/validity's DISPLAYABLE_RULE:
@@ -48862,10 +48858,11 @@ const ButtonUI = props => {
48862
48858
  cta,
48863
48859
  spacing,
48864
48860
  contentDisplay,
48865
- // Whether the button draws the loading outline itself. A button that is
48866
- // one half of a bigger control says no: what is busy is the control, and
48867
- // the outline belongs around the whole of it (see split_button.jsx).
48868
- loadingOutline = true
48861
+ // Who draws the loading outline. Left unsaid, the button does; "custom"
48862
+ // says someone else does — a button that is one half of a bigger control:
48863
+ // what is busy is the control, and the outline belongs around the whole
48864
+ // of it (see split_button.jsx).
48865
+ loadingOutline
48869
48866
  } = props;
48870
48867
  const [buttonControlRootProps, buttonControlHostProps, controlChildrenWrapperProps] = useControlProps(props, {
48871
48868
  controlType: "button"
@@ -48991,7 +48988,7 @@ const ButtonUI = props => {
48991
48988
  visualSelector: visualSelector,
48992
48989
  hasChildUsingForwardedProps: true,
48993
48990
  children: [jsx(LoadingOutline, {
48994
- loading: loadingOutline && loading,
48991
+ loading: loadingOutline === "custom" ? false : loading,
48995
48992
  inset: -1,
48996
48993
  color: "var(--button-loader-color)"
48997
48994
  }), jsx(ControlChildrenWrapper, {
@@ -61238,6 +61235,9 @@ const DIALOG_STYLE_CSS_VARS = {
61238
61235
  animationDuration: "--popup-animation-duration",
61239
61236
  backdropColor: "--backdrop-background",
61240
61237
  backdropFilter: "--backdrop-filter",
61238
+ backgroundColor: "--dialog-background-color",
61239
+ boxShadow: "--dialog-box-shadow",
61240
+ borderWidth: "--dialog-border-width",
61241
61241
  borderRadius: "--dialog-border-radius",
61242
61242
  minWidth: "--dialog-min-width",
61243
61243
  maxWidth: "--dialog-max-width",
@@ -62831,10 +62831,17 @@ const POPOVER_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible",
62831
62831
  // Lets consumers pass animationDuration="0.5s"/borderRadius="8px" as regular
62832
62832
  // props; Box maps them to the CSS vars for us (see box.jsx's styleCSSVars
62833
62833
  // handling).
62834
+ // The paint props go through the popover's own vars rather than the properties
62835
+ // themselves, so the stylesheet keeps the last word on them (the same reason
62836
+ // dialog.jsx gives for borderRadius).
62834
62837
  const POPUP_STYLE_CSS_VARS = {
62835
62838
  animationDuration: "--popup-animation-duration",
62836
62839
  backdropColor: "--backdrop-background",
62837
62840
  backdropFilter: "--backdrop-filter",
62841
+ backgroundColor: "--popover-background-color",
62842
+ boxShadow: "--popover-box-shadow",
62843
+ borderWidth: "--popover-border-width",
62844
+ borderRadius: "--popover-border-radius",
62838
62845
  minWidth: "--popover-min-width",
62839
62846
  maxWidth: "--popover-max-width",
62840
62847
  minHeight: "--popover-min-height",
@@ -63097,83 +63104,56 @@ const Popup = props => {
63097
63104
  Popup.Close = PopupClose;
63098
63105
 
63099
63106
  installImportMetaCssBuild(import.meta);
63100
- const css$B = /* css */`.navi_picker {
63101
- &[aria-haspopup="listbox"] {
63102
- & .navi_popover {
63103
- --popover-border-radius: var(--picker-popup-border-radius, var(--picker-border-radius));
63104
- --popover-border-width: var(--picker-border-width);
63105
- --popover-border-color: var(--x-picker-border-color);
63106
- --popover-background-color: var(--picker-popup-background-color, var(--navi-popup-background-color));
63107
- --popover-outline-width: var(--picker-outline-width);
63108
- --popover-outline-color: var(--picker-outline-color);
63109
- --popover-box-shadow: var(--picker-popup-box-shadow, var(--navi-popup-box-shadow));
63110
- --popover-max-height: var(--picker-popover-max-height);
63111
- min-width: var(--picker-popover-min-width, var(--anchor-width, 0px));
63112
- cursor: default;
63113
-
63114
- & .navi_list_container {
63115
- --list-border-radius: max(0px,
63116
- var(--popover-border-radius) - var(--popover-border-width));
63117
- overscroll-behavior: none;
63118
- width: 100%;
63107
+ const css$B = /* css */`.navi_popover[data-picker-popup] {
63108
+ --popover-border-radius: var(--picker-border-radius);
63109
+ --popover-border-width: var(--picker-border-width);
63110
+ --popover-border-color: var(--x-picker-border-color);
63111
+ --popover-outline-width: var(--picker-outline-width);
63112
+ --popover-outline-color: var(--picker-outline-color);
63113
+ cursor: default;
63119
63114
 
63120
- &:not([data-overflow-visible]) {
63121
- overflow: auto;
63122
- }
63123
- }
63124
- }
63115
+ &:not([navi-hidden]) {
63116
+ flex-direction: column;
63117
+ display: flex;
63118
+ }
63125
63119
 
63126
- &[aria-expanded="true"] {
63127
- &[navi-popover-mode="overlay"] {
63128
- border-color: #0000;
63129
- }
63120
+ & .navi_list_container {
63121
+ --list-border-radius: max(0px,
63122
+ var(--popover-border-radius) - var(--popover-border-width));
63123
+ overscroll-behavior: none;
63124
+ width: 100%;
63130
63125
 
63131
- & .navi_popover {
63132
- flex-direction: column;
63133
- display: flex;
63134
- }
63126
+ &:not([data-overflow-visible]) {
63127
+ overflow: auto;
63135
63128
  }
63136
63129
  }
63130
+ }
63137
63131
 
63138
- &[aria-haspopup="dialog"] {
63139
- & .navi_dialog {
63140
- --dialog-border-radius: var(--picker-popup-border-radius, var(--picker-border-radius));
63141
- --dialog-border-width: var(--picker-dialog-border-width, 0px);
63142
- --dialog-border-color: var(--x-picker-border-color);
63143
- --dialog-background-color: var(--picker-popup-background-color, var(--navi-popup-background-color));
63144
- --dialog-outline-width: var(--picker-outline-width);
63145
- --dialog-outline-color: var(--picker-outline-color);
63146
- --dialog-box-shadow: var(--picker-popup-box-shadow, var(--navi-popup-box-shadow));
63147
- --dialog-min-width: var(--picker-dialog-min-width);
63148
- --dialog-min-height: var(--picker-dialog-min-height);
63149
- --dialog-max-width: var(--picker-dialog-max-width);
63150
- --dialog-max-height: var(--picker-dialog-max-height);
63151
- cursor: default;
63132
+ .navi_picker[aria-haspopup="listbox"][aria-expanded="true"][navi-popover-mode="overlay"] {
63133
+ border-color: #0000;
63134
+ }
63152
63135
 
63153
- &[open] {
63154
- flex-direction: column;
63155
- }
63136
+ .navi_dialog[data-picker-popup] {
63137
+ --dialog-border-radius: var(--picker-border-radius);
63138
+ --dialog-border-color: var(--x-picker-border-color);
63139
+ --dialog-outline-width: var(--picker-outline-width);
63140
+ --dialog-outline-color: var(--picker-outline-color);
63141
+ cursor: default;
63156
63142
 
63157
- & .navi_list_container {
63158
- --list-border-radius: max(0px,
63159
- var(--dialog-border-radius) - var(--dialog-border-width));
63160
- overscroll-behavior: none;
63161
- width: 100%;
63143
+ &[open] {
63144
+ flex-direction: column;
63145
+ }
63162
63146
 
63163
- &:not([data-overflow-visible]) {
63164
- overflow: auto;
63165
- }
63166
- }
63167
- }
63147
+ & .navi_list_container {
63148
+ --list-border-radius: max(0px,
63149
+ var(--dialog-border-radius) - var(--dialog-border-width));
63150
+ overscroll-behavior: none;
63151
+ width: 100%;
63168
63152
 
63169
- &[data-dialog-size-from-anchor] .navi_dialog {
63170
- --dialog-max-width: var(--picker-dialog-max-width, var(--anchor-width));
63153
+ &:not([data-overflow-visible]) {
63154
+ overflow: auto;
63171
63155
  }
63172
63156
  }
63173
-
63174
- &[data-popup-width-fit-content] {
63175
- --picker-popover-min-width: 0px;
63176
- }
63177
63157
  }
63178
63158
  `;
63179
63159
  const PickerCustomResolver = props => {
@@ -63937,6 +63917,22 @@ const PickerContentInsidePopup = props => {
63937
63917
  // dialogMaxWidth="var(--anchor-width)", as a ceiling) — what keeps a card
63938
63918
  // its own width once lifted. Dialog's own `sizeFromAnchor`.
63939
63919
  dialogSizeFromAnchor,
63920
+ // The caller's word on the popup's own box. Written on the popup element,
63921
+ // where Popover/Dialog map them to their own vars, rather than as vars on
63922
+ // the picker: a var on the picker inherits into everything the popup
63923
+ // holds, and the pickers in there would read it as their own.
63924
+ popupBackgroundColor,
63925
+ popupBorderRadius,
63926
+ popupBoxShadow,
63927
+ popoverMaxHeight,
63928
+ dialogBorderWidth,
63929
+ dialogMinWidth,
63930
+ dialogMinHeight,
63931
+ dialogMaxWidth,
63932
+ dialogMaxHeight,
63933
+ // The popover is at least as wide as the trigger. True when the CONTENT
63934
+ // should size it (a Wheel) instead of being stretched to the trigger.
63935
+ popupWidthFitContent,
63940
63936
  animation,
63941
63937
  grow,
63942
63938
  animationDuration,
@@ -63955,7 +63951,6 @@ const PickerContentInsidePopup = props => {
63955
63951
  return jsx(Next, {
63956
63952
  "aria-haspopup": isPopover ? "listbox" : "dialog",
63957
63953
  "navi-popover-mode": isPopover ? popoverMode : undefined,
63958
- "data-dialog-size-from-anchor": !isPopover && dialogSizeFromAnchor ? "" : undefined,
63959
63954
  ...rest,
63960
63955
  // On popupProps already (see the picker's popup assembly); they mean
63961
63956
  // nothing to the picker element.
@@ -63997,7 +63992,23 @@ const PickerContentInsidePopup = props => {
63997
63992
  })
63998
63993
  }) : jsx(Popup, {
63999
63994
  ...popupProps,
64000
- "data-testid": popupTestId,
63995
+ "data-testid": popupTestId
63996
+ // What the CSS above keys on: this picker's own popup, and no other
63997
+ // popup below the picker.
63998
+ ,
63999
+ "data-picker-popup": "",
64000
+ backgroundColor: popupBackgroundColor,
64001
+ borderRadius: popupBorderRadius,
64002
+ boxShadow: popupBoxShadow,
64003
+ borderWidth: isPopover ? undefined : dialogBorderWidth,
64004
+ minWidth: isPopover ? popupWidthFitContent ? undefined : "var(--anchor-width, 0px)" : dialogMinWidth,
64005
+ minHeight: isPopover ? undefined : dialogMinHeight
64006
+ // As wide as the trigger under dialogSizeFromAnchor: Dialog's own
64007
+ // sizeFromAnchor is a floor, this is the ceiling. A dialogMaxWidth of
64008
+ // the caller's still wins.
64009
+ ,
64010
+ maxWidth: isPopover ? undefined : dialogMaxWidth === undefined && dialogSizeFromAnchor ? "var(--anchor-width)" : dialogMaxWidth,
64011
+ maxHeight: isPopover ? popoverMaxHeight : dialogMaxHeight,
64001
64012
  mode: mode,
64002
64013
  layer: popupLayer,
64003
64014
  animation: animation,
@@ -72095,6 +72106,26 @@ const css$t = /* css */`@layer navi {
72095
72106
  }
72096
72107
  }
72097
72108
 
72109
+ & [data-picker-content] {
72110
+ --picker-padding: initial;
72111
+ --picker-padding-x: initial;
72112
+ --picker-padding-y: initial;
72113
+ --picker-padding-top: initial;
72114
+ --picker-padding-right: initial;
72115
+ --picker-padding-bottom: initial;
72116
+ --picker-padding-left: initial;
72117
+ --picker-press-padding: initial;
72118
+ --picker-press-padding-x: initial;
72119
+ --picker-press-padding-y: initial;
72120
+ --picker-press-padding-top: initial;
72121
+ --picker-press-padding-right: initial;
72122
+ --picker-press-padding-bottom: initial;
72123
+ --picker-press-padding-left: initial;
72124
+ --picker-slot-spacing: initial;
72125
+ --picker-align-x: initial;
72126
+ --picker-align-y: initial;
72127
+ }
72128
+
72098
72129
  & .navi_picker_content {
72099
72130
  --x-corner-top-left-radius: initial;
72100
72131
  --x-corner-top-right-radius: initial;
@@ -72298,11 +72329,6 @@ const PickerButton = props => {
72298
72329
  placeholder,
72299
72330
  ui,
72300
72331
  maxLines: maxLinesProp = 1,
72301
- // By default the popover is at least as wide as the trigger (min-width:
72302
- // --anchor-width). Set true when the CONTENT should dictate the popover width
72303
- // (e.g. a Wheel) instead of being stretched to the trigger — see
72304
- // picker_custom.jsx.
72305
- popupWidthFitContent,
72306
72332
  // Adds a clear button to the right slot, the same one type="search" puts at
72307
72333
  // the end of an input: a picker holds a value the user chose, and unsetting
72308
72334
  // it should not require reopening the popup to hunt for a "none" entry.
@@ -72327,7 +72353,13 @@ const PickerButton = props => {
72327
72353
  // (see PickerInput below).
72328
72354
  role,
72329
72355
  readOnly,
72330
- error
72356
+ error,
72357
+ // Who draws the loading outline. Left unsaid, the picker does, around its
72358
+ // own box; "custom" says someone else does — a bigger surface this box is
72359
+ // a piece of, waiting as a whole (see the `loadingOutline` doc).
72360
+ loadingOutline,
72361
+ // Where the outline runs, in px from the picker's box: negative is outside.
72362
+ loadingOutlineInset = -2
72331
72363
  } = props;
72332
72364
  // A word in a sentence is never truncated — and the clamp's overflow: hidden
72333
72365
  // would cut its dotted underline, which sits on the edge of the line box
@@ -72447,7 +72479,6 @@ const PickerButton = props => {
72447
72479
  "navi-single-line": isSingleLine ? "" : undefined,
72448
72480
  "navi-ui-custom": ui === "default" ? undefined : "",
72449
72481
  "data-readonly-opens": readOnlyOpens ? "" : undefined,
72450
- "data-popup-width-fit-content": popupWidthFitContent ? "" : undefined,
72451
72482
  ...pickerRemainingProps,
72452
72483
  basePseudoState: basePseudoState,
72453
72484
  styleCSSVars: PickerStyleCSSVars,
@@ -72462,9 +72493,10 @@ const PickerButton = props => {
72462
72493
  openWhileReadOnly: undefined,
72463
72494
  ui: undefined,
72464
72495
  maxLines: undefined,
72465
- popupWidthFitContent: undefined,
72466
72496
  error: undefined,
72467
- dayLabel: undefined
72497
+ dayLabel: undefined,
72498
+ loadingOutline: undefined,
72499
+ loadingOutlineInset: undefined
72468
72500
  // This wrapper will receive keyboard event bubbling from the picker popup content
72469
72501
  // we re-dispatch on the input (to get escape to close for instance)
72470
72502
  ,
@@ -72495,9 +72527,9 @@ const PickerButton = props => {
72495
72527
  children: jsxs(PickerContext.Provider, {
72496
72528
  value: pickerContext,
72497
72529
  children: [variant === "headless" ? null : jsx(LoadingOutline, {
72498
- loading: loading,
72530
+ loading: loadingOutline === "custom" ? false : loading,
72499
72531
  color: "var(--picker-loader-color)",
72500
- inset: -2
72532
+ inset: loadingOutlineInset
72501
72533
  }), jsx(PickerInput, {
72502
72534
  role: hostRole,
72503
72535
  "aria-labelledby": hostLabelId,
@@ -72732,7 +72764,7 @@ const PickerClear = ({
72732
72764
  // removed is the whole field's, and the picker already draws the wait
72733
72765
  // around all of it. Two outlines for one wait is one too many.
72734
72766
  ,
72735
- loadingOutline: false
72767
+ loadingOutline: "custom"
72736
72768
  // preventDefault, not just tabIndex="-1": a mousedown focuses its target
72737
72769
  // before any click happens, and this button should never hold focus at
72738
72770
  // all — the field keeps it.
@@ -72906,21 +72938,16 @@ const PICKER_BUTTON_PSEUDO_CLASSES = [":hover", ":focus", ":focus-visible", ":fo
72906
72938
  const PickerInputPseudoClasses = [":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":-navi-has-value", ":-navi-expanded"];
72907
72939
 
72908
72940
  // A pair is [the css variable, the style whose values the prop is written in]:
72909
- // popupBorderRadius takes what borderRadius takes, dialogMinWidth what minWidth
72910
- // takes — see box.jsx's readCSSVarEntry.
72941
+ // slotSpacing takes what margin takes, pressPadding what padding takes — see
72942
+ // box.jsx's readCSSVarEntry.
72943
+ // What the props say about the picker's own box only. The popup props
72944
+ // (popupBackgroundColor, dialogMaxWidth…) are handed to the popup element by
72945
+ // picker_custom.jsx: a variable on the root inherits into everything the popup
72946
+ // holds, and the pickers in there would read it as their own.
72911
72947
  const PickerStyleCSSVars = {
72912
72948
  "outlineWidth": "--picker-outline-width",
72913
72949
  "borderWidth": "--picker-border-width",
72914
72950
  "borderRadius": "--picker-border-radius",
72915
- "popoverMaxHeight": ["--picker-popover-max-height", "maxHeight"],
72916
- "dialogMinWidth": ["--picker-dialog-min-width", "minWidth"],
72917
- "dialogMinHeight": ["--picker-dialog-min-height", "minHeight"],
72918
- "dialogMaxWidth": ["--picker-dialog-max-width", "maxWidth"],
72919
- "dialogMaxHeight": ["--picker-dialog-max-height", "maxHeight"],
72920
- "popupBackgroundColor": "--picker-popup-background-color",
72921
- "popupBorderRadius": ["--picker-popup-border-radius", "borderRadius"],
72922
- "popupBoxShadow": ["--picker-popup-box-shadow", "boxShadow"],
72923
- "dialogBorderWidth": ["--picker-dialog-border-width", "borderWidth"],
72924
72951
  "slotSpacing": ["--picker-slot-spacing", "margin"],
72925
72952
  "pressPadding": ["--picker-press-padding", "padding"],
72926
72953
  "pressPaddingX": ["--picker-press-padding-x", "padding"],
@@ -73027,6 +73054,8 @@ const PickerFirstResolver = props => {
73027
73054
  * popoverMode?: "nearby" | "overlay",
73028
73055
  * positionArea?: string,
73029
73056
  * popupWidthFitContent?: boolean,
73057
+ * loadingOutline?: "custom",
73058
+ * loadingOutlineInset?: number,
73030
73059
  * variant?: "icon" | "circle" | "headless" | "discrete" | "button" | "text" | "bare" | "picker",
73031
73060
  * icon?: boolean,
73032
73061
  * alignX?: "start" | "center" | "end",
@@ -73144,9 +73173,12 @@ const PickerFirstResolver = props => {
73144
73173
  * its own as well. Either way a card shows the score the moment the sheet
73145
73174
  * leaves and takes it back on a refusal, with nothing held by the caller.
73146
73175
  * `loading` is the wait it wears meanwhile: navi draws its loading outline
73147
- * on every trigger, but on a card-sized `variant="bare"` a two-pixel run
73148
- * around the box says nothing — a big surface draws its own waiting state
73149
- * from that flag, or from `[data-loading]` on the picker root in css.
73176
+ * on every trigger, two pixels out of the box. On a card-sized
73177
+ * `variant="bare"` that run sits on the card's own border and reads as a
73178
+ * border blinking; `loadingOutlineInset` moves it clear of it. And when the
73179
+ * picker's box is not the shape of what waits, `loadingOutline="custom"`
73180
+ * lets the drawing draw the wait itself, from that flag or from
73181
+ * `[data-loading]` on the picker root in css.
73150
73182
  * @param {boolean} [readOnly] Nothing in this picker can be changed — and it
73151
73183
  * still opens, so what is in the popup can be read: everything in there is
73152
73184
  * held read-only in turn, each control greying out and saying why on its own.
@@ -73273,6 +73305,21 @@ const PickerFirstResolver = props => {
73273
73305
  * nearest positioned ancestor it happens to find. `"picker"` (or an explicit
73274
73306
  * `variant={undefined}`) asks a confirm or callout picker for the field-like
73275
73307
  * drawing every other picker has.
73308
+ * @param {"custom"} [loadingOutline] Who draws the loading outline while the
73309
+ * action runs. Left unsaid, the picker does, around its own box. `"custom"`
73310
+ * says someone else does — what it means on a `Button` too — for a picker
73311
+ * that carries a wait drawn elsewhere: two nested pickers sharing one run
73312
+ * (a score picker drawn inside the card an edit picker wraps, see
73313
+ * `12_picker_card_demo.html`) are one wait, and one outline; and a
73314
+ * `variant="bare"` whose box is not
73315
+ * the shape of what waits (a card with a note pinned under it) leaves the
73316
+ * outline to the card, which knows its own box. The wait itself is still
73317
+ * worn: `aria-busy`, the busy refusal, the error callout, `[data-loading]`.
73318
+ * @param {number} [loadingOutlineInset=-2] Where the loading outline runs, in
73319
+ * px from the picker's box — negative is outside. A field wants it right on
73320
+ * its border; a card-sized trigger wants it a few pixels out, clear of the
73321
+ * card's own frame, so it reads as a run around the card rather than as its
73322
+ * border blinking.
73276
73323
  * @param {boolean} [icon] The trigger is one icon — the `ui`, or the chevron
73277
73324
  * when there is none. Its box is that icon's box plus the padding asked for
73278
73325
  * and nothing else: no right slot, and no control line to be as tall as, so
@@ -73371,9 +73418,9 @@ const PickerFirstResolver = props => {
73371
73418
  * as a ceiling, unless a `dialogMaxWidth` says otherwise. What keeps a card
73372
73419
  * its own width once lifted out of the page (`animation="growing"`, with
73373
73420
  * `data-grow` on the card inside the popup): it moves, and nothing else
73374
- * about it changes. The ceiling is set here rather than through
73375
- * `dialogMaxWidth="var(--anchor-width)"` because that variable lives on the
73376
- * dialog element, and a prop on the picker is resolved on the picker.
73421
+ * about it changes. `dialogMaxWidth="var(--anchor-width)"` says the same
73422
+ * ceiling by hand: the popup props are written on the dialog element, where
73423
+ * that variable lives.
73377
73424
  * @param {"cancel"|"close"} [escapeEffect="cancel"] What Escape does to an open
73378
73425
  * picker. "cancel" puts back the value the picker had at open, and a dialog
73379
73426
  * picker also goes back in history — so anything written to the url while it
@@ -76019,7 +76066,7 @@ const SplitButton = props => {
76019
76066
  readOnly,
76020
76067
  disabled,
76021
76068
  // The outline belongs around the pair, drawn below.
76022
- loadingOutline: false
76069
+ loadingOutline: "custom"
76023
76070
  };
76024
76071
  return jsxs(Box, {
76025
76072
  ref: rootRef,