@jsenv/navi 0.29.352 → 0.29.354
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/dev/jsenv_navi.js +281 -64
- package/dist/dev/jsenv_navi.js.map +12 -12
- package/dist/jsenv_navi.js +281 -64
- package/dist/jsenv_navi.js.map +12 -12
- package/package.json +1 -1
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -663,6 +663,11 @@ naviI18n.addAll({
|
|
|
663
663
|
en: ":",
|
|
664
664
|
fr: "h",
|
|
665
665
|
},
|
|
666
|
+
// A time of whole hours has nothing to stand between: what follows the hour.
|
|
667
|
+
"time.hour_suffix": {
|
|
668
|
+
en: "",
|
|
669
|
+
fr: "h",
|
|
670
|
+
},
|
|
666
671
|
"time.hour_label": {
|
|
667
672
|
en: "Hours",
|
|
668
673
|
fr: "Heures",
|
|
@@ -4869,14 +4874,12 @@ const minutesFromTime$1 = (time) => {
|
|
|
4869
4874
|
return parts.hour * 60 + parts.minute;
|
|
4870
4875
|
};
|
|
4871
4876
|
|
|
4877
|
+
// Not folded back into the day: 1440 is "24:00", the end of the day a span can
|
|
4878
|
+
// run into.
|
|
4872
4879
|
const timeFromMinutes = (minutes) => {
|
|
4873
|
-
|
|
4874
|
-
((minutes % MINUTES_PER_DAY) + MINUTES_PER_DAY) % MINUTES_PER_DAY;
|
|
4875
|
-
return `${padTwo$1(Math.floor(inDay / 60))}:${padTwo$1(inDay % 60)}`;
|
|
4880
|
+
return `${padTwo$1(Math.floor(minutes / 60))}:${padTwo$1(minutes % 60)}`;
|
|
4876
4881
|
};
|
|
4877
4882
|
|
|
4878
|
-
const MINUTES_PER_DAY = 24 * 60;
|
|
4879
|
-
|
|
4880
4883
|
const padTwo$1 = (value) => String(value).padStart(2, "0");
|
|
4881
4884
|
|
|
4882
4885
|
// Maps validity type names → navi input type names.
|
|
@@ -5772,6 +5775,14 @@ const constraintAttributeFromProp = (key) => {
|
|
|
5772
5775
|
const isConstraintAttributeOn = (value) =>
|
|
5773
5776
|
value !== undefined && value !== null && value !== false;
|
|
5774
5777
|
|
|
5778
|
+
/**
|
|
5779
|
+
* Whether a constraint attribute asks for the value to be CORRECTED rather
|
|
5780
|
+
* than refused: `singleSpace="autoFix"`. Only constraints whose rule knows a
|
|
5781
|
+
* correction offer it, and only the fields that write it get one — a
|
|
5782
|
+
* `maxLength` silently truncating what someone wrote would be a bad default.
|
|
5783
|
+
*/
|
|
5784
|
+
const isConstraintAttributeAutoFix = (value) => value === "autoFix";
|
|
5785
|
+
|
|
5775
5786
|
const CONSTRAINT_NAME_TO_PROP = {
|
|
5776
5787
|
disabled: "disabledMessage",
|
|
5777
5788
|
required: "requiredMessage",
|
|
@@ -10639,14 +10650,13 @@ const tryActionAfterInteractionAllowed = (
|
|
|
10639
10650
|
|
|
10640
10651
|
// Resolve proxy so navi_action_* fires on the real control element.
|
|
10641
10652
|
let elementForAction = controlHost;
|
|
10642
|
-
let
|
|
10653
|
+
let activeController = controller;
|
|
10643
10654
|
if (controller) {
|
|
10644
10655
|
const proxyTargetController = findControlProxyTargetController(controller);
|
|
10645
10656
|
if (proxyTargetController) {
|
|
10646
10657
|
elementForAction = proxyTargetController.ref.current;
|
|
10658
|
+
activeController = proxyTargetController;
|
|
10647
10659
|
}
|
|
10648
|
-
const activeController = proxyTargetController ?? controller;
|
|
10649
|
-
uiState = activeController?.uiState;
|
|
10650
10660
|
}
|
|
10651
10661
|
|
|
10652
10662
|
// Validity gate: re-check (handles autoResetOnAction side effects), then read
|
|
@@ -10673,6 +10683,11 @@ const tryActionAfterInteractionAllowed = (
|
|
|
10673
10683
|
}
|
|
10674
10684
|
}
|
|
10675
10685
|
|
|
10686
|
+
// Read after the gate, never before: a constraint allowed to correct the
|
|
10687
|
+
// value rewrites it in there (see applyAutoFix), and what goes out has to be
|
|
10688
|
+
// what the control ends up holding.
|
|
10689
|
+
const uiState = activeController?.uiState;
|
|
10690
|
+
|
|
10676
10691
|
if (action === "auto" || action?.isAction) {
|
|
10677
10692
|
// A control that commits gets the last word on whether this particular
|
|
10678
10693
|
// value is worth acting on — see Form's own `shouldRequestAction`, which
|
|
@@ -13125,9 +13140,22 @@ CONSTRAINT_ATTRIBUTE_SET.add("data-same-as");
|
|
|
13125
13140
|
* `data-single-space` — no leading or trailing space, never two in a row.
|
|
13126
13141
|
* The rule itself is @jsenv/validity's SINGLE_SPACE_RULE, so a server checking
|
|
13127
13142
|
* the value again refuses it for the same reason and in the same words.
|
|
13143
|
+
*
|
|
13144
|
+
* `data-single-space="autoFix"` corrects the value instead of refusing it. A
|
|
13145
|
+
* text field ends with a space more often than anyone means it to — a word
|
|
13146
|
+
* then a pause, a mobile keyboard after a suggestion, a paste — and the person
|
|
13147
|
+
* is then asked to find and delete a character they cannot see. The correction
|
|
13148
|
+
* is the rule's own, so the value a server re-checks with `singleSpace: true`
|
|
13149
|
+
* is one it accepts.
|
|
13128
13150
|
*/
|
|
13129
13151
|
|
|
13130
13152
|
|
|
13153
|
+
const applyRule = (field) => {
|
|
13154
|
+
const valueAsString =
|
|
13155
|
+
field.uiState === undefined ? "" : String(field.uiState);
|
|
13156
|
+
return SINGLE_SPACE_RULE.applyOn(true, valueAsString);
|
|
13157
|
+
};
|
|
13158
|
+
|
|
13131
13159
|
const SINGLE_SPACE_CONSTRAINT = {
|
|
13132
13160
|
name: "single_space",
|
|
13133
13161
|
messageAttribute: "data-single-space-message",
|
|
@@ -13136,14 +13164,29 @@ const SINGLE_SPACE_CONSTRAINT = {
|
|
|
13136
13164
|
if (!isConstraintAttributeOn(singleSpace)) {
|
|
13137
13165
|
return null;
|
|
13138
13166
|
}
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13167
|
+
if (isConstraintAttributeAutoFix(singleSpace)) {
|
|
13168
|
+
// The correction the rule knows always lands on a value the rule
|
|
13169
|
+
// accepts, and it runs on every commit — so there is nothing left for
|
|
13170
|
+
// the person to do about this, and nothing to say to them.
|
|
13171
|
+
return null;
|
|
13172
|
+
}
|
|
13173
|
+
const result = applyRule(field);
|
|
13142
13174
|
if (!result) {
|
|
13143
13175
|
return null;
|
|
13144
13176
|
}
|
|
13145
13177
|
return naviI18nFromValidityMessage(result);
|
|
13146
13178
|
},
|
|
13179
|
+
autoFix: (field) => {
|
|
13180
|
+
const singleSpace = field.controlHostProps["data-single-space"];
|
|
13181
|
+
if (!isConstraintAttributeAutoFix(singleSpace)) {
|
|
13182
|
+
return null;
|
|
13183
|
+
}
|
|
13184
|
+
const result = applyRule(field);
|
|
13185
|
+
if (!result) {
|
|
13186
|
+
return null;
|
|
13187
|
+
}
|
|
13188
|
+
return result.autoFix();
|
|
13189
|
+
},
|
|
13147
13190
|
};
|
|
13148
13191
|
CONSTRAINT_ATTRIBUTE_SET.add("data-single-space");
|
|
13149
13192
|
|
|
@@ -13335,6 +13378,74 @@ const createControlValidation = (
|
|
|
13335
13378
|
const getConstraintValidityState = () => constraintValidityState;
|
|
13336
13379
|
controlValidity.getConstraintValidityState = getConstraintValidityState;
|
|
13337
13380
|
|
|
13381
|
+
const getConstraintSet = () => {
|
|
13382
|
+
const constraintSet = new Set([
|
|
13383
|
+
...DEFAULT_CONSTRAINT_SET,
|
|
13384
|
+
...dynamicConstraintSet,
|
|
13385
|
+
]);
|
|
13386
|
+
// An app constraint declared at the call site: `constraints={[MY_CONSTRAINT]}`.
|
|
13387
|
+
// Read from the raw props on every check so a constraint whose parameters
|
|
13388
|
+
// are closed over is re-created freely, and last so the constraints navi
|
|
13389
|
+
// ships are the ones reported first (see pickConstraintFailureInfo).
|
|
13390
|
+
const constraintsFromProps = controller.props.constraints;
|
|
13391
|
+
if (constraintsFromProps) {
|
|
13392
|
+
for (const constraintFromProps of constraintsFromProps) {
|
|
13393
|
+
constraintSet.add(normalizeConstraint(constraintFromProps));
|
|
13394
|
+
}
|
|
13395
|
+
}
|
|
13396
|
+
return constraintSet;
|
|
13397
|
+
};
|
|
13398
|
+
|
|
13399
|
+
/**
|
|
13400
|
+
* Lets the constraints allowed to correct the value do so, and puts what
|
|
13401
|
+
* they return in the control — ui state, bound signal, the field itself.
|
|
13402
|
+
*
|
|
13403
|
+
* Called at the moments a value is COMMITTED: the field is left, an action
|
|
13404
|
+
* is about to read it. Never while it is being typed into, where removing
|
|
13405
|
+
* the trailing space would eat the one the person is about to follow with a
|
|
13406
|
+
* word.
|
|
13407
|
+
*
|
|
13408
|
+
* Returns whether the value moved, so a caller outside the validity pass can
|
|
13409
|
+
* re-read what the control is now worth.
|
|
13410
|
+
*/
|
|
13411
|
+
const applyAutoFix = (event) => {
|
|
13412
|
+
const proxyTargetController = findControlProxyTargetController(controller);
|
|
13413
|
+
if (proxyTargetController) {
|
|
13414
|
+
return proxyTargetController.rules.validation.applyAutoFix(event);
|
|
13415
|
+
}
|
|
13416
|
+
// A value nobody can edit is not navi's to rewrite: it is the app's, and
|
|
13417
|
+
// correcting it would change what gets sent behind the app's back.
|
|
13418
|
+
const controlHostProps = controller.controlHostProps;
|
|
13419
|
+
if (
|
|
13420
|
+
controlHostProps.disabled ||
|
|
13421
|
+
controlHostProps.readOnly ||
|
|
13422
|
+
controlHostProps["aria-readonly"] === "true"
|
|
13423
|
+
) {
|
|
13424
|
+
return false;
|
|
13425
|
+
}
|
|
13426
|
+
let fixed = false;
|
|
13427
|
+
for (const constraint of getConstraintSet()) {
|
|
13428
|
+
if (!constraint.autoFix) {
|
|
13429
|
+
continue;
|
|
13430
|
+
}
|
|
13431
|
+
const fixedValue = constraint.autoFix(controller);
|
|
13432
|
+
if (fixedValue === null || fixedValue === undefined) {
|
|
13433
|
+
continue;
|
|
13434
|
+
}
|
|
13435
|
+
if (compareTwoJsValues(fixedValue, controller.uiState)) {
|
|
13436
|
+
continue;
|
|
13437
|
+
}
|
|
13438
|
+
const autoFixEvent = new CustomEvent("auto_fix", {
|
|
13439
|
+
detail: { constraint: constraint.name },
|
|
13440
|
+
});
|
|
13441
|
+
chainEvent(autoFixEvent, event);
|
|
13442
|
+
controller.setUIState(fixedValue, autoFixEvent);
|
|
13443
|
+
fixed = true;
|
|
13444
|
+
}
|
|
13445
|
+
return fixed;
|
|
13446
|
+
};
|
|
13447
|
+
controlValidity.applyAutoFix = applyAutoFix;
|
|
13448
|
+
|
|
13338
13449
|
const checkValidity = ({
|
|
13339
13450
|
event,
|
|
13340
13451
|
requester = controller.ref.current,
|
|
@@ -13375,21 +13486,15 @@ const createControlValidation = (
|
|
|
13375
13486
|
}
|
|
13376
13487
|
}
|
|
13377
13488
|
|
|
13378
|
-
|
|
13379
|
-
|
|
13380
|
-
|
|
13381
|
-
|
|
13382
|
-
|
|
13383
|
-
// An app constraint declared at the call site: `constraints={[MY_CONSTRAINT]}`.
|
|
13384
|
-
// Read from the raw props on every check so a constraint whose parameters
|
|
13385
|
-
// are closed over is re-created freely, and last so the constraints navi
|
|
13386
|
-
// ships are the ones reported first (see pickConstraintFailureInfo).
|
|
13387
|
-
const constraintsFromProps = controller.props.constraints;
|
|
13388
|
-
if (constraintsFromProps) {
|
|
13389
|
-
for (const constraintFromProps of constraintsFromProps) {
|
|
13390
|
-
constraintSet.add(normalizeConstraint(constraintFromProps));
|
|
13391
|
-
}
|
|
13489
|
+
if (fromRequestAction) {
|
|
13490
|
+
// The value is about to be read and sent: whoever may correct it gets
|
|
13491
|
+
// the last word before the constraints judge it, so a field is never
|
|
13492
|
+
// refused for something navi knows how to put right.
|
|
13493
|
+
applyAutoFix(event);
|
|
13392
13494
|
}
|
|
13495
|
+
|
|
13496
|
+
let newConstraintValidityState = { valid: true };
|
|
13497
|
+
const constraintSet = getConstraintSet();
|
|
13393
13498
|
const elementSig = getElementSignature(controller.ref.current);
|
|
13394
13499
|
// Not logged: every control checks its constraints on every interaction and
|
|
13395
13500
|
// almost always passes, so this line alone was most of the debug output —
|
|
@@ -19680,7 +19785,22 @@ const TYPO_PROPS = {
|
|
|
19680
19785
|
}
|
|
19681
19786
|
return lineClampStyles(value);
|
|
19682
19787
|
},
|
|
19683
|
-
|
|
19788
|
+
/* A sized inline Box (a Text with a width) is a flex row the caller did not
|
|
19789
|
+
ask for, where the text runs are one anonymous item sized to their content:
|
|
19790
|
+
text-align alone moves nothing, the item is placed by justify-content. A
|
|
19791
|
+
flex row asked for keeps the two apart (Picker: textAlign is the text in
|
|
19792
|
+
the value slot, justify-content the slots). alignX, when given, wins. */
|
|
19793
|
+
textAlign: (value, { flexFromSize, remainingProps }) => {
|
|
19794
|
+
if (
|
|
19795
|
+
flexFromSize &&
|
|
19796
|
+
value !== "justify" &&
|
|
19797
|
+
remainingProps.alignX === undefined &&
|
|
19798
|
+
remainingProps.align === undefined
|
|
19799
|
+
) {
|
|
19800
|
+
return { textAlign: value, justifyContent: value };
|
|
19801
|
+
}
|
|
19802
|
+
return { textAlign: value };
|
|
19803
|
+
},
|
|
19684
19804
|
textBox: PASS_THROUGH,
|
|
19685
19805
|
textBoxTrim: PASS_THROUGH,
|
|
19686
19806
|
textBoxEdge: PASS_THROUGH,
|
|
@@ -22294,8 +22414,12 @@ const computeBox = (props, parentBoxFlow) => {
|
|
|
22294
22414
|
block = true;
|
|
22295
22415
|
}
|
|
22296
22416
|
}
|
|
22417
|
+
// An inline box ignores width/height, so a sized one becomes a flex row the
|
|
22418
|
+
// caller never asked for; textAlign reads that (see box_style_util.js).
|
|
22419
|
+
let flexFromSize = false;
|
|
22297
22420
|
if (inline && (rest.width !== undefined || rest.height !== undefined) && flex === undefined) {
|
|
22298
22421
|
flex = "x";
|
|
22422
|
+
flexFromSize = true;
|
|
22299
22423
|
}
|
|
22300
22424
|
let boxFlow;
|
|
22301
22425
|
if (inline) {
|
|
@@ -22389,6 +22513,7 @@ const computeBox = (props, parentBoxFlow) => {
|
|
|
22389
22513
|
const styleContext = {
|
|
22390
22514
|
parentBoxFlow,
|
|
22391
22515
|
boxFlow,
|
|
22516
|
+
flexFromSize,
|
|
22392
22517
|
styleCSSVars,
|
|
22393
22518
|
pseudoState: innerPseudoState,
|
|
22394
22519
|
pseudoClasses,
|
|
@@ -37839,7 +37964,8 @@ const useUIStateController = (
|
|
|
37839
37964
|
}
|
|
37840
37965
|
if (
|
|
37841
37966
|
e.type === "facade_propagate_up" ||
|
|
37842
|
-
e.type === "cancel_rollback"
|
|
37967
|
+
e.type === "cancel_rollback" ||
|
|
37968
|
+
e.type === "auto_fix"
|
|
37843
37969
|
) {
|
|
37844
37970
|
// Exception: when the facade propagates a child state change up to the
|
|
37845
37971
|
// real picker input, also notify the parent group (e.g. Form) so it
|
|
@@ -37849,6 +37975,9 @@ const useUIStateController = (
|
|
|
37849
37975
|
// A cancel takes the same road back: the Form was told what the
|
|
37850
37976
|
// popup was picking, so it has to be told the picker went back to
|
|
37851
37977
|
// where it opened, or it sends a value the user said no to.
|
|
37978
|
+
// A correction is the same story once more: the Form sends what
|
|
37979
|
+
// its fields add up to, and a field that just put its own value
|
|
37980
|
+
// right has to be counted for the corrected one.
|
|
37852
37981
|
s.parentUIStateController?.onChildUIAction(controller, e, {
|
|
37853
37982
|
stateChanged: true,
|
|
37854
37983
|
});
|
|
@@ -39743,6 +39872,11 @@ const INTERNAL_EVENT_SET = new Set([
|
|
|
39743
39872
|
// notification below still happen, exactly as they did on the way in (see
|
|
39744
39873
|
// picker_custom.jsx's onClose).
|
|
39745
39874
|
"cancel_rollback",
|
|
39875
|
+
// A constraint allowed to correct the value put it right as the value was
|
|
39876
|
+
// committed (see applyAutoFix). Nobody pressed anything, so no command and
|
|
39877
|
+
// no action of the control's own — but what it holds really did move, so
|
|
39878
|
+
// uiAction, the bound signal and the parent notification below all happen.
|
|
39879
|
+
"auto_fix",
|
|
39746
39880
|
]);
|
|
39747
39881
|
const isInternalEvent = (e) => {
|
|
39748
39882
|
return INTERNAL_EVENT_SET.has(e.type);
|
|
@@ -40823,6 +40957,24 @@ const useControlProps = (props, {
|
|
|
40823
40957
|
syncDomState(readControlValue(el), e);
|
|
40824
40958
|
};
|
|
40825
40959
|
}
|
|
40960
|
+
// Leaving the field is one of the two moments a value is committed — the
|
|
40961
|
+
// other being an action about to read it (see applyAutoFix). A constraint
|
|
40962
|
+
// allowed to correct the value puts it right here, so what the field
|
|
40963
|
+
// shows, what the counter counts and what a submit would send are one
|
|
40964
|
+
// thing well before the submit.
|
|
40965
|
+
if (controlType === "input") {
|
|
40966
|
+
const onBlurFromProps = controlHostProps.onBlur;
|
|
40967
|
+
controlHostProps.onBlur = e => {
|
|
40968
|
+
onBlurFromProps?.(e);
|
|
40969
|
+
const validation = uiStateController.rules.validation;
|
|
40970
|
+
if (validation.applyAutoFix(e)) {
|
|
40971
|
+
// The value moved without anyone typing: what the constraints had to
|
|
40972
|
+
// say about the old one is out of date, and so is what the controls
|
|
40973
|
+
// above read from this one.
|
|
40974
|
+
validation.syncValidity(e);
|
|
40975
|
+
}
|
|
40976
|
+
};
|
|
40977
|
+
}
|
|
40826
40978
|
}
|
|
40827
40979
|
const uiState = uiStateController.uiStateSignal.peek();
|
|
40828
40980
|
const domProps = toDomProps(uiState);
|
|
@@ -81034,8 +81186,10 @@ const css$l = /* css */`.navi_time_range_label {
|
|
|
81034
81186
|
}
|
|
81035
81187
|
`;
|
|
81036
81188
|
const HOUR_COUNT = 24;
|
|
81189
|
+
const END_OF_DAY_HOUR = 24;
|
|
81037
81190
|
const MINUTES_PER_HOUR = 60;
|
|
81038
81191
|
const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
|
|
81192
|
+
const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
81039
81193
|
|
|
81040
81194
|
/**
|
|
81041
81195
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
@@ -81055,16 +81209,21 @@ const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
|
|
|
81055
81209
|
* }>}
|
|
81056
81210
|
* @param {string} [value] The time shown, as "HH:MM".
|
|
81057
81211
|
* @param {number} [minuteStep=1] How many minutes apart the values on the
|
|
81058
|
-
* minute wheel are — 15 for quarters of an hour.
|
|
81212
|
+
* minute wheel are — 15 for quarters of an hour. At 60 the time is a whole
|
|
81213
|
+
* hour: there is no minute wheel at all, the value stays "HH:MM" ("08:00"),
|
|
81214
|
+
* and a value arriving with minutes is shown on its hour.
|
|
81059
81215
|
* @param {{min?: number, max?: number}|number[]} [hours] Which hours the wheel
|
|
81060
81216
|
* offers: `{ min: 7, max: 21 }` for a day that starts and ends somewhere, or
|
|
81061
|
-
* the list itself.
|
|
81062
|
-
* in the way.
|
|
81217
|
+
* the list itself. 0 to 23 by default. Rows nobody will ever land on are rows
|
|
81218
|
+
* in the way. 24 is the end of the day ("24:00", midnight at the end of a
|
|
81219
|
+
* span) and has no minutes: turned onto it, the minute wheel goes back to 0.
|
|
81063
81220
|
* @param {boolean} [loop=true] The wheels go round: 23h then 0h, 59 minutes
|
|
81064
81221
|
* then 0. What a clock does. Say `loop={false}` for two ends one cannot turn
|
|
81065
81222
|
* past.
|
|
81066
81223
|
* @param {import("ignore:preact").ComponentChildren} [separator] What is written
|
|
81067
|
-
* between the hours and the minutes. "h" in French, ":" elsewhere.
|
|
81224
|
+
* between the hours and the minutes. "h" in French, ":" elsewhere. In a time
|
|
81225
|
+
* of whole hours it follows each hour on its row instead ("8h"), and nothing
|
|
81226
|
+
* is written by default outside French.
|
|
81068
81227
|
* @param {string} [placeholder] What the wheels show while the time holds
|
|
81069
81228
|
* nothing, as "HH:MM". Wheels have no blank row to land on, so their
|
|
81070
81229
|
* placeholder is a position rather than a grey word — shown, but not an
|
|
@@ -81079,13 +81238,20 @@ const TimeWheel = ({
|
|
|
81079
81238
|
hours,
|
|
81080
81239
|
loop = true,
|
|
81081
81240
|
placeholder,
|
|
81082
|
-
separator
|
|
81241
|
+
separator,
|
|
81083
81242
|
hourLabel = naviI18n("time.hour_label"),
|
|
81084
81243
|
minuteLabel = naviI18n("time.minute_label"),
|
|
81085
81244
|
size,
|
|
81086
81245
|
wheelProps,
|
|
81246
|
+
onnavi_wheel_settle,
|
|
81087
81247
|
...rest
|
|
81088
81248
|
}) => {
|
|
81249
|
+
const wholeHours = minuteStep >= MINUTES_PER_HOUR;
|
|
81250
|
+
if (separator === undefined) {
|
|
81251
|
+
separator = naviI18n(wholeHours ? "time.hour_suffix" : "time.hour_separator");
|
|
81252
|
+
}
|
|
81253
|
+
const hourWheelRef = useRef(null);
|
|
81254
|
+
const minuteWheelRef = useRef(null);
|
|
81089
81255
|
const minutes = useMemo(() => {
|
|
81090
81256
|
const minuteList = [];
|
|
81091
81257
|
let minute = 0;
|
|
@@ -81099,13 +81265,42 @@ const TimeWheel = ({
|
|
|
81099
81265
|
const {
|
|
81100
81266
|
aggregateChildStates,
|
|
81101
81267
|
distributeChildUIState
|
|
81102
|
-
} = useAnswered(placeholder, rest, aggregateTime, distributeTime);
|
|
81268
|
+
} = useAnswered(placeholder, rest, aggregateTime, (groupState, child) => distributeTime(groupState, child, minuteStep));
|
|
81103
81269
|
const placeholderParts = parseTimeParts(placeholder);
|
|
81270
|
+
|
|
81271
|
+
// The end of the day has no minutes. The time already reads "24:00" whatever
|
|
81272
|
+
// the minute wheel shows (see aggregateTime); on settle the wheel is brought
|
|
81273
|
+
// back to 0 so what is drawn is what is held.
|
|
81274
|
+
const endOfDayHasNoMinutes = e => {
|
|
81275
|
+
const hourEl = hourWheelRef.current;
|
|
81276
|
+
const minuteEl = minuteWheelRef.current;
|
|
81277
|
+
if (!hourEl || !minuteEl) {
|
|
81278
|
+
return;
|
|
81279
|
+
}
|
|
81280
|
+
if (getUIStateFromElement(hourEl) !== END_OF_DAY_HOUR) {
|
|
81281
|
+
return;
|
|
81282
|
+
}
|
|
81283
|
+
if (getUIStateFromElement(minuteEl) === 0) {
|
|
81284
|
+
return;
|
|
81285
|
+
}
|
|
81286
|
+
dispatchRequestSetUIState(minuteEl, 0, {
|
|
81287
|
+
event: e
|
|
81288
|
+
});
|
|
81289
|
+
};
|
|
81104
81290
|
return jsxs(WheelGroup, {
|
|
81105
81291
|
aggregateChildStates: aggregateChildStates,
|
|
81106
81292
|
distributeChildUIState: distributeChildUIState,
|
|
81293
|
+
onnavi_wheel_settle: e => {
|
|
81294
|
+
if (!wholeHours) {
|
|
81295
|
+
endOfDayHasNoMinutes(e);
|
|
81296
|
+
}
|
|
81297
|
+
if (onnavi_wheel_settle) {
|
|
81298
|
+
onnavi_wheel_settle(e);
|
|
81299
|
+
}
|
|
81300
|
+
},
|
|
81107
81301
|
...rest,
|
|
81108
81302
|
children: [jsx(Wheel, {
|
|
81303
|
+
ref: hourWheelRef,
|
|
81109
81304
|
name: "hour",
|
|
81110
81305
|
type: "integer",
|
|
81111
81306
|
bounded: !loop,
|
|
@@ -81116,24 +81311,29 @@ const TimeWheel = ({
|
|
|
81116
81311
|
children: hourList.map(hour => jsx(Wheel.Item, {
|
|
81117
81312
|
value: hour,
|
|
81118
81313
|
paddingX: "s",
|
|
81119
|
-
children:
|
|
81314
|
+
children: wholeHours ? jsxs(Fragment, {
|
|
81315
|
+
children: [hour, separator]
|
|
81316
|
+
}) : padTwo(hour)
|
|
81120
81317
|
}, hour))
|
|
81121
|
-
}),
|
|
81122
|
-
|
|
81123
|
-
|
|
81124
|
-
|
|
81125
|
-
|
|
81126
|
-
|
|
81127
|
-
|
|
81128
|
-
|
|
81129
|
-
|
|
81130
|
-
|
|
81131
|
-
|
|
81132
|
-
|
|
81133
|
-
|
|
81134
|
-
|
|
81135
|
-
|
|
81136
|
-
|
|
81318
|
+
}), wholeHours ? null : jsxs(Fragment, {
|
|
81319
|
+
children: [jsx(WheelGroup.Separator, {
|
|
81320
|
+
size: size,
|
|
81321
|
+
children: separator
|
|
81322
|
+
}), jsx(Wheel, {
|
|
81323
|
+
ref: minuteWheelRef,
|
|
81324
|
+
name: "minute",
|
|
81325
|
+
type: "integer",
|
|
81326
|
+
bounded: !loop,
|
|
81327
|
+
size: size,
|
|
81328
|
+
"aria-label": minuteLabel,
|
|
81329
|
+
defaultValue: placeholderParts ? floorToStep(placeholderParts.minute, minuteStep) : undefined,
|
|
81330
|
+
...wheelProps,
|
|
81331
|
+
children: minutes.map(minute => jsx(Wheel.Item, {
|
|
81332
|
+
value: minute,
|
|
81333
|
+
paddingX: "s",
|
|
81334
|
+
children: padTwo(minute)
|
|
81335
|
+
}, minute))
|
|
81336
|
+
})]
|
|
81137
81337
|
})]
|
|
81138
81338
|
});
|
|
81139
81339
|
};
|
|
@@ -81167,11 +81367,12 @@ const TimeWheel = ({
|
|
|
81167
81367
|
* prepositions belong there; the group is still a row, and takes `flexWrap`
|
|
81168
81368
|
* for screens too narrow to hold both columns.
|
|
81169
81369
|
* @param {number} [minuteStep=1] How many minutes apart the values on both
|
|
81170
|
-
* minute wheels are.
|
|
81370
|
+
* minute wheels are. 60 for a span of whole hours ("de 8h à 12h").
|
|
81171
81371
|
* @param {{min?: number, max?: number}|number[]} [hours] Which hours both
|
|
81172
|
-
* wheels offer — see `TimeWheel`.
|
|
81372
|
+
* wheels offer — see `TimeWheel`. A 24 is offered to the end alone: a span
|
|
81373
|
+
* can run until midnight ("24:00"), it cannot start there.
|
|
81173
81374
|
* @param {number} [minDuration=0] How long the span must last at least, in
|
|
81174
|
-
* minutes. Zero by default: a span of no length is a span all the same, only
|
|
81375
|
+
* minutes, rounded up to the step — the wheels have nothing in between. Zero by default: a span of no length is a span all the same, only
|
|
81175
81376
|
* one that goes backwards is not. It is what the bounds keep between them as
|
|
81176
81377
|
* they turn — turn the start into the end and the end moves along, keeping
|
|
81177
81378
|
* that much room.
|
|
@@ -81204,6 +81405,12 @@ const TimeRangeWheel = ({
|
|
|
81204
81405
|
const startId = useId();
|
|
81205
81406
|
const startRef = useRef(null);
|
|
81206
81407
|
const endRef = useRef(null);
|
|
81408
|
+
const endHourList = useMemo(() => resolveHourList(hours), [hours ? hours.min : undefined, hours ? hours.max : undefined, hours]);
|
|
81409
|
+
const startHourList = useMemo(() => endHourList.filter(hour => hour !== END_OF_DAY_HOUR), [endHourList]);
|
|
81410
|
+
const step = minuteStep >= MINUTES_PER_HOUR ? MINUTES_PER_HOUR : minuteStep;
|
|
81411
|
+
const minGap = ceilToStep(minDuration, step);
|
|
81412
|
+
// The latest the end can be pushed to: the last time its wheels can show.
|
|
81413
|
+
const lastEnd = endHourList.includes(END_OF_DAY_HOUR) ? END_OF_DAY : floorToStep(LAST_MINUTE_OF_DAY, step);
|
|
81207
81414
|
// One turn settles the whole span: a start somebody chose makes the end an
|
|
81208
81415
|
// answer too, left where the placeholder put it.
|
|
81209
81416
|
const {
|
|
@@ -81235,18 +81442,18 @@ const TimeRangeWheel = ({
|
|
|
81235
81442
|
return;
|
|
81236
81443
|
}
|
|
81237
81444
|
const duration = movedSide === "start" ? otherMinutes - movedMinutes : movedMinutes - otherMinutes;
|
|
81238
|
-
if (duration >=
|
|
81445
|
+
if (duration >= minGap) {
|
|
81239
81446
|
return;
|
|
81240
81447
|
}
|
|
81241
|
-
let pushedMinutes = movedSide === "start" ? movedMinutes +
|
|
81448
|
+
let pushedMinutes = movedSide === "start" ? movedMinutes + minGap : movedMinutes - minGap;
|
|
81242
81449
|
// The day has ends the wheels do not: pushed past midnight, the other bound
|
|
81243
81450
|
// would come back round on the wrong side of the one that pushed it. It
|
|
81244
81451
|
// stops at the edge instead, and the span that no longer fits is what the
|
|
81245
81452
|
// send-time constraint is there to say (see time_range_constraint.js).
|
|
81246
81453
|
if (pushedMinutes < 0) {
|
|
81247
81454
|
pushedMinutes = 0;
|
|
81248
|
-
} else if (pushedMinutes >
|
|
81249
|
-
pushedMinutes =
|
|
81455
|
+
} else if (pushedMinutes > lastEnd) {
|
|
81456
|
+
pushedMinutes = lastEnd;
|
|
81250
81457
|
}
|
|
81251
81458
|
dispatchRequestSetUIState(otherEl, timeFromMinutes(pushedMinutes), {
|
|
81252
81459
|
event: e
|
|
@@ -81274,7 +81481,7 @@ const TimeRangeWheel = ({
|
|
|
81274
81481
|
ref: startRef,
|
|
81275
81482
|
name: "start",
|
|
81276
81483
|
minuteStep: minuteStep,
|
|
81277
|
-
hours:
|
|
81484
|
+
hours: startHourList,
|
|
81278
81485
|
loop: loop,
|
|
81279
81486
|
size: size,
|
|
81280
81487
|
placeholder: placeholder ? placeholder.start : undefined
|
|
@@ -81299,7 +81506,7 @@ const TimeRangeWheel = ({
|
|
|
81299
81506
|
ref: endRef,
|
|
81300
81507
|
name: "end",
|
|
81301
81508
|
minuteStep: minuteStep,
|
|
81302
|
-
hours:
|
|
81509
|
+
hours: endHourList,
|
|
81303
81510
|
loop: loop,
|
|
81304
81511
|
size: size,
|
|
81305
81512
|
placeholder: placeholder ? placeholder.end : undefined,
|
|
@@ -81311,7 +81518,7 @@ const TimeRangeWheel = ({
|
|
|
81311
81518
|
// the time one would have to move is (see time_range_constraint.js).
|
|
81312
81519
|
,
|
|
81313
81520
|
"data-time-after": startId,
|
|
81314
|
-
"data-time-min-duration":
|
|
81521
|
+
"data-time-min-duration": minGap,
|
|
81315
81522
|
...timeProps,
|
|
81316
81523
|
...endTimeProps
|
|
81317
81524
|
})
|
|
@@ -81473,10 +81680,11 @@ const distributeSpan = (groupState, childUIStateController) => {
|
|
|
81473
81680
|
return groupState[childUIStateController.name];
|
|
81474
81681
|
};
|
|
81475
81682
|
|
|
81476
|
-
// The
|
|
81683
|
+
// The wheels as one value, "HH:MM". A time of whole hours has no minute wheel
|
|
81684
|
+
// and is on the hour; so is the end of the day, whatever the minute wheel says.
|
|
81477
81685
|
const aggregateTime = childUIStateControllers => {
|
|
81478
81686
|
let hour = "";
|
|
81479
|
-
let minute =
|
|
81687
|
+
let minute = 0;
|
|
81480
81688
|
for (const child of childUIStateControllers) {
|
|
81481
81689
|
if (child.name === "hour") {
|
|
81482
81690
|
hour = child.uiState ?? "";
|
|
@@ -81485,18 +81693,27 @@ const aggregateTime = childUIStateControllers => {
|
|
|
81485
81693
|
minute = child.uiState ?? "";
|
|
81486
81694
|
}
|
|
81487
81695
|
}
|
|
81696
|
+
if (hour === END_OF_DAY_HOUR) {
|
|
81697
|
+
return formatTimeParts(hour, 0);
|
|
81698
|
+
}
|
|
81488
81699
|
return formatTimeParts(hour, minute);
|
|
81489
81700
|
};
|
|
81490
81701
|
|
|
81491
81702
|
// The way back: what the group is set to (a value given to it, a form being
|
|
81492
|
-
// reset, the other bound pushing it) lands on the wheel it belongs to.
|
|
81493
|
-
|
|
81703
|
+
// reset, the other bound pushing it) lands on the wheel it belongs to. Minutes
|
|
81704
|
+
// the wheel does not offer land on the one before.
|
|
81705
|
+
const distributeTime = (groupState, childUIStateController, minuteStep) => {
|
|
81494
81706
|
const parts = parseTimeParts(groupState);
|
|
81495
81707
|
if (!parts) {
|
|
81496
81708
|
return undefined;
|
|
81497
81709
|
}
|
|
81710
|
+
if (childUIStateController.name === "minute") {
|
|
81711
|
+
return floorToStep(parts.minute, minuteStep);
|
|
81712
|
+
}
|
|
81498
81713
|
return parts[childUIStateController.name];
|
|
81499
81714
|
};
|
|
81715
|
+
const floorToStep = (minutes, step) => Math.floor(minutes / step) * step;
|
|
81716
|
+
const ceilToStep = (minutes, step) => Math.ceil(minutes / step) * step;
|
|
81500
81717
|
|
|
81501
81718
|
const TableSelectionContext = createContext();
|
|
81502
81719
|
const useTableSelectionContextValue = (
|