mado-ui 0.5.4 → 0.5.5

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.
Files changed (83) hide show
  1. package/css/index.css +189 -1
  2. package/dist/components/form/index.d.ts +1 -0
  3. package/dist/components/form/index.d.ts.map +1 -1
  4. package/dist/components/form/input/date/index.d.ts +36 -16
  5. package/dist/components/form/input/date/index.d.ts.map +1 -1
  6. package/dist/components/form/input/date/views/calendar-view.d.ts +2 -0
  7. package/dist/components/form/input/date/views/calendar-view.d.ts.map +1 -0
  8. package/dist/components/form/input/date/views/clock-view.d.ts +2 -0
  9. package/dist/components/form/input/date/views/clock-view.d.ts.map +1 -0
  10. package/dist/components/form/input/date/views/dropdown-view.d.ts +2 -0
  11. package/dist/components/form/input/date/views/dropdown-view.d.ts.map +1 -0
  12. package/dist/components/form/input/date/views/index.d.ts +5 -0
  13. package/dist/components/form/input/date/views/index.d.ts.map +1 -0
  14. package/dist/components/form/input/date/views/rotary-view.d.ts +2 -0
  15. package/dist/components/form/input/date/views/rotary-view.d.ts.map +1 -0
  16. package/dist/components/form/input/date/views/view-handler.d.ts +21 -0
  17. package/dist/components/form/input/date/views/view-handler.d.ts.map +1 -0
  18. package/dist/components/form/input/index.d.ts +4 -87
  19. package/dist/components/form/input/index.d.ts.map +1 -1
  20. package/dist/components/form/input/input-types.d.ts +102 -0
  21. package/dist/components/form/input/input-types.d.ts.map +1 -0
  22. package/dist/components/form/input/number-input.d.ts +4 -0
  23. package/dist/components/form/input/number-input.d.ts.map +1 -0
  24. package/dist/components/form/input/text-input.d.ts +36 -0
  25. package/dist/components/form/input/text-input.d.ts.map +1 -0
  26. package/dist/components/form/select.d.ts +41 -0
  27. package/dist/components/form/select.d.ts.map +1 -0
  28. package/dist/components.d.ts +161 -62
  29. package/dist/components.esm.js +482 -183
  30. package/dist/components.esm.js.map +1 -1
  31. package/dist/components.js +481 -181
  32. package/dist/components.js.map +1 -1
  33. package/dist/graphics/index.d.ts +2 -0
  34. package/dist/graphics/index.d.ts.map +1 -0
  35. package/dist/graphics/social-media/facebook-logo.d.ts +33 -0
  36. package/dist/graphics/social-media/facebook-logo.d.ts.map +1 -0
  37. package/dist/graphics/social-media/google-logo.d.ts +13 -0
  38. package/dist/graphics/social-media/google-logo.d.ts.map +1 -0
  39. package/dist/graphics/social-media/index.d.ts +8 -0
  40. package/dist/graphics/social-media/index.d.ts.map +1 -0
  41. package/dist/graphics/social-media/instagram-logo.d.ts +13 -0
  42. package/dist/graphics/social-media/instagram-logo.d.ts.map +1 -0
  43. package/dist/graphics/social-media/linkedin-logo.d.ts +26 -0
  44. package/dist/graphics/social-media/linkedin-logo.d.ts.map +1 -0
  45. package/dist/graphics/social-media/tiktok-logo.d.ts +26 -0
  46. package/dist/graphics/social-media/tiktok-logo.d.ts.map +1 -0
  47. package/dist/graphics/social-media/x-logo.d.ts +6 -0
  48. package/dist/graphics/social-media/x-logo.d.ts.map +1 -0
  49. package/dist/graphics/social-media/youtube-logo.d.ts +26 -0
  50. package/dist/graphics/social-media/youtube-logo.d.ts.map +1 -0
  51. package/dist/graphics.d.ts +141 -0
  52. package/dist/graphics.d.ts.map +1 -0
  53. package/dist/graphics.esm.js +241 -0
  54. package/dist/graphics.esm.js.map +1 -0
  55. package/dist/graphics.js +249 -0
  56. package/dist/graphics.js.map +1 -0
  57. package/dist/hooks/use-fieldset-context.d.ts.map +1 -1
  58. package/dist/hooks/use-form-context.d.ts +5 -5
  59. package/dist/hooks/use-form-context.d.ts.map +1 -1
  60. package/dist/hooks.d.ts +5 -5
  61. package/dist/hooks.esm.js.map +1 -1
  62. package/dist/hooks.js.map +1 -1
  63. package/dist/index.d.ts +161 -62
  64. package/dist/index.esm.js +482 -183
  65. package/dist/index.esm.js.map +1 -1
  66. package/dist/index.js +481 -181
  67. package/dist/index.js.map +1 -1
  68. package/dist/utils/get-date.d.ts +32 -20
  69. package/dist/utils/get-date.d.ts.map +1 -1
  70. package/dist/utils/string-manipulation.d.ts +23 -1
  71. package/dist/utils/string-manipulation.d.ts.map +1 -1
  72. package/dist/utils.d.ts +57 -23
  73. package/dist/utils.esm.js +1139 -57
  74. package/dist/utils.esm.js.map +1 -1
  75. package/dist/utils.js +1145 -58
  76. package/dist/utils.js.map +1 -1
  77. package/package.json +100 -96
  78. package/dist/components/mado-ui-provider.d.ts +0 -1
  79. package/dist/hooks/use-anime-scope.d.ts +0 -15
  80. package/dist/types.esm.js +0 -2
  81. package/dist/types.esm.js.map +0 -1
  82. package/dist/types.js +0 -3
  83. package/dist/types.js.map +0 -1
package/dist/index.js CHANGED
@@ -139,7 +139,7 @@ getMonthName();
139
139
  /** The name of the current day of the week */
140
140
  getWeekdayName();
