@jsenv/navi 0.29.101 → 0.29.102

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.
@@ -49,6 +49,16 @@ const css$15 = /* css */`
49
49
  --navi-z-index-control-hovered: 1;
50
50
  --navi-z-index-control-focused: 2;
51
51
 
52
+ /* A control the user is not on and yet still has to paint in front: one
53
+ holding something open (a picker showing its list, an expandable
54
+ header). Its border keeps the color the open state gives it, while
55
+ hover and focus have both moved on — into the popup, or onto whatever
56
+ the pointer travelled to next — so neither of the two values above is
57
+ there to raise it, and the neighbour painted after it cuts the very
58
+ border that says what is open. Above both: the member holding the
59
+ popup open outranks a member merely hovered or focused. */
60
+ --navi-z-index-control-expanded: 3;
61
+
52
62
  /* Kept stuck while something scrolls under it: a list header, the head
53
63
  and foot of a side panel, a table's sticky cells, the header and
54
64
  footer of any scrolling Box. Above raised controls — a control
@@ -9977,7 +9987,7 @@ const formatDateIso = (iso, inputType) => {
9977
9987
  const READONLY_CONSTRAINT = {
9978
9988
  name: "readonly",
9979
9989
  messageAttribute: "data-readonly-message",
9980
- check: (field) => {
9990
+ check: (field, { intent } = {}) => {
9981
9991
  const readOnly = Boolean(
9982
9992
  field.controlHostProps.readOnly ||
9983
9993
  field.controlHostProps["aria-readonly"] === "true",
@@ -9986,6 +9996,18 @@ const READONLY_CONSTRAINT = {
9986
9996
  return null;
9987
9997
  }
9988
9998
 
9999
+ // Read-only, and what it opens still opens: a picker's answer often lives
10000
+ // in a shape only its popup draws — a plan with one tile ringed, a wheel
10001
+ // stopped on a time — and refusing to open leaves that shape unreadable.
10002
+ // Opening reads and nothing more, so it goes through; everything that would
10003
+ // write is refused below, and the popup content is handed the same
10004
+ // read-only state so each control in there refuses on its own terms. Which
10005
+ // controls say so, and when, is theirs to answer (see createControlInfo's
10006
+ // readOnlyOpens).
10007
+ if (intent === "read" && field.readOnlyOpens) {
10008
+ return null;
10009
+ }
10010
+
9989
10011
  // A selection guarding its length (see maxLengthGuard) is what holds this
9990
10012
  // one back, so max_length is what refuses it: same name, same message, same
9991
10013
  // `maxLengthMessage` to say it in the caller's own words. Read-only is only
@@ -10050,6 +10072,8 @@ const readOnlyMessage = (field) => {
10050
10072
  * → "navi_request_interaction" event
10051
10073
  * → onRequestInteraction
10052
10074
  * → check disabled / read-only / busy (via controller.controlInteraction)
10075
+ * against the interaction's `intent` ("write" by default, "read" for one
10076
+ * that only shows what is already there — see READONLY_CONSTRAINT)
10053
10077
  * → if blocked → prevented()
10054
10078
  * → if allowed → allowed()
10055
10079
  * → (in allowed callback) setUIState(value)
@@ -10083,10 +10107,10 @@ const createControlInteraction = (
10083
10107
  // The title this rule put on the element, if any (see checkInteractivity).
10084
10108
  let titleWritten = null;
10085
10109
 
10086
- const checkInteractivity = ({ event } = {}) => {
10110
+ const checkInteractivity = ({ event, intent = "write" } = {}) => {
10087
10111
  interactionFailedConstraintInfo = null;
10088
10112
  for (const constraint of INTERACTION_CONSTRAINT_SET) {
10089
- const checkResult = constraint.check(controller);
10113
+ const checkResult = constraint.check(controller, { intent });
10090
10114
  if (!checkResult) {
10091
10115
  continue;
10092
10116
  }
@@ -10113,7 +10137,7 @@ const createControlInteraction = (
10113
10137
  if (!mci) {
10114
10138
  continue;
10115
10139
  }
10116
- const canInteract = mci.checkInteractivity({ event });
10140
+ const canInteract = mci.checkInteractivity({ event, intent });
10117
10141
  if (canInteract) {
10118
10142
  continue;
10119
10143
  }
@@ -10127,8 +10151,14 @@ const createControlInteraction = (
10127
10151
  }
10128
10152
 
10129
10153
  // Keep title attribute in sync for accessibility.
10154
+ // Only off a check that asked the general question: a title is read
10155
+ // whenever a pointer rests on the element, so it says what is true of the
10156
+ // control as a whole. A check made for an interaction that only reads (a
10157
+ // read-only picker asked to open its popup) answers about that one
10158
+ // interaction — writing the title from it would take away the "read-only"
10159
+ // the first time someone opened the popup.
10130
10160
  const titleLess = !controller.controlHostProps?.title;
10131
- if (titleLess) {
10161
+ if (intent === "write" && titleLess) {
10132
10162
  const element = controller.ref.current;
10133
10163
  if (element) {
10134
10164
  if (interactionFailedConstraintInfo) {
@@ -10234,6 +10264,12 @@ const onRequestInteraction = (
10234
10264
  const {
10235
10265
  event,
10236
10266
  name,
10267
+ // What this interaction would do to the control: write it, or only read it.
10268
+ // Everything writes unless it says otherwise — an interaction that merely
10269
+ // shows what is already there (opening a picker's popup, closing it again)
10270
+ // says "read", and that is what a control held read-only can still let
10271
+ // through (see READONLY_CONSTRAINT).
10272
+ intent = "write",
10237
10273
  bypassInteractivity = false,
10238
10274
  prevented,
10239
10275
  allowed,
@@ -10264,7 +10300,7 @@ const onRequestInteraction = (
10264
10300
  if (controller && !bypassInteractivity) {
10265
10301
  const ci = controller?.rules.interaction;
10266
10302
  if (ci) {
10267
- const canInteract = ci.checkInteractivity({ event });
10303
+ const canInteract = ci.checkInteractivity({ event, intent });
10268
10304
  if (!canInteract) {
10269
10305
  const failedInfo =
10270
10306
  ci.interactionFailedConstraintInfo ??
@@ -26705,6 +26741,7 @@ const useControlProps = (props, {
26705
26741
  }
26706
26742
  const {
26707
26743
  name,
26744
+ intent,
26708
26745
  bypassInteractivity = false,
26709
26746
  allowed,
26710
26747
  prevented,
@@ -26714,6 +26751,7 @@ const useControlProps = (props, {
26714
26751
  return dispatchRequestInteraction(control, {
26715
26752
  event: e,
26716
26753
  name,
26754
+ intent,
26717
26755
  bypassInteractivity,
26718
26756
  prevented: () => {
26719
26757
  debugInteraction(e, `interaction not allowed`);
@@ -26853,6 +26891,7 @@ const createControlInfo = (props, {
26853
26891
  let defaultStatePropName;
26854
26892
  let stateInitial;
26855
26893
  let readOnlySupported = false;
26894
+ let readOnlyOpens = false;
26856
26895
  let disabledSupported = false;
26857
26896
  let hasStateProp;
26858
26897
  let value;
@@ -26939,6 +26978,14 @@ const createControlInfo = (props, {
26939
26978
  // aria-readonly plus a refused interaction — see the select reactions in
26940
26979
  // getDefaultEventReactionDefinitions.
26941
26980
  readOnlySupported = controlType === "picker" && INPUT_TYPE_SUPPORTING_READONLY_SET.has(typeProp);
26981
+ // A picker's popup is content of its own — a plan with one tile ringed, a
26982
+ // wheel stopped on a time, a list showing what was chosen — so read-only
26983
+ // does not close it: it opens, and everything in it is held read-only in
26984
+ // turn (see the ReadOnlyContext in picker.jsx). Two pickers this is not
26985
+ // true of: one with no popup of its own, which opens the browser's and
26986
+ // cannot hold that read-only (see PickerNative), and one whose caller says
26987
+ // its popup is a form with nothing to read (openWhileReadOnly={false}).
26988
+ readOnlyOpens = controlType === "picker" && props.children !== undefined && props.openWhileReadOnly !== false;
26942
26989
  }
26943
26990
 
26944
26991
  // The suggestion the control starts on, as opposed to what it holds — what a
@@ -26965,6 +27012,7 @@ const createControlInfo = (props, {
26965
27012
  signalHoldsChecked,
26966
27013
  stateFromSignal,
26967
27014
  readOnlySupported,
27015
+ readOnlyOpens,
26968
27016
  disabledSupported
26969
27017
  };
26970
27018
  };
@@ -27315,6 +27363,10 @@ const useInteractiveProps = (props, {
27315
27363
  const actionLoading = optimistic ? false : actionStatus.loading;
27316
27364
  const loadingResolved = loadingBase || actionLoading;
27317
27365
  const readOnlyResolved = readOnlyBase || actionLoading;
27366
+ // Read-only, and what this control opens still opens: reading what is in
27367
+ // there changes nothing. Read by READONLY_CONSTRAINT, which lets an
27368
+ // interaction that only reads through on it.
27369
+ uiStateController.readOnlyOpens = Boolean(controlInfo.readOnlyOpens);
27318
27370
  // Both halves of "busy" that do not come from the bound action, kept apart
27319
27371
  // from each other and from it: BUSY_CONSTRAINT answers each from its own
27320
27372
  // live source rather than from the rendered aria-busy, which conflates all
@@ -47678,7 +47730,27 @@ installImportMetaCssBuild(import.meta);const css$J = /* css */`
47678
47730
  border-width: var(--border-width);
