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.js CHANGED
@@ -208,6 +208,9 @@ function getMonth(month = d) {
208
208
  formattedMonth = `0${formattedMonth}`;
209
209
  return formattedMonth;
210
210
  }
211
+ function getMonthIndexFromName(name) {
212
+ return monthNamesList.findIndex(monthName => monthName === name);
213
+ }
211
214
  /**
212
215
  * ### Get Month Name
213
216
  * - Returns the name of the specified month
@@ -233,6 +236,30 @@ function getSeconds(seconds = d) {
233
236
  formattedSeconds = `0${formattedSeconds}`;
234
237
  return formattedSeconds;
235
238
  }
239
+ /**
240
+ * ### Get User Readable Date
241
+ * - Returns a string of the current date in a user-friendly way
242
+ * - Includes `'Yesterday'`, '`Today'`, and `'Tomorrow'`
243
+ * @param date (default: `new Date()`)
244
+ * @returns {'Today'|'Tomorrow'|'Yesterday'|string} `'weekday, month day, year'` | `'Yesterday'` | `'Today'` | `'Tomorrow'`
245
+ */
246
+ function getUserReadableDate(date = d) {
247
+ const dateTime = date.getTime();
248
+ const today = new Date(), isToday = dateTime === today.getTime();
249
+ if (isToday)
250
+ return 'Today';
251
+ const yesterday = new Date(today.getDate() - 1), isYesterday = dateTime === yesterday.getTime();
252
+ if (isYesterday)
253
+ return 'Yesterday';
254
+ const tomorrow = new Date(today.getDate() + 1), isTomorrow = dateTime === tomorrow.getTime();
255
+ if (isTomorrow)
256
+ return 'Tomorrow';
257
+ const thisYear = today.getFullYear(), isSameYear = date.getFullYear() === thisYear;
258
+ const fullDateString = toFullDateString(date, {
259
+ year: !isSameYear,
260
+ });
261
+ return fullDateString;
262
+ }
236
263
  /**
237
264
  * ### Get Weekday Name
238
265
  * - Returns the weekday name of the specified day
@@ -245,6 +272,45 @@ function getWeekdayName(weekday = d) {
245
272
  // Return the name of the day of the week
246
273
  return weekdayNamesList[weekday.getDay()];
247
274
  }
275
+ /**
276
+ * ### To Full Date String
277
+ * - Returns a formatted string to display the date
278
+ * @param {Date} date (default: `new Date()`)
279
+ * @param {ToFullDateStringOptionsProps} options Change how to display the weekday, month name, day of the month, and year.
280
+ * @returns {string} '`weekday`, `month` `day`, `year`'
281
+ */
282
+ function toFullDateString(date = d, options) {
283
+ let weekdayName = getWeekdayName(date), monthName = getMonthName(date), dayOfMonth = date.getDate(), year = date.getFullYear().toString();
284
+ if (options) {
285
+ const includesDay = options.day !== false, includesMonth = options.month !== false, includesYear = options.year !== false;
286
+ {
287
+ weekdayName = weekdayName.slice(0, 3);
288
+ if (includesMonth || includesDay || includesYear)
289
+ weekdayName += ', ';
290
+ }
291
+ if (includesMonth) {
292
+ if (options.month === 'code')
293
+ monthName = monthName.slice(0, 3);
294
+ if (includesDay)
295
+ monthName += ' ';
296
+ }
297
+ else {
298
+ monthName = '';
299
+ }
300
+ if (!includesDay)
301
+ dayOfMonth = '';
302
+ if (includesYear) {
303
+ if (options.year === 'code')
304
+ year = year.slice(-2);
305
+ if (includesMonth || includesDay)
306
+ year = ', ' + year;
307
+ }
308
+ else {
309
+ year = '';
310
+ }
311
+ }
312
+ return weekdayName + monthName + dayOfMonth + year;
313
+ }
248
314
 