141
141
  /**
142
- * ### Get Date
142
+ * # Get Date
143
143
  * - Returns the date with two digits
144
144
  * @param {number|Date} date The date to get date
145
145
  * @returns {string} The date with two digits
@@ -153,7 +153,7 @@ function getDate(date = d) {
153
153
  return formattedDate;
154
154
  }
155
155
  /**
156
- * ### Get Hours
156
+ * # Get Hours
157
157
  * - Returns the hours with two digits
158
158
  * @param {number|Date} hours The date to get hours
159
159
  * @returns {string} The hours with two digits
@@ -167,7 +167,7 @@ function getHours(hours = d) {
167
167
  return formattedHours;
168
168
  }
169
169
  /**
170
- * ### Get Milliseconds
170
+ * # Get Milliseconds
171
171
  * - Returns the milliseconds with two digits
172
172
  * @param {number|Date} milliseconds The date to get milliseconds
173
173
  * @returns {string} The milliseconds with two digits
@@ -181,7 +181,7 @@ function getMilliseconds(milliseconds = d) {
181
181
  return formattedMilliseconds;
182
182
  }
183
183
  /**
184
- * ### Get Minutes
184
+ * # Get Minutes
185
185
  * - Returns the minutes with two digits
186
186
  * @param {number|Date} minutes The date to get minutes
187
187
  * @returns {string} The minutes with two digits
@@ -195,7 +195,7 @@ function getMinutes(minutes = d) {
195
195
  return formattedMinutes;
196
196
  }
197
197
  /**
198
- * ### Get Month
198
+ * # Get Month
199
199
  * - Returns the month with two digits
200
200
  * @param {number|Date} month The date to get month
201
201
  * @returns {string} The month with two digits
@@ -212,7 +212,7 @@ function getMonthIndexFromName(name) {
212
212
  return monthNamesList.findIndex(monthName => monthName === name);
213
213
  }
214
214
  /**
215
- * ### Get Month Name
215
+ * # Get Month Name
216
216
  * - Returns the name of the specified month
217
217
  * @param {Date} date A Date object representing the month to get the name of the month from. (Preset to the current date)
218
218
  * @returns {MonthName} The name of the specified month
@@ -223,7 +223,7 @@ function getMonthName(date = d) {
223
223
  return monthNamesList[date.getMonth()];
224
224
  }
225
225
  /**
226
- * ### Get Seconds
226
+ * # Get Seconds
227
227
  * - Returns the seconds with two digits
228
228
  * @param {number|Date} seconds The date to get seconds
229
229
  * @returns {string} The seconds with two digits
@@ -237,7 +237,7 @@ function getSeconds(seconds = d) {
237
237
  return formattedSeconds;
238
238
  }
239
239
  /**
240
- * ### Get User Readable Date
240
+ * # Get User Readable Date
241
241
  * - Returns a string of the current date in a user-friendly way
242
242
  * - Includes `'Yesterday'`, '`Today'`, and `'Tomorrow'`
243
243
  * @param date (default: `new Date()`)
@@ -261,7 +261,7 @@ function getUserReadableDate(date = d) {
261
261
  return fullDateString;
262
262
  }
263
263
  /**
264
- * ### Get Weekday Name
264
+ * # Get Weekday Name
265
265
  * - Returns the weekday name of the specified day
266
266
  * @param {number | Date} weekday A Date object or number representing the day to get the weekday name from. (Preset to the current date)
267
267
  * @returns {WeekdayName} The name of the specified weekday
@@ -273,7 +273,7 @@ function getWeekdayName(weekday = d) {
273
273
  return weekdayNamesList[weekday.getDay()];
274
274
  }
275
275
  /**
276
- * ### To Full Date String
276
+ * # To Full Date String
277
277
  * - Returns a formatted string to display the date
278
278
  * @param {Date} date (default: `new Date()`)
279
279
  * @param {ToFullDateStringOptionsProps} options Change how to display the weekday, month name, day of the month, and year.
@@ -395,13 +395,14 @@ function isPhoneNumber(tel) {
395
395
  * @returns {string} string formatted
396
396
  */