47679
47731
  border-style: solid;
47680
47732
  border-color: var(--x-border-color);
47681
- border-radius: var(--border-radius);
47733
+ /* Squared from the outside, corner by corner: a checkbox in a group can
47734
+ arrive wrapped (in a label, in a row carrying a state), and the ask then
47735
+ travels down as inherited custom properties rather than as a radius
47736
+ landing on this element. Each corner falls back to the checkbox's own
47737
+ radius when nothing asks for anything. */
47738
+ border-top-left-radius: var(
47739
+ --x-corner-top-left-radius,
47740
+ var(--border-radius)
47741
+ );
47742
+ border-top-right-radius: var(
47743
+ --x-corner-top-right-radius,
47744
+ var(--border-radius)
47745
+ );
47746
+ border-bottom-right-radius: var(
47747
+ --x-corner-bottom-right-radius,
47748
+ var(--border-radius)
47749
+ );
47750
+ border-bottom-left-radius: var(
47751
+ --x-corner-bottom-left-radius,
47752
+ var(--border-radius)
47753
+ );
47682
47754
  outline-width: var(--outline-width);
47683
47755
  outline-style: none;
47684
47756
  outline-color: var(--outline-color);
@@ -48644,7 +48716,26 @@ installImportMetaCssBuild(import.meta);const css$H = /* css */`
48644
48716
  border-width: var(--button-border-width);
48645
48717
  border-style: solid;
48646
48718
  border-color: var(--x-border-color);
48647
- border-radius: var(--button-border-radius);
48719
+ /* Squared from the outside, corner by corner: a row of these is the
48720
+ segmented control a Group is for, and one of them can arrive wrapped
48721
+ (in a tooltip, in a label) — so the ask travels down as inherited
48722
+ custom properties rather than as a radius landing on this element. */
48723
+ border-top-left-radius: var(
48724
+ --x-corner-top-left-radius,
48725
+ var(--button-border-radius)
48726
+ );
48727
+ border-top-right-radius: var(
48728
+ --x-corner-top-right-radius,
48729
+ var(--button-border-radius)
48730
+ );
48731
+ border-bottom-right-radius: var(
48732
+ --x-corner-bottom-right-radius,
48733
+ var(--button-border-radius)
48734
+ );
48735
+ border-bottom-left-radius: var(
48736
+ --x-corner-bottom-left-radius,
48737
+ var(--button-border-radius)
48738
+ );
48648
48739
 
48649
48740
  .navi_icon,
48650
48741
  img {
@@ -48953,6 +49044,28 @@ installImportMetaCssBuild(import.meta);const css$G = /* css */`
48953
49044
  --x-thumb-color: var(--thumb-color);
