@jsenv/navi 0.29.85 → 0.29.87
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 +489 -129
- package/dist/jsenv_navi.js.map +34 -14
- package/docs/autofocus.md +20 -12
- package/docs/control_object.md +34 -0
- package/docs/control_value.md +28 -1
- package/docs/form_changed.md +4 -1
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -15122,8 +15122,8 @@ const TIME_RANGE_CONSTRAINT = {
|
|
|
15122
15122
|
console.warn(`Time after constraint: no control with id "${after}"`);
|
|
15123
15123
|
return null;
|
|
15124
15124
|
}
|
|
15125
|
-
const timeBefore = minutesFromTime(otherController.uiState);
|
|
15126
|
-
const timeAfter = minutesFromTime(field.uiState);
|
|
15125
|
+
const timeBefore = minutesFromTime$1(otherController.uiState);
|
|
15126
|
+
const timeAfter = minutesFromTime$1(field.uiState);
|
|
15127
15127
|
if (timeBefore === null || timeAfter === null) {
|
|
15128
15128
|
return null;
|
|
15129
15129
|
}
|
|
@@ -15148,7 +15148,7 @@ CONSTRAINT_ATTRIBUTE_SET.add("data-time-min-duration");
|
|
|
15148
15148
|
|
|
15149
15149
|
// "HH:MM" as a number of minutes, which is what two times are compared and
|
|
15150
15150
|
// subtracted as. Anything else is a time nobody has finished writing.
|
|
15151
|
-
const minutesFromTime = (time) => {
|
|
15151
|
+
const minutesFromTime$1 = (time) => {
|
|
15152
15152
|
if (typeof time !== "string") {
|
|
15153
15153
|
return null;
|
|
15154
15154
|
}
|
|
@@ -21855,11 +21855,11 @@ const markAutofocusRestoreOnClose = (
|
|
|
21855
21855
|
* @param {object} [options]
|
|
21856
21856
|
* @param {boolean} [options.skipFirstFocusable]
|
|
21857
21857
|
* Drops step 2 — the focus then goes where something ASKED for it, or to the
|
|
21858
|
-
* last resort, which for a container is itself.
|
|
21859
|
-
*
|
|
21860
|
-
*
|
|
21861
|
-
*
|
|
21862
|
-
*
|
|
21858
|
+
* last resort, which for a container is itself. What arrives is read before
|
|
21859
|
+
* it is reached: the first focusable is wherever the content happens to put
|
|
21860
|
+
* it, so landing there scrolls whatever comes before it out of sight.
|
|
21861
|
+
* transferFocus turns this on by itself wherever the keyboard is a virtual
|
|
21862
|
+
* one — see the reasoning there.
|
|
21863
21863
|
* @returns {{target: HTMLElement, reason: string}|undefined}
|
|
21864
21864
|
*/
|
|
21865
21865
|
const findFocusTarget = (containerEl, { skipFirstFocusable } = {}) => {
|
|
@@ -21978,11 +21978,22 @@ const prepareFocusTransfer = (prepareEvent, debugFocus) => {
|
|
|
21978
21978
|
* undefined when it focused straight away and there is nothing to take
|
|
21979
21979
|
* back.
|
|
21980
21980
|
*/
|
|
21981
|
-
transferFocus: (
|
|
21982
|
-
|
|
21983
|
-
|
|
21984
|
-
|
|
21985
|
-
|
|
21981
|
+
transferFocus: (transferEvent, containerEl, { getDelay } = {}) => {
|
|
21982
|
+
// Where the keyboard is a virtual one, an arrival lands on what ASKED for
|
|
21983
|
+
// the focus, or on the surface — never on the first focusable that
|
|
21984
|
+
// happens to be there. That element costs the top of what just arrived
|
|
21985
|
+
// twice over: the browser scrolls it into view, and a field raises a
|
|
21986
|
+
// keyboard taking a third of what is left, so the title and the sentence
|
|
21987
|
+
// saying what this is about are gone before it has been looked at. A
|
|
21988
|
+
// field that really is what one came for asks by name (step 2) and gets
|
|
21989
|
+
// the keyboard anyway.
|
|
21990
|
+
//
|
|
21991
|
+
// The device, not the interaction (unlike the delay callers apply on top
|
|
21992
|
+
// of this): whether focusing raises a keyboard over what arrived is true
|
|
21993
|
+
// of the screen, and an arrival with no pointer in it at all — a popup
|
|
21994
|
+
// opened by the page loading, a travel asked for by code — is precisely
|
|
21995
|
+
// the one that must not be answered "no keyboard here".
|
|
21996
|
+
const skipFirstFocusable = coarsePointerSignal.value;
|
|
21986
21997
|
let target;
|
|
21987
21998
|
let reason;
|
|
21988
21999
|
containerEl.removeAttribute(AUTOFOCUS_UNPLACED_ATTRIBUTE);
|
|
@@ -25000,6 +25011,7 @@ const useUIStateController = (
|
|
|
25000
25011
|
const controlType = controlInfo.controlType;
|
|
25001
25012
|
const isRadio = controlType === "input" && props.type === "radio";
|
|
25002
25013
|
const isProxy = Boolean(props["navi-control-proxy-for"]);
|
|
25014
|
+
const emptyUIState = resolveEmptyUIState(props, controlType);
|
|
25003
25015
|
|
|
25004
25016
|
const scope = useRenderScope(
|
|
25005
25017
|
// ── init: runs once on mount ───────────────────────────────────────────
|
|
@@ -25050,6 +25062,7 @@ const useUIStateController = (
|
|
|
25050
25062
|
parentUiStateSignalHolder,
|
|
25051
25063
|
isProxy,
|
|
25052
25064
|
allowNameless,
|
|
25065
|
+
emptyUIState,
|
|
25053
25066
|
// Set here too, not only in `update` below: a control rendered once and
|
|
25054
25067
|
// never re-rendered would otherwise never say whether it was GIVEN a
|
|
25055
25068
|
// value (`value`, or a bound signal with no default of its own) or is
|
|
@@ -25242,6 +25255,9 @@ const useUIStateController = (
|
|
|
25242
25255
|
}
|
|
25243
25256
|
debugUIState(e, `publishUIState(${JSON.stringify(newUIState)})`);
|
|
25244
25257
|
publishUIState(newUIState, e);
|
|
25258
|
+
// A picker hands what it holds to the control in its popup — see
|
|
25259
|
+
// useUIFacadeStateController, which is what puts this here.
|
|
25260
|
+
controller.pushStateDownToFacadeChild?.(newUIState, e);
|
|
25245
25261
|
const el = controller.ref.current;
|
|
25246
25262
|
// Always notify the element that its UI state changed.
|
|
25247
25263
|
// Listeners use this to stay in sync (e.g. input_effect.js tracks currentState,
|
|
@@ -25433,12 +25449,7 @@ const useUIStateController = (
|
|
|
25433
25449
|
return true;
|
|
25434
25450
|
},
|
|
25435
25451
|
clearUIState: (e) => {
|
|
25436
|
-
|
|
25437
|
-
// Passing `""` would set checked=true because `"" !== undefined`.
|
|
25438
|
-
const isCheckable =
|
|
25439
|
-
controlType === "input" &&
|
|
25440
|
-
(props.type === "radio" || props.type === "checkbox");
|
|
25441
|
-
controller.setUIState(isCheckable ? undefined : "", e);
|
|
25452
|
+
controller.setUIState(resolveClearedUIState(controller), e);
|
|
25442
25453
|
},
|
|
25443
25454
|
resetUIState: (e) => {
|
|
25444
25455
|
controller.setUIState(controller.state, e);
|
|
@@ -25515,6 +25526,7 @@ const useUIStateController = (
|
|
|
25515
25526
|
controller.ref = props.ref;
|
|
25516
25527
|
controller.id = props.id; // never supposed to change, not supported for now
|
|
25517
25528
|
controller.name = props.name;
|
|
25529
|
+
controller.emptyUIState = emptyUIState;
|
|
25518
25530
|
controller.parentUIStateController = parentUIStateController;
|
|
25519
25531
|
const {
|
|
25520
25532
|
value,
|
|
@@ -25705,14 +25717,18 @@ const GROUP_DEFAULTS = {
|
|
|
25705
25717
|
single: {
|
|
25706
25718
|
// The same exclusions canRegisterAsFacadeChild already makes below (the
|
|
25707
25719
|
// picker façade asked the very same question: which child IS the value).
|
|
25708
|
-
// Buttons and links never hold one
|
|
25709
|
-
//
|
|
25710
|
-
//
|
|
25711
|
-
//
|
|
25720
|
+
// Buttons and links never hold one, and neither does a control that
|
|
25721
|
+
// declared itself nameless. A control *carrying* navi-list is the search
|
|
25722
|
+
// box driving some other list — not the list itself, which stays a
|
|
25723
|
+
// perfectly good single value here (one item, or the array a multiple list
|
|
25724
|
+
// exposes). Excluding the searcher is what leaves the list alone.
|
|
25712
25725
|
childControlFilter: (child) => {
|
|
25713
25726
|
if (child.controlType === "button" || child.controlType === "link") {
|
|
25714
25727
|
return false;
|
|
25715
25728
|
}
|
|
25729
|
+
if (child.allowNameless) {
|
|
25730
|
+
return false;
|
|
25731
|
+
}
|
|
25716
25732
|
if (child.props?.["navi-list"]) {
|
|
25717
25733
|
return false;
|
|
25718
25734
|
}
|
|
@@ -25745,7 +25761,14 @@ const GROUP_DEFAULTS = {
|
|
|
25745
25761
|
aggregateChildStates: (children) => {
|
|
25746
25762
|
const groupValues = {};
|
|
25747
25763
|
for (const child of children) {
|
|
25748
|
-
const { name,
|
|
25764
|
+
const { name, allowNameless, emptyUIState } = child;
|
|
25765
|
+
// A control holding nothing writes its own empty, not a hole: the key
|
|
25766
|
+
// is in the object either way, and what is read from it keeps the shape
|
|
25767
|
+
// the reader was promised (see resolveEmptyUIState).
|
|
25768
|
+
const uiState =
|
|
25769
|
+
child.uiState === undefined && emptyUIState !== undefined
|
|
25770
|
+
? emptyUIState
|
|
25771
|
+
: child.uiState;
|
|
25749
25772
|
if (!name) {
|
|
25750
25773
|
// A nameless GROUP is a grouping, not a value: it exists to hold its
|
|
25751
25774
|
// children together (a WheelGroup sharing navigation, a fieldset-ish
|
|
@@ -25993,6 +26016,25 @@ const useUIGroupStateController = (
|
|
|
25993
26016
|
ref,
|
|
25994
26017
|
getPropFromState: (uiState) => uiState,
|
|
25995
26018
|
distributeChildUIState: resolvedDistributeChildUIState,
|
|
26019
|
+
// Where the group puts a value on ONE child: the only place that knows
|
|
26020
|
+
// what each child gets, and the only one that sees a child it cannot
|
|
26021
|
+
// place — see warnChildAnswersForItself.
|
|
26022
|
+
placeChildUIState: (childUIStateController, groupUIState, e) => {
|
|
26023
|
+
if (!shouldPropagateStateToChild(childUIStateController)) {
|
|
26024
|
+
return;
|
|
26025
|
+
}
|
|
26026
|
+
if (childUIStateController.hasStateProp) {
|
|
26027
|
+
return;
|
|
26028
|
+
}
|
|
26029
|
+
const childNewState = resolvedDistributeChildUIState(
|
|
26030
|
+
groupUIState,
|
|
26031
|
+
childUIStateController,
|
|
26032
|
+
);
|
|
26033
|
+
if (childNewState === CANNOT_DERIVE) {
|
|
26034
|
+
return;
|
|
26035
|
+
}
|
|
26036
|
+
childUIStateController.setUIState(childNewState, e);
|
|
26037
|
+
},
|
|
25996
26038
|
setUIState: (newUIState, e) => {
|
|
25997
26039
|
if (
|
|
25998
26040
|
stateType === "object" &&
|
|
@@ -26025,14 +26067,9 @@ const useUIGroupStateController = (
|
|
|
26025
26067
|
});
|
|
26026
26068
|
chainEvent(propagateDownEvent, e);
|
|
26027
26069
|
for (const childUIStateController of childUIStateControllerArray) {
|
|
26028
|
-
|
|
26029
|
-
const childNewState = resolvedDistributeChildUIState(
|
|
26030
|
-
newUIState,
|
|
26070
|
+
controller.placeChildUIState(
|
|
26031
26071
|
childUIStateController,
|
|
26032
|
-
|
|
26033
|
-
if (childNewState === CANNOT_DERIVE) continue;
|
|
26034
|
-
childUIStateController.setUIState(
|
|
26035
|
-
childNewState,
|
|
26072
|
+
newUIState,
|
|
26036
26073
|
propagateDownEvent,
|
|
26037
26074
|
);
|
|
26038
26075
|
}
|
|
@@ -26091,27 +26128,17 @@ const useUIGroupStateController = (
|
|
|
26091
26128
|
debugUIGroup(
|
|
26092
26129
|
`${controlType}.registerChild("${childControlType}") -> registered (total: ${childUIStateControllerArray.length})`,
|
|
26093
26130
|
);
|
|
26094
|
-
if (
|
|
26131
|
+
if (controller.hasValueProp || controller.hasDefaultValueProp) {
|
|
26095
26132
|
const initialEvent = new CustomEvent("initial_state_push", {
|
|
26096
26133
|
detail: {},
|
|
26097
26134
|
});
|
|
26098
|
-
|
|
26099
|
-
|
|
26100
|
-
|
|
26101
|
-
|
|
26102
|
-
|
|
26103
|
-
|
|
26104
|
-
|
|
26105
|
-
}
|
|
26106
|
-
} else if (controller.hasDefaultValueProp) {
|
|
26107
|
-
const childNewState = resolvedDistributeChildUIState(
|
|
26108
|
-
controller.defaultValue,
|
|
26109
|
-
childUIStateController,
|
|
26110
|
-
);
|
|
26111
|
-
if (childNewState !== CANNOT_DERIVE) {
|
|
26112
|
-
childUIStateController.setUIState(childNewState, initialEvent);
|
|
26113
|
-
}
|
|
26114
|
-
}
|
|
26135
|
+
controller.placeChildUIState(
|
|
26136
|
+
childUIStateController,
|
|
26137
|
+
controller.hasValueProp
|
|
26138
|
+
? controller.value
|
|
26139
|
+
: controller.defaultValue,
|
|
26140
|
+
initialEvent,
|
|
26141
|
+
);
|
|
26115
26142
|
}
|
|
26116
26143
|
onChange(new CustomEvent(`${childControlType}_mount`), {
|
|
26117
26144
|
notifyExternal: "silent",
|
|
@@ -26290,14 +26317,11 @@ const useUIGroupStateController = (
|
|
|
26290
26317
|
{ detail: {} },
|
|
26291
26318
|
);
|
|
26292
26319
|
for (const childUIStateController of childUIStateControllerArray) {
|
|
26293
|
-
|
|
26294
|
-
if (childUIStateController.hasStateProp) continue;
|
|
26295
|
-
const childNewState = controller.distributeChildUIState(
|
|
26296
|
-
value,
|
|
26320
|
+
controller.placeChildUIState(
|
|
26297
26321
|
childUIStateController,
|
|
26322
|
+
value,
|
|
26323
|
+
propagateDownEvent,
|
|
26298
26324
|
);
|
|
26299
|
-
if (childNewState === CANNOT_DERIVE) continue;
|
|
26300
|
-
childUIStateController.setUIState(childNewState, propagateDownEvent);
|
|
26301
26325
|
}
|
|
26302
26326
|
controller.syncInternalState(value);
|
|
26303
26327
|
}
|
|
@@ -26315,14 +26339,11 @@ const useUIGroupStateController = (
|
|
|
26315
26339
|
{ detail: {} },
|
|
26316
26340
|
);
|
|
26317
26341
|
for (const childUIStateController of childUIStateControllerArray) {
|
|
26318
|
-
|
|
26319
|
-
if (childUIStateController.hasStateProp) continue;
|
|
26320
|
-
const childNewState = controller.distributeChildUIState(
|
|
26321
|
-
defaultValue,
|
|
26342
|
+
controller.placeChildUIState(
|
|
26322
26343
|
childUIStateController,
|
|
26344
|
+
defaultValue,
|
|
26345
|
+
propagateDownEvent,
|
|
26323
26346
|
);
|
|
26324
|
-
if (childNewState === CANNOT_DERIVE) continue;
|
|
26325
|
-
childUIStateController.setUIState(childNewState, propagateDownEvent);
|
|
26326
26347
|
}
|
|
26327
26348
|
controller.syncInternalState(defaultValue);
|
|
26328
26349
|
}
|
|
@@ -26440,6 +26461,12 @@ const useUIFacadeStateController = (props, realUIStateController) => {
|
|
|
26440
26461
|
if (childController.controlType === "link") return false;
|
|
26441
26462
|
if (childController.controlType === "facade") return false;
|
|
26442
26463
|
if (childController.isProxy) return false;
|
|
26464
|
+
if (childController.allowNameless) {
|
|
26465
|
+
// A control saying it is not a field is not the one the picker talks
|
|
26466
|
+
// to: the search box above the list, the "select all" switch beside
|
|
26467
|
+
// it. It is there to help find the answer, not to be it.
|
|
26468
|
+
return false;
|
|
26469
|
+
}
|
|
26443
26470
|
if (childController.props["navi-list"]) {
|
|
26444
26471
|
// Controls with navi-list act as standalone list navigators and should
|
|
26445
26472
|
// not be treated as the picker's synced child.
|
|
@@ -26448,9 +26475,38 @@ const useUIFacadeStateController = (props, realUIStateController) => {
|
|
|
26448
26475
|
return true;
|
|
26449
26476
|
};
|
|
26450
26477
|
|
|
26478
|
+
// Picker → child. Handed to the real controller during THIS render so it
|
|
26479
|
+
// is in place before any layout effect runs: the value a parent form
|
|
26480
|
+
// distributes reaches its children from their registration effect, which
|
|
26481
|
+
// fires before the picker's own effects — a façade that only started
|
|
26482
|
+
// listening from an effect of its own was told nothing, and the popup
|
|
26483
|
+
// opened empty on a value the picker already held.
|
|
26484
|
+
const pushStateDownToChild = (newUIState, e) => {
|
|
26485
|
+
if (updatingRef.current) {
|
|
26486
|
+
return;
|
|
26487
|
+
}
|
|
26488
|
+
const child = firstChildControllerRef.current;
|
|
26489
|
+
if (!child) {
|
|
26490
|
+
return;
|
|
26491
|
+
}
|
|
26492
|
+
updatingRef.current = true;
|
|
26493
|
+
const propagateEventType =
|
|
26494
|
+
e.type === "initial_state_push"
|
|
26495
|
+
? "initial_state_push"
|
|
26496
|
+
: "propagate_down_set_ui_state";
|
|
26497
|
+
const propagateDownEvent = new CustomEvent(propagateEventType, {
|
|
26498
|
+
detail: {},
|
|
26499
|
+
});
|
|
26500
|
+
chainEvent(propagateDownEvent, e);
|
|
26501
|
+
child.setUIState(newUIState, propagateDownEvent);
|
|
26502
|
+
updatingRef.current = false;
|
|
26503
|
+
};
|
|
26504
|
+
realUIStateController.pushStateDownToFacadeChild = pushStateDownToChild;
|
|
26505
|
+
|
|
26451
26506
|
const facadeUIStateController = {
|
|
26452
26507
|
controlType: "facade",
|
|
26453
26508
|
props,
|
|
26509
|
+
pushStateDownToChild,
|
|
26454
26510
|
ref: realUIStateController.ref,
|
|
26455
26511
|
uiStateSignal: realUIStateController.uiStateSignal,
|
|
26456
26512
|
controlHostProps: realUIStateController.controlHostProps,
|
|
@@ -26464,7 +26520,8 @@ const useUIFacadeStateController = (props, realUIStateController) => {
|
|
|
26464
26520
|
`[navi] a second control ("${childType}"${child.name ? ` name="${child.name}"` : ""}) registered in the ${describePicker(props)} popup. ` +
|
|
26465
26521
|
`A picker talks to ONE control: the first one receives the picker's whole value and is the only one read back, ` +
|
|
26466
26522
|
`so this one is neither filled nor collected. ` +
|
|
26467
|
-
`A popup holding several values needs one group around them — wrap them in a <ControlGroup>, name each control inside it, and give the picker type="object"
|
|
26523
|
+
`A popup holding several values needs one group around them — wrap them in a <ControlGroup>, name each control inside it, and give the picker type="object". ` +
|
|
26524
|
+
`A control that is there to FIND the answer rather than be it (a search box, a "select all") says so with allowNameless and steps out of the way.`,
|
|
26468
26525
|
child,
|
|
26469
26526
|
);
|
|
26470
26527
|
} else {
|
|
@@ -26564,6 +26621,8 @@ const useUIFacadeStateController = (props, realUIStateController) => {
|
|
|
26564
26621
|
s.controller.ref = realUIStateController.ref;
|
|
26565
26622
|
s.controller.uiStateSignal = realUIStateController.uiStateSignal;
|
|
26566
26623
|
s.controller.controlHostProps = realUIStateController.controlHostProps;
|
|
26624
|
+
realUIStateController.pushStateDownToFacadeChild =
|
|
26625
|
+
s.controller.pushStateDownToChild;
|
|
26567
26626
|
|
|
26568
26627
|
return {
|
|
26569
26628
|
realUIStateController,
|
|
@@ -26571,26 +26630,6 @@ const useUIFacadeStateController = (props, realUIStateController) => {
|
|
|
26571
26630
|
},
|
|
26572
26631
|
);
|
|
26573
26632
|
|
|
26574
|
-
useLayoutEffect(() => {
|
|
26575
|
-
return realUIStateController.subscribe((newUIState, e) => {
|
|
26576
|
-
if (updatingRef.current) {
|
|
26577
|
-
return;
|
|
26578
|
-
}
|
|
26579
|
-
const child = firstChildControllerRef.current;
|
|
26580
|
-
if (!child) {
|
|
26581
|
-
return;
|
|
26582
|
-
}
|
|
26583
|
-
updatingRef.current = true;
|
|
26584
|
-
const propagateDownEvent = new CustomEvent(
|
|
26585
|
-
"propagate_down_set_ui_state",
|
|
26586
|
-
{ detail: {} },
|
|
26587
|
-
);
|
|
26588
|
-
chainEvent(propagateDownEvent, e);
|
|
26589
|
-
child.setUIState(newUIState, propagateDownEvent);
|
|
26590
|
-
updatingRef.current = false;
|
|
26591
|
-
});
|
|
26592
|
-
}, [realUIStateController]);
|
|
26593
|
-
|
|
26594
26633
|
return scope.controller;
|
|
26595
26634
|
};
|
|
26596
26635
|
|
|
@@ -26685,6 +26724,60 @@ const dispatchSyntheticInput = (el, inputEvent, causeEvent) => {
|
|
|
26685
26724
|
el.dispatchEvent(inputEvent);
|
|
26686
26725
|
};
|
|
26687
26726
|
|
|
26727
|
+
/**
|
|
26728
|
+
* What a control is worth when it holds nothing — nothing, in the shape of the
|
|
26729
|
+
* question it answers. A list of days nobody picked is an empty list, not an
|
|
26730
|
+
* empty string; a yes/no nobody said yes to is `false`. Without this the shape
|
|
26731
|
+
* changes under the reader as soon as the answer is empty, and the conversion
|
|
26732
|
+
* back gets written after the wrong value has already been sent.
|
|
26733
|
+
*
|
|
26734
|
+
* `undefined` means the control has no empty of its own — it is simply not
|
|
26735
|
+
* there, which is what an untouched date or an unchecked radio is.
|
|
26736
|
+
*/
|
|
26737
|
+
const resolveEmptyUIState = (props, controlType) => {
|
|
26738
|
+
if (controlType === "input" && props.type === "checkbox") {
|
|
26739
|
+
// A checkbox is a member of a set, the way HTML has it: checked it sends
|
|
26740
|
+
// its value ("on" by default), unchecked it sends nothing at all. Only one
|
|
26741
|
+
// holding `true` is a yes/no, and a yes/no nobody said yes to is `false`.
|
|
26742
|
+
return props.value === true ? false : undefined;
|
|
26743
|
+
}
|
|
26744
|
+
const stateShape = props["navi-state-shape"];
|
|
26745
|
+
if (stateShape === "array") {
|
|
26746
|
+
return EMPTY_ARRAY;
|
|
26747
|
+
}
|
|
26748
|
+
if (stateShape === "object") {
|
|
26749
|
+
return EMPTY_OBJECT;
|
|
26750
|
+
}
|
|
26751
|
+
return undefined;
|
|
26752
|
+
};
|
|
26753
|
+
|
|
26754
|
+
// What a cleared control shows: its own empty, kept in the shape it was holding.
|
|
26755
|
+
const resolveClearedUIState = (controller) => {
|
|
26756
|
+
const { controlType, props } = controller;
|
|
26757
|
+
if (
|
|
26758
|
+
controlType === "input" &&
|
|
26759
|
+
(props.type === "radio" || props.type === "checkbox")
|
|
26760
|
+
) {
|
|
26761
|
+
// Unchecked is `undefined`: any other value reads as checked, `false`
|
|
26762
|
+
// included (see the `checked` line in control_hooks' toDomProps). What such
|
|
26763
|
+
// a control is worth once unchecked is `emptyUIState`, read where the value
|
|
26764
|
+
// is collected rather than stored here.
|
|
26765
|
+
return undefined;
|
|
26766
|
+
}
|
|
26767
|
+
const { emptyUIState } = controller;
|
|
26768
|
+
if (emptyUIState !== undefined) {
|
|
26769
|
+
return emptyUIState;
|
|
26770
|
+
}
|
|
26771
|
+
const currentUIState = controller.uiState;
|
|
26772
|
+
if (Array.isArray(currentUIState)) {
|
|
26773
|
+
return EMPTY_ARRAY;
|
|
26774
|
+
}
|
|
26775
|
+
if (currentUIState !== null && typeof currentUIState === "object") {
|
|
26776
|
+
return EMPTY_OBJECT;
|
|
26777
|
+
}
|
|
26778
|
+
return "";
|
|
26779
|
+
};
|
|
26780
|
+
|
|
26688
26781
|
// What a control says when it has nothing to say: no value at all, or the empty
|
|
26689
26782
|
// array/object a group falls back to while it has no child to aggregate.
|
|
26690
26783
|
const uiStateHoldsNothing = (uiState) => {
|
|
@@ -29331,19 +29424,6 @@ const createOpenController = (
|
|
|
29331
29424
|
findEvent(requestOpenEvent, isTouchDrivenEvent),
|
|
29332
29425
|
);
|
|
29333
29426
|
const cancelPendingFocus = focusTransfer.transferFocus(e, el, {
|
|
29334
|
-
// A popup is READ before it is reached wherever the keyboard is a
|
|
29335
|
-
// virtual one. Landing on the first focusable there costs the top of
|
|
29336
|
-
// the popup twice over: the browser scrolls that element into view,
|
|
29337
|
-
// and a field raises a keyboard that takes a third of what is left —
|
|
29338
|
-
// so the title and the sentence saying what this is about are gone
|
|
29339
|
-
// before the popup has been looked at. Only something that ASKED for
|
|
29340
|
-
// the focus is worth that, and asking is what `autoFocus` is.
|
|
29341
|
-
//
|
|
29342
|
-
// The device, not the opening (unlike the delay below): whether
|
|
29343
|
-
// focusing raises a keyboard over the popup is true of the screen,
|
|
29344
|
-
// and a popup opened by the page loading — no pointer in it at all —
|
|
29345
|
-
// is precisely the one that must not be answered "no keyboard here".
|
|
29346
|
-
skipFirstFocusable: coarsePointerSignal.value,
|
|
29347
29427
|
getDelay: (target) =>
|
|
29348
29428
|
openedByTouch && isEditableTarget(target)
|
|
29349
29429
|
? FOCUS_DELAY_ON_KEYBOARD_MS
|
|
@@ -50583,6 +50663,55 @@ const getNowHoursRoundedToStep = (stepMinutes, offsetMinutes = 0) => {
|
|
|
50583
50663
|
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
|
50584
50664
|
};
|
|
50585
50665
|
|
|
50666
|
+
/**
|
|
50667
|
+
* "HH:MM" and its two numbers, in both directions — what any control made of an
|
|
50668
|
+
* hour beside a minute (fields, wheels) aggregates to and is placed from. Held
|
|
50669
|
+
* as numbers, written on two digits: how they are shown is each control's own
|
|
50670
|
+
* business.
|
|
50671
|
+
*/
|
|
50672
|
+
const parseTimeParts = (time) => {
|
|
50673
|
+
if (typeof time !== "string") {
|
|
50674
|
+
return null;
|
|
50675
|
+
}
|
|
50676
|
+
const match = /^(\d{1,2}):(\d{1,2})/.exec(time);
|
|
50677
|
+
if (!match) {
|
|
50678
|
+
return null;
|
|
50679
|
+
}
|
|
50680
|
+
return { hour: Number(match[1]), minute: Number(match[2]) };
|
|
50681
|
+
};
|
|
50682
|
+
|
|
50683
|
+
// Half a time is not a time: a control holding one of the two and nothing in
|
|
50684
|
+
// the other has no value at all, and a form has nothing to send about it.
|
|
50685
|
+
const formatTimeParts = (hour, minute) => {
|
|
50686
|
+
if (
|
|
50687
|
+
hour === "" ||
|
|
50688
|
+
hour === undefined ||
|
|
50689
|
+
minute === "" ||
|
|
50690
|
+
minute === undefined
|
|
50691
|
+
) {
|
|
50692
|
+
return undefined;
|
|
50693
|
+
}
|
|
50694
|
+
return `${padTwo$1(hour)}:${padTwo$1(minute)}`;
|
|
50695
|
+
};
|
|
50696
|
+
|
|
50697
|
+
const minutesFromTime = (time) => {
|
|
50698
|
+
const parts = parseTimeParts(time);
|
|
50699
|
+
if (!parts) {
|
|
50700
|
+
return null;
|
|
50701
|
+
}
|
|
50702
|
+
return parts.hour * 60 + parts.minute;
|
|
50703
|
+
};
|
|
50704
|
+
|
|
50705
|
+
const timeFromMinutes = (minutes) => {
|
|
50706
|
+
const inDay =
|
|
50707
|
+
((minutes % MINUTES_PER_DAY) + MINUTES_PER_DAY) % MINUTES_PER_DAY;
|
|
50708
|
+
return `${padTwo$1(Math.floor(inDay / 60))}:${padTwo$1(inDay % 60)}`;
|
|
50709
|
+
};
|
|
50710
|
+
|
|
50711
|
+
const MINUTES_PER_DAY = 24 * 60;
|
|
50712
|
+
|
|
50713
|
+
const padTwo$1 = (value) => String(value).padStart(2, "0");
|
|
50714
|
+
|
|
50586
50715
|
// Maps validity type names → navi input type names.
|
|
50587
50716
|
// Numeric signal types must not fall through to the native type="number"
|
|
50588
50717
|
// (which adds spinner buttons and has poor UX) — they map to navi_number instead.
|
|
@@ -62007,8 +62136,7 @@ const PickerNaviTime = props => {
|
|
|
62007
62136
|
const {
|
|
62008
62137
|
min = "00:00",
|
|
62009
62138
|
max = "23:30",
|
|
62010
|
-
step
|
|
62011
|
-
value
|
|
62139
|
+
step
|
|
62012
62140
|
} = props;
|
|
62013
62141
|
const stepSeconds = timeStringToSeconds(step) ?? 1800;
|
|
62014
62142
|
const slots = useMemo(() => generateTimeSlots(min, max, stepSeconds), [min, max, stepSeconds]);
|
|
@@ -62023,7 +62151,6 @@ const PickerNaviTime = props => {
|
|
|
62023
62151
|
id: slot,
|
|
62024
62152
|
index: i,
|
|
62025
62153
|
value: slot,
|
|
62026
|
-
selected: value === slot,
|
|
62027
62154
|
children: jsx(Time, {
|
|
62028
62155
|
type: "time",
|
|
62029
62156
|
children: slot
|
|
@@ -62522,7 +62649,8 @@ const PickerObject = props => {
|
|
|
62522
62649
|
return jsx(Next, {
|
|
62523
62650
|
ui: jsx(PickerObjectUI, {}),
|
|
62524
62651
|
...props,
|
|
62525
|
-
type: "navi_js"
|
|
62652
|
+
type: "navi_js",
|
|
62653
|
+
"navi-state-shape": "object"
|
|
62526
62654
|
});
|
|
62527
62655
|
};
|
|
62528
62656
|
const PickerObjectUI = () => {
|
|
@@ -62557,7 +62685,8 @@ const PickerArray = props => {
|
|
|
62557
62685
|
maxLines: "3",
|
|
62558
62686
|
ui: jsx(PickerArrayUI, {}),
|
|
62559
62687
|
...props,
|
|
62560
|
-
type: "navi_js"
|
|
62688
|
+
type: "navi_js",
|
|
62689
|
+
"navi-state-shape": "array"
|
|
62561
62690
|
});
|
|
62562
62691
|
};
|
|
62563
62692
|
const PickerArrayUI = () => {
|
|
@@ -65031,8 +65160,8 @@ const TimeSpin = ({
|
|
|
65031
65160
|
minuteLabel = naviI18n("time.minute_label"),
|
|
65032
65161
|
...rest
|
|
65033
65162
|
}) => jsxs(SpinGroup, {
|
|
65034
|
-
aggregateChildStates: aggregateTime,
|
|
65035
|
-
distributeChildUIState: distributeTime,
|
|
65163
|
+
aggregateChildStates: aggregateTime$1,
|
|
65164
|
+
distributeChildUIState: distributeTime$1,
|
|
65036
65165
|
...rest,
|
|
65037
65166
|
children: [jsx(NumberSpin, {
|
|
65038
65167
|
name: "hour",
|
|
@@ -65058,9 +65187,8 @@ const TimeSpin = ({
|
|
|
65058
65187
|
})]
|
|
65059
65188
|
});
|
|
65060
65189
|
|
|
65061
|
-
// The two fields as one value, "HH:MM"
|
|
65062
|
-
|
|
65063
|
-
const aggregateTime = childUIStateControllers => {
|
|
65190
|
+
// The two fields as one value, "HH:MM".
|
|
65191
|
+
const aggregateTime$1 = childUIStateControllers => {
|
|
65064
65192
|
let hour = "";
|
|
65065
65193
|
let minute = "";
|
|
65066
65194
|
for (const child of childUIStateControllers) {
|
|
@@ -65071,37 +65199,18 @@ const aggregateTime = childUIStateControllers => {
|
|
|
65071
65199
|
minute = child.uiState ?? "";
|
|
65072
65200
|
}
|
|
65073
65201
|
}
|
|
65074
|
-
|
|
65075
|
-
return undefined;
|
|
65076
|
-
}
|
|
65077
|
-
return `${padTwo(hour)}:${padTwo(minute)}`;
|
|
65202
|
+
return formatTimeParts(hour, minute);
|
|
65078
65203
|
};
|
|
65079
65204
|
|
|
65080
65205
|
// The way back: what the group is set to (a picked value, a form being reset)
|
|
65081
65206
|
// lands on the field it belongs to.
|
|
65082
|
-
const distributeTime = (groupState, childUIStateController) => {
|
|
65083
|
-
const parts =
|
|
65207
|
+
const distributeTime$1 = (groupState, childUIStateController) => {
|
|
65208
|
+
const parts = parseTimeParts(groupState);
|
|
65084
65209
|
if (!parts) {
|
|
65085
65210
|
return undefined;
|
|
65086
65211
|
}
|
|
65087
65212
|
return parts[childUIStateController.name];
|
|
65088
65213
|
};
|
|
65089
|
-
const parseTime = time => {
|
|
65090
|
-
if (typeof time !== "string") {
|
|
65091
|
-
return null;
|
|
65092
|
-
}
|
|
65093
|
-
const match = /^(\d{1,2}):(\d{1,2})/.exec(time);
|
|
65094
|
-
if (!match) {
|
|
65095
|
-
return null;
|
|
65096
|
-
}
|
|
65097
|
-
// Numbers: what an hour and a minute are held as. How they are written —
|
|
65098
|
-
// "07" — is the field's business (see NumberSpin's `pad`).
|
|
65099
|
-
return {
|
|
65100
|
-
hour: Number(match[1]),
|
|
65101
|
-
minute: Number(match[2])
|
|
65102
|
-
};
|
|
65103
|
-
};
|
|
65104
|
-
const padTwo = value => String(value).padStart(2, "0");
|
|
65105
65214
|
|
|
65106
65215
|
/**
|
|
65107
65216
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
@@ -66658,7 +66767,6 @@ const SplitButton = props => {
|
|
|
66658
66767
|
id: `${menuId}_${index}`,
|
|
66659
66768
|
index: index,
|
|
66660
66769
|
value: optionValue,
|
|
66661
|
-
selected: optionValue === valueResolved,
|
|
66662
66770
|
padding: "s",
|
|
66663
66771
|
spacing: "s",
|
|
66664
66772
|
...optionRest,
|
|
@@ -69352,6 +69460,258 @@ const WheelColon = props => {
|
|
|
69352
69460
|
};
|
|
69353
69461
|
Wheel.Colon = WheelColon;
|
|
69354
69462
|
|
|
69463
|
+
/**
|
|
69464
|
+
* A time of day, and a span between two of them, set by turning rather than by
|
|
69465
|
+
* typing. A wheel only ever shows values that exist: there is no half-written
|
|
69466
|
+
* hour to bound and correct under the fingers, which is what a time typed digit
|
|
69467
|
+
* by digit puts a field through ("1" on its way to "18").
|
|
69468
|
+
*
|
|
69469
|
+
* `TimeWheel` is a `WheelGroup` of two `Wheel`s and carries a single "HH:MM",
|
|
69470
|
+
* like `TimeSpin` — the two are interchangeable in a form. `TimeRangeWheel` is
|
|
69471
|
+
* two of those and carries `{ start, end }`, with the rule such a pair always
|
|
69472
|
+
* has: the end comes after the start. Here that rule is lived rather than
|
|
69473
|
+
* checked — the bounds push each other while they turn, so what the wheels show
|
|
69474
|
+
* is always a span. The send-time constraint stays underneath for what pushing
|
|
69475
|
+
* cannot fix (a start so late the span no longer fits in the day).
|
|
69476
|
+
*/
|
|
69477
|
+
|
|
69478
|
+
const HOUR_COUNT = 24;
|
|
69479
|
+
const MINUTES_PER_HOUR = 60;
|
|
69480
|
+
const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
|
|
69481
|
+
|
|
69482
|
+
/**
|
|
69483
|
+
* @type {import("ignore:preact").FunctionComponent<{
|
|
69484
|
+
* name?: string,
|
|
69485
|
+
* value?: string,
|
|
69486
|
+
* defaultValue?: string,
|
|
69487
|
+
* signal?: import("@preact/signals").Signal<string>,
|
|
69488
|
+
* minuteStep?: number,
|
|
69489
|
+
* loop?: boolean,
|
|
69490
|
+
* separator?: import("ignore:preact").ComponentChildren,
|
|
69491
|
+
* hourLabel?: string,
|
|
69492
|
+
* minuteLabel?: string,
|
|
69493
|
+
* size?: string,
|
|
69494
|
+
* visibleCount?: number,
|
|
69495
|
+
* wheelProps?: object,
|
|
69496
|
+
* [key: string]: any,
|
|
69497
|
+
* }>}
|
|
69498
|
+
* @param {string} [value] The time shown, as "HH:MM".
|
|
69499
|
+
* @param {number} [minuteStep=1] How many minutes apart the values on the
|
|
69500
|
+
* minute wheel are — 15 for quarters of an hour.
|
|
69501
|
+
* @param {boolean} [loop=true] The wheels go round: 23h then 0h, 59 minutes
|
|
69502
|
+
* then 0. What a clock does. Say `loop={false}` for two ends one cannot turn
|
|
69503
|
+
* past.
|
|
69504
|
+
* @param {import("ignore:preact").ComponentChildren} [separator] What is written
|
|
69505
|
+
* between the hours and the minutes. "h" in French, ":" elsewhere.
|
|
69506
|
+
* @param {object} [wheelProps] Anything a `Wheel` takes, said once for both of
|
|
69507
|
+
* them — `visibleCount`, `itemWidth`, `glideSpeed`.
|
|
69508
|
+
*/
|
|
69509
|
+
const TimeWheel = ({
|
|
69510
|
+
minuteStep = 1,
|
|
69511
|
+
loop = true,
|
|
69512
|
+
separator = naviI18n("time.hour_separator"),
|
|
69513
|
+
hourLabel = naviI18n("time.hour_label"),
|
|
69514
|
+
minuteLabel = naviI18n("time.minute_label"),
|
|
69515
|
+
size,
|
|
69516
|
+
wheelProps,
|
|
69517
|
+
...rest
|
|
69518
|
+
}) => {
|
|
69519
|
+
const minutes = useMemo(() => {
|
|
69520
|
+
const minuteList = [];
|
|
69521
|
+
let minute = 0;
|
|
69522
|
+
while (minute < MINUTES_PER_HOUR) {
|
|
69523
|
+
minuteList.push(minute);
|
|
69524
|
+
minute += minuteStep;
|
|
69525
|
+
}
|
|
69526
|
+
return minuteList;
|
|
69527
|
+
}, [minuteStep]);
|
|
69528
|
+
return jsxs(WheelGroup, {
|
|
69529
|
+
aggregateChildStates: aggregateTime,
|
|
69530
|
+
distributeChildUIState: distributeTime,
|
|
69531
|
+
...rest,
|
|
69532
|
+
children: [jsx(Wheel, {
|
|
69533
|
+
name: "hour",
|
|
69534
|
+
type: "integer",
|
|
69535
|
+
bounded: !loop,
|
|
69536
|
+
size: size,
|
|
69537
|
+
"aria-label": hourLabel,
|
|
69538
|
+
...wheelProps,
|
|
69539
|
+
children: HOURS.map(hour => jsx(Wheel.Item, {
|
|
69540
|
+
value: hour,
|
|
69541
|
+
paddingX: "s",
|
|
69542
|
+
children: padTwo(hour)
|
|
69543
|
+
}, hour))
|
|
69544
|
+
}), jsx(WheelGroup.Separator, {
|
|
69545
|
+
size: size,
|
|
69546
|
+
children: separator
|
|
69547
|
+
}), jsx(Wheel, {
|
|
69548
|
+
name: "minute",
|
|
69549
|
+
type: "integer",
|
|
69550
|
+
bounded: !loop,
|
|
69551
|
+
size: size,
|
|
69552
|
+
"aria-label": minuteLabel,
|
|
69553
|
+
...wheelProps,
|
|
69554
|
+
children: minutes.map(minute => jsx(Wheel.Item, {
|
|
69555
|
+
value: minute,
|
|
69556
|
+
paddingX: "s",
|
|
69557
|
+
children: padTwo(minute)
|
|
69558
|
+
}, minute))
|
|
69559
|
+
})]
|
|
69560
|
+
});
|
|
69561
|
+
};
|
|
69562
|
+
|
|
69563
|
+
/**
|
|
69564
|
+
* @type {import("ignore:preact").FunctionComponent<{
|
|
69565
|
+
* name?: string,
|
|
69566
|
+
* value?: { start?: string, end?: string },
|
|
69567
|
+
* defaultValue?: { start?: string, end?: string },
|
|
69568
|
+
* signal?: import("@preact/signals").Signal<{ start?: string, end?: string }>,
|
|
69569
|
+
* minuteStep?: number,
|
|
69570
|
+
* minDuration?: number,
|
|
69571
|
+
* loop?: boolean,
|
|
69572
|
+
* size?: string,
|
|
69573
|
+
* startLabel?: import("ignore:preact").ComponentChildren,
|
|
69574
|
+
* endLabel?: import("ignore:preact").ComponentChildren,
|
|
69575
|
+
* timeProps?: object,
|
|
69576
|
+
* [key: string]: any,
|
|
69577
|
+
* }>}
|
|
69578
|
+
* @param {{ start?: string, end?: string }} [value] The span shown, as two
|
|
69579
|
+
* "HH:MM".
|
|
69580
|
+
* @param {import("ignore:preact").ComponentChildren} [startLabel] What is written
|
|
69581
|
+
* before the first time ("De"), and `endLabel` between the two ("à"). Say
|
|
69582
|
+
* `null` for neither.
|
|
69583
|
+
* @param {number} [minuteStep=1] How many minutes apart the values on both
|
|
69584
|
+
* minute wheels are.
|
|
69585
|
+
* @param {number} [minDuration=0] How long the span must last at least, in
|
|
69586
|
+
* minutes. Zero by default: a span of no length is a span all the same, only
|
|
69587
|
+
* one that goes backwards is not. It is what the bounds keep between them as
|
|
69588
|
+
* they turn — turn the start into the end and the end moves along, keeping
|
|
69589
|
+
* that much room.
|
|
69590
|
+
* @param {object} [timeProps] Anything a `TimeWheel` takes, said once for both
|
|
69591
|
+
* of them. `startTimeProps`/`endTimeProps` say it to one of the two, and win
|
|
69592
|
+
* over this one.
|
|
69593
|
+
*/
|
|
69594
|
+
const TimeRangeWheel = ({
|
|
69595
|
+
minuteStep = 1,
|
|
69596
|
+
minDuration = 0,
|
|
69597
|
+
loop = true,
|
|
69598
|
+
size,
|
|
69599
|
+
startLabel = naviI18n("time_range.from"),
|
|
69600
|
+
endLabel = naviI18n("time_range.to"),
|
|
69601
|
+
timeProps,
|
|
69602
|
+
startTimeProps,
|
|
69603
|
+
endTimeProps,
|
|
69604
|
+
...rest
|
|
69605
|
+
}) => {
|
|
69606
|
+
const startId = useId();
|
|
69607
|
+
const startRef = useRef(null);
|
|
69608
|
+
const endRef = useRef(null);
|
|
69609
|
+
|
|
69610
|
+
// What the pair does while it is being turned: the bound that just moved is
|
|
69611
|
+
// the one the user is holding, so it stays where it was put and the OTHER one
|
|
69612
|
+
// gives way. A refusal at the end of the gesture would leave the person to
|
|
69613
|
+
// undo what they just did.
|
|
69614
|
+
const keepBoundsApart = (movedSide, movedTime, e) => {
|
|
69615
|
+
const movedMinutes = minutesFromTime(movedTime);
|
|
69616
|
+
if (movedMinutes === null) {
|
|
69617
|
+
return;
|
|
69618
|
+
}
|
|
69619
|
+
const otherEl = movedSide === "start" ? endRef.current : startRef.current;
|
|
69620
|
+
if (!otherEl) {
|
|
69621
|
+
return;
|
|
69622
|
+
}
|
|
69623
|
+
const otherMinutes = minutesFromTime(getUIStateFromElement(otherEl));
|
|
69624
|
+
if (otherMinutes === null) {
|
|
69625
|
+
return;
|
|
69626
|
+
}
|
|
69627
|
+
const duration = movedSide === "start" ? otherMinutes - movedMinutes : movedMinutes - otherMinutes;
|
|
69628
|
+
if (duration >= minDuration) {
|
|
69629
|
+
return;
|
|
69630
|
+
}
|
|
69631
|
+
let pushedMinutes = movedSide === "start" ? movedMinutes + minDuration : movedMinutes - minDuration;
|
|
69632
|
+
// The day has ends the wheels do not: pushed past midnight, the other bound
|
|
69633
|
+
// would come back round on the wrong side of the one that pushed it. It
|
|
69634
|
+
// stops at the edge instead, and the span that no longer fits is what the
|
|
69635
|
+
// send-time constraint is there to say (see time_range_constraint.js).
|
|
69636
|
+
if (pushedMinutes < 0) {
|
|
69637
|
+
pushedMinutes = 0;
|
|
69638
|
+
} else if (pushedMinutes > LAST_MINUTE_OF_DAY) {
|
|
69639
|
+
pushedMinutes = LAST_MINUTE_OF_DAY;
|
|
69640
|
+
}
|
|
69641
|
+
dispatchRequestSetUIState(otherEl, timeFromMinutes(pushedMinutes), {
|
|
69642
|
+
event: e
|
|
69643
|
+
});
|
|
69644
|
+
};
|
|
69645
|
+
return jsxs(ControlGroup, {
|
|
69646
|
+
flex: true,
|
|
69647
|
+
alignY: "center",
|
|
69648
|
+
spacing: "s",
|
|
69649
|
+
size: size,
|
|
69650
|
+
...rest,
|
|
69651
|
+
children: [startLabel === null ? null : jsx(Text, {
|
|
69652
|
+
size: size,
|
|
69653
|
+
children: startLabel
|
|
69654
|
+
}), jsx(TimeWheel, {
|
|
69655
|
+
id: startId,
|
|
69656
|
+
ref: startRef,
|
|
69657
|
+
name: "start",
|
|
69658
|
+
minuteStep: minuteStep,
|
|
69659
|
+
loop: loop,
|
|
69660
|
+
size: size,
|
|
69661
|
+
uiAction: (value, e) => keepBoundsApart("start", value, e),
|
|
69662
|
+
...timeProps,
|
|
69663
|
+
...startTimeProps
|
|
69664
|
+
}), endLabel === null ? null : jsx(Text, {
|
|
69665
|
+
size: size,
|
|
69666
|
+
children: endLabel
|
|
69667
|
+
}), jsx(TimeWheel, {
|
|
69668
|
+
ref: endRef,
|
|
69669
|
+
name: "end",
|
|
69670
|
+
minuteStep: minuteStep,
|
|
69671
|
+
loop: loop,
|
|
69672
|
+
size: size,
|
|
69673
|
+
uiAction: (value, e) => keepBoundsApart("end", value, e)
|
|
69674
|
+
// Which time it comes after, and how much room there must be between
|
|
69675
|
+
// the two: said on the LATER of the two, so the answer is given where
|
|
69676
|
+
// the time one would have to move is (see time_range_constraint.js).
|
|
69677
|
+
,
|
|
69678
|
+
"data-time-after": startId,
|
|
69679
|
+
"data-time-min-duration": minDuration,
|
|
69680
|
+
...timeProps,
|
|
69681
|
+
...endTimeProps
|
|
69682
|
+
})]
|
|
69683
|
+
});
|
|
69684
|
+
};
|
|
69685
|
+
const HOURS = Array.from({
|
|
69686
|
+
length: HOUR_COUNT
|
|
69687
|
+
}, (_, hour) => hour);
|
|
69688
|
+
const padTwo = value => String(value).padStart(2, "0");
|
|
69689
|
+
|
|
69690
|
+
// The two wheels as one value, "HH:MM".
|
|
69691
|
+
const aggregateTime = childUIStateControllers => {
|
|
69692
|
+
let hour = "";
|
|
69693
|
+
let minute = "";
|
|
69694
|
+
for (const child of childUIStateControllers) {
|
|
69695
|
+
if (child.name === "hour") {
|
|
69696
|
+
hour = child.uiState ?? "";
|
|
69697
|
+
}
|
|
69698
|
+
if (child.name === "minute") {
|
|
69699
|
+
minute = child.uiState ?? "";
|
|
69700
|
+
}
|
|
69701
|
+
}
|
|
69702
|
+
return formatTimeParts(hour, minute);
|
|
69703
|
+
};
|
|
69704
|
+
|
|
69705
|
+
// The way back: what the group is set to (a value given to it, a form being
|
|
69706
|
+
// reset, the other bound pushing it) lands on the wheel it belongs to.
|
|
69707
|
+
const distributeTime = (groupState, childUIStateController) => {
|
|
69708
|
+
const parts = parseTimeParts(groupState);
|
|
69709
|
+
if (!parts) {
|
|
69710
|
+
return undefined;
|
|
69711
|
+
}
|
|
69712
|
+
return parts[childUIStateController.name];
|
|
69713
|
+
};
|
|
69714
|
+
|
|
69355
69715
|
const TableSelectionContext = createContext();
|
|
69356
69716
|
const useTableSelectionContextValue = (
|
|
69357
69717
|
selection,
|
|
@@ -75378,5 +75738,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
75378
75738
|
})
|
|
75379
75739
|
});
|
|
75380
75740
|
|
|
75381
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeSpin, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineNaviConfirmPopupOptions, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markErrorAsDisplayedBy, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
|
|
75741
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineNaviConfirmPopupOptions, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markErrorAsDisplayedBy, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
|
|
75382
75742
|
//# sourceMappingURL=jsenv_navi.js.map
|