mado-ui 0.3.2 → 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 +41 -6
  2. package/dist/components/button.d.ts +3 -23
  3. package/dist/components/details.d.ts +2 -2
  4. package/dist/components/drop-down.d.ts +1 -1
  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 +1 -1
  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 +3 -14
  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 +466 -122
  20. package/dist/components.esm.js.map +1 -1
  21. package/dist/components.js +465 -119
  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 +466 -122
  135. package/dist/index.esm.js.map +1 -1
  136. package/dist/index.js +465 -119
  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
@@ -915,7 +1053,7 @@ function DropDownItems({ anchor, children, className, containerClassName, style,
915
1053
  }
916
1054
  function DropDownSection({ children, label, labelProps, separatorAbove, separatorBelow, ...props }) {
917
1055
  const { labelClassName, ...restLabelProps } = { labelClassName: labelProps?.className || '', ...labelProps };
918
- 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', 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, {})] })) }));
919
1057
  }
920
1058
  function DropDownSeparator({ className, ...props }) {
921
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) }));
@@ -975,12 +1113,106 @@ function createFastContext(defaultInitialState) {
975
1113
  function defineField(fieldDefinition) {
976
1114
  return fieldDefinition;
977
1115
  }
978
- 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([]);
979
1120
  function FormContextProvider({ children }) {
980
- return jsx(Provider$1, { children: children });
1121
+ return jsx(Provider$2, { children: children });
981
1122
  }
982
1123
  function useFormContext() {
983
- 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 }];
984
1216
  }
985
1217
 
986
1218
  const DEFAULT_STATUS = 'incomplete';
@@ -992,9 +1224,18 @@ function useFormStatus() {
992
1224
  return useStore(store => store);
993
1225
  }
994
1226
 
995
- 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;
996
1230
  const { className: legendClassName, ...restLegendProps } = legendProps || {};
997
- 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] })) }) }));
998
1239
  }
999
1240
 
