@bsol-oss/react-datatable5 13.0.1-beta.3 → 13.0.1-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -30,8 +30,8 @@ import addFormats from 'ajv-formats';
30
30
  import dayjs from 'dayjs';
31
31
  import utc from 'dayjs/plugin/utc';
32
32
  import timezone from 'dayjs/plugin/timezone';
33
- import { TiDeleteOutline } from 'react-icons/ti';
34
33
  import customParseFormat from 'dayjs/plugin/customParseFormat';
34
+ import { TiDeleteOutline } from 'react-icons/ti';
35
35
  import { rankItem } from '@tanstack/match-sorter-utils';
36
36
 
37
37
  const DataTableContext = createContext({
@@ -4126,6 +4126,22 @@ const convertAjvErrorsToFieldErrors = (errors, schema) => {
4126
4126
  // Get the schema node for this field to check for custom error messages
4127
4127
  const fieldSchema = getSchemaNodeForField(schema, fieldName);
4128
4128
  const customMessage = fieldSchema?.errorMessages?.[error.keyword];
4129
+ // Debug log when error message is missing
4130
+ if (!customMessage) {
4131
+ console.debug(`[Form Validation] Missing error message for field '${fieldName}' with keyword '${error.keyword}'. Add errorMessages.${error.keyword} to schema for field '${fieldName}'`, {
4132
+ fieldName,
4133
+ keyword: error.keyword,
4134
+ instancePath: error.instancePath,
4135
+ schemaPath: error.schemaPath,
4136
+ params: error.params,
4137
+ fieldSchema: fieldSchema
4138
+ ? {
4139
+ type: fieldSchema.type,
4140
+ errorMessages: fieldSchema.errorMessages,
4141
+ }
4142
+ : undefined,
4143
+ });
4144
+ }
4129
4145
  // Provide helpful fallback message if no custom message is provided
4130
4146
  const fallbackMessage = customMessage ||
4131
4147
  `Missing error message for ${error.keyword}. Add errorMessages.${error.keyword} to schema for field '${fieldName}'`;
@@ -4368,7 +4384,7 @@ function removeIndex(str) {
4368
4384
  *
4369
4385
  * @param column - The column name
4370
4386
  * @param prefix - The prefix for the field (usually empty string or parent path)
4371
- * @param schema - Optional schema object with title property
4387
+ * @param schema - Required schema object with title property
4372
4388
  * @returns Object with label helper functions
4373
4389
  *
4374
4390
  * @example
@@ -4401,9 +4417,21 @@ const useFormI18n = (column, prefix = '', schema) => {
4401
4417
  * Uses schema.title if available, otherwise: translate.t(removeIndex(`${colLabel}.field_label`))
4402
4418
  */
4403
4419
  label: (options) => {
4404
- if (schema?.title) {
4420
+ if (schema.title) {
4405
4421
  return schema.title;
4406
4422
  }
4423
+ // Debug log when field title is missing
4424
+ console.debug(`[Form Field Label] Missing title for field '${colLabel}'. Add title property to schema for field '${colLabel}'.`, {
4425
+ fieldName: column,
4426
+ colLabel,
4427
+ prefix,
4428
+ schema: {
4429
+ type: schema.type,
4430
+ errorMessages: schema.errorMessages
4431
+ ? Object.keys(schema.errorMessages)
4432
+ : undefined,
4433
+ },
4434
+ });
4407
4435
  return translate.t(removeIndex(`${colLabel}.field_label`), options);
4408
4436
  },
4409
4437
  /**
@@ -4497,6 +4525,9 @@ const CustomInput = ({ column, schema, prefix }) => {
4497
4525
  }));
4498
4526
  };
4499
4527
 
4528
+ dayjs.extend(utc);
4529
+ dayjs.extend(timezone);
4530
+ dayjs.extend(customParseFormat);
4500
4531
  const Calendar = ({ calendars, getBackProps, getForwardProps, getDateProps, firstDayOfWeek = 0, }) => {
4501
4532
  const { labels } = useContext(DatePickerContext);
4502
4533
  const { monthNamesShort, weekdayNamesShort, backButtonLabel, forwardButtonLabel, } = labels;
@@ -4566,6 +4597,9 @@ const DatePickerContext = createContext({
4566
4597
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
4567
4598
  backButtonLabel: 'Back',
4568
4599
  forwardButtonLabel: 'Next',
4600
+ todayLabel: 'Today',
4601
+ yesterdayLabel: 'Yesterday',
4602
+ tomorrowLabel: 'Tomorrow',
4569
4603
  },
4570
4604
  });
4571
4605
  const DatePicker$1 = ({ labels = {
@@ -4586,6 +4620,9 @@ const DatePicker$1 = ({ labels = {
4586
4620
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
4587
4621
  backButtonLabel: 'Back',
4588
4622
  forwardButtonLabel: 'Next',
4623
+ todayLabel: 'Today',
4624
+ yesterdayLabel: 'Yesterday',
4625
+ tomorrowLabel: 'Tomorrow',
4589
4626
  }, onDateSelected, selected, firstDayOfWeek, showOutsideDays, date, minDate, maxDate, monthsToDisplay, render, }) => {
4590
4627
  const calendarData = useCalendar({
4591
4628
  onDateSelected,
@@ -4600,9 +4637,164 @@ const DatePicker$1 = ({ labels = {
4600
4637
  return (jsx(DatePickerContext.Provider, { value: { labels }, children: render ? (render(calendarData)) : (jsx(Calendar, { ...calendarData,
4601
4638
  firstDayOfWeek })) }));
4602
4639
  };
4640
+ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
4641
+ monthNamesShort: [
4642
+ 'Jan',
4643
+ 'Feb',
4644
+ 'Mar',
4645
+ 'Apr',
4646
+ 'May',
4647
+ 'Jun',
4648
+ 'Jul',
4649
+ 'Aug',
4650
+ 'Sep',
4651
+ 'Oct',
4652
+ 'Nov',
4653
+ 'Dec',
4654
+ ],
4655
+ weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
4656
+ backButtonLabel: 'Back',
4657
+ forwardButtonLabel: 'Next',
4658
+ todayLabel: 'Today',
4659
+ yesterdayLabel: 'Yesterday',
4660
+ tomorrowLabel: 'Tomorrow',
4661
+ }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, showHelperButtons = true, }) {
4662
+ const [open, setOpen] = useState(false);
4663
+ const [inputValue, setInputValue] = useState('');
4664
+ // Update input value when prop value changes
4665
+ useEffect(() => {
4666
+ if (value) {
4667
+ const formatted = typeof value === 'string'
4668
+ ? dayjs(value).tz(timezone).isValid()
4669
+ ? dayjs(value).tz(timezone).format(displayFormat)
4670
+ : ''
4671
+ : dayjs(value).tz(timezone).format(displayFormat);
4672
+ setInputValue(formatted);
4673
+ }
4674
+ else {
4675
+ setInputValue('');
4676
+ }
4677
+ }, [value, displayFormat, timezone]);
4678
+ // Convert value to Date object for DatePicker
4679
+ const selectedDate = value
4680
+ ? typeof value === 'string'
4681
+ ? dayjs(value).tz(timezone).isValid()
4682
+ ? dayjs(value).tz(timezone).toDate()
4683
+ : new Date()
4684
+ : value
4685
+ : new Date();
4686
+ // Shared function to parse and validate input value
4687
+ const parseAndValidateInput = (inputVal) => {
4688
+ // If empty, clear the value
4689
+ if (!inputVal.trim()) {
4690
+ onChange?.(undefined);
4691
+ setInputValue('');
4692
+ return;
4693
+ }
4694
+ // Try parsing with displayFormat first
4695
+ let parsedDate = dayjs(inputVal, displayFormat, true);
4696
+ // If that fails, try common date formats
4697
+ if (!parsedDate.isValid()) {
4698
+ parsedDate = dayjs(inputVal);
4699
+ }
4700
+ // If still invalid, try parsing with dateFormat
4701
+ if (!parsedDate.isValid()) {
4702
+ parsedDate = dayjs(inputVal, dateFormat, true);
4703
+ }
4704
+ // If valid, check constraints and update
4705
+ if (parsedDate.isValid()) {
4706
+ const dateObj = parsedDate.tz(timezone).toDate();
4707
+ // Check min/max constraints
4708
+ if (minDate && dateObj < minDate) {
4709
+ // Invalid: before minDate, reset to prop value
4710
+ resetToPropValue();
4711
+ return;
4712
+ }
4713
+ if (maxDate && dateObj > maxDate) {
4714
+ // Invalid: after maxDate, reset to prop value
4715
+ resetToPropValue();
4716
+ return;
4717
+ }
4718
+ // Valid date - format and update
4719
+ const formattedDate = parsedDate.tz(timezone).format(dateFormat);
4720
+ const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
4721
+ onChange?.(formattedDate);
4722
+ setInputValue(formattedDisplay);
4723
+ }
4724
+ else {
4725
+ // Invalid date - reset to prop value
4726
+ resetToPropValue();
4727
+ }
4728
+ };
4729
+ // Helper function to reset input to prop value
4730
+ const resetToPropValue = () => {
4731
+ if (value) {
4732
+ const formatted = typeof value === 'string'
4733
+ ? dayjs(value).tz(timezone).isValid()
4734
+ ? dayjs(value).tz(timezone).format(displayFormat)
4735
+ : ''
4736
+ : dayjs(value).tz(timezone).format(displayFormat);
4737
+ setInputValue(formatted);
4738
+ }
4739
+ else {
4740
+ setInputValue('');
4741
+ }
4742
+ };
4743
+ const handleInputChange = (e) => {
4744
+ // Only update the input value, don't parse yet
4745
+ setInputValue(e.target.value);
4746
+ };
4747
+ const handleInputBlur = () => {
4748
+ // Parse and validate when input loses focus
4749
+ parseAndValidateInput(inputValue);
4750
+ };
4751
+ const handleKeyDown = (e) => {
4752
+ // Parse and validate when Enter is pressed
4753
+ if (e.key === 'Enter') {
4754
+ e.preventDefault();
4755
+ parseAndValidateInput(inputValue);
4756
+ }
4757
+ };
4758
+ const handleDateSelected = ({ date }) => {
4759
+ const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
4760
+ onChange?.(formattedDate);
4761
+ setOpen(false);
4762
+ };
4763
+ // Helper function to get dates in the correct timezone
4764
+ const getToday = () => dayjs().tz(timezone).startOf('day').toDate();
4765
+ const getYesterday = () => dayjs().tz(timezone).subtract(1, 'day').startOf('day').toDate();
4766
+ const getTomorrow = () => dayjs().tz(timezone).add(1, 'day').startOf('day').toDate();
4767
+ // Check if a date is within min/max constraints
4768
+ const isDateValid = (date) => {
4769
+ if (minDate) {
4770
+ const minDateStart = dayjs(minDate).tz(timezone).startOf('day').toDate();
4771
+ const dateStart = dayjs(date).tz(timezone).startOf('day').toDate();
4772
+ if (dateStart < minDateStart)
4773
+ return false;
4774
+ }
4775
+ if (maxDate) {
4776
+ const maxDateStart = dayjs(maxDate).tz(timezone).startOf('day').toDate();
4777
+ const dateStart = dayjs(date).tz(timezone).startOf('day').toDate();
4778
+ if (dateStart > maxDateStart)
4779
+ return false;
4780
+ }
4781
+ return true;
4782
+ };
4783
+ const handleHelperButtonClick = (date) => {
4784
+ if (isDateValid(date)) {
4785
+ handleDateSelected({ date });
4786
+ }
4787
+ };
4788
+ const today = getToday();
4789
+ const yesterday = getYesterday();
4790
+ const tomorrow = getTomorrow();
4791
+ const datePickerContent = (jsxs(Grid, { gap: 2, children: [showHelperButtons && (jsxs(Grid, { templateColumns: "repeat(3, 1fr)", gap: 2, children: [jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(yesterday), disabled: !isDateValid(yesterday), children: labels.yesterdayLabel ?? 'Yesterday' }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(today), disabled: !isDateValid(today), children: labels.todayLabel ?? 'Today' }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(tomorrow), disabled: !isDateValid(tomorrow), children: labels.tomorrowLabel ?? 'Tomorrow' })] })), jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay })] }));
4792
+ return (jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(InputGroup, { endElement: jsx(Popover.Trigger, { asChild: true, children: jsx(IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsx(Icon, { children: jsx(MdDateRange, {}) }) }) }), children: jsx(Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }));
4793
+ }
4603
4794
 
4604
4795
  dayjs.extend(utc);
4605
4796
  dayjs.extend(timezone);
4797
+ dayjs.extend(customParseFormat);
4606
4798
  const DatePicker = ({ column, schema, prefix }) => {
4607
4799
  const { watch, formState: { errors }, setValue, } = useFormContext();
4608
4800
  const { timezone, dateTimePickerLabels, insideDialog } = useSchemaContext();
@@ -4611,15 +4803,29 @@ const DatePicker = ({ column, schema, prefix }) => {
4611
4803
  const isRequired = required?.some((columnId) => columnId === column);
4612
4804
  const colLabel = formI18n.colLabel;
4613
4805
  const [open, setOpen] = useState(false);
4806
+ const [inputValue, setInputValue] = useState('');
4614
4807
  const selectedDate = watch(colLabel);
4615
- const displayDate = dayjs(selectedDate)
4616
- .tz(timezone)
4617
- .format(displayDateFormat);
4808
+ // Update input value when form value changes
4809
+ useEffect(() => {
4810
+ if (selectedDate) {
4811
+ const parsedDate = dayjs(selectedDate).tz(timezone);
4812
+ if (parsedDate.isValid()) {
4813
+ const formatted = parsedDate.format(displayDateFormat);
4814
+ setInputValue(formatted);
4815
+ }
4816
+ else {
4817
+ setInputValue('');
4818
+ }
4819
+ }
4820
+ else {
4821
+ setInputValue('');
4822
+ }
4823
+ }, [selectedDate, displayDateFormat, timezone]);
4824
+ // Format and validate existing value
4618
4825
  useEffect(() => {
4619
4826
  try {
4620
4827
  if (selectedDate) {
4621
4828
  // Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
4622
- // For example, parse as UTC:
4623
4829
  const parsedDate = dayjs(selectedDate).tz(timezone);
4624
4830
  if (!parsedDate.isValid())
4625
4831
  return;
@@ -4637,7 +4843,7 @@ const DatePicker = ({ column, schema, prefix }) => {
4637
4843
  catch (e) {
4638
4844
  console.error(e);
4639
4845
  }
4640
- }, [selectedDate, dateFormat, colLabel, setValue]);
4846
+ }, [selectedDate, dateFormat, colLabel, setValue, timezone]);
4641
4847
  const datePickerLabels = {
4642
4848
  monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
4643
4849
  'January',
@@ -4665,14 +4871,92 @@ const DatePicker = ({ column, schema, prefix }) => {
4665
4871
  backButtonLabel: dateTimePickerLabels?.backButtonLabel ?? 'Back',
4666
4872
  forwardButtonLabel: dateTimePickerLabels?.forwardButtonLabel ?? 'Forward',
4667
4873
  };
4668
- const datePickerContent = (jsx(DatePicker$1, { selected: new Date(selectedDate), onDateSelected: ({ date }) => {
4669
- setValue(colLabel, dayjs(date).format(dateFormat));
4670
- setOpen(false);
4671
- }, labels: datePickerLabels }));
4874
+ // Convert value to Date object for DatePicker
4875
+ const selectedDateObj = selectedDate
4876
+ ? dayjs(selectedDate).tz(timezone).isValid()
4877
+ ? dayjs(selectedDate).tz(timezone).toDate()
4878
+ : new Date()
4879
+ : new Date();
4880
+ // Shared function to parse and validate input value
4881
+ const parseAndValidateInput = (inputVal) => {
4882
+ // If empty, clear the value
4883
+ if (!inputVal.trim()) {
4884
+ setValue(colLabel, undefined, {
4885
+ shouldValidate: true,
4886
+ shouldDirty: true,
4887
+ });
4888
+ setInputValue('');
4889
+ return;
4890
+ }
4891
+ // Try parsing with displayDateFormat first
4892
+ let parsedDate = dayjs(inputVal, displayDateFormat, true);
4893
+ // If that fails, try common date formats
4894
+ if (!parsedDate.isValid()) {
4895
+ parsedDate = dayjs(inputVal);
4896
+ }
4897
+ // If still invalid, try parsing with dateFormat
4898
+ if (!parsedDate.isValid()) {
4899
+ parsedDate = dayjs(inputVal, dateFormat, true);
4900
+ }
4901
+ // If valid, format and update
4902
+ if (parsedDate.isValid()) {
4903
+ const formattedDate = parsedDate.tz(timezone).format(dateFormat);
4904
+ const formattedDisplay = parsedDate
4905
+ .tz(timezone)
4906
+ .format(displayDateFormat);
4907
+ setValue(colLabel, formattedDate, {
4908
+ shouldValidate: true,
4909
+ shouldDirty: true,
4910
+ });
4911
+ setInputValue(formattedDisplay);
4912
+ }
4913
+ else {
4914
+ // Invalid date - reset to prop value
4915
+ resetToPropValue();
4916
+ }
4917
+ };
4918
+ // Helper function to reset input to prop value
4919
+ const resetToPropValue = () => {
4920
+ if (selectedDate) {
4921
+ const parsedDate = dayjs(selectedDate).tz(timezone);
4922
+ if (parsedDate.isValid()) {
4923
+ const formatted = parsedDate.format(displayDateFormat);
4924
+ setInputValue(formatted);
4925
+ }
4926
+ else {
4927
+ setInputValue('');
4928
+ }
4929
+ }
4930
+ else {
4931
+ setInputValue('');
4932
+ }
4933
+ };
4934
+ const handleInputChange = (e) => {
4935
+ // Only update the input value, don't parse yet
4936
+ setInputValue(e.target.value);
4937
+ };
4938
+ const handleInputBlur = () => {
4939
+ // Parse and validate when input loses focus
4940
+ parseAndValidateInput(inputValue);
4941
+ };
4942
+ const handleKeyDown = (e) => {
4943
+ // Parse and validate when Enter is pressed
4944
+ if (e.key === 'Enter') {
4945
+ e.preventDefault();
4946
+ parseAndValidateInput(inputValue);
4947
+ }
4948
+ };
4949
+ const handleDateSelected = ({ date }) => {
4950
+ const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
4951
+ setValue(colLabel, formattedDate, {
4952
+ shouldValidate: true,
4953
+ shouldDirty: true,
4954
+ });
4955
+ setOpen(false);
4956
+ };
4957
+ const datePickerContent = (jsx(DatePicker$1, { selected: selectedDateObj, onDateSelected: handleDateSelected, labels: datePickerLabels }));
4672
4958
  return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4673
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
4674
- setOpen(true);
4675
- }, justifyContent: 'start', children: [jsx(MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }) }));
4959
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(InputGroup, { endElement: jsx(Popover.Trigger, { asChild: true, children: jsx(IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsx(Icon, { children: jsx(MdDateRange, {}) }) }) }), children: jsx(Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: formI18n.label(), size: "sm" }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }) }));
4676
4960
  };
4677
4961
 
4678
4962
  dayjs.extend(utc);
@@ -4680,7 +4964,7 @@ dayjs.extend(timezone);
4680
4964
  const DateRangePicker = ({ column, schema, prefix, }) => {
4681
4965
  const { watch, formState: { errors }, setValue, } = useFormContext();
4682
4966
  const { timezone, insideDialog } = useSchemaContext();
4683
- const formI18n = useFormI18n(column, prefix);
4967
+ const formI18n = useFormI18n(column, prefix, schema);
4684
4968
  const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
4685
4969
  const isRequired = required?.some((columnId) => columnId === column);
4686
4970
  const colLabel = formI18n.colLabel;
@@ -5325,7 +5609,7 @@ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, en
5325
5609
  }) })) }))] }));
5326
5610
  };
5327
5611
 
5328
- function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, colLabel, }) {
5612
+ function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, }) {
5329
5613
  const [selectedFile, setSelectedFile] = useState(undefined);
5330
5614
  const [activeTab, setActiveTab] = useState('browse');
5331
5615
  const [uploadingFiles, setUploadingFiles] = useState(new Set());
@@ -5409,7 +5693,7 @@ function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly =
5409
5693
  const FilePicker = ({ column, schema, prefix }) => {
5410
5694
  const { setValue, formState: { errors }, watch, } = useFormContext();
5411
5695
  const { filePickerLabels } = useSchemaContext();
5412
- const formI18n = useFormI18n(column, prefix);
5696
+ const formI18n = useFormI18n(column, prefix, schema);
5413
5697
  const { required, gridColumn = 'span 12', gridRow = 'span 1', type, } = schema;
5414
5698
  const isRequired = required?.some((columnId) => columnId === column);
5415
5699
  const isSingleSelect = type === 'string';
@@ -5485,7 +5769,7 @@ const FilePicker = ({ column, schema, prefix }) => {
5485
5769
  const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5486
5770
  const { setValue, formState: { errors }, watch, } = useFormContext();
5487
5771
  const { filePickerLabels } = useSchemaContext();
5488
- const formI18n = useFormI18n(column, prefix);
5772
+ const formI18n = useFormI18n(column, prefix, schema);
5489
5773
  const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
5490
5774
  const isRequired = required?.some((columnId) => columnId === column);
5491
5775
  const isSingleSelect = type === 'string';
@@ -5580,9 +5864,7 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5580
5864
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5581
5865
  gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsx(VStack, { align: "stretch", gap: 2, children: jsx(Button$1, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
5582
5866
  formI18n.t('browse_library') ??
5583
- 'Browse from Library' }) }), jsx(MediaBrowserDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
5584
- filePickerLabels?.dialogTitle ??
5585
- 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, onUploadFile: onUploadFile, enableUpload: enableUpload, labels: filePickerLabels, colLabel: colLabel }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
5867
+ 'Browse from Library' }) }), jsx(MediaBrowserDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ?? formI18n.label() ?? 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, onUploadFile: onUploadFile, enableUpload: enableUpload, labels: filePickerLabels, colLabel: colLabel }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
5586
5868
  const file = fileMap.get(fileId);
5587
5869
  const isImage = file
5588
5870
  ? /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name)
@@ -5673,7 +5955,8 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5673
5955
  }
5674
5956
  // Use schema's loadInitialValues (required for id-picker)
5675
5957
  if (!loadInitialValues) {
5676
- throw new Error(`loadInitialValues is required in schema for IdPicker field '${column}'.`);
5958
+ console.warn(`loadInitialValues is required in schema for IdPicker field '${column}'. Returning empty idMap.`);
5959
+ return { data: [], count: 0 };
5677
5960
  }
5678
5961
  const result = await loadInitialValues({
5679
5962
  ids: missingIds,
@@ -6405,14 +6688,74 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6405
6688
  }
6406
6689
  }
6407
6690
  }
6408
- return options;
6691
+ // Sort options by time (convert to 24-hour for proper chronological sorting)
6692
+ return options.sort((a, b) => {
6693
+ // Convert 12-hour to 24-hour for comparison
6694
+ let hour24A = a.hour;
6695
+ if (a.meridiem === 'am' && a.hour === 12)
6696
+ hour24A = 0;
6697
+ else if (a.meridiem === 'pm' && a.hour < 12)
6698
+ hour24A = a.hour + 12;
6699
+ let hour24B = b.hour;
6700
+ if (b.meridiem === 'am' && b.hour === 12)
6701
+ hour24B = 0;
6702
+ else if (b.meridiem === 'pm' && b.hour < 12)
6703
+ hour24B = b.hour + 12;
6704
+ // Compare by hour first, then minute
6705
+ if (hour24A !== hour24B) {
6706
+ return hour24A - hour24B;
6707
+ }
6708
+ return a.minute - b.minute;
6709
+ });
6409
6710
  }, [startTime, selectedDate, timezone]);
6410
- const { contains } = useFilter({ sensitivity: 'base' });
6711
+ // itemToString returns only the clean display text (no metadata)
6712
+ const itemToString = useMemo(() => {
6713
+ return (item) => {
6714
+ return item.searchText; // Clean display text only
6715
+ };
6716
+ }, []);
6717
+ // Custom filter function that filters by time and supports 24-hour format input
6718
+ const customTimeFilter = useMemo(() => {
6719
+ return (itemText, filterText) => {
6720
+ if (!filterText) {
6721
+ return true; // Show all items when no filter
6722
+ }
6723
+ const lowerItemText = itemText.toLowerCase();
6724
+ const lowerFilterText = filterText.toLowerCase();
6725
+ // First, try matching against the display text (12-hour format)
6726
+ if (lowerItemText.includes(lowerFilterText)) {
6727
+ return true;
6728
+ }
6729
+ // Find the corresponding item to check 24-hour format matches
6730
+ const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
6731
+ if (!item) {
6732
+ return false;
6733
+ }
6734
+ // Convert item to 24-hour format for matching
6735
+ let hour24 = item.hour;
6736
+ if (item.meridiem === 'am' && item.hour === 12)
6737
+ hour24 = 0;
6738
+ else if (item.meridiem === 'pm' && item.hour < 12)
6739
+ hour24 = item.hour + 12;
6740
+ const hour24Str = hour24.toString().padStart(2, '0');
6741
+ const minuteStr = item.minute.toString().padStart(2, '0');
6742
+ // Check if filterText matches 24-hour format variations
6743
+ const formats = [
6744
+ `${hour24Str}:${minuteStr}`, // "13:30"
6745
+ `${hour24Str}${minuteStr}`, // "1330"
6746
+ hour24Str, // "13"
6747
+ `${hour24}:${minuteStr}`, // "13:30" (without padding)
6748
+ hour24.toString(), // "13" (without padding)
6749
+ ];
6750
+ return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
6751
+ lowerFilterText.includes(format.toLowerCase()));
6752
+ };
6753
+ }, [timeOptions]);
6411
6754
  const { collection, filter } = useListCollection({
6412
6755
  initialItems: timeOptions,
6413
- itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
6756
+ itemToString: itemToString,
6414
6757
  itemToValue: (item) => item.value,
6415
- filter: contains,
6758
+ filter: customTimeFilter,
6416
6759
  });
6417
6760
  // Get current value string for combobox
6418
6761
  const currentValue = useMemo(() => {
@@ -6502,6 +6845,47 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6502
6845
  if (!trimmedValue) {
6503
6846
  return;
6504
6847
  }
6848
+ // Parse 24-hour format first (e.g., "13:30", "14:00", "1330", "1400", "9:05", "905")
6849
+ const timePattern24Hour = /^(\d{1,2}):?(\d{2})$/;
6850
+ const match24Hour = trimmedValue.match(timePattern24Hour);
6851
+ if (match24Hour) {
6852
+ const parsedHour24 = parseInt(match24Hour[1], 10);
6853
+ const parsedMinute = parseInt(match24Hour[2], 10);
6854
+ // Validate 24-hour format ranges
6855
+ if (parsedHour24 >= 0 &&
6856
+ parsedHour24 <= 23 &&
6857
+ parsedMinute >= 0 &&
6858
+ parsedMinute <= 59) {
6859
+ // Convert 24-hour to 12-hour format
6860
+ let hour12;
6861
+ let meridiem;
6862
+ if (parsedHour24 === 0) {
6863
+ hour12 = 12;
6864
+ meridiem = 'am';
6865
+ }
6866
+ else if (parsedHour24 === 12) {
6867
+ hour12 = 12;
6868
+ meridiem = 'pm';
6869
+ }
6870
+ else if (parsedHour24 > 12) {
6871
+ hour12 = parsedHour24 - 12;
6872
+ meridiem = 'pm';
6873
+ }
6874
+ else {
6875
+ hour12 = parsedHour24;
6876
+ meridiem = 'am';
6877
+ }
6878
+ setHour(hour12);
6879
+ setMinute(parsedMinute);
6880
+ setMeridiem(meridiem);
6881
+ onChange({
6882
+ hour: hour12,
6883
+ minute: parsedMinute,
6884
+ meridiem: meridiem,
6885
+ });
6886
+ return;
6887
+ }
6888
+ }
6505
6889
  // Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
6506
6890
  const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
6507
6891
  const match12Hour = trimmedValue.match(timePattern12Hour);
@@ -6667,133 +7051,6 @@ const TimePicker = ({ column, schema, prefix }) => {
6667
7051
  }, justifyContent: 'start', children: [jsx(IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsx(Popover.Body, { overflow: "visible", children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { children: jsx(Popover.Body, { children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) }) }))] }) }));
6668
7052
  };
6669
7053
 
6670
- dayjs.extend(utc);
6671
- dayjs.extend(timezone);
6672
- dayjs.extend(customParseFormat);
6673
- function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
6674
- monthNamesShort: [
6675
- 'Jan',
6676
- 'Feb',
6677
- 'Mar',
6678
- 'Apr',
6679
- 'May',
6680
- 'Jun',
6681
- 'Jul',
6682
- 'Aug',
6683
- 'Sep',
6684
- 'Oct',
6685
- 'Nov',
6686
- 'Dec',
6687
- ],
6688
- weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
6689
- backButtonLabel: 'Back',
6690
- forwardButtonLabel: 'Next',
6691
- }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, }) {
6692
- const [open, setOpen] = useState(false);
6693
- const [inputValue, setInputValue] = useState('');
6694
- // Update input value when prop value changes
6695
- useEffect(() => {
6696
- if (value) {
6697
- const formatted = typeof value === 'string'
6698
- ? dayjs(value).tz(timezone).isValid()
6699
- ? dayjs(value).tz(timezone).format(displayFormat)
6700
- : ''
6701
- : dayjs(value).tz(timezone).format(displayFormat);
6702
- setInputValue(formatted);
6703
- }
6704
- else {
6705
- setInputValue('');
6706
- }
6707
- }, [value, displayFormat, timezone]);
6708
- // Convert value to Date object for DatePicker
6709
- const selectedDate = value
6710
- ? typeof value === 'string'
6711
- ? dayjs(value).tz(timezone).isValid()
6712
- ? dayjs(value).tz(timezone).toDate()
6713
- : new Date()
6714
- : value
6715
- : new Date();
6716
- // Shared function to parse and validate input value
6717
- const parseAndValidateInput = (inputVal) => {
6718
- // If empty, clear the value
6719
- if (!inputVal.trim()) {
6720
- onChange?.(undefined);
6721
- setInputValue('');
6722
- return;
6723
- }
6724
- // Try parsing with displayFormat first
6725
- let parsedDate = dayjs(inputVal, displayFormat, true);
6726
- // If that fails, try common date formats
6727
- if (!parsedDate.isValid()) {
6728
- parsedDate = dayjs(inputVal);
6729
- }
6730
- // If still invalid, try parsing with dateFormat
6731
- if (!parsedDate.isValid()) {
6732
- parsedDate = dayjs(inputVal, dateFormat, true);
6733
- }
6734
- // If valid, check constraints and update
6735
- if (parsedDate.isValid()) {
6736
- const dateObj = parsedDate.tz(timezone).toDate();
6737
- // Check min/max constraints
6738
- if (minDate && dateObj < minDate) {
6739
- // Invalid: before minDate, reset to prop value
6740
- resetToPropValue();
6741
- return;
6742
- }
6743
- if (maxDate && dateObj > maxDate) {
6744
- // Invalid: after maxDate, reset to prop value
6745
- resetToPropValue();
6746
- return;
6747
- }
6748
- // Valid date - format and update
6749
- const formattedDate = parsedDate.tz(timezone).format(dateFormat);
6750
- const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
6751
- onChange?.(formattedDate);
6752
- setInputValue(formattedDisplay);
6753
- }
6754
- else {
6755
- // Invalid date - reset to prop value
6756
- resetToPropValue();
6757
- }
6758
- };
6759
- // Helper function to reset input to prop value
6760
- const resetToPropValue = () => {
6761
- if (value) {
6762
- const formatted = typeof value === 'string'
6763
- ? dayjs(value).tz(timezone).isValid()
6764
- ? dayjs(value).tz(timezone).format(displayFormat)
6765
- : ''
6766
- : dayjs(value).tz(timezone).format(displayFormat);
6767
- setInputValue(formatted);
6768
- }
6769
- else {
6770
- setInputValue('');
6771
- }
6772
- };
6773
- const handleInputChange = (e) => {
6774
- // Only update the input value, don't parse yet
6775
- setInputValue(e.target.value);
6776
- };
6777
- const handleInputBlur = () => {
6778
- // Parse and validate when input loses focus
6779
- parseAndValidateInput(inputValue);
6780
- };
6781
- const handleKeyDown = (e) => {
6782
- // Parse and validate when Enter is pressed
6783
- if (e.key === 'Enter') {
6784
- e.preventDefault();
6785
- parseAndValidateInput(inputValue);
6786
- }
6787
- };
6788
- const handleDateSelected = ({ date }) => {
6789
- const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
6790
- onChange?.(formattedDate);
6791
- setOpen(false);
6792
- };
6793
- const datePickerContent = (jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay }));
6794
- return (jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(InputGroup, { endElement: jsx(Popover.Trigger, { asChild: true, children: jsx(IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsx(Icon, { children: jsx(MdDateRange, {}) }) }) }), children: jsx(Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }));
6795
- }
6796
-
6797
7054
  dayjs.extend(utc);
6798
7055
  dayjs.extend(timezone);
6799
7056
  function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
@@ -7037,7 +7294,20 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
7037
7294
  });
7038
7295
  }
7039
7296
  };
7297
+ const [inputValue, setInputValue] = useState('');
7298
+ // Sync inputValue with currentValue when time changes externally
7299
+ useEffect(() => {
7300
+ if (hour !== null && minute !== null && second !== null) {
7301
+ const formattedValue = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
7302
+ setInputValue(formattedValue);
7303
+ }
7304
+ else {
7305
+ setInputValue('');
7306
+ }
7307
+ }, [hour, minute, second]);
7040
7308
  const handleInputValueChange = (details) => {
7309
+ // Update local input value state
7310
+ setInputValue(details.inputValue);
7041
7311
  // Filter the collection based on input, but don't parse yet
7042
7312
  filter(details.inputValue);
7043
7313
  };
@@ -7047,24 +7317,26 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
7047
7317
  };
7048
7318
  const handleBlur = (e) => {
7049
7319
  // Parse and commit the input value when losing focus
7050
- const inputValue = e.target.value;
7051
- if (inputValue) {
7052
- parseAndCommitInput(inputValue);
7320
+ const inputVal = e.target.value;
7321
+ setInputValue(inputVal);
7322
+ if (inputVal) {
7323
+ parseAndCommitInput(inputVal);
7053
7324
  }
7054
7325
  };
7055
7326
  const handleKeyDown = (e) => {
7056
7327
  // Commit input on Enter key
7057
7328
  if (e.key === 'Enter') {
7058
7329
  e.preventDefault();
7059
- const inputValue = e.currentTarget.value;
7060
- if (inputValue) {
7061
- parseAndCommitInput(inputValue);
7330
+ const inputVal = e.currentTarget.value;
7331
+ setInputValue(inputVal);
7332
+ if (inputVal) {
7333
+ parseAndCommitInput(inputVal);
7062
7334
  }
7063
7335
  // Blur the input
7064
7336
  e.currentTarget?.blur();
7065
7337
  }
7066
7338
  };
7067
- return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: labels.placeholder, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: labels.emptyMessage }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }) }));
7339
+ return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { value: inputValue, placeholder: labels.placeholder, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: labels.emptyMessage }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }) }));
7068
7340
  }
7069
7341
 
7070
7342
  dayjs.extend(utc);
@@ -7087,7 +7359,7 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
7087
7359
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
7088
7360
  backButtonLabel: 'Back',
7089
7361
  forwardButtonLabel: 'Next',
7090
- }, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, }) {
7362
+ }, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, }) {
7091
7363
  console.log('[DateTimePicker] Component initialized with props:', {
7092
7364
  value,
7093
7365
  format,
@@ -7162,13 +7434,77 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
7162
7434
  value,
7163
7435
  initialTime,
7164
7436
  });
7437
+ // Normalize startTime to ignore milliseconds (needed for effectiveDefaultDate calculation)
7438
+ const normalizedStartTime = startTime
7439
+ ? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
7440
+ : undefined;
7441
+ // Calculate effective defaultDate: use prop if provided, otherwise use startTime date, otherwise use today
7442
+ const effectiveDefaultDate = useMemo(() => {
7443
+ if (defaultDate) {
7444
+ return defaultDate;
7445
+ }
7446
+ if (normalizedStartTime &&
7447
+ dayjs(normalizedStartTime).tz(timezone).isValid()) {
7448
+ return dayjs(normalizedStartTime).tz(timezone).format('YYYY-MM-DD');
7449
+ }
7450
+ return dayjs().tz(timezone).format('YYYY-MM-DD');
7451
+ }, [defaultDate, normalizedStartTime, timezone]);
7452
+ // Initialize time with default values if no value is provided
7453
+ const getInitialTimeValues = () => {
7454
+ if (value && initialTime.hour12 !== null) {
7455
+ return initialTime;
7456
+ }
7457
+ // If no value or no time in value, use defaultTime or 00:00
7458
+ if (defaultTime) {
7459
+ if (format === 'iso-date-time') {
7460
+ const defaultTime24 = defaultTime;
7461
+ return {
7462
+ hour12: null,
7463
+ minute: defaultTime24.minute ?? 0,
7464
+ meridiem: null,
7465
+ hour24: defaultTime24.hour ?? 0,
7466
+ second: showSeconds ? defaultTime24.second ?? 0 : null,
7467
+ };
7468
+ }
7469
+ else {
7470
+ const defaultTime12 = defaultTime;
7471
+ return {
7472
+ hour12: defaultTime12.hour ?? 12,
7473
+ minute: defaultTime12.minute ?? 0,
7474
+ meridiem: defaultTime12.meridiem ?? 'am',
7475
+ hour24: null,
7476
+ second: null,
7477
+ };
7478
+ }
7479
+ }
7480
+ // Default to 00:00
7481
+ if (format === 'iso-date-time') {
7482
+ return {
7483
+ hour12: null,
7484
+ minute: 0,
7485
+ meridiem: null,
7486
+ hour24: 0,
7487
+ second: showSeconds ? 0 : null,
7488
+ };
7489
+ }
7490
+ else {
7491
+ return {
7492
+ hour12: 12,
7493
+ minute: 0,
7494
+ meridiem: 'am',
7495
+ hour24: null,
7496
+ second: null,
7497
+ };
7498
+ }
7499
+ };
7500
+ const initialTimeValues = getInitialTimeValues();
7165
7501
  // Time state for 12-hour format
7166
- const [hour12, setHour12] = useState(initialTime.hour12);
7167
- const [minute, setMinute] = useState(initialTime.minute);
7168
- const [meridiem, setMeridiem] = useState(initialTime.meridiem);
7502
+ const [hour12, setHour12] = useState(initialTimeValues.hour12);
7503
+ const [minute, setMinute] = useState(initialTimeValues.minute);
7504
+ const [meridiem, setMeridiem] = useState(initialTimeValues.meridiem);
7169
7505
  // Time state for 24-hour format
7170
- const [hour24, setHour24] = useState(initialTime.hour24);
7171
- const [second, setSecond] = useState(initialTime.second);
7506
+ const [hour24, setHour24] = useState(initialTimeValues.hour24);
7507
+ const [second, setSecond] = useState(initialTimeValues.second);
7172
7508
  // Sync selectedDate and time states when value prop changes
7173
7509
  useEffect(() => {
7174
7510
  console.log('[DateTimePicker] useEffect triggered - value changed:', {
@@ -7176,27 +7512,47 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
7176
7512
  timezone,
7177
7513
  format,
7178
7514
  });
7179
- // If value is null, undefined, or invalid, clear all fields
7515
+ // If value is null, undefined, or invalid, clear date but keep default time values
7180
7516
  if (!value || value === null || value === undefined) {
7181
- console.log('[DateTimePicker] Value is null/undefined, clearing all fields');
7517
+ console.log('[DateTimePicker] Value is null/undefined, clearing date but keeping default time');
7182
7518
  setSelectedDate('');
7183
- setHour12(null);
7184
- setMinute(null);
7185
- setMeridiem(null);
7186
- setHour24(null);
7187
- setSecond(null);
7519
+ // Keep default time values instead of clearing them
7520
+ if (format === 'iso-date-time') {
7521
+ setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
7522
+ setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7523
+ setSecond(showSeconds
7524
+ ? defaultTime
7525
+ ? defaultTime.second ?? 0
7526
+ : 0
7527
+ : null);
7528
+ }
7529
+ else {
7530
+ setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
7531
+ setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7532
+ setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
7533
+ }
7188
7534
  return;
7189
7535
  }
7190
7536
  // Check if value is valid
7191
7537
  const dateObj = dayjs(value).tz(timezone);
7192
7538
  if (!dateObj.isValid()) {
7193
- console.log('[DateTimePicker] Invalid value, clearing all fields');
7539
+ console.log('[DateTimePicker] Invalid value, clearing date but keeping default time');
7194
7540
  setSelectedDate('');
7195
- setHour12(null);
7196
- setMinute(null);
7197
- setMeridiem(null);
7198
- setHour24(null);
7199
- setSecond(null);
7541
+ // Keep default time values instead of clearing them
7542
+ if (format === 'iso-date-time') {
7543
+ setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
7544
+ setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7545
+ setSecond(showSeconds
7546
+ ? defaultTime
7547
+ ? defaultTime.second ?? 0
7548
+ : 0
7549
+ : null);
7550
+ }
7551
+ else {
7552
+ setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
7553
+ setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7554
+ setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
7555
+ }
7200
7556
  return;
7201
7557
  }
7202
7558
  const dateString = getDateString(value);
@@ -7252,16 +7608,76 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
7252
7608
  onChange?.(undefined);
7253
7609
  return;
7254
7610
  }
7611
+ // Check if time values are null - if so, use defaultTime or set to 00:00
7612
+ const hasTimeValues = format === 'iso-date-time'
7613
+ ? hour24 !== null || minute !== null
7614
+ : hour12 !== null || minute !== null || meridiem !== null;
7615
+ let timeDataToUse = undefined;
7616
+ if (!hasTimeValues) {
7617
+ // Use defaultTime if provided, otherwise default to 00:00
7618
+ if (defaultTime) {
7619
+ console.log('[DateTimePicker] No time values set, using defaultTime');
7620
+ if (format === 'iso-date-time') {
7621
+ const defaultTime24 = defaultTime;
7622
+ setHour24(defaultTime24.hour ?? 0);
7623
+ setMinute(defaultTime24.minute ?? 0);
7624
+ if (showSeconds) {
7625
+ setSecond(defaultTime24.second ?? 0);
7626
+ }
7627
+ timeDataToUse = {
7628
+ hour: defaultTime24.hour ?? 0,
7629
+ minute: defaultTime24.minute ?? 0,
7630
+ second: showSeconds ? defaultTime24.second ?? 0 : undefined,
7631
+ };
7632
+ }
7633
+ else {
7634
+ const defaultTime12 = defaultTime;
7635
+ setHour12(defaultTime12.hour ?? 12);
7636
+ setMinute(defaultTime12.minute ?? 0);
7637
+ setMeridiem(defaultTime12.meridiem ?? 'am');
7638
+ timeDataToUse = {
7639
+ hour: defaultTime12.hour ?? 12,
7640
+ minute: defaultTime12.minute ?? 0,
7641
+ meridiem: defaultTime12.meridiem ?? 'am',
7642
+ };
7643
+ }
7644
+ }
7645
+ else {
7646
+ console.log('[DateTimePicker] No time values set, defaulting to 00:00');
7647
+ if (format === 'iso-date-time') {
7648
+ setHour24(0);
7649
+ setMinute(0);
7650
+ if (showSeconds) {
7651
+ setSecond(0);
7652
+ }
7653
+ timeDataToUse = {
7654
+ hour: 0,
7655
+ minute: 0,
7656
+ second: showSeconds ? 0 : undefined,
7657
+ };
7658
+ }
7659
+ else {
7660
+ setHour12(12);
7661
+ setMinute(0);
7662
+ setMeridiem('am');
7663
+ timeDataToUse = {
7664
+ hour: 12,
7665
+ minute: 0,
7666
+ meridiem: 'am',
7667
+ };
7668
+ }
7669
+ }
7670
+ }
7255
7671
  // When showSeconds is false, ignore seconds from the date
7256
7672
  if (!showSeconds) {
7257
7673
  const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
7258
7674
  console.log('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
7259
- updateDateTime(dateWithoutSeconds);
7675
+ updateDateTime(dateWithoutSeconds, timeDataToUse);
7260
7676
  }
7261
7677
  else {
7262
7678
  const dateWithSeconds = dateObj.toISOString();
7263
7679
  console.log('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
7264
- updateDateTime(dateWithSeconds);
7680
+ updateDateTime(dateWithSeconds, timeDataToUse);
7265
7681
  }
7266
7682
  };
7267
7683
  const handleTimeChange = (timeData) => {
@@ -7291,17 +7707,36 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
7291
7707
  setMinute(data.minute);
7292
7708
  setMeridiem(data.meridiem);
7293
7709
  }
7294
- // Use selectedDate if valid, otherwise clear all fields
7710
+ // Use selectedDate if valid, otherwise use effectiveDefaultDate or clear all fields
7295
7711
  if (!selectedDate || !dayjs(selectedDate).isValid()) {
7296
- console.log('[DateTimePicker] No valid selectedDate, clearing all fields');
7297
- setSelectedDate('');
7298
- setHour12(null);
7299
- setMinute(null);
7300
- setMeridiem(null);
7301
- setHour24(null);
7302
- setSecond(null);
7303
- onChange?.(undefined);
7304
- return;
7712
+ // If effectiveDefaultDate is available, use it instead of clearing
7713
+ if (effectiveDefaultDate && dayjs(effectiveDefaultDate).isValid()) {
7714
+ console.log('[DateTimePicker] No valid selectedDate, using effectiveDefaultDate:', effectiveDefaultDate);
7715
+ setSelectedDate(effectiveDefaultDate);
7716
+ const dateObj = dayjs(effectiveDefaultDate).tz(timezone);
7717
+ if (dateObj.isValid()) {
7718
+ updateDateTime(dateObj.toISOString(), timeData);
7719
+ }
7720
+ else {
7721
+ console.warn('[DateTimePicker] Invalid effectiveDefaultDate, clearing fields');
7722
+ setSelectedDate('');
7723
+ setHour12(null);
7724
+ setMinute(null);
7725
+ setMeridiem(null);
7726
+ setHour24(null);
7727
+ setSecond(null);
7728
+ onChange?.(undefined);
7729
+ }
7730
+ return;
7731
+ }
7732
+ else {
7733
+ console.log('[DateTimePicker] No valid selectedDate and no effectiveDefaultDate, keeping time values but no date');
7734
+ // Keep the time values that were just set, but don't set a date
7735
+ // This should rarely happen as effectiveDefaultDate always defaults to today
7736
+ setSelectedDate('');
7737
+ onChange?.(undefined);
7738
+ return;
7739
+ }
7305
7740
  }
7306
7741
  const dateObj = dayjs(selectedDate).tz(timezone);
7307
7742
  if (dateObj.isValid()) {
@@ -7444,18 +7879,24 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
7444
7879
  };
7445
7880
  const handleClear = () => {
7446
7881
  setSelectedDate('');
7447
- setHour12(null);
7448
- setHour24(null);
7449
- setMinute(null);
7450
- setSecond(null);
7451
- setMeridiem(null);
7882
+ // Reset to default time values instead of clearing them
7883
+ if (format === 'iso-date-time') {
7884
+ setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
7885
+ setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7886
+ setSecond(showSeconds
7887
+ ? defaultTime
7888
+ ? defaultTime.second ?? 0
7889
+ : 0
7890
+ : null);
7891
+ }
7892
+ else {
7893
+ setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
7894
+ setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7895
+ setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
7896
+ }
7452
7897
  onChange?.(undefined);
7453
7898
  };
7454
7899
  const isISO = format === 'iso-date-time';
7455
- // Normalize startTime to ignore milliseconds
7456
- const normalizedStartTime = startTime
7457
- ? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
7458
- : undefined;
7459
7900
  // Determine minDate: prioritize explicit minDate prop, then fall back to startTime
7460
7901
  const effectiveMinDate = minDate
7461
7902
  ? minDate
@@ -7533,7 +7974,7 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
7533
7974
  const dateObj = dayjs.tz(selectedDate, timezone);
7534
7975
  return dateObj.isValid() ? dateObj.format('Z') : null;
7535
7976
  }, [selectedDate, timezone]);
7536
- return (jsxs(Flex, { direction: "column", gap: 4, children: [jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
7977
+ return (jsxs(Flex, { direction: "column", gap: 2, children: [jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
7537
7978
  if (date) {
7538
7979
  handleDateChange(date);
7539
7980
  }
@@ -7541,7 +7982,7 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
7541
7982
  setSelectedDate('');
7542
7983
  onChange?.(undefined);
7543
7984
  }
7544
- }, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly: true }), jsxs(Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: showSeconds ? second : null, setSecond: showSeconds ? setSecond : () => { }, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })) : (jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsx(Icon, { as: FaTrash }) })] }), displayText && (jsxs(Flex, { gap: 2, children: [jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: displayText }), timezoneOffset && (jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezoneOffset })), jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
7985
+ }, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly: false }), jsxs(Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 2, children: [isISO ? (jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: showSeconds ? second : null, setSecond: showSeconds ? setSecond : () => { }, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })) : (jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsx(Icon, { as: FaTrash }) })] }), displayText && (jsxs(Flex, { gap: 2, children: [jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: displayText }), timezoneOffset && (jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezoneOffset })), jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
7545
7986
  }
7546
7987
 
7547
7988
  dayjs.extend(utc);
@@ -7603,7 +8044,7 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
7603
8044
  return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
7604
8045
  gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
7605
8046
  setOpen(true);
7606
- }, justifyContent: 'start', children: [jsx(MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
8047
+ }, justifyContent: 'start', children: [jsx(MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
7607
8048
  };
7608
8049
 
7609
8050
  const SchemaRenderer = ({ schema, prefix, column, }) => {
@@ -7756,15 +8197,15 @@ const DateViewer = ({ column, schema, prefix }) => {
7756
8197
 
7757
8198
  const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
7758
8199
  const { watch, formState: { errors }, } = useFormContext();
7759
- const formI18n = useFormI18n(column, prefix);
8200
+ const formI18n = useFormI18n(column, prefix, schema);
7760
8201
  const { required } = schema;
7761
8202
  const isRequired = required?.some((columnId) => columnId === column);
7762
- const { gridColumn = "span 12", gridRow = "span 1", renderDisplay } = schema;
8203
+ const { gridColumn = 'span 12', gridRow = 'span 1', renderDisplay } = schema;
7763
8204
  const colLabel = formI18n.colLabel;
7764
8205
  const watchEnum = watch(colLabel);
7765
8206
  const watchEnums = (watch(colLabel) ?? []);
7766
- return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: "stretch", gridColumn,
7767
- gridRow, children: [isMultiple && (jsx(Flex, { flexFlow: "wrap", gap: 1, children: watchEnums.map((enumValue) => {
8207
+ return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
8208
+ gridRow, children: [isMultiple && (jsx(Flex, { flexFlow: 'wrap', gap: 1, children: watchEnums.map((enumValue) => {
7768
8209
  const item = enumValue;
7769
8210
  if (item === undefined) {
7770
8211
  return jsx(Fragment, { children: "undefined" });
@@ -7772,7 +8213,7 @@ const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
7772
8213
  return (jsx(Tag, { size: "lg", children: !!renderDisplay === true
7773
8214
  ? renderDisplay(item)
7774
8215
  : formI18n.t(item) }, item));
7775
- }) })), !isMultiple && jsx(Text, { children: formI18n.t(watchEnum) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: formI18n.required() }))] }));
8216
+ }) })), !isMultiple && jsx(Text, { children: formI18n.t(watchEnum) }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
7776
8217
  };
7777
8218
 
7778
8219
  const FileViewer = ({ column, schema, prefix }) => {
@@ -8185,6 +8626,17 @@ const FormBody = () => {
8185
8626
 
8186
8627
  const FormTitle = () => {
8187
8628
  const { schema } = useSchemaContext();
8629
+ // Debug log when form title is missing
8630
+ if (!schema.title) {
8631
+ console.debug('[Form Title] Missing title in root schema. Add title property to schema.', {
8632
+ schema: {
8633
+ type: schema.type,
8634
+ properties: schema.properties
8635
+ ? Object.keys(schema.properties)
8636
+ : undefined,
8637
+ },
8638
+ });
8639
+ }
8188
8640
  return jsx(Heading, { children: schema.title ?? 'Form' });
8189
8641
  };
8190
8642