@jsenv/navi 0.29.101 → 0.29.103
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 +1579 -487
- package/dist/jsenv_navi.js.map +110 -46
- package/dist/jsenv_navi_side_effects.js +32 -6
- package/dist/jsenv_navi_side_effects.js.map +2 -2
- package/docs/AI_INSTRUCTIONS.md +19 -3
- package/docs/badge_list.md +80 -0
- package/docs/control_group.md +14 -9
- package/docs/control_value.md +50 -5
- package/docs/interactions.md +31 -6
- 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
|
|
@@ -1329,6 +1339,10 @@ naviI18n.addAll({
|
|
|
1329
1339
|
en: "More actions",
|
|
1330
1340
|
fr: "Autres actions",
|
|
1331
1341
|
},
|
|
1342
|
+
"button.remove": {
|
|
1343
|
+
en: "Remove",
|
|
1344
|
+
fr: "Retirer",
|
|
1345
|
+
},
|
|
1332
1346
|
});
|
|
1333
1347
|
|
|
1334
1348
|
// Default built-in translations — apps can override any key via add()
|
|
@@ -1628,6 +1642,14 @@ naviI18n.addAll({
|
|
|
1628
1642
|
fr: "Cette option n'est pas disponible.",
|
|
1629
1643
|
en: "This option is not available.",
|
|
1630
1644
|
},
|
|
1645
|
+
"constraint.readonly.selection": {
|
|
1646
|
+
fr: "La sélection ne peut plus être modifiée.",
|
|
1647
|
+
en: "This selection cannot be changed.",
|
|
1648
|
+
},
|
|
1649
|
+
"constraint.readonly.choice": {
|
|
1650
|
+
fr: "Ce choix ne peut plus être changé.",
|
|
1651
|
+
en: "This choice cannot be changed.",
|
|
1652
|
+
},
|
|
1631
1653
|
"constraint.readonly.item": {
|
|
1632
1654
|
fr: "Cet élément n'est pas disponible.",
|
|
1633
1655
|
en: "This item is not available.",
|
|
@@ -2104,6 +2126,276 @@ naviI18n.addAll({
|
|
|
2104
2126
|
|
|
2105
2127
|
const FormContext = createContext();
|
|
2106
2128
|
|
|
2129
|
+
const CONSTRAINT_ATTRIBUTE_SET = new Set();
|
|
2130
|
+
|
|
2131
|
+
const CONSTRAINT_NAME_TO_PROP = {
|
|
2132
|
+
disabled: "disabledMessage",
|
|
2133
|
+
required: "requiredMessage",
|
|
2134
|
+
pattern: "patternMessage",
|
|
2135
|
+
type_email: "typeMessage",
|
|
2136
|
+
type_number: "typeMessage",
|
|
2137
|
+
min_length: "minLengthMessage",
|
|
2138
|
+
max_length: "maxLengthMessage",
|
|
2139
|
+
min: "minMessage",
|
|
2140
|
+
max: "maxMessage",
|
|
2141
|
+
single_space: "singleSpaceMessage",
|
|
2142
|
+
same_as: "sameAsMessage",
|
|
2143
|
+
min_lower_letter: "minLowerLetterMessage",
|
|
2144
|
+
min_upper_letter: "minUpperLetterMessage",
|
|
2145
|
+
min_digit: "minDigitMessage",
|
|
2146
|
+
min_special_char: "minSpecialCharMessage",
|
|
2147
|
+
one_of: "oneOfMessage",
|
|
2148
|
+
readonly: "readOnlyMessage",
|
|
2149
|
+
available: "availableMessage",
|
|
2150
|
+
};
|
|
2151
|
+
|
|
2152
|
+
const CONSTRAINT_MESSAGE_PROP_NAME_SET = new Set(
|
|
2153
|
+
Object.values(CONSTRAINT_NAME_TO_PROP),
|
|
2154
|
+
);
|
|
2155
|
+
|
|
2156
|
+
const extractMessageAndRemainingProps = (props) => {
|
|
2157
|
+
const ownMessages = {};
|
|
2158
|
+
const remaining = {};
|
|
2159
|
+
const keyToVisit = new Set(Object.keys(props));
|
|
2160
|
+
for (const key of keyToVisit) {
|
|
2161
|
+
if (CONSTRAINT_MESSAGE_PROP_NAME_SET.has(key)) {
|
|
2162
|
+
ownMessages[key] = props[key];
|
|
2163
|
+
} else {
|
|
2164
|
+
remaining[key] = props[key];
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
return [ownMessages, remaining];
|
|
2168
|
+
};
|
|
2169
|
+
|
|
2170
|
+
const getConstraintMessage = (
|
|
2171
|
+
controller,
|
|
2172
|
+
constraint,
|
|
2173
|
+
generatedMessage,
|
|
2174
|
+
{ requester },
|
|
2175
|
+
) => {
|
|
2176
|
+
const { name: constraintName } = constraint;
|
|
2177
|
+
const propName = CONSTRAINT_NAME_TO_PROP[constraintName];
|
|
2178
|
+
|
|
2179
|
+
// 1. Search first on the requester (e.g. the <li> that was clicked),
|
|
2180
|
+
// then fall back to element (e.g. the hidden <input>).
|
|
2181
|
+
if (requester) {
|
|
2182
|
+
const requesterController = requester.__uiStateController__;
|
|
2183
|
+
if (requesterController && requesterController !== controller) {
|
|
2184
|
+
const requesterControllerMessage = requesterController.props[propName];
|
|
2185
|
+
if (requesterControllerMessage) {
|
|
2186
|
+
return {
|
|
2187
|
+
message: requesterControllerMessage,
|
|
2188
|
+
origin: "requester controller",
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
const controllerMessage = controller.props[propName];
|
|
2195
|
+
if (controllerMessage) {
|
|
2196
|
+
return {
|
|
2197
|
+
message: controllerMessage,
|
|
2198
|
+
origin: "controller",
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
return {
|
|
2203
|
+
message: generatedMessage,
|
|
2204
|
+
origin: "generated message",
|
|
2205
|
+
};
|
|
2206
|
+
};
|
|
2207
|
+
|
|
2208
|
+
// prop that we'll set on the control
|
|
2209
|
+
const CONTROL_ATTRIBUTE_SET = new Set([
|
|
2210
|
+
...CONSTRAINT_ATTRIBUTE_SET,
|
|
2211
|
+
|
|
2212
|
+
"ref",
|
|
2213
|
+
"children",
|
|
2214
|
+
"id",
|
|
2215
|
+
"name",
|
|
2216
|
+
"type",
|
|
2217
|
+
"value",
|
|
2218
|
+
"checked",
|
|
2219
|
+
"placeholder",
|
|
2220
|
+
"inputMode",
|
|
2221
|
+
"autoComplete",
|
|
2222
|
+
"spellcheck",
|
|
2223
|
+
"autoCorrect",
|
|
2224
|
+
"aria-controls",
|
|
2225
|
+
"tabIndex",
|
|
2226
|
+
"command",
|
|
2227
|
+
"commandFor",
|
|
2228
|
+
"command-value", // not standard but make sense, allow to give param to the command in question
|
|
2229
|
+
"list",
|
|
2230
|
+
|
|
2231
|
+
// "ui-action-target",
|
|
2232
|
+
"navi-input-type",
|
|
2233
|
+
"navi-value-pad",
|
|
2234
|
+
"navi-control-proxy-for",
|
|
2235
|
+
"navi-command-proxy-for",
|
|
2236
|
+
"navi-command-target",
|
|
2237
|
+
"onnavi_command",
|
|
2238
|
+
"onnavi_request_open",
|
|
2239
|
+
"onnavi_request_close",
|
|
2240
|
+
|
|
2241
|
+
"data-callout-arrow-x",
|
|
2242
|
+
"data-callout-point-to-border-box",
|
|
2243
|
+
"data-callout-point-to-content-box",
|
|
2244
|
+
"data-callout-viewport-spacing",
|
|
2245
|
+
"data-callout-position",
|
|
2246
|
+
"data-callout-position-fixed",
|
|
2247
|
+
|
|
2248
|
+
"data-testid", // playwright, cypress
|
|
2249
|
+
"data-separator", // used by InputGroup paste-to-fill
|
|
2250
|
+
]);
|
|
2251
|
+
// prop concerning control but that won't end up in the DOM if not inside CONTROL_ATTRIBUTE_SET
|
|
2252
|
+
const CONTROL_PROP_SET = new Set([
|
|
2253
|
+
...CONTROL_ATTRIBUTE_SET,
|
|
2254
|
+
...CONSTRAINT_MESSAGE_PROP_NAME_SET,
|
|
2255
|
+
|
|
2256
|
+
"action",
|
|
2257
|
+
"confirm",
|
|
2258
|
+
"confirmPopupContent",
|
|
2259
|
+
"actionEvent",
|
|
2260
|
+
"actionAfterChange",
|
|
2261
|
+
"actionOnMouseDown",
|
|
2262
|
+
"actionDebounce",
|
|
2263
|
+
// A signal bound two-way to the control: its value seeds the control's state and
|
|
2264
|
+
// is written back on every uiAction. Precludes value/checked (see createControlInfo).
|
|
2265
|
+
"signal",
|
|
2266
|
+
"defaultValue",
|
|
2267
|
+
"defaultChecked",
|
|
2268
|
+
"readOnly", // will depend wether readOnly is supported
|
|
2269
|
+
|
|
2270
|
+
"loading",
|
|
2271
|
+
"basePseudoState",
|
|
2272
|
+
"constraints",
|
|
2273
|
+
|
|
2274
|
+
// A real target inside a zone that belongs to another control — see
|
|
2275
|
+
// own_target.js.
|
|
2276
|
+
"ownTarget",
|
|
2277
|
+
|
|
2278
|
+
"autoFocus",
|
|
2279
|
+
"autoFocusVisible",
|
|
2280
|
+
"autoFocusSelect",
|
|
2281
|
+
|
|
2282
|
+
"onMouseDown",
|
|
2283
|
+
"onClick",
|
|
2284
|
+
"onKeyDown",
|
|
2285
|
+
"onPaste",
|
|
2286
|
+
"onInput",
|
|
2287
|
+
"eventReactionDefinitions",
|
|
2288
|
+
|
|
2289
|
+
"onCancel",
|
|
2290
|
+
"cancelOnBlurInvalid",
|
|
2291
|
+
"cancelOnEscape",
|
|
2292
|
+
"onActionPrevented",
|
|
2293
|
+
"onActionStart",
|
|
2294
|
+
"onActionAborted",
|
|
2295
|
+
"onActionError",
|
|
2296
|
+
"actionErrorEffect",
|
|
2297
|
+
"errorMapping",
|
|
2298
|
+
"onActionEnd",
|
|
2299
|
+
|
|
2300
|
+
"resetOnCancel",
|
|
2301
|
+
"resetOnAbort",
|
|
2302
|
+
"resetOnError",
|
|
2303
|
+
"optimistic",
|
|
2304
|
+
|
|
2305
|
+
"charGuard",
|
|
2306
|
+
"maxLengthGuard",
|
|
2307
|
+
]);
|
|
2308
|
+
|
|
2309
|
+
const MessagePropsRefContext = createContext();
|
|
2310
|
+
|
|
2311
|
+
const ControlIdContext = createContext();
|
|
2312
|
+
const ControlNameContext = createContext();
|
|
2313
|
+
const DisabledContext = createContext();
|
|
2314
|
+
const ReadOnlyContext = createContext();
|
|
2315
|
+
const RequiredContext = createContext();
|
|
2316
|
+
const LoadingContext$1 = createContext();
|
|
2317
|
+
createContext();
|
|
2318
|
+
|
|
2319
|
+
const ActionContext = createContext();
|
|
2320
|
+
const ActionRequesterContext = createContext();
|
|
2321
|
+
|
|
2322
|
+
/**
|
|
2323
|
+
* A target of its own inside a zone that belongs to another control.
|
|
2324
|
+
*
|
|
2325
|
+
* A pressable row, a picker's façade, a slide that travels under the finger:
|
|
2326
|
+
* each of them answers a press that lands anywhere in its box. An affordance an
|
|
2327
|
+
* application draws in there — a chip's cross, an eye that opens a profile, a
|
|
2328
|
+
* diskette that saves a guest — is aimed AT, not merely inside, and the press
|
|
2329
|
+
* belongs to it alone.
|
|
2330
|
+
*
|
|
2331
|
+
* Saying that by hand takes three guards, one per moment of the same press: the
|
|
2332
|
+
* pointerdown where gestures are arbitrated, the mousedown where a picker opens,
|
|
2333
|
+
* the click where it opens too when a gesture disputed the press. All three are
|
|
2334
|
+
* navi's own knowledge of navi's own event flow, and an application that gets
|
|
2335
|
+
* one wrong finds out by opening a popup it meant to keep shut. `ownTarget` is
|
|
2336
|
+
* that knowledge, said once.
|
|
2337
|
+
*
|
|
2338
|
+
* The other half is interactivity: the affordance is a control, so it already
|
|
2339
|
+
* refuses on its own terms once the zone around it holds it read-only — but a
|
|
2340
|
+
* caller's `onClick` is plain DOM and fires before any gate. So an own target
|
|
2341
|
+
* withholds the caller's handler until its own gate has allowed it, and by
|
|
2342
|
+
* default goes rather than greys: a remove cross that still removes is worse
|
|
2343
|
+
* than no cross, and one that refuses politely still says "there is something to
|
|
2344
|
+
* remove here" on a row that is only being read.
|
|
2345
|
+
*/
|
|
2346
|
+
|
|
2347
|
+
|
|
2348
|
+
const OWN_TARGET_ATTRIBUTE = "data-navi-own-target";
|
|
2349
|
+
|
|
2350
|
+
/**
|
|
2351
|
+
* Whether `event` was aimed at an own target sitting below this control — in
|
|
2352
|
+
* which case the control is not what the press was for and must not answer it.
|
|
2353
|
+
*
|
|
2354
|
+
* Read from the event's target rather than from a mark left by a handler: the
|
|
2355
|
+
* question is "who is this press for", and the DOM between the pointer and the
|
|
2356
|
+
* control is the whole answer. Nothing is asked of the own target itself, which
|
|
2357
|
+
* is what lets it be anything — a button, a link, a field.
|
|
2358
|
+
*/
|
|
2359
|
+
const isAimedAtOwnTargetBelow = (event, controlHost) => {
|
|
2360
|
+
const target = event?.target;
|
|
2361
|
+
if (!target || typeof target.closest !== "function") {
|
|
2362
|
+
return false;
|
|
2363
|
+
}
|
|
2364
|
+
const ownTarget = target.closest(`[${OWN_TARGET_ATTRIBUTE}]`);
|
|
2365
|
+
if (!ownTarget) {
|
|
2366
|
+
return false;
|
|
2367
|
+
}
|
|
2368
|
+
// The claim is against what is ABOVE it: the control that IS the own target,
|
|
2369
|
+
// and any control living inside it, are being aimed at like anything else.
|
|
2370
|
+
if (ownTarget === controlHost || ownTarget.contains(controlHost)) {
|
|
2371
|
+
return false;
|
|
2372
|
+
}
|
|
2373
|
+
// From the root rather than the host: a layered control (a picker holding an
|
|
2374
|
+
// input) has its gate on the input, and what the application drew sits beside
|
|
2375
|
+
// it, not in it.
|
|
2376
|
+
const controlRoot = controlHost.closest("[navi-control]") || controlHost;
|
|
2377
|
+
return controlRoot.contains(ownTarget);
|
|
2378
|
+
};
|
|
2379
|
+
|
|
2380
|
+
/**
|
|
2381
|
+
* Whether an own target has nothing to offer where it sits: the zone around it
|
|
2382
|
+
* is read-only, disabled or busy, so the affordance goes.
|
|
2383
|
+
*
|
|
2384
|
+
* `ownTarget="refuse"` keeps it on screen instead, refusing with a callout like
|
|
2385
|
+
* every other navi control — for an affordance whose presence is information in
|
|
2386
|
+
* itself (an eye that opens a profile is worth seeing on a row being read).
|
|
2387
|
+
*/
|
|
2388
|
+
const useOwnTargetHidden = (props) => {
|
|
2389
|
+
const disabled = useContext(DisabledContext);
|
|
2390
|
+
const readOnly = useContext(ReadOnlyContext);
|
|
2391
|
+
const loading = useContext(LoadingContext$1);
|
|
2392
|
+
const { ownTarget } = props;
|
|
2393
|
+
if (!ownTarget || ownTarget === "refuse") {
|
|
2394
|
+
return false;
|
|
2395
|
+
}
|
|
2396
|
+
return Boolean(disabled || readOnly || loading);
|
|
2397
|
+
};
|
|
2398
|
+
|
|
2107
2399
|
/*
|
|
2108
2400
|
* Deep structural equality for arbitrary JS values — what `===` can't do but this
|
|
2109
2401
|
* codebase constantly needs: memoization cache keys ({ id: 1 } equal to { id: 1 }),
|
|
@@ -6480,83 +6772,6 @@ const isInertOnClick = (element) => {
|
|
|
6480
6772
|
return true;
|
|
6481
6773
|
};
|
|
6482
6774
|
|
|
6483
|
-
const CONSTRAINT_NAME_TO_PROP = {
|
|
6484
|
-
disabled: "disabledMessage",
|
|
6485
|
-
required: "requiredMessage",
|
|
6486
|
-
pattern: "patternMessage",
|
|
6487
|
-
type_email: "typeMessage",
|
|
6488
|
-
type_number: "typeMessage",
|
|
6489
|
-
min_length: "minLengthMessage",
|
|
6490
|
-
max_length: "maxLengthMessage",
|
|
6491
|
-
min: "minMessage",
|
|
6492
|
-
max: "maxMessage",
|
|
6493
|
-
single_space: "singleSpaceMessage",
|
|
6494
|
-
same_as: "sameAsMessage",
|
|
6495
|
-
min_lower_letter: "minLowerLetterMessage",
|
|
6496
|
-
min_upper_letter: "minUpperLetterMessage",
|
|
6497
|
-
min_digit: "minDigitMessage",
|
|
6498
|
-
min_special_char: "minSpecialCharMessage",
|
|
6499
|
-
one_of: "oneOfMessage",
|
|
6500
|
-
readonly: "readOnlyMessage",
|
|
6501
|
-
available: "availableMessage",
|
|
6502
|
-
};
|
|
6503
|
-
|
|
6504
|
-
const CONSTRAINT_MESSAGE_PROP_NAME_SET = new Set(
|
|
6505
|
-
Object.values(CONSTRAINT_NAME_TO_PROP),
|
|
6506
|
-
);
|
|
6507
|
-
|
|
6508
|
-
const extractMessageAndRemainingProps = (props) => {
|
|
6509
|
-
const ownMessages = {};
|
|
6510
|
-
const remaining = {};
|
|
6511
|
-
const keyToVisit = new Set(Object.keys(props));
|
|
6512
|
-
for (const key of keyToVisit) {
|
|
6513
|
-
if (CONSTRAINT_MESSAGE_PROP_NAME_SET.has(key)) {
|
|
6514
|
-
ownMessages[key] = props[key];
|
|
6515
|
-
} else {
|
|
6516
|
-
remaining[key] = props[key];
|
|
6517
|
-
}
|
|
6518
|
-
}
|
|
6519
|
-
return [ownMessages, remaining];
|
|
6520
|
-
};
|
|
6521
|
-
|
|
6522
|
-
const getConstraintMessage = (
|
|
6523
|
-
controller,
|
|
6524
|
-
constraint,
|
|
6525
|
-
generatedMessage,
|
|
6526
|
-
{ requester },
|
|
6527
|
-
) => {
|
|
6528
|
-
const { name: constraintName } = constraint;
|
|
6529
|
-
const propName = CONSTRAINT_NAME_TO_PROP[constraintName];
|
|
6530
|
-
|
|
6531
|
-
// 1. Search first on the requester (e.g. the <li> that was clicked),
|
|
6532
|
-
// then fall back to element (e.g. the hidden <input>).
|
|
6533
|
-
if (requester) {
|
|
6534
|
-
const requesterController = requester.__uiStateController__;
|
|
6535
|
-
if (requesterController && requesterController !== controller) {
|
|
6536
|
-
const requesterControllerMessage = requesterController.props[propName];
|
|
6537
|
-
if (requesterControllerMessage) {
|
|
6538
|
-
return {
|
|
6539
|
-
message: requesterControllerMessage,
|
|
6540
|
-
origin: "requester controller",
|
|
6541
|
-
};
|
|
6542
|
-
}
|
|
6543
|
-
}
|
|
6544
|
-
}
|
|
6545
|
-
|
|
6546
|
-
const controllerMessage = controller.props[propName];
|
|
6547
|
-
if (controllerMessage) {
|
|
6548
|
-
return {
|
|
6549
|
-
message: controllerMessage,
|
|
6550
|
-
origin: "controller",
|
|
6551
|
-
};
|
|
6552
|
-
}
|
|
6553
|
-
|
|
6554
|
-
return {
|
|
6555
|
-
message: generatedMessage,
|
|
6556
|
-
origin: "generated message",
|
|
6557
|
-
};
|
|
6558
|
-
};
|
|
6559
|
-
|
|
6560
6775
|
/**
|
|
6561
6776
|
* DOM utilities for the proxy control pattern.
|
|
6562
6777
|
*
|
|
@@ -8231,8 +8446,6 @@ const ABORTED = { id: "aborted" };
|
|
|
8231
8446
|
const FAILED = { id: "failed" };
|
|
8232
8447
|
const COMPLETED = { id: "completed" };
|
|
8233
8448
|
|
|
8234
|
-
const CONSTRAINT_ATTRIBUTE_SET = new Set();
|
|
8235
|
-
|
|
8236
8449
|
const BUSY_CONSTRAINT = {
|
|
8237
8450
|
name: "busy",
|
|
8238
8451
|
messageAttribute: "data-busy-message",
|
|
@@ -9977,7 +10190,7 @@ const formatDateIso = (iso, inputType) => {
|
|
|
9977
10190
|
const READONLY_CONSTRAINT = {
|
|
9978
10191
|
name: "readonly",
|
|
9979
10192
|
messageAttribute: "data-readonly-message",
|
|
9980
|
-
check: (field) => {
|
|
10193
|
+
check: (field, { intent } = {}) => {
|
|
9981
10194
|
const readOnly = Boolean(
|
|
9982
10195
|
field.controlHostProps.readOnly ||
|
|
9983
10196
|
field.controlHostProps["aria-readonly"] === "true",
|
|
@@ -9986,6 +10199,18 @@ const READONLY_CONSTRAINT = {
|
|
|
9986
10199
|
return null;
|
|
9987
10200
|
}
|
|
9988
10201
|
|
|
10202
|
+
// Read-only, and what it opens still opens: a picker's answer often lives
|
|
10203
|
+
// in a shape only its popup draws — a plan with one tile ringed, a wheel
|
|
10204
|
+
// stopped on a time — and refusing to open leaves that shape unreadable.
|
|
10205
|
+
// Opening reads and nothing more, so it goes through; everything that would
|
|
10206
|
+
// write is refused below, and the popup content is handed the same
|
|
10207
|
+
// read-only state so each control in there refuses on its own terms. Which
|
|
10208
|
+
// controls say so, and when, is theirs to answer (see createControlInfo's
|
|
10209
|
+
// readOnlyOpens).
|
|
10210
|
+
if (intent === "read" && field.readOnlyOpens) {
|
|
10211
|
+
return null;
|
|
10212
|
+
}
|
|
10213
|
+
|
|
9989
10214
|
// A selection guarding its length (see maxLengthGuard) is what holds this
|
|
9990
10215
|
// one back, so max_length is what refuses it: same name, same message, same
|
|
9991
10216
|
// `maxLengthMessage` to say it in the caller's own words. Read-only is only
|
|
@@ -10050,6 +10275,8 @@ const readOnlyMessage = (field) => {
|
|
|
10050
10275
|
* → "navi_request_interaction" event
|
|
10051
10276
|
* → onRequestInteraction
|
|
10052
10277
|
* → check disabled / read-only / busy (via controller.controlInteraction)
|
|
10278
|
+
* against the interaction's `intent` ("write" by default, "read" for one
|
|
10279
|
+
* that only shows what is already there — see READONLY_CONSTRAINT)
|
|
10053
10280
|
* → if blocked → prevented()
|
|
10054
10281
|
* → if allowed → allowed()
|
|
10055
10282
|
* → (in allowed callback) setUIState(value)
|
|
@@ -10083,10 +10310,10 @@ const createControlInteraction = (
|
|
|
10083
10310
|
// The title this rule put on the element, if any (see checkInteractivity).
|
|
10084
10311
|
let titleWritten = null;
|
|
10085
10312
|
|
|
10086
|
-
const checkInteractivity = ({ event } = {}) => {
|
|
10313
|
+
const checkInteractivity = ({ event, intent = "write" } = {}) => {
|
|
10087
10314
|
interactionFailedConstraintInfo = null;
|
|
10088
10315
|
for (const constraint of INTERACTION_CONSTRAINT_SET) {
|
|
10089
|
-
const checkResult = constraint.check(controller);
|
|
10316
|
+
const checkResult = constraint.check(controller, { intent });
|
|
10090
10317
|
if (!checkResult) {
|
|
10091
10318
|
continue;
|
|
10092
10319
|
}
|
|
@@ -10113,7 +10340,7 @@ const createControlInteraction = (
|
|
|
10113
10340
|
if (!mci) {
|
|
10114
10341
|
continue;
|
|
10115
10342
|
}
|
|
10116
|
-
const canInteract = mci.checkInteractivity({ event });
|
|
10343
|
+
const canInteract = mci.checkInteractivity({ event, intent });
|
|
10117
10344
|
if (canInteract) {
|
|
10118
10345
|
continue;
|
|
10119
10346
|
}
|
|
@@ -10127,8 +10354,14 @@ const createControlInteraction = (
|
|
|
10127
10354
|
}
|
|
10128
10355
|
|
|
10129
10356
|
// Keep title attribute in sync for accessibility.
|
|
10357
|
+
// Only off a check that asked the general question: a title is read
|
|
10358
|
+
// whenever a pointer rests on the element, so it says what is true of the
|
|
10359
|
+
// control as a whole. A check made for an interaction that only reads (a
|
|
10360
|
+
// read-only picker asked to open its popup) answers about that one
|
|
10361
|
+
// interaction — writing the title from it would take away the "read-only"
|
|
10362
|
+
// the first time someone opened the popup.
|
|
10130
10363
|
const titleLess = !controller.controlHostProps?.title;
|
|
10131
|
-
if (titleLess) {
|
|
10364
|
+
if (intent === "write" && titleLess) {
|
|
10132
10365
|
const element = controller.ref.current;
|
|
10133
10366
|
if (element) {
|
|
10134
10367
|
if (interactionFailedConstraintInfo) {
|
|
@@ -10234,6 +10467,12 @@ const onRequestInteraction = (
|
|
|
10234
10467
|
const {
|
|
10235
10468
|
event,
|
|
10236
10469
|
name,
|
|
10470
|
+
// What this interaction would do to the control: write it, or only read it.
|
|
10471
|
+
// Everything writes unless it says otherwise — an interaction that merely
|
|
10472
|
+
// shows what is already there (opening a picker's popup, closing it again)
|
|
10473
|
+
// says "read", and that is what a control held read-only can still let
|
|
10474
|
+
// through (see READONLY_CONSTRAINT).
|
|
10475
|
+
intent = "write",
|
|
10237
10476
|
bypassInteractivity = false,
|
|
10238
10477
|
prevented,
|
|
10239
10478
|
allowed,
|
|
@@ -10259,12 +10498,26 @@ const onRequestInteraction = (
|
|
|
10259
10498
|
|
|
10260
10499
|
const currentTarget = requestInteractionCustomEvent.currentTarget;
|
|
10261
10500
|
const controlHost = findControlHost(currentTarget) || currentTarget;
|
|
10501
|
+
|
|
10502
|
+
// Aimed at something else that lives in this control's box: a chip's cross, an
|
|
10503
|
+
// eye, a diskette. The press is that affordance's alone (see own_target.js),
|
|
10504
|
+
// and stepping back here — rather than stopping the propagation over there —
|
|
10505
|
+
// is what leaves the event whole for everything that is not a navi
|
|
10506
|
+
// interaction. Stepping back, not refusing: the reaction never happened, so
|
|
10507
|
+
// its `prevented`/`always` (an `e.preventDefault()`, for most of them) have
|
|
10508
|
+
// nothing to undo and would take the press from the affordance itself.
|
|
10509
|
+
if (isAimedAtOwnTargetBelow(event, controlHost)) {
|
|
10510
|
+
debugInteraction(event, `"${name}" is for an own target below`);
|
|
10511
|
+
requestInteractionCustomEvent.preventDefault();
|
|
10512
|
+
return false;
|
|
10513
|
+
}
|
|
10514
|
+
|
|
10262
10515
|
const controller = controlHost.__uiStateController__;
|
|
10263
10516
|
|
|
10264
10517
|
if (controller && !bypassInteractivity) {
|
|
10265
10518
|
const ci = controller?.rules.interaction;
|
|
10266
10519
|
if (ci) {
|
|
10267
|
-
const canInteract = ci.checkInteractivity({ event });
|
|
10520
|
+
const canInteract = ci.checkInteractivity({ event, intent });
|
|
10268
10521
|
if (!canInteract) {
|
|
10269
10522
|
const failedInfo =
|
|
10270
10523
|
ci.interactionFailedConstraintInfo ??
|
|
@@ -16049,6 +16302,26 @@ const POSITION_PROPS = {
|
|
|
16049
16302
|
return { transform: `skew(${value})` };
|
|
16050
16303
|
},
|
|
16051
16304
|
};
|
|
16305
|
+
const singleLineEllipsisStyles = () => {
|
|
16306
|
+
return {
|
|
16307
|
+
overflow: "hidden",
|
|
16308
|
+
textOverflow: "ellipsis",
|
|
16309
|
+
overflowWrap: "normal",
|
|
16310
|
+
};
|
|
16311
|
+
};
|
|
16312
|
+
// The lines beyond the clamp are still laid out, so the clip edge decides
|
|
16313
|
+
// whether the top of the first hidden one is visible: "overflow: hidden" clips
|
|
16314
|
+
// at the padding box and lets it show inside the block-end padding. Clipping at
|
|
16315
|
+
// the content box instead ends the element right after its last visible line.
|
|
16316
|
+
const lineClampStyles = (value) => {
|
|
16317
|
+
return {
|
|
16318
|
+
"overflow": "clip",
|
|
16319
|
+
"overflowClipMargin": "content-box",
|
|
16320
|
+
"display": "-webkit-box",
|
|
16321
|
+
"-webkit-box-orient": "vertical",
|
|
16322
|
+
"-webkit-line-clamp": value,
|
|
16323
|
+
};
|
|
16324
|
+
};
|
|
16052
16325
|
const TYPO_PROPS = {
|
|
16053
16326
|
font: applyOnCSSProp("fontFamily"),
|
|
16054
16327
|
fontFamily: PASS_THROUGH,
|
|
@@ -16086,39 +16359,21 @@ const TYPO_PROPS = {
|
|
|
16086
16359
|
return null;
|
|
16087
16360
|
}
|
|
16088
16361
|
if (value === 1 || value === "1") {
|
|
16089
|
-
return
|
|
16090
|
-
overflow: "hidden",
|
|
16091
|
-
textOverflow: "ellipsis",
|
|
16092
|
-
overflowWrap: "normal",
|
|
16093
|
-
};
|
|
16362
|
+
return singleLineEllipsisStyles();
|
|
16094
16363
|
}
|
|
16095
|
-
return
|
|
16096
|
-
"overflow": "hidden",
|
|
16097
|
-
"display": "-webkit-box",
|
|
16098
|
-
"-webkit-box-orient": "vertical",
|
|
16099
|
-
"-webkit-line-clamp": value,
|
|
16100
|
-
};
|
|
16364
|
+
return lineClampStyles(value);
|
|
16101
16365
|
},
|
|
16102
16366
|
overflowEllipsis: (value) => {
|
|
16103
16367
|
if (!value) {
|
|
16104
16368
|
return null;
|
|
16105
16369
|
}
|
|
16106
|
-
return
|
|
16107
|
-
overflow: "hidden",
|
|
16108
|
-
textOverflow: "ellipsis",
|
|
16109
|
-
overflowWrap: "normal",
|
|
16110
|
-
};
|
|
16370
|
+
return singleLineEllipsisStyles();
|
|
16111
16371
|
},
|
|
16112
16372
|
lineClamp: (value) => {
|
|
16113
16373
|
if (!value) {
|
|
16114
16374
|
return null;
|
|
16115
16375
|
}
|
|
16116
|
-
return
|
|
16117
|
-
"overflow": "hidden",
|
|
16118
|
-
"display": "-webkit-box",
|
|
16119
|
-
"-webkit-box-orient": "vertical",
|
|
16120
|
-
"-webkit-line-clamp": value,
|
|
16121
|
-
};
|
|
16376
|
+
return lineClampStyles(value);
|
|
16122
16377
|
},
|
|
16123
16378
|
textAlign: PASS_THROUGH,
|
|
16124
16379
|
textBox: PASS_THROUGH,
|
|
@@ -16164,6 +16419,7 @@ const VISUAL_PROPS = {
|
|
|
16164
16419
|
overflow: PASS_THROUGH,
|
|
16165
16420
|
overflowX: PASS_THROUGH,
|
|
16166
16421
|
overflowY: PASS_THROUGH,
|
|
16422
|
+
overflowClipMargin: PASS_THROUGH,
|
|
16167
16423
|
objectFit: PASS_THROUGH,
|
|
16168
16424
|
accentColor: PASS_THROUGH,
|
|
16169
16425
|
scrollbarWidth: PASS_THROUGH,
|
|
@@ -23431,116 +23687,6 @@ registerNaviCommand("--navi-unselect", (source, event) => {
|
|
|
23431
23687
|
};
|
|
23432
23688
|
});
|
|
23433
23689
|
|
|
23434
|
-
// prop that we'll set on the control
|
|
23435
|
-
const CONTROL_ATTRIBUTE_SET = new Set([
|
|
23436
|
-
...CONSTRAINT_ATTRIBUTE_SET,
|
|
23437
|
-
|
|
23438
|
-
"ref",
|
|
23439
|
-
"children",
|
|
23440
|
-
"id",
|
|
23441
|
-
"name",
|
|
23442
|
-
"type",
|
|
23443
|
-
"value",
|
|
23444
|
-
"checked",
|
|
23445
|
-
"placeholder",
|
|
23446
|
-
"inputMode",
|
|
23447
|
-
"autoComplete",
|
|
23448
|
-
"spellcheck",
|
|
23449
|
-
"autoCorrect",
|
|
23450
|
-
"aria-controls",
|
|
23451
|
-
"tabIndex",
|
|
23452
|
-
"command",
|
|
23453
|
-
"commandFor",
|
|
23454
|
-
"command-value", // not standard but make sense, allow to give param to the command in question
|
|
23455
|
-
"list",
|
|
23456
|
-
|
|
23457
|
-
// "ui-action-target",
|
|
23458
|
-
"navi-input-type",
|
|
23459
|
-
"navi-value-pad",
|
|
23460
|
-
"navi-control-proxy-for",
|
|
23461
|
-
"navi-command-proxy-for",
|
|
23462
|
-
"navi-command-target",
|
|
23463
|
-
"onnavi_command",
|
|
23464
|
-
"onnavi_request_open",
|
|
23465
|
-
"onnavi_request_close",
|
|
23466
|
-
|
|
23467
|
-
"data-callout-arrow-x",
|
|
23468
|
-
"data-callout-point-to-border-box",
|
|
23469
|
-
"data-callout-point-to-content-box",
|
|
23470
|
-
"data-callout-viewport-spacing",
|
|
23471
|
-
"data-callout-position",
|
|
23472
|
-
"data-callout-position-fixed",
|
|
23473
|
-
|
|
23474
|
-
"data-testid", // playwright, cypress
|
|
23475
|
-
"data-separator", // used by InputGroup paste-to-fill
|
|
23476
|
-
]);
|
|
23477
|
-
// prop concerning control but that won't end up in the DOM if not inside CONTROL_ATTRIBUTE_SET
|
|
23478
|
-
const CONTROL_PROP_SET = new Set([
|
|
23479
|
-
...CONTROL_ATTRIBUTE_SET,
|
|
23480
|
-
...CONSTRAINT_MESSAGE_PROP_NAME_SET,
|
|
23481
|
-
|
|
23482
|
-
"action",
|
|
23483
|
-
"confirm",
|
|
23484
|
-
"confirmPopupContent",
|
|
23485
|
-
"actionEvent",
|
|
23486
|
-
"actionAfterChange",
|
|
23487
|
-
"actionOnMouseDown",
|
|
23488
|
-
"actionDebounce",
|
|
23489
|
-
// A signal bound two-way to the control: its value seeds the control's state and
|
|
23490
|
-
// is written back on every uiAction. Precludes value/checked (see createControlInfo).
|
|
23491
|
-
"signal",
|
|
23492
|
-
"defaultValue",
|
|
23493
|
-
"defaultChecked",
|
|
23494
|
-
"readOnly", // will depend wether readOnly is supported
|
|
23495
|
-
|
|
23496
|
-
"loading",
|
|
23497
|
-
"basePseudoState",
|
|
23498
|
-
"constraints",
|
|
23499
|
-
|
|
23500
|
-
"autoFocus",
|
|
23501
|
-
"autoFocusVisible",
|
|
23502
|
-
"autoFocusSelect",
|
|
23503
|
-
|
|
23504
|
-
"onMouseDown",
|
|
23505
|
-
"onClick",
|
|
23506
|
-
"onKeyDown",
|
|
23507
|
-
"onPaste",
|
|
23508
|
-
"onInput",
|
|
23509
|
-
"eventReactionDefinitions",
|
|
23510
|
-
|
|
23511
|
-
"onCancel",
|
|
23512
|
-
"cancelOnBlurInvalid",
|
|
23513
|
-
"cancelOnEscape",
|
|
23514
|
-
"onActionPrevented",
|
|
23515
|
-
"onActionStart",
|
|
23516
|
-
"onActionAborted",
|
|
23517
|
-
"onActionError",
|
|
23518
|
-
"actionErrorEffect",
|
|
23519
|
-
"errorMapping",
|
|
23520
|
-
"onActionEnd",
|
|
23521
|
-
|
|
23522
|
-
"resetOnCancel",
|
|
23523
|
-
"resetOnAbort",
|
|
23524
|
-
"resetOnError",
|
|
23525
|
-
"optimistic",
|
|
23526
|
-
|
|
23527
|
-
"charGuard",
|
|
23528
|
-
"maxLengthGuard",
|
|
23529
|
-
]);
|
|
23530
|
-
|
|
23531
|
-
const MessagePropsRefContext = createContext();
|
|
23532
|
-
|
|
23533
|
-
const ControlIdContext = createContext();
|
|
23534
|
-
const ControlNameContext = createContext();
|
|
23535
|
-
const DisabledContext = createContext();
|
|
23536
|
-
const ReadOnlyContext = createContext();
|
|
23537
|
-
const RequiredContext = createContext();
|
|
23538
|
-
const LoadingContext$1 = createContext();
|
|
23539
|
-
createContext();
|
|
23540
|
-
|
|
23541
|
-
const ActionContext = createContext();
|
|
23542
|
-
const ActionRequesterContext = createContext();
|
|
23543
|
-
|
|
23544
23690
|
/**
|
|
23545
23691
|
* How a control tells the labels pointing at it what it is (disabled, readOnly,
|
|
23546
23692
|
* required) and when it goes away.
|
|
@@ -25762,18 +25908,18 @@ const useUIFacadeStateController = (props, realUIStateController) => {
|
|
|
25762
25908
|
if (child !== firstChildControllerRef.current) {
|
|
25763
25909
|
return;
|
|
25764
25910
|
}
|
|
25765
|
-
if (
|
|
25766
|
-
silent &&
|
|
25767
|
-
uiStateHoldsNothing(child.uiState) &&
|
|
25768
|
-
!uiStateHoldsNothing(s.realUIStateController.uiState)
|
|
25769
|
-
) {
|
|
25911
|
+
if (silent && uiStateHoldsNothing(child.uiState)) {
|
|
25770
25912
|
// A silent sync means the child's own structure changed (children
|
|
25771
25913
|
// mounted/unmounted), not that the user acted. A child that ends up
|
|
25772
25914
|
// with no value there is one that currently *cannot* express one —
|
|
25773
25915
|
// a <List loading> holds no items yet, a popup whose items are gone
|
|
25774
25916
|
// aggregates to the empty array its stateType falls back to — which
|
|
25775
25917
|
// must not read as the user clearing the picker, nor fire its
|
|
25776
|
-
// uiAction.
|
|
25918
|
+
// uiAction. And when the picker holds nothing either, there is
|
|
25919
|
+
// still nothing to adopt: the sync would only respell one nothing
|
|
25920
|
+
// as another (an array picker's [] becoming undefined, say) and
|
|
25921
|
+
// hand that to uiAction — an empty array picker opened its popup
|
|
25922
|
+
// and told its owner the value changed.
|
|
25777
25923
|
return;
|
|
25778
25924
|
}
|
|
25779
25925
|
updatingRef.current = true;
|
|
@@ -25786,7 +25932,18 @@ const useUIFacadeStateController = (props, realUIStateController) => {
|
|
|
25786
25932
|
detail: {},
|
|
25787
25933
|
});
|
|
25788
25934
|
chainEvent(propagateUpEvent, e);
|
|
25789
|
-
s
|
|
25935
|
+
// What the popup aggregates arrives in the popup's terms, where an
|
|
25936
|
+
// empty multiple list is `undefined`. The picker answers a question of
|
|
25937
|
+
// its own shape (see resolveEmptyUIState): a <Picker type="array">
|
|
25938
|
+
// whose last item was unselected holds [], the same thing clearing it
|
|
25939
|
+
// leaves — not a value that changes type on its owner the moment it
|
|
25940
|
+
// empties.
|
|
25941
|
+
const { emptyUIState } = s.realUIStateController;
|
|
25942
|
+
const uiStateToAdopt =
|
|
25943
|
+
child.uiState === undefined && emptyUIState !== undefined
|
|
25944
|
+
? emptyUIState
|
|
25945
|
+
: child.uiState;
|
|
25946
|
+
s.realUIStateController.setUIState(uiStateToAdopt, propagateUpEvent);
|
|
25790
25947
|
updatingRef.current = false;
|
|
25791
25948
|
},
|
|
25792
25949
|
};
|
|
@@ -26696,15 +26853,34 @@ const useControlProps = (props, {
|
|
|
26696
26853
|
}
|
|
26697
26854
|
return dispatched;
|
|
26698
26855
|
};
|
|
26699
|
-
|
|
26856
|
+
|
|
26857
|
+
// What the caller wrote runs from inside the gate when this control is an
|
|
26858
|
+
// own target: a plain `onClick` is DOM, and it would fire from a cross drawn
|
|
26859
|
+
// greyed by the read-only control the affordance sits in (see own_target.js).
|
|
26860
|
+
const callerHandlerIsGated = Boolean(props.ownTarget);
|
|
26861
|
+
const gateCallerHandler = (handler, e) => {
|
|
26862
|
+
if (!handler) {
|
|
26863
|
+
return undefined;
|
|
26864
|
+
}
|
|
26865
|
+
if (callerHandlerIsGated) {
|
|
26866
|
+
return handler;
|
|
26867
|
+
}
|
|
26868
|
+
handler(e);
|
|
26869
|
+
return undefined;
|
|
26870
|
+
};
|
|
26871
|
+
const applyEventReaction = (eventName, e, callerHandler) => {
|
|
26700
26872
|
const defaultEventReactionDefinition = defaultEventReactionDefinitions?.[eventName];
|
|
26701
26873
|
const customEventReactionDefinition = eventReactionDefinitions?.[eventName];
|
|
26702
26874
|
const reaction = customEventReactionDefinition?.(e) ?? defaultEventReactionDefinition?.(e);
|
|
26703
26875
|
if (!reaction) {
|
|
26876
|
+
// No reaction means no gate: there is nothing here that could refuse the
|
|
26877
|
+
// caller's handler, so withholding it would only lose it.
|
|
26878
|
+
callerHandler?.(e);
|
|
26704
26879
|
return false;
|
|
26705
26880
|
}
|
|
26706
26881
|
const {
|
|
26707
26882
|
name,
|
|
26883
|
+
intent,
|
|
26708
26884
|
bypassInteractivity = false,
|
|
26709
26885
|
allowed,
|
|
26710
26886
|
prevented,
|
|
@@ -26714,6 +26890,7 @@ const useControlProps = (props, {
|
|
|
26714
26890
|
return dispatchRequestInteraction(control, {
|
|
26715
26891
|
event: e,
|
|
26716
26892
|
name,
|
|
26893
|
+
intent,
|
|
26717
26894
|
bypassInteractivity,
|
|
26718
26895
|
prevented: () => {
|
|
26719
26896
|
debugInteraction(e, `interaction not allowed`);
|
|
@@ -26723,19 +26900,18 @@ const useControlProps = (props, {
|
|
|
26723
26900
|
prevented?.();
|
|
26724
26901
|
},
|
|
26725
26902
|
allowed: () => {
|
|
26903
|
+
callerHandler?.(e);
|
|
26726
26904
|
allowed?.();
|
|
26727
26905
|
},
|
|
26728
26906
|
always
|
|
26729
26907
|
});
|
|
26730
26908
|
};
|
|
26731
26909
|
const onMouseDown = e => {
|
|
26732
|
-
props.onMouseDown
|
|
26733
|
-
applyEventReaction("mouseDown", e);
|
|
26910
|
+
applyEventReaction("mouseDown", e, gateCallerHandler(props.onMouseDown, e));
|
|
26734
26911
|
transferFocusToTarget(e);
|
|
26735
26912
|
};
|
|
26736
26913
|
const onClick = e => {
|
|
26737
|
-
props.onClick
|
|
26738
|
-
applyEventReaction("click", e);
|
|
26914
|
+
applyEventReaction("click", e, gateCallerHandler(props.onClick, e));
|
|
26739
26915
|
transferFocusToTarget(e);
|
|
26740
26916
|
};
|
|
26741
26917
|
const onKeyDown = e => {
|
|
@@ -26853,6 +27029,7 @@ const createControlInfo = (props, {
|
|
|
26853
27029
|
let defaultStatePropName;
|
|
26854
27030
|
let stateInitial;
|
|
26855
27031
|
let readOnlySupported = false;
|
|
27032
|
+
let readOnlyOpens = false;
|
|
26856
27033
|
let disabledSupported = false;
|
|
26857
27034
|
let hasStateProp;
|
|
26858
27035
|
let value;
|
|
@@ -26939,6 +27116,14 @@ const createControlInfo = (props, {
|
|
|
26939
27116
|
// aria-readonly plus a refused interaction — see the select reactions in
|
|
26940
27117
|
// getDefaultEventReactionDefinitions.
|
|
26941
27118
|
readOnlySupported = controlType === "picker" && INPUT_TYPE_SUPPORTING_READONLY_SET.has(typeProp);
|
|
27119
|
+
// A picker's popup is content of its own — a plan with one tile ringed, a
|
|
27120
|
+
// wheel stopped on a time, a list showing what was chosen — so read-only
|
|
27121
|
+
// does not close it: it opens, and everything in it is held read-only in
|
|
27122
|
+
// turn (see the ReadOnlyContext in picker.jsx). Two pickers this is not
|
|
27123
|
+
// true of: one with no popup of its own, which opens the browser's and
|
|
27124
|
+
// cannot hold that read-only (see PickerNative), and one whose caller says
|
|
27125
|
+
// its popup is a form with nothing to read (openWhileReadOnly={false}).
|
|
27126
|
+
readOnlyOpens = controlType === "picker" && props.children !== undefined && props.openWhileReadOnly !== false;
|
|
26942
27127
|
}
|
|
26943
27128
|
|
|
26944
27129
|
// The suggestion the control starts on, as opposed to what it holds — what a
|
|
@@ -26965,6 +27150,7 @@ const createControlInfo = (props, {
|
|
|
26965
27150
|
signalHoldsChecked,
|
|
26966
27151
|
stateFromSignal,
|
|
26967
27152
|
readOnlySupported,
|
|
27153
|
+
readOnlyOpens,
|
|
26968
27154
|
disabledSupported
|
|
26969
27155
|
};
|
|
26970
27156
|
};
|
|
@@ -27248,6 +27434,15 @@ const useInteractiveProps = (props, {
|
|
|
27248
27434
|
} = props;
|
|
27249
27435
|
const [controlRootProps, controlHostProps] = splitControlProps(props);
|
|
27250
27436
|
controlRootProps["navi-control"] = controlInfo.controlType;
|
|
27437
|
+
if (props.ownTarget) {
|
|
27438
|
+
// "This press is mine" said in the DOM, because that is where it is read
|
|
27439
|
+
// from the outside: by the controls above (see own_target.js), and by the
|
|
27440
|
+
// two gesture readers below — the one that travels a box and the one that
|
|
27441
|
+
// carries a piece, each with its own way of being told to keep out.
|
|
27442
|
+
controlRootProps[OWN_TARGET_ATTRIBUTE] = "";
|
|
27443
|
+
controlRootProps["data-no-drag-travel"] = "";
|
|
27444
|
+
controlRootProps["data-drag-ignore"] = "";
|
|
27445
|
+
}
|
|
27251
27446
|
const {
|
|
27252
27447
|
"navi-control-proxy-for": naviProxyFor
|
|
27253
27448
|
} = props;
|
|
@@ -27315,6 +27510,10 @@ const useInteractiveProps = (props, {
|
|
|
27315
27510
|
const actionLoading = optimistic ? false : actionStatus.loading;
|
|
27316
27511
|
const loadingResolved = loadingBase || actionLoading;
|
|
27317
27512
|
const readOnlyResolved = readOnlyBase || actionLoading;
|
|
27513
|
+
// Read-only, and what this control opens still opens: reading what is in
|
|
27514
|
+
// there changes nothing. Read by READONLY_CONSTRAINT, which lets an
|
|
27515
|
+
// interaction that only reads through on it.
|
|
27516
|
+
uiStateController.readOnlyOpens = Boolean(controlInfo.readOnlyOpens);
|
|
27318
27517
|
// Both halves of "busy" that do not come from the bound action, kept apart
|
|
27319
27518
|
// from each other and from it: BUSY_CONSTRAINT answers each from its own
|
|
27320
27519
|
// live source rather than from the rendered aria-busy, which conflates all
|
|
@@ -28360,6 +28559,7 @@ const ButtonFirstResolver = props => {
|
|
|
28360
28559
|
const Next = useNextResolver();
|
|
28361
28560
|
const defaultRef = useRef(null);
|
|
28362
28561
|
props.ref = props.ref || defaultRef;
|
|
28562
|
+
const ownTargetHidden = useOwnTargetHidden(props);
|
|
28363
28563
|
|
|
28364
28564
|
// Attached to the element rather than kept as a prop: the action a button
|
|
28365
28565
|
// requests is not always run by the button (a submit button hands the send to
|
|
@@ -28369,6 +28569,9 @@ const ButtonFirstResolver = props => {
|
|
|
28369
28569
|
message: props.confirm,
|
|
28370
28570
|
content: props.confirmPopupContent
|
|
28371
28571
|
});
|
|
28572
|
+
if (ownTargetHidden) {
|
|
28573
|
+
return null;
|
|
28574
|
+
}
|
|
28372
28575
|
return jsx(Next, {
|
|
28373
28576
|
...props
|
|
28374
28577
|
});
|
|
@@ -28459,6 +28662,21 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
28459
28662
|
children: naviI18n("button.open")
|
|
28460
28663
|
})
|
|
28461
28664
|
};
|
|
28665
|
+
|
|
28666
|
+
/**
|
|
28667
|
+
* @type {import("ignore:preact").FunctionComponent<{
|
|
28668
|
+
* ownTarget?: boolean | "refuse",
|
|
28669
|
+
* [key: string]: any,
|
|
28670
|
+
* }>}
|
|
28671
|
+
* @param {boolean|"refuse"} [ownTarget] A real target inside a zone that belongs
|
|
28672
|
+
* to another control — a chip's cross on a picker's façade, an eye on a
|
|
28673
|
+
* pressable row, a diskette inside a slide that travels. The press is this
|
|
28674
|
+
* button's alone (no travel starts, no popup opens, nothing above answers) and
|
|
28675
|
+
* its `onClick` waits for its own interaction gate instead of firing from the
|
|
28676
|
+
* DOM. Where the zone around it is read-only, disabled or busy the button
|
|
28677
|
+
* goes; `"refuse"` keeps it on screen refusing with a callout, for an
|
|
28678
|
+
* affordance whose presence is information in itself.
|
|
28679
|
+
*/
|
|
28462
28680
|
const Button = createComponentResolver([ButtonFirstResolver, ButtonRouteResolver, ButtonCommandPropResolver, ButtonUI]);
|
|
28463
28681
|
|
|
28464
28682
|
// How long a popup waits before handing the focus to a field, when giving it
|
|
@@ -30019,6 +30237,10 @@ const css$_ = /* css */`
|
|
|
30019
30237
|
padding: 0;
|
|
30020
30238
|
flex-direction: column;
|
|
30021
30239
|
|
|
30240
|
+
/* A new surface writes in its own ink, not in its container's — see
|
|
30241
|
+
--navi-popup-color. Declared here rather than left to the UA's own
|
|
30242
|
+
dialog { color: CanvasText } so the ink is themed along with the paper. */
|
|
30243
|
+
color: var(--navi-popup-color);
|
|
30022
30244
|
background-color: var(--dialog-background-color);
|
|
30023
30245
|
border-width: var(--dialog-border-width);
|
|
30024
30246
|
border-style: solid;
|
|
@@ -31548,6 +31770,10 @@ const css$Z = /* css */`
|
|
|
31548
31770
|
var(--x-popover-max-height)
|
|
31549
31771
|
);
|
|
31550
31772
|
max-height: var(--x-popover-max-height);
|
|
31773
|
+
/* A new surface writes in its own ink, not in its container's — see
|
|
31774
|
+
--navi-popup-color. The UA only resets color on a [popover] element,
|
|
31775
|
+
and the local renderer is a plain div. */
|
|
31776
|
+
color: var(--navi-popup-color);
|
|
31551
31777
|
background-color: var(--popover-background-color);
|
|
31552
31778
|
border-width: var(--popover-border-width);
|
|
31553
31779
|
border-style: solid;
|
|
@@ -47678,7 +47904,27 @@ installImportMetaCssBuild(import.meta);const css$J = /* css */`
|
|
|
47678
47904
|
border-width: var(--border-width);
|
|
47679
47905
|
border-style: solid;
|
|
47680
47906
|
border-color: var(--x-border-color);
|
|
47681
|
-
|
|
47907
|
+
/* Squared from the outside, corner by corner: a checkbox in a group can
|
|
47908
|
+
arrive wrapped (in a label, in a row carrying a state), and the ask then
|
|
47909
|
+
travels down as inherited custom properties rather than as a radius
|
|
47910
|
+
landing on this element. Each corner falls back to the checkbox's own
|
|
47911
|
+
radius when nothing asks for anything. */
|
|
47912
|
+
border-top-left-radius: var(
|
|
47913
|
+
--x-corner-top-left-radius,
|
|
47914
|
+
var(--border-radius)
|
|
47915
|
+
);
|
|
47916
|
+
border-top-right-radius: var(
|
|
47917
|
+
--x-corner-top-right-radius,
|
|
47918
|
+
var(--border-radius)
|
|
47919
|
+
);
|
|
47920
|
+
border-bottom-right-radius: var(
|
|
47921
|
+
--x-corner-bottom-right-radius,
|
|
47922
|
+
var(--border-radius)
|
|
47923
|
+
);
|
|
47924
|
+
border-bottom-left-radius: var(
|
|
47925
|
+
--x-corner-bottom-left-radius,
|
|
47926
|
+
var(--border-radius)
|
|
47927
|
+
);
|
|
47682
47928
|
outline-width: var(--outline-width);
|
|
47683
47929
|
outline-style: none;
|
|
47684
47930
|
outline-color: var(--outline-color);
|
|
@@ -48644,7 +48890,26 @@ installImportMetaCssBuild(import.meta);const css$H = /* css */`
|
|
|
48644
48890
|
border-width: var(--button-border-width);
|
|
48645
48891
|
border-style: solid;
|
|
48646
48892
|
border-color: var(--x-border-color);
|
|
48647
|
-
|
|
48893
|
+
/* Squared from the outside, corner by corner: a row of these is the
|
|
48894
|
+
segmented control a Group is for, and one of them can arrive wrapped
|
|
48895
|
+
(in a tooltip, in a label) — so the ask travels down as inherited
|
|
48896
|
+
custom properties rather than as a radius landing on this element. */
|
|
48897
|
+
border-top-left-radius: var(
|
|
48898
|
+
--x-corner-top-left-radius,
|
|
48899
|
+
var(--button-border-radius)
|
|
48900
|
+
);
|
|
48901
|
+
border-top-right-radius: var(
|
|
48902
|
+
--x-corner-top-right-radius,
|
|
48903
|
+
var(--button-border-radius)
|
|
48904
|
+
);
|
|
48905
|
+
border-bottom-right-radius: var(
|
|
48906
|
+
--x-corner-bottom-right-radius,
|
|
48907
|
+
var(--button-border-radius)
|
|
48908
|
+
);
|
|
48909
|
+
border-bottom-left-radius: var(
|
|
48910
|
+
--x-corner-bottom-left-radius,
|
|
48911
|
+
var(--button-border-radius)
|
|
48912
|
+
);
|
|
48648
48913
|
|
|
48649
48914
|
.navi_icon,
|
|
48650
48915
|
img {
|
|
@@ -48953,6 +49218,28 @@ installImportMetaCssBuild(import.meta);const css$G = /* css */`
|
|
|
48953
49218
|
--x-thumb-color: var(--thumb-color);
|
|
48954
49219
|
--x-thumb-border: none;
|
|
48955
49220
|
--x-thumb-cursor: var(--thumb-cursor);
|
|
49221
|
+
/* Squared from the outside, corner by corner — resolved here once because
|
|
49222
|
+
what draws the frame is not this element but the three layers stacked
|
|
49223
|
+
below it (background, track, fill), which cannot take it by inherit the
|
|
49224
|
+
way a control with a single frame does. A range can arrive wrapped, so
|
|
49225
|
+
the ask travels down as inherited custom properties; each corner falls
|
|
49226
|
+
back to the track's own radius when nothing asks for anything. */
|
|
49227
|
+
--x-range-corner-top-left: var(
|
|
49228
|
+
--x-corner-top-left-radius,
|
|
49229
|
+
var(--border-radius)
|
|
49230
|
+
);
|
|
49231
|
+
--x-range-corner-top-right: var(
|
|
49232
|
+
--x-corner-top-right-radius,
|
|
49233
|
+
var(--border-radius)
|
|
49234
|
+
);
|
|
49235
|
+
--x-range-corner-bottom-right: var(
|
|
49236
|
+
--x-corner-bottom-right-radius,
|
|
49237
|
+
var(--border-radius)
|
|
49238
|
+
);
|
|
49239
|
+
--x-range-corner-bottom-left: var(
|
|
49240
|
+
--x-corner-bottom-left-radius,
|
|
49241
|
+
var(--border-radius)
|
|
49242
|
+
);
|
|
48956
49243
|
|
|
48957
49244
|
position: relative;
|
|
48958
49245
|
box-sizing: border-box;
|
|
@@ -48961,10 +49248,26 @@ installImportMetaCssBuild(import.meta);const css$G = /* css */`
|
|
|
48961
49248
|
margin: 2px;
|
|
48962
49249
|
flex-direction: row;
|
|
48963
49250
|
align-items: center;
|
|
48964
|
-
/* Just for the outline, the real border radius of the range is fixed */
|
|
48965
49251
|
font-size: var(--font-size);
|
|
48966
49252
|
font-family: var(--font-family);
|
|
48967
|
-
|
|
49253
|
+
/* The ring around the whole control, which follows the claim too: a range
|
|
49254
|
+
squared along a seam must not keep a rounded ring over it. */
|
|
49255
|
+
border-top-left-radius: var(
|
|
49256
|
+
--x-corner-top-left-radius,
|
|
49257
|
+
var(--outline-border-radius)
|
|
49258
|
+
);
|
|
49259
|
+
border-top-right-radius: var(
|
|
49260
|
+
--x-corner-top-right-radius,
|
|
49261
|
+
var(--outline-border-radius)
|
|
49262
|
+
);
|
|
49263
|
+
border-bottom-right-radius: var(
|
|
49264
|
+
--x-corner-bottom-right-radius,
|
|
49265
|
+
var(--outline-border-radius)
|
|
49266
|
+
);
|
|
49267
|
+
border-bottom-left-radius: var(
|
|
49268
|
+
--x-corner-bottom-left-radius,
|
|
49269
|
+
var(--outline-border-radius)
|
|
49270
|
+
);
|
|
48968
49271
|
outline-width: var(--outline-width);
|
|
48969
49272
|
outline-style: none;
|
|
48970
49273
|
outline-color: var(--outline-color);
|
|
@@ -49005,7 +49308,10 @@ installImportMetaCssBuild(import.meta);const css$G = /* css */`
|
|
|
49005
49308
|
border-width: var(--border-width);
|
|
49006
49309
|
border-style: solid;
|
|
49007
49310
|
border-color: var(--x-border-color);
|
|
49008
|
-
border-radius: var(--
|
|
49311
|
+
border-top-left-radius: var(--x-range-corner-top-left);
|
|
49312
|
+
border-top-right-radius: var(--x-range-corner-top-right);
|
|
49313
|
+
border-bottom-right-radius: var(--x-range-corner-bottom-right);
|
|
49314
|
+
border-bottom-left-radius: var(--x-range-corner-bottom-left);
|
|
49009
49315
|
}
|
|
49010
49316
|
.navi_input_range_track {
|
|
49011
49317
|
position: absolute;
|
|
@@ -49015,7 +49321,10 @@ installImportMetaCssBuild(import.meta);const css$G = /* css */`
|
|
|
49015
49321
|
border-width: var(--border-width);
|
|
49016
49322
|
border-style: solid;
|
|
49017
49323
|
border-color: var(--x-track-border-color);
|
|
49018
|
-
border-radius: var(--
|
|
49324
|
+
border-top-left-radius: var(--x-range-corner-top-left);
|
|
49325
|
+
border-top-right-radius: var(--x-range-corner-top-right);
|
|
49326
|
+
border-bottom-right-radius: var(--x-range-corner-bottom-right);
|
|
49327
|
+
border-bottom-left-radius: var(--x-range-corner-bottom-left);
|
|
49019
49328
|
}
|
|
49020
49329
|
.navi_input_range_fill {
|
|
49021
49330
|
position: absolute;
|
|
@@ -49023,7 +49332,10 @@ installImportMetaCssBuild(import.meta);const css$G = /* css */`
|
|
|
49023
49332
|
height: var(--height);
|
|
49024
49333
|
background: var(--x-fill-color);
|
|
49025
49334
|
background-clip: content-box;
|
|
49026
|
-
border-radius: var(--
|
|
49335
|
+
border-top-left-radius: var(--x-range-corner-top-left);
|
|
49336
|
+
border-top-right-radius: var(--x-range-corner-top-right);
|
|
49337
|
+
border-bottom-right-radius: var(--x-range-corner-bottom-right);
|
|
49338
|
+
border-bottom-left-radius: var(--x-range-corner-bottom-left);
|
|
49027
49339
|
clip-path: inset(0 calc((1 - var(--x-fill-ratio)) * 100%) 0 0);
|
|
49028
49340
|
}
|
|
49029
49341
|
.navi_input_range_thumb {
|
|
@@ -50095,7 +50407,27 @@ const inputCss = /* css */`
|
|
|
50095
50407
|
border-width: var(--border-width);
|
|
50096
50408
|
border-style: solid;
|
|
50097
50409
|
border-color: var(--x-border-color);
|
|
50098
|
-
|
|
50410
|
+
/* Squared from the outside, corner by corner: an input is not always the
|
|
50411
|
+
member a Group joins — it can arrive wrapped (in a Box carrying a state,
|
|
50412
|
+
in a tooltip) — so the ask travels down as inherited custom properties
|
|
50413
|
+
rather than as a radius landing on this element. Each corner falls back
|
|
50414
|
+
to the input's own radius when nothing asks for anything. */
|
|
50415
|
+
border-top-left-radius: var(
|
|
50416
|
+
--x-corner-top-left-radius,
|
|
50417
|
+
var(--border-radius)
|
|
50418
|
+
);
|
|
50419
|
+
border-top-right-radius: var(
|
|
50420
|
+
--x-corner-top-right-radius,
|
|
50421
|
+
var(--border-radius)
|
|
50422
|
+
);
|
|
50423
|
+
border-bottom-right-radius: var(
|
|
50424
|
+
--x-corner-bottom-right-radius,
|
|
50425
|
+
var(--border-radius)
|
|
50426
|
+
);
|
|
50427
|
+
border-bottom-left-radius: var(
|
|
50428
|
+
--x-corner-bottom-left-radius,
|
|
50429
|
+
var(--border-radius)
|
|
50430
|
+
);
|
|
50099
50431
|
outline-width: var(--outline-width);
|
|
50100
50432
|
outline-color: var(--outline-color);
|
|
50101
50433
|
outline-offset: var(--outline-offset);
|
|
@@ -51836,7 +52168,7 @@ const css$E = /* css */`
|
|
|
51836
52168
|
positioned element to mean anything, hence position: relative.
|
|
51837
52169
|
Deliberately not paired with isolation: isolate — a stacking context
|
|
51838
52170
|
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
|
|
52171
|
+
counts on its own band reaching the whole page. What keeps these
|
|
51840
52172
|
values from escaping is instead that everything they could reach is a
|
|
51841
52173
|
band above them (see navi_z_indexes.js). */
|
|
51842
52174
|
> *:hover,
|
|
@@ -51856,6 +52188,25 @@ const css$E = /* css */`
|
|
|
51856
52188
|
position: relative;
|
|
51857
52189
|
z-index: var(--navi-z-index-control-focused);
|
|
51858
52190
|
}
|
|
52191
|
+
/* The member holding something open. Neither of the two above covers it:
|
|
52192
|
+
the click that opened the popup gives no focus ring, the focus itself
|
|
52193
|
+
left for the popup's content, and the pointer is free to travel to a
|
|
52194
|
+
neighbour — yet the member keeps the border color its open state gives
|
|
52195
|
+
it, and that border is exactly what the neighbour painted after it
|
|
52196
|
+
slices. Read as a state, not as a pseudo-class: :active only lasts as
|
|
52197
|
+
long as the button is held down, and while it is held :hover is true
|
|
52198
|
+
anyway, so it would add nothing here.
|
|
52199
|
+
|
|
52200
|
+
:has, for the same reason as focus-visible above — the group member can
|
|
52201
|
+
be an enrobage around the control that expands — and reaching a popup
|
|
52202
|
+
held inline (a Popover with layer="local" renders inside its member)
|
|
52203
|
+
costs nothing: that popup only reads expanded while its own member is,
|
|
52204
|
+
which is the member this raises. */
|
|
52205
|
+
> *[aria-expanded="true"],
|
|
52206
|
+
> *:has([aria-expanded="true"]) {
|
|
52207
|
+
position: relative;
|
|
52208
|
+
z-index: var(--navi-z-index-control-expanded);
|
|
52209
|
+
}
|
|
51859
52210
|
|
|
51860
52211
|
/* Horizontal (default): Cumulative margin for border overlap */
|
|
51861
52212
|
&:not([data-vertical]) {
|
|
@@ -55286,6 +55637,16 @@ const isTextInputElement = (el) => {
|
|
|
55286
55637
|
);
|
|
55287
55638
|
};
|
|
55288
55639
|
|
|
55640
|
+
// How many lines the thing around a component gives it, when that thing caps
|
|
55641
|
+
// its own height and cannot cap what it holds.
|
|
55642
|
+
//
|
|
55643
|
+
// A <Picker> is the case it exists for: its value is clamped with maxLines,
|
|
55644
|
+
// which is CSS line-clamp — it counts line boxes of inline text and has no idea
|
|
55645
|
+
// what a wrapped flex row is. So a <BadgeList> rendered as a picker's ui reads
|
|
55646
|
+
// the number from here and caps its own rows to it, and the picker turns its
|
|
55647
|
+
// own clamp off (see .navi_picker_value:has(.navi_badge_list) in picker.jsx).
|
|
55648
|
+
const MaxLinesContext = createContext(undefined);
|
|
55649
|
+
|
|
55289
55650
|
// When a component render a prop that can be anything (js value of preact element)
|
|
55290
55651
|
// make sure it cannot throw during render by converting it to a string if it's not a valid preact element or a primitive value
|
|
55291
55652
|
const renderSafe = (value) => {
|
|
@@ -55783,6 +56144,14 @@ const PickerNative = props => {
|
|
|
55783
56144
|
dispatchRequestInteraction(pickerInput, {
|
|
55784
56145
|
event: e,
|
|
55785
56146
|
name: "navi_request_open to show native picker",
|
|
56147
|
+
// No "read" intent here, unlike a picker holding a popup of its own
|
|
56148
|
+
// (see PickerCustom): the browser's picker cannot be held read-only,
|
|
56149
|
+
// whichever way its type falls. Where `readonly` applies (date, time,
|
|
56150
|
+
// month, number…) the input is not mutable and showPicker() refuses
|
|
56151
|
+
// it — there is nothing to open. Where it does not (color, file) the
|
|
56152
|
+
// browser opens all the same and writes whatever is chosen straight
|
|
56153
|
+
// into the input, which is read-only in name only. So a read-only
|
|
56154
|
+
// native picker says why instead, on the trigger.
|
|
55786
56155
|
prevented: () => {
|
|
55787
56156
|
e.preventDefault();
|
|
55788
56157
|
},
|
|
@@ -55973,14 +56342,20 @@ const PickerCustom = props => {
|
|
|
55973
56342
|
// already what it is, and this only re-runs the same reaction.
|
|
55974
56343
|
const inputEl = getPickerInput(ref.current);
|
|
55975
56344
|
const valueAtClose = getUIStateFromElement(inputEl);
|
|
55976
|
-
|
|
56345
|
+
const controller = inputEl?.__uiStateController__;
|
|
56346
|
+
if (controller?.controlHostProps.readOnly) {
|
|
56347
|
+
// Opened only to be read: what it shows stays the suggestion it
|
|
56348
|
+
// was. A look is not an answer, and the signal behind it is not
|
|
56349
|
+
// written by one.
|
|
56350
|
+
debugPopup(closeEvent, `picker is read-only -> nothing to commit`);
|
|
56351
|
+
} else if (valueAtOpen === undefined && compareTwoJsValues(valueAtClose, valueAtOpen)) {
|
|
55977
56352
|
// Same third case onRequestClose steps around: nothing held,
|
|
55978
56353
|
// nothing shown, nothing picked. There is no suggestion here to
|
|
55979
56354
|
// turn into an answer.
|
|
55980
56355
|
debugPopup(closeEvent, `picker showed nothing -> nothing to commit`);
|
|
55981
56356
|
} else {
|
|
55982
56357
|
debugPopup(closeEvent, `picker defined a suggestion -> commit it`);
|
|
55983
|
-
commitUIStateAsAnswer(
|
|
56358
|
+
commitUIStateAsAnswer(controller, closeEvent);
|
|
55984
56359
|
}
|
|
55985
56360
|
}
|
|
55986
56361
|
leaveExpanded({
|
|
@@ -56069,6 +56444,13 @@ const PickerCustom = props => {
|
|
|
56069
56444
|
requestInteraction({
|
|
56070
56445
|
event: e,
|
|
56071
56446
|
name: "navi_request_open_event",
|
|
56447
|
+
// Showing what the picker already holds, in the shape only the popup
|
|
56448
|
+
// draws it in — nothing of the value is written on the way in, nor on
|
|
56449
|
+
// the way out. Every interaction below says the same, which is what
|
|
56450
|
+
// lets a read-only picker be opened and read while everything that
|
|
56451
|
+
// would write it (paste, cut, the clear cross) stays refused. See
|
|
56452
|
+
// READONLY_CONSTRAINT.
|
|
56453
|
+
intent: "read",
|
|
56072
56454
|
allowed: () => {
|
|
56073
56455
|
requestOpen(e);
|
|
56074
56456
|
}
|
|
@@ -56077,6 +56459,7 @@ const PickerCustom = props => {
|
|
|
56077
56459
|
onnavi_request_close: e => {
|
|
56078
56460
|
requestInteraction({
|
|
56079
56461
|
event: e,
|
|
56462
|
+
intent: "read",
|
|
56080
56463
|
allowed: () => {
|
|
56081
56464
|
requestClose(e, {
|
|
56082
56465
|
isCancel: e.detail.isCancel
|
|
@@ -56095,6 +56478,7 @@ const PickerCustom = props => {
|
|
|
56095
56478
|
"a-z": e => {
|
|
56096
56479
|
return {
|
|
56097
56480
|
name: "letter key to open",
|
|
56481
|
+
intent: "read",
|
|
56098
56482
|
allowed: () => {
|
|
56099
56483
|
requestOpen(e);
|
|
56100
56484
|
}
|
|
@@ -56103,6 +56487,7 @@ const PickerCustom = props => {
|
|
|
56103
56487
|
"0-9": e => {
|
|
56104
56488
|
return {
|
|
56105
56489
|
name: "numeric key to open",
|
|
56490
|
+
intent: "read",
|
|
56106
56491
|
allowed: () => {
|
|
56107
56492
|
requestOpen(e);
|
|
56108
56493
|
}
|
|
@@ -56111,6 +56496,7 @@ const PickerCustom = props => {
|
|
|
56111
56496
|
"arrowdown": e => {
|
|
56112
56497
|
return {
|
|
56113
56498
|
name: "arrow_down_to_open",
|
|
56499
|
+
intent: "read",
|
|
56114
56500
|
allowed: () => {
|
|
56115
56501
|
requestOpen(e);
|
|
56116
56502
|
e.preventDefault(); // prevent container scroll
|
|
@@ -56120,6 +56506,7 @@ const PickerCustom = props => {
|
|
|
56120
56506
|
"arrowup": e => {
|
|
56121
56507
|
return {
|
|
56122
56508
|
name: "arrow_up_to_open",
|
|
56509
|
+
intent: "read",
|
|
56123
56510
|
allowed: () => {
|
|
56124
56511
|
requestOpen(e);
|
|
56125
56512
|
e.preventDefault(); // prevent container scroll
|
|
@@ -56129,6 +56516,7 @@ const PickerCustom = props => {
|
|
|
56129
56516
|
"space": e => {
|
|
56130
56517
|
return {
|
|
56131
56518
|
name: "space_to_open",
|
|
56519
|
+
intent: "read",
|
|
56132
56520
|
allowed: () => {
|
|
56133
56521
|
requestOpen(e);
|
|
56134
56522
|
e.preventDefault(); // prevent scroll
|
|
@@ -56143,6 +56531,7 @@ const PickerCustom = props => {
|
|
|
56143
56531
|
}
|
|
56144
56532
|
return {
|
|
56145
56533
|
name: "enter_to_open",
|
|
56534
|
+
intent: "read",
|
|
56146
56535
|
allowed: () => {
|
|
56147
56536
|
requestOpen(e);
|
|
56148
56537
|
e.preventDefault(); // prevent form submission
|
|
@@ -56156,6 +56545,7 @@ const PickerCustom = props => {
|
|
|
56156
56545
|
const isCancel = escapeEffect === "cancel";
|
|
56157
56546
|
return {
|
|
56158
56547
|
name: isCancel ? "escape_to_cancel" : "escape_to_close",
|
|
56548
|
+
intent: "read",
|
|
56159
56549
|
allowed: () => {
|
|
56160
56550
|
requestClose(e, {
|
|
56161
56551
|
isCancel
|
|
@@ -56187,6 +56577,7 @@ const PickerCustom = props => {
|
|
|
56187
56577
|
// choice being taken from anyone.
|
|
56188
56578
|
return {
|
|
56189
56579
|
name: "mousedown to close picker",
|
|
56580
|
+
intent: "read",
|
|
56190
56581
|
allowed: () => requestClose(e, {
|
|
56191
56582
|
isCancel: true
|
|
56192
56583
|
})
|
|
@@ -56197,6 +56588,7 @@ const PickerCustom = props => {
|
|
|
56197
56588
|
}
|
|
56198
56589
|
return {
|
|
56199
56590
|
name: "mousedown to open picker",
|
|
56591
|
+
intent: "read",
|
|
56200
56592
|
allowed: () => {
|
|
56201
56593
|
debugFocus(e, `prevent browser giving focus to button (mousedown.preventDefault())`);
|
|
56202
56594
|
requestOpen(e);
|
|
@@ -56214,6 +56606,7 @@ const PickerCustom = props => {
|
|
|
56214
56606
|
// above), this is where the picker opens for real.
|
|
56215
56607
|
return {
|
|
56216
56608
|
name: e.detail === 0 ? "click (keyboard or progammatic) to open picker" : "click to open picker",
|
|
56609
|
+
intent: "read",
|
|
56217
56610
|
prevented: () => {
|
|
56218
56611
|
e.preventDefault();
|
|
56219
56612
|
},
|
|
@@ -57652,12 +58045,24 @@ const ListItemSelectable = props => {
|
|
|
57652
58045
|
...rest
|
|
57653
58046
|
} = props;
|
|
57654
58047
|
const multiple = useContext(SelectableListMultipleContext);
|
|
58048
|
+
// Whose reason it is that this row cannot be taken. Read-only reaching it
|
|
58049
|
+
// from above is the LIST's, and what is settled is then the whole answer —
|
|
58050
|
+
// said as the selection where several things are taken, as the choice where
|
|
58051
|
+
// one thing is. Said of each row in turn, "this option is not available"
|
|
58052
|
+
// describes something else entirely: a list where each row happens to be
|
|
58053
|
+
// unavailable for its own reasons, which goes on being said that way. Busy
|
|
58054
|
+
// is not settled either: a list waiting on something says nothing about what
|
|
58055
|
+
// will be possible once it is done, so the row keeps its own words.
|
|
58056
|
+
const readOnlyFromAbove = useContext(ReadOnlyContext);
|
|
58057
|
+
const loadingFromAbove = useContext(LoadingContext$1);
|
|
58058
|
+
const answerIsSettled = Boolean(readOnlyFromAbove) && !loadingFromAbove;
|
|
58059
|
+
const readOnlyMessageKey = answerIsSettled ? multiple ? `constraint.readonly.selection` : `constraint.readonly.choice` : `constraint.readonly.option`;
|
|
57655
58060
|
const inputRef = useRef();
|
|
57656
58061
|
const inputType = multiple ? "checkbox" : "radio";
|
|
57657
58062
|
const inputId = `${id}_input`;
|
|
57658
58063
|
inputRef.nullCanHappen = true; // virtualization
|
|
57659
58064
|
const [checkableRootProps, checkableProps, controlChildrenWrapperProps] = useCheckableProps({
|
|
57660
|
-
readOnlyMessage: naviI18n(
|
|
58065
|
+
readOnlyMessage: naviI18n(readOnlyMessageKey, props),
|
|
57661
58066
|
...rest,
|
|
57662
58067
|
ref: inputRef,
|
|
57663
58068
|
id: inputId,
|
|
@@ -62302,6 +62707,51 @@ const PickerPresetResolver = props => {
|
|
|
62302
62707
|
});
|
|
62303
62708
|
};
|
|
62304
62709
|
|
|
62710
|
+
// Put around its children by BadgeList so a Badge below knows it is inside one.
|
|
62711
|
+
// A badge then renders nothing at all: it hands its props to the list and the
|
|
62712
|
+
// list renders it. Badges register in tree order, so by the time the list gets
|
|
62713
|
+
// to its own content it holds them all, in source order, and knows how many
|
|
62714
|
+
// there are before deciding what to show — without ever walking children
|
|
62715
|
+
// vnodes, and without rendering a badge it then has to take back.
|
|
62716
|
+
const BadgeListContext = createContext(null);
|
|
62717
|
+
|
|
62718
|
+
const createBadgeRegistry = () => {
|
|
62719
|
+
let pass = 0;
|
|
62720
|
+
let passCount = 0;
|
|
62721
|
+
let entries = [];
|
|
62722
|
+
let childrenAreNew = true;
|
|
62723
|
+
|
|
62724
|
+
return {
|
|
62725
|
+
// Called by BadgeList at the top of every render. childrenAreNew says
|
|
62726
|
+
// whether it was handed fresh children vnodes: when it re-renders on its
|
|
62727
|
+
// own (its own state changed) Preact hands the untouched children straight
|
|
62728
|
+
// back and skips them, so none of the badges registers again. That empty
|
|
62729
|
+
// pass means "unchanged", not "no badge left" — see getEntries.
|
|
62730
|
+
startPass: (areNew) => {
|
|
62731
|
+
pass++;
|
|
62732
|
+
passCount = 0;
|
|
62733
|
+
childrenAreNew = areNew;
|
|
62734
|
+
},
|
|
62735
|
+
// entryState is the badge's own memory. A badge that re-renders on its own
|
|
62736
|
+
// must update its entry, not append a second one.
|
|
62737
|
+
register: (entryState, props) => {
|
|
62738
|
+
if (entryState.pass !== pass) {
|
|
62739
|
+
entryState.pass = pass;
|
|
62740
|
+
entryState.index = passCount;
|
|
62741
|
+
passCount++;
|
|
62742
|
+
}
|
|
62743
|
+
entries[entryState.index] = props;
|
|
62744
|
+
},
|
|
62745
|
+
getEntries: () => {
|
|
62746
|
+
if (passCount === 0 && !childrenAreNew) {
|
|
62747
|
+
return entries;
|
|
62748
|
+
}
|
|
62749
|
+
entries.length = passCount;
|
|
62750
|
+
return entries;
|
|
62751
|
+
},
|
|
62752
|
+
};
|
|
62753
|
+
};
|
|
62754
|
+
|
|
62305
62755
|
installImportMetaCssBuild(import.meta);const css$x = /* css */`
|
|
62306
62756
|
@layer navi {
|
|
62307
62757
|
}
|
|
@@ -62360,6 +62810,11 @@ installImportMetaCssBuild(import.meta);const css$x = /* css */`
|
|
|
62360
62810
|
align-items: stretch;
|
|
62361
62811
|
color: var(--x-color);
|
|
62362
62812
|
font-size: var(--font-size);
|
|
62813
|
+
/* Cuts the font's half-leading above the first line and below the last one,
|
|
62814
|
+
down to cap-height/baseline: the padding becomes the only vertical space
|
|
62815
|
+
and the text is exactly centered. Space between wrapped lines is left
|
|
62816
|
+
untouched, unlike a line-height tweak. */
|
|
62817
|
+
text-box: trim-both cap alphabetic;
|
|
62363
62818
|
background: var(--x-background);
|
|
62364
62819
|
background-color: var(--x-background-color);
|
|
62365
62820
|
border-radius: 1em;
|
|
@@ -62409,7 +62864,22 @@ installImportMetaCssBuild(import.meta);const css$x = /* css */`
|
|
|
62409
62864
|
}
|
|
62410
62865
|
}
|
|
62411
62866
|
`;
|
|
62412
|
-
const Badge =
|
|
62867
|
+
const Badge = props => {
|
|
62868
|
+
const badgeList = useContext(BadgeListContext);
|
|
62869
|
+
const entryStateRef = useRef();
|
|
62870
|
+
if (badgeList) {
|
|
62871
|
+
// Inside a BadgeList the badge renders nothing: it hands itself over and
|
|
62872
|
+
// the list renders it, which is how the list gets to see them all before
|
|
62873
|
+
// deciding how many it shows. See badge_list_context.js.
|
|
62874
|
+
const entryState = entryStateRef.current || (entryStateRef.current = {});
|
|
62875
|
+
badgeList.register(entryState, props);
|
|
62876
|
+
return null;
|
|
62877
|
+
}
|
|
62878
|
+
return jsx(BadgeUI, {
|
|
62879
|
+
...props
|
|
62880
|
+
});
|
|
62881
|
+
};
|
|
62882
|
+
const BadgeUI = ({
|
|
62413
62883
|
children,
|
|
62414
62884
|
className,
|
|
62415
62885
|
...props
|
|
@@ -62424,7 +62894,14 @@ const Badge = ({
|
|
|
62424
62894
|
return jsx(Text, {
|
|
62425
62895
|
className: withPropsClassName("navi_badge", className),
|
|
62426
62896
|
bold: true,
|
|
62427
|
-
maxLines: 1
|
|
62897
|
+
maxLines: 1
|
|
62898
|
+
// The text-box trim ends the content box at the baseline, and a clamped
|
|
62899
|
+
// badge is clipped there: descenders of the last visible line would be
|
|
62900
|
+
// cut. Halfway to the next line's cap top keeps them, still above any
|
|
62901
|
+
// ink from the line the clamp hides.
|
|
62902
|
+
,
|
|
62903
|
+
|
|
62904
|
+
overflowClipMargin: "content-box calc((1lh - 1cap) / 2)",
|
|
62428
62905
|
...props,
|
|
62429
62906
|
styleCSSVars: BadgeStyleCSSVars,
|
|
62430
62907
|
spacing: jsx("span", {}),
|
|
@@ -62448,6 +62925,15 @@ const BadgeStyleCSSVars = {
|
|
|
62448
62925
|
fontSize: "--font-size"
|
|
62449
62926
|
};
|
|
62450
62927
|
const BadgeButton = props => {
|
|
62928
|
+
const ownTargetHidden = useOwnTargetHidden(props);
|
|
62929
|
+
if (ownTargetHidden) {
|
|
62930
|
+
return null;
|
|
62931
|
+
}
|
|
62932
|
+
return jsx(BadgeButtonUI, {
|
|
62933
|
+
...props
|
|
62934
|
+
});
|
|
62935
|
+
};
|
|
62936
|
+
const BadgeButtonUI = props => {
|
|
62451
62937
|
const defaultRef = useRef();
|
|
62452
62938
|
props.ref = props.ref || defaultRef;
|
|
62453
62939
|
const [buttonRootProps, buttonHostProps] = useControlProps(props, {
|
|
@@ -62478,20 +62964,208 @@ installImportMetaCssBuild(import.meta);const css$w = /* css */`
|
|
|
62478
62964
|
visibility: hidden;
|
|
62479
62965
|
pointer-events: none;
|
|
62480
62966
|
}
|
|
62967
|
+
|
|
62968
|
+
/* maxLines renders every badge for one layout, reads where the rows fell,
|
|
62969
|
+
then renders again with only what fits. The in-between is hidden rather
|
|
62970
|
+
than clipped: the badges that don't make it must leave the DOM, not sit
|
|
62971
|
+
there cut in half. Both renders land in the same frame (the second one
|
|
62972
|
+
is queued from a layout effect), so nothing shows up half measured. */
|
|
62973
|
+
&[navi-badge-list-measuring] {
|
|
62974
|
+
visibility: hidden;
|
|
62975
|
+
}
|
|
62481
62976
|
}
|
|
62482
62977
|
|
|
62483
62978
|
.navi_badge.navi_badge_more {
|
|
62484
62979
|
white-space: nowrap;
|
|
62485
62980
|
}
|
|
62486
62981
|
`;
|
|
62487
|
-
|
|
62982
|
+
|
|
62983
|
+
// Groups badges by the row they wrapped onto.
|
|
62984
|
+
// The signal we look for is horizontal: inside a row each badge starts further
|
|
62985
|
+
// right than the previous one, at a wrap the next one starts back at the row
|
|
62986
|
+
// start. Vertical positions can't be used because "align-items: center" gives
|
|
62987
|
+
// badges of different heights different tops within a single row.
|
|
62988
|
+
const groupRectsByRow = elements => {
|
|
62989
|
+
const rows = [];
|
|
62990
|
+
let previousLeft = -Infinity;
|
|
62991
|
+
for (const element of elements) {
|
|
62992
|
+
const rect = element.getBoundingClientRect();
|
|
62993
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
62994
|
+
continue;
|
|
62995
|
+
}
|
|
62996
|
+
if (rows.length === 0 || rect.left <= previousLeft) {
|
|
62997
|
+
rows.push([]);
|
|
62998
|
+
}
|
|
62999
|
+
rows[rows.length - 1].push(rect);
|
|
63000
|
+
previousLeft = rect.left;
|
|
63001
|
+
}
|
|
63002
|
+
return rows;
|
|
63003
|
+
};
|
|
63004
|
+
|
|
63005
|
+
// Reads a list that currently holds every badge plus the "+N" badge and tells
|
|
63006
|
+
// how many badges fit in maxLines rows.
|
|
63007
|
+
const measureRowFit = (listEl, maxLines) => {
|
|
63008
|
+
const elements = Array.from(listEl.children);
|
|
63009
|
+
if (elements.length < 2) {
|
|
63010
|
+
return elements.length;
|
|
63011
|
+
}
|
|
63012
|
+
// The "+N" badge is rendered last. It sits after every badge so it moves none
|
|
63013
|
+
// of them, which is what lets it be measured in the same layout it is left
|
|
63014
|
+
// out of.
|
|
63015
|
+
const moreRect = elements[elements.length - 1].getBoundingClientRect();
|
|
63016
|
+
const badgeElements = elements.slice(0, -1);
|
|
63017
|
+
const rows = groupRectsByRow(badgeElements);
|
|
63018
|
+
if (rows.length <= maxLines) {
|
|
63019
|
+
return badgeElements.length;
|
|
63020
|
+
}
|
|
63021
|
+
const styles = getComputedStyle(listEl);
|
|
63022
|
+
const gap = parseFloat(styles.columnGap) || 0;
|
|
63023
|
+
const contentRight = listEl.getBoundingClientRect().right - (parseFloat(styles.paddingRight) || 0) - (parseFloat(styles.borderRightWidth) || 0);
|
|
63024
|
+
|
|
63025
|
+
// The "+N" badge lands right after the last kept badge, so it eats into the
|
|
63026
|
+
// last visible row: drop badges from that row until it fits. Emptying that
|
|
63027
|
+
// row entirely is a valid outcome — the badge then wraps onto it and takes it
|
|
63028
|
+
// for itself, which is still within maxLines.
|
|
63029
|
+
const rowsKept = rows.slice(0, maxLines);
|
|
63030
|
+
const lastRow = rowsKept[rowsKept.length - 1];
|
|
63031
|
+
let lastRowCount = lastRow.length;
|
|
63032
|
+
while (lastRowCount > 0 && lastRow[lastRowCount - 1].right + gap + moreRect.width > contentRight + 0.5) {
|
|
63033
|
+
lastRowCount--;
|
|
63034
|
+
}
|
|
63035
|
+
return rowsKept.slice(0, -1).reduce((count, row) => count + row.length, 0) + lastRowCount;
|
|
63036
|
+
};
|
|
63037
|
+
const BADGE_LIST_PROPS = {
|
|
63038
|
+
inline: true,
|
|
63039
|
+
flex: "x",
|
|
63040
|
+
alignY: "center",
|
|
63041
|
+
spacing: "xs"
|
|
63042
|
+
};
|
|
63043
|
+
|
|
63044
|
+
// The badges hand themselves over as they render instead of being read upfront
|
|
63045
|
+
// from the children vnodes: see badge_list_context.js. Only worth it when the
|
|
63046
|
+
// list has something to decide — how many to show, or whether there is none at
|
|
63047
|
+
// all. Otherwise the badges render themselves and nothing has to be collected.
|
|
63048
|
+
const useBadgeRegistry = (children, enabled) => {
|
|
63049
|
+
const registryRef = useRef();
|
|
63050
|
+
const previousChildrenRef = useRef();
|
|
63051
|
+
if (!enabled) {
|
|
63052
|
+
return null;
|
|
63053
|
+
}
|
|
63054
|
+
const registry = registryRef.current || (registryRef.current = createBadgeRegistry());
|
|
63055
|
+
// Whether Preact is about to render the badges again or hand back the ones it
|
|
63056
|
+
// already has, which it does when the list re-renders on its own.
|
|
63057
|
+
registry.startPass(previousChildrenRef.current !== children);
|
|
63058
|
+
previousChildrenRef.current = children;
|
|
63059
|
+
return registry;
|
|
63060
|
+
};
|
|
63061
|
+
|
|
63062
|
+
/**
|
|
63063
|
+
* A row of badges that wraps.
|
|
63064
|
+
*
|
|
63065
|
+
* Four shapes behind one name, because what the list has to do at runtime is
|
|
63066
|
+
* not the same in each. Nothing is set up for a case that cannot happen: a
|
|
63067
|
+
* plain list is one element holding its children as-is — no registry, no
|
|
63068
|
+
* effect —, a capped one collects its badges but measures nothing, and only
|
|
63069
|
+
* shrinkWrap ever builds the measurement ghost.
|
|
63070
|
+
*
|
|
63071
|
+
* @param {import("ignore:preact").ComponentChildren} [fallback]
|
|
63072
|
+
* Rendered in place of the badges when there is none. Without it an empty
|
|
63073
|
+
* list renders nothing at all. In a <Picker> this is the only placeholder
|
|
63074
|
+
* the user gets — a picker given a `ui` does not draw its own — so pass the
|
|
63075
|
+
* placeholder text: `fallback="Select skills…"`. Plain text is the right
|
|
63076
|
+
* choice there: it reads at the picker's size, in the placeholder color the
|
|
63077
|
+
* picker gives its empty value slot, and the box stays the same height. A
|
|
63078
|
+
* transparent <Badge> matches the slot to the pixel instead, at the price of
|
|
63079
|
+
* badge-sized text. See docs/badge_list.md.
|
|
63080
|
+
* @param {boolean} [shrinkWrap]
|
|
63081
|
+
* Narrows the list down to its widest row so the last row isn't ragged.
|
|
63082
|
+
* Defaults to true inside a <Picker> — the trigger draws a border around the
|
|
63083
|
+
* list, so the ragged edge shows — and false elsewhere, where the work would
|
|
63084
|
+
* often go unseen: opt in where an edge is visible. Ignored when maxLines is
|
|
63085
|
+
* in play, which needs the full width to know where the rows fall.
|
|
63086
|
+
* @param {number} [max]
|
|
63087
|
+
* Caps how many badges are rendered; the surplus becomes a "+N" badge, which
|
|
63088
|
+
* takes one of the max slots.
|
|
63089
|
+
* @param {number} [maxLines]
|
|
63090
|
+
* Caps how many rows are shown, measured. Falls back to what the surrounding
|
|
63091
|
+
* component grants (a <Picker>, see max_lines_context.js).
|
|
63092
|
+
*/
|
|
63093
|
+
const BadgeList = props => {
|
|
63094
|
+
import.meta.css = [css$w, "@jsenv/navi/src/text/badge_list.jsx"];
|
|
63095
|
+
const {
|
|
63096
|
+
maxLines,
|
|
63097
|
+
max,
|
|
63098
|
+
fallback
|
|
63099
|
+
} = props;
|
|
63100
|
+
const maxLinesFromAbove = useContext(MaxLinesContext);
|
|
63101
|
+
const maxLinesResolved = maxLines === undefined ? maxLinesFromAbove : maxLines;
|
|
63102
|
+
// Granted lines mean a clamping container — a <Picker> — whose border makes
|
|
63103
|
+
// the ragged last row show; nothing of the sort around us, and shrink
|
|
63104
|
+
// wrapping is work nobody sees unless asked for.
|
|
63105
|
+
const {
|
|
63106
|
+
shrinkWrap = maxLinesFromAbove !== undefined
|
|
63107
|
+
} = props;
|
|
63108
|
+
if (maxLinesResolved !== undefined) {
|
|
63109
|
+
// shrinkWrap is dropped on purpose: it narrows the list down to its widest
|
|
63110
|
+
// row, which would re-wrap the badges under the cap just measured.
|
|
63111
|
+
return jsx(BadgeListMaxLines, {
|
|
63112
|
+
...props,
|
|
63113
|
+
maxLines: maxLinesResolved
|
|
63114
|
+
});
|
|
63115
|
+
}
|
|
63116
|
+
if (shrinkWrap) {
|
|
63117
|
+
return jsx(BadgeListShrinkWrap, {
|
|
63118
|
+
...props
|
|
63119
|
+
});
|
|
63120
|
+
}
|
|
63121
|
+
if (max !== undefined || fallback !== undefined) {
|
|
63122
|
+
return jsx(BadgeListCounted, {
|
|
63123
|
+
...props
|
|
63124
|
+
});
|
|
63125
|
+
}
|
|
63126
|
+
return jsx(BadgeListPlain, {
|
|
63127
|
+
...props
|
|
63128
|
+
});
|
|
63129
|
+
};
|
|
63130
|
+
|
|
63131
|
+
// Nothing to measure, cap, or count: the badges render themselves — no
|
|
63132
|
+
// registry, no context, no effect, one element.
|
|
63133
|
+
const BadgeListPlain = props => {
|
|
63134
|
+
return jsx(Box, {
|
|
63135
|
+
baseClassName: "navi_badge_list",
|
|
63136
|
+
...BADGE_LIST_PROPS,
|
|
63137
|
+
...props
|
|
63138
|
+
});
|
|
63139
|
+
};
|
|
63140
|
+
|
|
63141
|
+
// max/fallback need the badge count, so the badges are collected — but nothing
|
|
63142
|
+
// is measured and no DOM is watched.
|
|
63143
|
+
const BadgeListCounted = ({
|
|
62488
63144
|
fallback,
|
|
62489
63145
|
children,
|
|
62490
|
-
shrinkWrap = true,
|
|
62491
63146
|
max,
|
|
62492
|
-
...
|
|
63147
|
+
...boxProps
|
|
62493
63148
|
}) => {
|
|
62494
|
-
|
|
63149
|
+
const registry = useBadgeRegistry(children, true);
|
|
63150
|
+
return jsx(Box, {
|
|
63151
|
+
baseClassName: "navi_badge_list",
|
|
63152
|
+
...BADGE_LIST_PROPS,
|
|
63153
|
+
...boxProps,
|
|
63154
|
+
children: jsx(BadgeListChildren, {
|
|
63155
|
+
registry: registry,
|
|
63156
|
+
max: max,
|
|
63157
|
+
fallback: fallback,
|
|
63158
|
+
children: children
|
|
63159
|
+
})
|
|
63160
|
+
});
|
|
63161
|
+
};
|
|
63162
|
+
const BadgeListShrinkWrap = ({
|
|
63163
|
+
fallback,
|
|
63164
|
+
children,
|
|
63165
|
+
max,
|
|
63166
|
+
...restProps
|
|
63167
|
+
}) => {
|
|
63168
|
+
const registry = useBadgeRegistry(children, max !== undefined || fallback !== undefined);
|
|
62495
63169
|
const measureRef = useRef();
|
|
62496
63170
|
const visibleRef = useRef();
|
|
62497
63171
|
useLayoutEffect(() => {
|
|
@@ -62504,18 +63178,25 @@ const BadgeList = ({
|
|
|
62504
63178
|
let rafId;
|
|
62505
63179
|
const measure = () => {
|
|
62506
63180
|
visibleEl.style.width = "";
|
|
62507
|
-
|
|
62508
|
-
|
|
62509
|
-
|
|
62510
|
-
|
|
62511
|
-
|
|
62512
|
-
|
|
62513
|
-
|
|
62514
|
-
|
|
62515
|
-
visibleEl.style.width = `${Math.ceil(optimalWidth)}px`;
|
|
62516
|
-
}
|
|
63181
|
+
// Clone the already-rendered DOM nodes instead of letting React/Preact
|
|
63182
|
+
// render the children a second time into the ghost: re-rendering would
|
|
63183
|
+
// instantiate Badge/Badge.Button a second time, double-registering their
|
|
63184
|
+
// controllers (and any other mount side effect) under the same id.
|
|
63185
|
+
measureEl.replaceChildren(...Array.from(visibleEl.children, child => child.cloneNode(true)));
|
|
63186
|
+
const optimalWidth = measureWidestChildRow(measureEl);
|
|
63187
|
+
if (optimalWidth !== null) {
|
|
63188
|
+
visibleEl.style.width = `${Math.ceil(optimalWidth)}px`;
|
|
62517
63189
|
}
|
|
62518
63190
|
};
|
|
63191
|
+
|
|
63192
|
+
// A single badge is a single row, and a single row is already the widest
|
|
63193
|
+
// one: there is nothing to narrow, and no width the list could be given
|
|
63194
|
+
// that would change that.
|
|
63195
|
+
if (visibleEl.children.length < 2) {
|
|
63196
|
+
visibleEl.style.width = "";
|
|
63197
|
+
measureEl.replaceChildren();
|
|
63198
|
+
return undefined;
|
|
63199
|
+
}
|
|
62519
63200
|
measure();
|
|
62520
63201
|
const onResize = () => {
|
|
62521
63202
|
cancelAnimationFrame(rafId);
|
|
@@ -62532,36 +63213,201 @@ const BadgeList = ({
|
|
|
62532
63213
|
observer?.disconnect();
|
|
62533
63214
|
window.removeEventListener("resize", onResize);
|
|
62534
63215
|
};
|
|
62535
|
-
}, [
|
|
62536
|
-
const
|
|
62537
|
-
|
|
62538
|
-
|
|
62539
|
-
const hiddenCount = hasMax ? childArray.length - (max - 1) : 0;
|
|
62540
|
-
const sharedProps = {
|
|
62541
|
-
inline: true,
|
|
62542
|
-
flex: "x",
|
|
62543
|
-
alignY: "center",
|
|
62544
|
-
spacing: "xs",
|
|
62545
|
-
...props
|
|
63216
|
+
}, [children]);
|
|
63217
|
+
const boxProps = {
|
|
63218
|
+
...BADGE_LIST_PROPS,
|
|
63219
|
+
...restProps
|
|
62546
63220
|
};
|
|
62547
|
-
return
|
|
62548
|
-
|
|
62549
|
-
|
|
62550
|
-
|
|
62551
|
-
|
|
62552
|
-
|
|
62553
|
-
|
|
62554
|
-
|
|
62555
|
-
|
|
62556
|
-
|
|
62557
|
-
|
|
62558
|
-
|
|
62559
|
-
|
|
62560
|
-
|
|
62561
|
-
|
|
62562
|
-
|
|
63221
|
+
return (
|
|
63222
|
+
// inline flex, not a plain block: the wrapper must sit on the line the way
|
|
63223
|
+
// the list itself would, otherwise the list lands a pixel low in some
|
|
63224
|
+
// containers.
|
|
63225
|
+
jsxs(Box, {
|
|
63226
|
+
relative: true,
|
|
63227
|
+
inline: true,
|
|
63228
|
+
flex: "x",
|
|
63229
|
+
children: [jsx(Box, {
|
|
63230
|
+
baseClassName: "navi_badge_list",
|
|
63231
|
+
...boxProps,
|
|
63232
|
+
ref: measureRef,
|
|
63233
|
+
"aria-hidden": "true",
|
|
63234
|
+
"navi-badge-list-clone": ""
|
|
63235
|
+
}), jsx(Box, {
|
|
63236
|
+
baseClassName: "navi_badge_list",
|
|
63237
|
+
...boxProps,
|
|
63238
|
+
ref: visibleRef,
|
|
63239
|
+
children: jsx(BadgeListChildren, {
|
|
63240
|
+
registry: registry,
|
|
63241
|
+
max: max,
|
|
63242
|
+
fallback: fallback,
|
|
63243
|
+
children: children
|
|
62563
63244
|
})
|
|
62564
63245
|
})]
|
|
63246
|
+
})
|
|
63247
|
+
);
|
|
63248
|
+
};
|
|
63249
|
+
const BadgeListMaxLines = ({
|
|
63250
|
+
fallback,
|
|
63251
|
+
children,
|
|
63252
|
+
max,
|
|
63253
|
+
maxLines,
|
|
63254
|
+
...boxProps
|
|
63255
|
+
}) => {
|
|
63256
|
+
const registry = useBadgeRegistry(children, true);
|
|
63257
|
+
const visibleRef = useRef();
|
|
63258
|
+
|
|
63259
|
+
// What the measure found: how many badges there were and how many of them fit
|
|
63260
|
+
// in maxLines rows. null means "not measured yet": the list then renders them
|
|
63261
|
+
// all, hidden, for the layout effect to read.
|
|
63262
|
+
const [fit, setFit] = useState(null);
|
|
63263
|
+
// The two widths this list gives whatever is around it: the one it takes with
|
|
63264
|
+
// every badge rendered, and the one it settles on once the surplus is gone.
|
|
63265
|
+
// Neither is a reason to measure again — see the resize watch below.
|
|
63266
|
+
const selfWidthsRef = useRef({
|
|
63267
|
+
full: -1,
|
|
63268
|
+
settled: -1
|
|
63269
|
+
});
|
|
63270
|
+
const measuring = fit === null;
|
|
63271
|
+
// A single badge is a single row whatever the width, so nothing can move it
|
|
63272
|
+
// out of the cap and nothing has to be watched.
|
|
63273
|
+
const watchesResize = fit !== null && fit.count > 1;
|
|
63274
|
+
|
|
63275
|
+
// Runs after every render, which is when the badges have registered and the
|
|
63276
|
+
// DOM holds whatever this render asked for.
|
|
63277
|
+
useLayoutEffect(() => {
|
|
63278
|
+
const visibleEl = visibleRef.current;
|
|
63279
|
+
if (!visibleEl) {
|
|
63280
|
+
return;
|
|
63281
|
+
}
|
|
63282
|
+
const count = registry.getEntries().length;
|
|
63283
|
+
const width = visibleEl.parentElement?.getBoundingClientRect().width ?? -1;
|
|
63284
|
+
if (fit === null) {
|
|
63285
|
+
selfWidthsRef.current.full = width;
|
|
63286
|
+
setFit({
|
|
63287
|
+
count,
|
|
63288
|
+
shown: count < 2 ? count : measureRowFit(visibleEl, maxLines)
|
|
63289
|
+
});
|
|
63290
|
+
return;
|
|
63291
|
+
}
|
|
63292
|
+
selfWidthsRef.current.settled = width;
|
|
63293
|
+
if (fit.count !== count) {
|
|
63294
|
+
// Badges came or went: what was measured no longer describes them.
|
|
63295
|
+
setFit(null);
|
|
63296
|
+
}
|
|
63297
|
+
});
|
|
63298
|
+
useLayoutEffect(() => {
|
|
63299
|
+
const outerParent = visibleRef.current?.parentElement;
|
|
63300
|
+
if (!watchesResize || !outerParent) {
|
|
63301
|
+
return undefined;
|
|
63302
|
+
}
|
|
63303
|
+
let rafId;
|
|
63304
|
+
const remeasure = () => {
|
|
63305
|
+
// Only a width change can move the rows — a height change is this list
|
|
63306
|
+
// growing or shrinking, never the room it was given.
|
|
63307
|
+
//
|
|
63308
|
+
// And not every width change either. Nothing guarantees an ancestor whose
|
|
63309
|
+
// width does not follow its content (a column with align-items: start
|
|
63310
|
+
// sizes every row to what is inside it), so rendering every badge widens
|
|
63311
|
+
// what is being watched and dropping the surplus narrows it right back.
|
|
63312
|
+
// Those two widths are this list talking to itself; measuring again on
|
|
63313
|
+
// them never ends. Any other width is the room around it changing.
|
|
63314
|
+
const width = outerParent.getBoundingClientRect().width;
|
|
63315
|
+
const {
|
|
63316
|
+
full,
|
|
63317
|
+
settled
|
|
63318
|
+
} = selfWidthsRef.current;
|
|
63319
|
+
if (Math.abs(width - full) < 0.5 || Math.abs(width - settled) < 0.5) {
|
|
63320
|
+
return;
|
|
63321
|
+
}
|
|
63322
|
+
cancelAnimationFrame(rafId);
|
|
63323
|
+
rafId = requestAnimationFrame(() => setFit(null));
|
|
63324
|
+
};
|
|
63325
|
+
const observer = new ResizeObserver(remeasure);
|
|
63326
|
+
observer.observe(outerParent);
|
|
63327
|
+
window.addEventListener("resize", remeasure);
|
|
63328
|
+
return () => {
|
|
63329
|
+
cancelAnimationFrame(rafId);
|
|
63330
|
+
observer.disconnect();
|
|
63331
|
+
window.removeEventListener("resize", remeasure);
|
|
63332
|
+
};
|
|
63333
|
+
}, [watchesResize, maxLines]);
|
|
63334
|
+
return jsx(Box, {
|
|
63335
|
+
baseClassName: "navi_badge_list",
|
|
63336
|
+
...BADGE_LIST_PROPS,
|
|
63337
|
+
...boxProps,
|
|
63338
|
+
ref: visibleRef,
|
|
63339
|
+
"navi-badge-list-measuring": measuring ? "" : undefined,
|
|
63340
|
+
children: jsx(BadgeListChildren, {
|
|
63341
|
+
registry: registry,
|
|
63342
|
+
max: max,
|
|
63343
|
+
fallback: fallback,
|
|
63344
|
+
measuring: measuring,
|
|
63345
|
+
rowFit: fit?.shown ?? null,
|
|
63346
|
+
children: children
|
|
63347
|
+
})
|
|
63348
|
+
});
|
|
63349
|
+
};
|
|
63350
|
+
const BadgeListChildren = ({
|
|
63351
|
+
registry,
|
|
63352
|
+
children,
|
|
63353
|
+
fallback,
|
|
63354
|
+
max,
|
|
63355
|
+
measuring,
|
|
63356
|
+
rowFit
|
|
63357
|
+
}) => {
|
|
63358
|
+
if (!registry) {
|
|
63359
|
+
// The badges are on their own: nothing here decides which of them render.
|
|
63360
|
+
return children;
|
|
63361
|
+
}
|
|
63362
|
+
return jsxs(Fragment$1, {
|
|
63363
|
+
children: [jsx(BadgeListContext.Provider, {
|
|
63364
|
+
value: registry,
|
|
63365
|
+
children: children
|
|
63366
|
+
}), jsx(BadgeListContent, {
|
|
63367
|
+
registry: registry,
|
|
63368
|
+
fallback: fallback,
|
|
63369
|
+
max: max,
|
|
63370
|
+
measuring: measuring,
|
|
63371
|
+
rowFit: rowFit
|
|
63372
|
+
})]
|
|
63373
|
+
});
|
|
63374
|
+
};
|
|
63375
|
+
const BadgeListContent = ({
|
|
63376
|
+
registry,
|
|
63377
|
+
fallback,
|
|
63378
|
+
max,
|
|
63379
|
+
measuring,
|
|
63380
|
+
rowFit
|
|
63381
|
+
}) => {
|
|
63382
|
+
const entries = registry.getEntries();
|
|
63383
|
+
const count = entries.length;
|
|
63384
|
+
if (count === 0) {
|
|
63385
|
+
return fallback;
|
|
63386
|
+
}
|
|
63387
|
+
|
|
63388
|
+
// The "+N" badge stands among the badges, so it takes one of the max slots
|
|
63389
|
+
// when there is a surplus to name. A list of exactly `max` badges has nothing
|
|
63390
|
+
// to name and keeps all of them.
|
|
63391
|
+
let shownCount = max !== undefined && count > max ? max - 1 : count;
|
|
63392
|
+
if (!measuring && rowFit !== null && rowFit !== undefined && rowFit < shownCount) {
|
|
63393
|
+
shownCount = rowFit;
|
|
63394
|
+
}
|
|
63395
|
+
// While measuring, everything above is on screen (hidden) along with the "+N"
|
|
63396
|
+
// badge, so the layout effect can see where the rows fall and how much room
|
|
63397
|
+
// that badge asks for. Its label then reads the worst case — every badge
|
|
63398
|
+
// hidden — so the room reserved is never short.
|
|
63399
|
+
const hasMore = measuring || shownCount < count;
|
|
63400
|
+
return jsxs(Fragment$1, {
|
|
63401
|
+
children: [entries.slice(0, shownCount).map((badgeProps, index) =>
|
|
63402
|
+
// Keyed by position: a badge's own key went to the registering vnode
|
|
63403
|
+
// above and doesn't reach here, and badges keep no state worth moving.
|
|
63404
|
+
jsx(BadgeUI, {
|
|
63405
|
+
...badgeProps
|
|
63406
|
+
}, index)), hasMore && jsx(BadgeUI, {
|
|
63407
|
+
className: "navi_badge_more",
|
|
63408
|
+
children: naviI18n("badge_list.more", {
|
|
63409
|
+
count: measuring ? count : count - shownCount
|
|
63410
|
+
})
|
|
62565
63411
|
})]
|
|
62566
63412
|
});
|
|
62567
63413
|
};
|
|
@@ -62792,7 +63638,6 @@ const PickerObjectUI = () => {
|
|
|
62792
63638
|
const PickerArray = props => {
|
|
62793
63639
|
const Next = useNextResolver();
|
|
62794
63640
|
return jsx(Next, {
|
|
62795
|
-
maxLines: "3",
|
|
62796
63641
|
ui: jsx(PickerArrayUI, {}),
|
|
62797
63642
|
...props,
|
|
62798
63643
|
type: "navi_js",
|
|
@@ -62822,6 +63667,57 @@ const PickerArrayUI = () => {
|
|
|
62822
63667
|
})
|
|
62823
63668
|
});
|
|
62824
63669
|
};
|
|
63670
|
+
|
|
63671
|
+
/**
|
|
63672
|
+
* One value the picker holds, drawn as a chip with a cross that takes it back
|
|
63673
|
+
* out. Sits wherever the application draws what was picked — on the picker's
|
|
63674
|
+
* façade (`ui`) or inside its popup — and both behave the same.
|
|
63675
|
+
*
|
|
63676
|
+
* The cross asks with `--navi-unselect` rather than writing a new list, and it
|
|
63677
|
+
* asks the picker — which holds what was picked, and hands it down to whatever
|
|
63678
|
+
* draws it in the popup. Nothing to name: the picker is the nearest control
|
|
63679
|
+
* around the chip. `commandFor` is for a chip that stands outside the picker it
|
|
63680
|
+
* speaks for.
|
|
63681
|
+
*
|
|
63682
|
+
* The cross is an own target (see own_target.js), so the press belongs to it
|
|
63683
|
+
* and not to the picker underneath, and it goes when the picker turns read-only
|
|
63684
|
+
* — a row being read still says what was picked, it just no longer offers to
|
|
63685
|
+
* unpick it.
|
|
63686
|
+
*
|
|
63687
|
+
* @type {import("ignore:preact").FunctionComponent<{
|
|
63688
|
+
* value: any,
|
|
63689
|
+
* commandFor?: string,
|
|
63690
|
+
* children?: import("ignore:preact").ComponentChildren,
|
|
63691
|
+
* [key: string]: any,
|
|
63692
|
+
* }>}
|
|
63693
|
+
* @param {any} value The value this chip stands for — one entry of what the
|
|
63694
|
+
* picker holds, and what `--navi-unselect` carries.
|
|
63695
|
+
* @param {string} [commandFor] The id of the picker to take the value out of,
|
|
63696
|
+
* when the chip does not sit inside it.
|
|
63697
|
+
*/
|
|
63698
|
+
const PickerChip = ({
|
|
63699
|
+
value,
|
|
63700
|
+
commandFor,
|
|
63701
|
+
children,
|
|
63702
|
+
...rest
|
|
63703
|
+
}) => {
|
|
63704
|
+
return jsxs(Badge, {
|
|
63705
|
+
inline: true,
|
|
63706
|
+
flex: true,
|
|
63707
|
+
...rest,
|
|
63708
|
+
children: [children, jsx(Badge.Button, {
|
|
63709
|
+
ownTarget: true,
|
|
63710
|
+
command: "--navi-unselect",
|
|
63711
|
+
commandFor: commandFor,
|
|
63712
|
+
value: value,
|
|
63713
|
+
"aria-label": naviI18n("button.remove"),
|
|
63714
|
+
children: jsx(Icon, {
|
|
63715
|
+
lineOverflow: "allow",
|
|
63716
|
+
children: jsx(CloseSvg, {})
|
|
63717
|
+
})
|
|
63718
|
+
})]
|
|
63719
|
+
});
|
|
63720
|
+
};
|
|
62825
63721
|
const PickerColor = props => {
|
|
62826
63722
|
const Next = useNextResolver();
|
|
62827
63723
|
return jsx(Next, {
|
|
@@ -63178,10 +64074,39 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
|
|
|
63178
64074
|
/* The frame is drawn by the box, but its radius is declared here, on the
|
|
63179
64075
|
control root, like every other navi control does — so anything styling
|
|
63180
64076
|
the picker from the outside (a Group squaring the corners it joins) has
|
|
63181
|
-
one element to talk to, and the box follows.
|
|
63182
|
-
|
|
64077
|
+
one element to talk to, and the box follows.
|
|
64078
|
+
Corner by corner rather than as the shorthand: a picker is not always
|
|
64079
|
+
the member a Group joins — it can arrive wrapped (in a Box carrying a
|
|
64080
|
+
state, in a link, in a tooltip), and the ask then travels down as
|
|
64081
|
+
inherited custom properties instead of landing on this element as a
|
|
64082
|
+
radius. Each corner falls back to the picker's own radius when nothing
|
|
64083
|
+
asks for anything. */
|
|
64084
|
+
border-top-left-radius: var(
|
|
64085
|
+
--x-corner-top-left-radius,
|
|
64086
|
+
var(--picker-border-radius)
|
|
64087
|
+
);
|
|
64088
|
+
border-top-right-radius: var(
|
|
64089
|
+
--x-corner-top-right-radius,
|
|
64090
|
+
var(--picker-border-radius)
|
|
64091
|
+
);
|
|
64092
|
+
border-bottom-right-radius: var(
|
|
64093
|
+
--x-corner-bottom-right-radius,
|
|
64094
|
+
var(--picker-border-radius)
|
|
64095
|
+
);
|
|
64096
|
+
border-bottom-left-radius: var(
|
|
64097
|
+
--x-corner-bottom-left-radius,
|
|
64098
|
+
var(--picker-border-radius)
|
|
64099
|
+
);
|
|
63183
64100
|
|
|
63184
64101
|
.navi_picker_box {
|
|
64102
|
+
/* The ask stops here: this element is the picker's frame, so nothing it
|
|
64103
|
+
holds is at the seam — the chevron and the clear cross in the slot, a
|
|
64104
|
+
button in a custom UI. */
|
|
64105
|
+
--x-corner-top-left-radius: initial;
|
|
64106
|
+
--x-corner-top-right-radius: initial;
|
|
64107
|
+
--x-corner-bottom-right-radius: initial;
|
|
64108
|
+
--x-corner-bottom-left-radius: initial;
|
|
64109
|
+
|
|
63185
64110
|
position: relative;
|
|
63186
64111
|
display: inline-flex;
|
|
63187
64112
|
box-sizing: border-box;
|
|
@@ -63231,16 +64156,32 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
|
|
|
63231
64156
|
&[navi-placeholder] {
|
|
63232
64157
|
color: var(--picker-placeholder-color);
|
|
63233
64158
|
}
|
|
64159
|
+
|
|
64160
|
+
/* A <BadgeList> caps its own rows: it renders the badges that fit and a
|
|
64161
|
+
"+N" badge for the rest, reading the number from MaxLinesContext right
|
|
64162
|
+
below. The picker must then not clamp on top of it — line-clamp turns
|
|
64163
|
+
the value into a -webkit-box and single-line truncation into a
|
|
64164
|
+
nowrap block, either of which takes the badge list out of the
|
|
64165
|
+
inline-flex layout it needs. maxLines writes those as inline styles on
|
|
64166
|
+
the element, hence !important. */
|
|
64167
|
+
&:has(.navi_badge_list) {
|
|
64168
|
+
display: inline-flex !important;
|
|
64169
|
+
-webkit-line-clamp: none !important;
|
|
64170
|
+
overflow: visible !important;
|
|
64171
|
+
-webkit-box-orient: horizontal !important;
|
|
64172
|
+
text-overflow: clip !important;
|
|
64173
|
+
white-space: normal !important;
|
|
64174
|
+
}
|
|
64175
|
+
|
|
64176
|
+
/* The façade is transparent to the pointer — a press on what it draws
|
|
64177
|
+
means "open the picker". An own target is the exception, the same way
|
|
64178
|
+
the clear cross is one in the slot below: it says the press is aimed at
|
|
64179
|
+
IT, so it has to be reachable at all. */
|
|
64180
|
+
[data-navi-own-target] {
|
|
64181
|
+
pointer-events: auto;
|
|
64182
|
+
}
|
|
63234
64183
|
}
|
|
63235
64184
|
.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
64185
|
display: inline-flex;
|
|
63245
64186
|
height: 1em;
|
|
63246
64187
|
height: 1lh;
|
|
@@ -63335,6 +64276,15 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
|
|
|
63335
64276
|
}
|
|
63336
64277
|
|
|
63337
64278
|
.navi_picker_content {
|
|
64279
|
+
/* The other side of the frame: what a picker holds here is what its
|
|
64280
|
+
popup shows, and a popup is never at a seam. Popover and Dialog stop
|
|
64281
|
+
the ask at their own root too — this covers content that reaches the
|
|
64282
|
+
popup through neither. */
|
|
64283
|
+
--x-corner-top-left-radius: initial;
|
|
64284
|
+
--x-corner-top-right-radius: initial;
|
|
64285
|
+
--x-corner-bottom-right-radius: initial;
|
|
64286
|
+
--x-corner-bottom-left-radius: initial;
|
|
64287
|
+
|
|
63338
64288
|
display: contents;
|
|
63339
64289
|
text-align: initial; /* Don't inherit picker text align */
|
|
63340
64290
|
}
|
|
@@ -63352,6 +64302,12 @@ installImportMetaCssBuild(import.meta);const css$u = /* css */`
|
|
|
63352
64302
|
--x-picker-icon-color: var(--picker-icon-color-readonly);
|
|
63353
64303
|
--x-picker-cursor: default;
|
|
63354
64304
|
}
|
|
64305
|
+
/* Read-only and still opening, so it still says so under the pointer.
|
|
64306
|
+
Before the disabled block below on purpose: a disabled picker opens
|
|
64307
|
+
nothing, read-only or not. */
|
|
64308
|
+
&[data-readonly-opens] {
|
|
64309
|
+
--x-picker-cursor: pointer;
|
|
64310
|
+
}
|
|
63355
64311
|
/* Focus */
|
|
63356
64312
|
&[data-focus-within]:has(.navi_picker_input[data-focus-visible]) {
|
|
63357
64313
|
--x-picker-border-color: transparent;
|
|
@@ -63480,6 +64436,7 @@ const PickerButton = props => {
|
|
|
63480
64436
|
// untouched (see the --navi-clear command).
|
|
63481
64437
|
clearConfirm,
|
|
63482
64438
|
clearConfirmPopupContent,
|
|
64439
|
+
readOnly,
|
|
63483
64440
|
error
|
|
63484
64441
|
} = props;
|
|
63485
64442
|
const isSingleLine = maxLines === 1;
|
|
@@ -63497,210 +64454,298 @@ const PickerButton = props => {
|
|
|
63497
64454
|
children
|
|
63498
64455
|
} = inputProps;
|
|
63499
64456
|
const loading = basePseudoState[":-navi-loading"];
|
|
64457
|
+
// The same chain useControlProps resolves (own prop first, then what the
|
|
64458
|
+
// group above says), for the two things it does not carry: the popup content,
|
|
64459
|
+
// which is read-only along with the picker, and the cursor, which stays a
|
|
64460
|
+
// pointer on a picker that still opens.
|
|
64461
|
+
const readOnlyFromAbove = useContext(ReadOnlyContext);
|
|
64462
|
+
const readOnlyResolved = readOnly || readOnlyFromAbove;
|
|
64463
|
+
// Read off the controller rather than worked out again: what makes a
|
|
64464
|
+
// read-only picker open is settled once, where the gate reads it (see
|
|
64465
|
+
// createControlInfo's readOnlyOpens). Needed here for the cursor.
|
|
64466
|
+
const readOnlyOpens = Boolean(readOnlyResolved) && uiStateController.readOnlyOpens;
|
|
64467
|
+
// Whether anything can still be changed — read by the clear cross below,
|
|
64468
|
+
// clearing being a modification like any other.
|
|
64469
|
+
const interactive = !basePseudoState[":disabled"] && !basePseudoState[":read-only"] && !loading;
|
|
63500
64470
|
usePickerErrorCallout(uiStateController, error);
|
|
63501
|
-
return
|
|
63502
|
-
|
|
63503
|
-
|
|
63504
|
-
|
|
63505
|
-
|
|
63506
|
-
|
|
63507
|
-
|
|
63508
|
-
,
|
|
64471
|
+
return (
|
|
64472
|
+
/* Read-only crosses into everything the picker is made of: what it really
|
|
64473
|
+
holds is drawn by controls of their own — in the popup, and on the façade
|
|
64474
|
+
where an application puts its own affordances — and each of them refuses
|
|
64475
|
+
in its own words once told. Said from the read-only state alone, never
|
|
64476
|
+
from the busy one — an action running for a moment is not the same thing
|
|
64477
|
+
as a value nobody may change. */
|
|
64478
|
+
jsx(ReadOnlyContext.Provider, {
|
|
64479
|
+
value: readOnlyResolved,
|
|
64480
|
+
children: jsxs(Box, {
|
|
64481
|
+
as: "div",
|
|
64482
|
+
ref: ref
|
|
64483
|
+
// The flow this element really has (.navi_picker is display:inline-flex).
|
|
64484
|
+
// Left unsaid, Box reads a <div> as block and resolves alignX into a
|
|
64485
|
+
// text-align — which is a different intention entirely (that one is the
|
|
64486
|
+
// textAlign prop, placing the text INSIDE the value slot).
|
|
64487
|
+
,
|
|
63509
64488
|
|
|
63510
|
-
|
|
63511
|
-
|
|
63512
|
-
|
|
63513
|
-
|
|
63514
|
-
|
|
63515
|
-
|
|
63516
|
-
|
|
63517
|
-
|
|
63518
|
-
|
|
63519
|
-
|
|
63520
|
-
|
|
63521
|
-
|
|
63522
|
-
|
|
63523
|
-
|
|
63524
|
-
|
|
63525
|
-
|
|
63526
|
-
|
|
63527
|
-
|
|
63528
|
-
|
|
63529
|
-
|
|
63530
|
-
|
|
63531
|
-
|
|
63532
|
-
|
|
63533
|
-
|
|
63534
|
-
|
|
63535
|
-
|
|
64489
|
+
inline: true,
|
|
64490
|
+
flex: "x",
|
|
64491
|
+
baseClassName: "navi_picker",
|
|
64492
|
+
pseudoClasses: PICKER_BUTTON_PSEUDO_CLASSES,
|
|
64493
|
+
"data-variant": variant,
|
|
64494
|
+
"navi-picker": "",
|
|
64495
|
+
"navi-single-line": isSingleLine ? "" : undefined,
|
|
64496
|
+
"navi-ui-custom": ui === "default" ? undefined : "",
|
|
64497
|
+
"data-readonly-opens": readOnlyOpens ? "" : undefined,
|
|
64498
|
+
"data-popup-width-fit-content": popupWidthFitContent ? "" : undefined,
|
|
64499
|
+
...pickerRemainingProps,
|
|
64500
|
+
basePseudoState: basePseudoState,
|
|
64501
|
+
styleCSSVars: PickerStyleCSSVars,
|
|
64502
|
+
variant: undefined,
|
|
64503
|
+
rightSlotIcon: undefined,
|
|
64504
|
+
rightSlotIconSize: undefined,
|
|
64505
|
+
rightSlot: undefined,
|
|
64506
|
+
clearConfirm: undefined,
|
|
64507
|
+
clearConfirmPopupContent: undefined,
|
|
64508
|
+
openWhileReadOnly: undefined,
|
|
64509
|
+
ui: undefined,
|
|
64510
|
+
maxLines: undefined,
|
|
64511
|
+
popupWidthFitContent: undefined,
|
|
64512
|
+
error: undefined,
|
|
64513
|
+
dayLabel: undefined
|
|
64514
|
+
// This wrapper will receive keyboard event bubbling from the picker popup content
|
|
64515
|
+
// we re-dispatch on the input (to get escape to close for instance)
|
|
64516
|
+
,
|
|
63536
64517
|
|
|
63537
|
-
|
|
63538
|
-
|
|
63539
|
-
|
|
63540
|
-
|
|
64518
|
+
onKeyDown: inputProps.onKeyDown
|
|
64519
|
+
// in case request open/close are dispatched on the control root ->
|
|
64520
|
+
// redispatch them to the host
|
|
64521
|
+
,
|
|
63541
64522
|
|
|
63542
|
-
|
|
63543
|
-
|
|
63544
|
-
|
|
63545
|
-
|
|
63546
|
-
|
|
63547
|
-
|
|
63548
|
-
|
|
63549
|
-
|
|
63550
|
-
|
|
63551
|
-
|
|
63552
|
-
|
|
63553
|
-
...inputProps,
|
|
63554
|
-
// eslint-disable-next-line react/no-children-prop
|
|
63555
|
-
children: undefined // we will render children into the div
|
|
64523
|
+
onnavi_request_open: inputProps.onnavi_request_open,
|
|
64524
|
+
onnavi_request_close: inputProps.onnavi_request_close
|
|
64525
|
+
// `--navi-select`/`--navi-unselect` about one entry of the list the
|
|
64526
|
+
// picker holds — a chip on the façade, a suggestion beside the field.
|
|
64527
|
+
// Answered here rather than by the control drawing that list in the
|
|
64528
|
+
// popup, even though rows are what such a control owns: a picker given
|
|
64529
|
+
// its own value builds its popup only on first open (see
|
|
64530
|
+
// popup_content_mount.js), so before that there is no such control at
|
|
64531
|
+
// all — and building the whole popup to have someone to talk to, for a
|
|
64532
|
+
// cross, is the wrong price. The picker holds the value in the first
|
|
64533
|
+
// place and hands it down whenever the popup is built.
|
|
63556
64534
|
,
|
|
63557
64535
|
|
|
63558
|
-
|
|
63559
|
-
|
|
63560
|
-
inputProps.onFocus?.(e);
|
|
63561
|
-
e.target.select();
|
|
64536
|
+
onnavi_request_select: e => {
|
|
64537
|
+
requestPickerListEntry(ref.current, inputRef.current, e, "select");
|
|
63562
64538
|
},
|
|
63563
|
-
|
|
63564
|
-
|
|
63565
|
-
if (isWithinPickerContent(e.target, pickerEl)) {
|
|
63566
|
-
return;
|
|
63567
|
-
}
|
|
63568
|
-
const uiState = uiStateController.uiState;
|
|
63569
|
-
if (uiState === undefined) {
|
|
63570
|
-
return;
|
|
63571
|
-
}
|
|
63572
|
-
e.preventDefault();
|
|
63573
|
-
const displayText = pickerEl.querySelector(".navi_picker_value")?.textContent ?? String(uiState);
|
|
63574
|
-
e.clipboardData.setData("text/plain", displayText);
|
|
63575
|
-
e.clipboardData.setData("application/x-navi", JSON.stringify(uiState));
|
|
63576
|
-
},
|
|
63577
|
-
onCut: e => {
|
|
63578
|
-
const pickerEl = ref.current;
|
|
63579
|
-
if (isWithinPickerContent(e.target, pickerEl)) {
|
|
63580
|
-
return;
|
|
63581
|
-
}
|
|
63582
|
-
const uiState = uiStateController.uiState;
|
|
63583
|
-
if (uiState === undefined) {
|
|
63584
|
-
return;
|
|
63585
|
-
}
|
|
63586
|
-
// the copy part don't need control to be interactable
|
|
63587
|
-
const displayText = pickerEl.querySelector(".navi_picker_value")?.textContent ?? String(uiState);
|
|
63588
|
-
e.clipboardData.setData("text/plain", displayText);
|
|
63589
|
-
e.clipboardData.setData("application/x-navi", JSON.stringify(uiState));
|
|
63590
|
-
// the clear ui state part need control to be interactable
|
|
63591
|
-
dispatchRequestInteraction(pickerEl, {
|
|
63592
|
-
event: e,
|
|
63593
|
-
name: "cut",
|
|
63594
|
-
allowed: () => {
|
|
63595
|
-
dispatchRequestClearUIState(inputRef.current, e);
|
|
63596
|
-
}
|
|
63597
|
-
});
|
|
63598
|
-
e.preventDefault();
|
|
64539
|
+
onnavi_request_unselect: e => {
|
|
64540
|
+
requestPickerListEntry(ref.current, inputRef.current, e, "unselect");
|
|
63599
64541
|
},
|
|
63600
|
-
|
|
63601
|
-
|
|
63602
|
-
|
|
63603
|
-
|
|
63604
|
-
|
|
63605
|
-
|
|
63606
|
-
|
|
63607
|
-
|
|
63608
|
-
|
|
63609
|
-
|
|
63610
|
-
|
|
63611
|
-
|
|
63612
|
-
|
|
63613
|
-
|
|
63614
|
-
|
|
63615
|
-
|
|
63616
|
-
|
|
63617
|
-
|
|
63618
|
-
|
|
63619
|
-
|
|
63620
|
-
|
|
63621
|
-
|
|
63622
|
-
|
|
64542
|
+
children: [jsxs("span", {
|
|
64543
|
+
className: "navi_picker_box",
|
|
64544
|
+
children: [variant === "headless" ? null : jsx(LoadingOutline, {
|
|
64545
|
+
loading: loading,
|
|
64546
|
+
color: "var(--picker-loader-color)",
|
|
64547
|
+
inset: -2
|
|
64548
|
+
}), jsx(PickerInput, {
|
|
64549
|
+
tabIndex: variant === "headless" ? -1 : undefined,
|
|
64550
|
+
"aria-hidden": variant === "headless" ? "true" : undefined,
|
|
64551
|
+
...inputProps,
|
|
64552
|
+
// eslint-disable-next-line react/no-children-prop
|
|
64553
|
+
children: undefined // we will render children into the div
|
|
64554
|
+
,
|
|
64555
|
+
|
|
64556
|
+
ui: ui,
|
|
64557
|
+
onFocus: e => {
|
|
64558
|
+
inputProps.onFocus?.(e);
|
|
64559
|
+
e.target.select();
|
|
64560
|
+
},
|
|
64561
|
+
onCopy: e => {
|
|
64562
|
+
const pickerEl = ref.current;
|
|
64563
|
+
if (isWithinPickerContent(e.target, pickerEl)) {
|
|
64564
|
+
return;
|
|
64565
|
+
}
|
|
64566
|
+
const uiState = uiStateController.uiState;
|
|
64567
|
+
if (uiState === undefined) {
|
|
64568
|
+
return;
|
|
64569
|
+
}
|
|
64570
|
+
e.preventDefault();
|
|
64571
|
+
const displayText = pickerEl.querySelector(".navi_picker_value")?.textContent ?? String(uiState);
|
|
64572
|
+
e.clipboardData.setData("text/plain", displayText);
|
|
64573
|
+
e.clipboardData.setData("application/x-navi", JSON.stringify(uiState));
|
|
64574
|
+
},
|
|
64575
|
+
onCut: e => {
|
|
64576
|
+
const pickerEl = ref.current;
|
|
64577
|
+
if (isWithinPickerContent(e.target, pickerEl)) {
|
|
64578
|
+
return;
|
|
64579
|
+
}
|
|
64580
|
+
const uiState = uiStateController.uiState;
|
|
64581
|
+
if (uiState === undefined) {
|
|
64582
|
+
return;
|
|
64583
|
+
}
|
|
64584
|
+
// the copy part don't need control to be interactable
|
|
64585
|
+
const displayText = pickerEl.querySelector(".navi_picker_value")?.textContent ?? String(uiState);
|
|
64586
|
+
e.clipboardData.setData("text/plain", displayText);
|
|
64587
|
+
e.clipboardData.setData("application/x-navi", JSON.stringify(uiState));
|
|
64588
|
+
// the clear ui state part need control to be interactable
|
|
64589
|
+
dispatchRequestInteraction(pickerEl, {
|
|
64590
|
+
event: e,
|
|
64591
|
+
name: "cut",
|
|
64592
|
+
allowed: () => {
|
|
64593
|
+
dispatchRequestClearUIState(inputRef.current, e);
|
|
64594
|
+
}
|
|
63623
64595
|
});
|
|
64596
|
+
e.preventDefault();
|
|
64597
|
+
},
|
|
64598
|
+
onPaste: e => {
|
|
64599
|
+
const pickerEl = ref.current;
|
|
64600
|
+
if (isWithinPickerContent(e.target, pickerEl)) {
|
|
64601
|
+
// Don't intercept inside the picker popup content.
|
|
64602
|
+
return;
|
|
64603
|
+
}
|
|
64604
|
+
const naviData = e.clipboardData.getData("application/x-navi");
|
|
64605
|
+
let pasteValue;
|
|
64606
|
+
if (naviData) {
|
|
64607
|
+
try {
|
|
64608
|
+
pasteValue = JSON.parse(naviData);
|
|
64609
|
+
} catch {
|
|
64610
|
+
pasteValue = naviData;
|
|
64611
|
+
}
|
|
64612
|
+
} else {
|
|
64613
|
+
pasteValue = e.clipboardData.getData("text/plain");
|
|
64614
|
+
}
|
|
64615
|
+
dispatchRequestInteraction(pickerEl, {
|
|
64616
|
+
event: e,
|
|
64617
|
+
name: "paste",
|
|
64618
|
+
allowed: () => {
|
|
64619
|
+
dispatchRequestSetUIState(inputRef.current, pasteValue, {
|
|
64620
|
+
event: e
|
|
64621
|
+
});
|
|
64622
|
+
}
|
|
64623
|
+
});
|
|
64624
|
+
e.preventDefault();
|
|
63624
64625
|
}
|
|
63625
|
-
})
|
|
63626
|
-
|
|
63627
|
-
|
|
63628
|
-
|
|
63629
|
-
|
|
63630
|
-
|
|
63631
|
-
|
|
63632
|
-
|
|
63633
|
-
|
|
63634
|
-
|
|
63635
|
-
|
|
63636
|
-
|
|
63637
|
-
|
|
63638
|
-
|
|
63639
|
-
|
|
63640
|
-
|
|
63641
|
-
|
|
63642
|
-
|
|
63643
|
-
|
|
63644
|
-
|
|
63645
|
-
|
|
63646
|
-
|
|
63647
|
-
|
|
63648
|
-
|
|
63649
|
-
|
|
63650
|
-
|
|
63651
|
-
|
|
63652
|
-
|
|
63653
|
-
|
|
63654
|
-
|
|
63655
|
-
|
|
63656
|
-
|
|
63657
|
-
|
|
63658
|
-
|
|
63659
|
-
|
|
63660
|
-
|
|
63661
|
-
|
|
63662
|
-
|
|
63663
|
-
|
|
63664
|
-
|
|
63665
|
-
|
|
63666
|
-
|
|
63667
|
-
|
|
63668
|
-
|
|
63669
|
-
|
|
63670
|
-
|
|
63671
|
-
|
|
63672
|
-
|
|
63673
|
-
|
|
63674
|
-
|
|
63675
|
-
|
|
63676
|
-
|
|
63677
|
-
|
|
63678
|
-
|
|
63679
|
-
|
|
63680
|
-
|
|
64626
|
+
}), variant === "icon" || variant === "headless" || ui === "default" ? null : jsx(Text, {
|
|
64627
|
+
className: "navi_picker_value",
|
|
64628
|
+
"navi-placeholder": uiStateHoldsNothing(value) ? "" : undefined,
|
|
64629
|
+
maxLines: maxLines,
|
|
64630
|
+
children: jsx(PickerOwnContent, {
|
|
64631
|
+
children: jsx(PickerContext.Provider, {
|
|
64632
|
+
value: {
|
|
64633
|
+
value,
|
|
64634
|
+
placeholder,
|
|
64635
|
+
maxLines
|
|
64636
|
+
},
|
|
64637
|
+
children: jsx(MaxLinesContext.Provider, {
|
|
64638
|
+
value: maxLines,
|
|
64639
|
+
children: ui === undefined ? jsx(PickerDefaultUI, {}) : ui
|
|
64640
|
+
})
|
|
64641
|
+
})
|
|
64642
|
+
})
|
|
64643
|
+
}), variant === "headless" || ui === "default" ? null : jsx("span", {
|
|
64644
|
+
className: "navi_picker_right_slot",
|
|
64645
|
+
children: jsx(PickerOwnContent, {
|
|
64646
|
+
children: clearable && interactive && value !== undefined && value !== "" ? jsx(Button, {
|
|
64647
|
+
command: "--navi-clear",
|
|
64648
|
+
commandFor: inputProps.id
|
|
64649
|
+
// The question, asked before the clear rather than by the
|
|
64650
|
+
// action the clear sends — see the --navi-clear command.
|
|
64651
|
+
,
|
|
64652
|
+
|
|
64653
|
+
confirm: clearConfirm,
|
|
64654
|
+
confirmPopupContent: clearConfirmPopupContent,
|
|
64655
|
+
tabIndex: "-1"
|
|
64656
|
+
// No navi-focus-delegate, unlike the identical button inside an
|
|
64657
|
+
// input: handing focus back to the picker's own input is what
|
|
64658
|
+
// opens the popup, and clearing is the opposite intention.
|
|
64659
|
+
,
|
|
64660
|
+
|
|
64661
|
+
icon: true,
|
|
64662
|
+
variant: "discrete"
|
|
64663
|
+
// What is busy once the clear is sent is the picker — the value
|
|
64664
|
+
// being removed is the whole field's, and the picker already
|
|
64665
|
+
// draws the wait around all of it. Two outlines for one wait is
|
|
64666
|
+
// one too many.
|
|
64667
|
+
,
|
|
64668
|
+
|
|
64669
|
+
loadingOutline: false
|
|
64670
|
+
// preventDefault, not just tabIndex="-1": a mousedown focuses
|
|
64671
|
+
// its target before any click happens, and this button should
|
|
64672
|
+
// never hold focus at all — the field keeps it.
|
|
64673
|
+
,
|
|
64674
|
+
|
|
64675
|
+
onMouseDown: e => {
|
|
64676
|
+
e.preventDefault();
|
|
64677
|
+
},
|
|
64678
|
+
flex: true,
|
|
64679
|
+
align: "center",
|
|
64680
|
+
children: jsx(Icon, {
|
|
64681
|
+
size: rightSlotIconSize,
|
|
64682
|
+
lineOverflow: "allow",
|
|
64683
|
+
children: jsx(CloseSvg, {})
|
|
64684
|
+
})
|
|
64685
|
+
}) : rightSlot === undefined ?
|
|
64686
|
+
// lineOverflow: what sits in the slot is an affordance, not a
|
|
64687
|
+
// character — a caller asking for a bigger one wants it bigger,
|
|
64688
|
+
// not capped at the height of the line it sits on
|
|
64689
|
+
jsx(Icon, {
|
|
63681
64690
|
size: rightSlotIconSize,
|
|
63682
64691
|
lineOverflow: "allow",
|
|
63683
|
-
children: jsx(
|
|
63684
|
-
})
|
|
63685
|
-
})
|
|
63686
|
-
|
|
63687
|
-
|
|
63688
|
-
|
|
63689
|
-
|
|
63690
|
-
|
|
63691
|
-
|
|
63692
|
-
children: rightSlotIcon === undefined ? jsx(ChevronDownSvg$1, {}) : rightSlotIcon
|
|
63693
|
-
}) : rightSlot
|
|
64692
|
+
children: rightSlotIcon === undefined ? jsx(ChevronDownSvg$1, {}) : rightSlotIcon
|
|
64693
|
+
}) : rightSlot
|
|
64694
|
+
})
|
|
64695
|
+
})]
|
|
64696
|
+
}), jsx(ControlFacadeChildrenWrapper, {
|
|
64697
|
+
...facadeChildrenProps,
|
|
64698
|
+
children: jsx("div", {
|
|
64699
|
+
className: "navi_picker_content",
|
|
64700
|
+
children: children
|
|
63694
64701
|
})
|
|
63695
|
-
})
|
|
63696
|
-
})]
|
|
63697
|
-
}), jsx(ControlFacadeChildrenWrapper, {
|
|
63698
|
-
...facadeChildrenProps,
|
|
63699
|
-
children: jsx("div", {
|
|
63700
|
-
className: "navi_picker_content",
|
|
63701
|
-
children: children
|
|
64702
|
+
})]
|
|
63702
64703
|
})
|
|
63703
|
-
})
|
|
64704
|
+
})
|
|
64705
|
+
);
|
|
64706
|
+
};
|
|
64707
|
+
// What the picker draws itself — the value it shows, the furniture in its slot,
|
|
64708
|
+
// and whatever a caller puts in either — is not another control of the field
|
|
64709
|
+
// around it: none of it may take the id (nor the name) a <Field> hands down,
|
|
64710
|
+
// which is the picker's. Two controls under one id is one registry entry, and
|
|
64711
|
+
// the one that unmounts first — the clear cross the moment the field it emptied
|
|
64712
|
+
// is empty, a chip the moment its value is taken out — takes the picker's own
|
|
64713
|
+
// entry with it, leaving the picker looking for a controller that is gone.
|
|
64714
|
+
const PickerOwnContent = ({
|
|
64715
|
+
children
|
|
64716
|
+
}) => jsx(ControlIdContext.Provider, {
|
|
64717
|
+
value: undefined,
|
|
64718
|
+
children: jsx(ControlNameContext.Provider, {
|
|
64719
|
+
value: undefined,
|
|
64720
|
+
children: children
|
|
64721
|
+
})
|
|
64722
|
+
});
|
|
64723
|
+
|
|
64724
|
+
// `id` is what --navi-select/--navi-unselect carry (a list addresses its rows by
|
|
64725
|
+
// id); asked of a picker, what they carry is one entry of the list the picker
|
|
64726
|
+
// holds — the same thing a `<Picker.Chip value>` stands for.
|
|
64727
|
+
const requestPickerListEntry = (pickerEl, pickerInputEl, e, goal) => {
|
|
64728
|
+
const uiState = getUIStateFromElement(pickerInputEl);
|
|
64729
|
+
if (!Array.isArray(uiState)) {
|
|
64730
|
+
return;
|
|
64731
|
+
}
|
|
64732
|
+
const {
|
|
64733
|
+
id: entry
|
|
64734
|
+
} = e.detail;
|
|
64735
|
+
const isThere = uiState.some(item => compareTwoJsValues(item, entry));
|
|
64736
|
+
if (goal === "select" ? isThere : !isThere) {
|
|
64737
|
+
return;
|
|
64738
|
+
}
|
|
64739
|
+
const uiStateNext = goal === "select" ? [...uiState, entry] : uiState.filter(item => !compareTwoJsValues(item, entry));
|
|
64740
|
+
dispatchRequestInteraction(pickerEl, {
|
|
64741
|
+
event: e,
|
|
64742
|
+
name: goal,
|
|
64743
|
+
prevented: () => e.preventDefault(),
|
|
64744
|
+
allowed: () => {
|
|
64745
|
+
dispatchRequestSetUIState(pickerInputEl, uiStateNext, {
|
|
64746
|
+
event: e
|
|
64747
|
+
});
|
|
64748
|
+
}
|
|
63704
64749
|
});
|
|
63705
64750
|
};
|
|
63706
64751
|
const isWithinPickerContent = (el, pickerEl) => {
|
|
@@ -63845,6 +64890,7 @@ const PickerFirstResolver = props => {
|
|
|
63845
64890
|
});
|
|
63846
64891
|
};
|
|
63847
64892
|
const Picker = createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
|
|
64893
|
+
Picker.Chip = PickerChip;
|
|
63848
64894
|
Picker.UI = PickerDefaultUI;
|
|
63849
64895
|
Picker.UI.Date = PickerDateUI;
|
|
63850
64896
|
Picker.UI.Time = PickerTimeUI;
|
|
@@ -63957,7 +65003,27 @@ const css$t = /* css */`
|
|
|
63957
65003
|
of one's own still wins — an inline style beats a stylesheet. */
|
|
63958
65004
|
border: var(--navi-control-border-width) solid
|
|
63959
65005
|
var(--navi-control-border-color);
|
|
63960
|
-
|
|
65006
|
+
/* Squared from the outside, corner by corner: a spin is not always the
|
|
65007
|
+
member a Group joins — it can arrive wrapped — so the ask travels down
|
|
65008
|
+
as inherited custom properties rather than as a radius landing on this
|
|
65009
|
+
element. The chevrons take these corners back by inherit (see below), so
|
|
65010
|
+
they follow. */
|
|
65011
|
+
border-top-left-radius: var(
|
|
65012
|
+
--x-corner-top-left-radius,
|
|
65013
|
+
var(--navi-control-border-radius)
|
|
65014
|
+
);
|
|
65015
|
+
border-top-right-radius: var(
|
|
65016
|
+
--x-corner-top-right-radius,
|
|
65017
|
+
var(--navi-control-border-radius)
|
|
65018
|
+
);
|
|
65019
|
+
border-bottom-right-radius: var(
|
|
65020
|
+
--x-corner-bottom-right-radius,
|
|
65021
|
+
var(--navi-control-border-radius)
|
|
65022
|
+
);
|
|
65023
|
+
border-bottom-left-radius: var(
|
|
65024
|
+
--x-corner-bottom-left-radius,
|
|
65025
|
+
var(--navi-control-border-radius)
|
|
65026
|
+
);
|
|
63961
65027
|
outline-width: var(--navi-focus-outline-width);
|
|
63962
65028
|
/* Just outside the border, never on it: the ring belongs to the whole
|
|
63963
65029
|
control — the two chevrons included, since pressing one lands the
|
|
@@ -64014,6 +65080,13 @@ const css$t = /* css */`
|
|
|
64014
65080
|
opens its calendar. Around the whole control it would open under a chevron;
|
|
64015
65081
|
around the value it opens under the value one pressed. */
|
|
64016
65082
|
.navi_picker_spin_middle {
|
|
65083
|
+
/* The ask stops here: the frame is the spin's box, and the picker or the
|
|
65084
|
+
field standing in the middle of it is behind that frame, not at a seam. */
|
|
65085
|
+
--x-corner-top-left-radius: initial;
|
|
65086
|
+
--x-corner-top-right-radius: initial;
|
|
65087
|
+
--x-corner-bottom-right-radius: initial;
|
|
65088
|
+
--x-corner-bottom-left-radius: initial;
|
|
65089
|
+
|
|
64017
65090
|
position: relative;
|
|
64018
65091
|
display: flex;
|
|
64019
65092
|
min-width: 0;
|
|
@@ -64191,7 +65264,26 @@ const css$t = /* css */`
|
|
|
64191
65264
|
font-family: var(--navi-control-font-family);
|
|
64192
65265
|
border: var(--navi-control-border-width) solid
|
|
64193
65266
|
var(--navi-control-border-color);
|
|
64194
|
-
|
|
65267
|
+
/* Squared from the outside, corner by corner (see .navi_picker_spin
|
|
65268
|
+
above): the group is the member a Group joins, and it can arrive
|
|
65269
|
+
wrapped. The spins inside give their radius up and take the corners of
|
|
65270
|
+
this one back through inherit, so they follow. */
|
|
65271
|
+
border-top-left-radius: var(
|
|
65272
|
+
--x-corner-top-left-radius,
|
|
65273
|
+
var(--navi-control-border-radius)
|
|
65274
|
+
);
|
|
65275
|
+
border-top-right-radius: var(
|
|
65276
|
+
--x-corner-top-right-radius,
|
|
65277
|
+
var(--navi-control-border-radius)
|
|
65278
|
+
);
|
|
65279
|
+
border-bottom-right-radius: var(
|
|
65280
|
+
--x-corner-bottom-right-radius,
|
|
65281
|
+
var(--navi-control-border-radius)
|
|
65282
|
+
);
|
|
65283
|
+
border-bottom-left-radius: var(
|
|
65284
|
+
--x-corner-bottom-left-radius,
|
|
65285
|
+
var(--navi-control-border-radius)
|
|
65286
|
+
);
|
|
64195
65287
|
outline-width: var(--navi-focus-outline-width);
|
|
64196
65288
|
outline-color: var(--navi-focus-outline-color);
|
|
64197
65289
|
outline-offset: 0px;
|