mado-ui 0.3.1 → 0.4.0

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 (148) hide show
  1. package/css/index.css +69 -14
  2. package/dist/components/button.d.ts +74 -29
  3. package/dist/components/details.d.ts +5 -7
  4. package/dist/components/drop-down.d.ts +4 -3
  5. package/dist/components/form/fieldset.d.ts +35 -7
  6. package/dist/components/form/index.d.ts +5 -6
  7. package/dist/components/form/input/date/index.d.ts +36 -0
  8. package/dist/components/form/input/index.d.ts +78 -12
  9. package/dist/components/form/submit-button.d.ts +3 -4
  10. package/dist/components/form/textarea.d.ts +1 -1
  11. package/dist/components/ghost.d.ts +1 -1
  12. package/dist/components/heading.d.ts +1 -1
  13. package/dist/components/iframe.d.ts +15 -0
  14. package/dist/components/index.d.ts +10 -11
  15. package/dist/components/link.d.ts +62 -16
  16. package/dist/components/modal.d.ts +1 -1
  17. package/dist/components/time.d.ts +1 -1
  18. package/dist/components/tooltip.d.ts +1 -1
  19. package/dist/components.esm.js +612 -138
  20. package/dist/components.esm.js.map +1 -1
  21. package/dist/components.js +613 -135
  22. package/dist/components.js.map +1 -1
  23. package/dist/hooks/index.d.ts +4 -3
  24. package/dist/hooks/use-fieldset-context.d.ts +22 -0
  25. package/dist/hooks/use-form-context.d.ts +18 -10
  26. package/dist/hooks.esm.js +99 -5
  27. package/dist/hooks.esm.js.map +1 -1
  28. package/dist/hooks.js +100 -3
  29. package/dist/hooks.js.map +1 -1
  30. package/dist/icons/3-people.d.ts +2 -2
  31. package/dist/icons/3-rectangles-desktop-fill.d.ts +2 -2
  32. package/dist/icons/3-rectangles-desktop.d.ts +2 -2
  33. package/dist/icons/airplane.d.ts +2 -2
  34. package/dist/icons/arrow-triangle-2-circlepath-circle-fill.d.ts +2 -2
  35. package/dist/icons/arrow-triangle-2-circlepath-circle.d.ts +2 -2
  36. package/dist/icons/bag-fill.d.ts +2 -2
  37. package/dist/icons/banknote.d.ts +2 -2
  38. package/dist/icons/bell-fill.d.ts +2 -2
  39. package/dist/icons/bolt-car.d.ts +2 -2
  40. package/dist/icons/bolt-fill.d.ts +2 -2
  41. package/dist/icons/bolt-ring-closed.d.ts +2 -2
  42. package/dist/icons/bolt-trianglebadge-exclamationmark.d.ts +2 -2
  43. package/dist/icons/book-fill.d.ts +2 -2
  44. package/dist/icons/bookmark-fill.d.ts +2 -2
  45. package/dist/icons/briefcase-fill.d.ts +2 -2
  46. package/dist/icons/bubble-left-fill.d.ts +2 -2
  47. package/dist/icons/building-2-fill.d.ts +2 -2
  48. package/dist/icons/calendar.d.ts +2 -2
  49. package/dist/icons/camera-fill.d.ts +2 -2
  50. package/dist/icons/car-fill.d.ts +2 -2
  51. package/dist/icons/cart-fill.d.ts +2 -2
  52. package/dist/icons/chart-bar-doc-horizontal.d.ts +2 -2
  53. package/dist/icons/checkmark-seal.d.ts +2 -2
  54. package/dist/icons/checkmark.d.ts +2 -2
  55. package/dist/icons/chevron-down.d.ts +2 -2
  56. package/dist/icons/chevron-left-forwardslash-chevron-right.d.ts +2 -2
  57. package/dist/icons/chevron-left.d.ts +2 -2
  58. package/dist/icons/chevron-right.d.ts +2 -2
  59. package/dist/icons/chevron-up-chevron-down.d.ts +2 -2
  60. package/dist/icons/circle-fill.d.ts +2 -2
  61. package/dist/icons/clock-badge-checkmark.d.ts +2 -2
  62. package/dist/icons/clock-fill.d.ts +2 -2
  63. package/dist/icons/cloud-fill.d.ts +2 -2
  64. package/dist/icons/cube-fill.d.ts +2 -2
  65. package/dist/icons/curve-point-left.d.ts +2 -2
  66. package/dist/icons/dial-high.d.ts +2 -2
  67. package/dist/icons/doc-fill.d.ts +2 -2
  68. package/dist/icons/doc-on-clipboard.d.ts +2 -2
  69. package/dist/icons/doc-on-doc-fill.d.ts +2 -2
  70. package/dist/icons/doc-on-doc.d.ts +2 -2
  71. package/dist/icons/doc-text-magnifyingglass.d.ts +2 -2
  72. package/dist/icons/dollar-sign.d.ts +2 -2
  73. package/dist/icons/ellipsis-circle-fill.d.ts +2 -2
  74. package/dist/icons/ellipsis-circle.d.ts +2 -2
  75. package/dist/icons/envelope-fill.d.ts +2 -2
  76. package/dist/icons/envelope.d.ts +2 -2
  77. package/dist/icons/exclamationmark-octagon.d.ts +2 -2
  78. package/dist/icons/eye.d.ts +2 -2
  79. package/dist/icons/figure-water-fitness.d.ts +2 -2
  80. package/dist/icons/flag-fill.d.ts +2 -2
  81. package/dist/icons/flame-fill.d.ts +2 -2
  82. package/dist/icons/folder-fill.d.ts +2 -2
  83. package/dist/icons/folder.d.ts +2 -2
  84. package/dist/icons/gearshape-fill.d.ts +2 -2
  85. package/dist/icons/gearshape.d.ts +2 -2
  86. package/dist/icons/gift-fill.d.ts +2 -2
  87. package/dist/icons/globe-americas-fill.d.ts +2 -2
  88. package/dist/icons/hare-fill.d.ts +2 -2
  89. package/dist/icons/house-deskclock.d.ts +2 -2
  90. package/dist/icons/house-fill.d.ts +2 -2
  91. package/dist/icons/house.d.ts +2 -2
  92. package/dist/icons/iphone-house.d.ts +2 -2
  93. package/dist/icons/light-ribbon.d.ts +2 -2
  94. package/dist/icons/lightbulb-fill.d.ts +2 -2
  95. package/dist/icons/lightbulb-led.d.ts +2 -2
  96. package/dist/icons/list-bullet-clipboard-fill.d.ts +2 -2
  97. package/dist/icons/magnifyingglass.d.ts +2 -2
  98. package/dist/icons/map-pin-ellipse.d.ts +2 -2
  99. package/dist/icons/minus-plus-batterblock.d.ts +2 -2
  100. package/dist/icons/network-shield.d.ts +2 -2
  101. package/dist/icons/network.d.ts +2 -2
  102. package/dist/icons/newspaper-fill.d.ts +2 -2
  103. package/dist/icons/number.d.ts +2 -2
  104. package/dist/icons/paperplane-fill.d.ts +2 -2
  105. package/dist/icons/person-crop-square.d.ts +2 -2
  106. package/dist/icons/person-fill-questionmark.d.ts +2 -2
  107. package/dist/icons/person-fill.d.ts +2 -2
  108. package/dist/icons/person.d.ts +2 -2
  109. package/dist/icons/phone-arrow-up-right.d.ts +2 -2
  110. package/dist/icons/phone-fill.d.ts +2 -2
  111. package/dist/icons/phone.d.ts +2 -2
  112. package/dist/icons/play-rectangle-fill.d.ts +2 -2
  113. package/dist/icons/plus.d.ts +2 -2
  114. package/dist/icons/qrcode.d.ts +2 -2
  115. package/dist/icons/rectangle-portrait-and-arrow-left-fill.d.ts +2 -2
  116. package/dist/icons/rectangle-portrait-and-arrow-left.d.ts +2 -2
  117. package/dist/icons/sensor.d.ts +2 -2
  118. package/dist/icons/signature.d.ts +2 -2
  119. package/dist/icons/solar-panel.d.ts +2 -2
  120. package/dist/icons/square-and-arrow-down-fill.d.ts +2 -2
  121. package/dist/icons/square-and-arrow-down.d.ts +2 -2
  122. package/dist/icons/square-and-arrow-up-fill.d.ts +2 -2
  123. package/dist/icons/square-and-arrow-up.d.ts +2 -2
  124. package/dist/icons/square-and-pencil-fill.d.ts +2 -2
  125. package/dist/icons/square-and-pencil.d.ts +2 -2
  126. package/dist/icons/text-bubble.d.ts +2 -2
  127. package/dist/icons/trash-fill.d.ts +2 -2
  128. package/dist/icons/trash.d.ts +2 -2
  129. package/dist/icons/tree.d.ts +2 -2
  130. package/dist/icons/umbrella-fill.d.ts +2 -2
  131. package/dist/icons/xmark.d.ts +2 -2
  132. package/dist/icons.esm.js.map +1 -1
  133. package/dist/icons.js.map +1 -1
  134. package/dist/index.esm.js +612 -138
  135. package/dist/index.esm.js.map +1 -1
  136. package/dist/index.js +613 -135
  137. package/dist/index.js.map +1 -1
  138. package/dist/types/index.d.ts +1 -2
  139. package/dist/types/utils.d.ts +20 -0
  140. package/dist/utils/get-date.d.ts +17 -0
  141. package/dist/utils/index.d.ts +8 -9
  142. package/dist/utils/string-manipulation.d.ts +38 -3
  143. package/dist/utils/tw-sort.d.ts +1 -1
  144. package/dist/utils.esm.js +112 -21
  145. package/dist/utils.esm.js.map +1 -1
  146. package/dist/utils.js +113 -20
  147. package/dist/utils.js.map +1 -1
  148. package/package.json +4 -4