48954
49045
  --x-thumb-border: none;
48955
49046
  --x-thumb-cursor: var(--thumb-cursor);
49047
+ /* Squared from the outside, corner by corner — resolved here once because
49048
+ what draws the frame is not this element but the three layers stacked
49049
+ below it (background, track, fill), which cannot take it by inherit the
49050
+ way a control with a single frame does. A range can arrive wrapped, so
49051
+ the ask travels down as inherited custom properties; each corner falls
49052
+ back to the track's own radius when nothing asks for anything. */
49053
+ --x-range-corner-top-left: var(
49054
+ --x-corner-top-left-radius,
49055
+ var(--border-radius)
49056
+ );
49057
+ --x-range-corner-top-right: var(
49058
+ --x-corner-top-right-radius,
49059
+ var(--border-radius)
49060
+ );
49061
+ --x-range-corner-bottom-right: var(
49062
+ --x-corner-bottom-right-radius,
49063
+ var(--border-radius)
49064
+ );
49065
+ --x-range-corner-bottom-left: var(
49066
+ --x-corner-bottom-left-radius,
49067
+ var(--border-radius)
49068
+ );
48956
49069
 
48957
49070
  position: relative;
48958
49071
  box-sizing: border-box;
@@ -48961,10 +49074,26 @@ installImportMetaCssBuild(import.meta);const css$G = /* css */`
48961
49074
  margin: 2px;
48962
49075
  flex-direction: row;
48963
49076
  align-items: center;
48964
- /* Just for the outline, the real border radius of the range is fixed */
48965
49077
  font-size: var(--font-size);
48966
49078
  font-family: var(--font-family);
48967
- border-radius: var(--outline-border-radius);
49079
+ /* The ring around the whole control, which follows the claim too: a range
49080
+ squared along a seam must not keep a rounded ring over it. */
49081
+ border-top-left-radius: var(
49082
+ --x-corner-top-left-radius,
49083
+ var(--outline-border-radius)
49084
+ );
49085
+ border-top-right-radius: var(
49086
+ --x-corner-top-right-radius,
49087
+ var(--outline-border-radius)
49088
+ );
49089
+ border-bottom-right-radius: var(
49090
+ --x-corner-bottom-right-radius,
49091
+ var(--outline-border-radius)
49092
+ );
49093
+ border-bottom-left-radius: var(
49094
+ --x-corner-bottom-left-radius,
49095
+ var(--outline-border-radius)
49096
+ );
48968
49097
  outline-width: var(--outline-width);
48969
49098
  outline-style: none;
48970
49099
  outline-color: var(--outline-color);
@@ -49005,7 +49134,10 @@ installImportMetaCssBuild(import.meta);const css$G = /* css */`
49005
49134
  border-width: var(--border-width);
