mado-ui 0.5.3 → 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.esm.js CHANGED
@@ -2,7 +2,7 @@ import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
2
  import { extendTailwindMerge, twJoin } from 'tailwind-merge';
3
3
  import * as React from 'react';
4
4
  import { Children, isValidElement, Fragment, createContext, useContext, useSyncExternalStore, useRef, useCallback, useEffect, Suspense, useState, useId, useLayoutEffect, cloneElement } from 'react';
5
- import { Button as Button$1, DisclosureButton, DisclosurePanel, Disclosure, MenuButton, MenuItem, MenuItems, MenuSection, MenuHeading, MenuSeparator, Menu, Fieldset as Fieldset$1, Legend, Field, Label, Input as Input$1, Description, Textarea as Textarea$1, DialogTitle, Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react';
5
+ import { Button as Button$1, DisclosureButton, DisclosurePanel, Disclosure, MenuButton, MenuItem, MenuItems, MenuSection, MenuHeading, MenuSeparator, Menu, Fieldset as Fieldset$1, Legend, Input as Input$1, Transition, Field, Label, Description, Listbox, ListboxButton, ListboxSelectedOption, ListboxOptions, Textarea as Textarea$1, DialogTitle, Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react';
6
6
  import * as ReactDOM from 'react-dom';
7
7
  import { createPortal } from 'react-dom';
8
8
 
@@ -119,7 +119,7 @@ getMonthName();
119
119
  /** The name of the current day of the week */
120
120
  getWeekdayName();
121
121
  /**
122
- * ### Get Date
122
+ * # Get Date
123
123
  * - Returns the date with two digits
124
124
  * @param {number|Date} date The date to get date
125
125
  * @returns {string} The date with two digits
@@ -133,7 +133,7 @@ function getDate(date = d) {
133
133
  return formattedDate;
134
134
  }
135
135
  /**
136
- * ### Get Hours
136
+ * # Get Hours
137
137
  * - Returns the hours with two digits
138
138
  * @param {number|Date} hours The date to get hours
139
139
  * @returns {string} The hours with two digits
@@ -147,7 +147,7 @@ function getHours(hours = d) {
147
147
  return formattedHours;
148
148
  }
149
149
  /**
150
- * ### Get Milliseconds
150
+ * # Get Milliseconds
151
151
  * - Returns the milliseconds with two digits
152
152
  * @param {number|Date} milliseconds The date to get milliseconds
153
153
  * @returns {string} The milliseconds with two digits
@@ -161,7 +161,7 @@ function getMilliseconds(milliseconds = d) {
161
161
  return formattedMilliseconds;
162
162
  }
163
163
  /**
164
- * ### Get Minutes
164
+ * # Get Minutes
165
165
  * - Returns the minutes with two digits
166
166
  * @param {number|Date} minutes The date to get minutes
167
167
  * @returns {string} The minutes with two digits
@@ -175,7 +175,7 @@ function getMinutes(minutes = d) {
175
175
  return formattedMinutes;
176
176
  }
177
177
  /**
178
- * ### Get Month
178
+ * # Get Month
179
179
  * - Returns the month with two digits
180
180
  * @param {number|Date} month The date to get month
181
181
  * @returns {string} The month with two digits
@@ -192,7 +192,7 @@ function getMonthIndexFromName(name) {
192
192
  return monthNamesList.findIndex(monthName => monthName === name);
193
193
  }
194
194
  /**
195
- * ### Get Month Name
195
+ * # Get Month Name
196
196
  * - Returns the name of the specified month
197
197
  * @param {Date} date A Date object representing the month to get the name of the month from. (Preset to the current date)
198
198
  * @returns {MonthName} The name of the specified month
@@ -203,7 +203,7 @@ function getMonthName(date = d) {
203
203
  return monthNamesList[date.getMonth()];
204
204
  }
205
205
  /**
206
- * ### Get Seconds
206
+ * # Get Seconds
207
207
  * - Returns the seconds with two digits
208
208
  * @param {number|Date} seconds The date to get seconds
209
209
  * @returns {string} The seconds with two digits
@@ -217,7 +217,7 @@ function getSeconds(seconds = d) {
217
217
  return formattedSeconds;
218
218
  }
219
219
  /**
220
- * ### Get User Readable Date
220
+ * # Get User Readable Date
221
221
  * - Returns a string of the current date in a user-friendly way
222
222
  * - Includes `'Yesterday'`, '`Today'`, and `'Tomorrow'`
223
223
  * @param date (default: `new Date()`)
@@ -241,7 +241,7 @@ function getUserReadableDate(date = d) {
241
241
  return fullDateString;
242
242
  }
243
243
  /**
244
- * ### Get Weekday Name
244
+ * # Get Weekday Name
245
245
  * - Returns the weekday name of the specified day
246
246
  * @param {number | Date} weekday A Date object or number representing the day to get the weekday name from. (Preset to the current date)
247
247
  * @returns {WeekdayName} The name of the specified weekday
@@ -253,7 +253,7 @@ function getWeekdayName(weekday = d) {
253
253
  return weekdayNamesList[weekday.getDay()];
254
254
  }
255
255
  /**
256
- * ### To Full Date String
256
+ * # To Full Date String
257
257
  * - Returns a formatted string to display the date
258
258
  * @param {Date} date (default: `new Date()`)
259
259
  * @param {ToFullDateStringOptionsProps} options Change how to display the weekday, month name, day of the month, and year.
@@ -375,13 +375,14 @@ function isPhoneNumber(tel) {
375
375
  * @returns {string} string formatted
376
376
  */
377
377
  function formatPhoneNumber(phoneNumber, options) {
378
- const format = options?.format || 'standard';
379
- if (format !== 'none')
380
- phoneNumber = phoneNumber.replace(/\D/g, '').slice(-10);
378
+ const format = options?.format || "standard";
379
+ if (format !== "none") {
380
+ phoneNumber = phoneNumber.replace(/\D/g, "").slice(-10);
381
+ }
381
382
  switch (format) {
382
- case 'dot':
383
+ case "dot":
383
384
  phoneNumber = phoneNumber
384
- .split('')
385
+ .split("")
385
386
  .map((char, index) => {
386
387
  if (index === 2)
387
388
  return `${char}.`;
@@ -389,11 +390,11 @@ function formatPhoneNumber(phoneNumber, options) {
389
390
  return `${char}.`;
390
391
  return char;
391
392
  })
392
- .join('');
393
+ .join("");
393
394
  break;
394
- case 'hyphenated':
395
+ case "hyphenated":
395
396
  phoneNumber = phoneNumber
396
- .split('')
397
+ .split("")
397
398
  .map((char, index) => {
398
399
  if (index === 2)
399
400
  return `${char}-`;
@@ -401,11 +402,11 @@ function formatPhoneNumber(phoneNumber, options) {
401
402
  return `${char}-`;
402
403
  return char;
403
404
  })
404
- .join('');
405
+ .join("");
405
406
  break;
406
- case 'space':
407
+ case "space":
407
408
  phoneNumber = phoneNumber
408
- .split('')
409
+ .split("")
409
410
  .map((char, index) => {
410
411
  if (index === 2)
411
412
  return `${char} `;
@@ -413,11 +414,11 @@ function formatPhoneNumber(phoneNumber, options) {
413
414
  return `${char} `;
414
415
  return char;
415
416
  })
416
- .join('');
417
+ .join("");
417
418
  break;
418
- case 'standard':
419
+ case "standard":
419
420
  phoneNumber = phoneNumber
420
- .split('')
421
+ .split("")
421
422
  .map((char, index) => {
422
423
  if (index === 0)
423
424
  return `(${char}`;
@@ -427,9 +428,10 @@ function formatPhoneNumber(phoneNumber, options) {
427
428
  return `${char}-`;
428
429
  return char;
429
430
  })
430
- .join('');
431
+ .join("");
431
432
  }
432
- return `${options?.countryCode ? `+${options?.countryCode} ` : ''}` + phoneNumber;
433
+ return `${options?.countryCode ? `+${options?.countryCode} ` : ""}` +
434
+ phoneNumber;
433
435
  }
434
436
  /**
435
437
  * # To Lower Case
@@ -441,7 +443,7 @@ function formatPhoneNumber(phoneNumber, options) {
441
443
  * @returns The lowercase version of the input string, with the replacements, if provided.
442
444
  */
443
445
  function toLowerCase(str, [delimiter, joiner]) {
444
- return str.toLowerCase().replaceAll(delimiter || ' ', joiner || ' ');
446
+ return str.toLowerCase().replaceAll(delimiter || " ", joiner || " ");
445
447
  }
446
448
 
447
449
  function twSort(className) {
@@ -2288,7 +2290,7 @@ function isOverflowElement(element) {
2288
2290
  overflowX,
2289
2291
  overflowY,
2290
2292
  display
2291
- } = getComputedStyle(element);
2293
+ } = getComputedStyle$1(element);
2292
2294
  return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !invalidOverflowDisplayValues.has(display);
2293
2295
  }
2294
2296
  const tableElements = /*#__PURE__*/new Set(['table', 'td', 'th']);
@@ -2310,7 +2312,7 @@ const willChangeValues = ['transform', 'translate', 'scale', 'rotate', 'perspect
2310
2312
  const containValues = ['paint', 'layout', 'strict', 'content'];
2311
2313
  function isContainingBlock(elementOrCss) {
2312
2314
  const webkit = isWebKit();
2313
- const css = isElement(elementOrCss) ? getComputedStyle(elementOrCss) : elementOrCss;
2315
+ const css = isElement(elementOrCss) ? getComputedStyle$1(elementOrCss) : elementOrCss;
2314
2316
 
2315
2317
  // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
2316
2318
  // https://drafts.csswg.org/css-transforms-2/#individual-transforms
@@ -2336,7 +2338,7 @@ const lastTraversableNodeNames = /*#__PURE__*/new Set(['html', 'body', '#documen
2336
2338
  function isLastTraversableNode(node) {
2337
2339
  return lastTraversableNodeNames.has(getNodeName(node));
2338
2340
  }
2339
- function getComputedStyle(element) {
2341
+ function getComputedStyle$1(element) {
2340
2342
  return getWindow(element).getComputedStyle(element);
2341
2343
  }
2342
2344
  function getNodeScroll(element) {
@@ -2398,7 +2400,7 @@ function getFrameElement(win) {
2398
2400
  }
2399
2401
 
2400
2402
  function getCssDimensions(element) {
2401
- const css = getComputedStyle(element);
2403
+ const css = getComputedStyle$1(element);
2402
2404
  // In testing environments, the `width` and `height` properties are empty
2403
2405
  // strings for SVG elements, returning NaN. Fallback to `0` in this case.
2404
2406
  let width = parseFloat(css.width) || 0;
@@ -2503,7 +2505,7 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar
2503
2505
  while (currentIFrame && offsetParent && offsetWin !== currentWin) {
2504
2506
  const iframeScale = getScale(currentIFrame);
2505
2507
  const iframeRect = currentIFrame.getBoundingClientRect();
2506
- const css = getComputedStyle(currentIFrame);
2508
+ const css = getComputedStyle$1(currentIFrame);
2507
2509
  const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;
2508
2510
  const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;
2509
2511
  x *= iframeScale.x;
@@ -2534,14 +2536,9 @@ function getWindowScrollBarX(element, rect) {
2534
2536
  return rect.left + leftScroll;
2535
2537
  }
2536
2538
 
2537
- function getHTMLOffset(documentElement, scroll, ignoreScrollbarX) {
2538
- if (ignoreScrollbarX === void 0) {
2539
- ignoreScrollbarX = false;
2540
- }
2539
+ function getHTMLOffset(documentElement, scroll) {
2541
2540
  const htmlRect = documentElement.getBoundingClientRect();
2542
- const x = htmlRect.left + scroll.scrollLeft - (ignoreScrollbarX ? 0 :
2543
- // RTL <body> scrollbar.
2544
- getWindowScrollBarX(documentElement, htmlRect));
2541
+ const x = htmlRect.left + scroll.scrollLeft - getWindowScrollBarX(documentElement, htmlRect);
2545
2542
  const y = htmlRect.top + scroll.scrollTop;
2546
2543
  return {
2547
2544
  x,
@@ -2580,7 +2577,7 @@ function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) {
2580
2577
  offsets.y = offsetRect.y + offsetParent.clientTop;
2581
2578
  }
2582
2579
  }
2583
- const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll, true) : createCoords(0);
2580
+ const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0);
2584
2581
  return {
2585
2582
  width: rect.width * scale.x,
2586
2583
  height: rect.height * scale.y,
@@ -2603,7 +2600,7 @@ function getDocumentRect(element) {
2603
2600
  const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);
2604
2601
  let x = -scroll.scrollLeft + getWindowScrollBarX(element);
2605
2602
  const y = -scroll.scrollTop;
2606
- if (getComputedStyle(body).direction === 'rtl') {
2603
+ if (getComputedStyle$1(body).direction === 'rtl') {
2607
2604
  x += max(html.clientWidth, body.clientWidth) - width;
2608
2605
  }
2609
2606
  return {
@@ -2614,6 +2611,10 @@ function getDocumentRect(element) {
2614
2611
  };
2615
2612
  }
2616
2613
 
2614
+ // Safety check: ensure the scrollbar space is reasonable in case this
2615
+ // calculation is affected by unusual styles.
2616
+ // Most scrollbars leave 15-18px of space.
2617
+ const SCROLLBAR_MAX = 25;
2617
2618
  function getViewportRect(element, strategy) {
2618
2619
  const win = getWindow(element);
2619
2620
  const html = getDocumentElement(element);
@@ -2631,6 +2632,24 @@ function getViewportRect(element, strategy) {
2631
2632
  y = visualViewport.offsetTop;
2632
2633
  }
2633
2634
  }
2635
+ const windowScrollbarX = getWindowScrollBarX(html);
2636
+ // <html> `overflow: hidden` + `scrollbar-gutter: stable` reduces the
2637
+ // visual width of the <html> but this is not considered in the size
2638
+ // of `html.clientWidth`.
2639
+ if (windowScrollbarX <= 0) {
2640
+ const doc = html.ownerDocument;
2641
+ const body = doc.body;
2642
+ const bodyStyles = getComputedStyle(body);
2643
+ const bodyMarginInline = doc.compatMode === 'CSS1Compat' ? parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight) || 0 : 0;
2644
+ const clippingStableScrollbarWidth = Math.abs(html.clientWidth - body.clientWidth - bodyMarginInline);
2645
+ if (clippingStableScrollbarWidth <= SCROLLBAR_MAX) {
2646
+ width -= clippingStableScrollbarWidth;
2647
+ }
2648
+ } else if (windowScrollbarX <= SCROLLBAR_MAX) {
2649
+ // If the <body> scrollbar is on the left, the width needs to be extended
2650
+ // by the scrollbar amount so there isn't extra space on the right.
2651
+ width += windowScrollbarX;
2652
+ }
2634
2653
  return {
2635
2654
  width,
2636
2655
  height,
@@ -2681,7 +2700,7 @@ function hasFixedPositionAncestor(element, stopNode) {
2681
2700
  if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) {
2682
2701
  return false;
2683
2702
  }
2684
- return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);
2703
+ return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode);
2685
2704
  }
2686
2705
 
2687
2706
  // A "clipping ancestor" is an `overflow` element with the characteristic of
@@ -2694,12 +2713,12 @@ function getClippingElementAncestors(element, cache) {
2694
2713
  }
2695
2714
  let result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body');
2696
2715
  let currentContainingBlockComputedStyle = null;
2697
- const elementIsFixed = getComputedStyle(element).position === 'fixed';
2716
+ const elementIsFixed = getComputedStyle$1(element).position === 'fixed';
2698
2717
  let currentNode = elementIsFixed ? getParentNode(element) : element;
2699
2718
 
2700
2719
  // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
2701
2720
  while (isElement(currentNode) && !isLastTraversableNode(currentNode)) {
2702
- const computedStyle = getComputedStyle(currentNode);
2721
+ const computedStyle = getComputedStyle$1(currentNode);
2703
2722
  const currentNodeIsContaining = isContainingBlock(currentNode);
2704
2723
  if (!currentNodeIsContaining && computedStyle.position === 'fixed') {
2705
2724
  currentContainingBlockComputedStyle = null;
@@ -2800,11 +2819,11 @@ function getRectRelativeToOffsetParent(element, offsetParent, strategy) {
2800
2819
  }
2801
2820
 
2802
2821
  function isStaticPositioned(element) {
2803
- return getComputedStyle(element).position === 'static';
2822
+ return getComputedStyle$1(element).position === 'static';
2804
2823
  }
2805
2824
 
2806
2825
  function getTrueOffsetParent(element, polyfill) {
2807
- if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') {
2826
+ if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') {
2808
2827
  return null;
2809
2828
  }
2810
2829
  if (polyfill) {
@@ -2865,7 +2884,7 @@ const getElementRects = async function (data) {
2865
2884
  };
2866
2885
 
2867
2886
  function isRTL(element) {
2868
- return getComputedStyle(element).direction === 'rtl';
2887
+ return getComputedStyle$1(element).direction === 'rtl';
2869
2888
  }
2870
2889
 
2871
2890
  const platform = {
@@ -3618,43 +3637,302 @@ function ArrowSvg({ className, ...props }) {
3618
3637
  }
3619
3638
 
3620
3639
  const specialCharacterRegex = new RegExp(/[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~€‚ƒ„…†‡‰‹‘’“”•–—™›¡¢£¥§©«¬®°±¶º»¿×÷]/);
3621
- 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 }) {
3622
- const [formContext, formContextFunctions] = useFormContext(), [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext(), [errorMessage, setErrorMessage] = useState(undefined);
3640
+ function TextInput({ className, fieldContextID, handleBlur, handleChange, options, placeholder, type, ...props }) {
3641
+ const [formContext] = useFormContext(), [fieldsetContext] = useFieldsetContext();
3642
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3643
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3623
3644
  if (type === 'password' && !placeholder)
3624
- placeholder = '••••••••' + (required && !label ? '*' : '');
3645
+ placeholder = '••••••••';
3646
+ const FORMAT_FUNCTIONS = {
3647
+ email: (value) => value.toLowerCase(),
3648
+ tel: (value) => formatPhoneNumber(value, options),
3649
+ };
3650
+ const VALIDATION_HANDLERS = {
3651
+ email: ({ multiple, value }) => {
3652
+ if (multiple) {
3653
+ const emailList = value.split(',').map(item => item.trim()), notEmailList = [];
3654
+ emailList.forEach(item => {
3655
+ if (!isEmail(item))
3656
+ notEmailList.push(item);
3657
+ });
3658
+ if (notEmailList.length > 0) {
3659
+ const multipleInvalid = notEmailList.length > 1;
3660
+ return `${multipleInvalid ? 'These email addresses are' : 'This email address is'} invalid: ${notEmailList.join(', ')}`;
3661
+ }
3662
+ return;
3663
+ }
3664
+ if (!isEmail(value))
3665
+ return 'This is not a valid email address.';
3666
+ },
3667
+ password: ({ value }) => {
3668
+ const errorMessageList = [];
3669
+ if (options) {
3670
+ const { matchPreviousInput, requireLowercaseCharacter, requireNumber, requireSpecialCharacter, requireUppercaseCharacter, } = options;
3671
+ if (matchPreviousInput && formContext && formContext.length >= 2) {
3672
+ if (isInFieldset && fieldsetContext.fieldList.length > 1) {
3673
+ const currentInputIndex = fieldsetContext.fieldList.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3674
+ if (currentInputIndex > 0) {
3675
+ const previousInput = fieldsetContext.fieldList.find((_, index) => index === currentInputIndex - 1);
3676
+ if (previousInput &&
3677
+ isStringField(previousInput) &&
3678
+ previousInput.value !== value)
3679
+ errorMessageList.push('Passwords must match.');
3680
+ }
3681
+ }
3682
+ else {
3683
+ const currentInputIndex = formContext.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3684
+ if (currentInputIndex > 0) {
3685
+ const previousInput = formContext.find((_, index) => index === currentInputIndex - 1);
3686
+ if (previousInput &&
3687
+ isStringField(previousInput) &&
3688
+ previousInput.value !== value)
3689
+ errorMessageList.push('Passwords must match.');
3690
+ }
3691
+ }
3692
+ }
3693
+ if (requireLowercaseCharacter && !/[a-z]/g.test(value))
3694
+ errorMessageList.push('You must include a lowercase character.');
3695
+ if (requireNumber && !/[0-9]/g.test(value))
3696
+ errorMessageList.push('You must include a number.');
3697
+ if (requireSpecialCharacter && !specialCharacterRegex.test(value))
3698
+ errorMessageList.push('You must include a special character.');
3699
+ if (requireUppercaseCharacter && !/[A-Z]/g.test(value))
3700
+ errorMessageList.push('You must include an uppercase character.');
3701
+ }
3702
+ return errorMessageList.length > 0 ? errorMessageList.join(' ') : undefined;
3703
+ },
3704
+ tel: ({ value }) => {
3705
+ if (!isPhoneNumber(value))
3706
+ return 'This is not a valid phone number.';
3707
+ },
3708
+ };
3709
+ const onChange = e => {
3710
+ const validation = type ? VALIDATION_HANDLERS[type] : undefined;
3711
+ handleChange({ e, validation });
3712
+ };
3713
+ const onBlur = e => {
3714
+ const format = type ? FORMAT_FUNCTIONS[type] : undefined, validation = type ? VALIDATION_HANDLERS[type] : undefined;
3715
+ handleBlur({ e, format, validation });
3716
+ };
3717
+ return (jsx(Input$1, { ...props, className: bag => twMerge(
3718
+ // Base styles
3719
+ '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',
3720
+ // Pseudo styles
3721
+ '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',
3722
+ // user-invalid styles
3723
+ '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))]',
3724
+ // Custom styles
3725
+ typeof className === 'function' ? className(bag) : className), onBlur: onBlur, onChange: onChange, placeholder: placeholder, type: type, value: fieldContext?.value || '' }));
3726
+ }
3727
+
3728
+ function DateTimeInput({ className, fieldContextID, handleBlur, handleChange, ref, theme, type, ui, ...props }) {
3729
+ const [formContext] = useFormContext(), [fieldsetContext] = useFieldsetContext();
3730
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3731
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3732
+ if (!ui)
3733
+ switch (type) {
3734
+ case 'date':
3735
+ ui = {
3736
+ desktop: ['calendar', 'dropdown'],
3737
+ mobile: ['calendar', 'rotary'],
3738
+ };
3739
+ break;
3740
+ case 'datetime':
3741
+ ui = {
3742
+ date: {
3743
+ desktop: ['calendar', 'dropdown'],
3744
+ mobile: ['calendar', 'rotary'],
3745
+ },
3746
+ time: {
3747
+ desktop: ['clock', 'dropdown'],
3748
+ mobile: ['rotary', 'clock'],
3749
+ },
3750
+ };
3751
+ break;
3752
+ case 'time':
3753
+ ui = {
3754
+ desktop: ['clock', 'dropdown'],
3755
+ mobile: ['rotary', 'clock'],
3756
+ };
3757
+ break;
3758
+ }
3759
+ const validation = ({ max, min, value }) => {
3760
+ const valueAsTime = new Date(value).getTime();
3761
+ if (min && !(min instanceof Date) && typeof min !== 'number') {
3762
+ if (Array.isArray(min)) {
3763
+ const monthIndex = typeof min[1] === 'number' ? min[1] - 1 : getMonthIndexFromName(min[1]);
3764
+ min = new Date(min[0], monthIndex, min[2]);
3765
+ }
3766
+ else if ('year' in min && 'month' in min && 'day' in min) {
3767
+ const monthIndex = typeof min.month === 'number' ? min.month - 1 : getMonthIndexFromName(min.month);
3768
+ min = new Date(min.year, monthIndex, min.day);
3769
+ }
3770
+ if (valueAsTime < min.getTime())
3771
+ return `Value cannot be lower than ${getUserReadableDate(min)}.`;
3772
+ }
3773
+ if (max && !(max instanceof Date) && typeof max !== 'number') {
3774
+ if (Array.isArray(max)) {
3775
+ const monthIndex = typeof max[1] === 'number' ? max[1] - 1 : getMonthIndexFromName(max[1]);
3776
+ max = new Date(max[0], monthIndex, max[2]);
3777
+ }
3778
+ else if ('year' in max && 'month' in max && 'day' in max) {
3779
+ const monthIndex = typeof max.month === 'number' ? max.month - 1 : getMonthIndexFromName(max.month);
3780
+ max = new Date(max.year, monthIndex, max.day);
3781
+ }
3782
+ if (valueAsTime > max.getTime())
3783
+ return `Value cannot be higher than ${getUserReadableDate(max)}.`;
3784
+ }
3785
+ };
3786
+ const onChange = e => {
3787
+ handleChange({ e, validation });
3788
+ };
3789
+ const onBlur = e => {
3790
+ handleBlur({ e, validation });
3791
+ };
3792
+ const [selectorIsOpen, setSelectorIsOpen] = useState(false);
3793
+ const toggleSelectorOpen = () => setSelectorIsOpen(previous => !previous);
3794
+ const { refs, floatingStyles, isPositioned, placement, middlewareData } = useFloating({
3795
+ middleware: [
3796
+ offset(8),
3797
+ flip({ padding: 20 }),
3798
+ shift({ padding: 20 }),
3799
+ size({
3800
+ apply({ availableHeight, availableWidth, elements }) {
3801
+ const height = `${Math.max(0, availableHeight) / 16}rem`, width = `${Math.min(418, availableWidth) / 16}rem`;
3802
+ elements.floating.style.maxHeight = height;
3803
+ elements.floating.style.maxWidth = width;
3804
+ },
3805
+ }),
3806
+ ],
3807
+ placement: 'top-start',
3808
+ strategy: 'fixed',
3809
+ whileElementsMounted: autoUpdate,
3810
+ open: selectorIsOpen,
3811
+ });
3812
+ const reversedAnchor = {
3813
+ top: 'bottom',
3814
+ right: 'left',
3815
+ bottom: 'top',
3816
+ left: 'right',
3817
+ 'top-start': 'bottom left',
3818
+ 'top-end': 'bottom right',
3819
+ 'right-start': 'top left',
3820
+ 'right-end': 'bottom left',
3821
+ 'bottom-start': 'top left',
3822
+ 'bottom-end': 'top right',
3823
+ 'left-start': 'top right',
3824
+ 'left-end': 'bottom right',
3825
+ }[placement];
3826
+ return (jsxs(Fragment$1, { children: [jsx(Input$1, { ...props, className: bag => twMerge(
3827
+ // Base styles
3828
+ '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',
3829
+ // Pseudo styles
3830
+ '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',
3831
+ // user-invalid styles
3832
+ '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))]',
3833
+ // Custom styles
3834
+ typeof className === 'function' ? className(bag) : className), onBlur: onBlur, onChange: onChange, onClick: toggleSelectorOpen, ref: node => {
3835
+ refs.setReference(node);
3836
+ return ref;
3837
+ }, type: 'text', value: fieldContext?.value || '' }), jsx(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: {
3838
+ ...floatingStyles,
3839
+ transformOrigin: reversedAnchor,
3840
+ }, transition: true })] }));
3841
+ }
3842
+
3843
+ function NumberInput({ className, fieldContextID, handleBlur, handleChange, options, placeholder, type, ...props }) {
3844
+ const [formContext] = useFormContext(), [fieldsetContext] = useFieldsetContext();
3845
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3846
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3847
+ const validation = ({ max, min, value }) => {
3848
+ const valueAsNumber = Number(value);
3849
+ if (isNaN(valueAsNumber))
3850
+ return 'This is not a valid number.';
3851
+ if (typeof max === 'number' && valueAsNumber > max)
3852
+ return `Value cannot be higher than ${max}.`;
3853
+ if (typeof min === 'number' && valueAsNumber < min)
3854
+ return `Value cannot be lower than ${min}.`;
3855
+ };
3856
+ const onChange = e => {
3857
+ handleChange({ e, validation });
3858
+ };
3859
+ const onBlur = e => {
3860
+ handleBlur({ e, validation });
3861
+ };
3862
+ return (jsx(Input$1, { ...props, className: bag => twMerge(
3863
+ // Base styles
3864
+ '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',
3865
+ // Pseudo styles
3866
+ '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',
3867
+ // user-invalid styles
3868
+ '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))]',
3869
+ // Custom styles
3870
+ typeof className === 'function' ? className(bag) : className), onBlur: onBlur, onChange: onChange, placeholder: placeholder, type: type, value: fieldContext?.value || '' }));
3871
+ }
3872
+
3873
+ const INPUT_COMPONENTS = {
3874
+ button: TextInput,
3875
+ checkbox: TextInput,
3876
+ color: TextInput,
3877
+ date: DateTimeInput,
3878
+ datetime: DateTimeInput,
3879
+ 'datetime-local': DateTimeInput,
3880
+ email: TextInput,
3881
+ file: TextInput,
3882
+ hidden: TextInput,
3883
+ image: TextInput,
3884
+ list: TextInput,
3885
+ month: DateTimeInput,
3886
+ number: NumberInput,
3887
+ password: TextInput,
3888
+ radio: TextInput,
3889
+ range: TextInput,
3890
+ reset: TextInput,
3891
+ search: TextInput,
3892
+ submit: TextInput,
3893
+ tel: TextInput,
3894
+ text: TextInput,
3895
+ time: DateTimeInput,
3896
+ url: TextInput,
3897
+ week: DateTimeInput,
3898
+ year: DateTimeInput,
3899
+ };
3900
+ 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 }) {
3901
+ const [formContext, formContextFunctions] = useFormContext(), [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext(), [errorMessage, setErrorMessage] = useState(undefined);
3625
3902
  if (placeholder === '*')
3626
3903
  placeholder = name + (required && !label ? '*' : '');
3627
3904
  if (label === '*')
3628
3905
  label = name;
3629
3906
  const uniqueID = useId(), fieldContextID = toLowerCase(name, [, '_']) + '§' + uniqueID;
3907
+ const [internalController, setInternalController] = useState(undefined), isControlledByForm = formContext !== undefined, isControlledExternally = typeof value !== 'undefined';
3630
3908
  const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3631
- const getFieldContextType = () => {
3632
- switch (type) {
3633
- case 'email':
3634
- return 'email';
3635
- case 'file':
3636
- return 'file';
3637
- case 'number':
3638
- return 'number';
3639
- case 'tel':
3640
- return 'tel';
3641
- case 'url':
3642
- return 'url';
3643
- default:
3644
- return 'string';
3645
- }
3646
- };
3647
- const fieldContextType = getFieldContextType();
3648
- const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3909
+ const fieldContextType = type === 'email'
3910
+ ? 'email'
3911
+ : type === 'file'
3912
+ ? 'file'
3913
+ : type === 'number'
3914
+ ? 'number'
3915
+ : type === 'tel'
3916
+ ? 'tel'
3917
+ : type === 'url'
3918
+ ? 'url'
3919
+ : 'string';
3920
+ const fieldContext = isControlledByForm
3921
+ ? (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID)
3922
+ : internalController;
3649
3923
  useEffect(() => {
3650
3924
  const initialFieldContext = defineField({
3651
- type: fieldContextType,
3652
3925
  id: fieldContextID,
3653
3926
  invalid,
3654
3927
  name,
3655
3928
  required,
3929
+ type: fieldContextType,
3656
3930
  value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3657
3931
  });
3932
+ if (!isControlledByForm) {
3933
+ setInternalController(initialFieldContext);
3934
+ return;
3935
+ }
3658
3936
  if (isInFieldset) {
3659
3937
  fieldsetContextFunctions.registerField(initialFieldContext);
3660
3938
  return () => {
@@ -3666,7 +3944,7 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3666
3944
  formContextFunctions.removeField(initialFieldContext.id);
3667
3945
  };
3668
3946
  }, [isInFieldset]);
3669
- const validateField = (validValue) => {
3947
+ const validateField = (validValue, validation) => {
3670
3948
  const noValue = !validValue || validValue === '';
3671
3949
  if (!required && noValue)
3672
3950
  return true;
@@ -3676,137 +3954,166 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3676
3954
  setErrorMessage(errorMessageList.join(' '));
3677
3955
  return false;
3678
3956
  }
3679
- switch (type) {
3680
- case 'email':
3681
- if (!isEmail(validValue))
3682
- errorMessageList.push('This is not a valid email.');
3683
- break;
3684
- case 'date':
3685
- const valueAsTime = new Date().getTime();
3686
- if (min && !(min instanceof Date) && typeof min !== 'number') {
3687
- if (Array.isArray(min)) {
3688
- const monthIndex = typeof min[1] === 'number' ? min[1] - 1 : getMonthIndexFromName(min[1]);
3689
- min = new Date(min[0], monthIndex, min[2]);
3690
- }
3691
- else if ('year' in min && 'month' in min && 'day' in min) {
3692
- const monthIndex = typeof min.month === 'number' ? min.month - 1 : getMonthIndexFromName(min.month);
3693
- min = new Date(min.year, monthIndex, min.day);
3694
- }
3695
- if (valueAsTime < min.getTime())
3696
- errorMessageList.push(`Value cannot be lower than ${getUserReadableDate(min)}.`);
3697
- }
3698
- if (max && !(max instanceof Date) && typeof max !== 'number') {
3699
- if (Array.isArray(max)) {
3700
- const monthIndex = typeof max[1] === 'number' ? max[1] - 1 : getMonthIndexFromName(max[1]);
3701
- max = new Date(max[0], monthIndex, max[2]);
3702
- }
3703
- else if ('year' in max && 'month' in max && 'day' in max) {
3704
- const monthIndex = typeof max.month === 'number' ? max.month - 1 : getMonthIndexFromName(max.month);
3705
- max = new Date(max.year, monthIndex, max.day);
3706
- }
3707
- if (valueAsTime > max.getTime())
3708
- errorMessageList.push(`Value cannot be higher than ${getUserReadableDate(max)}.`);
3709
- }
3710
- break;
3711
- case 'number':
3712
- const valueAsNumber = Number(validValue);
3713
- if (isNaN(valueAsNumber))
3714
- errorMessageList.push('This is not a valid number.');
3715
- if (typeof max === 'number' && valueAsNumber > max)
3716
- errorMessageList.push(`Value cannot be higher than ${max}.`);
3717
- if (typeof min === 'number' && valueAsNumber < min)
3718
- errorMessageList.push(`Value cannot be lower than ${min}.`);
3719
- break;
3720
- case 'password':
3721
- if (options) {
3722
- const { matchPreviousInput, requireLowercaseCharacter, requireNumber, requireSpecialCharacter, requireUppercaseCharacter, } = options;
3723
- if (matchPreviousInput && formContext && formContext.length >= 2) {
3724
- if (isInFieldset && fieldsetContext.fieldList.length > 1) {
3725
- const currentInputIndex = fieldsetContext.fieldList.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3726
- if (currentInputIndex > 0) {
3727
- const previousInput = fieldsetContext.fieldList.find((_, index) => index === currentInputIndex - 1);
3728
- if (previousInput &&
3729
- isStringField(previousInput) &&
3730
- previousInput.value !== validValue)
3731
- errorMessageList.push('Passwords must match.');
3732
- }
3733
- }
3734
- else {
3735
- const currentInputIndex = formContext.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3736
- if (currentInputIndex > 0) {
3737
- const previousInput = formContext.find((_, index) => index === currentInputIndex - 1);
3738
- if (previousInput &&
3739
- isStringField(previousInput) &&
3740
- previousInput.value !== validValue)
3741
- errorMessageList.push('Passwords must match.');
3742
- }
3743
- }
3744
- }
3745
- if (requireLowercaseCharacter && !/[a-z]/g.test(validValue))
3746
- errorMessageList.push('You must include a lowercase character.');
3747
- if (requireNumber && !/[0-9]/g.test(validValue))
3748
- errorMessageList.push('You must include a number.');
3749
- if (requireSpecialCharacter && !specialCharacterRegex.test(validValue))
3750
- errorMessageList.push('You must include a special character.');
3751
- if (requireUppercaseCharacter && !/[A-Z]/g.test(validValue))
3752
- errorMessageList.push('You must include an uppercase character.');
3753
- }
3754
- break;
3755
- case 'tel':
3756
- if (!isPhoneNumber(validValue))
3757
- errorMessageList.push('This is not a valid phone number.');
3758
- break;
3759
- }
3760
3957
  if (props.maxLength && validValue.length > Number(props.maxLength))
3761
3958
  errorMessageList.push(`This may not have more than ${props.maxLength} characters.`);
3762
3959
  if (props.minLength && validValue.length < Number(props.minLength))
3763
3960
  errorMessageList.push(`This must have at least ${props.minLength} characters.`);
3961
+ if (typeof validation !== 'undefined') {
3962
+ const validationResult = validation({
3963
+ max,
3964
+ min,
3965
+ maxLength: props.maxLength,
3966
+ minLength: props.minLength,
3967
+ multiple: props.multiple,
3968
+ step: props.step,
3969
+ value: validValue,
3970
+ });
3971
+ if (typeof validationResult === 'string')
3972
+ errorMessageList.push(validationResult);
3973
+ }
3764
3974
  if (errorMessageList.length === 0)
3765
3975
  return true;
3766
3976
  setErrorMessage(errorMessageList.join(' '));
3767
3977
  return false;
3768
3978
  };
3769
- const handleChange = e => {
3979
+ const handleChange = ({ e, validation }) => {
3770
3980
  if (disabled) {
3771
3981
  e.preventDefault();
3772
3982
  return;
3773
3983
  }
3774
3984
  const { currentTarget } = e, { value: newValue } = currentTarget;
3775
- if (isInFieldset) {
3776
- fieldsetContextFunctions.updateField(fieldContextID, {
3777
- value: newValue,
3778
- invalid: validateField(newValue) === false,
3779
- });
3985
+ const isInvalid = validateField(newValue, validation) === false;
3986
+ const processedValue = isControlledExternally ? `${value}` : newValue;
3987
+ const updatedFieldValues = {
3988
+ invalid: isInvalid,
3989
+ value: processedValue,
3990
+ };
3991
+ if (isControlledByForm) {
3992
+ if (isInFieldset) {
3993
+ fieldsetContextFunctions.updateField(fieldContextID, updatedFieldValues);
3994
+ }
3995
+ else {
3996
+ formContextFunctions.updateField(fieldContextID, updatedFieldValues);
3997
+ }
3780
3998
  }
3781
3999
  else {
3782
- formContextFunctions.updateField(fieldContextID, { value: newValue, invalid: validateField(newValue) === false });
4000
+ setInternalController(previous => ({
4001
+ ...previous,
4002
+ ...updatedFieldValues,
4003
+ }));
3783
4004
  }
3784
4005
  onChange?.(e);
3785
4006
  };
3786
- const handleBlur = e => {
4007
+ const handleBlur = ({ e, format, validation }) => {
3787
4008
  if (disabled) {
3788
4009
  e.preventDefault();
3789
4010
  return;
3790
4011
  }
3791
4012
  const { currentTarget } = e, { value: newValue } = currentTarget;
3792
- if (required)
3793
- validateField(newValue);
3794
- let processedValue = newValue;
3795
- switch (type) {
3796
- case 'email':
3797
- processedValue = newValue.toLowerCase();
3798
- break;
3799
- case 'tel':
3800
- processedValue = formatPhoneNumber(newValue, options);
3801
- break;
4013
+ const controlledValue = isControlledExternally ? `${value}` : newValue, processedValue = format?.(controlledValue) || controlledValue;
4014
+ const isInvalid = validateField(processedValue, validation) === false;
4015
+ const updatedFieldValues = {
4016
+ invalid: isInvalid,
4017
+ value: processedValue,
4018
+ };
4019
+ if (isControlledByForm) {
4020
+ if (isInFieldset) {
4021
+ fieldsetContextFunctions.updateField(fieldContextID, updatedFieldValues);
4022
+ }
4023
+ else {
4024
+ formContextFunctions.updateField(fieldContextID, updatedFieldValues);
4025
+ }
4026
+ }
4027
+ else {
4028
+ setInternalController(previous => ({
4029
+ ...previous,
4030
+ ...updatedFieldValues,
4031
+ }));
3802
4032
  }
4033
+ onBlur?.(e);
4034
+ };
4035
+ const cleanFieldProps = fieldProps
4036
+ ? Object.fromEntries(Object.entries(fieldProps).filter(([key]) => key !== 'className'))
4037
+ : {};
4038
+ const cleanLabalProps = labelProps
4039
+ ? Object.fromEntries(Object.entries(labelProps).filter(([key]) => key !== 'className'))
4040
+ : {};
4041
+ const cleanDescriptionProps = descriptionProps
4042
+ ? Object.fromEntries(Object.entries(descriptionProps).filter(([key]) => key !== 'className'))
4043
+ : {};
4044
+ const InputComponent = INPUT_COMPONENTS[type];
4045
+ return (jsxs(Field, { ...cleanFieldProps, className: bag => twMerge('grid gap-1', typeof fieldProps?.className === 'function' ? fieldProps?.className(bag) : fieldProps?.className), disabled: disabled, children: [label && (jsx(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 })), jsxs("div", { children: [jsx(InputComponent, { ...props, fieldContextID: fieldContextID, handleBlur: handleBlur, handleChange: handleChange, invalid: invalid, options: options, placeholder: placeholder, required: required, type: type }), fieldContext?.invalid && errorMessage && (jsxs(Tooltip, { anchor: 'top-end', arrow: true, portal: true, children: [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: jsx(ExclamationmarkOctagon, { className: 'absolute top-1/2 left-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-70' }) }), jsx(TooltipPanel, { children: errorMessage })] }))] }), description && (jsx(Description, { ...cleanDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
4046
+ ? descriptionProps?.className(bag)
4047
+ : descriptionProps?.className), children: description }))] }));
4048
+ }
4049
+
4050
+ function extractStringValue(value) {
4051
+ if (Array.isArray(value))
4052
+ return value.join(', ');
4053
+ if (typeof value === 'string')
4054
+ return value;
4055
+ return value.value;
4056
+ }
4057
+ function Select({ defaultValue, description, descriptionProps, disabled, fieldProps, invalid, label, labelProps, multiple, name, onChange, optionList, required, value, ...props }) {
4058
+ const [formContext, formContextFunctions] = useFormContext(), [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext();
4059
+ if (label === '*')
4060
+ label = name;
4061
+ const uniqueID = useId(), fieldContextID = toLowerCase(name, [, '_']) + '§' + uniqueID;
4062
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
4063
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
4064
+ useEffect(() => {
4065
+ const initialFieldContext = multiple
4066
+ ? defineField({
4067
+ type: 'array',
4068
+ of: value
4069
+ ? value.map(item => ({
4070
+ type: 'string',
4071
+ value: typeof item === 'string' ? item : item.value,
4072
+ }))
4073
+ : defaultValue
4074
+ ? defaultValue.map(item => ({
4075
+ type: 'string',
4076
+ value: typeof item === 'string' ? item : item.value,
4077
+ }))
4078
+ : [],
4079
+ id: fieldContextID,
4080
+ invalid,
4081
+ name,
4082
+ required,
4083
+ })
4084
+ : defineField({
4085
+ type: 'string',
4086
+ id: fieldContextID,
4087
+ invalid,
4088
+ name,
4089
+ required,
4090
+ value: value ? extractStringValue(value) : defaultValue ? extractStringValue(defaultValue) : '',
4091
+ });
3803
4092
  if (isInFieldset) {
3804
- fieldsetContextFunctions.updateField(fieldContextID, { value: processedValue });
4093
+ fieldsetContextFunctions.registerField(initialFieldContext);
4094
+ return () => {
4095
+ fieldsetContextFunctions.removeField(initialFieldContext.id);
4096
+ };
4097
+ }
4098
+ formContextFunctions.registerField(initialFieldContext);
4099
+ return () => {
4100
+ formContextFunctions.removeField(initialFieldContext.id);
4101
+ };
4102
+ }, [isInFieldset]);
4103
+ const handleChange = (newValue) => {
4104
+ if (disabled)
4105
+ return;
4106
+ const isInvalid = newValue === '' || !newValue;
4107
+ if (isInFieldset) {
4108
+ fieldsetContextFunctions.updateField(fieldContextID, {
4109
+ invalid: isInvalid,
4110
+ value: newValue,
4111
+ });
3805
4112
  }
3806
4113
  else {
3807
- formContextFunctions.updateField(fieldContextID, { value: processedValue });
4114
+ formContextFunctions.updateField(fieldContextID, { value: newValue, invalid: isInvalid });
3808
4115
  }
3809
- onBlur?.(e);
4116
+ onChange?.(newValue);
3810
4117
  };
3811
4118
  const restFieldProps = fieldProps
3812
4119
  ? Object.fromEntries(Object.entries(fieldProps).filter(([key]) => key !== 'className'))
@@ -3817,15 +4124,7 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3817
4124
  const restDescriptionProps = descriptionProps
3818
4125
  ? Object.fromEntries(Object.entries(descriptionProps).filter(([key]) => key !== 'className'))
3819
4126
  : {};
3820
- return (jsxs(Field, { ...restFieldProps, className: bag => twMerge('grid gap-1', typeof fieldProps?.className === 'function' ? fieldProps?.className(bag) : fieldProps?.className), disabled: disabled, children: [label && (jsx(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 })), jsxs("div", { children: [jsx(Input$1, { ...props, className: bag => twMerge(
3821
- // Base styles
3822
- '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',
3823
- // Pseudo styles
3824
- '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',
3825
- // user-invalid styles
3826
- '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))]',
3827
- // Custom styles
3828
- 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 && (jsxs(Tooltip, { anchor: 'top-end', arrow: true, portal: true, children: [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: jsx(ExclamationmarkOctagon, { className: 'absolute top-1/2 left-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-70' }) }), jsx(TooltipPanel, { children: errorMessage })] }))] }), description && (jsx(Description, { ...restDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
4127
+ return (jsxs(Field, { ...restFieldProps, className: bag => twMerge('grid gap-1', typeof fieldProps?.className === 'function' ? fieldProps?.className(bag) : fieldProps?.className), disabled: disabled, children: [label && (jsx(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 })), jsxs(Listbox, { ...props, onChange: handleChange, value: fieldContext.value, children: [jsx(ListboxButton, { children: jsx(ListboxSelectedOption, { options: optionList, placeholder: `Select ${multiple ? 'Any' : 'One'}` }) }), jsx(ListboxOptions, {})] }), description && (jsx(Description, { ...restDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
3829
4128
  ? descriptionProps?.className(bag)
3830
4129
  : descriptionProps?.className), children: description }))] }));
3831
4130
  }
@@ -4656,5 +4955,5 @@ function Video({ autoPlay, className, controls = true, poster, ref, srcSet, titl
4656
4955
  }, className: 'bg-neutral-900/50 px-1.5 py-1.5 text-neutral-50 backdrop-blur-xs backdrop-brightness-110', children: jsxs(DropDownItem, { as: 'button', className: 'flex items-center gap-2 rounded-lg px-1.5 py-0.5 text-sm drop-shadow-[0_.125rem_1rem] drop-shadow-neutral-950/75 transition-[scale,background-color,color] duration-300 ease-exponential active:scale-95 data-active:bg-neutral-50/90 data-active:text-neutral-950 pointer-fine:hover:scale-105 pointer-fine:active:scale-95', onClick: captureCurrentFrame, children: [jsx(PhotoBadgeArrowDownFill, { className: 'size-5' }), "Capture Current Frame"] }) })] }), jsxs("button", { className: 'grid size-6 rounded-xs transition-transform duration-300 ease-exponential active:scale-95 pointer-fine:hover:scale-105 pointer-fine:active:scale-95', onClick: toggleFullscreen, title: `${isFullscreen ? 'Exit' : 'Enter'} Fullscreen`, children: [jsx(ArrowUpForwardAndArrowDownBackwardRectangle, { className: 'col-start-0 col-end-1 row-start-0 row-end-1 size-full scale-0 opacity-0 drop-shadow-[0_.125rem_1rem] drop-shadow-neutral-950/75 transition-[scale,opacity] duration-500 ease-exponential group-data-fullscreen/video:scale-80 group-data-fullscreen/video:opacity-100' }), jsx(ArrowDownBackwardAndArrowUpForwardRectangle, { className: '-right-0.5 col-start-0 col-end-1 row-start-0 row-end-1 size-full scale-80 drop-shadow-[0_.125rem_1rem] drop-shadow-neutral-950/75 transition-[scale,opacity] duration-500 ease-exponential group-data-fullscreen/video:scale-0 group-data-fullscreen/video:opacity-0' })] })] }), jsx("div", { "aria-hidden": 'true', className: 'absolute inset-0 -z-10 text-[1px] text-transparent', onClick: togglePlay, onMouseMove: handleMouseMoveControls, onMouseLeave: clearMouseMoveControlsTimeout, children: isPlaying ? 'Pause' : 'Play' })] }))] }));
4657
4956
  }
4658
4957
 
4659
- export { Anchor, Button, Details, DetailsBody, DetailsSummary, DropDown, DropDownButton, DropDownItem, DropDownItems, DropDownSection, DropDownSeparator, Fieldset, Form, Ghost, Heading, IFrame, Input, Link, Modal, ModalClose, ModalDialog, ModalTitle, ModalTrigger, SubmitButton, Textarea, Time, Tooltip, TooltipPanel, TooltipTrigger, Video, createButton, createLink };
4958
+ export { Anchor, Button, Details, DetailsBody, DetailsSummary, DropDown, DropDownButton, DropDownItem, DropDownItems, DropDownSection, DropDownSeparator, Fieldset, Form, Ghost, Heading, IFrame, Input, Link, Modal, ModalClose, ModalDialog, ModalTitle, ModalTrigger, Select, SubmitButton, Textarea, Time, Tooltip, TooltipPanel, TooltipTrigger, Video, createButton, createLink };
4660
4959
  //# sourceMappingURL=index.esm.js.map