package/dist/index.esm.js CHANGED
@@ -1,8 +1,8 @@
1
1
  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
- import { Children, isValidElement, Fragment, createContext, useContext, useSyncExternalStore, useRef, Suspense, useLayoutEffect, useState, useEffect, useCallback, cloneElement, useId } from 'react';
5
- import { Button as Button$1, Disclosure, DisclosurePanel, DisclosureButton, Menu, MenuButton, MenuItem, MenuItems, MenuSection, MenuHeading, MenuSeparator, Fieldset as Fieldset$1, Legend, Field, Label, Input as Input$1, Description, Textarea as Textarea$1, Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react';
4
+ import { Children, isValidElement, Fragment, createContext, useContext, useSyncExternalStore, useRef, useCallback, useEffect, Suspense, useId, useLayoutEffect, cloneElement, useState } 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';
6
6
  import * as ReactDOM from 'react-dom';
7
7
  import { createPortal } from 'react-dom';
8
8
 
@@ -188,6 +188,9 @@ function getMonth(month = d) {
188
188
  formattedMonth = `0${formattedMonth}`;
189
189
  return formattedMonth;
190
190
  }
191
+ function getMonthIndexFromName(name) {
192
+ return monthNamesList.findIndex(monthName => monthName === name);
193
+ }
191
194
  /**
192
195
  * ### Get Month Name
193
196
  * - Returns the name of the specified month
@@ -213,6 +216,30 @@ function getSeconds(seconds = d) {
213
216
  formattedSeconds = `0${formattedSeconds}`;
214
217
  return formattedSeconds;
215
218
  }
219
+ /**
220
+ * ### Get User Readable Date
221
+ * - Returns a string of the current date in a user-friendly way
222
+ * - Includes `'Yesterday'`, '`Today'`, and `'Tomorrow'`
223
+ * @param date (default: `new Date()`)
224
+ * @returns {'Today'|'Tomorrow'|'Yesterday'|string} `'weekday, month day, year'` | `'Yesterday'` | `'Today'` | `'Tomorrow'`
225
+ */
226
+ function getUserReadableDate(date = d) {
227
+ const dateTime = date.getTime();
228
+ const today = new Date(), isToday = dateTime === today.getTime();
229
+ if (isToday)
230
+ return 'Today';
231
+ const yesterday = new Date(today.getDate() - 1), isYesterday = dateTime === yesterday.getTime();
232
+ if (isYesterday)
233
+ return 'Yesterday';
234
+ const tomorrow = new Date(today.getDate() + 1), isTomorrow = dateTime === tomorrow.getTime();
235
+ if (isTomorrow)
236
+ return 'Tomorrow';
237
+ const thisYear = today.getFullYear(), isSameYear = date.getFullYear() === thisYear;
238
+ const fullDateString = toFullDateString(date, {
239
+ year: !isSameYear,
240
+ });
241
+ return fullDateString;
242
+ }
216
243
  /**
217
244
  * ### Get Weekday Name
218
245
  * - Returns the weekday name of the specified day
@@ -225,6 +252,45 @@ function getWeekdayName(weekday = d) {
225
252
  // Return the name of the day of the week
226
253
  return weekdayNamesList[weekday.getDay()];
227
254
  }
255
+ /**
256
+ * ### To Full Date String
257
+ * - Returns a formatted string to display the date
258
+ * @param {Date} date (default: `new Date()`)
259
+ * @param {ToFullDateStringOptionsProps} options Change how to display the weekday, month name, day of the month, and year.
260
+ * @returns {string} '`weekday`, `month` `day`, `year`'
261
+ */
262
+ function toFullDateString(date = d, options) {
263
+ let weekdayName = getWeekdayName(date), monthName = getMonthName(date), dayOfMonth = date.getDate(), year = date.getFullYear().toString();
264
+ if (options) {
265
+ const includesDay = options.day !== false, includesMonth = options.month !== false, includesYear = options.year !== false;
266
+ {
267
+ weekdayName = weekdayName.slice(0, 3);
268
+ if (includesMonth || includesDay || includesYear)
269
+ weekdayName += ', ';
270
+ }
271
+ if (includesMonth) {
272
+ if (options.month === 'code')
273
+ monthName = monthName.slice(0, 3);
274
+ if (includesDay)
275
+ monthName += ' ';
276
+ }
277
+ else {
278
+ monthName = '';
279
+ }
280
+ if (!includesDay)
281
+ dayOfMonth = '';
282
+ if (includesYear) {
283
+ if (options.year === 'code')
284
+ year = year.slice(-2);
285
+ if (includesMonth || includesDay)
286
+ year = ', ' + year;
287
+ }
288
+ else {
289
+ year = '';
290
+ }
291
+ }
292
+ return weekdayName + monthName + dayOfMonth + year;
293
+ }
228
294
 
229
295
  function findComponentByType(children, componentType) {
230
296
  const childrenArray = Children.toArray(children);
@@ -273,25 +339,97 @@ function isPhoneNumber(tel) {
273
339
  /**
274
340
  * # Format Phone Number
275
341
  * Converts any string containing at least 10 numbers to a formatted phone number
276
- * @param {string} string
277
- * @returns {string} string formatted (000) 000-0000
342
+ * @param {string} phoneNumber
343
+ * @param options
344
+ * @property {string} `countryCode`
345
+ * @property {'continuous' | 'dot' | 'hyphenated' | 'none' | 'space' | 'standard'} `format`
346
+ *
347
+ * Input: a555b555c5555d
348
+ *
349
+ * @example
350
+ * format: 'continuous'
351
+ * countryCode: '1'
352
+ * returns: +1 5555555555
353
+ *
354
+ * @example
355
+ * format: 'dot'
356
+ * returns: 555.555.5555
357
+ *
358
+ * @example
359
+ * format: 'hyphenated'
360
+ * returns: 555-555-5555
361
+ *
362
+ * @example
363
+ * format: 'none'
364
+ * countryCode: '1'
365
+ * returns: +1 a555b555c5555d
366
+ *
367
+ * @example
368
+ * format: 'space'
369
+ * returns: 555 555 5555
370
+ *
371
+ * @example
372
+ * format: 'standard' (default)
373
+ * returns: (555) 555-5555
374
+ *
375
+ * @returns {string} string formatted
278
376
  */
279
- function formatPhoneNumber(string, countryCode) {
280
- return (`${`+${countryCode} ` }` +
281
- string
282
- .replace(/\D/g, '')
283
- .slice(-10)
284
- .split('')
285
- .map((char, index) => {
286
- if (index === 0)
287
- return `(${char}`;
288
- if (index === 2)
289
- return `${char}) `;
290
- if (index === 5)
291
- return `${char}-`;
292
- return char;
293
- })
294
- .join(''));
377
+ function formatPhoneNumber(phoneNumber, options) {
378
+ const format = options?.format || 'standard';
379
+ if (format !== 'none')
380
+ phoneNumber = phoneNumber.replace(/\D/g, '').slice(-10);
381
+ switch (format) {
382
+ case 'dot':
383
+ phoneNumber = phoneNumber
384
+ .split('')
385
+ .map((char, index) => {
386
+ if (index === 2)
387
+ return `${char}.`;
388
+ if (index === 5)
389
+ return `${char}.`;
390
+ return char;
391
+ })
392
+ .join('');
393
+ break;
394
+ case 'hyphenated':
395
+ phoneNumber = phoneNumber
396
+ .split('')
397
+ .map((char, index) => {
398
+ if (index === 2)
399
+ return `${char}-`;
400
+ if (index === 5)
401
+ return `${char}-`;
402
+ return char;
403
+ })
404
+ .join('');
405
+ break;
406
+ case 'space':
407
+ phoneNumber = phoneNumber
408
+ .split('')
409
+ .map((char, index) => {
410
+ if (index === 2)
411
+ return `${char} `;
412
+ if (index === 5)
413
+ return `${char} `;
414
+ return char;
415
+ })
416
+ .join('');
417
+ break;
418
+ case 'standard':
419
+ phoneNumber = phoneNumber
420
+ .split('')
421
+ .map((char, index) => {
422
+ if (index === 0)
423
+ return `(${char}`;
424
+ if (index === 2)
425
+ return `${char}) `;
426
+ if (index === 5)
427
+ return `${char}-`;
428
+ return char;
429
+ })
430
+ .join('');
431
+ }
432
+ return `${options?.countryCode ? `+${options?.countryCode} ` : ''}` + phoneNumber;
295
433
  }
296
434
  /**
297
435
  * # To Lower Case
@@ -346,7 +484,7 @@ const lineLiftClasses = twJoin([
346
484
  scaleYClasses,
347
485
  'after:origin-bottom after:translate-y-1 after:scale-x-75 active:after:translate-y-0 active:after:scale-x-100 pointer-fine:hover:after:translate-y-0 pointer-fine:hover:after:scale-x-100',
348
486
  ]);
349
- const fillClasses = twJoin(baseClasses, 'whitespace-nowrap transition-[transform_color] after:top-1/2 after:h-[calc(100%+0.05rem)] after:w-[calc(100%+0.25rem)] after:-translate-y-1/2 after:rounded after:ease-exponential active:text-zinc-50 pointer-fine:hover:text-zinc-50');
487
+ const fillClasses = twJoin(baseClasses, 'whitespace-nowrap transition-[transform,color] after:top-1/2 after:h-[calc(100%+0.05rem)] after:w-[calc(100%+0.25rem)] after:-translate-y-1/2 after:rounded after:ease-exponential active:text-zinc-50 pointer-fine:hover:text-zinc-50');
350
488
  // Define theme-specific fill color transition classes
351
489
  const getFillColorTransitionClasses = (theme = 'blue', customTheme) => {
352
490
  let fillColorTransitionClasses = twJoin(fillClasses, 'transition-transform after:bg-(--theme-color)');
@@ -399,7 +537,7 @@ const getFillColorTransitionClasses = (theme = 'blue', customTheme) => {
399
537
  };
400
538
  // Define theme-specific fill center classes
401
539
  const getFillCenterClasses = (theme = 'blue', customTheme) => {
402
- let fillCenterColorClasses = twJoin(fillClasses, 'after:scale-x-50 after:scale-y-[0.25] after:bg-(--theme-color)/0 after:transition-[transform_background-color] active:after:scale-x-100 active:after:scale-y-100 active:after:bg-(--theme-color) pointer-fine:hover:after:scale-x-100 pointer-fine:hover:after:scale-y-100 pointer-fine:hover:after:bg-(--theme-color)');
540
+ let fillCenterColorClasses = twJoin(fillClasses, 'after:scale-x-50 after:scale-y-[0.25] after:bg-(--theme-color)/0 after:transition-[transform,background-color] active:after:scale-x-100 active:after:scale-y-100 active:after:bg-(--theme-color) pointer-fine:hover:after:scale-x-100 pointer-fine:hover:after:scale-y-100 pointer-fine:hover:after:bg-(--theme-color)');
403
541
  switch (theme) {
404
542
  case 'blue':
405
543
  fillCenterColorClasses = twJoin(fillCenterColorClasses, 'after:[--theme-color:var(--color-ui-blue)]');
@@ -458,7 +596,7 @@ const multilineLineCenterClasses = twJoin([multilineXClasses, 'bg-[position:50%_
458
596
  const multilineLineLiftClasses = twJoin(multilineLineClasses, 'bg-[size:auto_0px] focus-visible:bg-[size:auto_2px] active:bg-[size:auto_2px] pointer-fine:hover:bg-[size:auto_2px]');
459
597
  const multilineFillBaseClasses = twJoin(multilineBaseClasses, 'rounded px-0.5 py-0.75 focus-visible:text-zinc-50 active:text-zinc-50 pointer-fine:hover:text-zinc-50');
460
598
  const getMultilineFillColorClasses = (theme = 'blue', customTheme) => {
461
- let multilineFillColorClasses = twJoin(multilineFillBaseClasses, 'from-(--theme-color) to-(--theme-color) transition-[background-size_color]');
599
+ let multilineFillColorClasses = twJoin(multilineFillBaseClasses, 'from-(--theme-color) to-(--theme-color) transition-[background-size,color]');
462
600
  switch (theme) {
463
601
  case 'blue':
464
602
  multilineFillColorClasses = twJoin(multilineFillColorClasses, '[--theme-color:var(--color-ui-blue)]');
@@ -507,7 +645,7 @@ const getMultilineFillColorClasses = (theme = 'blue', customTheme) => {
507
645
  return multilineFillColorClasses;
508
646
  };
509
647
  const getMultilineFillClasses = (theme = 'blue', customTheme) => {
510
- let multilineFillColorClasses = twJoin(multilineFillBaseClasses, 'from-(--theme-color)/0 to-(--theme-color)/0 bg-[size:50%_0px] bg-[position:50%_50%] transition-[background-size_background-image_color] focus-visible:from-(--theme-color) focus-visible:to-(--theme-color) focus-visible:bg-[size:100%_100%] active:from-(--theme-color) active:to-(--theme-color) active:bg-[size:100%_100%] contrast-more:from-(--theme-color)/0 pointer-fine:hover:from-(--theme-color) pointer-fine:hover:to-(--theme-color) pointer-fine:hover:bg-[size:100%_100%]');
648
+ let multilineFillColorClasses = twJoin(multilineFillBaseClasses, 'from-(--theme-color)/0 to-(--theme-color)/0 bg-[size:50%_0px] bg-[position:50%_50%] transition-[background-size,background-image,color] focus-visible:from-(--theme-color) focus-visible:to-(--theme-color) focus-visible:bg-[size:100%_100%] active:from-(--theme-color) active:to-(--theme-color) active:bg-[size:100%_100%] contrast-more:from-(--theme-color)/0 pointer-fine:hover:from-(--theme-color) pointer-fine:hover:to-(--theme-color) pointer-fine:hover:bg-[size:100%_100%]');
511
649
  switch (theme) {
512
650
  case 'blue':
513
651
  multilineFillColorClasses = twJoin(multilineFillColorClasses, '[--theme-color:var(--color-ui-blue)]');
@@ -647,6 +785,71 @@ function Link({ as, className, customTheme, theme = 'blue', type, ...props }) {
647
785
  const LinkElement = as || Anchor;
648
786
  return jsx(LinkElement, { ...props, className: twMerge(linkClasses, className) });
649
787
  }
788
+ /**
789
+ * # createLink
790
+ * Creates an extended Link component with additional theme options.
791
+ *
792
+ * @param config - Configuration object defining new themes and defaults
793
+ * @returns A new Link component with extended theme support
794
+ *
795
+ * @example
796
+ * ```tsx
797
+ * const MyLink = createLink({
798
+ * as: NextLink,
799
+ * className: 'font-bold',
800
+ * type: 'fill',
801
+ * theme: {
802
+ * primary: {
803
+ * customTheme: {
804
+ * fill: 'after:[--theme-color:var(--color-primary-500)]',
805
+ * multilineFill: '[--theme-color:var(--color-primary-500)]'
806
+ * },
807
+ * className: 'text-white'
808
+ * }
809
+ * }
810
+ * })
811
+ * ```
812
+ */
813
+ function createLink(config) {
814
+ return function ExtendedLink({ theme, className, customTheme, type, as, ...props }) {
815
+ const finalType = type !== undefined ? type : config.type, finalTheme = theme !== undefined ? theme : config.defaultTheme;
816
+ const configClassName = config.className;
817
+ const shouldOverrideElement = !Boolean(as) && Boolean(config.as);
818
+ const linkProps = {
819
+ ...props,
820
+ className: undefined,
821
+ customTheme: undefined,
822
+ type: finalType,
823
+ };
824
+ if (shouldOverrideElement) {
825
+ linkProps.as = config.as;
826
+ }
827
+ else if (as) {
828
+ linkProps.as = as;
829
+ }
830
+ if (finalTheme && typeof finalTheme === 'string' && config.theme && finalTheme in config.theme) {
831
+ const extendedTheme = config.theme[finalTheme];
832
+ if (customTheme)
833
+ return (jsx(Link, { ...linkProps, theme: 'custom', customTheme: customTheme, className: twMerge(configClassName, extendedTheme.className, className) }));
834
+ let resolvedCustomTheme;
835
+ if (extendedTheme.customTheme.themeColor) {
836
+ const isMultilineType = finalType ? finalType.includes('multiline') : false;
837
+ resolvedCustomTheme = {
838
+ themeColor: isMultilineType
839
+ ? extendedTheme.customTheme.themeColor.multilineFill
840
+ : extendedTheme.customTheme.themeColor.fill,
841
+ };
842
+ }
843
+ else {
844
+ resolvedCustomTheme = {
845
+ classes: extendedTheme.customTheme.classes,
846
+ };
847
+ }
848
+ return (jsx(Link, { ...linkProps, theme: 'custom', customTheme: resolvedCustomTheme, className: twMerge(configClassName, extendedTheme.className, className) }));
849
+ }
850
+ return (jsx(Link, { ...linkProps, theme: finalTheme, className: twMerge(configClassName, className), customTheme: customTheme }));
851
+ };
852
+ }
650
853
 
651
854
  /**
652
855
  * # Button
@@ -732,9 +935,76 @@ function Button({ className, customTheme, gradient = false, padding = 'md', roun
732
935
  themeClasses,
733
936
  className,
734
937
  ]);
735
- const ButtonElement = 'as' in props ? props.as : props.href ? Anchor : Button$1;
736
- const { as, ...restProps } = 'as' in props ? props : { ...props, as: undefined };
737
- return jsx(ButtonElement, { ...restProps, className: buttonClasses });
938
+ if ('href' in props && !props.as && props.href)
939
+ return jsx(Button$1, { ...props, as: Anchor, className: buttonClasses });
940
+ return jsx(Button$1, { ...props, className: buttonClasses });
941
+ }
942
+ /**
943
+ * # createButton
944
+ * Creates an extended Button component with additional theme options.
945
+ *
946
+ * @param extendedThemes - Configuration object defining new themes
947
+ * @returns A new Button component with extended theme support
948
+ *
949
+ * @example
950
+ * ```tsx
951
+ * const MyButton = createButton({
952
+ * as: {
953
+ * default: 'div',
954
+ * link: NextLink
955
+ * },
956
+ * className: 'min-w-64',
957
+ * padding: 'sm',
958
+ * rounded: 'full',
959
+ * theme: {
960
+ * primary: {
961
+ * customTheme: { themeColor: '[--theme-color:var(--color-primary-500)]' },
962
+ * className: 'text-white'
963
+ * }
964
+ * }
965
+ * })
966
+ * ```
967
+ */
968
+ function createButton(config) {
969
+ return function ExtendedButton({ theme, className, customTheme, gradient, padding, rounded, as, ...props }) {
970
+ const finalGradient = gradient !== undefined ? gradient : config.gradient, finalPadding = padding !== undefined ? padding : config.padding, finalRounded = rounded !== undefined ? rounded : config.rounded, finalTheme = theme !== undefined ? theme : config.defaultTheme;
971
+ const configClassName = config.className;
972
+ const shouldOverrideElement = !Boolean(as) && Boolean(config.as);
973
+ const getOverrideElement = () => {
974
+ if (!config.as)
975
+ return undefined;
976
+ if (typeof config.as === 'function' || typeof config.as === 'string')
977
+ return config.as;
978
+ const hasHref = 'href' in props && props.href;
979
+ if (hasHref && config.as.link) {
980
+ return config.as.link;
981
+ }
982
+ else if (!hasHref && config.as.default) {
983
+ return config.as.default;
984
+ }
985
+ return undefined;
986
+ };
987
+ const buttonProps = {
988
+ ...props,
989
+ className: undefined,
990
+ customTheme: undefined,
991
+ gradient: finalGradient,
992
+ padding: finalPadding,
993
+ rounded: finalRounded,
994
+ };
995
+ if (shouldOverrideElement) {
996
+ const overrideElement = getOverrideElement();
997
+ if (overrideElement)
998
+ buttonProps.as = overrideElement;
999
+ }
1000
+ else if (as)
1001
+ buttonProps.as = as;
1002
+ if (finalTheme && typeof finalTheme === 'string' && config.theme && finalTheme in config.theme) {
1003
+ const extendedTheme = config.theme[finalTheme];
1004
+ return (jsx(Button, { ...buttonProps, theme: 'custom', customTheme: customTheme || extendedTheme.customTheme, className: twMerge(configClassName, extendedTheme.className, className) }));
1005
+ }
1006
+ return (jsx(Button, { ...buttonProps, theme: finalTheme, className: twMerge(configClassName, className), customTheme: customTheme }));
1007
+ };
738
1008
  }
739
1009
 
740
1010
  function ChevronDown(props) {
@@ -749,10 +1019,8 @@ function xmark(props) {
749
1019
  return (jsx("svg", { viewBox: '0 0 64 64', ...props, children: jsx("path", { d: 'M1,63c0.7,0.7,1.6,1,2.6,1s1.9-0.3,2.6-1L32,37.1L57.8,63c0.7,0.7,1.5,1,2.5,1c1,0,1.9-0.3,2.6-1c0.7-0.7,1-1.6,1-2.6 c0-1-0.3-1.8-1-2.5L37.1,32L63,6.2c0.7-0.7,1-1.6,1-2.6S63.7,1.7,63,1c-0.7-0.7-1.6-1-2.6-1c-1,0-1.8,0.3-2.5,1L32,26.9L6.2,1 C5.5,0.3,4.6,0,3.6,0C2.6,0,1.7,0.3,1,1C0.3,1.7,0,2.6,0,3.6c0,1,0.3,1.9,1,2.6L26.9,32L1,57.8c-0.7,0.7-1,1.5-1,2.6 C0,61.4,0.3,62.3,1,63z' }) }));
750
1020
  }
751
1021
 
752
- function DetailsSummary({ arrow = true, as, children, className, ...props }) {
753
- return (
754
- // @ts-expect-error Button has some extra props
755
- jsx(DisclosureButton, { ...props, as: as || (Button), className: twMerge('w-full', className, Boolean(arrow) && 'grid grid-cols-[1fr_1rem] gap-2'), role: 'summary', children: bag => (jsxs(Fragment$1, { children: [typeof children === 'function' ? children(bag) : children, arrow &&
1022
+ function DetailsSummary({ arrow = true, children, className, ...props }) {
1023
+ return (jsx(DisclosureButton, { ...props, as: (Button), className: twMerge('w-full', className, Boolean(arrow) && 'grid grid-cols-[1fr_1rem] gap-2'), role: 'summary', children: bag => (jsxs(Fragment$1, { children: [typeof children === 'function' ? children(bag) : children, arrow &&
756
1024
  (typeof arrow === 'boolean' ? (jsx(ChevronDown, { className: 'absolute top-1/2 right-3 block w-4 -translate-y-1/2' })) : (arrow))] })) }));
757
1025
  }
758
1026
  function DetailsBody({ children, className, ...props }) {
@@ -769,7 +1037,7 @@ function DropDownButton({ arrow = true, as, children, className, ...props }) {
769
1037
  function DropDownItem({ as, ...props }) {
770
1038
  return jsx(MenuItem, { as: as || 'div', ...props });
771
1039
  }
772
- function DropDownItems({ anchor, children, className, style, ...props }) {
1040
+ function DropDownItems({ anchor, children, className, containerClassName, style, ...props }) {
773
1041
  const getAnchorProps = () => {
774
1042
  let initialAnchor = { gap: '1rem', padding: '1rem', to: 'bottom start' };
775
1043
  if (anchor) {
@@ -781,11 +1049,11 @@ function DropDownItems({ anchor, children, className, style, ...props }) {
781
1049
  return initialAnchor;
782
1050
  };
783
1051
  const anchorProps = getAnchorProps();
784
- return (jsx(MenuItems, { ...props, anchor: anchorProps, className: 'grid grid-rows-1fr rounded-xl shadow-xl transition-rows duration-500 ease-exponential data-closed:grid-rows-0fr', transition: true, style: { ...style, minWidth: 'var(--button-width)' }, children: bag => (jsx("div", { className: 'overflow-y-hidden', children: jsx("div", { className: twMerge('bg-neutral-50/20 px-6 py-5 backdrop-blur-md backdrop-brightness-150', className), children: typeof children === 'function' ? children(bag) : children }) })) }));
1052
+ return (jsx(MenuItems, { ...props, anchor: anchorProps, className: twMerge('grid grid-rows-1fr rounded-xl shadow-xl transition-rows duration-500 ease-exponential data-closed:grid-rows-0fr', containerClassName), transition: true, style: { ...style, minWidth: 'var(--button-width)' }, children: bag => (jsx("div", { className: 'overflow-y-scroll', children: jsx("div", { className: twMerge('rounded-xl bg-neutral-50/20 px-6 py-5 backdrop-blur-md backdrop-brightness-150', className), children: typeof children === 'function' ? children(bag) : children }) })) }));
785
1053
  }
786
1054
  function DropDownSection({ children, label, labelProps, separatorAbove, separatorBelow, ...props }) {
787
1055
  const { labelClassName, ...restLabelProps } = { labelClassName: labelProps?.className || '', ...labelProps };
788
- return (jsx(MenuSection, { ...props, children: sectionBag => (jsxs(Fragment$1, { children: [separatorAbove && jsx(DropDownSeparator, {}), jsx(MenuHeading, { ...restLabelProps, className: headingBag => twMerge('text-[size:larger] font-bold', typeof labelClassName === 'function' ? labelClassName(headingBag) : labelClassName), children: label }), typeof children === 'function' ? children(sectionBag) : children, separatorBelow && jsx(DropDownSeparator, {})] })) }));
1056
+ return (jsx(MenuSection, { ...props, children: sectionBag => (jsxs(Fragment$1, { children: [separatorAbove && jsx(DropDownSeparator, {}), label && (jsx(MenuHeading, { ...restLabelProps, className: headingBag => twMerge('text-[size:larger] font-bold text-current/80', typeof labelClassName === 'function' ? labelClassName(headingBag) : labelClassName), children: label })), typeof children === 'function' ? children(sectionBag) : children, separatorBelow && jsx(DropDownSeparator, {})] })) }));
789
1057
  }
790
1058
  function DropDownSeparator({ className, ...props }) {
791
1059
  return (jsx(MenuSeparator, { ...props, className: bag => twMerge('my-4 block h-px rounded-full bg-neutral-950/20', typeof className === 'function' ? className(bag) : className) }));
@@ -845,12 +1113,106 @@ function createFastContext(defaultInitialState) {
845
1113
  function defineField(fieldDefinition) {
846
1114
  return fieldDefinition;
847
1115
  }
848
- const { Provider: Provider$1, useStore: useStore$1 } = createFastContext([]);
1116
+ function isStringField(field) {
1117
+ return Boolean(field.type !== 'object' && field.type !== 'array');
1118
+ }
1119
+ const { Provider: Provider$2, useStore: useStore$2 } = createFastContext([]);
849
1120
  function FormContextProvider({ children }) {
850
- return jsx(Provider$1, { children: children });
1121
+ return jsx(Provider$2, { children: children });
851
1122
  }
852
1123
  function useFormContext() {
853
- return useStore$1(store => store);
1124
+ const [formContext, setFormContext] = useStore$2(store => store);
1125
+ const registerField = useCallback((field) => {
1126
+ setFormContext?.(prevContext => {
1127
+ const otherFields = (prevContext || []).filter(otherField => otherField.id !== field.id);
1128
+ return [...otherFields, field];
1129
+ });
1130
+ }, []);
1131
+ const removeField = useCallback((fieldID) => {
1132
+ setFormContext?.(prevContext => (prevContext || []).filter(field => field.id !== fieldID));
1133
+ }, []);
1134
+ const updateField = useCallback((fieldID, updates) => {
1135
+ setFormContext?.(prevContext => {
1136
+ const field = prevContext.find(({ id }) => id === fieldID);
1137
+ if (!field)
1138
+ throw new Error(`Field with id "${fieldID}" not found in form context.`);
1139
+ const otherFields = prevContext.filter(({ id }) => id !== fieldID);
1140
+ const updatedField = { ...field, ...updates };
1141
+ return [...otherFields, updatedField];
1142
+ });
1143
+ }, []);
1144
+ return [formContext, { registerField, removeField, updateField }];
1145
+ }
1146
+
1147
+ const { Provider: Provider$1, useStore: useStore$1 } = createFastContext(undefined);
1148
+ function FieldsetContextProvider({ children, initialValue, }) {
1149
+ return jsx(Provider$1, { initialValue: initialValue, children: children });
1150
+ }
1151
+ function useFieldsetContext() {
1152
+ const [fieldsetContext, setFieldsetContext] = useStore$1(store => store), [, formContextFunctions] = useFormContext();
1153
+ const registerField = useCallback((field) => {
1154
+ setFieldsetContext?.(prev => {
1155
+ if (!prev)
1156
+ return prev;
1157
+ const existingFieldIndex = prev.fieldList.findIndex(existingField => existingField.id === field.id);
1158
+ const newFieldList = existingFieldIndex >= 0
1159
+ ? prev.fieldList.map((existingField, index) => (index === existingFieldIndex ? field : existingField))
1160
+ : [...prev.fieldList, field];
1161
+ return { ...prev, fieldList: newFieldList };
1162
+ });
1163
+ }, []);
1164
+ const removeField = useCallback((fieldID) => {
1165
+ setFieldsetContext?.(prev => {
1166
+ if (!prev)
1167
+ return prev;
1168
+ return {
1169
+ ...prev,
1170
+ fieldList: prev.fieldList.filter(field => field.id !== fieldID),
1171
+ };
1172
+ });
1173
+ }, []);
1174
+ const updateField = useCallback((fieldID, updates) => {
1175
+ setFieldsetContext?.(prev => {
1176
+ if (!prev)
1177
+ return prev;
1178
+ return {
1179
+ ...prev,
1180
+ fieldList: prev.fieldList.map(field => (field.id === fieldID ? { ...field, ...updates } : field)),
1181
+ };
1182
+ });
1183
+ }, []);
1184
+ const createFormContextEntry = useCallback((fieldsetEntry) => {
1185
+ if (fieldsetEntry.join) {
1186
+ const joinedValue = fieldsetEntry.fieldList
1187
+ .filter(field => isStringField(field) && field.value !== '')
1188
+ .map(field => field.value)
1189
+ .join(fieldsetEntry.join);
1190
+ return defineField({
1191
+ type: 'string',
1192
+ id: fieldsetEntry.id,
1193
+ name: fieldsetEntry.name,
1194
+ value: joinedValue,
1195
+ required: fieldsetEntry.fieldList.some(field => field.required),
1196
+ invalid: fieldsetEntry.fieldList.some(field => field.invalid),
1197
+ });
1198
+ }
1199
+ return defineField({
1200
+ type: 'object',
1201
+ id: fieldsetEntry.id,
1202
+ name: fieldsetEntry.name,
1203
+ fields: fieldsetEntry.fieldList,
1204
+ });
1205
+ }, []);
1206
+ useEffect(() => {
1207
+ if (!fieldsetContext || fieldsetContext.decorative)
1208
+ return;
1209
+ const formContextEntry = createFormContextEntry(fieldsetContext);
1210
+ formContextFunctions.registerField(formContextEntry);
1211
+ return () => {
1212
+ formContextFunctions.removeField(fieldsetContext.id);
1213
+ };
1214
+ }, [fieldsetContext, formContextFunctions.registerField, formContextFunctions.removeField, createFormContextEntry]);
1215
+ return [fieldsetContext, { registerField, removeField, updateField }];
854
1216
  }
855
1217
 
856
1218
  const DEFAULT_STATUS = 'incomplete';
@@ -862,9 +1224,18 @@ function useFormStatus() {
862
1224
  return useStore(store => store);
863
1225
  }
864
1226
 
865
- function Fieldset({ children, className, legend, legendProps, ...props }) {
1227
+ function Fieldset({ children, className, decorative = false, join, legend, legendProps, name, ...props }) {
1228
+ const uniqueID = useId();
1229
+ const fieldsetId = toLowerCase(legend || name, [' ', '_']) + '§' + uniqueID;
866
1230
  const { className: legendClassName, ...restLegendProps } = legendProps || {};
867
- return (jsx(Fieldset$1, { ...props, className: bag => twMerge('contents', typeof className === 'function' ? className(bag) : className), children: bag => (jsxs(Fragment$1, { children: [jsx(Legend, { ...restLegendProps, className: twMerge('text-lg font-bold sm:text-xl', typeof legendClassName === 'function' ? legendClassName(bag) : legendClassName), children: legend }), typeof children === 'function' ? children(bag) : children] })) }));
1231
+ name = legend || name;
1232
+ return (jsx(FieldsetContextProvider, { initialValue: {
1233
+ decorative,
1234
+ fieldList: [],
1235
+ id: fieldsetId,
1236
+ join,
1237
+ name,
1238
+ }, children: jsx(Fieldset$1, { ...props, name: name, className: bag => twMerge('contents', typeof className === 'function' ? className(bag) : className), children: bag => (jsxs(Fragment$1, { children: [legend && (jsx(Legend, { ...restLegendProps, className: twMerge('text-xl font-bold text-current/80', typeof legendClassName === 'function' ? legendClassName(bag) : legendClassName), children: legend })), typeof children === 'function' ? children(bag) : children] })) }) }));
868
1239
  }
869
1240
 
870
1241
  /**
@@ -3056,15 +3427,17 @@ function ArrowSvg({ className, ...props }) {
3056
3427
  return (jsxs("svg", { viewBox: '0 0 20 10', className: twMerge('h-2.5 w-5 fill-none', className), ...props, children: [jsx("path", { d: 'M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z', className: 'fill-neutral-50 dark:fill-neutral-800' }), jsx("path", { d: 'M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z', className: 'fill-neutral-400 dark:fill-none' }), jsx("path", { d: 'M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z', className: 'dark:fill-neutral-600' })] }));
3057
3428
  }
3058
3429
 
3059
- function Input({ checked, className, defaultValue, description, descriptionProps, disabled, fieldProps, invalid = true, label, labelProps, name, onBlur, onChange, placeholder, ref, required = true, type, value, ...props }) {
3060
- const [formContext, setFormContext] = useFormContext(), [errorMessage, setErrorMessage] = useState(undefined);
3430
+ const specialCharacterRegex = new RegExp(/[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~€‚ƒ„…†‡‰‹‘’“”•–—™›¡¢£¥§©«¬®°±¶º»¿×÷]/);
3431
+ 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 }) {
3432
+ const [formContext, formContextFunctions] = useFormContext(), [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext(), [errorMessage, setErrorMessage] = useState(undefined);
3433
+ if (type === 'password' && !placeholder)
3434
+ placeholder = '••••••••' + (required && !label ? '*' : '');
3061
3435
  if (placeholder === '*')
3062
3436
  placeholder = name + (required && !label ? '*' : '');
3063
3437
  if (label === '*')
3064
3438
  label = name;
3065
3439
  const uniqueID = useId(), fieldContextID = toLowerCase(name, [, '_']) + '§' + uniqueID;
3066
- if (Boolean(formContext?.find(field => field.id === fieldContextID)?.invalid))
3067
- invalid = true;
3440
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3068
3441
  const getFieldContextType = () => {
3069
3442
  switch (type) {
3070
3443
  case 'email':
@@ -3082,26 +3455,27 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3082
3455
  }
3083
3456
  };
3084
3457
  const fieldContextType = getFieldContextType();
3085
- const initialFieldContext = defineField({
3086
- type: fieldContextType,
3087
- id: fieldContextID,
3088
- invalid,
3089
- name,
3090
- required,
3091
- value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3092
- });
3458
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3093
3459
  useEffect(() => {
3094
- if (!setFormContext)
3095
- return;
3096
- setFormContext(prevContext => {
3097
- const otherFields = (prevContext || []).filter(field => field.id !== initialFieldContext.id);
3098
- return [...otherFields, initialFieldContext];
3460
+ const initialFieldContext = defineField({
3461
+ type: fieldContextType,
3462
+ id: fieldContextID,
3463
+ invalid,
3464
+ name,
3465
+ required,
3466
+ value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3099
3467
  });
3468
+ if (isInFieldset) {
3469
+ fieldsetContextFunctions.registerField(initialFieldContext);
3470
+ return () => {
3471
+ fieldsetContextFunctions.removeField(initialFieldContext.id);
3472
+ };
3473
+ }
3474
+ formContextFunctions.registerField(initialFieldContext);
3100
3475
  return () => {
3101
- setFormContext(prevContext => (prevContext || []).filter(field => field.id !== initialFieldContext.id));
3476
+ formContextFunctions.removeField(initialFieldContext.id);
3102
3477
  };
3103
- }, [setFormContext]);
3104
- const fieldContext = formContext?.find(({ id: fieldID }) => fieldID === initialFieldContext.id) || initialFieldContext;
3478
+ }, [isInFieldset]);
3105
3479
  const validateField = (validValue) => {
3106
3480
  const noValue = !validValue || validValue === '';
3107
3481
  if (!required && noValue)
@@ -3117,15 +3491,86 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3117
3491
  if (!isEmail(validValue))
3118
3492
  errorMessageList.push('This is not a valid email.');
3119
3493
  break;
3494
+ case 'date':
3495
+ const valueAsTime = new Date().getTime();
3496
+ if (min && !(min instanceof Date) && typeof min !== 'number') {
3497
+ if (Array.isArray(min)) {
3498
+ const monthIndex = typeof min[1] === 'number' ? min[1] - 1 : getMonthIndexFromName(min[1]);
3499
+ min = new Date(min[0], monthIndex, min[2]);
3500
+ }
3501
+ else if ('year' in min && 'month' in min && 'day' in min) {
3502
+ const monthIndex = typeof min.month === 'number' ? min.month - 1 : getMonthIndexFromName(min.month);
3503
+ min = new Date(min.year, monthIndex, min.day);
3504
+ }
3505
+ if (valueAsTime < min.getTime())
3506
+ errorMessageList.push(`Value cannot be lower than ${getUserReadableDate(min)}.`);
3507
+ }
3508
+ if (max && !(max instanceof Date) && typeof max !== 'number') {
3509
+ if (Array.isArray(max)) {
3510
+ const monthIndex = typeof max[1] === 'number' ? max[1] - 1 : getMonthIndexFromName(max[1]);
3511
+ max = new Date(max[0], monthIndex, max[2]);
3512
+ }
3513
+ else if ('year' in max && 'month' in max && 'day' in max) {
3514
+ const monthIndex = typeof max.month === 'number' ? max.month - 1 : getMonthIndexFromName(max.month);
3515
+ max = new Date(max.year, monthIndex, max.day);
3516
+ }
3517
+ if (valueAsTime > max.getTime())
3518
+ errorMessageList.push(`Value cannot be higher than ${getUserReadableDate(max)}.`);
3519
+ }
3520
+ break;
3120
3521
  case 'number':
3121
- if (isNaN(Number(validValue)))
3522
+ const valueAsNumber = Number(validValue);
3523
+ if (isNaN(valueAsNumber))
3122
3524
  errorMessageList.push('This is not a valid number.');
3525
+ if (typeof max === 'number' && valueAsNumber > max)
3526
+ errorMessageList.push(`Value cannot be higher than ${max}.`);
3527
+ if (typeof min === 'number' && valueAsNumber < min)
3528
+ errorMessageList.push(`Value cannot be lower than ${min}.`);
3529
+ break;
3530
+ case 'password':
3531
+ if (options) {
3532
+ const { matchPreviousInput, requireLowercaseCharacter, requireNumber, requireSpecialCharacter, requireUppercaseCharacter, } = options;
3533
+ if (matchPreviousInput && formContext && formContext.length >= 2) {
3534
+ if (isInFieldset && fieldsetContext.fieldList.length > 1) {
3535
+ const currentInputIndex = fieldsetContext.fieldList.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3536
+ if (currentInputIndex > 0) {
3537
+ const previousInput = fieldsetContext.fieldList.find((_, index) => index === currentInputIndex - 1);
3538
+ if (previousInput &&
3539
+ isStringField(previousInput) &&
3540
+ previousInput.value !== validValue)
3541
+ errorMessageList.push('Passwords must match.');
3542
+ }
3543
+ }
3544
+ else {
3545
+ const currentInputIndex = formContext.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3546
+ if (currentInputIndex > 0) {
3547
+ const previousInput = formContext.find((_, index) => index === currentInputIndex - 1);
3548
+ if (previousInput &&
3549
+ isStringField(previousInput) &&
3550
+ previousInput.value !== validValue)
3551
+ errorMessageList.push('Passwords must match.');
3552
+ }
3553
+ }
3554
+ }
3555
+ if (requireLowercaseCharacter && !/[a-z]/g.test(validValue))
3556
+ errorMessageList.push('You must include a lowercase character.');
3557
+ if (requireNumber && !/[0-9]/g.test(validValue))
3558
+ errorMessageList.push('You must include a number.');
3559
+ if (requireSpecialCharacter && !specialCharacterRegex.test(validValue))
3560
+ errorMessageList.push('You must include a special character.');
3561
+ if (requireUppercaseCharacter && !/[A-Z]/g.test(validValue))
3562
+ errorMessageList.push('You must include an uppercase character.');
3563
+ }
3123
3564
  break;
3124
3565
  case 'tel':
3125
3566
  if (!isPhoneNumber(validValue))
3126
3567
  errorMessageList.push('This is not a valid phone number.');
3127
3568
  break;
3128
3569
  }
3570
+ if (props.maxLength && validValue.length > Number(props.maxLength))
3571
+ errorMessageList.push(`This may not have more than ${props.maxLength} characters.`);
3572
+ if (props.minLength && validValue.length < Number(props.minLength))
3573
+ errorMessageList.push(`This must have at least ${props.minLength} characters.`);
3129
3574
  if (errorMessageList.length === 0)
3130
3575
  return true;
3131
3576
  setErrorMessage(errorMessageList.join(' '));
@@ -3137,19 +3582,15 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3137
3582
  return;
3138
3583
  }
3139
3584
  const { currentTarget } = e, { value: newValue } = currentTarget;
3140
- setFormContext?.(prevContext => {
3141
- if (!prevContext)
3142
- return [];
3143
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3144
- if (!field)
3145
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3146
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3147
- const updatedField = { ...field, value: newValue };
3148
- const invalidField = validateField(newValue) === false;
3149
- if (invalidField !== field.invalid)
3150
- updatedField.invalid = invalidField;
3151
- return [...otherFields, updatedField];
3152
- });
3585
+ if (isInFieldset) {
3586
+ fieldsetContextFunctions.updateField(fieldContextID, {
3587
+ value: newValue,
3588
+ invalid: validateField(newValue) === false,
3589
+ });
3590
+ }
3591
+ else {
3592
+ formContextFunctions.updateField(fieldContextID, { value: newValue, invalid: validateField(newValue) === false });
3593
+ }
3153
3594
  onChange?.(e);
3154
3595
  };
3155
3596
  const handleBlur = e => {
@@ -3160,32 +3601,21 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3160
3601
  const { currentTarget } = e, { value: newValue } = currentTarget;
3161
3602
  if (required)
3162
3603
  validateField(newValue);
3604
+ let processedValue = newValue;
3163
3605
  switch (type) {
3164
3606
  case 'email':
3165
- setFormContext?.(prevContext => {
3166
- if (!prevContext)
3167
- return [];
3168
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3169
- if (!field)
3170
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3171
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3172
- const updatedField = { ...field, value: newValue.toLowerCase() };
3173
- return [...otherFields, updatedField];
3174
- });
3607
+ processedValue = newValue.toLowerCase();
3175
3608
  break;
3176
3609
  case 'tel':
3177
- setFormContext?.(prevContext => {
3178
- if (!prevContext)
3179
- return [];
3180
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3181
- if (!field)
3182
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3183
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3184
- const updatedField = { ...field, value: formatPhoneNumber(newValue, '1') };
3185
- return [...otherFields, updatedField];
3186
- });
3610
+ processedValue = formatPhoneNumber(newValue, options);
3187
3611
  break;
3188
3612
  }
3613
+ if (isInFieldset) {
3614
+ fieldsetContextFunctions.updateField(fieldContextID, { value: processedValue });
3615
+ }
3616
+ else {
3617
+ formContextFunctions.updateField(fieldContextID, { value: processedValue });
3618
+ }
3189
3619
  onBlur?.(e);
3190
3620
  };
3191
3621
  const restFieldProps = fieldProps
@@ -3205,12 +3635,12 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3205
3635
  // user-invalid styles
3206
3636
  '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))]',
3207
3637
  // Custom styles
3208
- 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'
3638
+ 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'
3209
3639
  ? descriptionProps?.className(bag)
3210
3640
  : descriptionProps?.className), children: description }))] }));
3211
3641
  }
3212
3642
 
3213
- function SubmitButton({ children, className, customTheme, error, incomplete, loading, success, type = 'submit', ...props }) {
3643
+ function SubmitButton({ children, className, customTheme, error, incomplete, loading, success, type, ...props }) {
3214
3644
  const [formStatus] = useFormStatus();
3215
3645
  const getFormStatusButtonClasses = () => {
3216
3646
  switch (formStatus) {
@@ -3258,40 +3688,43 @@ function SubmitButton({ children, className, customTheme, error, incomplete, loa
3258
3688
  }
3259
3689
  };
3260
3690
  const dataFormState = getDataFormState();
3261
- return (jsx(Button, { ...props, ...dataFormState, as: 'button', className: twMerge([formStatusButtonClasses, 'w-full text-white data-loading:text-black', className]), customTheme: {
3691
+ return (jsx(Button, { ...props, ...dataFormState, className: twMerge([formStatusButtonClasses, 'w-full text-white data-loading:text-black', className]), customTheme: {
3262
3692
  themeColor: twMerge('data-error:[--theme-color:var(--color-ui-red)] data-incomplete:[--theme-color:var(--color-ui-grey)] data-loading:[--theme-color:var(--color-ui-yellow)] data-readonly:[--theme-color:var(--color-ui-grey)] data-ready:[--theme-color:var(--color-ui-blue)] data-success:[--theme-color:var(--color-ui-green)]', customTheme?.themeColor),
3263
- }, theme: 'custom', type: type, children: buttonText }));
3693
+ }, theme: 'custom', type: type || 'submit', children: buttonText }));
3264
3694
  }
3265
3695
 
3266
3696
  function Textarea({ className, defaultValue, description, descriptionProps, disabled, fieldProps, invalid = true, label, labelProps, name, onBlur, onChange, placeholder, ref, required = true, value, ...props }) {
3267
- const [formContext, setFormContext] = useFormContext(), [errorMessage, setErrorMessage] = useState(undefined);
3697
+ const [formContext, formContextFunctions] = useFormContext();
3698
+ const [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext();
3699
+ const [errorMessage, setErrorMessage] = useState(undefined);
3268
3700
  if (placeholder === '*')
3269
3701
  placeholder = name + (required && !label ? '*' : '');
3270
3702
  if (label === '*')
3271
3703
  label = name;
3272
- const uniqueID = useId(), fieldContextID = toLowerCase(name, [, '_']) + '§' + uniqueID;
3273
- if (Boolean(formContext?.find(field => field.id === fieldContextID)?.invalid))
3274
- invalid = true;
3275
- const initialFieldContext = defineField({
3276
- type: 'textarea',
3277
- id: fieldContextID,
3278
- invalid,
3279
- name,
3280
- required,
3281
- value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3282
- });
3704
+ const uniqueID = useId();
3705
+ const fieldContextID = toLowerCase(name, [' ', '_']) + '§' + uniqueID;
3706
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3283
3707
  useEffect(() => {
3284
- if (!setFormContext)
3285
- return;
3286
- setFormContext(prevContext => {
3287
- const otherFields = (prevContext || []).filter(field => field.id !== initialFieldContext.id);
3288
- return [...otherFields, initialFieldContext];
3708
+ const initialFieldContext = defineField({
3709
+ type: 'textarea',
3710
+ id: fieldContextID,
3711
+ invalid,
3712
+ name,
3713
+ required,
3714
+ value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3289
3715
  });
3716
+ if (isInFieldset) {
3717
+ fieldsetContextFunctions.registerField(initialFieldContext);
3718
+ return () => {
3719
+ fieldsetContextFunctions.removeField(initialFieldContext.id);
3720
+ };
3721
+ }
3722
+ formContextFunctions.registerField(initialFieldContext);
3290
3723
  return () => {
3291
- setFormContext(prevContext => (prevContext || []).filter(field => field.id !== initialFieldContext.id));
3724
+ formContextFunctions.removeField(initialFieldContext.id);
3292
3725
  };
3293
- }, [setFormContext]);
3294
- const fieldContext = formContext?.find(({ id: fieldID }) => fieldID === initialFieldContext.id) || initialFieldContext;
3726
+ }, [isInFieldset]);
3727
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3295
3728
  const validateField = (validValue) => {
3296
3729
  const noValue = !validValue || validValue === '';
3297
3730
  if (!required && noValue)
@@ -3302,6 +3735,10 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
3302
3735
  setErrorMessage(errorMessageList.join(' '));
3303
3736
  return false;
3304
3737
  }
3738
+ if (props.maxLength && validValue.length > Number(props.maxLength))
3739
+ errorMessageList.push(`This may not have more than ${props.maxLength} characters.`);
3740
+ if (props.minLength && validValue.length < Number(props.minLength))
3741
+ errorMessageList.push(`This must have at least ${props.minLength} characters.`);
3305
3742
  if (errorMessageList.length === 0)
3306
3743
  return true;
3307
3744
  setErrorMessage(errorMessageList.join(' '));
@@ -3312,22 +3749,41 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
3312
3749
  e.preventDefault();
3313
3750
  return;
3314
3751
  }
3315
- const { currentTarget } = e, { value: newValue } = currentTarget;
3316
- setFormContext?.(prevContext => {
3317
- if (!prevContext)
3318
- return [];
3319
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3320
- if (!field)
3321
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3322
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3323
- const updatedField = { ...field, value: newValue };
3324
- const invalidField = validateField(newValue) === false;
3325
- if (invalidField !== field.invalid)
3326
- updatedField.invalid = invalidField;
3327
- return [...otherFields, updatedField];
3328
- });
3752
+ const { currentTarget } = e;
3753
+ const { value: newValue } = currentTarget;
3754
+ if (isInFieldset) {
3755
+ fieldsetContextFunctions.updateField(fieldContextID, {
3756
+ value: newValue,
3757
+ invalid: validateField(newValue) === false,
3758
+ });
3759
+ }
3760
+ else {
3761
+ formContextFunctions.updateField(fieldContextID, {
3762
+ value: newValue,
3763
+ invalid: validateField(newValue) === false,
3764
+ });
3765
+ }
3329
3766
  onChange?.(e);
3330
3767
  };
3768
+ const handleBlur = e => {
3769
+ if (disabled) {
3770
+ e.preventDefault();
3771
+ return;
3772
+ }
3773
+ const { currentTarget } = e;
3774
+ const { value: newValue } = currentTarget;
3775
+ if (required)
3776
+ validateField(newValue);
3777
+ // No special processing needed for textarea like email/phone formatting
3778
+ const processedValue = newValue;
3779
+ if (isInFieldset) {
3780
+ fieldsetContextFunctions.updateField(fieldContextID, { value: processedValue });
3781
+ }
3782
+ else {
3783
+ formContextFunctions.updateField(fieldContextID, { value: processedValue });
3784
+ }
3785
+ onBlur?.(e);
3786
+ };
3331
3787
  const restFieldProps = fieldProps
3332
3788
  ? Object.fromEntries(Object.entries(fieldProps).filter(([key]) => key !== 'className'))
3333
3789
  : {};
@@ -3345,23 +3801,34 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
3345
3801
  // user-invalid styles
3346
3802
  '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))]',
3347
3803
  // Custom styles
3348
- typeof className === 'function' ? className(bag) : className), id: fieldContext?.id, invalid: invalid, onChange: handleChange, placeholder: placeholder, ref: ref, required: required, 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'
3804
+ typeof className === 'function' ? className(bag) : className), id: fieldContext?.id, invalid: invalid, onChange: handleChange, onBlur: handleBlur, placeholder: placeholder, ref: ref, required: required, 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'
3349
3805
  ? descriptionProps?.className(bag)
3350
3806
  : descriptionProps?.className), children: description }))] }));
3351
3807
  }
3352
3808
 
3353
- // import { findComponentByType } from '../../utils'
3354
3809
  function FormComponent({ as, children, className, handleSubmit, onError, onSubmit, onSuccess, ...props }) {
3355
3810
  const [formContext] = useFormContext(), [formStatus, setFormStatus] = useFormStatus();
3356
- // const submitButton = findComponentByType(children, SubmitButton)
3811
+ const checkField = useCallback((field) => {
3812
+ if (field.type !== 'array' && field.type !== 'object' && !field.invalid)
3813
+ return true;
3814
+ if (field.type === 'object')
3815
+ return field.fields.every(objectField => checkField(objectField));
3816
+ return false;
3817
+ }, []);
3818
+ const everyFieldIsValid = useCallback(() => {
3819
+ if (!formContext)
3820
+ return false;
3821
+ return formContext.every(field => checkField(field));
3822
+ }, [formContext, checkField]);
3357
3823
  useEffect(() => {
3358
3824
  if (!formContext)
3359
3825
  return;
3360
- if (formStatus !== 'incomplete' && formContext.find(({ invalid }) => invalid))
3826
+ if (formStatus !== 'incomplete' &&
3827
+ formContext.find(context => context.type !== 'array' && context.type !== 'object' && context.invalid))
3361
3828
  setFormStatus?.('incomplete');
3362
- if (formStatus !== 'ready' && formContext.every(({ invalid }) => !invalid))
3829
+ if (formStatus !== 'ready' && everyFieldIsValid())
3363
3830
  setFormStatus?.('ready');
3364
- }, [formContext]);
3831
+ }, [formContext, everyFieldIsValid]);
3365
3832
  const processSubmit = handleSubmit ||
3366
3833
  (async (e) => {
3367
3834
  e.preventDefault();
@@ -3434,9 +3901,16 @@ function ModalTrigger({ as, ...props }) {
3434
3901
  const Element = as || Button$1;
3435
3902
  return jsx(Element, { ...props });
3436
3903
  }
3904
+ function ModalTitle(props) {
3905
+ return jsx(DialogTitle, { ...props });
3906
+ }
3437
3907
  function ModalDialog(props) {
3438
3908
  return jsx("div", { ...props });
3439
3909
  }
3910
+ function ModalClose({ as, ...props }) {
3911
+ const Element = as || Button$1;
3912
+ return jsx(Element, { ...props });
3913
+ }
3440
3914
  function Modal({ children, className, onClose, onOpen, place = 'bottom' }) {
3441
3915
  const [bodyElement, setBodyElement] = useState(null);
3442
3916
  useEffect(() => {
@@ -3602,5 +4076,5 @@ function Time({ children, dateObject, dateTime, day, hours, milliseconds, minute
3602
4076
  return (jsx("time", { dateTime: dateAndTime, ref: ref, ...props, children: dateDisplay }));
3603
4077
  }
3604
4078
 
3605
- export { Anchor, Button, Details, DetailsBody, DetailsSummary, DropDown, DropDownButton, DropDownItem, DropDownItems, DropDownSection, DropDownSeparator, Fieldset, Form, Ghost, Heading, Input, Link, Modal, ModalDialog, ModalTrigger, SubmitButton, Textarea, Time, Tooltip, TooltipPanel, TooltipTrigger };
4079
+ export { Anchor, Button, Details, DetailsBody, DetailsSummary, DropDown, DropDownButton, DropDownItem, DropDownItems, DropDownSection, DropDownSeparator, Fieldset, Form, Ghost, Heading, Input, Link, Modal, ModalClose, ModalDialog, ModalTitle, ModalTrigger, SubmitButton, Textarea, Time, Tooltip, TooltipPanel, TooltipTrigger, createButton, createLink };
3606
4080
  //# sourceMappingURL=index.esm.js.map