1000
1241
  /**
@@ -3186,15 +3427,17 @@ function ArrowSvg({ className, ...props }) {
3186
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' })] }));
3187
3428
  }
3188
3429
 
3189
- function Input({ checked, className, defaultValue, description, descriptionProps, disabled, fieldProps, invalid = true, label, labelProps, name, onBlur, onChange, placeholder, ref, required = true, type, value, ...props }) {
3190
- 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 ? '*' : '');
3191
3435
  if (placeholder === '*')
3192
3436
  placeholder = name + (required && !label ? '*' : '');
3193
3437
  if (label === '*')
3194
3438
  label = name;
3195
3439
  const uniqueID = useId(), fieldContextID = toLowerCase(name, [, '_']) + '§' + uniqueID;
3196
- if (Boolean(formContext?.find(field => field.id === fieldContextID)?.invalid))
3197
- invalid = true;
3440
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3198
3441
  const getFieldContextType = () => {
3199
3442
  switch (type) {
3200
3443
  case 'email':
@@ -3212,26 +3455,27 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3212
3455
  }
3213
3456
  };
3214
3457
  const fieldContextType = getFieldContextType();
3215
- const initialFieldContext = defineField({
3216
- type: fieldContextType,
3217
- id: fieldContextID,
3218
- invalid,
3219
- name,
3220
- required,
3221
- value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3222
- });
3458
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3223
3459
  useEffect(() => {
3224
- if (!setFormContext)
3225
- return;
3226
- setFormContext(prevContext => {
3227
- const otherFields = (prevContext || []).filter(field => field.id !== initialFieldContext.id);
3228
- 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}` : '',
3229
3467
  });
3468
+ if (isInFieldset) {
3469
+ fieldsetContextFunctions.registerField(initialFieldContext);
3470
+ return () => {
3471
+ fieldsetContextFunctions.removeField(initialFieldContext.id);
3472
+ };
3473
+ }
3474
+ formContextFunctions.registerField(initialFieldContext);
3230
3475
  return () => {
3231
- setFormContext(prevContext => (prevContext || []).filter(field => field.id !== initialFieldContext.id));
3476
+ formContextFunctions.removeField(initialFieldContext.id);
3232
3477
  };
3233
- }, [setFormContext]);
3234
- const fieldContext = formContext?.find(({ id: fieldID }) => fieldID === initialFieldContext.id) || initialFieldContext;
3478
+ }, [isInFieldset]);
3235
3479
  const validateField = (validValue) => {
3236
3480
  const noValue = !validValue || validValue === '';
3237
3481
  if (!required && noValue)
@@ -3247,15 +3491,86 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3247
3491
  if (!isEmail(validValue))
3248
3492
  errorMessageList.push('This is not a valid email.');
3249
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;
3250
3521
  case 'number':
3251
- if (isNaN(Number(validValue)))
3522
+ const valueAsNumber = Number(validValue);
3523
+ if (isNaN(valueAsNumber))
3252
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
+ }
3253
3564
  break;
3254
3565
  case 'tel':
3255
3566
  if (!isPhoneNumber(validValue))
3256
3567
  errorMessageList.push('This is not a valid phone number.');
3257
3568
  break;
3258
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.`);
3259
3574
  if (errorMessageList.length === 0)
3260
3575
  return true;
3261
3576
  setErrorMessage(errorMessageList.join(' '));
@@ -3267,19 +3582,15 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3267
3582
  return;
3268
3583
  }
3269
3584
  const { currentTarget } = e, { value: newValue } = currentTarget;
3270
- setFormContext?.(prevContext => {
3271
- if (!prevContext)
3272
- return [];
3273
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3274
- if (!field)
3275
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3276
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3277
- const updatedField = { ...field, value: newValue };
3278
- const invalidField = validateField(newValue) === false;
3279
- if (invalidField !== field.invalid)
3280
- updatedField.invalid = invalidField;
3281
- return [...otherFields, updatedField];
3282
- });
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
+ }
3283
3594
  onChange?.(e);
3284
3595
  };
3285
3596
  const handleBlur = e => {
@@ -3290,32 +3601,21 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3290
3601
  const { currentTarget } = e, { value: newValue } = currentTarget;
3291
3602
  if (required)
3292
3603
  validateField(newValue);
3604
+ let processedValue = newValue;
3293
3605
  switch (type) {
3294
3606
  case 'email':
3295
- setFormContext?.(prevContext => {
3296
- if (!prevContext)
3297
- return [];
3298
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3299
- if (!field)
3300
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3301
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3302
- const updatedField = { ...field, value: newValue.toLowerCase() };
3303
- return [...otherFields, updatedField];
3304
- });
3607
+ processedValue = newValue.toLowerCase();
3305
3608
  break;
3306
3609
  case 'tel':
3307
- setFormContext?.(prevContext => {
3308
- if (!prevContext)
3309
- return [];
3310
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3311
- if (!field)
3312
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3313
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3314
- const updatedField = { ...field, value: formatPhoneNumber(newValue, '1') };
3315
- return [...otherFields, updatedField];
3316
- });
3610
+ processedValue = formatPhoneNumber(newValue, options);
3317
3611
  break;
3318
3612
  }
3613
+ if (isInFieldset) {
3614
+ fieldsetContextFunctions.updateField(fieldContextID, { value: processedValue });
3615
+ }
3616
+ else {
3617
+ formContextFunctions.updateField(fieldContextID, { value: processedValue });
3618
+ }
3319
3619
  onBlur?.(e);
3320
3620
  };
3321
3621
  const restFieldProps = fieldProps
@@ -3335,7 +3635,7 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3335
3635
  // user-invalid styles
3336
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))]',
3337
3637
  // Custom styles
3338
- 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'
3339
3639
  ? descriptionProps?.className(bag)
3340
3640
  : descriptionProps?.className), children: description }))] }));
3341
3641
  }
@@ -3394,34 +3694,37 @@ function SubmitButton({ children, className, customTheme, error, incomplete, loa
3394
3694
  }
3395
3695
 
3396
3696
  function Textarea({ className, defaultValue, description, descriptionProps, disabled, fieldProps, invalid = true, label, labelProps, name, onBlur, onChange, placeholder, ref, required = true, value, ...props }) {
3397
- const [formContext, setFormContext] = useFormContext(), [errorMessage, setErrorMessage] = useState(undefined);
3697
+ const [formContext, formContextFunctions] = useFormContext();
3698
+ const [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext();
3699
+ const [errorMessage, setErrorMessage] = useState(undefined);
3398
3700
  if (placeholder === '*')
3399
3701
  placeholder = name + (required && !label ? '*' : '');
3400
3702
  if (label === '*')
3401
3703
  label = name;
3402
- const uniqueID = useId(), fieldContextID = toLowerCase(name, [, '_']) + '§' + uniqueID;
3403
- if (Boolean(formContext?.find(field => field.id === fieldContextID)?.invalid))
3404
- invalid = true;
3405
- const initialFieldContext = defineField({
3406
- type: 'textarea',
3407
- id: fieldContextID,
3408
- invalid,
3409
- name,
3410
- required,
3411
- value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3412
- });
3704
+ const uniqueID = useId();
3705
+ const fieldContextID = toLowerCase(name, [' ', '_']) + '§' + uniqueID;
3706
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3413
3707
  useEffect(() => {
3414
- if (!setFormContext)
3415
- return;
3416
- setFormContext(prevContext => {
3417
- const otherFields = (prevContext || []).filter(field => field.id !== initialFieldContext.id);
3418
- 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}` : '',
3419
3715
  });
3716
+ if (isInFieldset) {
3717
+ fieldsetContextFunctions.registerField(initialFieldContext);
3718
+ return () => {
3719
+ fieldsetContextFunctions.removeField(initialFieldContext.id);
3720
+ };
3721
+ }
3722
+ formContextFunctions.registerField(initialFieldContext);
3420
3723
  return () => {
3421
- setFormContext(prevContext => (prevContext || []).filter(field => field.id !== initialFieldContext.id));
3724
+ formContextFunctions.removeField(initialFieldContext.id);
3422
3725
  };
3423
- }, [setFormContext]);
3424
- const fieldContext = formContext?.find(({ id: fieldID }) => fieldID === initialFieldContext.id) || initialFieldContext;
3726
+ }, [isInFieldset]);
3727
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3425
3728
  const validateField = (validValue) => {
3426
3729
  const noValue = !validValue || validValue === '';
3427
3730
  if (!required && noValue)
@@ -3432,6 +3735,10 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
3432
3735
  setErrorMessage(errorMessageList.join(' '));
3433
3736
  return false;
3434
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.`);
3435
3742
  if (errorMessageList.length === 0)
3436
3743
  return true;
3437
3744
  setErrorMessage(errorMessageList.join(' '));
@@ -3442,22 +3749,41 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
3442
3749
  e.preventDefault();
3443
3750
  return;
3444
3751
  }
3445
- const { currentTarget } = e, { value: newValue } = currentTarget;
3446
- setFormContext?.(prevContext => {
3447
- if (!prevContext)
3448
- return [];
3449
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3450
- if (!field)
3451
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3452
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3453
- const updatedField = { ...field, value: newValue };
3454
- const invalidField = validateField(newValue) === false;
3455
- if (invalidField !== field.invalid)
3456
- updatedField.invalid = invalidField;
3457
- return [...otherFields, updatedField];
3458
- });
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
+ }
3459
3766
  onChange?.(e);
3460
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
+ };
3461
3787
  const restFieldProps = fieldProps
3462
3788
  ? Object.fromEntries(Object.entries(fieldProps).filter(([key]) => key !== 'className'))
3463
3789
  : {};
@@ -3475,23 +3801,34 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
3475
3801
  // user-invalid styles
3476
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))]',
3477
3803
  // Custom styles
3478
- 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'
3479
3805
  ? descriptionProps?.className(bag)
3480
3806
  : descriptionProps?.className), children: description }))] }));
3481
3807
  }
3482
3808
 
3483
- // import { findComponentByType } from '../../utils'
3484
3809
  function FormComponent({ as, children, className, handleSubmit, onError, onSubmit, onSuccess, ...props }) {
3485
3810
  const [formContext] = useFormContext(), [formStatus, setFormStatus] = useFormStatus();
3486
- // 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]);
3487
3823
  useEffect(() => {
3488
3824
  if (!formContext)
3489
3825
  return;
3490
- if (formStatus !== 'incomplete' && formContext.find(({ invalid }) => invalid))
3826
+ if (formStatus !== 'incomplete' &&
3827
+ formContext.find(context => context.type !== 'array' && context.type !== 'object' && context.invalid))
3491
3828
  setFormStatus?.('incomplete');
3492
- if (formStatus !== 'ready' && formContext.every(({ invalid }) => !invalid))
3829
+ if (formStatus !== 'ready' && everyFieldIsValid())
3493
3830
  setFormStatus?.('ready');
3494
- }, [formContext]);
3831
+ }, [formContext, everyFieldIsValid]);
3495
3832
  const processSubmit = handleSubmit ||
3496
3833
  (async (e) => {
3497
3834
  e.preventDefault();
@@ -3564,9 +3901,16 @@ function ModalTrigger({ as, ...props }) {
3564
3901
  const Element = as || Button$1;
3565
3902
  return jsx(Element, { ...props });
3566
3903
  }
3904
+ function ModalTitle(props) {
3905
+ return jsx(DialogTitle, { ...props });
3906
+ }
3567
3907
  function ModalDialog(props) {
3568
3908
  return jsx("div", { ...props });
3569
3909
  }
3910
+ function ModalClose({ as, ...props }) {
3911
+ const Element = as || Button$1;
3912
+ return jsx(Element, { ...props });
3913
+ }
3570
3914
  function Modal({ children, className, onClose, onOpen, place = 'bottom' }) {
3571
3915
  const [bodyElement, setBodyElement] = useState(null);
3572
3916
  useEffect(() => {
@@ -3732,5 +4076,5 @@ function Time({ children, dateObject, dateTime, day, hours, milliseconds, minute
3732
4076
  return (jsx("time", { dateTime: dateAndTime, ref: ref, ...props, children: dateDisplay }));
3733
4077
  }
3734
4078
 
3735
- 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, createButton, createLink };
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 };
3736
4080
  //# sourceMappingURL=index.esm.js.map