@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.
- package/dist/jsenv_navi.js +343 -33
- package/dist/jsenv_navi.js.map +18 -15
- package/docs/control_group.md +14 -9
- package/docs/interactions.md +14 -0
- package/docs/z_index.md +8 -8
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
63700
|
-
|
|
63701
|
-
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
|
-
|
|
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
|
-
|
|
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;
|