397
397
  function formatPhoneNumber(phoneNumber, options) {
398
- const format = options?.format || 'standard';
399
- if (format !== 'none')
400
- phoneNumber = phoneNumber.replace(/\D/g, '').slice(-10);
398
+ const format = options?.format || "standard";
399
+ if (format !== "none") {
400
+ phoneNumber = phoneNumber.replace(/\D/g, "").slice(-10);
401
+ }
401
402
  switch (format) {
402
- case 'dot':
403
+ case "dot":
403
404
  phoneNumber = phoneNumber
404
- .split('')
405
+ .split("")
405
406
  .map((char, index) => {
406
407
  if (index === 2)
407
408
  return `${char}.`;
@@ -409,11 +410,11 @@ function formatPhoneNumber(phoneNumber, options) {
409
410
  return `${char}.`;
410
411
  return char;
411
412
  })
412
- .join('');
413
+ .join("");
413
414
  break;
414
- case 'hyphenated':
415
+ case "hyphenated":
415
416
  phoneNumber = phoneNumber
416
- .split('')
417
+ .split("")
417
418
  .map((char, index) => {
418
419
  if (index === 2)
419
420
  return `${char}-`;
@@ -421,11 +422,11 @@ function formatPhoneNumber(phoneNumber, options) {
421
422
  return `${char}-`;
422
423
  return char;
423
424
  })
424
- .join('');
425
+ .join("");
425
426
  break;
426
- case 'space':
427
+ case "space":
427
428
  phoneNumber = phoneNumber
428
- .split('')
429
+ .split("")
429
430
  .map((char, index) => {
430
431
  if (index === 2)
431
432
  return `${char} `;
@@ -433,11 +434,11 @@ function formatPhoneNumber(phoneNumber, options) {
433
434
  return `${char} `;
434
435
  return char;
435
436
  })
436
- .join('');
437
+ .join("");
437
438
  break;
438
- case 'standard':
439
+ case "standard":
439
440
  phoneNumber = phoneNumber
440
- .split('')
441
+ .split("")
441
442
  .map((char, index) => {
442
443
  if (index === 0)
443
444
  return `(${char}`;
@@ -447,9 +448,10 @@ function formatPhoneNumber(phoneNumber, options) {
447
448
  return `${char}-`;
448
449
  return char;
449
450
  })
450
- .join('');
451
+ .join("");
451
452
  }
452
- return `${options?.countryCode ? `+${options?.countryCode} ` : ''}` + phoneNumber;
453
+ return `${options?.countryCode ? `+${options?.countryCode} ` : ""}` +
454
+ phoneNumber;
453
455
  }
454
456
  /**
455
457
  * # To Lower Case
@@ -461,7 +463,7 @@ function formatPhoneNumber(phoneNumber, options) {
461
463
  * @returns The lowercase version of the input string, with the replacements, if provided.
462
464
  */
463
465
  function toLowerCase(str, [delimiter, joiner]) {
464
- return str.toLowerCase().replaceAll(delimiter || ' ', joiner || ' ');
466
+ return str.toLowerCase().replaceAll(delimiter || " ", joiner || " ");
465
467
  }
466
468
 
467
469
  function twSort(className) {
@@ -2308,7 +2310,7 @@ function isOverflowElement(element) {
2308
2310
  overflowX,
2309
2311
  overflowY,
2310
2312
  display
2311
- } = getComputedStyle(element);
2313
+ } = getComputedStyle$1(element);
2312
2314
  return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !invalidOverflowDisplayValues.has(display);
2313
2315
  }
2314
2316
  const tableElements = /*#__PURE__*/new Set(['table', 'td', 'th']);
@@ -2330,7 +2332,7 @@ const willChangeValues = ['transform', 'translate', 'scale', 'rotate', 'perspect
2330
2332
  const containValues = ['paint', 'layout', 'strict', 'content'];
2331
2333
  function isContainingBlock(elementOrCss) {
2332
2334
  const webkit = isWebKit();
2333
- const css = isElement(elementOrCss) ? getComputedStyle(elementOrCss) : elementOrCss;
2335
+ const css = isElement(elementOrCss) ? getComputedStyle$1(elementOrCss) : elementOrCss;
2334
2336
 
2335
2337
  // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
2336
2338
  // https://drafts.csswg.org/css-transforms-2/#individual-transforms
@@ -2356,7 +2358,7 @@ const lastTraversableNodeNames = /*#__PURE__*/new Set(['html', 'body', '#documen
2356
2358
  function isLastTraversableNode(node) {
2357
2359
  return lastTraversableNodeNames.has(getNodeName(node));
2358
2360
  }
2359
- function getComputedStyle(element) {
2361
+ function getComputedStyle$1(element) {
2360
2362
  return getWindow(element).getComputedStyle(element);
2361
2363
  }
2362
2364
  function getNodeScroll(element) {
@@ -2418,7 +2420,7 @@ function getFrameElement(win) {
2418
2420
  }
2419
2421
 
2420
2422
  function getCssDimensions(element) {
2421
- const css = getComputedStyle(element);
2423
+ const css = getComputedStyle$1(element);
2422
2424
  // In testing environments, the `width` and `height` properties are empty
2423
2425
  // strings for SVG elements, returning NaN. Fallback to `0` in this case.
2424
2426
  let width = parseFloat(css.width) || 0;
@@ -2523,7 +2525,7 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar
2523
2525
  while (currentIFrame && offsetParent && offsetWin !== currentWin) {
2524
2526
  const iframeScale = getScale(currentIFrame);
2525
2527
  const iframeRect = currentIFrame.getBoundingClientRect();
2526
- const css = getComputedStyle(currentIFrame);
2528
+ const css = getComputedStyle$1(currentIFrame);
2527
2529
  const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;
2528
2530
  const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;
2529
2531
  x *= iframeScale.x;
@@ -2554,14 +2556,9 @@ function getWindowScrollBarX(element, rect) {
2554
2556
  return rect.left + leftScroll;
2555
2557
  }
2556
2558
 
2557
- function getHTMLOffset(documentElement, scroll, ignoreScrollbarX) {
2558
- if (ignoreScrollbarX === void 0) {
2559
- ignoreScrollbarX = false;
2560
- }
2559
+ function getHTMLOffset(documentElement, scroll) {
2561
2560
  const htmlRect = documentElement.getBoundingClientRect();
2562
- const x = htmlRect.left + scroll.scrollLeft - (ignoreScrollbarX ? 0 :
2563
- // RTL <body> scrollbar.
2564
- getWindowScrollBarX(documentElement, htmlRect));
2561
+ const x = htmlRect.left + scroll.scrollLeft - getWindowScrollBarX(documentElement, htmlRect);
2565
2562
  const y = htmlRect.top + scroll.scrollTop;
2566
2563
  return {
2567
2564
  x,
@@ -2600,7 +2597,7 @@ function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) {
2600
2597
  offsets.y = offsetRect.y + offsetParent.clientTop;
2601
2598
  }
2602
2599
  }
2603
- const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll, true) : createCoords(0);
2600
+ const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0);
2604
2601
  return {
2605
2602
  width: rect.width * scale.x,
2606
2603
  height: rect.height * scale.y,
@@ -2623,7 +2620,7 @@ function getDocumentRect(element) {
2623
2620
  const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);
2624
2621
  let x = -scroll.scrollLeft + getWindowScrollBarX(element);
2625
2622
  const y = -scroll.scrollTop;
2626
- if (getComputedStyle(body).direction === 'rtl') {
2623
+ if (getComputedStyle$1(body).direction === 'rtl') {
2627
2624
  x += max(html.clientWidth, body.clientWidth) - width;
2628
2625
  }
2629
2626
  return {
@@ -2634,6 +2631,10 @@ function getDocumentRect(element) {
2634
2631
  };
2635
2632
  }
2636
2633
 
2634
+ // Safety check: ensure the scrollbar space is reasonable in case this
2635
+ // calculation is affected by unusual styles.
2636
+ // Most scrollbars leave 15-18px of space.
2637
+ const SCROLLBAR_MAX = 25;
2637
2638
  function getViewportRect(element, strategy) {
2638
2639
  const win = getWindow(element);
2639
2640
  const html = getDocumentElement(element);
@@ -2651,6 +2652,24 @@ function getViewportRect(element, strategy) {
2651
2652
  y = visualViewport.offsetTop;
2652
2653
  }
2653
2654
  }
2655
+ const windowScrollbarX = getWindowScrollBarX(html);
2656
+ // <html> `overflow: hidden` + `scrollbar-gutter: stable` reduces the
2657
+ // visual width of the <html> but this is not considered in the size
2658
+ // of `html.clientWidth`.
2659
+ if (windowScrollbarX <= 0) {
2660
+ const doc = html.ownerDocument;
2661
+ const body = doc.body;
2662
+ const bodyStyles = getComputedStyle(body);
2663
+ const bodyMarginInline = doc.compatMode === 'CSS1Compat' ? parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight) || 0 : 0;
2664
+ const clippingStableScrollbarWidth = Math.abs(html.clientWidth - body.clientWidth - bodyMarginInline);
2665
+ if (clippingStableScrollbarWidth <= SCROLLBAR_MAX) {
2666
+ width -= clippingStableScrollbarWidth;
2667
+ }
2668
+ } else if (windowScrollbarX <= SCROLLBAR_MAX) {
2669
+ // If the <body> scrollbar is on the left, the width needs to be extended
2670
+ // by the scrollbar amount so there isn't extra space on the right.
2671
+ width += windowScrollbarX;
2672
+ }
2654
2673
  return {
2655
2674
  width,
2656
2675
  height,
@@ -2701,7 +2720,7 @@ function hasFixedPositionAncestor(element, stopNode) {
2701
2720
  if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) {
2702
2721
  return false;
2703
2722
  }
2704
- return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);
2723
+ return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);
2705
2724
  }
2706
2725
 
2707
2726
  // A "clipping ancestor" is an `overflow` element with the characteristic of
@@ -2714,12 +2733,12 @@ function getClippingElementAncestors(element, cache) {
2714
2733
  }
2715
2734
  let result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body');
2716
2735
  let currentContainingBlockComputedStyle = null;
2717
- const elementIsFixed = getComputedStyle(element).position === 'fixed';
2736
+ const elementIsFixed = getComputedStyle$1(element).position === 'fixed';
2718
2737
  let currentNode = elementIsFixed ? getParentNode(element) : element;
2719
2738
 
2720
2739
  // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
2721
2740
  while (isElement(currentNode) && !isLastTraversableNode(currentNode)) {
2722
- const computedStyle = getComputedStyle(currentNode);
2741
+ const computedStyle = getComputedStyle$1(currentNode);
2723
2742
  const currentNodeIsContaining = isContainingBlock(currentNode);
2724
2743
  if (!currentNodeIsContaining && computedStyle.position === 'fixed') {
2725
2744
  currentContainingBlockComputedStyle = null;
@@ -2820,11 +2839,11 @@ function getRectRelativeToOffsetParent(element, offsetParent, strategy) {
2820
2839
  }
2821
2840
 
2822
2841
  function isStaticPositioned(element) {
2823
- return getComputedStyle(element).position === 'static';
2842
+ return getComputedStyle$1(element).position === 'static';
2824
2843
  }
2825
2844
 
2826
2845
  function getTrueOffsetParent(element, polyfill) {
2827
- if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') {
2846
+ if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') {
2828
2847
  return null;
2829
2848
  }
2830
2849
  if (polyfill) {
@@ -2885,7 +2904,7 @@ const getElementRects = async function (data) {
2885
2904
  };
2886
2905
 
2887
2906
  function isRTL(element) {
2888
- return getComputedStyle(element).direction === 'rtl';
2907
+ return getComputedStyle$1(element).direction === 'rtl';
2889
2908
  }
2890
2909
 
2891
2910
  const platform = {
@@ -3638,43 +3657,302 @@ function ArrowSvg({ className, ...props }) {
3638
3657
  }
3639
3658
 
3640
3659
  const specialCharacterRegex = new RegExp(/[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~€‚ƒ„…†‡‰‹‘’“”•–—™›¡¢£¥§©«¬®°±¶º»¿×÷]/);
3641
- function Input({ checked, className, defaultValue, description, descriptionProps, disabled, fieldProps, invalid = true, label, labelProps, max, min, name, onBlur, onChange, options, placeholder, ref, required = true, type, value, ...props }) {
3642
- const [formContext, formContextFunctions] = useFormContext(), [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext(), [errorMessage, setErrorMessage] = React.useState(undefined);
3660
+ function TextInput({ className, fieldContextID, handleBlur, handleChange, options, placeholder, type, ...props }) {
3661
+ const [formContext] = useFormContext(), [fieldsetContext] = useFieldsetContext();
3662
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3663
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3643
3664
  if (type === 'password' && !placeholder)
3644
- placeholder = '••••••••' + (required && !label ? '*' : '');
3665
+ placeholder = '••••••••';
3666
+ const FORMAT_FUNCTIONS = {
3667
+ email: (value) => value.toLowerCase(),
3668
+ tel: (value) => formatPhoneNumber(value, options),
3669
+ };
3670
+ const VALIDATION_HANDLERS = {
3671
+ email: ({ multiple, value }) => {
3672
+ if (multiple) {
3673
+ const emailList = value.split(',').map(item => item.trim()), notEmailList = [];
3674
+ emailList.forEach(item => {
3675
+ if (!isEmail(item))
3676
+ notEmailList.push(item);
3677
+ });
3678
+ if (notEmailList.length > 0) {
3679
+ const multipleInvalid = notEmailList.length > 1;
3680
+ return `${multipleInvalid ? 'These email addresses are' : 'This email address is'} invalid: ${notEmailList.join(', ')}`;
3681
+ }
3682
+ return;
3683
+ }
3684
+ if (!isEmail(value))
3685
+ return 'This is not a valid email address.';
3686
+ },
3687
+ password: ({ value }) => {
3688
+ const errorMessageList = [];
3689
+ if (options) {
3690
+ const { matchPreviousInput, requireLowercaseCharacter, requireNumber, requireSpecialCharacter, requireUppercaseCharacter, } = options;
3691
+ if (matchPreviousInput && formContext && formContext.length >= 2) {
3692
+ if (isInFieldset && fieldsetContext.fieldList.length > 1) {
3693
+ const currentInputIndex = fieldsetContext.fieldList.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3694
+ if (currentInputIndex > 0) {
3695
+ const previousInput = fieldsetContext.fieldList.find((_, index) => index === currentInputIndex - 1);
3696
+ if (previousInput &&
3697
+ isStringField(previousInput) &&
3698
+ previousInput.value !== value)
3699
+ errorMessageList.push('Passwords must match.');
3700
+ }
3701
+ }
3702
+ else {
3703
+ const currentInputIndex = formContext.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3704
+ if (currentInputIndex > 0) {
3705
+ const previousInput = formContext.find((_, index) => index === currentInputIndex - 1);
3706
+ if (previousInput &&
3707
+ isStringField(previousInput) &&
3708
+ previousInput.value !== value)
3709
+ errorMessageList.push('Passwords must match.');
3710
+ }
3711
+ }
3712
+ }
3713
+ if (requireLowercaseCharacter && !/[a-z]/g.test(value))
3714
+ errorMessageList.push('You must include a lowercase character.');
3715
+ if (requireNumber && !/[0-9]/g.test(value))
3716
+ errorMessageList.push('You must include a number.');
3717
+ if (requireSpecialCharacter && !specialCharacterRegex.test(value))
3718
+ errorMessageList.push('You must include a special character.');
3719
+ if (requireUppercaseCharacter && !/[A-Z]/g.test(value))
3720
+ errorMessageList.push('You must include an uppercase character.');
3721
+ }
3722
+ return errorMessageList.length > 0 ? errorMessageList.join(' ') : undefined;
3723
+ },
3724
+ tel: ({ value }) => {
3725
+ if (!isPhoneNumber(value))
3726
+ return 'This is not a valid phone number.';
3727
+ },
3728
+ };
3729
+ const onChange = e => {
3730
+ const validation = type ? VALIDATION_HANDLERS[type] : undefined;
3731
+ handleChange({ e, validation });
3732
+ };
3733
+ const onBlur = e => {
3734
+ const format = type ? FORMAT_FUNCTIONS[type] : undefined, validation = type ? VALIDATION_HANDLERS[type] : undefined;
3735
+ handleBlur({ e, format, validation });
3736
+ };
3737
+ return (jsxRuntime.jsx(react.Input, { ...props, className: bag => twMerge(
3738
+ // Base styles
3739
+ 'w-full rounded-xl border-1 border-neutral-500/50 bg-neutral-100 py-1 pl-2 text-neutral-950 outline-offset-1 outline-ui-sky-blue/95 transition-[background-color] duration-300 ease-exponential dark:bg-neutral-700 dark:text-neutral-50',
3740
+ // Pseudo styles
3741
+ 'focus-visible:bg-neutral-50 focus-visible:outline-3 active:bg-neutral-200 dark:focus-visible:bg-neutral-600 dark:active:bg-neutral-800 pointer-fine:hover:bg-neutral-50 pointer-fine:active:bg-neutral-200 dark:pointer-fine:hover:bg-neutral-600 dark:pointer-fine:active:bg-neutral-800',
3742
+ // user-invalid styles
3743
+ 'user-invalid:border-ui-red user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-100))] user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-800))] dark:user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))] user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-100))] user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))]',
3744
+ // Custom styles
3745
+ typeof className === 'function' ? className(bag) : className), onBlur: onBlur, onChange: onChange, placeholder: placeholder, type: type, value: fieldContext?.value || '' }));
3746
+ }
3747
+
3748
+ function DateTimeInput({ className, fieldContextID, handleBlur, handleChange, ref, theme, type, ui, ...props }) {
3749
+ const [formContext] = useFormContext(), [fieldsetContext] = useFieldsetContext();
3750
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3751
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3752
+ if (!ui)
3753
+ switch (type) {
3754
+ case 'date':
3755
+ ui = {
3756
+ desktop: ['calendar', 'dropdown'],
3757
+ mobile: ['calendar', 'rotary'],
3758
+ };
3759
+ break;
3760
+ case 'datetime':
3761
+ ui = {
3762
+ date: {
3763
+ desktop: ['calendar', 'dropdown'],
3764
+ mobile: ['calendar', 'rotary'],
3765
+ },
3766
+ time: {
3767
+ desktop: ['clock', 'dropdown'],
3768
+ mobile: ['rotary', 'clock'],
3769
+ },
3770
+ };
3771
+ break;
3772
+ case 'time':
3773
+ ui = {
3774
+ desktop: ['clock', 'dropdown'],
3775
+ mobile: ['rotary', 'clock'],
3776
+ };
3777
+ break;
3778
+ }
3779
+ const validation = ({ max, min, value }) => {
3780
+ const valueAsTime = new Date(value).getTime();
3781
+ if (min && !(min instanceof Date) && typeof min !== 'number') {
3782
+ if (Array.isArray(min)) {
3783
+ const monthIndex = typeof min[1] === 'number' ? min[1] - 1 : getMonthIndexFromName(min[1]);
3784
+ min = new Date(min[0], monthIndex, min[2]);
3785
+ }
3786
+ else if ('year' in min && 'month' in min && 'day' in min) {
3787
+ const monthIndex = typeof min.month === 'number' ? min.month - 1 : getMonthIndexFromName(min.month);
3788
+ min = new Date(min.year, monthIndex, min.day);
3789
+ }
3790
+ if (valueAsTime < min.getTime())
3791
+ return `Value cannot be lower than ${getUserReadableDate(min)}.`;
3792
+ }
3793
+ if (max && !(max instanceof Date) && typeof max !== 'number') {
3794
+ if (Array.isArray(max)) {
3795
+ const monthIndex = typeof max[1] === 'number' ? max[1] - 1 : getMonthIndexFromName(max[1]);
3796
+ max = new Date(max[0], monthIndex, max[2]);
3797
+ }
3798
+ else if ('year' in max && 'month' in max && 'day' in max) {
3799
+ const monthIndex = typeof max.month === 'number' ? max.month - 1 : getMonthIndexFromName(max.month);
3800
+ max = new Date(max.year, monthIndex, max.day);
3801
+ }
3802
+ if (valueAsTime > max.getTime())
3803
+ return `Value cannot be higher than ${getUserReadableDate(max)}.`;
3804
+ }
3805
+ };
3806
+ const onChange = e => {
3807
+ handleChange({ e, validation });
3808
+ };
3809
+ const onBlur = e => {
3810
+ handleBlur({ e, validation });
3811
+ };
3812
+ const [selectorIsOpen, setSelectorIsOpen] = React.useState(false);
3813
+ const toggleSelectorOpen = () => setSelectorIsOpen(previous => !previous);
3814
+ const { refs, floatingStyles, isPositioned, placement, middlewareData } = useFloating({
3815
+ middleware: [
3816
+ offset(8),
3817
+ flip({ padding: 20 }),
3818
+ shift({ padding: 20 }),
3819
+ size({
3820
+ apply({ availableHeight, availableWidth, elements }) {
3821
+ const height = `${Math.max(0, availableHeight) / 16}rem`, width = `${Math.min(418, availableWidth) / 16}rem`;
3822
+ elements.floating.style.maxHeight = height;
3823
+ elements.floating.style.maxWidth = width;
3824
+ },
3825
+ }),
3826
+ ],
3827
+ placement: 'top-start',
3828
+ strategy: 'fixed',
3829
+ whileElementsMounted: autoUpdate,
3830
+ open: selectorIsOpen,
3831
+ });
3832
+ const reversedAnchor = {
3833
+ top: 'bottom',
3834
+ right: 'left',
3835
+ bottom: 'top',
3836
+ left: 'right',
3837
+ 'top-start': 'bottom left',
3838
+ 'top-end': 'bottom right',
3839
+ 'right-start': 'top left',
3840
+ 'right-end': 'bottom left',
3841
+ 'bottom-start': 'top left',
3842
+ 'bottom-end': 'top right',
3843
+ 'left-start': 'top right',
3844
+ 'left-end': 'bottom right',
3845
+ }[placement];
3846
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(react.Input, { ...props, className: bag => twMerge(
3847
+ // Base styles
3848
+ 'w-full rounded-xl border-1 border-neutral-500/50 bg-neutral-100 py-1 pl-2 text-neutral-950 outline-offset-1 outline-ui-sky-blue/95 transition-[background-color] duration-300 ease-exponential dark:bg-neutral-700 dark:text-neutral-50',
3849
+ // Pseudo styles
3850
+ 'focus-visible:bg-neutral-50 focus-visible:outline-3 active:bg-neutral-200 dark:focus-visible:bg-neutral-600 dark:active:bg-neutral-800 pointer-fine:hover:bg-neutral-50 pointer-fine:active:bg-neutral-200 dark:pointer-fine:hover:bg-neutral-600 dark:pointer-fine:active:bg-neutral-800',
3851
+ // user-invalid styles
3852
+ 'user-invalid:border-ui-red user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-100))] user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-800))] dark:user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))] user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-100))] user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))]',
3853
+ // Custom styles
3854
+ typeof className === 'function' ? className(bag) : className), onBlur: onBlur, onChange: onChange, onClick: toggleSelectorOpen, ref: node => {
3855
+ refs.setReference(node);
3856
+ return ref;
3857
+ }, type: 'text', value: fieldContext?.value || '' }), jsxRuntime.jsx(react.Transition, { as: 'div', className: 'absolute rounded-xl bg-neutral-100 p-4 transition-opacity duration-500 ease-exponential data-closed:opacity-0 data-open:opacity-100', ref: refs.setFloating, show: selectorIsOpen, style: {
3858
+ ...floatingStyles,
3859
+ transformOrigin: reversedAnchor,
3860
+ }, transition: true })] }));
3861
+ }
3862
+
3863
+ function NumberInput({ className, fieldContextID, handleBlur, handleChange, options, placeholder, type, ...props }) {
3864
+ const [formContext] = useFormContext(), [fieldsetContext] = useFieldsetContext();
3865
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3866
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3867
+ const validation = ({ max, min, value }) => {
3868
+ const valueAsNumber = Number(value);
3869
+ if (isNaN(valueAsNumber))
3870
+ return 'This is not a valid number.';
3871
+ if (typeof max === 'number' && valueAsNumber > max)
3872
+ return `Value cannot be higher than ${max}.`;
3873
+ if (typeof min === 'number' && valueAsNumber < min)
3874
+ return `Value cannot be lower than ${min}.`;
3875
+ };
3876
+ const onChange = e => {
3877
+ handleChange({ e, validation });
3878
+ };
3879
+ const onBlur = e => {
3880
+ handleBlur({ e, validation });
3881
+ };
3882
+ return (jsxRuntime.jsx(react.Input, { ...props, className: bag => twMerge(
3883
+ // Base styles
3884
+ 'w-full rounded-xl border-1 border-neutral-500/50 bg-neutral-100 py-1 pl-2 text-neutral-950 outline-offset-1 outline-ui-sky-blue/95 transition-[background-color] duration-300 ease-exponential dark:bg-neutral-700 dark:text-neutral-50',
3885
+ // Pseudo styles
3886
+ 'focus-visible:bg-neutral-50 focus-visible:outline-3 active:bg-neutral-200 dark:focus-visible:bg-neutral-600 dark:active:bg-neutral-800 pointer-fine:hover:bg-neutral-50 pointer-fine:active:bg-neutral-200 dark:pointer-fine:hover:bg-neutral-600 dark:pointer-fine:active:bg-neutral-800',
3887
+ // user-invalid styles
3888
+ 'user-invalid:border-ui-red user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-100))] user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-800))] dark:user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))] user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-100))] user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))]',
3889
+ // Custom styles
3890
+ typeof className === 'function' ? className(bag) : className), onBlur: onBlur, onChange: onChange, placeholder: placeholder, type: type, value: fieldContext?.value || '' }));
3891
+ }
3892
+
3893
+ const INPUT_COMPONENTS = {
3894
+ button: TextInput,
3895
+ checkbox: TextInput,
3896
+ color: TextInput,
3897
+ date: DateTimeInput,
3898
+ datetime: DateTimeInput,
3899
+ 'datetime-local': DateTimeInput,
3900
+ email: TextInput,
3901
+ file: TextInput,
3902
+ hidden: TextInput,
3903
+ image: TextInput,
3904
+ list: TextInput,
3905
+ month: DateTimeInput,
3906
+ number: NumberInput,
3907
+ password: TextInput,
3908
+ radio: TextInput,
3909
+ range: TextInput,
3910
+ reset: TextInput,
3911
+ search: TextInput,
3912
+ submit: TextInput,
3913
+ tel: TextInput,
3914
+ text: TextInput,
3915
+ time: DateTimeInput,
3916
+ url: TextInput,
3917
+ week: DateTimeInput,
3918
+ year: DateTimeInput,
3919
+ };
3920
+ function Input({ as, checked, className, defaultValue, description, descriptionProps, disabled, fieldProps, invalid = true, label, labelProps, max, min, name, onBlur, onChange, options, placeholder, ref, required = true, type = 'text', value, ...props }) {
3921
+ const [formContext, formContextFunctions] = useFormContext(), [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext(), [errorMessage, setErrorMessage] = React.useState(undefined);
3645
3922
  if (placeholder === '*')
3646
3923
  placeholder = name + (required && !label ? '*' : '');
3647
3924
  if (label === '*')
3648
3925
  label = name;
3649
3926
  const uniqueID = React.useId(), fieldContextID = toLowerCase(name, [, '_']) + '§' + uniqueID;
3927
+ const [internalController, setInternalController] = React.useState(undefined), isControlledByForm = formContext !== undefined, isControlledExternally = typeof value !== 'undefined';
3650
3928
  const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3651
- const getFieldContextType = () => {
3652
- switch (type) {
3653
- case 'email':
3654
- return 'email';
3655
- case 'file':
3656
- return 'file';
3657
- case 'number':
3658
- return 'number';
3659
- case 'tel':
3660
- return 'tel';
3661
- case 'url':
3662
- return 'url';
3663
- default:
3664
- return 'string';
3665
- }
3666
- };
3667
- const fieldContextType = getFieldContextType();
3668
- const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3929
+ const fieldContextType = type === 'email'
3930
+ ? 'email'
3931
+ : type === 'file'
3932
+ ? 'file'
3933
+ : type === 'number'
3934
+ ? 'number'
3935
+ : type === 'tel'
3936
+ ? 'tel'
3937
+ : type === 'url'
3938
+ ? 'url'
3939
+ : 'string';
3940
+ const fieldContext = isControlledByForm
3941
+ ? (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID)
3942
+ : internalController;
3669
3943
  React.useEffect(() => {
3670
3944
  const initialFieldContext = defineField({
3671
- type: fieldContextType,
3672
3945
  id: fieldContextID,
3673
3946
  invalid,
3674
3947
  name,
3675
3948
  required,
3949
+ type: fieldContextType,
3676
3950
  value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3677
3951
  });
3952
+ if (!isControlledByForm) {
3953
+ setInternalController(initialFieldContext);
3954
+ return;
3955
+ }
3678
3956
  if (isInFieldset) {
3679
3957
  fieldsetContextFunctions.registerField(initialFieldContext);
3680
3958
  return () => {
@@ -3686,7 +3964,7 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3686
3964
  formContextFunctions.removeField(initialFieldContext.id);
3687
3965
  };
3688
3966
  }, [isInFieldset]);
3689
- const validateField = (validValue) => {
3967
+ const validateField = (validValue, validation) => {
3690
3968
  const noValue = !validValue || validValue === '';
3691
3969
  if (!required && noValue)
3692
3970
  return true;
@@ -3696,137 +3974,166 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3696
3974
  setErrorMessage(errorMessageList.join(' '));
3697
3975
  return false;
3698
3976
  }
3699
- switch (type) {
3700
- case 'email':
3701
- if (!isEmail(validValue))
3702
- errorMessageList.push('This is not a valid email.');
3703
- break;
3704
- case 'date':
3705
- const valueAsTime = new Date().getTime();
3706
- if (min && !(min instanceof Date) && typeof min !== 'number') {
3707
- if (Array.isArray(min)) {
3708
- const monthIndex = typeof min[1] === 'number' ? min[1] - 1 : getMonthIndexFromName(min[1]);
3709
- min = new Date(min[0], monthIndex, min[2]);
3710
- }
3711
- else if ('year' in min && 'month' in min && 'day' in min) {
3712
- const monthIndex = typeof min.month === 'number' ? min.month - 1 : getMonthIndexFromName(min.month);
3713
- min = new Date(min.year, monthIndex, min.day);
3714
- }
3715
- if (valueAsTime < min.getTime())
3716
- errorMessageList.push(`Value cannot be lower than ${getUserReadableDate(min)}.`);
3717
- }
3718
- if (max && !(max instanceof Date) && typeof max !== 'number') {
3719
- if (Array.isArray(max)) {
3720
- const monthIndex = typeof max[1] === 'number' ? max[1] - 1 : getMonthIndexFromName(max[1]);
3721
- max = new Date(max[0], monthIndex, max[2]);
3722
- }
3723
- else if ('year' in max && 'month' in max && 'day' in max) {
3724
- const monthIndex = typeof max.month === 'number' ? max.month - 1 : getMonthIndexFromName(max.month);
3725
- max = new Date(max.year, monthIndex, max.day);
3726
- }
3727
- if (valueAsTime > max.getTime())
3728
- errorMessageList.push(`Value cannot be higher than ${getUserReadableDate(max)}.`);
3729
- }
3730
- break;
3731
- case 'number':
3732
- const valueAsNumber = Number(validValue);
3733
- if (isNaN(valueAsNumber))
3734
- errorMessageList.push('This is not a valid number.');
3735
- if (typeof max === 'number' && valueAsNumber > max)
3736
- errorMessageList.push(`Value cannot be higher than ${max}.`);
3737
- if (typeof min === 'number' && valueAsNumber < min)
3738
- errorMessageList.push(`Value cannot be lower than ${min}.`);
3739
- break;
3740
- case 'password':
3741
- if (options) {
3742
- const { matchPreviousInput, requireLowercaseCharacter, requireNumber, requireSpecialCharacter, requireUppercaseCharacter, } = options;
3743
- if (matchPreviousInput && formContext && formContext.length >= 2) {
3744
- if (isInFieldset && fieldsetContext.fieldList.length > 1) {
3745
- const currentInputIndex = fieldsetContext.fieldList.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3746
- if (currentInputIndex > 0) {
3747
- const previousInput = fieldsetContext.fieldList.find((_, index) => index === currentInputIndex - 1);
3748
- if (previousInput &&
3749
- isStringField(previousInput) &&
3750
- previousInput.value !== validValue)
3751
- errorMessageList.push('Passwords must match.');
3752
- }
3753
- }
3754
- else {
3755
- const currentInputIndex = formContext.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3756
- if (currentInputIndex > 0) {
3757
- const previousInput = formContext.find((_, index) => index === currentInputIndex - 1);
3758
- if (previousInput &&
3759
- isStringField(previousInput) &&
3760
- previousInput.value !== validValue)
3761
- errorMessageList.push('Passwords must match.');
3762
- }
3763
- }
3764
- }
3765
- if (requireLowercaseCharacter && !/[a-z]/g.test(validValue))
3766
- errorMessageList.push('You must include a lowercase character.');
3767
- if (requireNumber && !/[0-9]/g.test(validValue))
3768
- errorMessageList.push('You must include a number.');
3769
- if (requireSpecialCharacter && !specialCharacterRegex.test(validValue))
3770
- errorMessageList.push('You must include a special character.');
3771
- if (requireUppercaseCharacter && !/[A-Z]/g.test(validValue))
3772
- errorMessageList.push('You must include an uppercase character.');
3773
- }
3774
- break;
3775
- case 'tel':
3776
- if (!isPhoneNumber(validValue))
3777
- errorMessageList.push('This is not a valid phone number.');
3778
- break;
3779
- }
3780
3977
  if (props.maxLength && validValue.length > Number(props.maxLength))
3781
3978
  errorMessageList.push(`This may not have more than ${props.maxLength} characters.`);
3782
3979
  if (props.minLength && validValue.length < Number(props.minLength))
3783
3980
  errorMessageList.push(`This must have at least ${props.minLength} characters.`);
3981
+ if (typeof validation !== 'undefined') {
3982
+ const validationResult = validation({
3983
+ max,
3984
+ min,
3985
+ maxLength: props.maxLength,
3986
+ minLength: props.minLength,
3987
+ multiple: props.multiple,
3988
+ step: props.step,
3989
+ value: validValue,
3990
+ });
3991
+ if (typeof validationResult === 'string')
3992
+ errorMessageList.push(validationResult);
3993
+ }
3784
3994
  if (errorMessageList.length === 0)
3785
3995
  return true;
3786
3996
  setErrorMessage(errorMessageList.join(' '));
3787
3997
  return false;
3788
3998
  };
3789
- const handleChange = e => {
3999
+ const handleChange = ({ e, validation }) => {
3790
4000
  if (disabled) {
3791
4001
  e.preventDefault();
3792
4002
  return;
3793
4003
  }
3794
4004
  const { currentTarget } = e, { value: newValue } = currentTarget;
3795
- if (isInFieldset) {
3796
- fieldsetContextFunctions.updateField(fieldContextID, {
3797
- value: newValue,
3798
- invalid: validateField(newValue) === false,
3799
- });
4005
+ const isInvalid = validateField(newValue, validation) === false;
4006
+ const processedValue = isControlledExternally ? `${value}` : newValue;
4007
+ const updatedFieldValues = {
4008
+ invalid: isInvalid,
4009
+ value: processedValue,
4010
+ };
4011
+ if (isControlledByForm) {
4012
+ if (isInFieldset) {
4013
+ fieldsetContextFunctions.updateField(fieldContextID, updatedFieldValues);
4014
+ }
4015
+ else {
4016
+ formContextFunctions.updateField(fieldContextID, updatedFieldValues);
4017
+ }
3800
4018
  }
3801
4019
  else {
3802
- formContextFunctions.updateField(fieldContextID, { value: newValue, invalid: validateField(newValue) === false });
4020
+ setInternalController(previous => ({
4021
+ ...previous,
4022
+ ...updatedFieldValues,
4023
+ }));
3803
4024
  }
3804
4025
  onChange?.(e);
3805
4026
  };
3806
- const handleBlur = e => {
4027
+ const handleBlur = ({ e, format, validation }) => {
3807
4028
  if (disabled) {
3808
4029
  e.preventDefault();
3809
4030
  return;
3810
4031
  }
3811
4032
  const { currentTarget } = e, { value: newValue } = currentTarget;
3812
- if (required)
3813
- validateField(newValue);
3814
- let processedValue = newValue;
3815
- switch (type) {
3816
- case 'email':
3817
- processedValue = newValue.toLowerCase();
3818
- break;
3819
- case 'tel':
3820
- processedValue = formatPhoneNumber(newValue, options);
3821
- break;
4033
+ const controlledValue = isControlledExternally ? `${value}` : newValue, processedValue = format?.(controlledValue) || controlledValue;
4034
+ const isInvalid = validateField(processedValue, validation) === false;
4035
+ const updatedFieldValues = {
4036
+ invalid: isInvalid,
4037
+ value: processedValue,
4038
+ };
4039
+ if (isControlledByForm) {
4040
+ if (isInFieldset) {
4041
+ fieldsetContextFunctions.updateField(fieldContextID, updatedFieldValues);
4042
+ }
4043
+ else {
4044
+ formContextFunctions.updateField(fieldContextID, updatedFieldValues);
4045
+ }
4046
+ }
4047
+ else {
4048
+ setInternalController(previous => ({
4049
+ ...previous,
4050
+ ...updatedFieldValues,
4051
+ }));
3822
4052
  }
4053
+ onBlur?.(e);
4054
+ };
4055
+ const cleanFieldProps = fieldProps
4056
+ ? Object.fromEntries(Object.entries(fieldProps).filter(([key]) => key !== 'className'))
4057
+ : {};
4058
+ const cleanLabalProps = labelProps
4059
+ ? Object.fromEntries(Object.entries(labelProps).filter(([key]) => key !== 'className'))
4060
+ : {};
4061
+ const cleanDescriptionProps = descriptionProps
4062
+ ? Object.fromEntries(Object.entries(descriptionProps).filter(([key]) => key !== 'className'))
4063
+ : {};
4064
+ const InputComponent = INPUT_COMPONENTS[type];
4065
+ return (jsxRuntime.jsxs(react.Field, { ...cleanFieldProps, className: bag => twMerge('grid gap-1', typeof fieldProps?.className === 'function' ? fieldProps?.className(bag) : fieldProps?.className), disabled: disabled, children: [label && (jsxRuntime.jsx(react.Label, { ...cleanLabalProps, className: bag => twMerge('text-sm font-medium', required ? 'after:text-ui-red after:content-["_*"]' : '', typeof labelProps?.className === 'function' ? labelProps?.className(bag) : labelProps?.className), children: label })), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx(InputComponent, { ...props, fieldContextID: fieldContextID, handleBlur: handleBlur, handleChange: handleChange, invalid: invalid, options: options, placeholder: placeholder, required: required, type: type }), fieldContext?.invalid && errorMessage && (jsxRuntime.jsxs(Tooltip, { anchor: 'top-end', arrow: true, portal: true, children: [jsxRuntime.jsx(TooltipTrigger, { as: Button, className: 'absolute top-1.25 right-1.25 z-10 size-6 min-w-0', padding: 'none', rounded: 'md', theme: 'red', children: jsxRuntime.jsx(ExclamationmarkOctagon, { className: 'absolute top-1/2 left-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-70' }) }), jsxRuntime.jsx(TooltipPanel, { children: errorMessage })] }))] }), description && (jsxRuntime.jsx(react.Description, { ...cleanDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
4066
+ ? descriptionProps?.className(bag)
4067
+ : descriptionProps?.className), children: description }))] }));
4068
+ }
4069
+
4070
+ function extractStringValue(value) {
4071
+ if (Array.isArray(value))
4072
+ return value.join(', ');
4073
+ if (typeof value === 'string')
4074
+ return value;
4075
+ return value.value;
4076
+ }
4077
+ function Select({ defaultValue, description, descriptionProps, disabled, fieldProps, invalid, label, labelProps, multiple, name, onChange, optionList, required, value, ...props }) {
4078
+ const [formContext, formContextFunctions] = useFormContext(), [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext();
4079
+ if (label === '*')
4080
+ label = name;
4081
+ const uniqueID = React.useId(), fieldContextID = toLowerCase(name, [, '_']) + '§' + uniqueID;
4082
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
4083
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
4084
+ React.useEffect(() => {
4085
+ const initialFieldContext = multiple
4086
+ ? defineField({
4087
+ type: 'array',
4088
+ of: value
4089
+ ? value.map(item => ({
4090
+ type: 'string',
4091
+ value: typeof item === 'string' ? item : item.value,
4092
+ }))
4093
+ : defaultValue
4094
+ ? defaultValue.map(item => ({
4095
+ type: 'string',
4096
+ value: typeof item === 'string' ? item : item.value,
4097
+ }))
4098
+ : [],
4099
+ id: fieldContextID,
4100
+ invalid,
4101
+ name,
4102
+ required,
4103
+ })
4104
+ : defineField({
4105
+ type: 'string',
4106
+ id: fieldContextID,
4107
+ invalid,
4108
+ name,
4109
+ required,
4110
+ value: value ? extractStringValue(value) : defaultValue ? extractStringValue(defaultValue) : '',
4111
+ });
3823
4112
  if (isInFieldset) {
3824
- fieldsetContextFunctions.updateField(fieldContextID, { value: processedValue });
4113
+ fieldsetContextFunctions.registerField(initialFieldContext);
4114
+ return () => {
4115
+ fieldsetContextFunctions.removeField(initialFieldContext.id);
4116
+ };
4117
+ }
4118
+ formContextFunctions.registerField(initialFieldContext);
4119
+ return () => {
4120
+ formContextFunctions.removeField(initialFieldContext.id);
4121
+ };
4122
+ }, [isInFieldset]);
4123
+ const handleChange = (newValue) => {
4124
+ if (disabled)
4125
+ return;
4126
+ const isInvalid = newValue === '' || !newValue;
4127
+ if (isInFieldset) {
4128
+ fieldsetContextFunctions.updateField(fieldContextID, {
4129
+ invalid: isInvalid,
4130
+ value: newValue,
4131
+ });
3825
4132
  }
3826
4133
  else {
3827
- formContextFunctions.updateField(fieldContextID, { value: processedValue });
4134
+ formContextFunctions.updateField(fieldContextID, { value: newValue, invalid: isInvalid });
3828
4135
  }
3829
- onBlur?.(e);
4136
+ onChange?.(newValue);
3830
4137
  };
3831
4138
  const restFieldProps = fieldProps
3832
4139
  ? Object.fromEntries(Object.entries(fieldProps).filter(([key]) => key !== 'className'))
@@ -3837,15 +4144,7 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3837
4144
  const restDescriptionProps = descriptionProps
3838
4145
  ? Object.fromEntries(Object.entries(descriptionProps).filter(([key]) => key !== 'className'))
3839
4146
  : {};
3840
- return (jsxRuntime.jsxs(react.Field, { ...restFieldProps, className: bag => twMerge('grid gap-1', typeof fieldProps?.className === 'function' ? fieldProps?.className(bag) : fieldProps?.className), disabled: disabled, children: [label && (jsxRuntime.jsx(react.Label, { ...restLabelProps, className: bag => twMerge('text-sm font-medium', required ? 'after:text-ui-red after:content-["_*"]' : '', typeof labelProps?.className === 'function' ? labelProps?.className(bag) : labelProps?.className), children: label })), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx(react.Input, { ...props, className: bag => twMerge(
3841
- // Base styles
3842
- 'w-full rounded-xl border-1 border-neutral-500/50 bg-neutral-100 py-1 pl-2 text-neutral-950 outline-offset-1 outline-ui-sky-blue/95 transition-[background-color] duration-300 ease-exponential dark:bg-neutral-700 dark:text-neutral-50',
3843
- // Pseudo styles
3844
- 'focus-visible:bg-neutral-50 focus-visible:outline-3 active:bg-neutral-200 dark:focus-visible:bg-neutral-600 dark:active:bg-neutral-800 pointer-fine:hover:bg-neutral-50 pointer-fine:active:bg-neutral-200 dark:pointer-fine:hover:bg-neutral-600 dark:pointer-fine:active:bg-neutral-800',
3845
- // user-invalid styles
3846
- 'user-invalid:border-ui-red user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-100))] user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:bg-[color-mix(in_oklab,var(--color-ui-red)_20%,var(--color-neutral-800))] dark:user-invalid:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))] user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-100))] user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-100))] user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-100))] dark:user-invalid:pointer-fine:hover:bg-[color-mix(in_oklab,var(--color-ui-red)_10%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:focus-visible:bg-[color-mix(in_oklab,var(--color-ui-red)_1%,var(--color-neutral-800))] dark:user-invalid:pointer-fine:active:bg-[color-mix(in_oklab,var(--color-ui-red)_25%,var(--color-neutral-800))]',
3847
- // Custom styles
3848
- typeof className === 'function' ? className(bag) : className), invalid: invalid, onBlur: handleBlur, onChange: handleChange, placeholder: placeholder, ref: ref, required: required, type: type, value: fieldContext?.value || '' }), fieldContext?.invalid && errorMessage && (jsxRuntime.jsxs(Tooltip, { anchor: 'top-end', arrow: true, portal: true, children: [jsxRuntime.jsx(TooltipTrigger, { as: Button, className: 'absolute top-1.25 right-1.25 z-10 size-6 min-w-0', padding: 'none', rounded: 'md', theme: 'red', children: jsxRuntime.jsx(ExclamationmarkOctagon, { className: 'absolute top-1/2 left-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-70' }) }), jsxRuntime.jsx(TooltipPanel, { children: errorMessage })] }))] }), description && (jsxRuntime.jsx(react.Description, { ...restDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
4147
+ return (jsxRuntime.jsxs(react.Field, { ...restFieldProps, className: bag => twMerge('grid gap-1', typeof fieldProps?.className === 'function' ? fieldProps?.className(bag) : fieldProps?.className), disabled: disabled, children: [label && (jsxRuntime.jsx(react.Label, { ...restLabelProps, className: bag => twMerge('text-sm font-medium', required ? 'after:text-ui-red after:content-["_*"]' : '', typeof labelProps?.className === 'function' ? labelProps?.className(bag) : labelProps?.className), children: label })), jsxRuntime.jsxs(react.Listbox, { ...props, onChange: handleChange, value: fieldContext.value, children: [jsxRuntime.jsx(react.ListboxButton, { children: jsxRuntime.jsx(react.ListboxSelectedOption, { options: optionList, placeholder: `Select ${multiple ? 'Any' : 'One'}` }) }), jsxRuntime.jsx(react.ListboxOptions, {})] }), description && (jsxRuntime.jsx(react.Description, { ...restDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
3849
4148
  ? descriptionProps?.className(bag)
3850
4149
  : descriptionProps?.className), children: description }))] }));
3851
4150
  }
@@ -4699,6 +4998,7 @@ exports.ModalClose = ModalClose;
4699
4998
  exports.ModalDialog = ModalDialog;
4700
4999
  exports.ModalTitle = ModalTitle;
4701
5000
  exports.ModalTrigger = ModalTrigger;
5001
+ exports.Select = Select;
4702
5002
  exports.SubmitButton = SubmitButton;
4703
5003
  exports.Textarea = Textarea;
4704
5004
  exports.Time = Time;