49006
49135
  border-style: solid;
49007
49136
  border-color: var(--x-border-color);
49008
- border-radius: var(--border-radius);
49137
+ border-top-left-radius: var(--x-range-corner-top-left);
49138
+ border-top-right-radius: var(--x-range-corner-top-right);
49139
+ border-bottom-right-radius: var(--x-range-corner-bottom-right);
49140
+ border-bottom-left-radius: var(--x-range-corner-bottom-left);
49009
49141
  }
49010
49142
  .navi_input_range_track {
49011
49143
  position: absolute;
@@ -49015,7 +49147,10 @@ installImportMetaCssBuild(import.meta);const css$G = /* css */`
49015
49147
  border-width: var(--border-width);
49016
49148
  border-style: solid;
49017
49149
  border-color: var(--x-track-border-color);
49018
- border-radius: var(--border-radius);
49150
+ border-top-left-radius: var(--x-range-corner-top-left);
49151
+ border-top-right-radius: var(--x-range-corner-top-right);
49152
+ border-bottom-right-radius: var(--x-range-corner-bottom-right);
49153
+ border-bottom-left-radius: var(--x-range-corner-bottom-left);
49019
49154
  }
49020
49155
  .navi_input_range_fill {
49021
49156
  position: absolute;
@@ -49023,7 +49158,10 @@ installImportMetaCssBuild(import.meta);const css$G = /* css */`
49023
49158
  height: var(--height);
49024
49159
  background: var(--x-fill-color);
49025
49160
  background-clip: content-box;
49026
- border-radius: var(--border-radius);
49161
+ border-top-left-radius: var(--x-range-corner-top-left);
49162
+ border-top-right-radius: var(--x-range-corner-top-right);
49163
+ border-bottom-right-radius: var(--x-range-corner-bottom-right);
49164
+ border-bottom-left-radius: var(--x-range-corner-bottom-left);
49027
49165
  clip-path: inset(0 calc((1 - var(--x-fill-ratio)) * 100%) 0 0);
49028
49166
  }
