@jsenv/navi 0.14.16 → 0.14.18

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.
@@ -4,7 +4,7 @@ import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
4
4
  import { createIterableWeakSet, mergeOneStyle, stringifyStyle, createPubSub, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, resolveCSSSize, findBefore, findAfter, createValueEffect, createStyleController, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, hasCSSSizeUnit, activeElementSignal, canInterceptKeys, pickLightOrDark, resolveColorLuminance, initFocusGroup, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, elementIsFocusable } from "@jsenv/dom";
5
5
  import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
6
6
  import { effect, signal, computed, batch, useSignal } from "@preact/signals";
7
- import { createContext, toChildArray, createRef, cloneElement } from "preact";
7
+ import { createContext, render, isValidElement, toChildArray, createRef, cloneElement } from "preact";
8
8
  import { createPortal, forwardRef } from "preact/compat";
9
9
 
10
10
  const actionPrivatePropertiesWeakMap = new WeakMap();
@@ -4736,6 +4736,23 @@ const DIMENSION_PROPS = {
4736
4736
  height: PASS_THROUGH,
4737
4737
  minHeight: PASS_THROUGH,
4738
4738
  maxHeight: PASS_THROUGH,
4739
+ square: (v) => {
4740
+ if (!v) {
4741
+ return null;
4742
+ }
4743
+ return {
4744
+ aspectRatio: "1/1",
4745
+ };
4746
+ },
4747
+ circle: (v) => {
4748
+ if (!v) {
4749
+ return null;
4750
+ }
4751
+ return {
4752
+ aspectRatio: "1/1",
4753
+ borderRadius: "100%",
4754
+ };
4755
+ },
4739
4756
  expand: applyOnTwoProps("expandX", "expandY"),
4740
4757
  shrink: applyOnTwoProps("shrinkX", "shrinkY"),
4741
4758
  // apply after width/height to override if both are set
@@ -5079,7 +5096,14 @@ const getNormalizer = (key) => {
5079
5096
  if (group === "typo") {
5080
5097
  return normalizeTypoStyle;
5081
5098
  }
5082
- return stringifyStyle;
5099
+ return normalizeRegularStyle;
5100
+ };
5101
+ const normalizeRegularStyle = (
5102
+ value,
5103
+ name,
5104
+ // styleContext, context
5105
+ ) => {
5106
+ return stringifyStyle(value, name);
5083
5107
  };
5084
5108
  const getHowToHandleStyleProp = (name) => {
5085
5109
  const getStyle = All_PROPS[name];
@@ -5625,12 +5649,18 @@ const initPseudoStyles = (
5625
5649
  return teardown;
5626
5650
  };
5627
5651
 
5628
- const applyStyle = (element, style, pseudoState, pseudoNamedStyles) => {
5652
+ const applyStyle = (
5653
+ element,
5654
+ style,
5655
+ pseudoState,
5656
+ pseudoNamedStyles,
5657
+ preventInitialTransition,
5658
+ ) => {
5629
5659
  if (!element) {
5630
5660
  return;
5631
5661
  }
5632
5662
  const styleToApply = getStyleToApply(style, pseudoState, pseudoNamedStyles);
5633
- updateStyle(element, styleToApply);
5663
+ updateStyle(element, styleToApply, preventInitialTransition);
5634
5664
  };
5635
5665
 
5636
5666
  const PSEUDO_STATE_DEFAULT = {};
@@ -5684,11 +5714,10 @@ const getStyleToApply = (styles, pseudoState, pseudoNamedStyles) => {
5684
5714
  };
5685
5715
 
5686
5716
  const styleKeySetWeakMap = new WeakMap();
5687
- const elementTransitionStateWeakMap = new WeakMap();
5717
+ const elementTransitionWeakMap = new WeakMap();
5688
5718
  const elementRenderedWeakSet = new WeakSet();
5689
5719
  const NO_STYLE_KEY_SET = new Set();
5690
-
5691
- const updateStyle = (element, style) => {
5720
+ const updateStyle = (element, style, preventInitialTransition) => {
5692
5721
  const styleKeySet = style ? new Set(Object.keys(style)) : NO_STYLE_KEY_SET;
5693
5722
  const oldStyleKeySet = styleKeySetWeakMap.get(element) || NO_STYLE_KEY_SET;
5694
5723
  // TRANSITION ANTI-FLICKER STRATEGY:
@@ -5703,26 +5732,26 @@ const updateStyle = (element, style) => {
5703
5732
  let styleKeySetToApply = styleKeySet;
5704
5733
  if (!elementRenderedWeakSet.has(element)) {
5705
5734
  const hasTransition = styleKeySet.has("transition");
5706
- if (hasTransition) {
5707
- if (elementTransitionStateWeakMap.has(element)) {
5708
- elementTransitionStateWeakMap.set(element, style.transition);
5735
+ if (hasTransition || preventInitialTransition) {
5736
+ if (elementTransitionWeakMap.has(element)) {
5737
+ elementTransitionWeakMap.set(element, style?.transition);
5709
5738
  } else {
5710
5739
  element.style.transition = "none";
5711
- elementTransitionStateWeakMap.set(element, style.transition);
5740
+ elementTransitionWeakMap.set(element, style?.transition);
5712
5741
  }
5713
5742
  // Don't apply the transition property now - we've set it to "none" temporarily
5714
5743
  styleKeySetToApply = new Set(styleKeySet);
5715
5744
  styleKeySetToApply.delete("transition");
5716
5745
  }
5717
5746
  requestAnimationFrame(() => {
5718
- if (elementTransitionStateWeakMap.has(element)) {
5719
- const transitionToRestore = elementTransitionStateWeakMap.get(element);
5747
+ if (elementTransitionWeakMap.has(element)) {
5748
+ const transitionToRestore = elementTransitionWeakMap.get(element);
5720
5749
  if (transitionToRestore === undefined) {
5721
5750
  element.style.transition = "";
5722
5751
  } else {
5723
5752
  element.style.transition = transitionToRestore;
5724
5753
  }
5725
- elementTransitionStateWeakMap.delete(element);
5754
+ elementTransitionWeakMap.delete(element);
5726
5755
  }
5727
5756
  elementRenderedWeakSet.add(element);
5728
5757
  });
@@ -5798,6 +5827,10 @@ const Box = props => {
5798
5827
  // -> introduced for <Input /> with a wrapped for loading, checkboxes, etc
5799
5828
  pseudoStateSelector,
5800
5829
  hasChildFunction,
5830
+ // preventInitialTransition can be used to prevent transition on mount
5831
+ // (when transition is set via props, this is done automatically)
5832
+ // so this prop is useful only when transition is enabled from "outside" (via CSS)
5833
+ preventInitialTransition,
5801
5834
  children,
5802
5835
  ...rest
5803
5836
  } = props;
@@ -5851,7 +5884,7 @@ const Box = props => {
5851
5884
  // Style context dependencies
5852
5885
  styleCSSVars, pseudoClasses, pseudoElements,
5853
5886
  // Selectors
5854
- visualSelector, pseudoStateSelector];
5887
+ visualSelector, pseudoStateSelector, preventInitialTransition];
5855
5888
  let innerPseudoState;
5856
5889
  if (basePseudoState && pseudoState) {
5857
5890
  innerPseudoState = {};
@@ -6059,7 +6092,7 @@ const Box = props => {
6059
6092
  }
6060
6093
  const updateStyle = useCallback(state => {
6061
6094
  const boxEl = ref.current;
6062
- applyStyle(boxEl, boxStyles, state, boxPseudoNamedStyles);
6095
+ applyStyle(boxEl, boxStyles, state, boxPseudoNamedStyles, preventInitialTransition);
6063
6096
  }, styleDeps);
6064
6097
  const finalStyleDeps = [pseudoStateSelector, innerPseudoState, updateStyle];
6065
6098
  // By default ":hover", ":active" are not tracked.
@@ -11204,6 +11237,20 @@ const useAutoFocus = (
11204
11237
  }, []);
11205
11238
  };
11206
11239
 
11240
+ const CalloutCloseContext = createContext();
11241
+ const useCalloutClose = () => {
11242
+ return useContext(CalloutCloseContext);
11243
+ };
11244
+ const renderIntoCallout = (jsx$1, calloutMessageElement, {
11245
+ close
11246
+ }) => {
11247
+ const calloutJsx = jsx(CalloutCloseContext.Provider, {
11248
+ value: close,
11249
+ children: jsx$1
11250
+ });
11251
+ render(calloutJsx, calloutMessageElement);
11252
+ };
11253
+
11207
11254
  installImportMetaCss(import.meta);
11208
11255
  /**
11209
11256
  * A callout component that mimics native browser validation messages.
@@ -11310,6 +11357,10 @@ import.meta.css = /* css */ `
11310
11357
  }
11311
11358
 
11312
11359
  .navi_callout_message {
11360
+ position: relative;
11361
+ display: inline-flex;
11362
+ box-sizing: border-box;
11363
+ box-decoration-break: clone;
11313
11364
  min-width: 0;
11314
11365
  align-self: center;
11315
11366
  word-break: break-word;
@@ -11452,28 +11503,44 @@ const openCallout = (
11452
11503
  closeOnClickOutside = options.closeOnClickOutside;
11453
11504
  }
11454
11505
 
11455
- if (Error.isError(newMessage)) {
11456
- const error = newMessage;
11457
- newMessage = error.message;
11458
- if (showErrorStack && error.stack) {
11459
- newMessage += `<pre class="navi_callout_error_stack">${escapeHtml(String(error.stack))}</pre>`;
11506
+ if (isValidElement(newMessage)) {
11507
+ calloutMessageElement.innerHTML = "";
11508
+ renderIntoCallout(newMessage, calloutMessageElement, { close });
11509
+ } else if (newMessage instanceof Node) {
11510
+ // Handle DOM node (cloned from CSS selector)
11511
+ calloutMessageElement.innerHTML = "";
11512
+ calloutMessageElement.appendChild(newMessage);
11513
+ } else if (typeof newMessage === "function") {
11514
+ calloutMessageElement.innerHTML = "";
11515
+ newMessage({
11516
+ renderIntoCallout: (jsx) =>
11517
+ renderIntoCallout(jsx, calloutMessageElement, { close }),
11518
+ close,
11519
+ });
11520
+ } else {
11521
+ if (Error.isError(newMessage)) {
11522
+ const error = newMessage;
11523
+ newMessage = error.message;
11524
+ if (showErrorStack && error.stack) {
11525
+ newMessage += `<pre class="navi_callout_error_stack">${escapeHtml(String(error.stack))}</pre>`;
11526
+ }
11460
11527
  }
11461
- }
11462
11528
 
11463
- // Check if the message is a full HTML document (starts with DOCTYPE)
11464
- if (typeof newMessage === "string" && isHtmlDocument(newMessage)) {
11465
- // Create iframe to isolate the HTML document
11466
- const iframe = document.createElement("iframe");
11467
- iframe.style.border = "none";
11468
- iframe.style.width = "100%";
11469
- iframe.style.backgroundColor = "white";
11470
- iframe.srcdoc = newMessage;
11529
+ // Check if the message is a full HTML document (starts with DOCTYPE)
11530
+ if (typeof newMessage === "string" && isHtmlDocument(newMessage)) {
11531
+ // Create iframe to isolate the HTML document
11532
+ const iframe = document.createElement("iframe");
11533
+ iframe.style.border = "none";
11534
+ iframe.style.width = "100%";
11535
+ iframe.style.backgroundColor = "white";
11536
+ iframe.srcdoc = newMessage;
11471
11537
 
11472
- // Clear existing content and add iframe
11473
- calloutMessageElement.innerHTML = "";
11474
- calloutMessageElement.appendChild(iframe);
11475
- } else {
11476
- calloutMessageElement.innerHTML = newMessage;
11538
+ // Clear existing content and add iframe
11539
+ calloutMessageElement.innerHTML = "";
11540
+ calloutMessageElement.appendChild(iframe);
11541
+ } else {
11542
+ calloutMessageElement.innerHTML = newMessage;
11543
+ }
11477
11544
  }
11478
11545
  };
11479
11546
  {
@@ -11502,6 +11569,15 @@ const openCallout = (
11502
11569
  document.removeEventListener("click", handleClickOutside, true);
11503
11570
  });
11504
11571
  }
11572
+ {
11573
+ const handleCustomCloseEvent = () => {
11574
+ close("custom_event");
11575
+ };
11576
+ calloutElement.addEventListener(
11577
+ "navi_callout_close",
11578
+ handleCustomCloseEvent,
11579
+ );
11580
+ }
11505
11581
  Object.assign(callout, {
11506
11582
  element: calloutElement,
11507
11583
  update,
@@ -12234,6 +12310,189 @@ const generateSvgWithoutArrow = (width, height) => {
12234
12310
  </svg>`;
12235
12311
  };
12236
12312
 
12313
+ /**
12314
+ * Creates a live mirror of a source DOM element that automatically stays in sync.
12315
+ *
12316
+ * The mirror is implemented as a custom element (`<navi-mirror>`) that:
12317
+ * - Copies the source element's content (innerHTML) and attributes
12318
+ * - Automatically updates when the source element changes
12319
+ * - Efficiently manages observers based on DOM presence (starts observing when
12320
+ * added to DOM, stops when removed)
12321
+ * - Excludes the 'id' attribute to avoid conflicts
12322
+ *
12323
+ * @param {Element} sourceElement - The DOM element to mirror. Any changes to this
12324
+ * element's content, attributes, or structure will be automatically reflected
12325
+ * in the returned mirror element.
12326
+ *
12327
+ * @returns {NaviMirror} A custom element that mirrors the source element. Can be
12328
+ * inserted into the DOM like any other element. The mirror will automatically
12329
+ * start/stop observing the source based on its DOM presence.
12330
+ */
12331
+ const createNaviMirror = (sourceElement) => {
12332
+ const naviMirror = new NaviMirror(sourceElement);
12333
+ return naviMirror;
12334
+ };
12335
+
12336
+ // Custom element that mirrors another element's content
12337
+ class NaviMirror extends HTMLElement {
12338
+ constructor(sourceElement) {
12339
+ super();
12340
+ this.sourceElement = null;
12341
+ this.sourceObserver = null;
12342
+ this.setSourceElement(sourceElement);
12343
+ }
12344
+
12345
+ setSourceElement(sourceElement) {
12346
+ this.sourceElement = sourceElement;
12347
+ this.updateFromSource();
12348
+ }
12349
+
12350
+ updateFromSource() {
12351
+ if (!this.sourceElement) return;
12352
+
12353
+ this.innerHTML = this.sourceElement.innerHTML;
12354
+ // Copy attributes from source (except id to avoid conflicts)
12355
+ for (const attr of Array.from(this.sourceElement.attributes)) {
12356
+ if (attr.name !== "id") {
12357
+ this.setAttribute(attr.name, attr.value);
12358
+ }
12359
+ }
12360
+ }
12361
+
12362
+ startObserving() {
12363
+ if (this.sourceObserver || !this.sourceElement) return;
12364
+ this.sourceObserver = new MutationObserver(() => {
12365
+ this.updateFromSource();
12366
+ });
12367
+ this.sourceObserver.observe(this.sourceElement, {
12368
+ childList: true,
12369
+ subtree: true,
12370
+ attributes: true,
12371
+ characterData: true,
12372
+ });
12373
+ }
12374
+
12375
+ stopObserving() {
12376
+ if (this.sourceObserver) {
12377
+ this.sourceObserver.disconnect();
12378
+ this.sourceObserver = null;
12379
+ }
12380
+ }
12381
+
12382
+ // Called when element is added to DOM
12383
+ connectedCallback() {
12384
+ this.startObserving();
12385
+ }
12386
+
12387
+ // Called when element is removed from DOM
12388
+ disconnectedCallback() {
12389
+ this.stopObserving();
12390
+ }
12391
+ }
12392
+
12393
+ // Register the custom element if not already registered
12394
+ if (!customElements.get("navi-mirror")) {
12395
+ customElements.define("navi-mirror", NaviMirror);
12396
+ }
12397
+
12398
+ const getMessageFromAttribute = (
12399
+ originalElement,
12400
+ attributeName,
12401
+ generatedMessage,
12402
+ ) => {
12403
+ const selectorAttributeName = `${attributeName}-selector`;
12404
+ const eventAttributeName = `${attributeName}-event`;
12405
+ const resolutionSteps = [
12406
+ {
12407
+ description: "original element",
12408
+ element: originalElement,
12409
+ },
12410
+ {
12411
+ description: "closest fieldset",
12412
+ element: originalElement.closest("fieldset"),
12413
+ },
12414
+ {
12415
+ description: "closest form",
12416
+ element: originalElement.closest("form"),
12417
+ },
12418
+ ];
12419
+ // Sub-steps for each element (in order of priority)
12420
+ const subSteps = ["event", "selector", "message"];
12421
+ let currentStepIndex = 0;
12422
+ let currentSubStepIndex = 0;
12423
+ const resolve = () => {
12424
+ while (currentStepIndex < resolutionSteps.length) {
12425
+ const { element } = resolutionSteps[currentStepIndex];
12426
+ if (element) {
12427
+ while (currentSubStepIndex < subSteps.length) {
12428
+ const subStep = subSteps[currentSubStepIndex];
12429
+ currentSubStepIndex++;
12430
+ if (subStep === "event") {
12431
+ const eventAttribute = element.getAttribute(eventAttributeName);
12432
+ if (eventAttribute) {
12433
+ return createEventHandler(element, eventAttribute);
12434
+ }
12435
+ }
12436
+ if (subStep === "selector") {
12437
+ const selectorAttribute = element.getAttribute(
12438
+ selectorAttributeName,
12439
+ );
12440
+ if (selectorAttribute) {
12441
+ return fromSelectorAttribute(selectorAttribute);
12442
+ }
12443
+ }
12444
+ if (subStep === "message") {
12445
+ const messageAttribute = element.getAttribute(attributeName);
12446
+ if (messageAttribute) {
12447
+ return messageAttribute;
12448
+ }
12449
+ }
12450
+ }
12451
+ }
12452
+ currentStepIndex++;
12453
+ currentSubStepIndex = 0;
12454
+ }
12455
+ return generatedMessage;
12456
+ };
12457
+
12458
+ const createEventHandler = (element, eventName) => {
12459
+ return ({ renderIntoCallout }) => {
12460
+ element.dispatchEvent(
12461
+ new CustomEvent(eventName, {
12462
+ detail: {
12463
+ render: (message) => {
12464
+ if (message) {
12465
+ renderIntoCallout(message);
12466
+ } else {
12467
+ // Resume resolution from next step
12468
+ const nextResult = resolve();
12469
+ renderIntoCallout(nextResult);
12470
+ }
12471
+ },
12472
+ },
12473
+ }),
12474
+ );
12475
+ };
12476
+ };
12477
+
12478
+ return resolve();
12479
+ };
12480
+
12481
+ // Helper function to resolve messages that might be CSS selectors
12482
+ const fromSelectorAttribute = (messageAttributeValue) => {
12483
+ // It's a CSS selector, find the DOM element
12484
+ const messageSourceElement = document.querySelector(messageAttributeValue);
12485
+ if (!messageSourceElement) {
12486
+ console.warn(
12487
+ `Message selector "${messageAttributeValue}" not found in DOM`,
12488
+ );
12489
+ return null; // Fallback to the generic message
12490
+ }
12491
+ const mirror = createNaviMirror(messageSourceElement);
12492
+ mirror.setAttribute("data-source-selector", messageAttributeValue);
12493
+ return mirror;
12494
+ };
12495
+
12237
12496
  const generateFieldInvalidMessage = (template, { field }) => {
12238
12497
  return replaceStringVars(template, {
12239
12498
  "{field}": () => generateThisFieldText(field),
@@ -12271,6 +12530,7 @@ const replaceStringVars = (string, replacers) => {
12271
12530
 
12272
12531
  const MIN_LOWER_LETTER_CONSTRAINT = {
12273
12532
  name: "min_lower_letter",
12533
+ messageAttribute: "data-min-lower-letter-message",
12274
12534
  check: (field) => {
12275
12535
  const fieldValue = field.value;
12276
12536
  if (!fieldValue && !field.required) {
@@ -12288,12 +12548,6 @@ const MIN_LOWER_LETTER_CONSTRAINT = {
12288
12548
  }
12289
12549
  }
12290
12550
  if (numberOfLowercaseChars < min) {
12291
- const messageAttribute = field.getAttribute(
12292
- "data-min-lower-letter-message",
12293
- );
12294
- if (messageAttribute) {
12295
- return messageAttribute;
12296
- }
12297
12551
  if (min === 0) {
12298
12552
  return generateFieldInvalidMessage(
12299
12553
  `{field} doit contenir au moins une lettre minuscule.`,
@@ -12310,6 +12564,7 @@ const MIN_LOWER_LETTER_CONSTRAINT = {
12310
12564
  };
12311
12565
  const MIN_UPPER_LETTER_CONSTRAINT = {
12312
12566
  name: "min_upper_letter",
12567
+ messageAttribute: "data-min-upper-letter-message",
12313
12568
  check: (field) => {
12314
12569
  const fieldValue = field.value;
12315
12570
  if (!fieldValue && !field.required) {
@@ -12327,12 +12582,6 @@ const MIN_UPPER_LETTER_CONSTRAINT = {
12327
12582
  }
12328
12583
  }
12329
12584
  if (numberOfUppercaseChars < min) {
12330
- const messageAttribute = field.getAttribute(
12331
- "data-min-upper-letter-message",
12332
- );
12333
- if (messageAttribute) {
12334
- return messageAttribute;
12335
- }
12336
12585
  if (min === 0) {
12337
12586
  return generateFieldInvalidMessage(
12338
12587
  `{field} doit contenir au moins une lettre majuscule.`,
@@ -12349,6 +12598,7 @@ const MIN_UPPER_LETTER_CONSTRAINT = {
12349
12598
  };
12350
12599
  const MIN_DIGIT_CONSTRAINT = {
12351
12600
  name: "min_digit",
12601
+ messageAttribute: "data-min-digit-message",
12352
12602
  check: (field) => {
12353
12603
  const fieldValue = field.value;
12354
12604
  if (!fieldValue && !field.required) {
@@ -12366,10 +12616,6 @@ const MIN_DIGIT_CONSTRAINT = {
12366
12616
  }
12367
12617
  }
12368
12618
  if (numberOfDigitChars < min) {
12369
- const messageAttribute = field.getAttribute("data-min-digit-message");
12370
- if (messageAttribute) {
12371
- return messageAttribute;
12372
- }
12373
12619
  if (min === 0) {
12374
12620
  return generateFieldInvalidMessage(
12375
12621
  `{field} doit contenir au moins un chiffre.`,
@@ -12384,44 +12630,39 @@ const MIN_DIGIT_CONSTRAINT = {
12384
12630
  return "";
12385
12631
  },
12386
12632
  };
12387
- const MIN_SPECIAL_CHARS_CONSTRAINT = {
12388
- name: "min_special_chars",
12633
+ const MIN_SPECIAL_CHAR_CONSTRAINT = {
12634
+ name: "min_special_char",
12635
+ messageAttribute: "data-min-special-char-message",
12389
12636
  check: (field) => {
12390
12637
  const fieldValue = field.value;
12391
12638
  if (!fieldValue && !field.required) {
12392
12639
  return "";
12393
12640
  }
12394
- const minSpecialChars = field.getAttribute("data-min-special-chars");
12641
+ const minSpecialChars = field.getAttribute("data-min-special-char");
12395
12642
  if (!minSpecialChars) {
12396
12643
  return "";
12397
12644
  }
12398
12645
  const min = parseInt(minSpecialChars, 10);
12399
- const specialChars = field.getAttribute("data-special-chars");
12400
- if (!specialChars) {
12401
- return "L'attribut data-special-chars doit être défini pour utiliser data-min-special-chars.";
12646
+ const specialCharset = field.getAttribute("data-special-charset");
12647
+ if (!specialCharset) {
12648
+ return "L'attribut data-special-charset doit être défini pour utiliser data-min-special-char.";
12402
12649
  }
12403
12650
 
12404
12651
  let numberOfSpecialChars = 0;
12405
12652
  for (const char of fieldValue) {
12406
- if (specialChars.includes(char)) {
12653
+ if (specialCharset.includes(char)) {
12407
12654
  numberOfSpecialChars++;
12408
12655
  }
12409
12656
  }
12410
12657
  if (numberOfSpecialChars < min) {
12411
- const messageAttribute = field.getAttribute(
12412
- "data-min-special-chars-message",
12413
- );
12414
- if (messageAttribute) {
12415
- return messageAttribute;
12416
- }
12417
12658
  if (min === 1) {
12418
12659
  return generateFieldInvalidMessage(
12419
- `{field} doit contenir au moins un caractère spécial. (${specialChars})`,
12660
+ `{field} doit contenir au moins un caractère spécial. (${specialCharset})`,
12420
12661
  { field },
12421
12662
  );
12422
12663
  }
12423
12664
  return generateFieldInvalidMessage(
12424
- `{field} doit contenir au moins ${min} caractères spéciaux (${specialChars})`,
12665
+ `{field} doit contenir au moins ${min} caractères spéciaux (${specialCharset})`,
12425
12666
  { field },
12426
12667
  );
12427
12668
  }
@@ -12431,6 +12672,7 @@ const MIN_SPECIAL_CHARS_CONSTRAINT = {
12431
12672
 
12432
12673
  const READONLY_CONSTRAINT = {
12433
12674
  name: "readonly",
12675
+ messageAttribute: "data-readonly-message",
12434
12676
  check: (field, { skipReadonly }) => {
12435
12677
  if (skipReadonly) {
12436
12678
  return null;
@@ -12444,25 +12686,21 @@ const READONLY_CONSTRAINT = {
12444
12686
  const isButton = field.tagName === "BUTTON";
12445
12687
  const isBusy = field.getAttribute("aria-busy") === "true";
12446
12688
  const readonlySilent = field.hasAttribute("data-readonly-silent");
12447
- const messageAttribute = field.getAttribute("data-readonly-message");
12448
12689
  if (readonlySilent) {
12449
12690
  return { silent: true };
12450
12691
  }
12451
12692
  if (isBusy) {
12452
12693
  return {
12453
12694
  target: field,
12454
- message:
12455
- messageAttribute || `Cette action est en cours. Veuillez patienter.`,
12695
+ message: `Cette action est en cours. Veuillez patienter.`,
12456
12696
  status: "info",
12457
12697
  };
12458
12698
  }
12459
12699
  return {
12460
12700
  target: field,
12461
- message:
12462
- messageAttribute ||
12463
- (isButton
12464
- ? `Cet action n'est pas disponible pour l'instant.`
12465
- : `Cet élément est en lecture seule et ne peut pas être modifié.`),
12701
+ message: isButton
12702
+ ? `Cet action n'est pas disponible pour l'instant.`
12703
+ : `Cet élément est en lecture seule et ne peut pas être modifié.`,
12466
12704
  status: "info",
12467
12705
  };
12468
12706
  },
@@ -12470,6 +12708,7 @@ const READONLY_CONSTRAINT = {
12470
12708
 
12471
12709
  const SAME_AS_CONSTRAINT = {
12472
12710
  name: "same_as",
12711
+ messageAttribute: "data-same-as-message",
12473
12712
  check: (field) => {
12474
12713
  const sameAs = field.getAttribute("data-same-as");
12475
12714
  if (!sameAs) {
@@ -12494,10 +12733,6 @@ const SAME_AS_CONSTRAINT = {
12494
12733
  if (fieldValue === otherFieldValue) {
12495
12734
  return null;
12496
12735
  }
12497
- const messageAttribute = field.getAttribute("data-same-as-message");
12498
- if (messageAttribute) {
12499
- return messageAttribute;
12500
- }
12501
12736
  const type = field.type;
12502
12737
  if (type === "password") {
12503
12738
  return `Ce mot de passe doit être identique au précédent.`;
@@ -12511,6 +12746,7 @@ const SAME_AS_CONSTRAINT = {
12511
12746
 
12512
12747
  const SINGLE_SPACE_CONSTRAINT = {
12513
12748
  name: "single_space",
12749
+ messageAttribute: "data-single-space-message",
12514
12750
  check: (field) => {
12515
12751
  const singleSpace = field.hasAttribute("data-single-space");
12516
12752
  if (!singleSpace) {
@@ -12521,10 +12757,6 @@ const SINGLE_SPACE_CONSTRAINT = {
12521
12757
  const hasTrailingSpace = fieldValue.endsWith(" ");
12522
12758
  const hasDoubleSpace = fieldValue.includes(" ");
12523
12759
  if (hasLeadingSpace || hasDoubleSpace || hasTrailingSpace) {
12524
- const messageAttribute = field.getAttribute("data-single-space-message");
12525
- if (messageAttribute) {
12526
- return messageAttribute;
12527
- }
12528
12760
  if (hasLeadingSpace) {
12529
12761
  return generateFieldInvalidMessage(
12530
12762
  `{field} ne doit pas commencer par un espace.`,
@@ -12555,6 +12787,7 @@ const SINGLE_SPACE_CONSTRAINT = {
12555
12787
  // in our case it's just here in case some code is wrongly calling "requestAction" or "checkValidity" on a disabled element
12556
12788
  const DISABLED_CONSTRAINT = {
12557
12789
  name: "disabled",
12790
+ messageAttribute: "data-disabled-message",
12558
12791
  check: (field) => {
12559
12792
  if (field.disabled) {
12560
12793
  return generateFieldInvalidMessage(`{field} est désactivé.`, { field });
@@ -12565,17 +12798,14 @@ const DISABLED_CONSTRAINT = {
12565
12798
 
12566
12799
  const REQUIRED_CONSTRAINT = {
12567
12800
  name: "required",
12801
+ messageAttribute: "data-required-message",
12568
12802
  check: (field, { registerChange }) => {
12569
12803
  if (!field.required) {
12570
12804
  return null;
12571
12805
  }
12572
- const messageAttribute = field.getAttribute("data-required-message");
12573
12806
 
12574
12807
  if (field.type === "checkbox") {
12575
12808
  if (!field.checked) {
12576
- if (messageAttribute) {
12577
- return messageAttribute;
12578
- }
12579
12809
  return `Veuillez cocher cette case.`;
12580
12810
  }
12581
12811
  return null;
@@ -12586,19 +12816,12 @@ const REQUIRED_CONSTRAINT = {
12586
12816
  if (!name) {
12587
12817
  // If no name, check just this radio
12588
12818
  if (!field.checked) {
12589
- if (messageAttribute) {
12590
- return messageAttribute;
12591
- }
12592
12819
  return `Veuillez sélectionner une option.`;
12593
12820
  }
12594
12821
  return null;
12595
12822
  }
12596
12823
 
12597
12824
  const closestFieldset = field.closest("fieldset");
12598
- const fieldsetRequiredMessage = closestFieldset
12599
- ? closestFieldset.getAttribute("data-required-message")
12600
- : null;
12601
-
12602
12825
  // Find the container (form or closest fieldset)
12603
12826
  const container = field.form || closestFieldset || document;
12604
12827
  // Check if any radio with the same name is checked
@@ -12617,10 +12840,7 @@ const REQUIRED_CONSTRAINT = {
12617
12840
  }
12618
12841
 
12619
12842
  return {
12620
- message:
12621
- messageAttribute ||
12622
- fieldsetRequiredMessage ||
12623
- `Veuillez sélectionner une option.`,
12843
+ message: `Veuillez sélectionner une option.`,
12624
12844
  target: closestFieldset
12625
12845
  ? closestFieldset.querySelector("legend")
12626
12846
  : undefined,
@@ -12629,9 +12849,6 @@ const REQUIRED_CONSTRAINT = {
12629
12849
  if (field.value) {
12630
12850
  return null;
12631
12851
  }
12632
- if (messageAttribute) {
12633
- return messageAttribute;
12634
- }
12635
12852
  if (field.type === "password") {
12636
12853
  return field.hasAttribute("data-same-as")
12637
12854
  ? `Veuillez confirmer le mot de passe.`
@@ -12650,6 +12867,7 @@ const REQUIRED_CONSTRAINT = {
12650
12867
 
12651
12868
  const PATTERN_CONSTRAINT = {
12652
12869
  name: "pattern",
12870
+ messageAttribute: "data-pattern-message",
12653
12871
  check: (field) => {
12654
12872
  const pattern = field.pattern;
12655
12873
  if (!pattern) {
@@ -12663,10 +12881,6 @@ const PATTERN_CONSTRAINT = {
12663
12881
  if (regex.test(value)) {
12664
12882
  return null;
12665
12883
  }
12666
- const messageAttribute = field.getAttribute("data-pattern-message");
12667
- if (messageAttribute) {
12668
- return messageAttribute;
12669
- }
12670
12884
  let message = generateFieldInvalidMessage(
12671
12885
  `{field} ne correspond pas au format requis.`,
12672
12886
  { field },
@@ -12683,6 +12897,7 @@ const emailregex =
12683
12897
  /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
12684
12898
  const TYPE_EMAIL_CONSTRAINT = {
12685
12899
  name: "type_email",
12900
+ messageAttribute: "data-type-message",
12686
12901
  check: (field) => {
12687
12902
  if (field.type !== "email") {
12688
12903
  return null;
@@ -12691,17 +12906,10 @@ const TYPE_EMAIL_CONSTRAINT = {
12691
12906
  if (!value && !field.required) {
12692
12907
  return null;
12693
12908
  }
12694
- const messageAttribute = field.getAttribute("data-type-email-message");
12695
12909
  if (!value.includes("@")) {
12696
- if (messageAttribute) {
12697
- return messageAttribute;
12698
- }
12699
12910
  return `Veuillez inclure "@" dans l'adresse e-mail. Il manque un symbole "@" dans ${value}.`;
12700
12911
  }
12701
12912
  if (!emailregex.test(value)) {
12702
- if (messageAttribute) {
12703
- return messageAttribute;
12704
- }
12705
12913
  return `Veuillez saisir une adresse e-mail valide.`;
12706
12914
  }
12707
12915
  return null;
@@ -12710,6 +12918,7 @@ const TYPE_EMAIL_CONSTRAINT = {
12710
12918
 
12711
12919
  const MIN_LENGTH_CONSTRAINT = {
12712
12920
  name: "min_length",
12921
+ messageAttribute: "data-min-length-message",
12713
12922
  check: (field) => {
12714
12923
  if (field.tagName === "INPUT") {
12715
12924
  if (!INPUT_TYPE_SUPPORTING_MIN_LENGTH_SET.has(field.type)) {
@@ -12731,10 +12940,6 @@ const MIN_LENGTH_CONSTRAINT = {
12731
12940
  if (valueLength >= minLength) {
12732
12941
  return null;
12733
12942
  }
12734
- const messageAttribute = field.getAttribute("data-min-length-message");
12735
- if (messageAttribute) {
12736
- return messageAttribute;
12737
- }
12738
12943
  if (valueLength === 1) {
12739
12944
  return generateFieldInvalidMessage(
12740
12945
  `{field} doit contenir au moins ${minLength} caractère (il contient actuellement un seul caractère).`,
@@ -12758,6 +12963,7 @@ const INPUT_TYPE_SUPPORTING_MIN_LENGTH_SET = new Set([
12758
12963
 
12759
12964
  const MAX_LENGTH_CONSTRAINT = {
12760
12965
  name: "max_length",
12966
+ messageAttribute: "data-max-length-message",
12761
12967
  check: (field) => {
12762
12968
  if (field.tagName === "INPUT") {
12763
12969
  if (!INPUT_TYPE_SUPPORTING_MAX_LENGTH_SET.has(field.type)) {
@@ -12775,10 +12981,6 @@ const MAX_LENGTH_CONSTRAINT = {
12775
12981
  if (valueLength <= maxLength) {
12776
12982
  return null;
12777
12983
  }
12778
- const messageAttribute = field.getAttribute("data-max-length-message");
12779
- if (messageAttribute) {
12780
- return messageAttribute;
12781
- }
12782
12984
  return generateFieldInvalidMessage(
12783
12985
  `{field} doit contenir au maximum ${maxLength} caractères (il contient actuellement ${valueLength} caractères).`,
12784
12986
  { field },
@@ -12791,6 +12993,7 @@ const INPUT_TYPE_SUPPORTING_MAX_LENGTH_SET = new Set(
12791
12993
 
12792
12994
  const TYPE_NUMBER_CONSTRAINT = {
12793
12995
  name: "type_number",
12996
+ messageAttribute: "data-type-message",
12794
12997
  check: (field) => {
12795
12998
  if (field.tagName !== "INPUT") {
12796
12999
  return null;
@@ -12803,10 +13006,6 @@ const TYPE_NUMBER_CONSTRAINT = {
12803
13006
  }
12804
13007
  const value = field.valueAsNumber;
12805
13008
  if (isNaN(value)) {
12806
- const messageAttribute = field.getAttribute("data-type-number-message");
12807
- if (messageAttribute) {
12808
- return messageAttribute;
12809
- }
12810
13009
  return generateFieldInvalidMessage(`{field} doit être un nombre.`, {
12811
13010
  field,
12812
13011
  });
@@ -12817,6 +13016,7 @@ const TYPE_NUMBER_CONSTRAINT = {
12817
13016
 
12818
13017
  const MIN_CONSTRAINT = {
12819
13018
  name: "min",
13019
+ messageAttribute: "data-min-message",
12820
13020
  check: (field) => {
12821
13021
  if (field.tagName !== "INPUT") {
12822
13022
  return null;
@@ -12835,10 +13035,6 @@ const MIN_CONSTRAINT = {
12835
13035
  return null;
12836
13036
  }
12837
13037
  if (valueAsNumber < minNumber) {
12838
- const messageAttribute = field.getAttribute("data-min-message");
12839
- if (messageAttribute) {
12840
- return messageAttribute;
12841
- }
12842
13038
  return generateFieldInvalidMessage(
12843
13039
  `{field} doit être supérieur ou égal à <strong>${minString}</strong>.`,
12844
13040
  { field },
@@ -12854,11 +13050,7 @@ const MIN_CONSTRAINT = {
12854
13050
  const [minHours, minMinutes] = min.split(":").map(Number);
12855
13051
  const value = field.value;
12856
13052
  const [hours, minutes] = value.split(":").map(Number);
12857
- const messageAttribute = field.getAttribute("data-min-message");
12858
13053
  if (hours < minHours || (hours === minHours && minMinutes < minutes)) {
12859
- if (messageAttribute) {
12860
- return messageAttribute;
12861
- }
12862
13054
  return generateFieldInvalidMessage(
12863
13055
  `{field} doit être <strong>${min}</strong> ou plus.`,
12864
13056
  { field },
@@ -12877,6 +13069,7 @@ const MIN_CONSTRAINT = {
12877
13069
 
12878
13070
  const MAX_CONSTRAINT = {
12879
13071
  name: "max",
13072
+ messageAttribute: "data-max-message",
12880
13073
  check: (field) => {
12881
13074
  if (field.tagName !== "INPUT") {
12882
13075
  return null;
@@ -12895,10 +13088,6 @@ const MAX_CONSTRAINT = {
12895
13088
  return null;
12896
13089
  }
12897
13090
  if (valueAsNumber > maxNumber) {
12898
- const messageAttribute = field.getAttribute("data-max-message");
12899
- if (messageAttribute) {
12900
- return messageAttribute;
12901
- }
12902
13091
  return generateFieldInvalidMessage(
12903
13092
  `{field} être <strong>${maxAttribute}</strong> ou plus.`,
12904
13093
  { field },
@@ -12915,10 +13104,6 @@ const MAX_CONSTRAINT = {
12915
13104
  const value = field.value;
12916
13105
  const [hours, minutes] = value.split(":").map(Number);
12917
13106
  if (hours > maxHours || (hours === maxHours && maxMinutes > minutes)) {
12918
- const messageAttribute = field.getAttribute("data-max-message");
12919
- if (messageAttribute) {
12920
- return messageAttribute;
12921
- }
12922
13107
  return generateFieldInvalidMessage(
12923
13108
  `{field} doit être <strong>${max}</strong> ou moins.`,
12924
13109
  { field },
@@ -13094,7 +13279,7 @@ const NAVI_CONSTRAINT_SET = new Set([
13094
13279
  // the order matters here, the last constraint is picked first when multiple constraints fail
13095
13280
  // so it's better to keep the most complex constraints at the beginning of the list
13096
13281
  // so the more basic ones shows up first
13097
- MIN_SPECIAL_CHARS_CONSTRAINT,
13282
+ MIN_SPECIAL_CHAR_CONSTRAINT,
13098
13283
  SINGLE_SPACE_CONSTRAINT,
13099
13284
  MIN_DIGIT_CONSTRAINT,
13100
13285
  MIN_UPPER_LETTER_CONSTRAINT,
@@ -13376,6 +13561,21 @@ const installCustomConstraintValidation = (
13376
13561
  typeof checkResult === "string"
13377
13562
  ? { message: checkResult }
13378
13563
  : checkResult;
13564
+ constraintValidityInfo.messageString = constraintValidityInfo.message;
13565
+
13566
+ if (constraint.messageAttribute) {
13567
+ const messageFromAttribute = getMessageFromAttribute(
13568
+ element,
13569
+ constraint.messageAttribute,
13570
+ constraintValidityInfo.message,
13571
+ );
13572
+ if (messageFromAttribute !== constraintValidityInfo.message) {
13573
+ constraintValidityInfo.message = messageFromAttribute;
13574
+ if (typeof messageFromAttribute === "string") {
13575
+ constraintValidityInfo.messageString = messageFromAttribute;
13576
+ }
13577
+ }
13578
+ }
13379
13579
  const thisConstraintFailureInfo = {
13380
13580
  name: constraint.name,
13381
13581
  constraint,
@@ -13409,7 +13609,7 @@ const installCustomConstraintValidation = (
13409
13609
  if (!hasTitleAttribute) {
13410
13610
  // when a constraint is failing browser displays that constraint message if the element has no title attribute.
13411
13611
  // We want to do the same with our message (overriding the browser in the process to get better messages)
13412
- element.setAttribute("title", failedConstraintInfo.message);
13612
+ element.setAttribute("title", failedConstraintInfo.messageString);
13413
13613
  }
13414
13614
  } else {
13415
13615
  if (!hasTitleAttribute) {
@@ -13874,7 +14074,15 @@ const useCustomValidationRef = (elementRef, targetSelector) => {
13874
14074
  return null;
13875
14075
  }
13876
14076
  let target;
13877
- {
14077
+ if (targetSelector) {
14078
+ target = element.querySelector(targetSelector);
14079
+ if (!target) {
14080
+ console.warn(
14081
+ `useCustomValidationRef: targetSelector "${targetSelector}" did not match in element`,
14082
+ );
14083
+ return null;
14084
+ }
14085
+ } else {
13878
14086
  target = element;
13879
14087
  }
13880
14088
  const unsubscribe = subscribe(element, target);
@@ -13912,7 +14120,28 @@ const unsubscribe = (element) => {
13912
14120
  }
13913
14121
  };
13914
14122
 
13915
- const useConstraints = (elementRef, constraints, targetSelector) => {
14123
+ const NO_CONSTRAINTS = [];
14124
+ const useConstraints = (elementRef, props, { targetSelector } = {}) => {
14125
+ const {
14126
+ constraints = NO_CONSTRAINTS,
14127
+ disabledMessage,
14128
+ requiredMessage,
14129
+ patternMessage,
14130
+ minLengthMessage,
14131
+ maxLengthMessage,
14132
+ typeMessage,
14133
+ minMessage,
14134
+ maxMessage,
14135
+ singleSpaceMessage,
14136
+ sameAsMessage,
14137
+ minDigitMessage,
14138
+ minLowerLetterMessage,
14139
+ minUpperLetterMessage,
14140
+ minSpecialCharMessage,
14141
+ availableMessage,
14142
+ ...remainingProps
14143
+ } = props;
14144
+
13916
14145
  const customValidationRef = useCustomValidationRef(
13917
14146
  elementRef,
13918
14147
  targetSelector,
@@ -13930,6 +14159,96 @@ const useConstraints = (elementRef, constraints, targetSelector) => {
13930
14159
  }
13931
14160
  };
13932
14161
  }, constraints);
14162
+
14163
+ useLayoutEffect(() => {
14164
+ const el = elementRef.current;
14165
+ if (!el) {
14166
+ return null;
14167
+ }
14168
+ const cleanupCallbackSet = new Set();
14169
+ const setupCustomEvent = (el, constraintName, Component) => {
14170
+ const attrName = `data-${constraintName}-message-event`;
14171
+ const customEventName = `${constraintName}_message_jsx`;
14172
+ el.setAttribute(attrName, customEventName);
14173
+ const onCustomEvent = (e) => {
14174
+ e.detail.render(Component);
14175
+ };
14176
+ el.addEventListener(customEventName, onCustomEvent);
14177
+ cleanupCallbackSet.add(() => {
14178
+ el.removeEventListener(customEventName, onCustomEvent);
14179
+ el.removeAttribute(attrName);
14180
+ });
14181
+ };
14182
+
14183
+ if (disabledMessage) {
14184
+ setupCustomEvent(el, "disabled", disabledMessage);
14185
+ }
14186
+ if (requiredMessage) {
14187
+ setupCustomEvent(el, "required", requiredMessage);
14188
+ }
14189
+ if (patternMessage) {
14190
+ setupCustomEvent(el, "pattern", patternMessage);
14191
+ }
14192
+ if (minLengthMessage) {
14193
+ setupCustomEvent(el, "min-length", minLengthMessage);
14194
+ }
14195
+ if (maxLengthMessage) {
14196
+ setupCustomEvent(el, "max-length", maxLengthMessage);
14197
+ }
14198
+ if (typeMessage) {
14199
+ setupCustomEvent(el, "type", typeMessage);
14200
+ }
14201
+ if (minMessage) {
14202
+ setupCustomEvent(el, "min", minMessage);
14203
+ }
14204
+ if (maxMessage) {
14205
+ setupCustomEvent(el, "max", maxMessage);
14206
+ }
14207
+ if (singleSpaceMessage) {
14208
+ setupCustomEvent(el, "single-space", singleSpaceMessage);
14209
+ }
14210
+ if (sameAsMessage) {
14211
+ setupCustomEvent(el, "same-as", sameAsMessage);
14212
+ }
14213
+ if (minDigitMessage) {
14214
+ setupCustomEvent(el, "min-digit", minDigitMessage);
14215
+ }
14216
+ if (minLowerLetterMessage) {
14217
+ setupCustomEvent(el, "min-lower-letter", minLowerLetterMessage);
14218
+ }
14219
+ if (minUpperLetterMessage) {
14220
+ setupCustomEvent(el, "min-upper-letter", minUpperLetterMessage);
14221
+ }
14222
+ if (minSpecialCharMessage) {
14223
+ setupCustomEvent(el, "min-special-char", minSpecialCharMessage);
14224
+ }
14225
+ if (availableMessage) {
14226
+ setupCustomEvent(el, "available", availableMessage);
14227
+ }
14228
+ return () => {
14229
+ for (const cleanupCallback of cleanupCallbackSet) {
14230
+ cleanupCallback();
14231
+ }
14232
+ };
14233
+ }, [
14234
+ disabledMessage,
14235
+ requiredMessage,
14236
+ patternMessage,
14237
+ minLengthMessage,
14238
+ maxLengthMessage,
14239
+ typeMessage,
14240
+ minMessage,
14241
+ maxMessage,
14242
+ singleSpaceMessage,
14243
+ sameAsMessage,
14244
+ minDigitMessage,
14245
+ minLowerLetterMessage,
14246
+ minUpperLetterMessage,
14247
+ minSpecialCharMessage,
14248
+ availableMessage,
14249
+ ]);
14250
+
14251
+ return remainingProps;
13933
14252
  };
13934
14253
 
13935
14254
  const useInitialTextSelection = (ref, textSelection) => {
@@ -14660,6 +14979,8 @@ const useExecuteAction = (
14660
14979
  message = errorMappingResult;
14661
14980
  } else if (Error.isError(errorMappingResult)) {
14662
14981
  message = errorMappingResult;
14982
+ } else if (isValidElement(errorMappingResult)) {
14983
+ message = errorMappingResult;
14663
14984
  } else if (
14664
14985
  typeof errorMappingResult === "object" &&
14665
14986
  errorMappingResult !== null
@@ -15975,7 +16296,6 @@ const ButtonBasic = props => {
15975
16296
  readOnly,
15976
16297
  disabled,
15977
16298
  loading,
15978
- constraints = [],
15979
16299
  autoFocus,
15980
16300
  // visual
15981
16301
  discrete,
@@ -15986,7 +16306,7 @@ const ButtonBasic = props => {
15986
16306
  const defaultRef = useRef();
15987
16307
  const ref = props.ref || defaultRef;
15988
16308
  useAutoFocus(ref, autoFocus);
15989
- useConstraints(ref, constraints);
16309
+ const remainingProps = useConstraints(ref, rest);
15990
16310
  const innerLoading = loading || contextLoading && contextLoadingElement === ref.current;
15991
16311
  const innerReadOnly = readOnly || contextReadOnly || innerLoading;
15992
16312
  const innerDisabled = disabled || contextDisabled;
@@ -16002,7 +16322,7 @@ const ButtonBasic = props => {
16002
16322
  const renderButtonContentMemoized = useCallback(renderButtonContent, [children]);
16003
16323
  return jsxs(Box, {
16004
16324
  "data-readonly-silent": innerLoading ? "" : undefined,
16005
- ...rest,
16325
+ ...remainingProps,
16006
16326
  as: "button",
16007
16327
  ref: ref,
16008
16328
  "data-icon": icon ? "" : undefined,
@@ -16542,7 +16862,6 @@ const LinkPlain = props => {
16542
16862
  disabled,
16543
16863
  autoFocus,
16544
16864
  spaceToClick = true,
16545
- constraints = [],
16546
16865
  onClick,
16547
16866
  onKeyDown,
16548
16867
  href,
@@ -16565,7 +16884,7 @@ const LinkPlain = props => {
16565
16884
  const ref = props.ref || defaultRef;
16566
16885
  const visited = useIsVisited(href);
16567
16886
  useAutoFocus(ref, autoFocus);
16568
- useConstraints(ref, constraints);
16887
+ const remainingProps = useConstraints(ref, rest);
16569
16888
  const shouldDimColor = readOnly || disabled;
16570
16889
  useDimColorWhen(ref, shouldDimColor);
16571
16890
  // subscribe to document url to re-render and re-compute getHrefTargetInfo
@@ -16606,7 +16925,7 @@ const LinkPlain = props => {
16606
16925
  as: "a",
16607
16926
  color: anchor && !innerChildren ? "inherit" : undefined,
16608
16927
  id: anchor ? href.slice(1) : undefined,
16609
- ...rest,
16928
+ ...remainingProps,
16610
16929
  ref: ref,
16611
16930
  href: href,
16612
16931
  rel: innerRel,
@@ -17209,7 +17528,7 @@ const TabBasic = ({
17209
17528
  });
17210
17529
  };
17211
17530
 
17212
- const createUniqueValueConstraint = (
17531
+ const createAvailableConstraint = (
17213
17532
  // the set might be incomplete (the front usually don't have the full copy of all the items from the backend)
17214
17533
  // but this is already nice to help user with what we know
17215
17534
  // it's also possible that front is unsync with backend, preventing user to choose a value
@@ -17220,7 +17539,8 @@ const createUniqueValueConstraint = (
17220
17539
  message = `"{value}" est utilisé. Veuillez entrer une autre valeur.`,
17221
17540
  ) => {
17222
17541
  return {
17223
- name: "unique",
17542
+ name: "available",
17543
+ messageAttribute: "data-available-message",
17224
17544
  check: (field) => {
17225
17545
  const fieldValue = field.value;
17226
17546
  const hasConflict = existingValueSet.has(fieldValue);
@@ -17543,7 +17863,6 @@ const InputCheckboxBasic = props => {
17543
17863
  required,
17544
17864
  loading,
17545
17865
  autoFocus,
17546
- constraints = [],
17547
17866
  onClick,
17548
17867
  onInput,
17549
17868
  color,
@@ -17559,7 +17878,7 @@ const InputCheckboxBasic = props => {
17559
17878
  reportReadOnlyOnLabel?.(innerReadOnly);
17560
17879
  reportDisabledOnLabel?.(innerDisabled);
17561
17880
  useAutoFocus(ref, autoFocus);
17562
- useConstraints(ref, constraints);
17881
+ const remainingProps = useConstraints(ref, rest);
17563
17882
  const checked = Boolean(uiState);
17564
17883
  const innerOnClick = useStableCallback(e => {
17565
17884
  if (innerReadOnly) {
@@ -17606,7 +17925,7 @@ const InputCheckboxBasic = props => {
17606
17925
  }, [color]);
17607
17926
  return jsxs(Box, {
17608
17927
  as: "span",
17609
- ...rest,
17928
+ ...remainingProps,
17610
17929
  ref: undefined,
17611
17930
  baseClassName: "navi_checkbox",
17612
17931
  pseudoStateSelector: ".navi_native_field",
@@ -18101,7 +18420,6 @@ const InputRadioBasic = props => {
18101
18420
  required,
18102
18421
  loading,
18103
18422
  autoFocus,
18104
- constraints = [],
18105
18423
  onClick,
18106
18424
  onInput,
18107
18425
  color,
@@ -18117,7 +18435,7 @@ const InputRadioBasic = props => {
18117
18435
  reportReadOnlyOnLabel?.(innerReadOnly);
18118
18436
  reportDisabledOnLabel?.(innerDisabled);
18119
18437
  useAutoFocus(ref, autoFocus);
18120
- useConstraints(ref, constraints);
18438
+ const remainingProps = useConstraints(ref, rest);
18121
18439
  const checked = Boolean(uiState);
18122
18440
  // we must first dispatch an event to inform all other radios they where unchecked
18123
18441
  // this way each other radio uiStateController knows thery are unchecked
@@ -18191,7 +18509,7 @@ const InputRadioBasic = props => {
18191
18509
  }, [color]);
18192
18510
  return jsxs(Box, {
18193
18511
  as: "span",
18194
- ...rest,
18512
+ ...remainingProps,
18195
18513
  ref: undefined,
18196
18514
  baseClassName: "navi_radio",
18197
18515
  pseudoStateSelector: ".navi_native_field",
@@ -18515,7 +18833,6 @@ const InputRangeBasic = props => {
18515
18833
  onInput,
18516
18834
  readOnly,
18517
18835
  disabled,
18518
- constraints = [],
18519
18836
  loading,
18520
18837
  autoFocus,
18521
18838
  autoFocusVisible,
@@ -18534,7 +18851,7 @@ const InputRangeBasic = props => {
18534
18851
  autoFocusVisible,
18535
18852
  autoSelect
18536
18853
  });
18537
- useConstraints(ref, constraints);
18854
+ const remainingProps = useConstraints(ref, rest);
18538
18855
  const innerOnInput = useStableCallback(onInput);
18539
18856
  const focusProxyId = `input_range_focus_proxy_${useId()}`;
18540
18857
  const inertButFocusable = innerReadOnly && !innerDisabled;
@@ -18624,7 +18941,7 @@ const InputRangeBasic = props => {
18624
18941
  pseudoClasses: InputPseudoClasses$1,
18625
18942
  pseudoElements: InputPseudoElements$1,
18626
18943
  hasChildFunction: true,
18627
- ...rest,
18944
+ ...remainingProps,
18628
18945
  ref: undefined,
18629
18946
  children: [jsx(LoaderBackground, {
18630
18947
  loading: innerLoading,
@@ -18900,7 +19217,6 @@ const InputTextualBasic = props => {
18900
19217
  onInput,
18901
19218
  readOnly,
18902
19219
  disabled,
18903
- constraints = [],
18904
19220
  loading,
18905
19221
  autoFocus,
18906
19222
  autoFocusVisible,
@@ -18919,7 +19235,7 @@ const InputTextualBasic = props => {
18919
19235
  autoFocusVisible,
18920
19236
  autoSelect
18921
19237
  });
18922
- useConstraints(ref, constraints);
19238
+ const remainingProps = useConstraints(ref, rest);
18923
19239
  const innerOnInput = useStableCallback(onInput);
18924
19240
  const renderInput = inputProps => {
18925
19241
  return jsx(Box, {
@@ -18968,7 +19284,7 @@ const InputTextualBasic = props => {
18968
19284
  pseudoClasses: InputPseudoClasses,
18969
19285
  pseudoElements: InputPseudoElements,
18970
19286
  hasChildFunction: true,
18971
- ...rest,
19287
+ ...remainingProps,
18972
19288
  ref: undefined,
18973
19289
  children: [jsx(LoaderBackground, {
18974
19290
  loading: innerLoading,
@@ -19417,7 +19733,7 @@ const FormBasic = props => {
19417
19733
  // instantiation validation to:
19418
19734
  // - receive "requestsubmit" custom event ensure submit is prevented
19419
19735
  // (and also execute action without validation if form.submit() is ever called)
19420
- useConstraints(ref, []);
19736
+ const remainingProps = useConstraints(ref, rest);
19421
19737
  const innerReadOnly = readOnly || loading;
19422
19738
  const formContextValue = useMemo(() => {
19423
19739
  return {
@@ -19425,7 +19741,7 @@ const FormBasic = props => {
19425
19741
  };
19426
19742
  }, [loading]);
19427
19743
  return jsx(Box, {
19428
- ...rest,
19744
+ ...remainingProps,
19429
19745
  as: "form",
19430
19746
  ref: ref,
19431
19747
  onReset: e => {
@@ -24325,5 +24641,5 @@ const UserSvg = () => jsx("svg", {
24325
24641
  })
24326
24642
  });
24327
24643
 
24328
- export { ActionRenderer, ActiveKeyboardShortcuts, BadgeCount, Box, Button, Caption, CheckSvg, Checkbox, CheckboxList, Code, Col, Colgroup, Details, DialogLayout, Editable, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, SearchSvg, Select, SelectionContext, SettingsSvg, StarSvg, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, addCustomMessage, compareTwoJsValues, createAction, createRequestCanceller, createSelectionKeyboardShortcuts, createUniqueValueConstraint, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, rerunActions, resource, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useActiveRouteInfo, useCellsAndColumns, useConstraintValidityState, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState$1 as useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useUrlSearchParam, valueInLocalStorage };
24644
+ export { ActionRenderer, ActiveKeyboardShortcuts, BadgeCount, Box, Button, Caption, CheckSvg, Checkbox, CheckboxList, Code, Col, Colgroup, Details, DialogLayout, Editable, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, SearchSvg, Select, SelectionContext, SettingsSvg, StarSvg, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, addCustomMessage, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, rerunActions, resource, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useActiveRouteInfo, useCalloutClose, useCellsAndColumns, useConstraintValidityState, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState$1 as useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useUrlSearchParam, valueInLocalStorage };
24329
24645
  //# sourceMappingURL=jsenv_navi.js.map