@jsenv/navi 0.9.3 → 0.10.1
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 +470 -309
- package/index.js +5 -0
- package/package.json +1 -1
- package/src/components/callout/callout.js +3 -1
- package/src/components/details/details.jsx +7 -8
- package/src/components/field/button.jsx +6 -63
- package/src/components/field/checkbox_list.jsx +0 -1
- package/src/components/field/form.jsx +2 -17
- package/src/components/field/input_checkbox.jsx +0 -1
- package/src/components/field/input_textual.jsx +5 -142
- package/src/components/field/radio_list.jsx +0 -1
- package/src/components/field/select.jsx +0 -1
- package/src/components/field/use_form_events.js +4 -0
- package/src/components/keyboard_shortcuts/keyboard_shortcuts.js +1 -1
- package/src/validation/constraints/native_constraints.js +43 -18
- package/src/validation/constraints/same_as_constraint.js +42 -0
- package/src/validation/custom_constraint_validation.js +254 -59
- package/src/validation/demos/demo_same_as_constraint.html +259 -0
- package/src/validation/input_change_effect.js +106 -0
package/dist/jsenv_navi.js
CHANGED
|
@@ -520,22 +520,26 @@ const createPubSub = (clearOnPublish = false) => {
|
|
|
520
520
|
|
|
521
521
|
const createValueEffect = (value) => {
|
|
522
522
|
const callbackSet = new Set();
|
|
523
|
-
const
|
|
523
|
+
const valueCleanupSet = new Set();
|
|
524
|
+
|
|
525
|
+
const cleanup = () => {
|
|
526
|
+
for (const valueCleanup of valueCleanupSet) {
|
|
527
|
+
valueCleanup();
|
|
528
|
+
}
|
|
529
|
+
valueCleanupSet.clear();
|
|
530
|
+
};
|
|
524
531
|
|
|
525
532
|
const updateValue = (newValue) => {
|
|
526
533
|
if (newValue === value) {
|
|
527
534
|
return;
|
|
528
535
|
}
|
|
529
|
-
|
|
530
|
-
cleanup();
|
|
531
|
-
}
|
|
532
|
-
previousValueCleanupSet.clear();
|
|
536
|
+
cleanup();
|
|
533
537
|
const oldValue = value;
|
|
534
538
|
value = newValue;
|
|
535
539
|
for (const callback of callbackSet) {
|
|
536
540
|
const returnValue = callback(newValue, oldValue);
|
|
537
541
|
if (typeof returnValue === "function") {
|
|
538
|
-
|
|
542
|
+
valueCleanupSet.add(returnValue);
|
|
539
543
|
}
|
|
540
544
|
}
|
|
541
545
|
};
|
|
@@ -547,7 +551,7 @@ const createValueEffect = (value) => {
|
|
|
547
551
|
};
|
|
548
552
|
};
|
|
549
553
|
|
|
550
|
-
return [updateValue, addEffect];
|
|
554
|
+
return [updateValue, addEffect, cleanup];
|
|
551
555
|
};
|
|
552
556
|
|
|
553
557
|
// https://github.com/davidtheclark/tabbable/blob/master/index.js
|
|
@@ -11840,7 +11844,9 @@ const openCallout = (
|
|
|
11840
11844
|
addTeardown(onClose);
|
|
11841
11845
|
}
|
|
11842
11846
|
|
|
11843
|
-
const [updateLevel, addLevelEffect] =
|
|
11847
|
+
const [updateLevel, addLevelEffect, cleanupLevelEffects] =
|
|
11848
|
+
createValueEffect(undefined);
|
|
11849
|
+
addTeardown(cleanupLevelEffects);
|
|
11844
11850
|
|
|
11845
11851
|
// Create and add callout to document
|
|
11846
11852
|
const calloutElement = createCalloutElement();
|
|
@@ -12814,10 +12820,25 @@ const REQUIRED_CONSTRAINT = {
|
|
|
12814
12820
|
: undefined,
|
|
12815
12821
|
};
|
|
12816
12822
|
}
|
|
12817
|
-
if (
|
|
12818
|
-
return
|
|
12823
|
+
if (element.value) {
|
|
12824
|
+
return null;
|
|
12819
12825
|
}
|
|
12820
|
-
|
|
12826
|
+
if (requiredMessage) {
|
|
12827
|
+
return requiredMessage;
|
|
12828
|
+
}
|
|
12829
|
+
if (element.type === "password") {
|
|
12830
|
+
return element.hasAttribute("data-same-as")
|
|
12831
|
+
? `Veuillez confirmer le mot de passe.`
|
|
12832
|
+
: `Veuillez saisir un mot de passe.`;
|
|
12833
|
+
}
|
|
12834
|
+
if (element.type === "email") {
|
|
12835
|
+
return element.hasAttribute("data-same-as")
|
|
12836
|
+
? `Veuillez confirmer l'adresse e-mail`
|
|
12837
|
+
: `Veuillez saisir une adresse e-mail.`;
|
|
12838
|
+
}
|
|
12839
|
+
return element.hasAttribute("data-same-as")
|
|
12840
|
+
? `Veuillez confirmer le champ précédent`
|
|
12841
|
+
: `Veuillez remplir ce champ.`;
|
|
12821
12842
|
},
|
|
12822
12843
|
};
|
|
12823
12844
|
const PATTERN_CONSTRAINT = {
|
|
@@ -12832,19 +12853,19 @@ const PATTERN_CONSTRAINT = {
|
|
|
12832
12853
|
return null;
|
|
12833
12854
|
}
|
|
12834
12855
|
const regex = new RegExp(pattern);
|
|
12835
|
-
if (
|
|
12836
|
-
|
|
12837
|
-
if (patternMessage) {
|
|
12838
|
-
return patternMessage;
|
|
12839
|
-
}
|
|
12840
|
-
let message = `Veuillez respecter le format requis.`;
|
|
12841
|
-
const title = input.title;
|
|
12842
|
-
if (title) {
|
|
12843
|
-
message += `<br />${title}`;
|
|
12844
|
-
}
|
|
12845
|
-
return message;
|
|
12856
|
+
if (regex.test(value)) {
|
|
12857
|
+
return null;
|
|
12846
12858
|
}
|
|
12847
|
-
|
|
12859
|
+
const patternMessage = input.getAttribute("data-pattern-message");
|
|
12860
|
+
if (patternMessage) {
|
|
12861
|
+
return patternMessage;
|
|
12862
|
+
}
|
|
12863
|
+
let message = `Veuillez respecter le format requis.`;
|
|
12864
|
+
const title = input.title;
|
|
12865
|
+
if (title) {
|
|
12866
|
+
message += `<br />${title}`;
|
|
12867
|
+
}
|
|
12868
|
+
return message;
|
|
12848
12869
|
},
|
|
12849
12870
|
};
|
|
12850
12871
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/email#validation
|
|
@@ -12892,10 +12913,11 @@ const MIN_LENGTH_CONSTRAINT = {
|
|
|
12892
12913
|
return null;
|
|
12893
12914
|
}
|
|
12894
12915
|
if (valueLength < minLength) {
|
|
12916
|
+
const thisField = generateThisFieldText(element);
|
|
12895
12917
|
if (valueLength === 1) {
|
|
12896
|
-
return
|
|
12918
|
+
return `${thisField} doit contenir au moins ${minLength} caractère (il contient actuellement un seul caractère).`;
|
|
12897
12919
|
}
|
|
12898
|
-
return
|
|
12920
|
+
return `${thisField} doit contenir au moins ${minLength} caractères (il contient actuellement ${valueLength} caractères).`;
|
|
12899
12921
|
}
|
|
12900
12922
|
return null;
|
|
12901
12923
|
},
|
|
@@ -12909,6 +12931,14 @@ const INPUT_TYPE_SUPPORTING_MIN_LENGTH_SET = new Set([
|
|
|
12909
12931
|
"password",
|
|
12910
12932
|
]);
|
|
12911
12933
|
|
|
12934
|
+
const generateThisFieldText = (field) => {
|
|
12935
|
+
return field.type === "password"
|
|
12936
|
+
? "Ce mot de passe"
|
|
12937
|
+
: field.type === "email"
|
|
12938
|
+
? "Cette adresse e-mail"
|
|
12939
|
+
: "Ce champ";
|
|
12940
|
+
};
|
|
12941
|
+
|
|
12912
12942
|
const MAX_LENGTH_CONSTRAINT = {
|
|
12913
12943
|
name: "max_length",
|
|
12914
12944
|
check: (element) => {
|
|
@@ -12928,7 +12958,8 @@ const MAX_LENGTH_CONSTRAINT = {
|
|
|
12928
12958
|
const value = element.value;
|
|
12929
12959
|
const valueLength = value.length;
|
|
12930
12960
|
if (valueLength > maxLength) {
|
|
12931
|
-
|
|
12961
|
+
const thisField = generateThisFieldText(element);
|
|
12962
|
+
return `${thisField} doit contenir au maximum ${maxLength} caractères (il contient actuellement ${valueLength} caractères).`;
|
|
12932
12963
|
}
|
|
12933
12964
|
return null;
|
|
12934
12965
|
},
|
|
@@ -13097,6 +13128,154 @@ const READONLY_CONSTRAINT = {
|
|
|
13097
13128
|
},
|
|
13098
13129
|
};
|
|
13099
13130
|
|
|
13131
|
+
const SAME_AS_CONSTRAINT = {
|
|
13132
|
+
name: "same_as",
|
|
13133
|
+
check: (element) => {
|
|
13134
|
+
const sameAs = element.getAttribute("data-same-as");
|
|
13135
|
+
if (!sameAs) {
|
|
13136
|
+
return null;
|
|
13137
|
+
}
|
|
13138
|
+
|
|
13139
|
+
const otherElement = document.querySelector(sameAs);
|
|
13140
|
+
if (!otherElement) {
|
|
13141
|
+
console.warn(
|
|
13142
|
+
`Same as constraint: could not find element for selector ${sameAs}`,
|
|
13143
|
+
);
|
|
13144
|
+
return null;
|
|
13145
|
+
}
|
|
13146
|
+
|
|
13147
|
+
const value = element.value;
|
|
13148
|
+
const otherValue = otherElement.value;
|
|
13149
|
+
if (value === "" || otherValue === "") {
|
|
13150
|
+
// don't validate if one of the two values is empty
|
|
13151
|
+
return null;
|
|
13152
|
+
}
|
|
13153
|
+
|
|
13154
|
+
if (value === otherValue) {
|
|
13155
|
+
return null;
|
|
13156
|
+
}
|
|
13157
|
+
|
|
13158
|
+
const message = element.getAttribute("data-same-as-message");
|
|
13159
|
+
if (message) {
|
|
13160
|
+
return message;
|
|
13161
|
+
}
|
|
13162
|
+
|
|
13163
|
+
const type = element.type;
|
|
13164
|
+
if (type === "password") {
|
|
13165
|
+
return `Ce mot de passe doit être identique au précédent.`;
|
|
13166
|
+
}
|
|
13167
|
+
if (type === "email") {
|
|
13168
|
+
return `Cette adresse e-mail doit être identique a la précédente.`;
|
|
13169
|
+
}
|
|
13170
|
+
return `Ce champ doit être identique au précédent.`;
|
|
13171
|
+
},
|
|
13172
|
+
};
|
|
13173
|
+
|
|
13174
|
+
const listenInputChange = (input, callback) => {
|
|
13175
|
+
const [teardown, addTeardown] = createPubSub();
|
|
13176
|
+
|
|
13177
|
+
let valueAtInteraction;
|
|
13178
|
+
const oninput = () => {
|
|
13179
|
+
valueAtInteraction = undefined;
|
|
13180
|
+
};
|
|
13181
|
+
const onkeydown = (e) => {
|
|
13182
|
+
if (e.key === "Enter") {
|
|
13183
|
+
/**
|
|
13184
|
+
* Browser trigger a "change" event right after the enter is pressed
|
|
13185
|
+
* if the input value has changed.
|
|
13186
|
+
* We need to prevent the next change event otherwise we would request action twice
|
|
13187
|
+
*/
|
|
13188
|
+
valueAtInteraction = input.value;
|
|
13189
|
+
}
|
|
13190
|
+
if (e.key === "Escape") {
|
|
13191
|
+
/**
|
|
13192
|
+
* Browser trigger a "change" event right after the escape is pressed
|
|
13193
|
+
* if the input value has changed.
|
|
13194
|
+
* We need to prevent the next change event otherwise we would request action when
|
|
13195
|
+
* we actually want to cancel
|
|
13196
|
+
*/
|
|
13197
|
+
valueAtInteraction = input.value;
|
|
13198
|
+
}
|
|
13199
|
+
};
|
|
13200
|
+
const onchange = (e) => {
|
|
13201
|
+
if (
|
|
13202
|
+
valueAtInteraction !== undefined &&
|
|
13203
|
+
e.target.value === valueAtInteraction
|
|
13204
|
+
) {
|
|
13205
|
+
valueAtInteraction = undefined;
|
|
13206
|
+
return;
|
|
13207
|
+
}
|
|
13208
|
+
callback(e);
|
|
13209
|
+
};
|
|
13210
|
+
input.addEventListener("input", oninput);
|
|
13211
|
+
input.addEventListener("keydown", onkeydown);
|
|
13212
|
+
input.addEventListener("change", onchange);
|
|
13213
|
+
addTeardown(() => {
|
|
13214
|
+
input.removeEventListener("input", oninput);
|
|
13215
|
+
input.removeEventListener("keydown", onkeydown);
|
|
13216
|
+
input.removeEventListener("change", onchange);
|
|
13217
|
+
});
|
|
13218
|
+
|
|
13219
|
+
{
|
|
13220
|
+
// Handle programmatic value changes that don't trigger browser change events
|
|
13221
|
+
//
|
|
13222
|
+
// Problem: When input values are set programmatically (not by user typing),
|
|
13223
|
+
// browsers don't fire the 'change' event. However, our application logic
|
|
13224
|
+
// still needs to detect these changes.
|
|
13225
|
+
//
|
|
13226
|
+
// Example scenario:
|
|
13227
|
+
// 1. User starts editing (letter key pressed, value set programmatically)
|
|
13228
|
+
// 2. User doesn't type anything additional (this is the key part)
|
|
13229
|
+
// 3. User clicks outside to finish editing
|
|
13230
|
+
// 4. Without this code, no change event would fire despite the fact that the input value did change from its original state
|
|
13231
|
+
//
|
|
13232
|
+
// This distinction is crucial because:
|
|
13233
|
+
//
|
|
13234
|
+
// - If the user typed additional text after the initial programmatic value,
|
|
13235
|
+
// the browser would fire change events normally
|
|
13236
|
+
// - But when they don't type anything else, the browser considers it as "no user interaction"
|
|
13237
|
+
// even though the programmatic initial value represents a meaningful change
|
|
13238
|
+
//
|
|
13239
|
+
// We achieve this by checking if the input value has changed between focus and blur without any user interaction
|
|
13240
|
+
// if yes we fire the callback because input value did change
|
|
13241
|
+
let valueAtStart = input.value;
|
|
13242
|
+
let interacted = false;
|
|
13243
|
+
|
|
13244
|
+
const onfocus = () => {
|
|
13245
|
+
interacted = false;
|
|
13246
|
+
valueAtStart = input.value;
|
|
13247
|
+
};
|
|
13248
|
+
const oninput = (e) => {
|
|
13249
|
+
if (!e.isTrusted) {
|
|
13250
|
+
// non trusted "input" events will be ignored by the browser when deciding to fire "change" event
|
|
13251
|
+
// we ignore them too
|
|
13252
|
+
return;
|
|
13253
|
+
}
|
|
13254
|
+
interacted = true;
|
|
13255
|
+
};
|
|
13256
|
+
const onblur = (e) => {
|
|
13257
|
+
if (interacted) {
|
|
13258
|
+
return;
|
|
13259
|
+
}
|
|
13260
|
+
if (valueAtStart === input.value) {
|
|
13261
|
+
return;
|
|
13262
|
+
}
|
|
13263
|
+
callback(e);
|
|
13264
|
+
};
|
|
13265
|
+
|
|
13266
|
+
input.addEventListener("focus", onfocus);
|
|
13267
|
+
input.addEventListener("input", oninput);
|
|
13268
|
+
input.addEventListener("blur", onblur);
|
|
13269
|
+
addTeardown(() => {
|
|
13270
|
+
input.removeEventListener("focus", onfocus);
|
|
13271
|
+
input.removeEventListener("input", oninput);
|
|
13272
|
+
input.removeEventListener("blur", onblur);
|
|
13273
|
+
});
|
|
13274
|
+
}
|
|
13275
|
+
|
|
13276
|
+
return teardown;
|
|
13277
|
+
};
|
|
13278
|
+
|
|
13100
13279
|
/**
|
|
13101
13280
|
* Custom form validation implementation
|
|
13102
13281
|
*
|
|
@@ -13133,9 +13312,9 @@ const requestAction = (
|
|
|
13133
13312
|
target,
|
|
13134
13313
|
action,
|
|
13135
13314
|
{
|
|
13315
|
+
actionOrigin,
|
|
13136
13316
|
event,
|
|
13137
13317
|
requester = target,
|
|
13138
|
-
actionOrigin,
|
|
13139
13318
|
method = "rerun",
|
|
13140
13319
|
meta = {},
|
|
13141
13320
|
confirmMessage,
|
|
@@ -13205,12 +13384,8 @@ const requestAction = (
|
|
|
13205
13384
|
// Single element validation case
|
|
13206
13385
|
isValid = validationInterface.checkValidity({ fromRequestAction: true });
|
|
13207
13386
|
if (!isValid) {
|
|
13208
|
-
if (event) {
|
|
13209
|
-
event.preventDefault();
|
|
13210
|
-
}
|
|
13211
13387
|
validationInterface.reportValidity();
|
|
13212
13388
|
}
|
|
13213
|
-
|
|
13214
13389
|
elementForConfirmation = target;
|
|
13215
13390
|
elementForDispatch = target;
|
|
13216
13391
|
}
|
|
@@ -13247,6 +13422,15 @@ const requestAction = (
|
|
|
13247
13422
|
return true;
|
|
13248
13423
|
};
|
|
13249
13424
|
|
|
13425
|
+
const forwardActionRequested = (e, action, target = e.target) => {
|
|
13426
|
+
requestAction(target, action, {
|
|
13427
|
+
actionOrigin: e.detail?.actionOrigin,
|
|
13428
|
+
event: e.detail?.event || e,
|
|
13429
|
+
requester: e.detail?.requester,
|
|
13430
|
+
meta: e.detail?.meta,
|
|
13431
|
+
});
|
|
13432
|
+
};
|
|
13433
|
+
|
|
13250
13434
|
const closeValidationMessage = (element, reason) => {
|
|
13251
13435
|
const validationInterface = element.__validationInterface__;
|
|
13252
13436
|
if (!validationInterface) {
|
|
@@ -13259,6 +13443,7 @@ const closeValidationMessage = (element, reason) => {
|
|
|
13259
13443
|
return validationMessage.close(reason);
|
|
13260
13444
|
};
|
|
13261
13445
|
|
|
13446
|
+
const formInstrumentedWeakSet = new WeakSet();
|
|
13262
13447
|
const installCustomConstraintValidation = (
|
|
13263
13448
|
element,
|
|
13264
13449
|
elementReceivingValidationMessage = element,
|
|
@@ -13277,20 +13462,25 @@ const installCustomConstraintValidation = (
|
|
|
13277
13462
|
validationMessage: null,
|
|
13278
13463
|
};
|
|
13279
13464
|
|
|
13280
|
-
const
|
|
13465
|
+
const [teardown, addTeardown] = createPubSub();
|
|
13281
13466
|
{
|
|
13282
13467
|
const uninstall = () => {
|
|
13283
|
-
|
|
13284
|
-
cleanupCallback();
|
|
13285
|
-
}
|
|
13286
|
-
cleanupCallbackSet.clear();
|
|
13468
|
+
teardown();
|
|
13287
13469
|
};
|
|
13288
13470
|
validationInterface.uninstall = uninstall;
|
|
13289
13471
|
}
|
|
13290
13472
|
|
|
13473
|
+
const isForm = element.tagName === "FORM";
|
|
13474
|
+
if (isForm) {
|
|
13475
|
+
formInstrumentedWeakSet.add(element);
|
|
13476
|
+
addTeardown(() => {
|
|
13477
|
+
formInstrumentedWeakSet.delete(element);
|
|
13478
|
+
});
|
|
13479
|
+
}
|
|
13480
|
+
|
|
13291
13481
|
{
|
|
13292
13482
|
element.__validationInterface__ = validationInterface;
|
|
13293
|
-
|
|
13483
|
+
addTeardown(() => {
|
|
13294
13484
|
delete element.__validationInterface__;
|
|
13295
13485
|
});
|
|
13296
13486
|
}
|
|
@@ -13299,7 +13489,6 @@ const installCustomConstraintValidation = (
|
|
|
13299
13489
|
const cancelEvent = new CustomEvent("cancel", options);
|
|
13300
13490
|
element.dispatchEvent(cancelEvent);
|
|
13301
13491
|
};
|
|
13302
|
-
|
|
13303
13492
|
const closeElementValidationMessage = (reason) => {
|
|
13304
13493
|
if (validationInterface.validationMessage) {
|
|
13305
13494
|
validationInterface.validationMessage.close(reason);
|
|
@@ -13319,6 +13508,7 @@ const installCustomConstraintValidation = (
|
|
|
13319
13508
|
constraintSet.add(MIN_CONSTRAINT);
|
|
13320
13509
|
constraintSet.add(MAX_CONSTRAINT);
|
|
13321
13510
|
constraintSet.add(READONLY_CONSTRAINT);
|
|
13511
|
+
constraintSet.add(SAME_AS_CONSTRAINT);
|
|
13322
13512
|
{
|
|
13323
13513
|
validationInterface.registerConstraint = (constraint) => {
|
|
13324
13514
|
if (typeof constraint === "function") {
|
|
@@ -13337,6 +13527,8 @@ const installCustomConstraintValidation = (
|
|
|
13337
13527
|
let failedConstraintInfo = null;
|
|
13338
13528
|
const validityInfoMap = new Map();
|
|
13339
13529
|
|
|
13530
|
+
const hasTitleAttribute = element.hasAttribute("title");
|
|
13531
|
+
|
|
13340
13532
|
const resetValidity = ({ fromRequestAction } = {}) => {
|
|
13341
13533
|
if (fromRequestAction && failedConstraintInfo) {
|
|
13342
13534
|
for (const [key, customMessage] of customMessageMap) {
|
|
@@ -13354,7 +13546,7 @@ const installCustomConstraintValidation = (
|
|
|
13354
13546
|
validityInfoMap.clear();
|
|
13355
13547
|
failedConstraintInfo = null;
|
|
13356
13548
|
};
|
|
13357
|
-
|
|
13549
|
+
addTeardown(resetValidity);
|
|
13358
13550
|
|
|
13359
13551
|
const checkValidity = ({ fromRequestAction, skipReadonly } = {}) => {
|
|
13360
13552
|
resetValidity({ fromRequestAction });
|
|
@@ -13399,7 +13591,16 @@ const installCustomConstraintValidation = (
|
|
|
13399
13591
|
validityInfoMap.set(constraint, failedConstraintInfo);
|
|
13400
13592
|
}
|
|
13401
13593
|
|
|
13402
|
-
if (
|
|
13594
|
+
if (failedConstraintInfo) {
|
|
13595
|
+
if (!hasTitleAttribute) {
|
|
13596
|
+
// when a constraint is failing browser displays that constraint message if the element has no title attribute.
|
|
13597
|
+
// We want to do the same with our message (overriding the browser in the process to get better messages)
|
|
13598
|
+
element.setAttribute("title", failedConstraintInfo.message);
|
|
13599
|
+
}
|
|
13600
|
+
} else {
|
|
13601
|
+
if (!hasTitleAttribute) {
|
|
13602
|
+
element.removeAttribute("title");
|
|
13603
|
+
}
|
|
13403
13604
|
closeElementValidationMessage("becomes_valid");
|
|
13404
13605
|
}
|
|
13405
13606
|
|
|
@@ -13425,9 +13626,9 @@ const installCustomConstraintValidation = (
|
|
|
13425
13626
|
if (!skipFocus) {
|
|
13426
13627
|
element.focus();
|
|
13427
13628
|
}
|
|
13428
|
-
const
|
|
13629
|
+
const removeCloseOnCleanup = addTeardown(() => {
|
|
13429
13630
|
closeElementValidationMessage("cleanup");
|
|
13430
|
-
};
|
|
13631
|
+
});
|
|
13431
13632
|
|
|
13432
13633
|
const anchorElement =
|
|
13433
13634
|
failedConstraintInfo.target || elementReceivingValidationMessage;
|
|
@@ -13438,7 +13639,7 @@ const installCustomConstraintValidation = (
|
|
|
13438
13639
|
level: failedConstraintInfo.level,
|
|
13439
13640
|
closeOnClickOutside: failedConstraintInfo.closeOnClickOutside,
|
|
13440
13641
|
onClose: () => {
|
|
13441
|
-
|
|
13642
|
+
removeCloseOnCleanup();
|
|
13442
13643
|
validationInterface.validationMessage = null;
|
|
13443
13644
|
if (failedConstraintInfo) {
|
|
13444
13645
|
failedConstraintInfo.reportStatus = "closed";
|
|
@@ -13450,7 +13651,6 @@ const installCustomConstraintValidation = (
|
|
|
13450
13651
|
},
|
|
13451
13652
|
);
|
|
13452
13653
|
failedConstraintInfo.reportStatus = "reported";
|
|
13453
|
-
cleanupCallbackSet.add(closeOnCleanup);
|
|
13454
13654
|
};
|
|
13455
13655
|
validationInterface.checkValidity = checkValidity;
|
|
13456
13656
|
validationInterface.reportValidity = reportValidity;
|
|
@@ -13485,7 +13685,7 @@ const installCustomConstraintValidation = (
|
|
|
13485
13685
|
reportValidity();
|
|
13486
13686
|
}
|
|
13487
13687
|
};
|
|
13488
|
-
|
|
13688
|
+
addTeardown(() => {
|
|
13489
13689
|
customMessageMap.clear();
|
|
13490
13690
|
});
|
|
13491
13691
|
Object.assign(validationInterface, {
|
|
@@ -13494,6 +13694,7 @@ const installCustomConstraintValidation = (
|
|
|
13494
13694
|
});
|
|
13495
13695
|
}
|
|
13496
13696
|
|
|
13697
|
+
checkValidity();
|
|
13497
13698
|
{
|
|
13498
13699
|
const oninput = () => {
|
|
13499
13700
|
customMessageMap.clear();
|
|
@@ -13501,7 +13702,7 @@ const installCustomConstraintValidation = (
|
|
|
13501
13702
|
checkValidity();
|
|
13502
13703
|
};
|
|
13503
13704
|
element.addEventListener("input", oninput);
|
|
13504
|
-
|
|
13705
|
+
addTeardown(() => {
|
|
13505
13706
|
element.removeEventListener("input", oninput);
|
|
13506
13707
|
});
|
|
13507
13708
|
}
|
|
@@ -13513,13 +13714,7 @@ const installCustomConstraintValidation = (
|
|
|
13513
13714
|
checkValidity();
|
|
13514
13715
|
};
|
|
13515
13716
|
element.addEventListener("actionend", onactionend);
|
|
13516
|
-
|
|
13517
|
-
element.form.addEventListener("actionend", onactionend);
|
|
13518
|
-
cleanupCallbackSet.add(() => {
|
|
13519
|
-
element.form.removeEventListener("actionend", onactionend);
|
|
13520
|
-
});
|
|
13521
|
-
}
|
|
13522
|
-
cleanupCallbackSet.add(() => {
|
|
13717
|
+
addTeardown(() => {
|
|
13523
13718
|
element.removeEventListener("actionend", onactionend);
|
|
13524
13719
|
});
|
|
13525
13720
|
}
|
|
@@ -13529,37 +13724,111 @@ const installCustomConstraintValidation = (
|
|
|
13529
13724
|
element.reportValidity = () => {
|
|
13530
13725
|
reportValidity();
|
|
13531
13726
|
};
|
|
13532
|
-
|
|
13727
|
+
addTeardown(() => {
|
|
13533
13728
|
element.reportValidity = nativeReportValidity;
|
|
13534
13729
|
});
|
|
13535
13730
|
}
|
|
13536
13731
|
|
|
13537
|
-
{
|
|
13538
|
-
|
|
13539
|
-
|
|
13732
|
+
request_on_enter: {
|
|
13733
|
+
if (element.tagName !== "INPUT") {
|
|
13734
|
+
// maybe we want it too for checkboxes etc, we'll see
|
|
13735
|
+
break request_on_enter;
|
|
13736
|
+
}
|
|
13737
|
+
const onkeydown = (keydownEvent) => {
|
|
13738
|
+
if (keydownEvent.defaultPrevented) {
|
|
13540
13739
|
return;
|
|
13541
13740
|
}
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13741
|
+
if (keydownEvent.key !== "Enter") {
|
|
13742
|
+
return;
|
|
13743
|
+
}
|
|
13744
|
+
if (element.hasAttribute("data-action")) {
|
|
13745
|
+
if (wouldKeydownSubmitForm(keydownEvent)) {
|
|
13746
|
+
keydownEvent.preventDefault();
|
|
13747
|
+
}
|
|
13748
|
+
dispatchActionRequestedCustomEvent(element, {
|
|
13749
|
+
event: keydownEvent,
|
|
13750
|
+
requester: element,
|
|
13751
|
+
});
|
|
13752
|
+
return;
|
|
13753
|
+
}
|
|
13754
|
+
const { form } = element;
|
|
13755
|
+
if (!form) {
|
|
13756
|
+
return;
|
|
13757
|
+
}
|
|
13758
|
+
keydownEvent.preventDefault();
|
|
13759
|
+
dispatchActionRequestedCustomEvent(form, {
|
|
13760
|
+
event: keydownEvent,
|
|
13761
|
+
requester: getFirstButtonSubmittingForm(form) || element,
|
|
13546
13762
|
});
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13763
|
+
};
|
|
13764
|
+
element.addEventListener("keydown", onkeydown);
|
|
13765
|
+
addTeardown(() => {
|
|
13766
|
+
element.removeEventListener("keydown", onkeydown);
|
|
13767
|
+
});
|
|
13768
|
+
}
|
|
13769
|
+
|
|
13770
|
+
{
|
|
13771
|
+
const onclick = (clickEvent) => {
|
|
13772
|
+
if (clickEvent.defaultPrevented) {
|
|
13773
|
+
return;
|
|
13774
|
+
}
|
|
13775
|
+
if (element.tagName !== "BUTTON") {
|
|
13776
|
+
return;
|
|
13777
|
+
}
|
|
13778
|
+
if (element.hasAttribute("data-action")) {
|
|
13779
|
+
if (wouldClickSubmitForm(clickEvent)) {
|
|
13780
|
+
clickEvent.preventDefault();
|
|
13781
|
+
}
|
|
13782
|
+
dispatchActionRequestedCustomEvent(element, {
|
|
13783
|
+
event: clickEvent,
|
|
13784
|
+
requester: element,
|
|
13785
|
+
});
|
|
13786
|
+
return;
|
|
13787
|
+
}
|
|
13788
|
+
const { form } = element;
|
|
13789
|
+
if (!form) {
|
|
13790
|
+
return;
|
|
13791
|
+
}
|
|
13792
|
+
if (wouldClickSubmitForm(clickEvent)) {
|
|
13793
|
+
clickEvent.preventDefault();
|
|
13550
13794
|
}
|
|
13795
|
+
dispatchActionRequestedCustomEvent(form, {
|
|
13796
|
+
event: clickEvent,
|
|
13797
|
+
requester: element,
|
|
13798
|
+
});
|
|
13551
13799
|
};
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13800
|
+
element.addEventListener("click", onclick);
|
|
13801
|
+
addTeardown(() => {
|
|
13802
|
+
element.removeEventListener("click", onclick);
|
|
13803
|
+
});
|
|
13804
|
+
}
|
|
13805
|
+
|
|
13806
|
+
request_on_input_change: {
|
|
13807
|
+
const isInput =
|
|
13808
|
+
element.tagName === "INPUT" || element.tagName === "TEXTAREA";
|
|
13809
|
+
if (!isInput) {
|
|
13810
|
+
break request_on_input_change;
|
|
13811
|
+
}
|
|
13812
|
+
const stop = listenInputChange(element, (e) => {
|
|
13813
|
+
if (element.hasAttribute("data-action")) {
|
|
13814
|
+
dispatchActionRequestedCustomEvent(element, {
|
|
13815
|
+
event: e,
|
|
13816
|
+
requester: element,
|
|
13817
|
+
});
|
|
13818
|
+
return;
|
|
13819
|
+
}
|
|
13820
|
+
});
|
|
13821
|
+
addTeardown(() => {
|
|
13822
|
+
stop();
|
|
13555
13823
|
});
|
|
13556
13824
|
}
|
|
13557
13825
|
|
|
13558
13826
|
execute_on_form_submit: {
|
|
13559
|
-
|
|
13560
|
-
if (!form) {
|
|
13827
|
+
if (!isForm) {
|
|
13561
13828
|
break execute_on_form_submit;
|
|
13562
13829
|
}
|
|
13830
|
+
// We will dispatch "action" when "submit" occurs (code called from.submit() to bypass validation)
|
|
13831
|
+
const form = element;
|
|
13563
13832
|
const removeListener = addEventListener(form, "submit", (e) => {
|
|
13564
13833
|
e.preventDefault();
|
|
13565
13834
|
const actionCustomEvent = new CustomEvent("action", {
|
|
@@ -13568,12 +13837,14 @@ const installCustomConstraintValidation = (
|
|
|
13568
13837
|
event: e,
|
|
13569
13838
|
method: "rerun",
|
|
13570
13839
|
requester: form,
|
|
13571
|
-
meta: {
|
|
13840
|
+
meta: {
|
|
13841
|
+
isSubmit: true,
|
|
13842
|
+
},
|
|
13572
13843
|
},
|
|
13573
13844
|
});
|
|
13574
13845
|
form.dispatchEvent(actionCustomEvent);
|
|
13575
13846
|
});
|
|
13576
|
-
|
|
13847
|
+
addTeardown(() => {
|
|
13577
13848
|
removeListener();
|
|
13578
13849
|
});
|
|
13579
13850
|
}
|
|
@@ -13587,7 +13858,7 @@ const installCustomConstraintValidation = (
|
|
|
13587
13858
|
}
|
|
13588
13859
|
};
|
|
13589
13860
|
element.addEventListener("keydown", onkeydown);
|
|
13590
|
-
|
|
13861
|
+
addTeardown(() => {
|
|
13591
13862
|
element.removeEventListener("keydown", onkeydown);
|
|
13592
13863
|
});
|
|
13593
13864
|
}
|
|
@@ -13595,7 +13866,11 @@ const installCustomConstraintValidation = (
|
|
|
13595
13866
|
{
|
|
13596
13867
|
const onblur = () => {
|
|
13597
13868
|
if (element.value === "") {
|
|
13598
|
-
dispatchCancelCustomEvent({
|
|
13869
|
+
dispatchCancelCustomEvent({
|
|
13870
|
+
detail: {
|
|
13871
|
+
reason: "blur_empty",
|
|
13872
|
+
},
|
|
13873
|
+
});
|
|
13599
13874
|
return;
|
|
13600
13875
|
}
|
|
13601
13876
|
// if we have failed constraint, we cancel too
|
|
@@ -13610,7 +13885,7 @@ const installCustomConstraintValidation = (
|
|
|
13610
13885
|
}
|
|
13611
13886
|
};
|
|
13612
13887
|
element.addEventListener("blur", onblur);
|
|
13613
|
-
|
|
13888
|
+
addTeardown(() => {
|
|
13614
13889
|
element.removeEventListener("blur", onblur);
|
|
13615
13890
|
});
|
|
13616
13891
|
}
|
|
@@ -13618,22 +13893,105 @@ const installCustomConstraintValidation = (
|
|
|
13618
13893
|
return validationInterface;
|
|
13619
13894
|
};
|
|
13620
13895
|
|
|
13621
|
-
|
|
13896
|
+
const wouldClickSubmitForm = (clickEvent) => {
|
|
13897
|
+
if (clickEvent.defaultPrevented) {
|
|
13898
|
+
return false;
|
|
13899
|
+
}
|
|
13900
|
+
const clickTarget = clickEvent.target;
|
|
13901
|
+
const { form } = clickTarget;
|
|
13902
|
+
if (!form) {
|
|
13903
|
+
return false;
|
|
13904
|
+
}
|
|
13905
|
+
const button = clickTarget.closest("button");
|
|
13906
|
+
if (!button) {
|
|
13907
|
+
return false;
|
|
13908
|
+
}
|
|
13909
|
+
const wouldSubmitFormByType =
|
|
13910
|
+
button.type === "submit" || button.type === "image";
|
|
13911
|
+
if (wouldSubmitFormByType) {
|
|
13912
|
+
return true;
|
|
13913
|
+
}
|
|
13914
|
+
if (button.type) {
|
|
13915
|
+
return false;
|
|
13916
|
+
}
|
|
13917
|
+
if (getFirstButtonSubmittingForm(form)) {
|
|
13918
|
+
// an other button is explicitly submitting the form, this one would not submit it
|
|
13919
|
+
return false;
|
|
13920
|
+
}
|
|
13921
|
+
// this is the only button inside the form without type attribute, so it defaults to type="submit"
|
|
13922
|
+
return true;
|
|
13923
|
+
};
|
|
13924
|
+
const getFirstButtonSubmittingForm = (form) => {
|
|
13925
|
+
return form.querySelector(
|
|
13926
|
+
`button[type="submit"], input[type="submit"], input[type="image"]`,
|
|
13927
|
+
);
|
|
13928
|
+
};
|
|
13929
|
+
|
|
13930
|
+
const wouldKeydownSubmitForm = (keydownEvent) => {
|
|
13931
|
+
if (keydownEvent.defaultPrevented) {
|
|
13932
|
+
return false;
|
|
13933
|
+
}
|
|
13934
|
+
const keydownTarget = keydownEvent.target;
|
|
13935
|
+
const { form } = keydownTarget;
|
|
13936
|
+
if (!form) {
|
|
13937
|
+
return false;
|
|
13938
|
+
}
|
|
13939
|
+
if (keydownEvent.key !== "Enter") {
|
|
13940
|
+
return false;
|
|
13941
|
+
}
|
|
13942
|
+
const isTextInput =
|
|
13943
|
+
keydownTarget.tagName === "INPUT" || keydownTarget.tagName === "TEXTAREA";
|
|
13944
|
+
if (!isTextInput) {
|
|
13945
|
+
return false;
|
|
13946
|
+
}
|
|
13947
|
+
return true;
|
|
13948
|
+
};
|
|
13622
13949
|
|
|
13623
|
-
const
|
|
13950
|
+
const dispatchActionRequestedCustomEvent = (
|
|
13951
|
+
fieldOrForm,
|
|
13952
|
+
{ actionOrigin = "action_prop", event, requester },
|
|
13953
|
+
) => {
|
|
13954
|
+
const actionRequestedCustomEvent = new CustomEvent("actionrequested", {
|
|
13955
|
+
cancelable: true,
|
|
13956
|
+
detail: {
|
|
13957
|
+
actionOrigin,
|
|
13958
|
+
event,
|
|
13959
|
+
requester,
|
|
13960
|
+
},
|
|
13961
|
+
});
|
|
13962
|
+
fieldOrForm.dispatchEvent(actionRequestedCustomEvent);
|
|
13963
|
+
};
|
|
13964
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation
|
|
13624
13965
|
const requestSubmit = HTMLFormElement.prototype.requestSubmit;
|
|
13625
13966
|
HTMLFormElement.prototype.requestSubmit = function (submitter) {
|
|
13626
|
-
|
|
13627
|
-
const
|
|
13628
|
-
|
|
13629
|
-
|
|
13630
|
-
for (const requestSubmitCallback of requestSubmitCallbackSet) {
|
|
13631
|
-
requestSubmitCallback(this, { submitter, preventDefault });
|
|
13632
|
-
}
|
|
13633
|
-
if (prevented) {
|
|
13967
|
+
const form = this;
|
|
13968
|
+
const isInstrumented = formInstrumentedWeakSet.has(form);
|
|
13969
|
+
if (!isInstrumented) {
|
|
13970
|
+
requestSubmit.call(form, submitter);
|
|
13634
13971
|
return;
|
|
13635
13972
|
}
|
|
13636
|
-
|
|
13973
|
+
const programmaticEvent = new CustomEvent("programmatic_requestsubmit", {
|
|
13974
|
+
cancelable: true,
|
|
13975
|
+
detail: {
|
|
13976
|
+
submitter,
|
|
13977
|
+
},
|
|
13978
|
+
});
|
|
13979
|
+
dispatchActionRequestedCustomEvent(form, {
|
|
13980
|
+
event: programmaticEvent,
|
|
13981
|
+
requester: submitter,
|
|
13982
|
+
});
|
|
13983
|
+
|
|
13984
|
+
// When all fields are valid calling the native requestSubmit would let browser go through the
|
|
13985
|
+
// standard form validation steps leading to form submission.
|
|
13986
|
+
// We don't want that because we have our own action system to handle forms
|
|
13987
|
+
// If we did that the form submission would happen in parallel of our action system
|
|
13988
|
+
// and because we listen to "submit" event to dispatch "action" event
|
|
13989
|
+
// we would end up with two actions being executed.
|
|
13990
|
+
//
|
|
13991
|
+
// In case we have discrepencies in our implementation compared to the browser standard
|
|
13992
|
+
// this also prevent the native validation message to show up.
|
|
13993
|
+
|
|
13994
|
+
// requestSubmit.call(this, submitter);
|
|
13637
13995
|
};
|
|
13638
13996
|
|
|
13639
13997
|
// const submit = HTMLFormElement.prototype.submit;
|
|
@@ -14381,10 +14739,10 @@ const useKeyboardShortcuts = (
|
|
|
14381
14739
|
}
|
|
14382
14740
|
const { action } = shortcutCandidate;
|
|
14383
14741
|
return requestAction(element, action, {
|
|
14742
|
+
actionOrigin: "keyboard_shortcut",
|
|
14384
14743
|
event: keyboardEvent,
|
|
14385
14744
|
requester: document.activeElement,
|
|
14386
14745
|
confirmMessage: shortcutCandidate.confirmMessage,
|
|
14387
|
-
actionOrigin: "keyboard_shortcut",
|
|
14388
14746
|
meta: {
|
|
14389
14747
|
shortcut: shortcutCandidate,
|
|
14390
14748
|
},
|
|
@@ -18276,33 +18634,33 @@ const SummaryMarker = ({
|
|
|
18276
18634
|
|
|
18277
18635
|
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
18278
18636
|
.navi_details {
|
|
18279
|
-
display: flex;
|
|
18280
|
-
flex-direction: column;
|
|
18281
18637
|
position: relative;
|
|
18282
18638
|
z-index: 1;
|
|
18639
|
+
display: flex;
|
|
18283
18640
|
flex-shrink: 0;
|
|
18641
|
+
flex-direction: column;
|
|
18284
18642
|
}
|
|
18285
18643
|
|
|
18286
18644
|
.navi_details > summary {
|
|
18287
|
-
flex-shrink: 0;
|
|
18288
|
-
cursor: pointer;
|
|
18289
18645
|
display: flex;
|
|
18646
|
+
flex-shrink: 0;
|
|
18290
18647
|
flex-direction: column;
|
|
18648
|
+
cursor: pointer;
|
|
18291
18649
|
user-select: none;
|
|
18292
18650
|
}
|
|
18293
18651
|
.summary_body {
|
|
18294
18652
|
display: flex;
|
|
18653
|
+
width: 100%;
|
|
18295
18654
|
flex-direction: row;
|
|
18296
18655
|
align-items: center;
|
|
18297
|
-
width: 100%;
|
|
18298
18656
|
gap: 0.2em;
|
|
18299
18657
|
}
|
|
18300
18658
|
.summary_label {
|
|
18301
18659
|
display: flex;
|
|
18660
|
+
padding-right: 10px;
|
|
18302
18661
|
flex: 1;
|
|
18303
|
-
gap: 0.2em;
|
|
18304
18662
|
align-items: center;
|
|
18305
|
-
|
|
18663
|
+
gap: 0.2em;
|
|
18306
18664
|
}
|
|
18307
18665
|
|
|
18308
18666
|
.navi_details > summary:focus {
|
|
@@ -18477,7 +18835,6 @@ const DetailsWithAction = forwardRef((props, ref) => {
|
|
|
18477
18835
|
const isOpen = toggleEvent.newState === "open";
|
|
18478
18836
|
if (isOpen) {
|
|
18479
18837
|
requestAction(toggleEvent.target, effectiveAction, {
|
|
18480
|
-
actionOrigin: "action_prop",
|
|
18481
18838
|
event: toggleEvent,
|
|
18482
18839
|
method: "run"
|
|
18483
18840
|
});
|
|
@@ -20490,8 +20847,7 @@ const InputCheckboxWithAction = forwardRef((props, ref) => {
|
|
|
20490
20847
|
loading: loading || actionLoading,
|
|
20491
20848
|
onChange: e => {
|
|
20492
20849
|
requestAction(e.target, actionBoundToUIState, {
|
|
20493
|
-
event: e
|
|
20494
|
-
actionOrigin: "action_prop"
|
|
20850
|
+
event: e
|
|
20495
20851
|
});
|
|
20496
20852
|
onChange?.(e);
|
|
20497
20853
|
}
|
|
@@ -21127,8 +21483,6 @@ const InputTextualWithAction = forwardRef((props, ref) => {
|
|
|
21127
21483
|
cancelOnBlurInvalid,
|
|
21128
21484
|
cancelOnEscape,
|
|
21129
21485
|
actionErrorEffect,
|
|
21130
|
-
onInput,
|
|
21131
|
-
onKeyDown,
|
|
21132
21486
|
...rest
|
|
21133
21487
|
} = props;
|
|
21134
21488
|
const innerRef = useRef(null);
|
|
@@ -21140,17 +21494,6 @@ const InputTextualWithAction = forwardRef((props, ref) => {
|
|
|
21140
21494
|
const executeAction = useExecuteAction(innerRef, {
|
|
21141
21495
|
errorEffect: actionErrorEffect
|
|
21142
21496
|
});
|
|
21143
|
-
const valueAtInteractionRef = useRef(null);
|
|
21144
|
-
useOnInputChange(innerRef, e => {
|
|
21145
|
-
if (valueAtInteractionRef.current !== null && e.target.value === valueAtInteractionRef.current) {
|
|
21146
|
-
valueAtInteractionRef.current = null;
|
|
21147
|
-
return;
|
|
21148
|
-
}
|
|
21149
|
-
requestAction(e.target, boundAction, {
|
|
21150
|
-
event: e,
|
|
21151
|
-
actionOrigin: "action_prop"
|
|
21152
|
-
});
|
|
21153
|
-
});
|
|
21154
21497
|
// here updating the input won't call the associated action
|
|
21155
21498
|
// (user have to blur or press enter for this to happen)
|
|
21156
21499
|
// so we can keep the ui state on cancel/abort/error and let user decide
|
|
@@ -21171,16 +21514,12 @@ const InputTextualWithAction = forwardRef((props, ref) => {
|
|
|
21171
21514
|
if (!cancelOnEscape) {
|
|
21172
21515
|
return;
|
|
21173
21516
|
}
|
|
21174
|
-
/**
|
|
21175
|
-
* Browser trigger a "change" event right after the escape is pressed
|
|
21176
|
-
* if the input value has changed.
|
|
21177
|
-
* We need to prevent the next change event otherwise we would request action when
|
|
21178
|
-
* we actually want to cancel
|
|
21179
|
-
*/
|
|
21180
|
-
valueAtInteractionRef.current = e.target.value;
|
|
21181
21517
|
}
|
|
21182
21518
|
onCancel?.(e, reason);
|
|
21183
21519
|
},
|
|
21520
|
+
onRequested: e => {
|
|
21521
|
+
forwardActionRequested(e, boundAction);
|
|
21522
|
+
},
|
|
21184
21523
|
onPrevented: onActionPrevented,
|
|
21185
21524
|
onAction: executeAction,
|
|
21186
21525
|
onStart: onActionStart,
|
|
@@ -21191,33 +21530,11 @@ const InputTextualWithAction = forwardRef((props, ref) => {
|
|
|
21191
21530
|
"data-action": boundAction.name,
|
|
21192
21531
|
...rest,
|
|
21193
21532
|
ref: innerRef,
|
|
21194
|
-
loading: loading || actionLoading
|
|
21195
|
-
onInput: e => {
|
|
21196
|
-
valueAtInteractionRef.current = null;
|
|
21197
|
-
onInput?.(e);
|
|
21198
|
-
},
|
|
21199
|
-
onKeyDown: e => {
|
|
21200
|
-
if (e.key !== "Enter") {
|
|
21201
|
-
return;
|
|
21202
|
-
}
|
|
21203
|
-
e.preventDefault();
|
|
21204
|
-
/**
|
|
21205
|
-
* Browser trigger a "change" event right after the enter is pressed
|
|
21206
|
-
* if the input value has changed.
|
|
21207
|
-
* We need to prevent the next change event otherwise we would request action twice
|
|
21208
|
-
*/
|
|
21209
|
-
valueAtInteractionRef.current = e.target.value;
|
|
21210
|
-
requestAction(e.target, boundAction, {
|
|
21211
|
-
event: e,
|
|
21212
|
-
actionOrigin: "action_prop"
|
|
21213
|
-
});
|
|
21214
|
-
onKeyDown?.(e);
|
|
21215
|
-
}
|
|
21533
|
+
loading: loading || actionLoading
|
|
21216
21534
|
});
|
|
21217
21535
|
});
|
|
21218
21536
|
const InputTextualInsideForm = forwardRef((props, ref) => {
|
|
21219
21537
|
const {
|
|
21220
|
-
onKeyDown,
|
|
21221
21538
|
// We destructure formContext to avoid passing it to the underlying input element
|
|
21222
21539
|
// eslint-disable-next-line no-unused-vars
|
|
21223
21540
|
formContext,
|
|
@@ -21225,94 +21542,10 @@ const InputTextualInsideForm = forwardRef((props, ref) => {
|
|
|
21225
21542
|
} = props;
|
|
21226
21543
|
return jsx(InputTextualBasic, {
|
|
21227
21544
|
...rest,
|
|
21228
|
-
ref: ref
|
|
21229
|
-
onKeyDown: e => {
|
|
21230
|
-
if (e.key === "Enter") {
|
|
21231
|
-
const inputElement = e.target;
|
|
21232
|
-
const {
|
|
21233
|
-
form
|
|
21234
|
-
} = inputElement;
|
|
21235
|
-
const formSubmitButton = form.querySelector("button[type='submit'], input[type='submit'], input[type='image']");
|
|
21236
|
-
e.preventDefault();
|
|
21237
|
-
form.dispatchEvent(new CustomEvent("actionrequested", {
|
|
21238
|
-
detail: {
|
|
21239
|
-
requester: formSubmitButton ? formSubmitButton : inputElement,
|
|
21240
|
-
event: e,
|
|
21241
|
-
meta: {
|
|
21242
|
-
isSubmit: true
|
|
21243
|
-
},
|
|
21244
|
-
actionOrigin: "action_prop"
|
|
21245
|
-
}
|
|
21246
|
-
}));
|
|
21247
|
-
}
|
|
21248
|
-
onKeyDown?.(e);
|
|
21249
|
-
}
|
|
21545
|
+
ref: ref
|
|
21250
21546
|
});
|
|
21251
21547
|
});
|
|
21252
|
-
const useOnInputChange = (inputRef, callback) => {
|
|
21253
|
-
// we must use a custom event listener because preact bind onChange to onInput for compat with react
|
|
21254
|
-
useEffect(() => {
|
|
21255
|
-
const input = inputRef.current;
|
|
21256
|
-
input.addEventListener("change", callback);
|
|
21257
|
-
return () => {
|
|
21258
|
-
input.removeEventListener("change", callback);
|
|
21259
|
-
};
|
|
21260
|
-
}, [callback]);
|
|
21261
21548
|
|
|
21262
|
-
// Handle programmatic value changes that don't trigger browser change events
|
|
21263
|
-
//
|
|
21264
|
-
// Problem: When input values are set programmatically (not by user typing),
|
|
21265
|
-
// browsers don't fire the 'change' event. However, our application logic
|
|
21266
|
-
// still needs to detect these changes.
|
|
21267
|
-
//
|
|
21268
|
-
// Example scenario:
|
|
21269
|
-
// 1. User starts editing (letter key pressed, value set programmatically)
|
|
21270
|
-
// 2. User doesn't type anything additional (this is the key part)
|
|
21271
|
-
// 3. User clicks outside to finish editing
|
|
21272
|
-
// 4. Without this code, no change event would fire despite the fact that the input value did change from its original state
|
|
21273
|
-
//
|
|
21274
|
-
// This distinction is crucial because:
|
|
21275
|
-
//
|
|
21276
|
-
// - If the user typed additional text after the initial programmatic value,
|
|
21277
|
-
// the browser would fire change events normally
|
|
21278
|
-
// - But when they don't type anything else, the browser considers it as "no user interaction"
|
|
21279
|
-
// even though the programmatic initial value represents a meaningful change
|
|
21280
|
-
const valueAtStartRef = useRef();
|
|
21281
|
-
const interactedRef = useRef(false);
|
|
21282
|
-
useLayoutEffect(() => {
|
|
21283
|
-
const input = inputRef.current;
|
|
21284
|
-
valueAtStartRef.current = input.value;
|
|
21285
|
-
const onfocus = () => {
|
|
21286
|
-
interactedRef.current = false;
|
|
21287
|
-
valueAtStartRef.current = input.value;
|
|
21288
|
-
};
|
|
21289
|
-
const oninput = e => {
|
|
21290
|
-
if (!e.isTrusted) {
|
|
21291
|
-
// non trusted "input" events will be ignored by the browser when deciding to fire "change" event
|
|
21292
|
-
// we ignore them too
|
|
21293
|
-
return;
|
|
21294
|
-
}
|
|
21295
|
-
interactedRef.current = true;
|
|
21296
|
-
};
|
|
21297
|
-
const onblur = e => {
|
|
21298
|
-
if (interactedRef.current) {
|
|
21299
|
-
return;
|
|
21300
|
-
}
|
|
21301
|
-
if (valueAtStartRef.current === input.value) {
|
|
21302
|
-
return;
|
|
21303
|
-
}
|
|
21304
|
-
callback(e);
|
|
21305
|
-
};
|
|
21306
|
-
input.addEventListener("focus", onfocus);
|
|
21307
|
-
input.addEventListener("input", oninput);
|
|
21308
|
-
input.addEventListener("blur", onblur);
|
|
21309
|
-
return () => {
|
|
21310
|
-
input.removeEventListener("focus", onfocus);
|
|
21311
|
-
input.removeEventListener("input", oninput);
|
|
21312
|
-
input.removeEventListener("blur", onblur);
|
|
21313
|
-
};
|
|
21314
|
-
}, []);
|
|
21315
|
-
};
|
|
21316
21549
|
// As explained in https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/datetime-local#setting_timezones
|
|
21317
21550
|
// datetime-local does not support timezones
|
|
21318
21551
|
const convertToLocalTimezone = dateTimeString => {
|
|
@@ -21536,6 +21769,7 @@ const useFormEvents = (
|
|
|
21536
21769
|
elementRef,
|
|
21537
21770
|
{
|
|
21538
21771
|
onFormReset,
|
|
21772
|
+
onFormActionRequested,
|
|
21539
21773
|
onFormActionPrevented,
|
|
21540
21774
|
onFormActionStart,
|
|
21541
21775
|
onFormActionAbort,
|
|
@@ -21544,6 +21778,7 @@ const useFormEvents = (
|
|
|
21544
21778
|
},
|
|
21545
21779
|
) => {
|
|
21546
21780
|
onFormReset = useStableCallback(onFormReset);
|
|
21781
|
+
onFormActionRequested = useStableCallback(onFormActionRequested);
|
|
21547
21782
|
onFormActionPrevented = useStableCallback(onFormActionPrevented);
|
|
21548
21783
|
onFormActionStart = useStableCallback(onFormActionStart);
|
|
21549
21784
|
onFormActionAbort = useStableCallback(onFormActionAbort);
|
|
@@ -21567,6 +21802,7 @@ const useFormEvents = (
|
|
|
21567
21802
|
}
|
|
21568
21803
|
return addManyEventListeners(form, {
|
|
21569
21804
|
reset: onFormReset,
|
|
21805
|
+
actionrequested: onFormActionRequested,
|
|
21570
21806
|
actionprevented: onFormActionPrevented,
|
|
21571
21807
|
actionstart: onFormActionStart,
|
|
21572
21808
|
actionabort: onFormActionAbort,
|
|
@@ -21575,6 +21811,7 @@ const useFormEvents = (
|
|
|
21575
21811
|
});
|
|
21576
21812
|
}, [
|
|
21577
21813
|
onFormReset,
|
|
21814
|
+
onFormActionRequested,
|
|
21578
21815
|
onFormActionPrevented,
|
|
21579
21816
|
onFormActionStart,
|
|
21580
21817
|
onFormActionAbort,
|
|
@@ -21821,7 +22058,6 @@ const ButtonWithAction = forwardRef((props, ref) => {
|
|
|
21821
22058
|
const {
|
|
21822
22059
|
action,
|
|
21823
22060
|
loading,
|
|
21824
|
-
onClick,
|
|
21825
22061
|
actionErrorEffect,
|
|
21826
22062
|
onActionPrevented,
|
|
21827
22063
|
onActionStart,
|
|
@@ -21839,22 +22075,15 @@ const ButtonWithAction = forwardRef((props, ref) => {
|
|
|
21839
22075
|
const executeAction = useExecuteAction(innerRef, {
|
|
21840
22076
|
errorEffect: actionErrorEffect
|
|
21841
22077
|
});
|
|
22078
|
+
const innerLoading = loading || actionLoading;
|
|
21842
22079
|
useActionEvents(innerRef, {
|
|
21843
22080
|
onPrevented: onActionPrevented,
|
|
22081
|
+
onRequested: e => forwardActionRequested(e, boundAction),
|
|
21844
22082
|
onAction: executeAction,
|
|
21845
22083
|
onStart: onActionStart,
|
|
21846
22084
|
onError: onActionError,
|
|
21847
22085
|
onEnd: onActionEnd
|
|
21848
22086
|
});
|
|
21849
|
-
const handleClick = event => {
|
|
21850
|
-
event.preventDefault();
|
|
21851
|
-
const button = innerRef.current;
|
|
21852
|
-
requestAction(button, boundAction, {
|
|
21853
|
-
event,
|
|
21854
|
-
actionOrigin: "action_prop"
|
|
21855
|
-
});
|
|
21856
|
-
};
|
|
21857
|
-
const innerLoading = loading || actionLoading;
|
|
21858
22087
|
return jsx(ButtonBasic
|
|
21859
22088
|
// put data-action first to help find it in devtools
|
|
21860
22089
|
, {
|
|
@@ -21862,10 +22091,6 @@ const ButtonWithAction = forwardRef((props, ref) => {
|
|
|
21862
22091
|
...rest,
|
|
21863
22092
|
ref: innerRef,
|
|
21864
22093
|
loading: innerLoading,
|
|
21865
|
-
onClick: event => {
|
|
21866
|
-
handleClick(event);
|
|
21867
|
-
onClick?.(event);
|
|
21868
|
-
},
|
|
21869
22094
|
children: children
|
|
21870
22095
|
});
|
|
21871
22096
|
});
|
|
@@ -21874,7 +22099,6 @@ const ButtonInsideForm = forwardRef((props, ref) => {
|
|
|
21874
22099
|
// eslint-disable-next-line no-unused-vars
|
|
21875
22100
|
formContext,
|
|
21876
22101
|
type,
|
|
21877
|
-
onClick,
|
|
21878
22102
|
children,
|
|
21879
22103
|
loading,
|
|
21880
22104
|
readOnly,
|
|
@@ -21882,50 +22106,14 @@ const ButtonInsideForm = forwardRef((props, ref) => {
|
|
|
21882
22106
|
} = props;
|
|
21883
22107
|
const innerRef = useRef();
|
|
21884
22108
|
useImperativeHandle(ref, () => innerRef.current);
|
|
21885
|
-
const wouldSubmitFormByType = type === "submit" || type === "image";
|
|
21886
22109
|
const innerLoading = loading;
|
|
21887
22110
|
const innerReadOnly = readOnly;
|
|
21888
|
-
const handleClick = event => {
|
|
21889
|
-
const buttonElement = innerRef.current;
|
|
21890
|
-
const {
|
|
21891
|
-
form
|
|
21892
|
-
} = buttonElement;
|
|
21893
|
-
let wouldSubmitForm = wouldSubmitFormByType;
|
|
21894
|
-
if (!wouldSubmitForm && type === undefined) {
|
|
21895
|
-
const formSubmitButton = form.querySelector("button[type='submit'], input[type='submit'], input[type='image']");
|
|
21896
|
-
const wouldSubmitFormBecauseSingleButtonWithoutType = !formSubmitButton;
|
|
21897
|
-
wouldSubmitForm = wouldSubmitFormBecauseSingleButtonWithoutType;
|
|
21898
|
-
}
|
|
21899
|
-
if (!wouldSubmitForm) {
|
|
21900
|
-
if (buttonElement.hasAttribute("data-readonly")) {
|
|
21901
|
-
event.preventDefault();
|
|
21902
|
-
}
|
|
21903
|
-
return;
|
|
21904
|
-
}
|
|
21905
|
-
// prevent default behavior that would submit the form
|
|
21906
|
-
// we want to go through the action execution process (with validation and all)
|
|
21907
|
-
event.preventDefault();
|
|
21908
|
-
form.dispatchEvent(new CustomEvent("actionrequested", {
|
|
21909
|
-
detail: {
|
|
21910
|
-
requester: buttonElement,
|
|
21911
|
-
event,
|
|
21912
|
-
meta: {
|
|
21913
|
-
isSubmit: true
|
|
21914
|
-
},
|
|
21915
|
-
actionOrigin: "action_prop"
|
|
21916
|
-
}
|
|
21917
|
-
}));
|
|
21918
|
-
};
|
|
21919
22111
|
return jsx(ButtonBasic, {
|
|
21920
22112
|
...rest,
|
|
21921
22113
|
ref: innerRef,
|
|
21922
22114
|
type: type,
|
|
21923
22115
|
loading: innerLoading,
|
|
21924
22116
|
readOnly: innerReadOnly,
|
|
21925
|
-
onClick: event => {
|
|
21926
|
-
handleClick(event);
|
|
21927
|
-
onClick?.(event);
|
|
21928
|
-
},
|
|
21929
22117
|
children: children
|
|
21930
22118
|
});
|
|
21931
22119
|
});
|
|
@@ -21939,7 +22127,6 @@ const ButtonWithActionInsideForm = forwardRef((props, ref) => {
|
|
|
21939
22127
|
action,
|
|
21940
22128
|
loading,
|
|
21941
22129
|
children,
|
|
21942
|
-
onClick,
|
|
21943
22130
|
onActionPrevented,
|
|
21944
22131
|
onActionStart,
|
|
21945
22132
|
onActionAbort,
|
|
@@ -21988,16 +22175,8 @@ const ButtonWithActionInsideForm = forwardRef((props, ref) => {
|
|
|
21988
22175
|
ref: innerRef,
|
|
21989
22176
|
type: type,
|
|
21990
22177
|
loading: innerLoading,
|
|
21991
|
-
|
|
21992
|
-
|
|
21993
|
-
const form = button.form;
|
|
21994
|
-
event.preventDefault();
|
|
21995
|
-
requestAction(form, actionBoundToFormParams, {
|
|
21996
|
-
event,
|
|
21997
|
-
requester: button,
|
|
21998
|
-
actionOrigin: "action_prop"
|
|
21999
|
-
});
|
|
22000
|
-
onClick?.(event);
|
|
22178
|
+
onactionrequested: e => {
|
|
22179
|
+
forwardActionRequested(e, actionBoundToFormParams, e.target.form);
|
|
22001
22180
|
},
|
|
22002
22181
|
children: children
|
|
22003
22182
|
});
|
|
@@ -22148,8 +22327,7 @@ const CheckboxListWithAction = forwardRef((props, ref) => {
|
|
|
22148
22327
|
const checkbox = event.target;
|
|
22149
22328
|
requestAction(checkboxList, boundAction, {
|
|
22150
22329
|
event,
|
|
22151
|
-
requester: checkbox
|
|
22152
|
-
actionOrigin: "action_prop"
|
|
22330
|
+
requester: checkbox
|
|
22153
22331
|
});
|
|
22154
22332
|
},
|
|
22155
22333
|
loading: loading || actionLoading,
|
|
@@ -22374,13 +22552,7 @@ const FormWithAction = forwardRef((props, ref) => {
|
|
|
22374
22552
|
useActionEvents(innerRef, {
|
|
22375
22553
|
onPrevented: onActionPrevented,
|
|
22376
22554
|
onRequested: e => {
|
|
22377
|
-
|
|
22378
|
-
requestAction(form, actionBoundToUIState, {
|
|
22379
|
-
requester: e.detail?.requester,
|
|
22380
|
-
event: e.detail?.event || e,
|
|
22381
|
-
meta: e.detail?.meta,
|
|
22382
|
-
actionOrigin: e.detail?.actionOrigin
|
|
22383
|
-
});
|
|
22555
|
+
forwardActionRequested(e, actionBoundToUIState);
|
|
22384
22556
|
},
|
|
22385
22557
|
onAction: e => {
|
|
22386
22558
|
const form = innerRef.current;
|
|
@@ -22416,15 +22588,6 @@ const FormWithAction = forwardRef((props, ref) => {
|
|
|
22416
22588
|
...rest,
|
|
22417
22589
|
ref: innerRef,
|
|
22418
22590
|
loading: innerLoading,
|
|
22419
|
-
onrequestsubmit: e => {
|
|
22420
|
-
// prevent "submit" event that would be dispatched by the browser after form.requestSubmit()
|
|
22421
|
-
// (not super important because our <form> listen the "action" and do does preventDefault on "submit")
|
|
22422
|
-
e.preventDefault();
|
|
22423
|
-
requestAction(e.target, actionBoundToUIState, {
|
|
22424
|
-
event: e,
|
|
22425
|
-
actionOrigin: "action_prop"
|
|
22426
|
-
});
|
|
22427
|
-
},
|
|
22428
22591
|
children: jsx(FormActionContext.Provider, {
|
|
22429
22592
|
value: actionBoundToUIState,
|
|
22430
22593
|
children: jsx(LoadingElementContext.Provider, {
|
|
@@ -22591,8 +22754,7 @@ const RadioListWithAction = forwardRef((props, ref) => {
|
|
|
22591
22754
|
const radioListContainer = innerRef.current;
|
|
22592
22755
|
requestAction(radioListContainer, boundAction, {
|
|
22593
22756
|
event: e,
|
|
22594
|
-
requester: radio
|
|
22595
|
-
actionOrigin: "action_prop"
|
|
22757
|
+
requester: radio
|
|
22596
22758
|
});
|
|
22597
22759
|
},
|
|
22598
22760
|
loading: loading || actionLoading,
|
|
@@ -22800,8 +22962,7 @@ const SelectWithAction = forwardRef((props, ref) => {
|
|
|
22800
22962
|
const optionSelected = select.querySelector(`option[value="${selectedValue}"]`);
|
|
22801
22963
|
requestAction(radioListContainer, boundAction, {
|
|
22802
22964
|
event,
|
|
22803
|
-
requester: optionSelected
|
|
22804
|
-
actionOrigin: "action_prop"
|
|
22965
|
+
requester: optionSelected
|
|
22805
22966
|
});
|
|
22806
22967
|
},
|
|
22807
22968
|
...rest,
|
|
@@ -28522,4 +28683,4 @@ const useDependenciesDiff = (inputs) => {
|
|
|
28522
28683
|
return diffRef.current;
|
|
28523
28684
|
};
|
|
28524
28685
|
|
|
28525
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Button, Checkbox, CheckboxList, Col, Colgroup, Details, Editable, ErrorBoundaryContext, FlexColumn, FlexItem, FlexRow, FontSizedSvg, Form, Icon, IconAndText, Input, Label, Link, LinkWithIcon, Overflow, Radio, RadioList, Route, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, Select, SelectionContext, Spacing, SummaryMarker, Tab, TabList, Table, TableCell, Tbody, Text, TextAndCount, Thead, Tr, UITransition, actionIntegratedVia, addCustomMessage, createAction, createSelectionKeyboardShortcuts, createUniqueValueConstraint, defineRoutes, enableDebugActions, enableDebugOnDocumentLoading, goBack, goForward, goTo, isCellSelected, isColumnSelected, isRowSelected, openCallout, rawUrlPart, reload, removeCustomMessage, rerunActions, resource, setBaseUrl, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useCellsAndColumns, useDependenciesDiff, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, valueInLocalStorage };
|
|
28686
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Button, Checkbox, CheckboxList, Col, Colgroup, Details, Editable, ErrorBoundaryContext, FlexColumn, FlexItem, FlexRow, FontSizedSvg, Form, Icon, IconAndText, Input, Label, Link, LinkWithIcon, Overflow, Radio, RadioList, Route, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, Select, SelectionContext, Spacing, SummaryMarker, Tab, TabList, Table, TableCell, Tbody, Text, TextAndCount, Thead, Tr, UITransition, actionIntegratedVia, addCustomMessage, createAction, createSelectionKeyboardShortcuts, createUniqueValueConstraint, defineRoutes, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, goBack, goForward, goTo, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, openCallout, rawUrlPart, reload, removeCustomMessage, rerunActions, resource, setBaseUrl, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useCellsAndColumns, useDependenciesDiff, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, valueInLocalStorage };
|