@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/jsenv_navi.js
CHANGED
|
@@ -636,6 +636,11 @@ naviI18n.addAll({
|
|
|
636
636
|
en: ":",
|
|
637
637
|
fr: "h",
|
|
638
638
|
},
|
|
639
|
+
// A time of whole hours has nothing to stand between: what follows the hour.
|
|
640
|
+
"time.hour_suffix": {
|
|
641
|
+
en: "",
|
|
642
|
+
fr: "h",
|
|
643
|
+
},
|
|
639
644
|
"time.hour_label": {
|
|
640
645
|
en: "Hours",
|
|
641
646
|
fr: "Heures",
|
|
@@ -4708,14 +4713,12 @@ const minutesFromTime$1 = (time) => {
|
|
|
4708
4713
|
return parts.hour * 60 + parts.minute;
|
|
4709
4714
|
};
|
|
4710
4715
|
|
|
4716
|
+
// Not folded back into the day: 1440 is "24:00", the end of the day a span can
|
|
4717
|
+
// run into.
|
|
4711
4718
|
const timeFromMinutes = (minutes) => {
|
|
4712
|
-
|
|
4713
|
-
((minutes % MINUTES_PER_DAY) + MINUTES_PER_DAY) % MINUTES_PER_DAY;
|
|
4714
|
-
return `${padTwo$1(Math.floor(inDay / 60))}:${padTwo$1(inDay % 60)}`;
|
|
4719
|
+
return `${padTwo$1(Math.floor(minutes / 60))}:${padTwo$1(minutes % 60)}`;
|
|
4715
4720
|
};
|
|
4716
4721
|
|
|
4717
|
-
const MINUTES_PER_DAY = 24 * 60;
|
|
4718
|
-
|
|
4719
4722
|
const padTwo$1 = (value) => String(value).padStart(2, "0");
|
|
4720
4723
|
|
|
4721
4724
|
// Maps validity type names → navi input type names.
|
|
@@ -5551,6 +5554,14 @@ const constraintAttributeFromProp = (key) => {
|
|
|
5551
5554
|
const isConstraintAttributeOn = (value) =>
|
|
5552
5555
|
value !== undefined && value !== null && value !== false;
|
|
5553
5556
|
|
|
5557
|
+
/**
|
|
5558
|
+
* Whether a constraint attribute asks for the value to be CORRECTED rather
|
|
5559
|
+
* than refused: `singleSpace="autoFix"`. Only constraints whose rule knows a
|
|
5560
|
+
* correction offer it, and only the fields that write it get one — a
|
|
5561
|
+
* `maxLength` silently truncating what someone wrote would be a bad default.
|
|
5562
|
+
*/
|
|
5563
|
+
const isConstraintAttributeAutoFix = (value) => value === "autoFix";
|
|
5564
|
+
|
|
5554
5565
|
const CONSTRAINT_NAME_TO_PROP = {
|
|
5555
5566
|
disabled: "disabledMessage",
|
|
5556
5567
|
required: "requiredMessage",
|
|
@@ -10298,14 +10309,13 @@ const tryActionAfterInteractionAllowed = (
|
|
|
10298
10309
|
|
|
10299
10310
|
// Resolve proxy so navi_action_* fires on the real control element.
|
|
10300
10311
|
let elementForAction = controlHost;
|
|
10301
|
-
let
|
|
10312
|
+
let activeController = controller;
|
|
10302
10313
|
if (controller) {
|
|
10303
10314
|
const proxyTargetController = findControlProxyTargetController(controller);
|
|
10304
10315
|
if (proxyTargetController) {
|
|
10305
10316
|
elementForAction = proxyTargetController.ref.current;
|
|
10317
|
+
activeController = proxyTargetController;
|
|
10306
10318
|
}
|
|
10307
|
-
const activeController = proxyTargetController ?? controller;
|
|
10308
|
-
uiState = activeController?.uiState;
|
|
10309
10319
|
}
|
|
10310
10320
|
|
|
10311
10321
|
// Validity gate: re-check (handles autoResetOnAction side effects), then read
|
|
@@ -10332,6 +10342,11 @@ const tryActionAfterInteractionAllowed = (
|
|
|
10332
10342
|
}
|
|
10333
10343
|
}
|
|
10334
10344
|
|
|
10345
|
+
// Read after the gate, never before: a constraint allowed to correct the
|
|
10346
|
+
// value rewrites it in there (see applyAutoFix), and what goes out has to be
|
|
10347
|
+
// what the control ends up holding.
|
|
10348
|
+
const uiState = activeController?.uiState;
|
|
10349
|
+
|
|
10335
10350
|
if (action === "auto" || action?.isAction) {
|
|
10336
10351
|
// A control that commits gets the last word on whether this particular
|
|
10337
10352
|
// value is worth acting on — see Form's own `shouldRequestAction`, which
|
|
@@ -12642,9 +12657,22 @@ CONSTRAINT_ATTRIBUTE_SET.add("data-same-as");
|
|
|
12642
12657
|
* `data-single-space` — no leading or trailing space, never two in a row.
|
|
12643
12658
|
* The rule itself is @jsenv/validity's SINGLE_SPACE_RULE, so a server checking
|
|
12644
12659
|
* the value again refuses it for the same reason and in the same words.
|
|
12660
|
+
*
|
|
12661
|
+
* `data-single-space="autoFix"` corrects the value instead of refusing it. A
|
|
12662
|
+
* text field ends with a space more often than anyone means it to — a word
|
|
12663
|
+
* then a pause, a mobile keyboard after a suggestion, a paste — and the person
|
|
12664
|
+
* is then asked to find and delete a character they cannot see. The correction
|
|
12665
|
+
* is the rule's own, so the value a server re-checks with `singleSpace: true`
|
|
12666
|
+
* is one it accepts.
|
|
12645
12667
|
*/
|
|
12646
12668
|
|
|
12647
12669
|
|
|
12670
|
+
const applyRule = (field) => {
|
|
12671
|
+
const valueAsString =
|
|
12672
|
+
field.uiState === undefined ? "" : String(field.uiState);
|
|
12673
|
+
return SINGLE_SPACE_RULE.applyOn(true, valueAsString);
|
|
12674
|
+
};
|
|
12675
|
+
|
|
12648
12676
|
const SINGLE_SPACE_CONSTRAINT = {
|
|
12649
12677
|
name: "single_space",
|
|
12650
12678
|
messageAttribute: "data-single-space-message",
|
|
@@ -12653,14 +12681,29 @@ const SINGLE_SPACE_CONSTRAINT = {
|
|
|
12653
12681
|
if (!isConstraintAttributeOn(singleSpace)) {
|
|
12654
12682
|
return null;
|
|
12655
12683
|
}
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12684
|
+
if (isConstraintAttributeAutoFix(singleSpace)) {
|
|
12685
|
+
// The correction the rule knows always lands on a value the rule
|
|
12686
|
+
// accepts, and it runs on every commit — so there is nothing left for
|
|
12687
|
+
// the person to do about this, and nothing to say to them.
|
|
12688
|
+
return null;
|
|
12689
|
+
}
|
|
12690
|
+
const result = applyRule(field);
|
|
12659
12691
|
if (!result) {
|
|
12660
12692
|
return null;
|
|
12661
12693
|
}
|
|
12662
12694
|
return naviI18nFromValidityMessage(result);
|
|
12663
12695
|
},
|
|
12696
|
+
autoFix: (field) => {
|
|
12697
|
+
const singleSpace = field.controlHostProps["data-single-space"];
|
|
12698
|
+
if (!isConstraintAttributeAutoFix(singleSpace)) {
|
|
12699
|
+
return null;
|
|
12700
|
+
}
|
|
12701
|
+
const result = applyRule(field);
|
|
12702
|
+
if (!result) {
|
|
12703
|
+
return null;
|
|
12704
|
+
}
|
|
12705
|
+
return result.autoFix();
|
|
12706
|
+
},
|
|
12664
12707
|
};
|
|
12665
12708
|
CONSTRAINT_ATTRIBUTE_SET.add("data-single-space");
|
|
12666
12709
|
|
|
@@ -12852,6 +12895,74 @@ const createControlValidation = (
|
|
|
12852
12895
|
const getConstraintValidityState = () => constraintValidityState;
|
|
12853
12896
|
controlValidity.getConstraintValidityState = getConstraintValidityState;
|
|
12854
12897
|
|
|
12898
|
+
const getConstraintSet = () => {
|
|
12899
|
+
const constraintSet = new Set([
|
|
12900
|
+
...DEFAULT_CONSTRAINT_SET,
|
|
12901
|
+
...dynamicConstraintSet,
|
|
12902
|
+
]);
|
|
12903
|
+
// An app constraint declared at the call site: `constraints={[MY_CONSTRAINT]}`.
|
|
12904
|
+
// Read from the raw props on every check so a constraint whose parameters
|
|
12905
|
+
// are closed over is re-created freely, and last so the constraints navi
|
|
12906
|
+
// ships are the ones reported first (see pickConstraintFailureInfo).
|
|
12907
|
+
const constraintsFromProps = controller.props.constraints;
|
|
12908
|
+
if (constraintsFromProps) {
|
|
12909
|
+
for (const constraintFromProps of constraintsFromProps) {
|
|
12910
|
+
constraintSet.add(normalizeConstraint(constraintFromProps));
|
|
12911
|
+
}
|
|
12912
|
+
}
|
|
12913
|
+
return constraintSet;
|
|
12914
|
+
};
|
|
12915
|
+
|
|
12916
|
+
/**
|
|
12917
|
+
* Lets the constraints allowed to correct the value do so, and puts what
|
|
12918
|
+
* they return in the control — ui state, bound signal, the field itself.
|
|
12919
|
+
*
|
|
12920
|
+
* Called at the moments a value is COMMITTED: the field is left, an action
|
|
12921
|
+
* is about to read it. Never while it is being typed into, where removing
|
|
12922
|
+
* the trailing space would eat the one the person is about to follow with a
|
|
12923
|
+
* word.
|
|
12924
|
+
*
|
|
12925
|
+
* Returns whether the value moved, so a caller outside the validity pass can
|
|
12926
|
+
* re-read what the control is now worth.
|
|
12927
|
+
*/
|
|
12928
|
+
const applyAutoFix = (event) => {
|
|
12929
|
+
const proxyTargetController = findControlProxyTargetController(controller);
|
|
12930
|
+
if (proxyTargetController) {
|
|
12931
|
+
return proxyTargetController.rules.validation.applyAutoFix(event);
|
|
12932
|
+
}
|
|
12933
|
+
// A value nobody can edit is not navi's to rewrite: it is the app's, and
|
|
12934
|
+
// correcting it would change what gets sent behind the app's back.
|
|
12935
|
+
const controlHostProps = controller.controlHostProps;
|
|
12936
|
+
if (
|
|
12937
|
+
controlHostProps.disabled ||
|
|
12938
|
+
controlHostProps.readOnly ||
|
|
12939
|
+
controlHostProps["aria-readonly"] === "true"
|
|
12940
|
+
) {
|
|
12941
|
+
return false;
|
|
12942
|
+
}
|
|
12943
|
+
let fixed = false;
|
|
12944
|
+
for (const constraint of getConstraintSet()) {
|
|
12945
|
+
if (!constraint.autoFix) {
|
|
12946
|
+
continue;
|
|
12947
|
+
}
|
|
12948
|
+
const fixedValue = constraint.autoFix(controller);
|
|
12949
|
+
if (fixedValue === null || fixedValue === undefined) {
|
|
12950
|
+
continue;
|
|
12951
|
+
}
|
|
12952
|
+
if (compareTwoJsValues(fixedValue, controller.uiState)) {
|
|
12953
|
+
continue;
|
|
12954
|
+
}
|
|
12955
|
+
const autoFixEvent = new CustomEvent("auto_fix", {
|
|
12956
|
+
detail: { constraint: constraint.name },
|
|
12957
|
+
});
|
|
12958
|
+
chainEvent(autoFixEvent, event);
|
|
12959
|
+
controller.setUIState(fixedValue, autoFixEvent);
|
|
12960
|
+
fixed = true;
|
|
12961
|
+
}
|
|
12962
|
+
return fixed;
|
|
12963
|
+
};
|
|
12964
|
+
controlValidity.applyAutoFix = applyAutoFix;
|
|
12965
|
+
|
|
12855
12966
|
const checkValidity = ({
|
|
12856
12967
|
event,
|
|
12857
12968
|
requester = controller.ref.current,
|
|
@@ -12892,21 +13003,15 @@ const createControlValidation = (
|
|
|
12892
13003
|
}
|
|
12893
13004
|
}
|
|
12894
13005
|
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
// An app constraint declared at the call site: `constraints={[MY_CONSTRAINT]}`.
|
|
12901
|
-
// Read from the raw props on every check so a constraint whose parameters
|
|
12902
|
-
// are closed over is re-created freely, and last so the constraints navi
|
|
12903
|
-
// ships are the ones reported first (see pickConstraintFailureInfo).
|
|
12904
|
-
const constraintsFromProps = controller.props.constraints;
|
|
12905
|
-
if (constraintsFromProps) {
|
|
12906
|
-
for (const constraintFromProps of constraintsFromProps) {
|
|
12907
|
-
constraintSet.add(normalizeConstraint(constraintFromProps));
|
|
12908
|
-
}
|
|
13006
|
+
if (fromRequestAction) {
|
|
13007
|
+
// The value is about to be read and sent: whoever may correct it gets
|
|
13008
|
+
// the last word before the constraints judge it, so a field is never
|
|
13009
|
+
// refused for something navi knows how to put right.
|
|
13010
|
+
applyAutoFix(event);
|
|
12909
13011
|
}
|
|
13012
|
+
|
|
13013
|
+
let newConstraintValidityState = { valid: true };
|
|
13014
|
+
const constraintSet = getConstraintSet();
|
|
12910
13015
|
const elementSig = getElementSignature(controller.ref.current);
|
|
12911
13016
|
// Not logged: every control checks its constraints on every interaction and
|
|
12912
13017
|
// almost always passes, so this line alone was most of the debug output —
|
|
@@ -19076,7 +19181,22 @@ const TYPO_PROPS = {
|
|
|
19076
19181
|
}
|
|
19077
19182
|
return lineClampStyles(value);
|
|
19078
19183
|
},
|
|
19079
|
-
|
|
19184
|
+
/* A sized inline Box (a Text with a width) is a flex row the caller did not
|
|
19185
|
+
ask for, where the text runs are one anonymous item sized to their content:
|
|
19186
|
+
text-align alone moves nothing, the item is placed by justify-content. A
|
|
19187
|
+
flex row asked for keeps the two apart (Picker: textAlign is the text in
|
|
19188
|
+
the value slot, justify-content the slots). alignX, when given, wins. */
|
|
19189
|
+
textAlign: (value, { flexFromSize, remainingProps }) => {
|
|
19190
|
+
if (
|
|
19191
|
+
flexFromSize &&
|
|
19192
|
+
value !== "justify" &&
|
|
19193
|
+
remainingProps.alignX === undefined &&
|
|
19194
|
+
remainingProps.align === undefined
|
|
19195
|
+
) {
|
|
19196
|
+
return { textAlign: value, justifyContent: value };
|
|
19197
|
+
}
|
|
19198
|
+
return { textAlign: value };
|
|
19199
|
+
},
|
|
19080
19200
|
textBox: PASS_THROUGH,
|
|
19081
19201
|
textBoxTrim: PASS_THROUGH,
|
|
19082
19202
|
textBoxEdge: PASS_THROUGH,
|
|
@@ -21654,8 +21774,12 @@ const computeBox = (props, parentBoxFlow) => {
|
|
|
21654
21774
|
block = true;
|
|
21655
21775
|
}
|
|
21656
21776
|
}
|
|
21777
|
+
// An inline box ignores width/height, so a sized one becomes a flex row the
|
|
21778
|
+
// caller never asked for; textAlign reads that (see box_style_util.js).
|
|
21779
|
+
let flexFromSize = false;
|
|
21657
21780
|
if (inline && (rest.width !== undefined || rest.height !== undefined) && flex === undefined) {
|
|
21658
21781
|
flex = "x";
|
|
21782
|
+
flexFromSize = true;
|
|
21659
21783
|
}
|
|
21660
21784
|
let boxFlow;
|
|
21661
21785
|
if (inline) {
|
|
@@ -21749,6 +21873,7 @@ const computeBox = (props, parentBoxFlow) => {
|
|
|
21749
21873
|
const styleContext = {
|
|
21750
21874
|
parentBoxFlow,
|
|
21751
21875
|
boxFlow,
|
|
21876
|
+
flexFromSize,
|
|
21752
21877
|
styleCSSVars,
|
|
21753
21878
|
pseudoState: innerPseudoState,
|
|
21754
21879
|
pseudoClasses,
|
|
@@ -36887,7 +37012,8 @@ const useUIStateController = (
|
|
|
36887
37012
|
}
|
|
36888
37013
|
if (
|
|
36889
37014
|
e.type === "facade_propagate_up" ||
|
|
36890
|
-
e.type === "cancel_rollback"
|
|
37015
|
+
e.type === "cancel_rollback" ||
|
|
37016
|
+
e.type === "auto_fix"
|
|
36891
37017
|
) {
|
|
36892
37018
|
// Exception: when the facade propagates a child state change up to the
|
|
36893
37019
|
// real picker input, also notify the parent group (e.g. Form) so it
|
|
@@ -36897,6 +37023,9 @@ const useUIStateController = (
|
|
|
36897
37023
|
// A cancel takes the same road back: the Form was told what the
|
|
36898
37024
|
// popup was picking, so it has to be told the picker went back to
|
|
36899
37025
|
// where it opened, or it sends a value the user said no to.
|
|
37026
|
+
// A correction is the same story once more: the Form sends what
|
|
37027
|
+
// its fields add up to, and a field that just put its own value
|
|
37028
|
+
// right has to be counted for the corrected one.
|
|
36900
37029
|
s.parentUIStateController?.onChildUIAction(controller, e, {
|
|
36901
37030
|
stateChanged: true,
|
|
36902
37031
|
});
|
|
@@ -38738,6 +38867,11 @@ const INTERNAL_EVENT_SET = new Set([
|
|
|
38738
38867
|
// notification below still happen, exactly as they did on the way in (see
|
|
38739
38868
|
// picker_custom.jsx's onClose).
|
|
38740
38869
|
"cancel_rollback",
|
|
38870
|
+
// A constraint allowed to correct the value put it right as the value was
|
|
38871
|
+
// committed (see applyAutoFix). Nobody pressed anything, so no command and
|
|
38872
|
+
// no action of the control's own — but what it holds really did move, so
|
|
38873
|
+
// uiAction, the bound signal and the parent notification below all happen.
|
|
38874
|
+
"auto_fix",
|
|
38741
38875
|
]);
|
|
38742
38876
|
const isInternalEvent = (e) => {
|
|
38743
38877
|
return INTERNAL_EVENT_SET.has(e.type);
|
|
@@ -39761,6 +39895,24 @@ const useControlProps = (props, {
|
|
|
39761
39895
|
syncDomState(readControlValue(el), e);
|
|
39762
39896
|
};
|
|
39763
39897
|
}
|
|
39898
|
+
// Leaving the field is one of the two moments a value is committed — the
|
|
39899
|
+
// other being an action about to read it (see applyAutoFix). A constraint
|
|
39900
|
+
// allowed to correct the value puts it right here, so what the field
|
|
39901
|
+
// shows, what the counter counts and what a submit would send are one
|
|
39902
|
+
// thing well before the submit.
|
|
39903
|
+
if (controlType === "input") {
|
|
39904
|
+
const onBlurFromProps = controlHostProps.onBlur;
|
|
39905
|
+
controlHostProps.onBlur = e => {
|
|
39906
|
+
onBlurFromProps?.(e);
|
|
39907
|
+
const validation = uiStateController.rules.validation;
|
|
39908
|
+
if (validation.applyAutoFix(e)) {
|
|
39909
|
+
// The value moved without anyone typing: what the constraints had to
|
|
39910
|
+
// say about the old one is out of date, and so is what the controls
|
|
39911
|
+
// above read from this one.
|
|
39912
|
+
validation.syncValidity(e);
|
|
39913
|
+
}
|
|
39914
|
+
};
|
|
39915
|
+
}
|
|
39764
39916
|
}
|
|
39765
39917
|
const uiState = uiStateController.uiStateSignal.peek();
|
|
39766
39918
|
const domProps = toDomProps(uiState);
|
|
@@ -79051,8 +79203,10 @@ const css$l = /* css */`.navi_time_range_label {
|
|
|
79051
79203
|
}
|
|
79052
79204
|
`;
|
|
79053
79205
|
const HOUR_COUNT = 24;
|
|
79206
|
+
const END_OF_DAY_HOUR = 24;
|
|
79054
79207
|
const MINUTES_PER_HOUR = 60;
|
|
79055
79208
|
const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
|
|
79209
|
+
const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
79056
79210
|
|
|
79057
79211
|
/**
|
|
79058
79212
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
@@ -79072,16 +79226,21 @@ const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
|
|
|
79072
79226
|
* }>}
|
|
79073
79227
|
* @param {string} [value] The time shown, as "HH:MM".
|
|
79074
79228
|
* @param {number} [minuteStep=1] How many minutes apart the values on the
|
|
79075
|
-
* minute wheel are — 15 for quarters of an hour.
|
|
79229
|
+
* minute wheel are — 15 for quarters of an hour. At 60 the time is a whole
|
|
79230
|
+
* hour: there is no minute wheel at all, the value stays "HH:MM" ("08:00"),
|
|
79231
|
+
* and a value arriving with minutes is shown on its hour.
|
|
79076
79232
|
* @param {{min?: number, max?: number}|number[]} [hours] Which hours the wheel
|
|
79077
79233
|
* offers: `{ min: 7, max: 21 }` for a day that starts and ends somewhere, or
|
|
79078
|
-
* the list itself.
|
|
79079
|
-
* in the way.
|
|
79234
|
+
* the list itself. 0 to 23 by default. Rows nobody will ever land on are rows
|
|
79235
|
+
* in the way. 24 is the end of the day ("24:00", midnight at the end of a
|
|
79236
|
+
* span) and has no minutes: turned onto it, the minute wheel goes back to 0.
|
|
79080
79237
|
* @param {boolean} [loop=true] The wheels go round: 23h then 0h, 59 minutes
|
|
79081
79238
|
* then 0. What a clock does. Say `loop={false}` for two ends one cannot turn
|
|
79082
79239
|
* past.
|
|
79083
79240
|
* @param {import("ignore:preact").ComponentChildren} [separator] What is written
|
|
79084
|
-
* between the hours and the minutes. "h" in French, ":" elsewhere.
|
|
79241
|
+
* between the hours and the minutes. "h" in French, ":" elsewhere. In a time
|
|
79242
|
+
* of whole hours it follows each hour on its row instead ("8h"), and nothing
|
|
79243
|
+
* is written by default outside French.
|
|
79085
79244
|
* @param {string} [placeholder] What the wheels show while the time holds
|
|
79086
79245
|
* nothing, as "HH:MM". Wheels have no blank row to land on, so their
|
|
79087
79246
|
* placeholder is a position rather than a grey word — shown, but not an
|
|
@@ -79096,13 +79255,20 @@ const TimeWheel = ({
|
|
|
79096
79255
|
hours,
|
|
79097
79256
|
loop = true,
|
|
79098
79257
|
placeholder,
|
|
79099
|
-
separator
|
|
79258
|
+
separator,
|
|
79100
79259
|
hourLabel = naviI18n("time.hour_label"),
|
|
79101
79260
|
minuteLabel = naviI18n("time.minute_label"),
|
|
79102
79261
|
size,
|
|
79103
79262
|
wheelProps,
|
|
79263
|
+
onnavi_wheel_settle,
|
|
79104
79264
|
...rest
|
|
79105
79265
|
}) => {
|
|
79266
|
+
const wholeHours = minuteStep >= MINUTES_PER_HOUR;
|
|
79267
|
+
if (separator === undefined) {
|
|
79268
|
+
separator = naviI18n(wholeHours ? "time.hour_suffix" : "time.hour_separator");
|
|
79269
|
+
}
|
|
79270
|
+
const hourWheelRef = useRef(null);
|
|
79271
|
+
const minuteWheelRef = useRef(null);
|
|
79106
79272
|
const minutes = useMemo(() => {
|
|
79107
79273
|
const minuteList = [];
|
|
79108
79274
|
let minute = 0;
|
|
@@ -79116,13 +79282,42 @@ const TimeWheel = ({
|
|
|
79116
79282
|
const {
|
|
79117
79283
|
aggregateChildStates,
|
|
79118
79284
|
distributeChildUIState
|
|
79119
|
-
} = useAnswered(placeholder, rest, aggregateTime, distributeTime);
|
|
79285
|
+
} = useAnswered(placeholder, rest, aggregateTime, (groupState, child) => distributeTime(groupState, child, minuteStep));
|
|
79120
79286
|
const placeholderParts = parseTimeParts(placeholder);
|
|
79287
|
+
|
|
79288
|
+
// The end of the day has no minutes. The time already reads "24:00" whatever
|
|
79289
|
+
// the minute wheel shows (see aggregateTime); on settle the wheel is brought
|
|
79290
|
+
// back to 0 so what is drawn is what is held.
|
|
79291
|
+
const endOfDayHasNoMinutes = e => {
|
|
79292
|
+
const hourEl = hourWheelRef.current;
|
|
79293
|
+
const minuteEl = minuteWheelRef.current;
|
|
79294
|
+
if (!hourEl || !minuteEl) {
|
|
79295
|
+
return;
|
|
79296
|
+
}
|
|
79297
|
+
if (getUIStateFromElement(hourEl) !== END_OF_DAY_HOUR) {
|
|
79298
|
+
return;
|
|
79299
|
+
}
|
|
79300
|
+
if (getUIStateFromElement(minuteEl) === 0) {
|
|
79301
|
+
return;
|
|
79302
|
+
}
|
|
79303
|
+
dispatchRequestSetUIState(minuteEl, 0, {
|
|
79304
|
+
event: e
|
|
79305
|
+
});
|
|
79306
|
+
};
|
|
79121
79307
|
return jsxs(WheelGroup, {
|
|
79122
79308
|
aggregateChildStates: aggregateChildStates,
|
|
79123
79309
|
distributeChildUIState: distributeChildUIState,
|
|
79310
|
+
onnavi_wheel_settle: e => {
|
|
79311
|
+
if (!wholeHours) {
|
|
79312
|
+
endOfDayHasNoMinutes(e);
|
|
79313
|
+
}
|
|
79314
|
+
if (onnavi_wheel_settle) {
|
|
79315
|
+
onnavi_wheel_settle(e);
|
|
79316
|
+
}
|
|
79317
|
+
},
|
|
79124
79318
|
...rest,
|
|
79125
79319
|
children: [jsx(Wheel, {
|
|
79320
|
+
ref: hourWheelRef,
|
|
79126
79321
|
name: "hour",
|
|
79127
79322
|
type: "integer",
|
|
79128
79323
|
bounded: !loop,
|
|
@@ -79133,24 +79328,29 @@ const TimeWheel = ({
|
|
|
79133
79328
|
children: hourList.map(hour => jsx(Wheel.Item, {
|
|
79134
79329
|
value: hour,
|
|
79135
79330
|
paddingX: "s",
|
|
79136
|
-
children:
|
|
79331
|
+
children: wholeHours ? jsxs(Fragment, {
|
|
79332
|
+
children: [hour, separator]
|
|
79333
|
+
}) : padTwo(hour)
|
|
79137
79334
|
}, hour))
|
|
79138
|
-
}),
|
|
79139
|
-
|
|
79140
|
-
|
|
79141
|
-
|
|
79142
|
-
|
|
79143
|
-
|
|
79144
|
-
|
|
79145
|
-
|
|
79146
|
-
|
|
79147
|
-
|
|
79148
|
-
|
|
79149
|
-
|
|
79150
|
-
|
|
79151
|
-
|
|
79152
|
-
|
|
79153
|
-
|
|
79335
|
+
}), wholeHours ? null : jsxs(Fragment, {
|
|
79336
|
+
children: [jsx(WheelGroup.Separator, {
|
|
79337
|
+
size: size,
|
|
79338
|
+
children: separator
|
|
79339
|
+
}), jsx(Wheel, {
|
|
79340
|
+
ref: minuteWheelRef,
|
|
79341
|
+
name: "minute",
|
|
79342
|
+
type: "integer",
|
|
79343
|
+
bounded: !loop,
|
|
79344
|
+
size: size,
|
|
79345
|
+
"aria-label": minuteLabel,
|
|
79346
|
+
defaultValue: placeholderParts ? floorToStep(placeholderParts.minute, minuteStep) : undefined,
|
|
79347
|
+
...wheelProps,
|
|
79348
|
+
children: minutes.map(minute => jsx(Wheel.Item, {
|
|
79349
|
+
value: minute,
|
|
79350
|
+
paddingX: "s",
|
|
79351
|
+
children: padTwo(minute)
|
|
79352
|
+
}, minute))
|
|
79353
|
+
})]
|
|
79154
79354
|
})]
|
|
79155
79355
|
});
|
|
79156
79356
|
};
|
|
@@ -79184,11 +79384,12 @@ const TimeWheel = ({
|
|
|
79184
79384
|
* prepositions belong there; the group is still a row, and takes `flexWrap`
|
|
79185
79385
|
* for screens too narrow to hold both columns.
|
|
79186
79386
|
* @param {number} [minuteStep=1] How many minutes apart the values on both
|
|
79187
|
-
* minute wheels are.
|
|
79387
|
+
* minute wheels are. 60 for a span of whole hours ("de 8h à 12h").
|
|
79188
79388
|
* @param {{min?: number, max?: number}|number[]} [hours] Which hours both
|
|
79189
|
-
* wheels offer — see `TimeWheel`.
|
|
79389
|
+
* wheels offer — see `TimeWheel`. A 24 is offered to the end alone: a span
|
|
79390
|
+
* can run until midnight ("24:00"), it cannot start there.
|
|
79190
79391
|
* @param {number} [minDuration=0] How long the span must last at least, in
|
|
79191
|
-
* minutes. Zero by default: a span of no length is a span all the same, only
|
|
79392
|
+
* 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
|
|
79192
79393
|
* one that goes backwards is not. It is what the bounds keep between them as
|
|
79193
79394
|
* they turn — turn the start into the end and the end moves along, keeping
|
|
79194
79395
|
* that much room.
|
|
@@ -79221,6 +79422,12 @@ const TimeRangeWheel = ({
|
|
|
79221
79422
|
const startId = useId();
|
|
79222
79423
|
const startRef = useRef(null);
|
|
79223
79424
|
const endRef = useRef(null);
|
|
79425
|
+
const endHourList = useMemo(() => resolveHourList(hours), [hours ? hours.min : undefined, hours ? hours.max : undefined, hours]);
|
|
79426
|
+
const startHourList = useMemo(() => endHourList.filter(hour => hour !== END_OF_DAY_HOUR), [endHourList]);
|
|
79427
|
+
const step = minuteStep >= MINUTES_PER_HOUR ? MINUTES_PER_HOUR : minuteStep;
|
|
79428
|
+
const minGap = ceilToStep(minDuration, step);
|
|
79429
|
+
// The latest the end can be pushed to: the last time its wheels can show.
|
|
79430
|
+
const lastEnd = endHourList.includes(END_OF_DAY_HOUR) ? END_OF_DAY : floorToStep(LAST_MINUTE_OF_DAY, step);
|
|
79224
79431
|
// One turn settles the whole span: a start somebody chose makes the end an
|
|
79225
79432
|
// answer too, left where the placeholder put it.
|
|
79226
79433
|
const {
|
|
@@ -79252,18 +79459,18 @@ const TimeRangeWheel = ({
|
|
|
79252
79459
|
return;
|
|
79253
79460
|
}
|
|
79254
79461
|
const duration = movedSide === "start" ? otherMinutes - movedMinutes : movedMinutes - otherMinutes;
|
|
79255
|
-
if (duration >=
|
|
79462
|
+
if (duration >= minGap) {
|
|
79256
79463
|
return;
|
|
79257
79464
|
}
|
|
79258
|
-
let pushedMinutes = movedSide === "start" ? movedMinutes +
|
|
79465
|
+
let pushedMinutes = movedSide === "start" ? movedMinutes + minGap : movedMinutes - minGap;
|
|
79259
79466
|
// The day has ends the wheels do not: pushed past midnight, the other bound
|
|
79260
79467
|
// would come back round on the wrong side of the one that pushed it. It
|
|
79261
79468
|
// stops at the edge instead, and the span that no longer fits is what the
|
|
79262
79469
|
// send-time constraint is there to say (see time_range_constraint.js).
|
|
79263
79470
|
if (pushedMinutes < 0) {
|
|
79264
79471
|
pushedMinutes = 0;
|
|
79265
|
-
} else if (pushedMinutes >
|
|
79266
|
-
pushedMinutes =
|
|
79472
|
+
} else if (pushedMinutes > lastEnd) {
|
|
79473
|
+
pushedMinutes = lastEnd;
|
|
79267
79474
|
}
|
|
79268
79475
|
dispatchRequestSetUIState(otherEl, timeFromMinutes(pushedMinutes), {
|
|
79269
79476
|
event: e
|
|
@@ -79291,7 +79498,7 @@ const TimeRangeWheel = ({
|
|
|
79291
79498
|
ref: startRef,
|
|
79292
79499
|
name: "start",
|
|
79293
79500
|
minuteStep: minuteStep,
|
|
79294
|
-
hours:
|
|
79501
|
+
hours: startHourList,
|
|
79295
79502
|
loop: loop,
|
|
79296
79503
|
size: size,
|
|
79297
79504
|
placeholder: placeholder ? placeholder.start : undefined
|
|
@@ -79316,7 +79523,7 @@ const TimeRangeWheel = ({
|
|
|
79316
79523
|
ref: endRef,
|
|
79317
79524
|
name: "end",
|
|
79318
79525
|
minuteStep: minuteStep,
|
|
79319
|
-
hours:
|
|
79526
|
+
hours: endHourList,
|
|
79320
79527
|
loop: loop,
|
|
79321
79528
|
size: size,
|
|
79322
79529
|
placeholder: placeholder ? placeholder.end : undefined,
|
|
@@ -79328,7 +79535,7 @@ const TimeRangeWheel = ({
|
|
|
79328
79535
|
// the time one would have to move is (see time_range_constraint.js).
|
|
79329
79536
|
,
|
|
79330
79537
|
"data-time-after": startId,
|
|
79331
|
-
"data-time-min-duration":
|
|
79538
|
+
"data-time-min-duration": minGap,
|
|
79332
79539
|
...timeProps,
|
|
79333
79540
|
...endTimeProps
|
|
79334
79541
|
})
|
|
@@ -79490,10 +79697,11 @@ const distributeSpan = (groupState, childUIStateController) => {
|
|
|
79490
79697
|
return groupState[childUIStateController.name];
|
|
79491
79698
|
};
|
|
79492
79699
|
|
|
79493
|
-
// The
|
|
79700
|
+
// The wheels as one value, "HH:MM". A time of whole hours has no minute wheel
|
|
79701
|
+
// and is on the hour; so is the end of the day, whatever the minute wheel says.
|
|
79494
79702
|
const aggregateTime = childUIStateControllers => {
|
|
79495
79703
|
let hour = "";
|
|
79496
|
-
let minute =
|
|
79704
|
+
let minute = 0;
|
|
79497
79705
|
for (const child of childUIStateControllers) {
|
|
79498
79706
|
if (child.name === "hour") {
|
|
79499
79707
|
hour = child.uiState ?? "";
|
|
@@ -79502,18 +79710,27 @@ const aggregateTime = childUIStateControllers => {
|
|
|
79502
79710
|
minute = child.uiState ?? "";
|
|
79503
79711
|
}
|
|
79504
79712
|
}
|
|
79713
|
+
if (hour === END_OF_DAY_HOUR) {
|
|
79714
|
+
return formatTimeParts(hour, 0);
|
|
79715
|
+
}
|
|
79505
79716
|
return formatTimeParts(hour, minute);
|
|
79506
79717
|
};
|
|
79507
79718
|
|
|
79508
79719
|
// The way back: what the group is set to (a value given to it, a form being
|
|
79509
|
-
// reset, the other bound pushing it) lands on the wheel it belongs to.
|
|
79510
|
-
|
|
79720
|
+
// reset, the other bound pushing it) lands on the wheel it belongs to. Minutes
|
|
79721
|
+
// the wheel does not offer land on the one before.
|
|
79722
|
+
const distributeTime = (groupState, childUIStateController, minuteStep) => {
|
|
79511
79723
|
const parts = parseTimeParts(groupState);
|
|
79512
79724
|
if (!parts) {
|
|
79513
79725
|
return undefined;
|
|
79514
79726
|
}
|
|
79727
|
+
if (childUIStateController.name === "minute") {
|
|
79728
|
+
return floorToStep(parts.minute, minuteStep);
|
|
79729
|
+
}
|
|
79515
79730
|
return parts[childUIStateController.name];
|
|
79516
79731
|
};
|
|
79732
|
+
const floorToStep = (minutes, step) => Math.floor(minutes / step) * step;
|
|
79733
|
+
const ceilToStep = (minutes, step) => Math.ceil(minutes / step) * step;
|
|
79517
79734
|
|
|
79518
79735
|
const TableSelectionContext = createContext();
|
|
79519
79736
|
const useTableSelectionContextValue = (
|