249
315
  function findComponentByType(children, componentType) {
250
316
  const childrenArray = React.Children.toArray(children);
@@ -293,25 +359,97 @@ function isPhoneNumber(tel) {
293
359
  /**
294
360
  * # Format Phone Number
295
361
  * Converts any string containing at least 10 numbers to a formatted phone number
296
- * @param {string} string
297
- * @returns {string} string formatted (000) 000-0000
362
+ * @param {string} phoneNumber
363
+ * @param options
364
+ * @property {string} `countryCode`
365
+ * @property {'continuous' | 'dot' | 'hyphenated' | 'none' | 'space' | 'standard'} `format`
366
+ *
367
+ * Input: a555b555c5555d
368
+ *
369
+ * @example
370
+ * format: 'continuous'
371
+ * countryCode: '1'
372
+ * returns: +1 5555555555
373
+ *
374
+ * @example
375
+ * format: 'dot'
376
+ * returns: 555.555.5555
377
+ *
378
+ * @example
379
+ * format: 'hyphenated'
380
+ * returns: 555-555-5555
381
+ *
382
+ * @example
383
+ * format: 'none'
384
+ * countryCode: '1'
385
+ * returns: +1 a555b555c5555d
386
+ *
387
+ * @example
388
+ * format: 'space'
389
+ * returns: 555 555 5555
390
+ *
391
+ * @example
392
+ * format: 'standard' (default)
393
+ * returns: (555) 555-5555
394
+ *
395
+ * @returns {string} string formatted
298
396
  */
299
- function formatPhoneNumber(string, countryCode) {
300
- return (`${`+${countryCode} ` }` +
301
- string
302
- .replace(/\D/g, '')
303
- .slice(-10)
304
- .split('')
305
- .map((char, index) => {
306
- if (index === 0)
307
- return `(${char}`;
308
- if (index === 2)
309
- return `${char}) `;
310
- if (index === 5)
311
- return `${char}-`;
312
- return char;
313
- })
314
- .join(''));
397
+ function formatPhoneNumber(phoneNumber, options) {
398
+ const format = options?.format || 'standard';
399
+ if (format !== 'none')
400
+ phoneNumber = phoneNumber.replace(/\D/g, '').slice(-10);
401
+ switch (format) {
402
+ case 'dot':
403
+ phoneNumber = phoneNumber
404
+ .split('')
405
+ .map((char, index) => {
406
+ if (index === 2)
407
+ return `${char}.`;
408
+ if (index === 5)
409
+ return `${char}.`;
410
+ return char;
411
+ })
412
+ .join('');
413
+ break;
414
+ case 'hyphenated':
415
+ phoneNumber = phoneNumber
416
+ .split('')
417
+ .map((char, index) => {
418
+ if (index === 2)
419
+ return `${char}-`;
420
+ if (index === 5)
421
+ return `${char}-`;
422
+ return char;
423
+ })
424
+ .join('');
425
+ break;
426
+ case 'space':
427
+ phoneNumber = phoneNumber
428
+ .split('')
429
+ .map((char, index) => {
430
+ if (index === 2)
431
+ return `${char} `;
432
+ if (index === 5)
433
+ return `${char} `;
434
+ return char;
435
+ })
436
+ .join('');
437
+ break;
438
+ case 'standard':
439
+ phoneNumber = phoneNumber
440
+ .split('')
441
+ .map((char, index) => {
442
+ if (index === 0)
443
+ return `(${char}`;
444
+ if (index === 2)
445
+ return `${char}) `;
446
+ if (index === 5)
447
+ return `${char}-`;
448
+ return char;
449
+ })
450
+ .join('');
451
+ }
452
+ return `${options?.countryCode ? `+${options?.countryCode} ` : ''}` + phoneNumber;
315
453
  }
316
454
  /**
317
455
  * # To Lower Case
@@ -935,7 +1073,7 @@ function DropDownItems({ anchor, children, className, containerClassName, style,
935
1073
  }
936
1074
  function DropDownSection({ children, label, labelProps, separatorAbove, separatorBelow, ...props }) {
937
1075
  const { labelClassName, ...restLabelProps } = { labelClassName: labelProps?.className || '', ...labelProps };
938
- return (jsxRuntime.jsx(react.MenuSection, { ...props, children: sectionBag => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [separatorAbove && jsxRuntime.jsx(DropDownSeparator, {}), label && (jsxRuntime.jsx(react.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 && jsxRuntime.jsx(DropDownSeparator, {})] })) }));
1076
+ return (jsxRuntime.jsx(react.MenuSection, { ...props, children: sectionBag => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [separatorAbove && jsxRuntime.jsx(DropDownSeparator, {}), label && (jsxRuntime.jsx(react.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 && jsxRuntime.jsx(DropDownSeparator, {})] })) }));
939
1077
  }
940
1078
  function DropDownSeparator({ className, ...props }) {
941
1079
  return (jsxRuntime.jsx(react.MenuSeparator, { ...props, className: bag => twMerge('my-4 block h-px rounded-full bg-neutral-950/20', typeof className === 'function' ? className(bag) : className) }));
@@ -995,12 +1133,106 @@ function createFastContext(defaultInitialState) {
995
1133
  function defineField(fieldDefinition) {
996
1134
  return fieldDefinition;
997
1135
  }
998
- const { Provider: Provider$1, useStore: useStore$1 } = createFastContext([]);
1136
+ function isStringField(field) {
1137
+ return Boolean(field.type !== 'object' && field.type !== 'array');
1138
+ }
1139
+ const { Provider: Provider$2, useStore: useStore$2 } = createFastContext([]);
999
1140
  function FormContextProvider({ children }) {
1000
- return jsxRuntime.jsx(Provider$1, { children: children });
1141
+ return jsxRuntime.jsx(Provider$2, { children: children });
1001
1142
  }
1002
1143
  function useFormContext() {
1003
- return useStore$1(store => store);
1144
+ const [formContext, setFormContext] = useStore$2(store => store);
1145
+ const registerField = React.useCallback((field) => {
1146
+ setFormContext?.(prevContext => {
1147
+ const otherFields = (prevContext || []).filter(otherField => otherField.id !== field.id);
1148
+ return [...otherFields, field];
1149
+ });
1150
+ }, []);
1151
+ const removeField = React.useCallback((fieldID) => {
1152
+ setFormContext?.(prevContext => (prevContext || []).filter(field => field.id !== fieldID));
1153
+ }, []);
1154
+ const updateField = React.useCallback((fieldID, updates) => {
1155
+ setFormContext?.(prevContext => {
1156
+ const field = prevContext.find(({ id }) => id === fieldID);
1157
+ if (!field)
1158
+ throw new Error(`Field with id "${fieldID}" not found in form context.`);
1159
+ const otherFields = prevContext.filter(({ id }) => id !== fieldID);
1160
+ const updatedField = { ...field, ...updates };
1161
+ return [...otherFields, updatedField];
1162
+ });
1163
+ }, []);
1164
+ return [formContext, { registerField, removeField, updateField }];
1165
+ }
1166
+
1167
+ const { Provider: Provider$1, useStore: useStore$1 } = createFastContext(undefined);
1168
+ function FieldsetContextProvider({ children, initialValue, }) {
1169
+ return jsxRuntime.jsx(Provider$1, { initialValue: initialValue, children: children });
1170
+ }
1171
+ function useFieldsetContext() {
1172
+ const [fieldsetContext, setFieldsetContext] = useStore$1(store => store), [, formContextFunctions] = useFormContext();
1173
+ const registerField = React.useCallback((field) => {
1174
+ setFieldsetContext?.(prev => {
1175
+ if (!prev)
1176
+ return prev;
1177
+ const existingFieldIndex = prev.fieldList.findIndex(existingField => existingField.id === field.id);
1178
+ const newFieldList = existingFieldIndex >= 0
1179
+ ? prev.fieldList.map((existingField, index) => (index === existingFieldIndex ? field : existingField))
1180
+ : [...prev.fieldList, field];
1181
+ return { ...prev, fieldList: newFieldList };
1182
+ });
1183
+ }, []);
1184
+ const removeField = React.useCallback((fieldID) => {
1185
+ setFieldsetContext?.(prev => {
1186
+ if (!prev)
1187
+ return prev;
1188
+ return {
1189
+ ...prev,
1190
+ fieldList: prev.fieldList.filter(field => field.id !== fieldID),
1191
+ };
1192
+ });
1193
+ }, []);
1194
+ const updateField = React.useCallback((fieldID, updates) => {
1195
+ setFieldsetContext?.(prev => {
1196
+ if (!prev)
1197
+ return prev;
1198
+ return {
1199
+ ...prev,
1200
+ fieldList: prev.fieldList.map(field => (field.id === fieldID ? { ...field, ...updates } : field)),
1201
+ };
1202
+ });
1203
+ }, []);
1204
+ const createFormContextEntry = React.useCallback((fieldsetEntry) => {
1205
+ if (fieldsetEntry.join) {
1206
+ const joinedValue = fieldsetEntry.fieldList
1207
+ .filter(field => isStringField(field) && field.value !== '')
1208
+ .map(field => field.value)
1209
+ .join(fieldsetEntry.join);
1210
+ return defineField({
1211
+ type: 'string',
1212
+ id: fieldsetEntry.id,
1213
+ name: fieldsetEntry.name,
1214
+ value: joinedValue,
1215
+ required: fieldsetEntry.fieldList.some(field => field.required),
1216
+ invalid: fieldsetEntry.fieldList.some(field => field.invalid),
1217
+ });
1218
+ }
1219
+ return defineField({
1220
+ type: 'object',
1221
+ id: fieldsetEntry.id,
1222
+ name: fieldsetEntry.name,
1223
+ fields: fieldsetEntry.fieldList,
1224
+ });
1225
+ }, []);
1226
+ React.useEffect(() => {
1227
+ if (!fieldsetContext || fieldsetContext.decorative)
1228
+ return;
1229
+ const formContextEntry = createFormContextEntry(fieldsetContext);
1230
+ formContextFunctions.registerField(formContextEntry);
1231
+ return () => {
1232
+ formContextFunctions.removeField(fieldsetContext.id);
1233
+ };
1234
+ }, [fieldsetContext, formContextFunctions.registerField, formContextFunctions.removeField, createFormContextEntry]);
1235
+ return [fieldsetContext, { registerField, removeField, updateField }];
1004
1236
  }
1005
1237
 
1006
1238
  const DEFAULT_STATUS = 'incomplete';
@@ -1012,9 +1244,18 @@ function useFormStatus() {
1012
1244
  return useStore(store => store);
1013
1245
  }
1014
1246
 
1015
- function Fieldset({ children, className, legend, legendProps, ...props }) {
1247
+ function Fieldset({ children, className, decorative = false, join, legend, legendProps, name, ...props }) {
1248
+ const uniqueID = React.useId();
1249
+ const fieldsetId = toLowerCase(legend || name, [' ', '_']) + '§' + uniqueID;
1016
1250
  const { className: legendClassName, ...restLegendProps } = legendProps || {};
1017
- return (jsxRuntime.jsx(react.Fieldset, { ...props, className: bag => twMerge('contents', typeof className === 'function' ? className(bag) : className), children: bag => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(react.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] })) }));
1251
+ name = legend || name;
1252
+ return (jsxRuntime.jsx(FieldsetContextProvider, { initialValue: {
1253
+ decorative,
1254
+ fieldList: [],
1255
+ id: fieldsetId,
1256
+ join,
1257
+ name,
1258
+ }, children: jsxRuntime.jsx(react.Fieldset, { ...props, name: name, className: bag => twMerge('contents', typeof className === 'function' ? className(bag) : className), children: bag => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [legend && (jsxRuntime.jsx(react.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] })) }) }));
1018
1259
  }
1019
1260
 
1020
1261
  /**
@@ -3206,15 +3447,17 @@ function ArrowSvg({ className, ...props }) {
3206
3447
  return (jsxRuntime.jsxs("svg", { viewBox: '0 0 20 10', className: twMerge('h-2.5 w-5 fill-none', className), ...props, children: [jsxRuntime.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' }), jsxRuntime.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' }), jsxRuntime.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' })] }));
3207
3448
  }
3208
3449
 
3209
- function Input({ checked, className, defaultValue, description, descriptionProps, disabled, fieldProps, invalid = true, label, labelProps, name, onBlur, onChange, placeholder, ref, required = true, type, value, ...props }) {
3210
- const [formContext, setFormContext] = useFormContext(), [errorMessage, setErrorMessage] = React.useState(undefined);
3450
+ const specialCharacterRegex = new RegExp(/[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~€‚ƒ„…†‡‰‹‘’“”•–—™›¡¢£¥§©«¬®°±¶º»¿×÷]/);
3451
+ 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 }) {
3452
+ const [formContext, formContextFunctions] = useFormContext(), [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext(), [errorMessage, setErrorMessage] = React.useState(undefined);
3453
+ if (type === 'password' && !placeholder)
3454
+ placeholder = '••••••••' + (required && !label ? '*' : '');
3211
3455
  if (placeholder === '*')
3212
3456
  placeholder = name + (required && !label ? '*' : '');
3213
3457
  if (label === '*')
3214
3458
  label = name;
3215
3459
  const uniqueID = React.useId(), fieldContextID = toLowerCase(name, [, '_']) + '§' + uniqueID;
3216
- if (Boolean(formContext?.find(field => field.id === fieldContextID)?.invalid))
3217
- invalid = true;
3460
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3218
3461
  const getFieldContextType = () => {
3219
3462
  switch (type) {
3220
3463
  case 'email':
@@ -3232,26 +3475,27 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3232
3475
  }
3233
3476
  };
3234
3477
  const fieldContextType = getFieldContextType();
3235
- const initialFieldContext = defineField({
3236
- type: fieldContextType,
3237
- id: fieldContextID,
3238
- invalid,
3239
- name,
3240
- required,
3241
- value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3242
- });
3478
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3243
3479
  React.useEffect(() => {
3244
- if (!setFormContext)
3245
- return;
3246
- setFormContext(prevContext => {
3247
- const otherFields = (prevContext || []).filter(field => field.id !== initialFieldContext.id);
3248
- return [...otherFields, initialFieldContext];
3480
+ const initialFieldContext = defineField({
3481
+ type: fieldContextType,
3482
+ id: fieldContextID,
3483
+ invalid,
3484
+ name,
3485
+ required,
3486
+ value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3249
3487
  });
3488
+ if (isInFieldset) {
3489
+ fieldsetContextFunctions.registerField(initialFieldContext);
3490
+ return () => {
3491
+ fieldsetContextFunctions.removeField(initialFieldContext.id);
3492
+ };
3493
+ }
3494
+ formContextFunctions.registerField(initialFieldContext);
3250
3495
  return () => {
3251
- setFormContext(prevContext => (prevContext || []).filter(field => field.id !== initialFieldContext.id));
3496
+ formContextFunctions.removeField(initialFieldContext.id);
3252
3497
  };
3253
- }, [setFormContext]);
3254
- const fieldContext = formContext?.find(({ id: fieldID }) => fieldID === initialFieldContext.id) || initialFieldContext;
3498
+ }, [isInFieldset]);
3255
3499
  const validateField = (validValue) => {
3256
3500
  const noValue = !validValue || validValue === '';
3257
3501
  if (!required && noValue)
@@ -3267,15 +3511,86 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3267
3511
  if (!isEmail(validValue))
3268
3512
  errorMessageList.push('This is not a valid email.');
3269
3513
  break;
3514
+ case 'date':
3515
+ const valueAsTime = new Date().getTime();
3516
+ if (min && !(min instanceof Date) && typeof min !== 'number') {
3517
+ if (Array.isArray(min)) {
3518
+ const monthIndex = typeof min[1] === 'number' ? min[1] - 1 : getMonthIndexFromName(min[1]);
3519
+ min = new Date(min[0], monthIndex, min[2]);
3520
+ }
3521
+ else if ('year' in min && 'month' in min && 'day' in min) {
3522
+ const monthIndex = typeof min.month === 'number' ? min.month - 1 : getMonthIndexFromName(min.month);
3523
+ min = new Date(min.year, monthIndex, min.day);
3524
+ }
3525
+ if (valueAsTime < min.getTime())
3526
+ errorMessageList.push(`Value cannot be lower than ${getUserReadableDate(min)}.`);
3527
+ }
3528
+ if (max && !(max instanceof Date) && typeof max !== 'number') {
3529
+ if (Array.isArray(max)) {
3530
+ const monthIndex = typeof max[1] === 'number' ? max[1] - 1 : getMonthIndexFromName(max[1]);
3531
+ max = new Date(max[0], monthIndex, max[2]);
3532
+ }
3533
+ else if ('year' in max && 'month' in max && 'day' in max) {
3534
+ const monthIndex = typeof max.month === 'number' ? max.month - 1 : getMonthIndexFromName(max.month);
3535
+ max = new Date(max.year, monthIndex, max.day);
3536
+ }
3537
+ if (valueAsTime > max.getTime())
3538
+ errorMessageList.push(`Value cannot be higher than ${getUserReadableDate(max)}.`);
3539
+ }
3540
+ break;
3270
3541
  case 'number':
3271
- if (isNaN(Number(validValue)))
3542
+ const valueAsNumber = Number(validValue);
3543
+ if (isNaN(valueAsNumber))
3272
3544
  errorMessageList.push('This is not a valid number.');
3545
+ if (typeof max === 'number' && valueAsNumber > max)
3546
+ errorMessageList.push(`Value cannot be higher than ${max}.`);
3547
+ if (typeof min === 'number' && valueAsNumber < min)
3548
+ errorMessageList.push(`Value cannot be lower than ${min}.`);
3549
+ break;
3550
+ case 'password':
3551
+ if (options) {
3552
+ const { matchPreviousInput, requireLowercaseCharacter, requireNumber, requireSpecialCharacter, requireUppercaseCharacter, } = options;
3553
+ if (matchPreviousInput && formContext && formContext.length >= 2) {
3554
+ if (isInFieldset && fieldsetContext.fieldList.length > 1) {
3555
+ const currentInputIndex = fieldsetContext.fieldList.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3556
+ if (currentInputIndex > 0) {
3557
+ const previousInput = fieldsetContext.fieldList.find((_, index) => index === currentInputIndex - 1);
3558
+ if (previousInput &&
3559
+ isStringField(previousInput) &&
3560
+ previousInput.value !== validValue)
3561
+ errorMessageList.push('Passwords must match.');
3562
+ }
3563
+ }
3564
+ else {
3565
+ const currentInputIndex = formContext.findIndex(({ id: fieldID }) => fieldID === fieldContext?.id);
3566
+ if (currentInputIndex > 0) {
3567
+ const previousInput = formContext.find((_, index) => index === currentInputIndex - 1);
3568
+ if (previousInput &&
3569
+ isStringField(previousInput) &&
3570
+ previousInput.value !== validValue)
3571
+ errorMessageList.push('Passwords must match.');
3572
+ }
3573
+ }
3574
+ }
3575
+ if (requireLowercaseCharacter && !/[a-z]/g.test(validValue))
3576
+ errorMessageList.push('You must include a lowercase character.');
3577
+ if (requireNumber && !/[0-9]/g.test(validValue))
3578
+ errorMessageList.push('You must include a number.');
3579
+ if (requireSpecialCharacter && !specialCharacterRegex.test(validValue))
3580
+ errorMessageList.push('You must include a special character.');
3581
+ if (requireUppercaseCharacter && !/[A-Z]/g.test(validValue))
3582
+ errorMessageList.push('You must include an uppercase character.');
3583
+ }
3273
3584
  break;
3274
3585
  case 'tel':
3275
3586
  if (!isPhoneNumber(validValue))
3276
3587
  errorMessageList.push('This is not a valid phone number.');
3277
3588
  break;
3278
3589
  }
3590
+ if (props.maxLength && validValue.length > Number(props.maxLength))
3591
+ errorMessageList.push(`This may not have more than ${props.maxLength} characters.`);
3592
+ if (props.minLength && validValue.length < Number(props.minLength))
3593
+ errorMessageList.push(`This must have at least ${props.minLength} characters.`);
3279
3594
  if (errorMessageList.length === 0)
3280
3595
  return true;
3281
3596
  setErrorMessage(errorMessageList.join(' '));
@@ -3287,19 +3602,15 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3287
3602
  return;
3288
3603
  }
3289
3604
  const { currentTarget } = e, { value: newValue } = currentTarget;
3290
- setFormContext?.(prevContext => {
3291
- if (!prevContext)
3292
- return [];
3293
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3294
- if (!field)
3295
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3296
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3297
- const updatedField = { ...field, value: newValue };
3298
- const invalidField = validateField(newValue) === false;
3299
- if (invalidField !== field.invalid)
3300
- updatedField.invalid = invalidField;
3301
- return [...otherFields, updatedField];
3302
- });
3605
+ if (isInFieldset) {
3606
+ fieldsetContextFunctions.updateField(fieldContextID, {
3607
+ value: newValue,
3608
+ invalid: validateField(newValue) === false,
3609
+ });
3610
+ }
3611
+ else {
3612
+ formContextFunctions.updateField(fieldContextID, { value: newValue, invalid: validateField(newValue) === false });
3613
+ }
3303
3614
  onChange?.(e);
3304
3615
  };
3305
3616
  const handleBlur = e => {
@@ -3310,32 +3621,21 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3310
3621
  const { currentTarget } = e, { value: newValue } = currentTarget;
3311
3622
  if (required)
3312
3623
  validateField(newValue);
3624
+ let processedValue = newValue;
3313
3625
  switch (type) {
3314
3626
  case 'email':
3315
- setFormContext?.(prevContext => {
3316
- if (!prevContext)
3317
- return [];
3318
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3319
- if (!field)
3320
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3321
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3322
- const updatedField = { ...field, value: newValue.toLowerCase() };
3323
- return [...otherFields, updatedField];
3324
- });
3627
+ processedValue = newValue.toLowerCase();
3325
3628
  break;
3326
3629
  case 'tel':
3327
- setFormContext?.(prevContext => {
3328
- if (!prevContext)
3329
- return [];
3330
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3331
- if (!field)
3332
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3333
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3334
- const updatedField = { ...field, value: formatPhoneNumber(newValue, '1') };
3335
- return [...otherFields, updatedField];
3336
- });
3630
+ processedValue = formatPhoneNumber(newValue, options);
3337
3631
  break;
3338
3632
  }
3633
+ if (isInFieldset) {
3634
+ fieldsetContextFunctions.updateField(fieldContextID, { value: processedValue });
3635
+ }
3636
+ else {
3637
+ formContextFunctions.updateField(fieldContextID, { value: processedValue });
3638
+ }
3339
3639
  onBlur?.(e);
3340
3640
  };
3341
3641
  const restFieldProps = fieldProps
@@ -3355,7 +3655,7 @@ function Input({ checked, className, defaultValue, description, descriptionProps
3355
3655
  // user-invalid styles
3356
3656
  '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))]',
3357
3657
  // Custom styles
3358
- typeof className === 'function' ? className(bag) : className), invalid: invalid, onBlur: handleBlur, onChange: handleChange, placeholder: placeholder, ref: ref, required: required, type: type, value: fieldContext?.value }), fieldContext.invalid && errorMessage && (jsxRuntime.jsxs(Tooltip, { anchor: 'top-end', arrow: true, portal: true, children: [jsxRuntime.jsx(TooltipTrigger, { as: Button, className: 'absolute top-1.25 right-1.25 z-10 size-6 min-w-0', padding: 'none', rounded: 'md', theme: 'red', children: jsxRuntime.jsx(ExclamationmarkOctagon, { className: 'absolute top-1/2 left-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-70' }) }), jsxRuntime.jsx(TooltipPanel, { children: errorMessage })] }))] }), description && (jsxRuntime.jsx(react.Description, { ...restDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
3658
+ typeof className === 'function' ? className(bag) : className), invalid: invalid, onBlur: handleBlur, onChange: handleChange, placeholder: placeholder, ref: ref, required: required, type: type, value: fieldContext?.value || '' }), fieldContext?.invalid && errorMessage && (jsxRuntime.jsxs(Tooltip, { anchor: 'top-end', arrow: true, portal: true, children: [jsxRuntime.jsx(TooltipTrigger, { as: Button, className: 'absolute top-1.25 right-1.25 z-10 size-6 min-w-0', padding: 'none', rounded: 'md', theme: 'red', children: jsxRuntime.jsx(ExclamationmarkOctagon, { className: 'absolute top-1/2 left-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-70' }) }), jsxRuntime.jsx(TooltipPanel, { children: errorMessage })] }))] }), description && (jsxRuntime.jsx(react.Description, { ...restDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
3359
3659
  ? descriptionProps?.className(bag)
3360
3660
  : descriptionProps?.className), children: description }))] }));
3361
3661
  }
@@ -3414,34 +3714,37 @@ function SubmitButton({ children, className, customTheme, error, incomplete, loa
3414
3714
  }
3415
3715
 
3416
3716
  function Textarea({ className, defaultValue, description, descriptionProps, disabled, fieldProps, invalid = true, label, labelProps, name, onBlur, onChange, placeholder, ref, required = true, value, ...props }) {
3417
- const [formContext, setFormContext] = useFormContext(), [errorMessage, setErrorMessage] = React.useState(undefined);
3717
+ const [formContext, formContextFunctions] = useFormContext();
3718
+ const [fieldsetContext, fieldsetContextFunctions] = useFieldsetContext();
3719
+ const [errorMessage, setErrorMessage] = React.useState(undefined);
3418
3720
  if (placeholder === '*')
3419
3721
  placeholder = name + (required && !label ? '*' : '');
3420
3722
  if (label === '*')
3421
3723
  label = name;
3422
- const uniqueID = React.useId(), fieldContextID = toLowerCase(name, [, '_']) + '§' + uniqueID;
3423
- if (Boolean(formContext?.find(field => field.id === fieldContextID)?.invalid))
3424
- invalid = true;
3425
- const initialFieldContext = defineField({
3426
- type: 'textarea',
3427
- id: fieldContextID,
3428
- invalid,
3429
- name,
3430
- required,
3431
- value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3432
- });
3724
+ const uniqueID = React.useId();
3725
+ const fieldContextID = toLowerCase(name, [' ', '_']) + '§' + uniqueID;
3726
+ const isInFieldset = fieldsetContext && !fieldsetContext.decorative;
3433
3727
  React.useEffect(() => {
3434
- if (!setFormContext)
3435
- return;
3436
- setFormContext(prevContext => {
3437
- const otherFields = (prevContext || []).filter(field => field.id !== initialFieldContext.id);
3438
- return [...otherFields, initialFieldContext];
3728
+ const initialFieldContext = defineField({
3729
+ type: 'textarea',
3730
+ id: fieldContextID,
3731
+ invalid,
3732
+ name,
3733
+ required,
3734
+ value: value ? `${value}` : defaultValue ? `${defaultValue}` : '',
3439
3735
  });
3736
+ if (isInFieldset) {
3737
+ fieldsetContextFunctions.registerField(initialFieldContext);
3738
+ return () => {
3739
+ fieldsetContextFunctions.removeField(initialFieldContext.id);
3740
+ };
3741
+ }
3742
+ formContextFunctions.registerField(initialFieldContext);
3440
3743
  return () => {
3441
- setFormContext(prevContext => (prevContext || []).filter(field => field.id !== initialFieldContext.id));
3744
+ formContextFunctions.removeField(initialFieldContext.id);
3442
3745
  };
3443
- }, [setFormContext]);
3444
- const fieldContext = formContext?.find(({ id: fieldID }) => fieldID === initialFieldContext.id) || initialFieldContext;
3746
+ }, [isInFieldset]);
3747
+ const fieldContext = (isInFieldset ? fieldsetContext.fieldList : formContext)?.find(({ id: fieldID }) => fieldID === fieldContextID);
3445
3748
  const validateField = (validValue) => {
3446
3749
  const noValue = !validValue || validValue === '';
3447
3750
  if (!required && noValue)
@@ -3452,6 +3755,10 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
3452
3755
  setErrorMessage(errorMessageList.join(' '));
3453
3756
  return false;
3454
3757
  }
3758
+ if (props.maxLength && validValue.length > Number(props.maxLength))
3759
+ errorMessageList.push(`This may not have more than ${props.maxLength} characters.`);
3760
+ if (props.minLength && validValue.length < Number(props.minLength))
3761
+ errorMessageList.push(`This must have at least ${props.minLength} characters.`);
3455
3762
  if (errorMessageList.length === 0)
3456
3763
  return true;
3457
3764
  setErrorMessage(errorMessageList.join(' '));
@@ -3462,22 +3769,41 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
3462
3769
  e.preventDefault();
3463
3770
  return;
3464
3771
  }
3465
- const { currentTarget } = e, { value: newValue } = currentTarget;
3466
- setFormContext?.(prevContext => {
3467
- if (!prevContext)
3468
- return [];
3469
- const field = prevContext.find(({ id: fieldID }) => fieldID === initialFieldContext.id);
3470
- if (!field)
3471
- throw new Error(`Field with id "${initialFieldContext.id}" not found in form context.`);
3472
- const otherFields = prevContext.filter(({ id: fieldID }) => fieldID !== initialFieldContext.id);
3473
- const updatedField = { ...field, value: newValue };
3474
- const invalidField = validateField(newValue) === false;
3475
- if (invalidField !== field.invalid)
3476
- updatedField.invalid = invalidField;
3477
- return [...otherFields, updatedField];
3478
- });
3772
+ const { currentTarget } = e;
3773
+ const { value: newValue } = currentTarget;
3774
+ if (isInFieldset) {
3775
+ fieldsetContextFunctions.updateField(fieldContextID, {
3776
+ value: newValue,
3777
+ invalid: validateField(newValue) === false,
3778
+ });
3779
+ }
3780
+ else {
3781
+ formContextFunctions.updateField(fieldContextID, {
3782
+ value: newValue,
3783
+ invalid: validateField(newValue) === false,
3784
+ });
3785
+ }
3479
3786
  onChange?.(e);
3480
3787
  };
3788
+ const handleBlur = e => {
3789
+ if (disabled) {
3790
+ e.preventDefault();
3791
+ return;
3792
+ }
3793
+ const { currentTarget } = e;
3794
+ const { value: newValue } = currentTarget;
3795
+ if (required)
3796
+ validateField(newValue);
3797
+ // No special processing needed for textarea like email/phone formatting
3798
+ const processedValue = newValue;
3799
+ if (isInFieldset) {
3800
+ fieldsetContextFunctions.updateField(fieldContextID, { value: processedValue });
3801
+ }
3802
+ else {
3803
+ formContextFunctions.updateField(fieldContextID, { value: processedValue });
3804
+ }
3805
+ onBlur?.(e);
3806
+ };
3481
3807
  const restFieldProps = fieldProps
3482
3808
  ? Object.fromEntries(Object.entries(fieldProps).filter(([key]) => key !== 'className'))
3483
3809
  : {};
@@ -3495,23 +3821,34 @@ function Textarea({ className, defaultValue, description, descriptionProps, disa
3495
3821
  // user-invalid styles
3496
3822
  '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))]',
3497
3823
  // Custom styles
3498
- 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 && (jsxRuntime.jsxs(Tooltip, { anchor: 'top-end', arrow: true, portal: true, children: [jsxRuntime.jsx(TooltipTrigger, { as: Button, className: 'absolute top-1.25 right-1.25 z-10 size-6 min-w-0', padding: 'none', rounded: 'md', theme: 'red', children: jsxRuntime.jsx(ExclamationmarkOctagon, { className: 'absolute top-1/2 left-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-70' }) }), jsxRuntime.jsx(TooltipPanel, { children: errorMessage })] }))] }), description && (jsxRuntime.jsx(react.Description, { ...restDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
3824
+ 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 && (jsxRuntime.jsxs(Tooltip, { anchor: 'top-end', arrow: true, portal: true, children: [jsxRuntime.jsx(TooltipTrigger, { as: Button, className: 'absolute top-1.25 right-1.25 z-10 size-6 min-w-0', padding: 'none', rounded: 'md', theme: 'red', children: jsxRuntime.jsx(ExclamationmarkOctagon, { className: 'absolute top-1/2 left-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-70' }) }), jsxRuntime.jsx(TooltipPanel, { children: errorMessage })] }))] }), description && (jsxRuntime.jsx(react.Description, { ...restDescriptionProps, className: bag => twMerge('text-xs', typeof descriptionProps?.className === 'function'
3499
3825
  ? descriptionProps?.className(bag)
3500
3826
  : descriptionProps?.className), children: description }))] }));
3501
3827
  }
3502
3828
 
3503
- // import { findComponentByType } from '../../utils'
3504
3829
  function FormComponent({ as, children, className, handleSubmit, onError, onSubmit, onSuccess, ...props }) {
3505
3830
  const [formContext] = useFormContext(), [formStatus, setFormStatus] = useFormStatus();
3506
- // const submitButton = findComponentByType(children, SubmitButton)
3831
+ const checkField = React.useCallback((field) => {
3832
+ if (field.type !== 'array' && field.type !== 'object' && !field.invalid)
3833
+ return true;
3834
+ if (field.type === 'object')
3835
+ return field.fields.every(objectField => checkField(objectField));
3836
+ return false;
3837
+ }, []);
3838
+ const everyFieldIsValid = React.useCallback(() => {
3839
+ if (!formContext)
3840
+ return false;
3841
+ return formContext.every(field => checkField(field));
3842
+ }, [formContext, checkField]);
3507
3843
  React.useEffect(() => {
3508
3844
  if (!formContext)
3509
3845
  return;
3510
- if (formStatus !== 'incomplete' && formContext.find(({ invalid }) => invalid))
3846
+ if (formStatus !== 'incomplete' &&
3847
+ formContext.find(context => context.type !== 'array' && context.type !== 'object' && context.invalid))
3511
3848
  setFormStatus?.('incomplete');
3512
- if (formStatus !== 'ready' && formContext.every(({ invalid }) => !invalid))
3849
+ if (formStatus !== 'ready' && everyFieldIsValid())
3513
3850
  setFormStatus?.('ready');
3514
- }, [formContext]);
3851
+ }, [formContext, everyFieldIsValid]);
3515
3852
  const processSubmit = handleSubmit ||
3516
3853
  (async (e) => {
3517
3854
  e.preventDefault();
@@ -3584,9 +3921,16 @@ function ModalTrigger({ as, ...props }) {
3584
3921
  const Element = as || react.Button;
3585
3922
  return jsxRuntime.jsx(Element, { ...props });
3586
3923
  }
3924
+ function ModalTitle(props) {
3925
+ return jsxRuntime.jsx(react.DialogTitle, { ...props });
3926
+ }
3587
3927
  function ModalDialog(props) {
3588
3928
  return jsxRuntime.jsx("div", { ...props });
3589
3929
  }
3930
+ function ModalClose({ as, ...props }) {
3931
+ const Element = as || react.Button;
3932
+ return jsxRuntime.jsx(Element, { ...props });
3933
+ }
3590
3934
  function Modal({ children, className, onClose, onOpen, place = 'bottom' }) {
3591
3935
  const [bodyElement, setBodyElement] = React.useState(null);
3592
3936
  React.useEffect(() => {
@@ -3770,7 +4114,9 @@ exports.Heading = Heading;
3770
4114
  exports.Input = Input;
3771
4115
  exports.Link = Link;
3772
4116
  exports.Modal = Modal;
4117
+ exports.ModalClose = ModalClose;
3773
4118
  exports.ModalDialog = ModalDialog;
4119
+ exports.ModalTitle = ModalTitle;
3774
4120
  exports.ModalTrigger = ModalTrigger;
3775
4121
  exports.SubmitButton = SubmitButton;
3776
4122
  exports.Textarea = Textarea;