49029
49167
  .navi_input_range_thumb {
@@ -50095,7 +50233,27 @@ const inputCss = /* css */`
50095
50233
  border-width: var(--border-width);
50096
50234
  border-style: solid;
50097
50235
  border-color: var(--x-border-color);
50098
- border-radius: var(--border-radius);
50236
+ /* Squared from the outside, corner by corner: an input is not always the
50237
+ member a Group joins — it can arrive wrapped (in a Box carrying a state,
50238
+ in a tooltip) — so the ask travels down as inherited custom properties
50239
+ rather than as a radius landing on this element. Each corner falls back
50240
+ to the input's own radius when nothing asks for anything. */
50241
+ border-top-left-radius: var(
50242
+ --x-corner-top-left-radius,
50243
+ var(--border-radius)
50244
+ );
50245
+ border-top-right-radius: var(
50246
+ --x-corner-top-right-radius,
50247
+ var(--border-radius)
50248
+ );
50249
+ border-bottom-right-radius: var(
50250
+ --x-corner-bottom-right-radius,
50251
+ var(--border-radius)
50252
+ );
50253
+ border-bottom-left-radius: var(
50254
+ --x-corner-bottom-left-radius,
50255
+ var(--border-radius)
50256
+ );
50099
50257
  outline-width: var(--outline-width);
50100
50258
  outline-color: var(--outline-color);
50101
50259
  outline-offset: var(--outline-offset);
@@ -51836,7 +51994,7 @@ const css$E = /* css */`
51836
51994
  positioned element to mean anything, hence position: relative.
51837
51995
  Deliberately not paired with isolation: isolate — a stacking context
51838
51996
  here would also trap the popup of a picker held in the group, which
51839
- counts on its own band reaching the whole page. What keeps these two
51997
+ counts on its own band reaching the whole page. What keeps these
51840
51998
  values from escaping is instead that everything they could reach is a
51841
51999
  band above them (see navi_z_indexes.js). */
51842
52000
  > *:hover,
@@ -51856,6 +52014,25 @@ const css$E = /* css */`
51856
52014
  position: relative;
51857
52015
  z-index: var(--navi-z-index-control-focused);
51858
52016
  }
52017
+ /* The member holding something open. Neither of the two above covers it:
52018
+ the click that opened the popup gives no focus ring, the focus itself
52019
+ left for the popup's content, and the pointer is free to travel to a
52020
+ neighbour — yet the member keeps the border color its open state gives
52021
+ it, and that border is exactly what the neighbour painted after it
52022
+ slices. Read as a state, not as a pseudo-class: :active only lasts as
52023
+ long as the button is held down, and while it is held :hover is true
52024
+ anyway, so it would add nothing here.
52025
+
52026
+ :has, for the same reason as focus-visible above — the group member can
52027
+ be an enrobage around the control that expands — and reaching a popup
52028
+ held inline (a Popover with layer="local" renders inside its member)
52029
+ costs nothing: that popup only reads expanded while its own member is,
52030
+ which is the member this raises. */
52031
+ > *[aria-expanded="true"],
52032
+ > *:has([aria-expanded="true"]) {
52033
+ position: relative;
52034
+ z-index: var(--navi-z-index-control-expanded);
52035
+ }
51859
52036
 
51860
52037
  /* Horizontal (default): Cumulative margin for border overlap */
51861
52038
  &:not([data-vertical]) {
@@ -55783,6 +55960,14 @@ const PickerNative = props => {
55783
55960
  dispatchRequestInteraction(pickerInput, {
55784
55961
  event: e,
55785
55962
  name: "navi_request_open to show native picker",
55963
+ // No "read" intent here, unlike a picker holding a popup of its own
55964
+ // (see PickerCustom): the browser's picker cannot be held read-only,
55965
+ // whichever way its type falls. Where `readonly` applies (date, time,
55966
+ // month, number…) the input is not mutable and showPicker() refuses
55967
+ // it — there is nothing to open. Where it does not (color, file) the
55968
+ // browser opens all the same and writes whatever is chosen straight
55969
+ // into the input, which is read-only in name only. So a read-only
55970
+ // native picker says why instead, on the trigger.
55786
55971
  prevented: () => {
55787
55972
  e.preventDefault();
55788
55973
  },
@@ -55973,14 +56158,20 @@ const PickerCustom = props => {
55973
56158
  // already what it is, and this only re-runs the same reaction.
55974
56159
  const inputEl = getPickerInput(ref.current);
55975
56160
  const valueAtClose = getUIStateFromElement(inputEl);
55976
- if (valueAtOpen === undefined && compareTwoJsValues(valueAtClose, valueAtOpen)) {
56161
+ const controller = inputEl?.__uiStateController__;
56162
+ if (controller?.controlHostProps.readOnly) {
56163
+ // Opened only to be read: what it shows stays the suggestion it
56164
+ // was. A look is not an answer, and the signal behind it is not
56165
+ // written by one.
56166
+ debugPopup(closeEvent, `picker is read-only -> nothing to commit`);
56167
+ } else if (valueAtOpen === undefined && compareTwoJsValues(valueAtClose, valueAtOpen)) {
55977
56168
  // Same third case onRequestClose steps around: nothing held,
55978
56169
  // nothing shown, nothing picked. There is no suggestion here to
55979
56170
  // turn into an answer.
55980
56171
  debugPopup(closeEvent, `picker showed nothing -> nothing to commit`);
55981
56172
  } else {
55982
56173
  debugPopup(closeEvent, `picker defined a suggestion -> commit it`);
55983
- commitUIStateAsAnswer(inputEl?.__uiStateController__, closeEvent);
56174
+ commitUIStateAsAnswer(controller, closeEvent);
55984
56175
  }
55985
56176
  }
55986
56177
  leaveExpanded({
@@ -56069,6 +56260,13 @@ const PickerCustom = props => {
56069
56260
  requestInteraction({
56070
56261
  event: e,
56071
56262
  name: "navi_request_open_event",
56263
+ // Showing what the picker already holds, in the shape only the popup
56264
+ // draws it in — nothing of the value is written on the way in, nor on
56265
+ // the way out. Every interaction below says the same, which is what
56266
+ // lets a read-only picker be opened and read while everything that
56267
+ // would write it (paste, cut, the clear cross) stays refused. See
56268
+ // READONLY_CONSTRAINT.
56269
+ intent: "read",
56072
56270
  allowed: () => {
56073
56271
  requestOpen(e);
56074
56272
  }
@@ -56077,6 +56275,7 @@ const PickerCustom = props => {
56077
56275
  onnavi_request_close: e => {
56078
56276
  requestInteraction({
56079
56277
  event: e,
56278
+ intent: "read",
56080
56279
  allowed: () => {
56081
56280
  requestClose(e, {
56082
56281
  isCancel: e.detail.isCancel
@@ -56095,6 +56294,7 @@ const PickerCustom = props => {
56095
56294
  "a-z": e => {
56096
56295
  return {
56097
56296
  name: "letter key to open",
56297
+ intent: "read",
56098
56298
  allowed: () => {
56099
56299
  requestOpen(e);
56100
56300
  }
@@ -56103,6 +56303,7 @@ const PickerCustom = props => {
56103
56303
  "0-9": e => {
56104
56304
  return {
56105
56305
  name: "numeric key to open",
56306
+ intent: "read",
56106
56307
  allowed: () => {
56107
56308
  requestOpen(e);
56108
56309
  }
@@ -56111,6 +56312,7 @@ const PickerCustom = props => {
56111
56312
  "arrowdown": e => {
56112
56313
  return {
56113
56314
  name: "arrow_down_to_open",
56315
+ intent: "read",
56114
56316
  allowed: () => {
56115
56317
  requestOpen(e);
56116
56318
  e.preventDefault(); // prevent container scroll
@@ -56120,6 +56322,7 @@ const PickerCustom = props => {
56120
56322
  "arrowup": e => {
56121
56323
  return {
56122
56324
  name: "arrow_up_to_open",
56325
+ intent: "read",
56123
56326
  allowed: () => {
56124
56327
  requestOpen(e);
56125
56328
  e.preventDefault(); // prevent container scroll
@@ -56129,6 +56332,7 @@ const PickerCustom = props => {
56129
56332
  "space": e => {
56130
56333
  return {
56131
56334
  name: "space_to_open",
56335
+ intent: "read",
56132
56336
  allowed: () => {
56133
56337
  requestOpen(e);
56134
56338
  e.preventDefault(); // prevent scroll
@@ -56143,6 +56347,7 @@ const PickerCustom = props => {
56143
56347
  }
56144
56348
  return {
56145
56349
  name: "enter_to_open",
56350
+ intent: "read",
56146
56351
  allowed: () => {
56147
56352
  requestOpen(e);
56148
56353
  e.preventDefault(); // prevent form submission
@@ -56156,6 +56361,7 @@ const PickerCustom = props => {
56156
56361
  const isCancel = escapeEffect === "cancel";
56157
56362
  return {
56158
56363
  name: isCancel ? "escape_to_cancel" : "escape_to_close",
56364
+ intent: "read",
56159
56365
  allowed: () => {
56160
56366
  requestClose(e, {
56161
56367
  isCancel
@@ -56187,6 +56393,7 @@ const PickerCustom = props => {
56187
56393
  // choice being taken from anyone.
56188
56394
  return {
56189
56395
  name: "mousedown to close picker",
56396
+ intent: "read",
56190
56397
  allowed: () => requestClose(e, {
56191
56398
  isCancel: true
56192
56399
  })
@@ -56197,6 +56404,7 @@ const PickerCustom = props => {
56197
56404
  }
56198
56405
  return {
56199
56406
  name: "mousedown to open picker",
56407
+ intent: "read",
56200
56408
  allowed: () => {
56201
56409
  debugFocus(e, `prevent browser giving focus to button (mousedown.preventDefault())`);
56202
56410
  requestOpen(e);
@@ -56214,6 +56422,7 @@ const PickerCustom = props => {
56214
56422
  // above), this is where the picker opens for real.
56215
56423
  return {
56216
56424
  name: e.detail === 0 ? "click (keyboard or progammatic) to open picker" : "click to open picker",
56425
+ intent: "read",
56217
56426
  prevented: () => {
56218
56427
  e.preventDefault();
56219
56428
  },
@@ -63178,10 +63387,39 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
63178
63387
  /* The frame is drawn by the box, but its radius is declared here, on the
63179
63388
  control root, like every other navi control does — so anything styling
63180
63389
  the picker from the outside (a Group squaring the corners it joins) has
63181
- one element to talk to, and the box follows. */
63182
- border-radius: var(--picker-border-radius);
63390
+ one element to talk to, and the box follows.
63391
+ Corner by corner rather than as the shorthand: a picker is not always
63392
+ the member a Group joins — it can arrive wrapped (in a Box carrying a
63393
+ state, in a link, in a tooltip), and the ask then travels down as
63394
+ inherited custom properties instead of landing on this element as a
63395
+ radius. Each corner falls back to the picker's own radius when nothing
63396
+ asks for anything. */
63397
+ border-top-left-radius: var(
63398
+ --x-corner-top-left-radius,
63399
+ var(--picker-border-radius)
63400
+ );
63401
+ border-top-right-radius: var(
63402
+ --x-corner-top-right-radius,
63403
+ var(--picker-border-radius)
63404
+ );
63405
+ border-bottom-right-radius: var(
63406
+ --x-corner-bottom-right-radius,
63407
+ var(--picker-border-radius)
63408
+ );
63409
+ border-bottom-left-radius: var(
63410
+ --x-corner-bottom-left-radius,
63411
+ var(--picker-border-radius)
63412
+ );
63183
63413
 
63184
63414
  .navi_picker_box {
63415
+ /* The ask stops here: this element is the picker's frame, so nothing it
63416
+ holds is at the seam — the chevron and the clear cross in the slot, a
63417
+ button in a custom UI. */
63418
+ --x-corner-top-left-radius: initial;
63419
+ --x-corner-top-right-radius: initial;
63420
+ --x-corner-bottom-right-radius: initial;
63421
+ --x-corner-bottom-left-radius: initial;
63422
+
63185
63423
  position: relative;
63186
63424
  display: inline-flex;
63187
63425
  box-sizing: border-box;
@@ -63233,14 +63471,6 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
63233
63471
  }
63234
63472
  }
63235
63473
  .navi_picker_right_slot {
63236
- /* A corner claimed from the outside (see group.jsx) is the frame's, and
63237
- what lives in here is not the frame — a button in a slot, the content
63238
- of a popup — so the ask stops at this boundary. */
63239
- --x-corner-top-left-radius: initial;
63240
- --x-corner-top-right-radius: initial;
63241
- --x-corner-bottom-right-radius: initial;
63242
- --x-corner-bottom-left-radius: initial;
63243
-
63244
63474
  display: inline-flex;
63245
63475
  height: 1em;
63246
63476
  height: 1lh;
@@ -63335,6 +63565,15 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
63335
63565
  }
63336
63566
 
63337
63567
  .navi_picker_content {
63568
+ /* The other side of the frame: what a picker holds here is what its
63569
+ popup shows, and a popup is never at a seam. Popover and Dialog stop
63570
+ the ask at their own root too — this covers content that reaches the
63571
+ popup through neither. */
63572
+ --x-corner-top-left-radius: initial;
63573
+ --x-corner-top-right-radius: initial;
63574
+ --x-corner-bottom-right-radius: initial;
63575
+ --x-corner-bottom-left-radius: initial;
63576
+
63338
63577
  display: contents;
63339
63578
  text-align: initial; /* Don't inherit picker text align */
63340
63579
  }
@@ -63352,6 +63591,12 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
63352
63591
  --x-picker-icon-color: var(--picker-icon-color-readonly);
63353
63592
  --x-picker-cursor: default;
63354
63593
  }
63594
+ /* Read-only and still opening, so it still says so under the pointer.
63595
+ Before the disabled block below on purpose: a disabled picker opens
63596
+ nothing, read-only or not. */
63597
+ &[data-readonly-opens] {
63598
+ --x-picker-cursor: pointer;
63599
+ }
63355
63600
  /* Focus */
63356
63601
  &[data-focus-within]:has(.navi_picker_input[data-focus-visible]) {
63357
63602
  --x-picker-border-color: transparent;
@@ -63480,6 +63725,7 @@ const PickerButton = props => {
63480
63725
  // untouched (see the --navi-clear command).
63481
63726
  clearConfirm,
63482
63727
  clearConfirmPopupContent,
63728
+ readOnly,
63483
63729
  error
63484
63730
  } = props;
63485
63731
  const isSingleLine = maxLines === 1;
@@ -63497,6 +63743,19 @@ const PickerButton = props => {
63497
63743
  children
63498
63744
  } = inputProps;
63499
63745
  const loading = basePseudoState[":-navi-loading"];
63746
+ // The same chain useControlProps resolves (own prop first, then what the
63747
+ // group above says), for the two things it does not carry: the popup content,
63748
+ // which is read-only along with the picker, and the cursor, which stays a
63749
+ // pointer on a picker that still opens.
63750
+ const readOnlyFromAbove = useContext(ReadOnlyContext);
63751
+ const readOnlyResolved = readOnly || readOnlyFromAbove;
63752
+ // Read off the controller rather than worked out again: what makes a
63753
+ // read-only picker open is settled once, where the gate reads it (see
63754
+ // createControlInfo's readOnlyOpens). Needed here for the cursor.
63755
+ const readOnlyOpens = Boolean(readOnlyResolved) && uiStateController.readOnlyOpens;
63756
+ // Whether anything can still be changed — read by the clear cross below,
63757
+ // clearing being a modification like any other.
63758
+ const interactive = !basePseudoState[":disabled"] && !basePseudoState[":read-only"] && !loading;
63500
63759
  usePickerErrorCallout(uiStateController, error);
63501
63760
  return jsxs(Box, {
63502
63761
  as: "div",
@@ -63515,6 +63774,7 @@ const PickerButton = props => {
63515
63774
  "navi-picker": "",
63516
63775
  "navi-single-line": isSingleLine ? "" : undefined,
63517
63776
  "navi-ui-custom": ui === "default" ? undefined : "",
63777
+ "data-readonly-opens": readOnlyOpens ? "" : undefined,
63518
63778
  "data-popup-width-fit-content": popupWidthFitContent ? "" : undefined,
63519
63779
  ...pickerRemainingProps,
63520
63780
  basePseudoState: basePseudoState,
@@ -63525,6 +63785,7 @@ const PickerButton = props => {
63525
63785
  rightSlot: undefined,
63526
63786
  clearConfirm: undefined,
63527
63787
  clearConfirmPopupContent: undefined,
63788
+ openWhileReadOnly: undefined,
63528
63789
  ui: undefined,
63529
63790
  maxLines: undefined,
63530
63791
  popupWidthFitContent: undefined,
@@ -63643,7 +63904,7 @@ const PickerButton = props => {
63643
63904
  value: undefined,
63644
63905
  children: jsx(ControlNameContext.Provider, {
63645
63906
  value: undefined,
63646
- children: clearable && value !== undefined && value !== "" ? jsx(Button, {
63907
+ children: clearable && interactive && value !== undefined && value !== "" ? jsx(Button, {
63647
63908
  command: "--navi-clear",
63648
63909
  commandFor: inputProps.id
63649
63910
  // The question, asked before the clear rather than by the
@@ -63696,9 +63957,12 @@ const PickerButton = props => {
63696
63957
  })]
63697
63958
  }), jsx(ControlFacadeChildrenWrapper, {
63698
63959
  ...facadeChildrenProps,
63699
- children: jsx("div", {
63700
- className: "navi_picker_content",
63701
- children: children
63960
+ children: jsx(ReadOnlyContext.Provider, {
63961
+ value: readOnlyResolved,
63962
+ children: jsx("div", {
63963
+ className: "navi_picker_content",
63964
+ children: children
63965
+ })
63702
63966
  })
63703
63967
  })]
63704
63968
  });
@@ -63957,7 +64221,27 @@ const css$t = /* css */`
63957
64221
  of one's own still wins — an inline style beats a stylesheet. */
63958
64222
  border: var(--navi-control-border-width) solid
63959
64223
  var(--navi-control-border-color);
63960
- border-radius: var(--navi-control-border-radius);
64224
+ /* Squared from the outside, corner by corner: a spin is not always the
64225
+ member a Group joins — it can arrive wrapped — so the ask travels down
64226
+ as inherited custom properties rather than as a radius landing on this
64227
+ element. The chevrons take these corners back by inherit (see below), so
64228
+ they follow. */
64229
+ border-top-left-radius: var(
64230
+ --x-corner-top-left-radius,
64231
+ var(--navi-control-border-radius)
64232
+ );
64233
+ border-top-right-radius: var(
64234
+ --x-corner-top-right-radius,
64235
+ var(--navi-control-border-radius)
64236
+ );
64237
+ border-bottom-right-radius: var(
64238
+ --x-corner-bottom-right-radius,
64239
+ var(--navi-control-border-radius)
64240
+ );
64241
+ border-bottom-left-radius: var(
64242
+ --x-corner-bottom-left-radius,
64243
+ var(--navi-control-border-radius)
64244
+ );
63961
64245
  outline-width: var(--navi-focus-outline-width);
63962
64246
  /* Just outside the border, never on it: the ring belongs to the whole
63963
64247
  control — the two chevrons included, since pressing one lands the
@@ -64014,6 +64298,13 @@ const css$t = /* css */`
64014
64298
  opens its calendar. Around the whole control it would open under a chevron;
64015
64299
  around the value it opens under the value one pressed. */
64016
64300
  .navi_picker_spin_middle {
64301
+ /* The ask stops here: the frame is the spin's box, and the picker or the
64302
+ field standing in the middle of it is behind that frame, not at a seam. */
64303
+ --x-corner-top-left-radius: initial;
64304
+ --x-corner-top-right-radius: initial;
64305
+ --x-corner-bottom-right-radius: initial;
64306
+ --x-corner-bottom-left-radius: initial;
64307
+
64017
64308
  position: relative;
64018
64309
  display: flex;
64019
64310
  min-width: 0;
@@ -64191,7 +64482,26 @@ const css$t = /* css */`
64191
64482
  font-family: var(--navi-control-font-family);
64192
64483
  border: var(--navi-control-border-width) solid
64193
64484
  var(--navi-control-border-color);
64194
- border-radius: var(--navi-control-border-radius);
64485
+ /* Squared from the outside, corner by corner (see .navi_picker_spin
64486
+ above): the group is the member a Group joins, and it can arrive
64487
+ wrapped. The spins inside give their radius up and take the corners of
64488
+ this one back through inherit, so they follow. */
64489
+ border-top-left-radius: var(
64490
+ --x-corner-top-left-radius,
64491
+ var(--navi-control-border-radius)
64492
+ );
64493
+ border-top-right-radius: var(
64494
+ --x-corner-top-right-radius,
64495
+ var(--navi-control-border-radius)
64496
+ );
64497
+ border-bottom-right-radius: var(
64498
+ --x-corner-bottom-right-radius,
64499
+ var(--navi-control-border-radius)
64500
+ );
64501
+ border-bottom-left-radius: var(
64502
+ --x-corner-bottom-left-radius,
64503
+ var(--navi-control-border-radius)
64504
+ );
64195
64505
  outline-width: var(--navi-focus-outline-width);
64196
64506
  outline-color: var(--navi-focus-outline-color);
64197
64507
  outline-offset: 0px;