@bsol-oss/react-datatable5 13.0.1-beta.0 → 13.0.1-beta.10

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.js CHANGED
@@ -29,10 +29,10 @@ var reactHookForm = require('react-hook-form');
29
29
  var Ajv = require('ajv');
30
30
  var addFormats = require('ajv-formats');
31
31
  var dayjs = require('dayjs');
32
- var utc = require('dayjs/plugin/utc');
32
+ var customParseFormat = require('dayjs/plugin/customParseFormat');
33
33
  var timezone = require('dayjs/plugin/timezone');
34
+ var utc = require('dayjs/plugin/utc');
34
35
  var ti = require('react-icons/ti');
35
- var customParseFormat = require('dayjs/plugin/customParseFormat');
36
36
  var matchSorterUtils = require('@tanstack/match-sorter-utils');
37
37
 
38
38
  function _interopNamespaceDefault(e) {
@@ -3714,7 +3714,7 @@ const TextWithCopy = ({ text, globalFilter, highlightedText, }) => {
3714
3714
  const displayText = highlightedText !== undefined
3715
3715
  ? highlightedText
3716
3716
  : highlightText$1(textValue, globalFilter);
3717
- return (jsxRuntime.jsxs(react.HStack, { gap: 2, alignItems: "center", children: [jsxRuntime.jsx(react.Text, { as: "span", children: displayText }), jsxRuntime.jsx(react.Clipboard.Root, { value: textValue, children: jsxRuntime.jsx(react.Clipboard.Trigger, { asChild: true, children: jsxRuntime.jsx(react.IconButton, { size: "xs", variant: "ghost", "aria-label": "Copy", fontSize: "1em", children: jsxRuntime.jsx(react.Clipboard.Indicator, { copied: jsxRuntime.jsx(lu.LuCheck, {}), children: jsxRuntime.jsx(lu.LuCopy, {}) }) }) }) })] }));
3717
+ return (jsxRuntime.jsxs(react.HStack, { gap: 2, alignItems: "center", children: [jsxRuntime.jsx(react.Text, { as: "span", children: displayText }), jsxRuntime.jsx(react.Clipboard.Root, { value: textValue, children: jsxRuntime.jsx(react.Clipboard.Trigger, { asChild: true, children: jsxRuntime.jsx(react.IconButton, { size: "2xs", variant: "ghost", "aria-label": "Copy", fontSize: "1em", children: jsxRuntime.jsx(react.Clipboard.Indicator, { copied: jsxRuntime.jsx(lu.LuCheck, {}), children: jsxRuntime.jsx(lu.LuCopy, {}) }) }) }) })] }));
3718
3718
  };
3719
3719
 
3720
3720
  // Helper function to highlight matching text
@@ -4036,7 +4036,6 @@ const getColumns = ({ schema, include = [], ignore = [], width = [], meta = {},
4036
4036
  //@ts-expect-error TODO: find appropriate type
4037
4037
  const SchemaFormContext = React.createContext({
4038
4038
  schema: {},
4039
- serverUrl: '',
4040
4039
  requestUrl: '',
4041
4040
  order: [],
4042
4041
  ignore: [],
@@ -4147,6 +4146,22 @@ const convertAjvErrorsToFieldErrors = (errors, schema) => {
4147
4146
  // Get the schema node for this field to check for custom error messages
4148
4147
  const fieldSchema = getSchemaNodeForField(schema, fieldName);
4149
4148
  const customMessage = fieldSchema?.errorMessages?.[error.keyword];
4149
+ // Debug log when error message is missing
4150
+ if (!customMessage) {
4151
+ console.debug(`[Form Validation] Missing error message for field '${fieldName}' with keyword '${error.keyword}'. Add errorMessages.${error.keyword} to schema for field '${fieldName}'`, {
4152
+ fieldName,
4153
+ keyword: error.keyword,
4154
+ instancePath: error.instancePath,
4155
+ schemaPath: error.schemaPath,
4156
+ params: error.params,
4157
+ fieldSchema: fieldSchema
4158
+ ? {
4159
+ type: fieldSchema.type,
4160
+ errorMessages: fieldSchema.errorMessages,
4161
+ }
4162
+ : undefined,
4163
+ });
4164
+ }
4150
4165
  // Provide helpful fallback message if no custom message is provided
4151
4166
  const fallbackMessage = customMessage ||
4152
4167
  `Missing error message for ${error.keyword}. Add errorMessages.${error.keyword} to schema for field '${fieldName}'`;
@@ -4276,7 +4291,7 @@ const idPickerSanityCheck = (column, foreign_key) => {
4276
4291
  throw new Error(`The key column does not exist in properties of column ${column} when using id-picker.`);
4277
4292
  }
4278
4293
  };
4279
- const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer, customSuccessRenderer, displayConfig = {
4294
+ const FormRoot = ({ schema, idMap, setIdMap, form, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer, customSuccessRenderer, displayConfig = {
4280
4295
  showSubmitButton: true,
4281
4296
  showResetButton: true,
4282
4297
  showTitle: true,
@@ -4317,9 +4332,11 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
4317
4332
  }
4318
4333
  };
4319
4334
  const defaultSubmitPromise = (data) => {
4335
+ if (!requestOptions.url) {
4336
+ throw new Error('requestOptions.url is required when onSubmit is not provided');
4337
+ }
4320
4338
  const options = {
4321
4339
  method: 'POST',
4322
- url: `${serverUrl}`,
4323
4340
  data: clearEmptyString(data),
4324
4341
  ...requestOptions,
4325
4342
  };
@@ -4337,7 +4354,6 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
4337
4354
  };
4338
4355
  return (jsxRuntime.jsx(SchemaFormContext.Provider, { value: {
4339
4356
  schema,
4340
- serverUrl,
4341
4357
  order,
4342
4358
  ignore,
4343
4359
  include,
@@ -4377,39 +4393,31 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
4377
4393
  }, children: jsxRuntime.jsx(reactHookForm.FormProvider, { ...form, children: children }) }));
4378
4394
  };
4379
4395
 
4380
- function removeIndex(str) {
4381
- return str.replace(/\.\d+\./g, ".");
4382
- }
4383
-
4384
4396
  /**
4385
- * Custom hook for form field labels and fallback text.
4386
- * Automatically handles colLabel construction and removeIndex logic.
4387
- * Uses schema.title when available, otherwise falls back to translate function.
4397
+ * Custom hook for form field labels.
4398
+ * Automatically handles colLabel construction.
4399
+ * Uses schema.title for labels and schema.errorMessages for error messages.
4388
4400
  *
4389
4401
  * @param column - The column name
4390
4402
  * @param prefix - The prefix for the field (usually empty string or parent path)
4391
- * @param schema - Optional schema object with title property
4403
+ * @param schema - Required schema object with title and errorMessages properties
4392
4404
  * @returns Object with label helper functions
4393
4405
  *
4394
4406
  * @example
4395
4407
  * ```tsx
4396
4408
  * const formI18n = useFormI18n(column, prefix, schema);
4397
4409
  *
4398
- * // Get field label (prefers schema.title)
4410
+ * // Get field label (from schema.title)
4399
4411
  * <Field label={formI18n.label()} />
4400
4412
  *
4401
- * // Get required error message
4413
+ * // Get required error message (from schema.errorMessages?.required)
4402
4414
  * <Text>{formI18n.required()}</Text>
4403
4415
  *
4404
- * // Get custom text
4405
- * <Text>{formI18n.t('add_more')}</Text>
4406
- *
4407
4416
  * // Access the raw colLabel
4408
4417
  * const colLabel = formI18n.colLabel;
4409
4418
  * ```
4410
4419
  */
4411
4420
  const useFormI18n = (column, prefix = '', schema) => {
4412
- const { translate } = useSchemaContext();
4413
4421
  const colLabel = `${prefix}${column}`;
4414
4422
  return {
4415
4423
  /**
@@ -4417,36 +4425,55 @@ const useFormI18n = (column, prefix = '', schema) => {
4417
4425
  */
4418
4426
  colLabel,
4419
4427
  /**
4420
- * Get the field label from schema title prop, or fall back to translate function
4421
- * Uses schema.title if available, otherwise: translate.t(removeIndex(`${colLabel}.field_label`))
4428
+ * Get the field label from schema title property.
4429
+ * Logs a debug message if title is missing.
4422
4430
  */
4423
- label: (options) => {
4424
- if (schema?.title) {
4431
+ label: () => {
4432
+ if (schema.title) {
4425
4433
  return schema.title;
4426
4434
  }
4427
- return translate.t(removeIndex(`${colLabel}.field_label`), options);
4428
- },
4429
- /**
4430
- * Get the required error message
4431
- * Equivalent to: translate.t(removeIndex(`${colLabel}.field_required`))
4432
- */
4433
- required: (options) => {
4434
- return translate.t(removeIndex(`${colLabel}.field_required`), options);
4435
+ // Debug log when field title is missing
4436
+ console.debug(`[Form Field Label] Missing title for field '${colLabel}'. Add title property to schema for field '${colLabel}'.`, {
4437
+ fieldName: column,
4438
+ colLabel,
4439
+ prefix,
4440
+ schema: {
4441
+ type: schema.type,
4442
+ errorMessages: schema.errorMessages
4443
+ ? Object.keys(schema.errorMessages)
4444
+ : undefined,
4445
+ },
4446
+ });
4447
+ // Return column name as fallback
4448
+ return column;
4435
4449
  },
4436
4450
  /**
4437
- * Get text for any custom key relative to the field
4438
- * Equivalent to: translate.t(removeIndex(`${colLabel}.${key}`))
4439
- *
4440
- * @param key - The key suffix (e.g., 'add_more', 'total', etc.)
4441
- * @param options - Optional options (e.g., defaultValue, interpolation variables)
4451
+ * Get the required error message from schema.errorMessages?.required.
4452
+ * Returns a helpful fallback message if not provided.
4442
4453
  */
4443
- t: (key, options) => {
4444
- return translate.t(removeIndex(`${colLabel}.${key}`), options);
4454
+ required: () => {
4455
+ const errorMessage = schema.errorMessages?.required;
4456
+ if (errorMessage) {
4457
+ return errorMessage;
4458
+ }
4459
+ // Debug log when error message is missing
4460
+ console.debug(`[Form Field Required] Missing error message for required field '${colLabel}'. Add errorMessages.required to schema for field '${colLabel}'.`, {
4461
+ fieldName: column,
4462
+ colLabel,
4463
+ prefix,
4464
+ schema: {
4465
+ type: schema.type,
4466
+ title: schema.title,
4467
+ required: schema.required,
4468
+ hasErrorMessages: !!schema.errorMessages,
4469
+ errorMessageKeys: schema.errorMessages
4470
+ ? Object.keys(schema.errorMessages)
4471
+ : undefined,
4472
+ },
4473
+ });
4474
+ // Return helpful fallback message
4475
+ return `Missing error message for required. Add errorMessages.required to schema for field '${colLabel}'`;
4445
4476
  },
4446
- /**
4447
- * Access to the original translate object for edge cases
4448
- */
4449
- translate,
4450
4477
  };
4451
4478
  };
4452
4479
 
@@ -4521,52 +4548,56 @@ const Calendar = ({ calendars, getBackProps, getForwardProps, getDateProps, firs
4521
4548
  const { labels } = React.useContext(DatePickerContext);
4522
4549
  const { monthNamesShort, weekdayNamesShort, backButtonLabel, forwardButtonLabel, } = labels;
4523
4550
  if (calendars.length) {
4524
- return (jsxRuntime.jsxs(react.Grid, { children: [jsxRuntime.jsxs(react.Grid, { templateColumns: 'repeat(4, auto)', justifyContent: 'center', children: [jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getBackProps({
4525
- calendars,
4526
- offset: 12,
4527
- }), children: '<<' }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getBackProps({ calendars }), children: backButtonLabel }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getForwardProps({ calendars }), children: forwardButtonLabel }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getForwardProps({
4528
- calendars,
4529
- offset: 12,
4530
- }), children: '>>' })] }), jsxRuntime.jsx(react.Grid, { templateColumns: 'repeat(2, auto)', justifyContent: 'center', children: calendars.map((calendar) => (jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsxs(react.Grid, { justifyContent: 'center', children: [monthNamesShort[calendar.month], " ", calendar.year] }), jsxRuntime.jsxs(react.Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: [[0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
4531
- const weekday = (weekdayNum + firstDayOfWeek) % 7;
4532
- return (jsxRuntime.jsx(react.Text, { textAlign: 'center', children: weekdayNamesShort[weekday] }, `${calendar.month}${calendar.year}${weekday}`));
4533
- }), calendar.weeks.map((week, weekIndex) => week.map((dateObj, index) => {
4534
- const key = `${calendar.month}${calendar.year}${weekIndex}${index}`;
4535
- if (!dateObj) {
4536
- return jsxRuntime.jsx(react.Grid, {}, key);
4551
+ return (jsxRuntime.jsx(react.Grid, { children: jsxRuntime.jsx(react.Grid, { templateColumns: 'repeat(2, auto)', justifyContent: 'center', children: calendars.map((calendar) => (jsxRuntime.jsxs(react.Grid, { gap: 2, children: [jsxRuntime.jsxs(react.Grid, { templateColumns: 'repeat(6, auto)', justifyContent: 'center', alignItems: 'center', gap: 2, children: [jsxRuntime.jsx(react.Button, { variant: 'ghost', size: 'sm', colorPalette: 'gray', ...getBackProps({ calendars }), children: '<' }), jsxRuntime.jsx(react.Text, { textAlign: 'center', children: monthNamesShort[calendar.month] }), jsxRuntime.jsx(react.Button, { variant: 'ghost', size: 'sm', colorPalette: 'gray', ...getForwardProps({ calendars }), children: '>' }), jsxRuntime.jsx(react.Button, { variant: 'ghost', size: 'sm', colorPalette: 'gray', ...getBackProps({
4552
+ calendars,
4553
+ offset: 12,
4554
+ }), children: '<' }), jsxRuntime.jsx(react.Text, { textAlign: 'center', children: calendar.year }), jsxRuntime.jsx(react.Button, { variant: 'ghost', size: 'sm', colorPalette: 'gray', ...getForwardProps({
4555
+ calendars,
4556
+ offset: 12,
4557
+ }), children: '>' })] }), jsxRuntime.jsxs(react.Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: [[0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
4558
+ const weekday = (weekdayNum + firstDayOfWeek) % 7;
4559
+ return (jsxRuntime.jsx(react.Text, { textAlign: 'center', children: weekdayNamesShort[weekday] }, `${calendar.month}${calendar.year}${weekday}`));
4560
+ }), calendar.weeks.map((week, weekIndex) => week.map((dateObj, index) => {
4561
+ const key = `${calendar.month}${calendar.year}${weekIndex}${index}`;
4562
+ if (!dateObj) {
4563
+ return jsxRuntime.jsx(react.Grid, {}, key);
4564
+ }
4565
+ const { date, selected, selectable, today, isCurrentMonth, } = dateObj;
4566
+ const getDateColor = ({ today, selected, selectable, }) => {
4567
+ if (!selectable) {
4568
+ return 'gray';
4537
4569
  }
4538
- const { date, selected, selectable, today, isCurrentMonth, } = dateObj;
4539
- const getDateColor = ({ today, selected, selectable, }) => {
4540
- if (!selectable) {
4541
- return 'gray';
4542
- }
4543
- if (selected) {
4544
- return 'blue';
4545
- }
4546
- if (today) {
4547
- return 'green';
4548
- }
4549
- return '';
4550
- };
4551
- const getVariant = ({ today, selected, selectable, }) => {
4552
- if (!selectable) {
4553
- return 'surface';
4554
- }
4555
- if (selected) {
4556
- return 'solid';
4557
- }
4558
- if (today) {
4559
- return 'surface';
4560
- }
4561
- return 'ghost';
4562
- };
4563
- const color = getDateColor({ today, selected, selectable });
4564
- const variant = getVariant({ today, selected, selectable });
4565
- return (jsxRuntime.jsx(react.Button, { variant: variant, colorPalette: color, opacity: isCurrentMonth ? 1 : 0.4, ...getDateProps({ dateObj }), children: selectable ? date.getDate() : 'X' }, key));
4566
- }))] })] }, `${calendar.month}${calendar.year}`))) })] }));
4570
+ if (selected) {
4571
+ return 'blue';
4572
+ }
4573
+ if (today) {
4574
+ return 'green';
4575
+ }
4576
+ return '';
4577
+ };
4578
+ const getVariant = ({ today, selected, selectable, }) => {
4579
+ if (!selectable) {
4580
+ return 'surface';
4581
+ }
4582
+ if (selected) {
4583
+ return 'surface';
4584
+ }
4585
+ if (today) {
4586
+ return 'outline';
4587
+ }
4588
+ return 'ghost';
4589
+ };
4590
+ const color = getDateColor({ today, selected, selectable });
4591
+ const variant = getVariant({ today, selected, selectable });
4592
+ return (jsxRuntime.jsx(react.Button, { variant: variant, colorPalette: color, size: 'xs', opacity: isCurrentMonth ? 1 : 0.4, ...getDateProps({ dateObj }), children: selectable ? date.getDate() : 'X' }, key));
4593
+ }))] })] }, `${calendar.month}${calendar.year}`))) }) }));
4567
4594
  }
4568
4595
  return null;
4569
4596
  };
4597
+
4598
+ dayjs.extend(utc);
4599
+ dayjs.extend(timezone);
4600
+ dayjs.extend(customParseFormat);
4570
4601
  const DatePickerContext = React.createContext({
4571
4602
  labels: {
4572
4603
  monthNamesShort: [
@@ -4586,6 +4617,9 @@ const DatePickerContext = React.createContext({
4586
4617
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
4587
4618
  backButtonLabel: 'Back',
4588
4619
  forwardButtonLabel: 'Next',
4620
+ todayLabel: 'Today',
4621
+ yesterdayLabel: 'Yesterday',
4622
+ tomorrowLabel: 'Tomorrow',
4589
4623
  },
4590
4624
  });
4591
4625
  const DatePicker$1 = ({ labels = {
@@ -4606,6 +4640,9 @@ const DatePicker$1 = ({ labels = {
4606
4640
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
4607
4641
  backButtonLabel: 'Back',
4608
4642
  forwardButtonLabel: 'Next',
4643
+ todayLabel: 'Today',
4644
+ yesterdayLabel: 'Yesterday',
4645
+ tomorrowLabel: 'Tomorrow',
4609
4646
  }, onDateSelected, selected, firstDayOfWeek, showOutsideDays, date, minDate, maxDate, monthsToDisplay, render, }) => {
4610
4647
  const calendarData = useCalendar({
4611
4648
  onDateSelected,
@@ -4620,9 +4657,171 @@ const DatePicker$1 = ({ labels = {
4620
4657
  return (jsxRuntime.jsx(DatePickerContext.Provider, { value: { labels }, children: render ? (render(calendarData)) : (jsxRuntime.jsx(Calendar, { ...calendarData,
4621
4658
  firstDayOfWeek })) }));
4622
4659
  };
4660
+ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
4661
+ monthNamesShort: [
4662
+ 'Jan',
4663
+ 'Feb',
4664
+ 'Mar',
4665
+ 'Apr',
4666
+ 'May',
4667
+ 'Jun',
4668
+ 'Jul',
4669
+ 'Aug',
4670
+ 'Sep',
4671
+ 'Oct',
4672
+ 'Nov',
4673
+ 'Dec',
4674
+ ],
4675
+ weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
4676
+ backButtonLabel: 'Back',
4677
+ forwardButtonLabel: 'Next',
4678
+ todayLabel: 'Today',
4679
+ yesterdayLabel: 'Yesterday',
4680
+ tomorrowLabel: 'Tomorrow',
4681
+ }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, showHelperButtons = true, }) {
4682
+ const [open, setOpen] = React.useState(false);
4683
+ const [inputValue, setInputValue] = React.useState('');
4684
+ // Sync inputValue with value prop changes
4685
+ React.useEffect(() => {
4686
+ if (value) {
4687
+ const formatted = typeof value === 'string'
4688
+ ? dayjs(value).tz(timezone).isValid()
4689
+ ? dayjs(value).tz(timezone).format(displayFormat)
4690
+ : ''
4691
+ : dayjs(value).tz(timezone).format(displayFormat);
4692
+ setInputValue(formatted);
4693
+ }
4694
+ else {
4695
+ setInputValue('');
4696
+ }
4697
+ }, [value, timezone, displayFormat]);
4698
+ // Convert value to Date object for DatePicker
4699
+ const selectedDate = value
4700
+ ? typeof value === 'string'
4701
+ ? dayjs(value).tz(timezone).isValid()
4702
+ ? dayjs(value).tz(timezone).toDate()
4703
+ : new Date()
4704
+ : value
4705
+ : new Date();
4706
+ // Shared function to parse and validate input value
4707
+ const parseAndValidateInput = (inputVal) => {
4708
+ // If empty, clear the value
4709
+ if (!inputVal.trim()) {
4710
+ onChange?.(undefined);
4711
+ setInputValue('');
4712
+ return;
4713
+ }
4714
+ // Try parsing with displayFormat first
4715
+ let parsedDate = dayjs(inputVal, displayFormat, true);
4716
+ // If that fails, try common date formats
4717
+ if (!parsedDate.isValid()) {
4718
+ parsedDate = dayjs(inputVal);
4719
+ }
4720
+ // If still invalid, try parsing with dateFormat
4721
+ if (!parsedDate.isValid()) {
4722
+ parsedDate = dayjs(inputVal, dateFormat, true);
4723
+ }
4724
+ // If valid, check constraints and update
4725
+ if (parsedDate.isValid()) {
4726
+ const dateObj = parsedDate.tz(timezone).toDate();
4727
+ // Check min/max constraints
4728
+ if (minDate && dateObj < minDate) {
4729
+ // Invalid: before minDate, reset to prop value
4730
+ resetToPropValue();
4731
+ return;
4732
+ }
4733
+ if (maxDate && dateObj > maxDate) {
4734
+ // Invalid: after maxDate, reset to prop value
4735
+ resetToPropValue();
4736
+ return;
4737
+ }
4738
+ // Valid date - format and update
4739
+ const formattedDate = parsedDate.tz(timezone).format(dateFormat);
4740
+ const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
4741
+ onChange?.(formattedDate);
4742
+ setInputValue(formattedDisplay);
4743
+ }
4744
+ else {
4745
+ // Invalid date - reset to prop value
4746
+ resetToPropValue();
4747
+ }
4748
+ };
4749
+ // Helper function to reset input to prop value
4750
+ const resetToPropValue = () => {
4751
+ if (value) {
4752
+ const formatted = typeof value === 'string'
4753
+ ? dayjs(value).tz(timezone).isValid()
4754
+ ? dayjs(value).tz(timezone).format(displayFormat)
4755
+ : ''
4756
+ : dayjs(value).tz(timezone).format(displayFormat);
4757
+ setInputValue(formatted);
4758
+ }
4759
+ else {
4760
+ setInputValue('');
4761
+ }
4762
+ };
4763
+ const handleInputChange = (e) => {
4764
+ // Only update the input value, don't parse yet
4765
+ setInputValue(e.target.value);
4766
+ };
4767
+ const handleInputBlur = () => {
4768
+ // Parse and validate when input loses focus
4769
+ parseAndValidateInput(inputValue);
4770
+ };
4771
+ const handleKeyDown = (e) => {
4772
+ // Parse and validate when Enter is pressed
4773
+ if (e.key === 'Enter') {
4774
+ e.preventDefault();
4775
+ parseAndValidateInput(inputValue);
4776
+ }
4777
+ };
4778
+ const handleDateSelected = ({ date }) => {
4779
+ console.debug('[DatePickerInput] handleDateSelected called:', {
4780
+ date: date.toISOString(),
4781
+ timezone,
4782
+ dateFormat,
4783
+ formattedDate: dayjs(date).tz(timezone).format(dateFormat),
4784
+ });
4785
+ const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
4786
+ console.debug('[DatePickerInput] Calling onChange with formatted date:', formattedDate);
4787
+ onChange?.(formattedDate);
4788
+ setOpen(false);
4789
+ };
4790
+ // Helper function to get dates in the correct timezone
4791
+ const getToday = () => dayjs().tz(timezone).startOf('day').toDate();
4792
+ const getYesterday = () => dayjs().tz(timezone).subtract(1, 'day').startOf('day').toDate();
4793
+ const getTomorrow = () => dayjs().tz(timezone).add(1, 'day').startOf('day').toDate();
4794
+ // Check if a date is within min/max constraints
4795
+ const isDateValid = (date) => {
4796
+ if (minDate) {
4797
+ const minDateStart = dayjs(minDate).tz(timezone).startOf('day').toDate();
4798
+ const dateStart = dayjs(date).tz(timezone).startOf('day').toDate();
4799
+ if (dateStart < minDateStart)
4800
+ return false;
4801
+ }
4802
+ if (maxDate) {
4803
+ const maxDateStart = dayjs(maxDate).tz(timezone).startOf('day').toDate();
4804
+ const dateStart = dayjs(date).tz(timezone).startOf('day').toDate();
4805
+ if (dateStart > maxDateStart)
4806
+ return false;
4807
+ }
4808
+ return true;
4809
+ };
4810
+ const handleHelperButtonClick = (date) => {
4811
+ if (isDateValid(date)) {
4812
+ handleDateSelected({ date });
4813
+ }
4814
+ };
4815
+ const today = getToday();
4816
+ const yesterday = getYesterday();
4817
+ const tomorrow = getTomorrow();
4818
+ const datePickerContent = (jsxRuntime.jsxs(react.Grid, { gap: 2, children: [showHelperButtons && (jsxRuntime.jsxs(react.Grid, { templateColumns: "repeat(3, 1fr)", gap: 2, children: [jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(yesterday), disabled: !isDateValid(yesterday), children: labels.yesterdayLabel ?? 'Yesterday' }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(today), disabled: !isDateValid(today), children: labels.todayLabel ?? 'Today' }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(tomorrow), disabled: !isDateValid(tomorrow), children: labels.tomorrowLabel ?? 'Tomorrow' })] })), jsxRuntime.jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay })] }));
4819
+ return (jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(InputGroup, { endElement: jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdDateRange, {}) }) }) }), children: jsxRuntime.jsx(react.Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) }) }))] }));
4820
+ }
4623
4821
 
4624
4822
  dayjs.extend(utc);
4625
4823
  dayjs.extend(timezone);
4824
+ dayjs.extend(customParseFormat);
4626
4825
  const DatePicker = ({ column, schema, prefix }) => {
4627
4826
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
4628
4827
  const { timezone, dateTimePickerLabels, insideDialog } = useSchemaContext();
@@ -4631,15 +4830,29 @@ const DatePicker = ({ column, schema, prefix }) => {
4631
4830
  const isRequired = required?.some((columnId) => columnId === column);
4632
4831
  const colLabel = formI18n.colLabel;
4633
4832
  const [open, setOpen] = React.useState(false);
4833
+ const [inputValue, setInputValue] = React.useState('');
4634
4834
  const selectedDate = watch(colLabel);
4635
- const displayDate = dayjs(selectedDate)
4636
- .tz(timezone)
4637
- .format(displayDateFormat);
4835
+ // Update input value when form value changes
4836
+ React.useEffect(() => {
4837
+ if (selectedDate) {
4838
+ const parsedDate = dayjs(selectedDate).tz(timezone);
4839
+ if (parsedDate.isValid()) {
4840
+ const formatted = parsedDate.format(displayDateFormat);
4841
+ setInputValue(formatted);
4842
+ }
4843
+ else {
4844
+ setInputValue('');
4845
+ }
4846
+ }
4847
+ else {
4848
+ setInputValue('');
4849
+ }
4850
+ }, [selectedDate, displayDateFormat, timezone]);
4851
+ // Format and validate existing value
4638
4852
  React.useEffect(() => {
4639
4853
  try {
4640
4854
  if (selectedDate) {
4641
4855
  // Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
4642
- // For example, parse as UTC:
4643
4856
  const parsedDate = dayjs(selectedDate).tz(timezone);
4644
4857
  if (!parsedDate.isValid())
4645
4858
  return;
@@ -4657,7 +4870,7 @@ const DatePicker = ({ column, schema, prefix }) => {
4657
4870
  catch (e) {
4658
4871
  console.error(e);
4659
4872
  }
4660
- }, [selectedDate, dateFormat, colLabel, setValue]);
4873
+ }, [selectedDate, dateFormat, colLabel, setValue, timezone]);
4661
4874
  const datePickerLabels = {
4662
4875
  monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
4663
4876
  'January',
@@ -4685,14 +4898,92 @@ const DatePicker = ({ column, schema, prefix }) => {
4685
4898
  backButtonLabel: dateTimePickerLabels?.backButtonLabel ?? 'Back',
4686
4899
  forwardButtonLabel: dateTimePickerLabels?.forwardButtonLabel ?? 'Forward',
4687
4900
  };
4688
- const datePickerContent = (jsxRuntime.jsx(DatePicker$1, { selected: new Date(selectedDate), onDateSelected: ({ date }) => {
4689
- setValue(colLabel, dayjs(date).format(dateFormat));
4690
- setOpen(false);
4691
- }, labels: datePickerLabels }));
4901
+ // Convert value to Date object for DatePicker
4902
+ const selectedDateObj = selectedDate
4903
+ ? dayjs(selectedDate).tz(timezone).isValid()
4904
+ ? dayjs(selectedDate).tz(timezone).toDate()
4905
+ : new Date()
4906
+ : new Date();
4907
+ // Shared function to parse and validate input value
4908
+ const parseAndValidateInput = (inputVal) => {
4909
+ // If empty, clear the value
4910
+ if (!inputVal.trim()) {
4911
+ setValue(colLabel, undefined, {
4912
+ shouldValidate: true,
4913
+ shouldDirty: true,
4914
+ });
4915
+ setInputValue('');
4916
+ return;
4917
+ }
4918
+ // Try parsing with displayDateFormat first
4919
+ let parsedDate = dayjs(inputVal, displayDateFormat, true);
4920
+ // If that fails, try common date formats
4921
+ if (!parsedDate.isValid()) {
4922
+ parsedDate = dayjs(inputVal);
4923
+ }
4924
+ // If still invalid, try parsing with dateFormat
4925
+ if (!parsedDate.isValid()) {
4926
+ parsedDate = dayjs(inputVal, dateFormat, true);
4927
+ }
4928
+ // If valid, format and update
4929
+ if (parsedDate.isValid()) {
4930
+ const formattedDate = parsedDate.tz(timezone).format(dateFormat);
4931
+ const formattedDisplay = parsedDate
4932
+ .tz(timezone)
4933
+ .format(displayDateFormat);
4934
+ setValue(colLabel, formattedDate, {
4935
+ shouldValidate: true,
4936
+ shouldDirty: true,
4937
+ });
4938
+ setInputValue(formattedDisplay);
4939
+ }
4940
+ else {
4941
+ // Invalid date - reset to prop value
4942
+ resetToPropValue();
4943
+ }
4944
+ };
4945
+ // Helper function to reset input to prop value
4946
+ const resetToPropValue = () => {
4947
+ if (selectedDate) {
4948
+ const parsedDate = dayjs(selectedDate).tz(timezone);
4949
+ if (parsedDate.isValid()) {
4950
+ const formatted = parsedDate.format(displayDateFormat);
4951
+ setInputValue(formatted);
4952
+ }
4953
+ else {
4954
+ setInputValue('');
4955
+ }
4956
+ }
4957
+ else {
4958
+ setInputValue('');
4959
+ }
4960
+ };
4961
+ const handleInputChange = (e) => {
4962
+ // Only update the input value, don't parse yet
4963
+ setInputValue(e.target.value);
4964
+ };
4965
+ const handleInputBlur = () => {
4966
+ // Parse and validate when input loses focus
4967
+ parseAndValidateInput(inputValue);
4968
+ };
4969
+ const handleKeyDown = (e) => {
4970
+ // Parse and validate when Enter is pressed
4971
+ if (e.key === 'Enter') {
4972
+ e.preventDefault();
4973
+ parseAndValidateInput(inputValue);
4974
+ }
4975
+ };
4976
+ const handleDateSelected = ({ date }) => {
4977
+ const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
4978
+ setValue(colLabel, formattedDate, {
4979
+ shouldValidate: true,
4980
+ shouldDirty: true,
4981
+ });
4982
+ setOpen(false);
4983
+ };
4984
+ const datePickerContent = (jsxRuntime.jsx(DatePicker$1, { selected: selectedDateObj, onDateSelected: handleDateSelected, labels: datePickerLabels }));
4692
4985
  return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4693
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
4694
- setOpen(true);
4695
- }, justifyContent: 'start', children: [jsxRuntime.jsx(md.MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) }) }))] }) }));
4986
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(InputGroup, { endElement: jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdDateRange, {}) }) }) }), children: jsxRuntime.jsx(react.Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: formI18n.label(), size: "sm" }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) }) }))] }) }));
4696
4987
  };
4697
4988
 
4698
4989
  dayjs.extend(utc);
@@ -4700,7 +4991,7 @@ dayjs.extend(timezone);
4700
4991
  const DateRangePicker = ({ column, schema, prefix, }) => {
4701
4992
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
4702
4993
  const { timezone, insideDialog } = useSchemaContext();
4703
- const formI18n = useFormI18n(column, prefix);
4994
+ const formI18n = useFormI18n(column, prefix, schema);
4704
4995
  const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
4705
4996
  const isRequired = required?.some((columnId) => columnId === column);
4706
4997
  const colLabel = formI18n.colLabel;
@@ -4806,27 +5097,82 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
4806
5097
  const watchEnum = watch(colLabel);
4807
5098
  const watchEnums = (watch(colLabel) ?? []);
4808
5099
  const dataList = schema.enum ?? [];
5100
+ // Helper function to render enum value (returns ReactNode)
5101
+ // If renderDisplay is provided, use it; otherwise show the enum string value directly
5102
+ const renderEnumValue = (value) => {
5103
+ if (renderDisplay) {
5104
+ return renderDisplay(value);
5105
+ }
5106
+ // If no renderDisplay provided, show the enum string value directly
5107
+ return value;
5108
+ };
5109
+ // Helper function to get string representation for input display
5110
+ // Converts ReactNode to string for combobox input display
5111
+ const getDisplayString = (value) => {
5112
+ if (renderDisplay) {
5113
+ const rendered = renderDisplay(value);
5114
+ // If renderDisplay returns a string, use it directly
5115
+ if (typeof rendered === 'string') {
5116
+ return rendered;
5117
+ }
5118
+ // If it's a React element, try to extract text content
5119
+ // For now, fallback to the raw value if we can't extract text
5120
+ // In most cases, renderDisplay should return a string or simple element
5121
+ if (typeof rendered === 'object' &&
5122
+ rendered !== null &&
5123
+ 'props' in rendered) {
5124
+ const props = rendered.props;
5125
+ // Try to extract text from React element props
5126
+ if (props?.children) {
5127
+ const children = props.children;
5128
+ if (typeof children === 'string') {
5129
+ return children;
5130
+ }
5131
+ }
5132
+ }
5133
+ // Fallback: use raw value if we can't extract string
5134
+ return value;
5135
+ }
5136
+ return value;
5137
+ };
5138
+ // Debug log when renderDisplay is missing
5139
+ if (!renderDisplay) {
5140
+ console.debug(`[EnumPicker] Missing renderDisplay for field '${colLabel}'. Add renderDisplay function to schema for field '${colLabel}' to provide custom UI rendering. Currently showing enum string values directly.`, {
5141
+ fieldName: column,
5142
+ colLabel,
5143
+ prefix,
5144
+ enumValues: dataList,
5145
+ });
5146
+ }
4809
5147
  // Current value for combobox (array format)
4810
5148
  const currentValue = isMultiple
4811
5149
  ? watchEnums.filter((val) => val != null && val !== '')
4812
5150
  : watchEnum
4813
5151
  ? [watchEnum]
4814
5152
  : [];
4815
- // Transform enum data for combobox collection
5153
+ // Track input focus state for single selection
5154
+ const [isInputFocused, setIsInputFocused] = React.useState(false);
5155
+ // Get the selected value for single selection display
5156
+ const selectedSingleValue = !isMultiple && watchEnum ? watchEnum : null;
5157
+ const selectedSingleRendered = selectedSingleValue
5158
+ ? renderEnumValue(selectedSingleValue)
5159
+ : null;
5160
+ const isSelectedSingleValueString = typeof selectedSingleRendered === 'string';
4816
5161
  const comboboxItems = React.useMemo(() => {
4817
5162
  return dataList.map((item) => ({
4818
- label: !!renderDisplay === true
4819
- ? String(renderDisplay(item))
4820
- : formI18n.t(item),
5163
+ label: item, // Internal: used for search/filtering only
4821
5164
  value: item,
5165
+ raw: item, // Passed to renderEnumValue for UI rendering
5166
+ displayLabel: getDisplayString(item), // Used for input display when selected
4822
5167
  }));
4823
- }, [dataList, renderDisplay, formI18n]);
5168
+ }, [dataList, renderDisplay]);
4824
5169
  // Use filter hook for combobox
4825
5170
  const { contains } = react.useFilter({ sensitivity: 'base' });
4826
5171
  // Create collection for combobox
5172
+ // itemToString uses displayLabel to show rendered display in input when selected
4827
5173
  const { collection, filter } = react.useListCollection({
4828
5174
  initialItems: comboboxItems,
4829
- itemToString: (item) => item.label,
5175
+ itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
4830
5176
  itemToValue: (item) => item.value,
4831
5177
  filter: contains,
4832
5178
  });
@@ -4850,9 +5196,7 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
4850
5196
  setValue(colLabel, details.value);
4851
5197
  }
4852
5198
  }, children: jsxRuntime.jsx(react.HStack, { gap: "6", children: dataList.map((item) => {
4853
- return (jsxRuntime.jsxs(react.RadioGroup.Item, { value: item, children: [jsxRuntime.jsx(react.RadioGroup.ItemHiddenInput, {}), jsxRuntime.jsx(react.RadioGroup.ItemIndicator, {}), jsxRuntime.jsx(react.RadioGroup.ItemText, { children: !!renderDisplay === true
4854
- ? renderDisplay(item)
4855
- : formI18n.t(item) })] }, `${colLabel}-${item}`));
5199
+ return (jsxRuntime.jsxs(react.RadioGroup.Item, { value: item, children: [jsxRuntime.jsx(react.RadioGroup.ItemHiddenInput, {}), jsxRuntime.jsx(react.RadioGroup.ItemIndicator, {}), jsxRuntime.jsx(react.RadioGroup.ItemText, { children: renderEnumValue(item) })] }, `${colLabel}-${item}`));
4856
5200
  }) }) }) }));
4857
5201
  }
4858
5202
  return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
@@ -4863,16 +5207,31 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
4863
5207
  return (jsxRuntime.jsx(Tag, { size: "lg", closable: true, onClick: () => {
4864
5208
  const newValue = currentValue.filter((val) => val !== enumValue);
4865
5209
  setValue(colLabel, newValue);
4866
- }, children: !!renderDisplay === true
4867
- ? renderDisplay(enumValue)
4868
- : formI18n.t(enumValue) }, enumValue));
5210
+ }, children: renderEnumValue(enumValue) }, enumValue));
4869
5211
  }) })), jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: insideDialog
4870
5212
  ? { strategy: 'fixed', hideWhenDetached: true }
4871
- : undefined, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.Combobox.Input, { placeholder: enumPickerLabels?.typeToSearch ?? formI18n.t('type_to_search') }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [!isMultiple && currentValue.length > 0 && (jsxRuntime.jsx(react.Combobox.ClearTrigger, { onClick: () => {
5213
+ : undefined, children: [jsxRuntime.jsxs(react.Combobox.Control, { position: "relative", children: [!isMultiple &&
5214
+ selectedSingleValue &&
5215
+ !isInputFocused &&
5216
+ !isSelectedSingleValueString &&
5217
+ selectedSingleRendered && (jsxRuntime.jsx(react.Box, { position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", pointerEvents: "none", zIndex: 1, maxWidth: "calc(100% - 60px)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: selectedSingleRendered })), jsxRuntime.jsx(react.Combobox.Input, { placeholder: !isMultiple && selectedSingleValue && !isInputFocused
5218
+ ? undefined
5219
+ : enumPickerLabels?.typeToSearch ?? 'Type to search', onFocus: () => setIsInputFocused(true), onBlur: () => setIsInputFocused(false), style: {
5220
+ color: !isMultiple &&
5221
+ selectedSingleValue &&
5222
+ !isInputFocused &&
5223
+ !isSelectedSingleValueString
5224
+ ? 'transparent'
5225
+ : undefined,
5226
+ caretColor: !isMultiple &&
5227
+ selectedSingleValue &&
5228
+ !isInputFocused &&
5229
+ !isSelectedSingleValueString
5230
+ ? 'transparent'
5231
+ : undefined,
5232
+ } }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [!isMultiple && currentValue.length > 0 && (jsxRuntime.jsx(react.Combobox.ClearTrigger, { onClick: () => {
4872
5233
  setValue(colLabel, '');
4873
- } })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }), insideDialog ? (jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [showTotalAndLimit && (jsxRuntime.jsx(react.Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? formI18n.t('total')}: ${collection.items.length}` })), collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ??
4874
- formI18n.t('empty_search_result') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [showTotalAndLimit && (jsxRuntime.jsx(react.Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? formI18n.t('total')}: ${collection.items.length}` })), collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ??
4875
- formI18n.t('empty_search_result') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) }) }))] })] }));
5234
+ } })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }), insideDialog ? (jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [showTotalAndLimit && (jsxRuntime.jsx(react.Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? 'Total'}: ${collection.items.length}` })), collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ?? 'No results found' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: renderEnumValue(item.raw) }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [showTotalAndLimit && (jsxRuntime.jsx(react.Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? 'Total'}: ${collection.items.length}` })), collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ?? 'No results found' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: renderEnumValue(item.raw) }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) }) }))] })] }));
4876
5235
  };
4877
5236
 
4878
5237
  function isEnteringWindow(_ref) {
@@ -5345,7 +5704,7 @@ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, en
5345
5704
  }) })) }))] }));
5346
5705
  };
5347
5706
 
5348
- function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, colLabel, }) {
5707
+ function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, }) {
5349
5708
  const [selectedFile, setSelectedFile] = React.useState(undefined);
5350
5709
  const [activeTab, setActiveTab] = React.useState('browse');
5351
5710
  const [uploadingFiles, setUploadingFiles] = React.useState(new Set());
@@ -5429,7 +5788,7 @@ function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly =
5429
5788
  const FilePicker = ({ column, schema, prefix }) => {
5430
5789
  const { setValue, formState: { errors }, watch, } = reactHookForm.useFormContext();
5431
5790
  const { filePickerLabels } = useSchemaContext();
5432
- const formI18n = useFormI18n(column, prefix);
5791
+ const formI18n = useFormI18n(column, prefix, schema);
5433
5792
  const { required, gridColumn = 'span 12', gridRow = 'span 1', type, } = schema;
5434
5793
  const isRequired = required?.some((columnId) => columnId === column);
5435
5794
  const isSingleSelect = type === 'string';
@@ -5487,7 +5846,7 @@ const FilePicker = ({ column, schema, prefix }) => {
5487
5846
  const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => cur.name === name));
5488
5847
  setValue(colLabel, [...currentFiles, ...newFiles]);
5489
5848
  }
5490
- }, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }) }), jsxRuntime.jsx(react.Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
5849
+ }, placeholder: filePickerLabels?.fileDropzone ?? 'Drop files here' }) }), jsxRuntime.jsx(react.Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
5491
5850
  const fileIdentifier = getFileIdentifier(file, index);
5492
5851
  const fileName = getFileName(file);
5493
5852
  const fileSize = getFileSize(file);
@@ -5505,7 +5864,7 @@ const FilePicker = ({ column, schema, prefix }) => {
5505
5864
  const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5506
5865
  const { setValue, formState: { errors }, watch, } = reactHookForm.useFormContext();
5507
5866
  const { filePickerLabels } = useSchemaContext();
5508
- const formI18n = useFormI18n(column, prefix);
5867
+ const formI18n = useFormI18n(column, prefix, schema);
5509
5868
  const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
5510
5869
  const isRequired = required?.some((columnId) => columnId === column);
5511
5870
  const isSingleSelect = type === 'string';
@@ -5598,11 +5957,7 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5598
5957
  }
5599
5958
  };
5600
5959
  return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5601
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 2, children: jsxRuntime.jsx(react.Button, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
5602
- formI18n.t('browse_library') ??
5603
- 'Browse from Library' }) }), jsxRuntime.jsx(MediaBrowserDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
5604
- filePickerLabels?.dialogTitle ??
5605
- 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, onUploadFile: onUploadFile, enableUpload: enableUpload, labels: filePickerLabels, colLabel: colLabel }), jsxRuntime.jsx(react.Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
5960
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 2, children: jsxRuntime.jsx(react.Button, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ?? 'Browse from Library' }) }), jsxRuntime.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 }), jsxRuntime.jsx(react.Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
5606
5961
  const file = fileMap.get(fileId);
5607
5962
  const isImage = file
5608
5963
  ? /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name)
@@ -5618,43 +5973,51 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5618
5973
  }) })] }));
5619
5974
  };
5620
5975
 
5621
- const getTableData = async ({ serverUrl, in_table, searching = "", where = [], limit = 10, offset = 0, }) => {
5622
- if (serverUrl === undefined || serverUrl.length == 0) {
5623
- throw new Error("The serverUrl is missing");
5624
- }
5625
- if (in_table === undefined || in_table.length == 0) {
5626
- throw new Error("The in_table is missing");
5627
- }
5628
- const options = {
5629
- method: "GET",
5630
- url: `${serverUrl}/api/g/${in_table}`,
5631
- params: {
5632
- searching,
5633
- where,
5634
- limit,
5635
- offset
5636
- },
5637
- };
5638
- try {
5639
- const { data } = await axios.request(options);
5640
- console.log(data);
5641
- return data;
5642
- }
5643
- catch (error) {
5644
- console.error(error);
5645
- throw error;
5646
- }
5647
- };
5648
-
5649
- // Default renderDisplay function that stringifies JSON
5976
+ // Default renderDisplay function that intelligently displays items
5977
+ // If item is an object, tries to find common display fields (name, title, label, etc.)
5978
+ // Otherwise falls back to JSON.stringify
5650
5979
  const defaultRenderDisplay = (item) => {
5980
+ // Check if item is an object (not null, not array, not primitive)
5981
+ if (item !== null &&
5982
+ typeof item === 'object' &&
5983
+ !Array.isArray(item) &&
5984
+ !(item instanceof Date)) {
5985
+ const obj = item;
5986
+ // Try common display fields in order of preference
5987
+ const displayFields = [
5988
+ 'name',
5989
+ 'title',
5990
+ 'label',
5991
+ 'displayName',
5992
+ 'display_name',
5993
+ 'text',
5994
+ 'value',
5995
+ ];
5996
+ for (const field of displayFields) {
5997
+ if (obj[field] !== undefined && obj[field] !== null) {
5998
+ const value = obj[field];
5999
+ // Return the value if it's a string or number, otherwise stringify it
6000
+ if (typeof value === 'string' || typeof value === 'number') {
6001
+ return String(value);
6002
+ }
6003
+ }
6004
+ }
6005
+ // If no display field found, fall back to JSON.stringify
6006
+ return JSON.stringify(item);
6007
+ }
6008
+ // For non-objects (primitives, arrays, dates), use JSON.stringify
5651
6009
  return JSON.stringify(item);
5652
6010
  };
5653
6011
 
5654
6012
  const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5655
6013
  const { watch, getValues, formState: { errors }, setValue, } = reactHookForm.useFormContext();
5656
- const { serverUrl, idMap, setIdMap, idPickerLabels, insideDialog } = useSchemaContext();
5657
- const { renderDisplay, foreign_key } = schema;
6014
+ const { idMap, setIdMap, idPickerLabels, insideDialog } = useSchemaContext();
6015
+ const { renderDisplay, itemToValue: schemaItemToValue, loadInitialValues, foreign_key, variant, } = schema;
6016
+ // loadInitialValues should be provided in schema for id-picker fields
6017
+ // It's used to load the record of the id so the display is human-readable
6018
+ if (variant === 'id-picker' && !loadInitialValues) {
6019
+ console.warn(`loadInitialValues is recommended in schema for IdPicker field '${column}'. Please provide loadInitialValues function in the schema to load records for human-readable display.`);
6020
+ }
5658
6021
  const { table, column: column_ref, customQueryFn, } = foreign_key;
5659
6022
  const [searchText, setSearchText] = React.useState('');
5660
6023
  const [debouncedSearchText, setDebouncedSearchText] = React.useState('');
@@ -5699,94 +6062,58 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5699
6062
  const missingIdsKey = React.useMemo(() => {
5700
6063
  return JSON.stringify([...missingIds].sort());
5701
6064
  }, [missingIds]);
6065
+ // Include idMap state in query key to force refetch when idMap is reset (e.g., on remount from another page)
6066
+ // This ensures the query runs even if React Query has cached data for the same missing IDs
6067
+ const idMapStateKey = React.useMemo(() => {
6068
+ // Create a key based on whether the required IDs are in idMap
6069
+ const hasRequiredIds = currentValue.every((id) => idMap[id]);
6070
+ return hasRequiredIds ? 'complete' : 'incomplete';
6071
+ }, [currentValue, idMap]);
5702
6072
  // Query to fetch initial values that are missing from idMap
5703
6073
  // This query runs automatically when missingIds.length > 0 and updates idMap
5704
6074
  const initialValuesQuery = reactQuery.useQuery({
5705
- queryKey: [`idpicker-initial`, column, missingIdsKey],
6075
+ queryKey: [`idpicker-initial`, column, missingIdsKey, idMapStateKey],
5706
6076
  queryFn: async () => {
5707
6077
  if (missingIds.length === 0) {
5708
6078
  return { data: [], count: 0 };
5709
6079
  }
5710
- if (customQueryFn) {
5711
- const { data, idMap } = await customQueryFn({
5712
- searching: '',
5713
- limit: missingIds.length,
5714
- offset: 0,
5715
- where: [
5716
- {
5717
- id: column_ref,
5718
- value: missingIds.length === 1 ? missingIds[0] : missingIds,
5719
- },
5720
- ],
5721
- });
5722
- setIdMap((state) => {
5723
- return { ...state, ...idMap };
5724
- });
5725
- return data;
6080
+ // Use schema's loadInitialValues (required for id-picker)
6081
+ if (!loadInitialValues) {
6082
+ console.warn(`loadInitialValues is required in schema for IdPicker field '${column}'. Returning empty idMap.`);
6083
+ return { data: [], count: 0 };
5726
6084
  }
5727
- const data = await getTableData({
5728
- serverUrl,
5729
- searching: '',
5730
- in_table: table,
5731
- limit: missingIds.length,
5732
- offset: 0,
5733
- where: [
5734
- {
5735
- id: column_ref,
5736
- value: missingIds.length === 1 ? missingIds[0] : missingIds,
5737
- },
5738
- ],
6085
+ const result = await loadInitialValues({
6086
+ ids: missingIds,
6087
+ foreign_key: foreign_key,
6088
+ setIdMap,
5739
6089
  });
5740
- const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
5741
- return [
5742
- item[column_ref],
5743
- {
5744
- ...item,
5745
- },
5746
- ];
5747
- }));
5748
- setIdMap((state) => {
5749
- return { ...state, ...newMap };
5750
- });
5751
- return data;
6090
+ return result.data;
5752
6091
  },
5753
6092
  enabled: missingIds.length > 0, // Only fetch if there are missing IDs
5754
- staleTime: 300000,
6093
+ staleTime: 0, // Always consider data stale to refetch on remount
6094
+ refetchOnMount: true, // Always refetch when component remounts (e.g., from another page)
6095
+ refetchOnWindowFocus: false, // Don't refetch on window focus
5755
6096
  });
5756
6097
  const { isLoading: isLoadingInitialValues, isFetching: isFetchingInitialValues, } = initialValuesQuery;
5757
6098
  // Query for search results (async loading)
5758
6099
  const query = reactQuery.useQuery({
5759
6100
  queryKey: [`idpicker`, { column, searchText: debouncedSearchText, limit }],
5760
6101
  queryFn: async () => {
5761
- if (customQueryFn) {
5762
- const { data, idMap } = await customQueryFn({
5763
- searching: debouncedSearchText ?? '',
5764
- limit: limit,
5765
- offset: 0,
5766
- });
5767
- setIdMap((state) => {
5768
- return { ...state, ...idMap };
5769
- });
5770
- return data;
6102
+ // customQueryFn is required when serverUrl is not available
6103
+ if (!customQueryFn) {
6104
+ throw new Error(`customQueryFn is required in foreign_key for table ${table}. serverUrl has been removed.`);
5771
6105
  }
5772
- const data = await getTableData({
5773
- serverUrl,
6106
+ const { data, idMap } = await customQueryFn({
5774
6107
  searching: debouncedSearchText ?? '',
5775
- in_table: table,
5776
6108
  limit: limit,
5777
6109
  offset: 0,
5778
6110
  });
5779
- const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
5780
- return [
5781
- item[column_ref],
5782
- {
5783
- ...item,
5784
- },
5785
- ];
5786
- }));
5787
- setIdMap((state) => {
5788
- return { ...state, ...newMap };
5789
- });
6111
+ // Update idMap with returned values
6112
+ if (idMap && Object.keys(idMap).length > 0) {
6113
+ setIdMap((state) => {
6114
+ return { ...state, ...idMap };
6115
+ });
6116
+ }
5790
6117
  return data;
5791
6118
  },
5792
6119
  enabled: true, // Always enabled for combobox
@@ -5818,17 +6145,51 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5818
6145
  // Depend on idMapKey which only changes when items we care about change
5819
6146
  // eslint-disable-next-line react-hooks/exhaustive-deps
5820
6147
  }, [currentValueKey, idMapKey]);
6148
+ // Default itemToValue function: extract value from item using column_ref
6149
+ const defaultItemToValue = (item) => String(item[column_ref]);
6150
+ // Use schema's itemToValue if provided, otherwise use default
6151
+ const itemToValueFn = schemaItemToValue
6152
+ ? (item) => schemaItemToValue(item)
6153
+ : defaultItemToValue;
6154
+ // itemToString function: convert item to readable string using renderDisplay
6155
+ // This ensures items can always be displayed as readable strings in the combobox
6156
+ const renderFn = renderDisplay || defaultRenderDisplay;
6157
+ const itemToStringFn = (item) => {
6158
+ const rendered = renderFn(item);
6159
+ // If already a string or number, return it
6160
+ if (typeof rendered === 'string')
6161
+ return rendered;
6162
+ if (typeof rendered === 'number')
6163
+ return String(rendered);
6164
+ // For ReactNode, fall back to defaultRenderDisplay which converts to string
6165
+ return String(defaultRenderDisplay(item));
6166
+ };
5821
6167
  // Transform data for combobox collection
5822
6168
  // label is used for filtering/searching (must be a string)
6169
+ // displayLabel is used for input display when selected (string representation of rendered display)
5823
6170
  // raw item is stored for custom rendering
5824
6171
  // Also include items from idMap that match currentValue (for initial values display)
5825
6172
  const comboboxItems = React.useMemo(() => {
5826
6173
  const renderFn = renderDisplay || defaultRenderDisplay;
6174
+ // Helper to convert rendered display to string for displayLabel
6175
+ // For ReactNodes (non-string/number), we can't safely stringify due to circular refs
6176
+ // So we use the label (which is already a string) as fallback
6177
+ const getDisplayString = (rendered, fallbackLabel) => {
6178
+ if (typeof rendered === 'string')
6179
+ return rendered;
6180
+ if (typeof rendered === 'number')
6181
+ return String(rendered);
6182
+ // For ReactNode, use the fallback label (which is already a string representation)
6183
+ // The actual ReactNode will be rendered in the overlay, not in the input
6184
+ return fallbackLabel;
6185
+ };
5827
6186
  const itemsFromDataList = dataList.map((item) => {
5828
6187
  const rendered = renderFn(item);
6188
+ const label = typeof rendered === 'string' ? rendered : JSON.stringify(item); // Use string for filtering
5829
6189
  return {
5830
- label: typeof rendered === 'string' ? rendered : JSON.stringify(item), // Use string for filtering
5831
- value: String(item[column_ref]),
6190
+ label, // Use string for filtering
6191
+ displayLabel: getDisplayString(rendered, label), // String representation for input display
6192
+ value: itemToValueFn(item),
5832
6193
  raw: item,
5833
6194
  };
5834
6195
  });
@@ -5837,25 +6198,28 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5837
6198
  const itemsFromIdMap = idMapItems
5838
6199
  .map((item) => {
5839
6200
  // Check if this item is already in itemsFromDataList
5840
- const alreadyIncluded = itemsFromDataList.some((i) => i.value === String(item[column_ref]));
6201
+ const alreadyIncluded = itemsFromDataList.some((i) => i.value === itemToValueFn(item));
5841
6202
  if (alreadyIncluded)
5842
6203
  return null;
5843
6204
  const rendered = renderFn(item);
6205
+ const label = typeof rendered === 'string' ? rendered : JSON.stringify(item);
5844
6206
  return {
5845
- label: typeof rendered === 'string' ? rendered : JSON.stringify(item),
5846
- value: String(item[column_ref]),
6207
+ label,
6208
+ displayLabel: getDisplayString(rendered, label), // String representation for input display
6209
+ value: itemToValueFn(item),
5847
6210
  raw: item,
5848
6211
  };
5849
6212
  })
5850
6213
  .filter((item) => item !== null);
5851
6214
  return [...itemsFromIdMap, ...itemsFromDataList];
5852
- }, [dataList, column_ref, renderDisplay, idMapItems]);
6215
+ }, [dataList, column_ref, renderDisplay, idMapItems, itemToValueFn]);
5853
6216
  // Use filter hook for combobox
5854
6217
  const { contains } = react.useFilter({ sensitivity: 'base' });
5855
6218
  // Create collection for combobox
6219
+ // itemToString uses displayLabel to show rendered display in input when selected
5856
6220
  const { collection, filter, set } = react.useListCollection({
5857
6221
  initialItems: comboboxItems,
5858
- itemToString: (item) => item.label,
6222
+ itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
5859
6223
  itemToValue: (item) => item.value,
5860
6224
  filter: contains,
5861
6225
  });
@@ -5910,6 +6274,10 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5910
6274
  idPickerLabels,
5911
6275
  insideDialog: insideDialog ?? false,
5912
6276
  renderDisplay,
6277
+ itemToValue: itemToValueFn,
6278
+ itemToString: itemToStringFn,
6279
+ loadInitialValues: loadInitialValues ??
6280
+ (async () => ({ data: { data: [], count: 0 }, idMap: {} })), // Fallback if not provided
5913
6281
  column_ref,
5914
6282
  errors,
5915
6283
  setValue,
@@ -5918,60 +6286,69 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5918
6286
 
5919
6287
  const IdPickerSingle = ({ column, schema, prefix, }) => {
5920
6288
  const formI18n = useFormI18n(column, prefix, schema);
5921
- const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, } = schema;
6289
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
5922
6290
  const isRequired = required?.some((columnId) => columnId === column);
5923
- const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, isLoadingInitialValues, isFetchingInitialValues, missingIds, collection, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, errors, setValue, } = useIdPickerData({
6291
+ const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, collection, filter, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, itemToValue, itemToString, errors, setValue, } = useIdPickerData({
5924
6292
  column,
5925
6293
  schema,
5926
6294
  prefix,
5927
6295
  isMultiple: false,
5928
6296
  });
5929
- const handleInputValueChange = (details) => {
5930
- setSearchText(details.inputValue);
5931
- };
5932
- const handleValueChange = (details) => {
5933
- setValue(colLabel, details.value[0] || '');
5934
- };
5935
- const renderDisplayFunction = renderDisplayFn || renderDisplay || defaultRenderDisplay;
5936
- return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5937
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [currentValue.length > 0 && (jsxRuntime.jsx(react.Flex, { mb: 2, children: (() => {
5938
- const id = currentValue[0];
5939
- const item = idMap[id];
5940
- // Show loading skeleton while fetching initial values
5941
- if (item === undefined &&
5942
- (isLoadingInitialValues || isFetchingInitialValues) &&
5943
- missingIds.includes(id)) {
5944
- return jsxRuntime.jsx(react.Skeleton, { height: "24px", width: "100px", borderRadius: "md" });
5945
- }
5946
- // Only show "not found" if we're not loading and item is still missing
5947
- if (item === undefined) {
5948
- return (jsxRuntime.jsx(react.Text, { fontSize: "sm", children: idPickerLabels?.undefined ?? 'Undefined' }));
5949
- }
5950
- return jsxRuntime.jsx(react.Text, { fontSize: "sm", children: renderDisplayFunction(item) });
5951
- })() })), jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: false, closeOnSelect: true, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: insideDialog
5952
- ? { strategy: 'fixed', hideWhenDetached: true }
5953
- : undefined, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? 'Type to search' }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && jsxRuntime.jsx(react.Spinner, { size: "xs" }), isError && (jsxRuntime.jsx(react.Icon, { color: "fg.error", children: jsxRuntime.jsx(bi.BiError, {}) })), currentValue.length > 0 && (jsxRuntime.jsx(react.Combobox.ClearTrigger, { onClick: () => {
5954
- setValue(colLabel, '');
5955
- } })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }), insideDialog ? (jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6297
+ // Get the selected value for single selection display
6298
+ const selectedId = currentValue.length > 0 ? currentValue[0] : null;
6299
+ const selectedItem = selectedId
6300
+ ? idMap[selectedId]
6301
+ : undefined;
6302
+ // Use itemToValue to get the combobox value from the selected item, or use the ID directly
6303
+ const comboboxValue = selectedItem
6304
+ ? itemToString(selectedItem)
6305
+ : selectedId || '';
6306
+ // itemToString is available from the hook and can be used to get a readable string
6307
+ // representation of any item. The collection's itemToString is automatically used
6308
+ // by the combobox to display selected values.
6309
+ // Use useCombobox hook to control input value
6310
+ const combobox = react.useCombobox({
6311
+ collection,
6312
+ value: [comboboxValue],
6313
+ onInputValueChange(e) {
6314
+ setSearchText(e.inputValue);
6315
+ filter(e.inputValue);
6316
+ },
6317
+ onValueChange(e) {
6318
+ setValue(colLabel, e.value[0] || '');
6319
+ // Clear the input value after selection
6320
+ setSearchText('');
6321
+ },
6322
+ multiple: false,
6323
+ closeOnSelect: true,
6324
+ openOnClick: true,
6325
+ invalid: !!errors[colLabel],
6326
+ });
6327
+ // Use renderDisplay from hook (which comes from schema) or fallback to default
6328
+ const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
6329
+ // Get the selected value for single selection display (already computed above)
6330
+ const selectedRendered = selectedItem
6331
+ ? renderDisplayFunction(selectedItem)
6332
+ : null;
6333
+ return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6334
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Combobox.RootProvider, { value: combobox, width: "100%", children: [jsxRuntime.jsx(react.Show, { when: selectedId && selectedRendered, children: jsxRuntime.jsxs(react.HStack, { justifyContent: 'space-between', children: [jsxRuntime.jsx(react.Box, { children: selectedRendered }), currentValue.length > 0 && (jsxRuntime.jsx(react.Button, { variant: "ghost", size: "sm", onClick: () => {
6335
+ setValue(colLabel, '');
6336
+ }, children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(bi.BiX, {}) }) }))] }) }), jsxRuntime.jsx(react.Show, { when: !selectedId || !selectedRendered, children: jsxRuntime.jsxs(react.Combobox.Control, { position: "relative", children: [jsxRuntime.jsx(react.Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? 'Type to search' }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && jsxRuntime.jsx(react.Spinner, { size: "xs" }), isError && (jsxRuntime.jsx(react.Icon, { color: "fg.error", children: jsxRuntime.jsx(bi.BiError, {}) })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }) }), insideDialog ? (jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6337
+ // Show skeleton items to prevent UI shift
6338
+ jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
6339
+ ? idPickerLabels?.emptySearchResult ?? 'No results found'
6340
+ : idPickerLabels?.initialResults ??
6341
+ 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
5956
6342
  // Show skeleton items to prevent UI shift
5957
6343
  jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
5958
6344
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
5959
6345
  : idPickerLabels?.initialResults ??
5960
- 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplayFunction === true
5961
- ? renderDisplayFunction(item.raw)
5962
- : item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
5963
- // Show skeleton items to prevent UI shift
5964
- jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
5965
- ? idPickerLabels?.emptySearchResult ?? 'No results found'
5966
- : idPickerLabels?.initialResults ??
5967
- 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplayFunction === true
5968
- ? renderDisplayFunction(item.raw)
5969
- : item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
6346
+ 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsx(react.Combobox.Item, { item: item, children: renderDisplayFunction(item.raw) }, item.value ?? `item-${index}`))) })) }) }) }))] }) }));
5970
6347
  };
5971
6348
 
5972
6349
  const IdPickerMultiple = ({ column, schema, prefix, }) => {
5973
6350
  const formI18n = useFormI18n(column, prefix, schema);
5974
- const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, } = schema;
6351
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
5975
6352
  const isRequired = required?.some((columnId) => columnId === column);
5976
6353
  const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, isLoadingInitialValues, isFetchingInitialValues, missingIds, collection, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, errors, setValue, } = useIdPickerData({
5977
6354
  column,
@@ -5985,7 +6362,8 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
5985
6362
  const handleValueChange = (details) => {
5986
6363
  setValue(colLabel, details.value);
5987
6364
  };
5988
- const renderDisplayFunction = renderDisplayFn || renderDisplay || defaultRenderDisplay;
6365
+ // Use renderDisplay from hook (which comes from schema) or fallback to default
6366
+ const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
5989
6367
  return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5990
6368
  gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [currentValue.length > 0 && (jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 1, mb: 2, children: currentValue.map((id) => {
5991
6369
  const item = idMap[id];
@@ -6010,16 +6388,12 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
6010
6388
  jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
6011
6389
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
6012
6390
  : idPickerLabels?.initialResults ??
6013
- 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplayFunction === true
6014
- ? renderDisplayFunction(item.raw)
6015
- : item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6391
+ 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6016
6392
  // Show skeleton items to prevent UI shift
6017
6393
  jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
6018
6394
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
6019
6395
  : idPickerLabels?.initialResults ??
6020
- 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplayFunction === true
6021
- ? renderDisplayFunction(item.raw)
6022
- : item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
6396
+ 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
6023
6397
  };
6024
6398
 
6025
6399
  const NumberInputRoot = React__namespace.forwardRef(function NumberInput(props, ref) {
@@ -6203,31 +6577,35 @@ react.RadioCard.ItemIndicator;
6203
6577
 
6204
6578
  const TagPicker = ({ column, schema, prefix }) => {
6205
6579
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
6206
- const { serverUrl } = useSchemaContext();
6207
6580
  if (schema.properties == undefined) {
6208
- throw new Error("schema properties undefined when using DatePicker");
6581
+ throw new Error('schema properties undefined when using DatePicker');
6209
6582
  }
6210
- const { gridColumn, gridRow, in_table, object_id_column } = schema;
6583
+ const { gridColumn, gridRow, in_table, object_id_column, tagPicker } = schema;
6211
6584
  if (in_table === undefined) {
6212
- throw new Error("in_table is undefined when using TagPicker");
6585
+ throw new Error('in_table is undefined when using TagPicker');
6213
6586
  }
6214
6587
  if (object_id_column === undefined) {
6215
- throw new Error("object_id_column is undefined when using TagPicker");
6588
+ throw new Error('object_id_column is undefined when using TagPicker');
6589
+ }
6590
+ if (!tagPicker?.queryFn) {
6591
+ throw new Error('tagPicker.queryFn is required in schema. serverUrl has been removed.');
6216
6592
  }
6217
6593
  const query = reactQuery.useQuery({
6218
6594
  queryKey: [`tagpicker`, in_table],
6219
6595
  queryFn: async () => {
6220
- return await getTableData({
6221
- serverUrl,
6222
- in_table: "tables_tags_view",
6596
+ const result = await tagPicker.queryFn({
6597
+ in_table: 'tables_tags_view',
6223
6598
  where: [
6224
6599
  {
6225
- id: "table_name",
6600
+ id: 'table_name',
6226
6601
  value: [in_table],
6227
6602
  },
6228
6603
  ],
6229
6604
  limit: 100,
6605
+ offset: 0,
6606
+ searching: '',
6230
6607
  });
6608
+ return result.data || { data: [] };
6231
6609
  },
6232
6610
  staleTime: 10000,
6233
6611
  });
@@ -6235,17 +6613,19 @@ const TagPicker = ({ column, schema, prefix }) => {
6235
6613
  const existingTagsQuery = reactQuery.useQuery({
6236
6614
  queryKey: [`existing`, { in_table, object_id_column }, object_id],
6237
6615
  queryFn: async () => {
6238
- return await getTableData({
6239
- serverUrl,
6616
+ const result = await tagPicker.queryFn({
6240
6617
  in_table: in_table,
6241
6618
  where: [
6242
6619
  {
6243
6620
  id: object_id_column,
6244
- value: object_id[0],
6621
+ value: [object_id[0]],
6245
6622
  },
6246
6623
  ],
6247
6624
  limit: 100,
6625
+ offset: 0,
6626
+ searching: '',
6248
6627
  });
6628
+ return result.data || { data: [] };
6249
6629
  },
6250
6630
  enabled: object_id != undefined,
6251
6631
  staleTime: 10000,
@@ -6256,9 +6636,9 @@ const TagPicker = ({ column, schema, prefix }) => {
6256
6636
  if (!!object_id === false) {
6257
6637
  return jsxRuntime.jsx(jsxRuntime.Fragment, {});
6258
6638
  }
6259
- return (jsxRuntime.jsxs(react.Flex, { flexFlow: "column", gap: 4, gridColumn,
6639
+ return (jsxRuntime.jsxs(react.Flex, { flexFlow: 'column', gap: 4, gridColumn,
6260
6640
  gridRow, children: [isFetching && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "isFetching" }), isLoading && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "isLoading" }), isPending && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "isPending" }), isError && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "isError" }), dataList.map(({ parent_tag_name, all_tags, is_mutually_exclusive }) => {
6261
- return (jsxRuntime.jsxs(react.Flex, { flexFlow: "column", gap: 2, children: [jsxRuntime.jsx(react.Text, { children: parent_tag_name }), is_mutually_exclusive && (jsxRuntime.jsx(RadioCardRoot, { defaultValue: "next", variant: "surface", onValueChange: (tagIds) => {
6641
+ return (jsxRuntime.jsxs(react.Flex, { flexFlow: 'column', gap: 2, children: [jsxRuntime.jsx(react.Text, { children: parent_tag_name }), is_mutually_exclusive && (jsxRuntime.jsx(RadioCardRoot, { defaultValue: "next", variant: 'surface', onValueChange: (tagIds) => {
6262
6642
  const existedTags = Object.values(all_tags)
6263
6643
  .filter(({ id }) => {
6264
6644
  return existingTagList.some(({ tag_id }) => tag_id === id);
@@ -6270,20 +6650,20 @@ const TagPicker = ({ column, schema, prefix }) => {
6270
6650
  tagIds.value,
6271
6651
  ]);
6272
6652
  setValue(`${column}.${parent_tag_name}.old`, existedTags);
6273
- }, children: jsxRuntime.jsx(react.Flex, { flexFlow: "wrap", gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
6653
+ }, children: jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
6274
6654
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
6275
- return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: "0 0 0%", disabled: true }, `${tagName}-${id}`));
6655
+ return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: '0 0 0%', disabled: true }, `${tagName}-${id}`));
6276
6656
  }
6277
- return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: "0 0 0%", colorPalette: "blue" }, `${tagName}-${id}`));
6657
+ return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: '0 0 0%', colorPalette: 'blue' }, `${tagName}-${id}`));
6278
6658
  }) }) })), !is_mutually_exclusive && (jsxRuntime.jsx(react.CheckboxGroup, { onValueChange: (tagIds) => {
6279
6659
  setValue(`${column}.${parent_tag_name}.current`, tagIds);
6280
- }, children: jsxRuntime.jsx(react.Flex, { flexFlow: "wrap", gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
6660
+ }, children: jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
6281
6661
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
6282
- return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: "0 0 0%", disabled: true, colorPalette: "blue" }, `${tagName}-${id}`));
6662
+ return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: '0 0 0%', disabled: true, colorPalette: 'blue' }, `${tagName}-${id}`));
6283
6663
  }
6284
- return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: "0 0 0%" }, `${tagName}-${id}`));
6664
+ return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: '0 0 0%' }, `${tagName}-${id}`));
6285
6665
  }) }) }))] }, `tag-${parent_tag_name}`));
6286
- }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: (errors[`${column}`]?.message ?? "No error message") }))] }));
6666
+ }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: 'red.400', children: (errors[`${column}`]?.message ?? 'No error message') }))] }));
6287
6667
  };
6288
6668
 
6289
6669
  const Textarea = React.forwardRef(({ value, defaultValue, placeholder, onChange, onFocus, onBlur, disabled = false, readOnly = false, className, rows = 4, maxLength, autoFocus = false, invalid = false, required = false, label, helperText, errorText, ...props }, ref) => {
@@ -6390,11 +6770,193 @@ const TextAreaInput = ({ column, schema, prefix, }) => {
6390
6770
 
6391
6771
  dayjs.extend(utc);
6392
6772
  dayjs.extend(timezone);
6393
- const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem, onChange = () => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6394
- placeholder: 'hh:mm AM/PM',
6395
- emptyMessage: 'No time found',
6396
- }, }) => {
6397
- // Generate time options (every 15 minutes in 12-hour format)
6773
+ const TimePicker$1 = (props) => {
6774
+ const { format = '12h', value: controlledValue, onChange: controlledOnChange, hour: uncontrolledHour, setHour: uncontrolledSetHour, minute: uncontrolledMinute, setMinute: uncontrolledSetMinute, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6775
+ placeholder: format === '24h' ? 'HH:mm:ss' : 'hh:mm AM/PM',
6776
+ emptyMessage: 'No time found',
6777
+ }, onTimeChange, } = props;
6778
+ const is24Hour = format === '24h';
6779
+ const uncontrolledMeridiem = is24Hour ? undefined : props.meridiem;
6780
+ const uncontrolledSetMeridiem = is24Hour ? undefined : props.setMeridiem;
6781
+ const uncontrolledSecond = is24Hour ? props.second : undefined;
6782
+ const uncontrolledSetSecond = is24Hour ? props.setSecond : undefined;
6783
+ // Determine if we're in controlled mode
6784
+ const isControlled = controlledValue !== undefined;
6785
+ // Parse time string to extract hour, minute, second, meridiem
6786
+ const parseTimeString = (timeStr) => {
6787
+ if (!timeStr || !timeStr.trim()) {
6788
+ return { hour: null, minute: null, second: null, meridiem: null };
6789
+ }
6790
+ // Remove timezone suffix if present (e.g., "14:30:00Z" -> "14:30:00")
6791
+ const timeWithoutTz = timeStr.replace(/[Z+-]\d{2}:?\d{2}$/, '').trim();
6792
+ // Try parsing 24-hour format: "HH:mm:ss" or "HH:mm"
6793
+ const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
6794
+ const match24 = timeWithoutTz.match(time24Pattern);
6795
+ if (match24) {
6796
+ const hour24 = parseInt(match24[1], 10);
6797
+ const minute = parseInt(match24[2], 10);
6798
+ const second = match24[3] ? parseInt(match24[3], 10) : 0;
6799
+ if (hour24 >= 0 &&
6800
+ hour24 <= 23 &&
6801
+ minute >= 0 &&
6802
+ minute <= 59 &&
6803
+ second >= 0 &&
6804
+ second <= 59) {
6805
+ if (is24Hour) {
6806
+ return { hour: hour24, minute, second, meridiem: null };
6807
+ }
6808
+ else {
6809
+ // Convert to 12-hour format
6810
+ let hour12 = hour24;
6811
+ let meridiem;
6812
+ if (hour24 === 0) {
6813
+ hour12 = 12;
6814
+ meridiem = 'am';
6815
+ }
6816
+ else if (hour24 === 12) {
6817
+ hour12 = 12;
6818
+ meridiem = 'pm';
6819
+ }
6820
+ else if (hour24 > 12) {
6821
+ hour12 = hour24 - 12;
6822
+ meridiem = 'pm';
6823
+ }
6824
+ else {
6825
+ hour12 = hour24;
6826
+ meridiem = 'am';
6827
+ }
6828
+ return { hour: hour12, minute, second: null, meridiem };
6829
+ }
6830
+ }
6831
+ }
6832
+ // Try parsing 12-hour format: "hh:mm AM/PM" or "hh:mm:ss AM/PM"
6833
+ const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm|AM|PM)$/i;
6834
+ const match12 = timeWithoutTz.match(time12Pattern);
6835
+ if (match12 && !is24Hour) {
6836
+ const hour12 = parseInt(match12[1], 10);
6837
+ const minute = parseInt(match12[2], 10);
6838
+ const second = match12[3] ? parseInt(match12[3], 10) : null;
6839
+ const meridiem = match12[4].toLowerCase();
6840
+ if (hour12 >= 1 &&
6841
+ hour12 <= 12 &&
6842
+ minute >= 0 &&
6843
+ minute <= 59 &&
6844
+ (second === null || (second >= 0 && second <= 59))) {
6845
+ return { hour: hour12, minute, second, meridiem };
6846
+ }
6847
+ }
6848
+ return { hour: null, minute: null, second: null, meridiem: null };
6849
+ };
6850
+ // Format time values to time string
6851
+ const formatTimeString = (hour, minute, second, meridiem) => {
6852
+ if (hour === null || minute === null) {
6853
+ return undefined;
6854
+ }
6855
+ if (is24Hour) {
6856
+ const h = hour.toString().padStart(2, '0');
6857
+ const m = minute.toString().padStart(2, '0');
6858
+ const s = (second ?? 0).toString().padStart(2, '0');
6859
+ return `${h}:${m}:${s}`;
6860
+ }
6861
+ else {
6862
+ if (meridiem === null) {
6863
+ return undefined;
6864
+ }
6865
+ const h = hour.toString();
6866
+ const m = minute.toString().padStart(2, '0');
6867
+ return `${h}:${m} ${meridiem.toUpperCase()}`;
6868
+ }
6869
+ };
6870
+ // Internal state for controlled mode
6871
+ const [internalHour, setInternalHour] = React.useState(null);
6872
+ const [internalMinute, setInternalMinute] = React.useState(null);
6873
+ const [internalSecond, setInternalSecond] = React.useState(null);
6874
+ const [internalMeridiem, setInternalMeridiem] = React.useState(null);
6875
+ // Use controlled or uncontrolled values
6876
+ const hour = isControlled ? internalHour : uncontrolledHour ?? null;
6877
+ const minute = isControlled ? internalMinute : uncontrolledMinute ?? null;
6878
+ const second = isControlled ? internalSecond : uncontrolledSecond ?? null;
6879
+ const meridiem = isControlled
6880
+ ? internalMeridiem
6881
+ : uncontrolledMeridiem ?? null;
6882
+ // Setters that work for both modes
6883
+ const setHour = isControlled
6884
+ ? setInternalHour
6885
+ : uncontrolledSetHour || (() => { });
6886
+ const setMinute = isControlled
6887
+ ? setInternalMinute
6888
+ : uncontrolledSetMinute || (() => { });
6889
+ const setSecond = isControlled
6890
+ ? setInternalSecond
6891
+ : uncontrolledSetSecond || (() => { });
6892
+ const setMeridiem = isControlled
6893
+ ? setInternalMeridiem
6894
+ : uncontrolledSetMeridiem || (() => { });
6895
+ // Sync internal state with controlled value prop
6896
+ const prevValueRef = React.useRef(controlledValue);
6897
+ React.useEffect(() => {
6898
+ if (!isControlled)
6899
+ return;
6900
+ if (prevValueRef.current === controlledValue) {
6901
+ return;
6902
+ }
6903
+ prevValueRef.current = controlledValue;
6904
+ const parsed = parseTimeString(controlledValue);
6905
+ setInternalHour(parsed.hour);
6906
+ setInternalMinute(parsed.minute);
6907
+ if (is24Hour) {
6908
+ setInternalSecond(parsed.second);
6909
+ }
6910
+ else {
6911
+ setInternalMeridiem(parsed.meridiem);
6912
+ }
6913
+ }, [controlledValue, isControlled, is24Hour]);
6914
+ // Wrapper onChange that calls both controlled and uncontrolled onChange
6915
+ const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
6916
+ if (isControlled) {
6917
+ const timeString = formatTimeString(newHour, newMinute, newSecond, newMeridiem);
6918
+ controlledOnChange?.(timeString);
6919
+ }
6920
+ else {
6921
+ // Call legacy onTimeChange if provided
6922
+ if (onTimeChange) {
6923
+ if (is24Hour) {
6924
+ const timeChange24h = onTimeChange;
6925
+ timeChange24h({
6926
+ hour: newHour,
6927
+ minute: newMinute,
6928
+ second: newSecond,
6929
+ });
6930
+ }
6931
+ else {
6932
+ const timeChange12h = onTimeChange;
6933
+ timeChange12h({
6934
+ hour: newHour,
6935
+ minute: newMinute,
6936
+ meridiem: newMeridiem,
6937
+ });
6938
+ }
6939
+ }
6940
+ }
6941
+ };
6942
+ const [inputValue, setInputValue] = React.useState('');
6943
+ // Sync inputValue with current time
6944
+ React.useEffect(() => {
6945
+ if (is24Hour && second !== undefined) {
6946
+ if (hour !== null && minute !== null && second !== null) {
6947
+ const formatted = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
6948
+ setInputValue(formatted);
6949
+ }
6950
+ else {
6951
+ setInputValue('');
6952
+ }
6953
+ }
6954
+ else {
6955
+ // 12-hour format - input is managed by combobox
6956
+ setInputValue('');
6957
+ }
6958
+ }, [hour, minute, second, is24Hour]);
6959
+ // Generate time options based on format
6398
6960
  const timeOptions = React.useMemo(() => {
6399
6961
  const options = [];
6400
6962
  // Get start time for comparison if provided
@@ -6405,32 +6967,25 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6405
6967
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6406
6968
  if (startDateObj.isValid() && selectedDateObj.isValid()) {
6407
6969
  startDateTime = startDateObj;
6408
- // Only filter if dates are the same
6409
6970
  shouldFilterByDate =
6410
6971
  startDateObj.format('YYYY-MM-DD') ===
6411
6972
  selectedDateObj.format('YYYY-MM-DD');
6412
6973
  }
6413
6974
  }
6414
- // Generate 12-hour format options (1-12 for hours, AM/PM)
6415
- for (let h = 1; h <= 12; h++) {
6416
- for (let m = 0; m < 60; m += 15) {
6417
- for (const mer of ['am', 'pm']) {
6418
- // Convert 12-hour to 24-hour for comparison
6419
- let hour24 = h;
6420
- if (mer === 'am' && h === 12)
6421
- hour24 = 0;
6422
- else if (mer === 'pm' && h < 12)
6423
- hour24 = h + 12;
6424
- // Filter out times that would result in negative duration (only when dates are the same)
6975
+ if (is24Hour) {
6976
+ // Generate 24-hour format options (0-23 for hours)
6977
+ for (let h = 0; h < 24; h++) {
6978
+ for (let m = 0; m < 60; m += 15) {
6979
+ // Filter out times that would result in negative duration
6425
6980
  if (startDateTime && selectedDate && shouldFilterByDate) {
6426
6981
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6427
6982
  const optionDateTime = selectedDateObj
6428
- .hour(hour24)
6983
+ .hour(h)
6429
6984
  .minute(m)
6430
6985
  .second(0)
6431
6986
  .millisecond(0);
6432
6987
  if (optionDateTime.isBefore(startDateTime)) {
6433
- continue; // Skip this option as it would result in negative duration
6988
+ continue;
6434
6989
  }
6435
6990
  }
6436
6991
  // Calculate duration if startTime is provided
@@ -6438,7 +6993,7 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6438
6993
  if (startDateTime && selectedDate) {
6439
6994
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6440
6995
  const optionDateTime = selectedDateObj
6441
- .hour(hour24)
6996
+ .hour(h)
6442
6997
  .minute(m)
6443
6998
  .second(0)
6444
6999
  .millisecond(0);
@@ -6463,58 +7018,204 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6463
7018
  }
6464
7019
  }
6465
7020
  }
6466
- const hourDisplay = h.toString();
6467
- const minuteDisplay = m.toString().padStart(2, '0');
6468
- const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
7021
+ const timeDisplay = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`;
6469
7022
  options.push({
6470
7023
  label: timeDisplay,
6471
- value: `${h}:${m}:${mer}`,
7024
+ value: `${h}:${m}:0`,
6472
7025
  hour: h,
6473
7026
  minute: m,
6474
- meridiem: mer,
6475
- searchText: timeDisplay, // Use base time without duration for searching
7027
+ second: 0,
7028
+ searchText: timeDisplay,
6476
7029
  durationText,
6477
7030
  });
6478
7031
  }
6479
7032
  }
6480
7033
  }
7034
+ else {
7035
+ // Generate 12-hour format options (1-12 for hours, AM/PM)
7036
+ for (let h = 1; h <= 12; h++) {
7037
+ for (let m = 0; m < 60; m += 15) {
7038
+ for (const mer of ['am', 'pm']) {
7039
+ // Convert 12-hour to 24-hour for comparison
7040
+ let hour24 = h;
7041
+ if (mer === 'am' && h === 12)
7042
+ hour24 = 0;
7043
+ else if (mer === 'pm' && h < 12)
7044
+ hour24 = h + 12;
7045
+ // Filter out times that would result in negative duration
7046
+ if (startDateTime && selectedDate && shouldFilterByDate) {
7047
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
7048
+ const optionDateTime = selectedDateObj
7049
+ .hour(hour24)
7050
+ .minute(m)
7051
+ .second(0)
7052
+ .millisecond(0);
7053
+ if (optionDateTime.isBefore(startDateTime)) {
7054
+ continue;
7055
+ }
7056
+ }
7057
+ // Calculate duration if startTime is provided
7058
+ let durationText;
7059
+ if (startDateTime && selectedDate) {
7060
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
7061
+ const optionDateTime = selectedDateObj
7062
+ .hour(hour24)
7063
+ .minute(m)
7064
+ .second(0)
7065
+ .millisecond(0);
7066
+ if (optionDateTime.isValid() &&
7067
+ optionDateTime.isAfter(startDateTime)) {
7068
+ const diffMs = optionDateTime.diff(startDateTime);
7069
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
7070
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
7071
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
7072
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
7073
+ let diffText = '';
7074
+ if (diffHours > 0) {
7075
+ diffText = `${diffHours}h ${diffMinutes}m`;
7076
+ }
7077
+ else if (diffMinutes > 0) {
7078
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
7079
+ }
7080
+ else {
7081
+ diffText = `${diffSeconds}s`;
7082
+ }
7083
+ durationText = `+${diffText}`;
7084
+ }
7085
+ }
7086
+ }
7087
+ const hourDisplay = h.toString();
7088
+ const minuteDisplay = m.toString().padStart(2, '0');
7089
+ const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
7090
+ options.push({
7091
+ label: timeDisplay,
7092
+ value: `${h}:${m}:${mer}`,
7093
+ hour: h,
7094
+ minute: m,
7095
+ meridiem: mer,
7096
+ searchText: timeDisplay,
7097
+ durationText,
7098
+ });
7099
+ }
7100
+ }
7101
+ }
7102
+ // Sort 12-hour options by time (convert to 24-hour for proper chronological sorting)
7103
+ return options.sort((a, b) => {
7104
+ const a12 = a;
7105
+ const b12 = b;
7106
+ let hour24A = a12.hour;
7107
+ if (a12.meridiem === 'am' && a12.hour === 12)
7108
+ hour24A = 0;
7109
+ else if (a12.meridiem === 'pm' && a12.hour < 12)
7110
+ hour24A = a12.hour + 12;
7111
+ let hour24B = b12.hour;
7112
+ if (b12.meridiem === 'am' && b12.hour === 12)
7113
+ hour24B = 0;
7114
+ else if (b12.meridiem === 'pm' && b12.hour < 12)
7115
+ hour24B = b12.hour + 12;
7116
+ if (hour24A !== hour24B) {
7117
+ return hour24A - hour24B;
7118
+ }
7119
+ return a12.minute - b12.minute;
7120
+ });
7121
+ }
6481
7122
  return options;
6482
- }, [startTime, selectedDate, timezone]);
7123
+ }, [startTime, selectedDate, timezone, is24Hour]);
7124
+ // itemToString returns only the clean display text (no metadata)
7125
+ const itemToString = React.useMemo(() => {
7126
+ return (item) => {
7127
+ return item.searchText;
7128
+ };
7129
+ }, []);
7130
+ // Custom filter function
6483
7131
  const { contains } = react.useFilter({ sensitivity: 'base' });
7132
+ const customTimeFilter = React.useMemo(() => {
7133
+ if (is24Hour) {
7134
+ return contains; // Simple contains filter for 24-hour format
7135
+ }
7136
+ // For 12-hour format, support both 12-hour and 24-hour input
7137
+ return (itemText, filterText) => {
7138
+ if (!filterText) {
7139
+ return true;
7140
+ }
7141
+ const lowerItemText = itemText.toLowerCase();
7142
+ const lowerFilterText = filterText.toLowerCase();
7143
+ if (lowerItemText.includes(lowerFilterText)) {
7144
+ return true;
7145
+ }
7146
+ const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
7147
+ if (!item || !('meridiem' in item)) {
7148
+ return false;
7149
+ }
7150
+ // Convert item to 24-hour format for matching
7151
+ let hour24 = item.hour;
7152
+ if (item.meridiem === 'am' && item.hour === 12)
7153
+ hour24 = 0;
7154
+ else if (item.meridiem === 'pm' && item.hour < 12)
7155
+ hour24 = item.hour + 12;
7156
+ const hour24Str = hour24.toString().padStart(2, '0');
7157
+ const minuteStr = item.minute.toString().padStart(2, '0');
7158
+ const formats = [
7159
+ `${hour24Str}:${minuteStr}`,
7160
+ `${hour24Str}${minuteStr}`,
7161
+ hour24Str,
7162
+ `${hour24}:${minuteStr}`,
7163
+ hour24.toString(),
7164
+ ];
7165
+ return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
7166
+ lowerFilterText.includes(format.toLowerCase()));
7167
+ };
7168
+ }, [timeOptions, is24Hour, contains]);
6484
7169
  const { collection, filter } = react.useListCollection({
6485
7170
  initialItems: timeOptions,
6486
- itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
7171
+ itemToString: itemToString,
6487
7172
  itemToValue: (item) => item.value,
6488
- filter: contains,
7173
+ filter: customTimeFilter,
6489
7174
  });
6490
7175
  // Get current value string for combobox
6491
7176
  const currentValue = React.useMemo(() => {
6492
- if (hour === null || minute === null || meridiem === null) {
6493
- return '';
7177
+ if (is24Hour) {
7178
+ if (hour === null || minute === null || second === null) {
7179
+ return '';
7180
+ }
7181
+ return `${hour}:${minute}:${second}`;
7182
+ }
7183
+ else {
7184
+ if (hour === null || minute === null || meridiem === null) {
7185
+ return '';
7186
+ }
7187
+ return `${hour}:${minute}:${meridiem}`;
6494
7188
  }
6495
- return `${hour}:${minute}:${meridiem}`;
6496
- }, [hour, minute, meridiem]);
7189
+ }, [hour, minute, second, meridiem, is24Hour]);
6497
7190
  // Calculate duration difference
6498
7191
  const durationDiff = React.useMemo(() => {
6499
- if (!startTime ||
6500
- !selectedDate ||
6501
- hour === null ||
6502
- minute === null ||
6503
- meridiem === null) {
7192
+ if (!startTime || !selectedDate || hour === null || minute === null) {
6504
7193
  return null;
6505
7194
  }
7195
+ if (is24Hour) {
7196
+ if (second === null)
7197
+ return null;
7198
+ }
7199
+ else {
7200
+ if (meridiem === null)
7201
+ return null;
7202
+ }
6506
7203
  const startDateObj = dayjs(startTime).tz(timezone);
6507
7204
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6508
- // Convert 12-hour to 24-hour format
7205
+ // Convert to 24-hour format
6509
7206
  let hour24 = hour;
6510
- if (meridiem === 'am' && hour === 12)
6511
- hour24 = 0;
6512
- else if (meridiem === 'pm' && hour < 12)
6513
- hour24 = hour + 12;
7207
+ if (!is24Hour && meridiem) {
7208
+ if (meridiem === 'am' && hour === 12)
7209
+ hour24 = 0;
7210
+ else if (meridiem === 'pm' && hour < 12)
7211
+ hour24 = hour + 12;
7212
+ }
6514
7213
  const currentDateTime = selectedDateObj
6515
7214
  .hour(hour24)
6516
7215
  .minute(minute)
6517
- .second(0)
7216
+ .second(is24Hour && second !== null && second !== undefined
7217
+ ? second
7218
+ : 0)
6518
7219
  .millisecond(0);
6519
7220
  if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6520
7221
  return null;
@@ -6540,13 +7241,28 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6540
7241
  return `+${diffText}`;
6541
7242
  }
6542
7243
  return null;
6543
- }, [hour, minute, meridiem, startTime, selectedDate, timezone]);
7244
+ }, [
7245
+ hour,
7246
+ minute,
7247
+ second,
7248
+ meridiem,
7249
+ startTime,
7250
+ selectedDate,
7251
+ timezone,
7252
+ is24Hour,
7253
+ ]);
6544
7254
  const handleClear = () => {
6545
7255
  setHour(null);
6546
7256
  setMinute(null);
6547
- setMeridiem(null);
6548
- filter(''); // Reset filter to show all options
6549
- onChange({ hour: null, minute: null, meridiem: null });
7257
+ if (is24Hour && setSecond) {
7258
+ setSecond(null);
7259
+ handleTimeChange(null, null, null, null);
7260
+ }
7261
+ else if (!is24Hour && setMeridiem) {
7262
+ setMeridiem(null);
7263
+ handleTimeChange(null, null, null, null);
7264
+ }
7265
+ filter('');
6550
7266
  };
6551
7267
  const handleValueChange = (details) => {
6552
7268
  if (details.value.length === 0) {
@@ -6558,71 +7274,165 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6558
7274
  if (selectedOption) {
6559
7275
  setHour(selectedOption.hour);
6560
7276
  setMinute(selectedOption.minute);
6561
- setMeridiem(selectedOption.meridiem);
6562
- filter(''); // Reset filter after selection
6563
- onChange({
6564
- hour: selectedOption.hour,
6565
- minute: selectedOption.minute,
6566
- meridiem: selectedOption.meridiem,
6567
- });
7277
+ filter('');
7278
+ if (is24Hour) {
7279
+ const opt24 = selectedOption;
7280
+ if (setSecond)
7281
+ setSecond(opt24.second);
7282
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
7283
+ }
7284
+ else {
7285
+ const opt12 = selectedOption;
7286
+ if (setMeridiem)
7287
+ setMeridiem(opt12.meridiem);
7288
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
7289
+ }
6568
7290
  }
6569
7291
  };
6570
7292
  // Parse input value and update state
6571
7293
  const parseAndCommitInput = (value) => {
6572
7294
  const trimmedValue = value.trim();
6573
- // Filter the collection based on input
6574
7295
  filter(trimmedValue);
6575
7296
  if (!trimmedValue) {
6576
7297
  return;
6577
7298
  }
6578
- // Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
6579
- const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
6580
- const match12Hour = trimmedValue.match(timePattern12Hour);
6581
- if (match12Hour) {
6582
- const parsedHour = parseInt(match12Hour[1], 10);
6583
- const parsedMinute = parseInt(match12Hour[2], 10);
6584
- const parsedMeridiem = match12Hour[3].toLowerCase();
6585
- // Validate ranges
6586
- if (parsedHour >= 1 &&
6587
- parsedHour <= 12 &&
6588
- parsedMinute >= 0 &&
6589
- parsedMinute <= 59) {
6590
- setHour(parsedHour);
6591
- setMinute(parsedMinute);
6592
- setMeridiem(parsedMeridiem);
6593
- onChange({
6594
- hour: parsedHour,
6595
- minute: parsedMinute,
6596
- meridiem: parsedMeridiem,
6597
- });
6598
- return;
7299
+ if (is24Hour) {
7300
+ // Parse 24-hour format: "HH:mm:ss" or "HH:mm" or "HHmmss" or "HHmm"
7301
+ const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
7302
+ const match = trimmedValue.match(timePattern);
7303
+ if (match) {
7304
+ const parsedHour = parseInt(match[1], 10);
7305
+ const parsedMinute = parseInt(match[2], 10);
7306
+ const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
7307
+ if (parsedHour >= 0 &&
7308
+ parsedHour <= 23 &&
7309
+ parsedMinute >= 0 &&
7310
+ parsedMinute <= 59 &&
7311
+ parsedSecond >= 0 &&
7312
+ parsedSecond <= 59) {
7313
+ setHour(parsedHour);
7314
+ setMinute(parsedMinute);
7315
+ if (setSecond)
7316
+ setSecond(parsedSecond);
7317
+ handleTimeChange(parsedHour, parsedMinute, parsedSecond, null);
7318
+ return;
7319
+ }
7320
+ }
7321
+ // Try numbers only format: "123045" or "1230"
7322
+ const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
7323
+ if (numbersOnly.length >= 4) {
7324
+ const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
7325
+ const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
7326
+ const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
7327
+ if (parsedHour >= 0 &&
7328
+ parsedHour <= 23 &&
7329
+ parsedMinute >= 0 &&
7330
+ parsedMinute <= 59 &&
7331
+ parsedSecond >= 0 &&
7332
+ parsedSecond <= 59) {
7333
+ setHour(parsedHour);
7334
+ setMinute(parsedMinute);
7335
+ if (setSecond)
7336
+ setSecond(parsedSecond);
7337
+ handleTimeChange(parsedHour, parsedMinute, parsedSecond, null);
7338
+ return;
7339
+ }
6599
7340
  }
6600
7341
  }
6601
- // Try to parse formats like "130pm" or "130 pm" (without colon)
6602
- const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
6603
- const matchNoColon = trimmedValue.match(timePatternNoColon);
6604
- if (matchNoColon) {
6605
- const numbersOnly = matchNoColon[1];
6606
- const parsedMeridiem = matchNoColon[2].toLowerCase();
6607
- if (numbersOnly.length >= 3) {
6608
- const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
6609
- const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
6610
- // Validate ranges
7342
+ else {
7343
+ // Parse 24-hour format first (e.g., "13:30", "14:00", "1330", "1400")
7344
+ const timePattern24Hour = /^(\d{1,2}):?(\d{2})$/;
7345
+ const match24Hour = trimmedValue.match(timePattern24Hour);
7346
+ if (match24Hour) {
7347
+ const parsedHour24 = parseInt(match24Hour[1], 10);
7348
+ const parsedMinute = parseInt(match24Hour[2], 10);
7349
+ if (parsedHour24 >= 0 &&
7350
+ parsedHour24 <= 23 &&
7351
+ parsedMinute >= 0 &&
7352
+ parsedMinute <= 59) {
7353
+ // Convert 24-hour to 12-hour format
7354
+ let hour12;
7355
+ let meridiem;
7356
+ if (parsedHour24 === 0) {
7357
+ hour12 = 12;
7358
+ meridiem = 'am';
7359
+ }
7360
+ else if (parsedHour24 === 12) {
7361
+ hour12 = 12;
7362
+ meridiem = 'pm';
7363
+ }
7364
+ else if (parsedHour24 > 12) {
7365
+ hour12 = parsedHour24 - 12;
7366
+ meridiem = 'pm';
7367
+ }
7368
+ else {
7369
+ hour12 = parsedHour24;
7370
+ meridiem = 'am';
7371
+ }
7372
+ setHour(hour12);
7373
+ setMinute(parsedMinute);
7374
+ if (setMeridiem)
7375
+ setMeridiem(meridiem);
7376
+ handleTimeChange(hour12, parsedMinute, null, meridiem);
7377
+ return;
7378
+ }
7379
+ }
7380
+ // Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
7381
+ const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
7382
+ const match12Hour = trimmedValue.match(timePattern12Hour);
7383
+ if (match12Hour) {
7384
+ const parsedHour = parseInt(match12Hour[1], 10);
7385
+ const parsedMinute = parseInt(match12Hour[2], 10);
7386
+ const parsedMeridiem = match12Hour[3].toLowerCase();
6611
7387
  if (parsedHour >= 1 &&
6612
7388
  parsedHour <= 12 &&
6613
7389
  parsedMinute >= 0 &&
6614
7390
  parsedMinute <= 59) {
6615
7391
  setHour(parsedHour);
6616
7392
  setMinute(parsedMinute);
6617
- setMeridiem(parsedMeridiem);
6618
- onChange({
6619
- hour: parsedHour,
6620
- minute: parsedMinute,
6621
- meridiem: parsedMeridiem,
6622
- });
7393
+ if (setMeridiem)
7394
+ setMeridiem(parsedMeridiem);
7395
+ handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
7396
+ return;
7397
+ }
7398
+ }
7399
+ // Parse formats like "12am" or "1pm" (hour only with meridiem, no minutes)
7400
+ const timePatternHourOnly = /^(\d{1,2})\s*(am|pm|AM|PM)$/i;
7401
+ const matchHourOnly = trimmedValue.match(timePatternHourOnly);
7402
+ if (matchHourOnly) {
7403
+ const parsedHour = parseInt(matchHourOnly[1], 10);
7404
+ const parsedMeridiem = matchHourOnly[2].toLowerCase();
7405
+ if (parsedHour >= 1 && parsedHour <= 12) {
7406
+ setHour(parsedHour);
7407
+ setMinute(0); // Default to 0 minutes when only hour is provided
7408
+ if (setMeridiem)
7409
+ setMeridiem(parsedMeridiem);
7410
+ handleTimeChange(parsedHour, 0, null, parsedMeridiem);
6623
7411
  return;
6624
7412
  }
6625
7413
  }
7414
+ // Try to parse formats like "130pm" or "130 pm" (without colon, with minutes)
7415
+ const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
7416
+ const matchNoColon = trimmedValue.match(timePatternNoColon);
7417
+ if (matchNoColon) {
7418
+ const numbersOnly = matchNoColon[1];
7419
+ const parsedMeridiem = matchNoColon[2].toLowerCase();
7420
+ if (numbersOnly.length >= 3) {
7421
+ const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
7422
+ const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
7423
+ if (parsedHour >= 1 &&
7424
+ parsedHour <= 12 &&
7425
+ parsedMinute >= 0 &&
7426
+ parsedMinute <= 59) {
7427
+ setHour(parsedHour);
7428
+ setMinute(parsedMinute);
7429
+ if (setMeridiem)
7430
+ setMeridiem(parsedMeridiem);
7431
+ handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
7432
+ return;
7433
+ }
7434
+ }
7435
+ }
6626
7436
  }
6627
7437
  // Parse failed, select first result
6628
7438
  selectFirstResult();
@@ -6633,58 +7443,87 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6633
7443
  const firstItem = collection.items[0];
6634
7444
  setHour(firstItem.hour);
6635
7445
  setMinute(firstItem.minute);
6636
- setMeridiem(firstItem.meridiem);
6637
- filter(''); // Reset filter after selection
6638
- onChange({
6639
- hour: firstItem.hour,
6640
- minute: firstItem.minute,
6641
- meridiem: firstItem.meridiem,
6642
- });
7446
+ filter('');
7447
+ if (is24Hour) {
7448
+ const opt24 = firstItem;
7449
+ if (setSecond)
7450
+ setSecond(opt24.second);
7451
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
7452
+ }
7453
+ else {
7454
+ const opt12 = firstItem;
7455
+ if (setMeridiem)
7456
+ setMeridiem(opt12.meridiem);
7457
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
7458
+ }
6643
7459
  }
6644
7460
  };
6645
7461
  const handleInputValueChange = (details) => {
6646
- // Filter the collection based on input, but don't parse yet
7462
+ if (is24Hour) {
7463
+ setInputValue(details.inputValue);
7464
+ }
6647
7465
  filter(details.inputValue);
6648
7466
  };
6649
7467
  const handleFocus = (e) => {
6650
- // Select all text when focusing
6651
7468
  e.target.select();
6652
7469
  };
6653
7470
  const handleBlur = (e) => {
6654
- // Parse and commit the input value when losing focus
6655
- const inputValue = e.target.value;
6656
- if (inputValue) {
6657
- parseAndCommitInput(inputValue);
7471
+ const inputVal = e.target.value;
7472
+ if (is24Hour) {
7473
+ setInputValue(inputVal);
7474
+ }
7475
+ if (inputVal) {
7476
+ parseAndCommitInput(inputVal);
6658
7477
  }
6659
7478
  };
6660
7479
  const handleKeyDown = (e) => {
6661
- // Commit input on Enter key
6662
7480
  if (e.key === 'Enter') {
6663
7481
  e.preventDefault();
6664
- const inputValue = e.currentTarget.value;
6665
- if (inputValue) {
6666
- parseAndCommitInput(inputValue);
7482
+ const inputVal = e.currentTarget.value;
7483
+ if (is24Hour) {
7484
+ setInputValue(inputVal);
7485
+ }
7486
+ if (inputVal) {
7487
+ parseAndCommitInput(inputVal);
6667
7488
  }
6668
- // Blur the input
6669
7489
  e.currentTarget?.blur();
6670
7490
  }
6671
7491
  };
6672
- return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: labels?.placeholder ?? 'hh:mm AM/PM', onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: labels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsxRuntime.jsx(react.Text, { flex: 1, children: item.label }), item.durationText && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: item.durationText }) }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: durationDiff }) })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdCancel, {}) }) })] }) }));
7492
+ return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { value: is24Hour ? inputValue : undefined, placeholder: labels?.placeholder ?? (is24Hour ? 'HH:mm:ss' : 'hh:mm AM/PM'), onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: labels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsxRuntime.jsx(react.Text, { flex: 1, children: item.label }), item.durationText && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: item.durationText }) }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: durationDiff }) }))] }) }));
6673
7493
  };
6674
7494
 
6675
7495
  dayjs.extend(timezone);
6676
7496
  const TimePicker = ({ column, schema, prefix }) => {
6677
7497
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
6678
7498
  const { timezone, insideDialog, timePickerLabels } = useSchemaContext();
6679
- const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', } = schema;
7499
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', startTimeField, selectedDateField, } = schema;
6680
7500
  const isRequired = required?.some((columnId) => columnId === column);
6681
7501
  const colLabel = `${prefix}${column}`;
6682
7502
  const formI18n = useFormI18n(column, prefix, schema);
6683
7503
  const [open, setOpen] = React.useState(false);
6684
7504
  const value = watch(colLabel);
6685
- const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
6686
- ? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
6687
- : '';
7505
+ // Watch startTime and selectedDate fields for offset calculation
7506
+ const startTimeValue = startTimeField
7507
+ ? watch(`${prefix}${startTimeField}`)
7508
+ : undefined;
7509
+ const selectedDateValue = selectedDateField
7510
+ ? watch(`${prefix}${selectedDateField}`)
7511
+ : undefined;
7512
+ // Convert to ISO string format for startTime if it's a date-time string
7513
+ const startTime = startTimeValue
7514
+ ? dayjs(startTimeValue).tz(timezone).isValid()
7515
+ ? dayjs(startTimeValue).tz(timezone).toISOString()
7516
+ : undefined
7517
+ : undefined;
7518
+ // Convert selectedDate to YYYY-MM-DD format
7519
+ const selectedDate = selectedDateValue
7520
+ ? dayjs(selectedDateValue).tz(timezone).isValid()
7521
+ ? dayjs(selectedDateValue).tz(timezone).format('YYYY-MM-DD')
7522
+ : undefined
7523
+ : undefined;
7524
+ const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
7525
+ ? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
7526
+ : '';
6688
7527
  // Parse the initial time parts from the time string (HH:mm:ssZ)
6689
7528
  const parseTime = (time) => {
6690
7529
  if (!time)
@@ -6737,884 +7576,898 @@ const TimePicker = ({ column, schema, prefix }) => {
6737
7576
  return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6738
7577
  gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
6739
7578
  setOpen(true);
6740
- }, justifyContent: 'start', children: [jsxRuntime.jsx(io.IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsxRuntime.jsx(react.Popover.Body, { overflow: "visible", children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { children: jsxRuntime.jsx(react.Popover.Body, { children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) }) }))] }) }));
7579
+ }, justifyContent: 'start', children: [jsxRuntime.jsx(io.IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsxRuntime.jsx(react.Popover.Body, { overflow: "visible", children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: startTime, selectedDate: selectedDate, timezone: timezone, portalled: false, labels: timePickerLabels }) }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { children: jsxRuntime.jsx(react.Popover.Body, { children: jsxRuntime.jsx(TimePicker$1, { format: "12h", hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: startTime, selectedDate: selectedDate, timezone: timezone, portalled: false, labels: timePickerLabels }) }) }) }) }))] }) }));
6741
7580
  };
6742
7581
 
6743
7582
  dayjs.extend(utc);
6744
7583
  dayjs.extend(timezone);
6745
7584
  dayjs.extend(customParseFormat);
6746
- function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
7585
+ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
6747
7586
  monthNamesShort: [
6748
- 'Jan',
6749
- 'Feb',
6750
- 'Mar',
6751
- 'Apr',
7587
+ 'January',
7588
+ 'February',
7589
+ 'March',
7590
+ 'April',
6752
7591
  'May',
6753
- 'Jun',
6754
- 'Jul',
6755
- 'Aug',
6756
- 'Sep',
6757
- 'Oct',
6758
- 'Nov',
6759
- 'Dec',
7592
+ 'June',
7593
+ 'July',
7594
+ 'August',
7595
+ 'September',
7596
+ 'October',
7597
+ 'November',
7598
+ 'December',
6760
7599
  ],
6761
7600
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
6762
7601
  backButtonLabel: 'Back',
6763
- forwardButtonLabel: 'Next',
6764
- }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, }) {
6765
- const [open, setOpen] = React.useState(false);
6766
- const [inputValue, setInputValue] = React.useState('');
6767
- // Update input value when prop value changes
6768
- React.useEffect(() => {
6769
- if (value) {
6770
- const formatted = typeof value === 'string'
6771
- ? dayjs(value).tz(timezone).isValid()
6772
- ? dayjs(value).tz(timezone).format(displayFormat)
6773
- : ''
6774
- : dayjs(value).tz(timezone).format(displayFormat);
6775
- setInputValue(formatted);
6776
- }
6777
- else {
6778
- setInputValue('');
7602
+ forwardButtonLabel: 'Forward',
7603
+ }, timePickerLabels, timezone: tz = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, showQuickActions = false, quickActionLabels = {
7604
+ yesterday: 'Yesterday',
7605
+ today: 'Today',
7606
+ tomorrow: 'Tomorrow',
7607
+ plus7Days: '+7 Days',
7608
+ }, showTimezoneSelector = false, timezoneOffset: controlledTimezoneOffset, onTimezoneOffsetChange, }) {
7609
+ const is24Hour = format === 'iso-date-time' || showSeconds;
7610
+ // Labels are used in calendarLabels useMemo
7611
+ // Parse value to get date and time
7612
+ const parsedValue = React.useMemo(() => {
7613
+ if (!value)
7614
+ return null;
7615
+ const dateObj = dayjs(value).tz(tz);
7616
+ if (!dateObj.isValid())
7617
+ return null;
7618
+ return dateObj;
7619
+ }, [value, tz]);
7620
+ // Initialize date state
7621
+ const [selectedDate, setSelectedDate] = React.useState(() => {
7622
+ if (parsedValue) {
7623
+ return parsedValue.toDate();
7624
+ }
7625
+ if (defaultDate) {
7626
+ const defaultDateObj = dayjs(defaultDate).tz(tz);
7627
+ return defaultDateObj.isValid() ? defaultDateObj.toDate() : new Date();
7628
+ }
7629
+ return new Date();
7630
+ });
7631
+ // Initialize time state
7632
+ const [hour, setHour] = React.useState(() => {
7633
+ if (parsedValue) {
7634
+ return parsedValue.hour();
6779
7635
  }
6780
- }, [value, displayFormat, timezone]);
6781
- // Convert value to Date object for DatePicker
6782
- const selectedDate = value
6783
- ? typeof value === 'string'
6784
- ? dayjs(value).tz(timezone).isValid()
6785
- ? dayjs(value).tz(timezone).toDate()
6786
- : new Date()
6787
- : value
6788
- : new Date();
6789
- // Shared function to parse and validate input value
6790
- const parseAndValidateInput = (inputVal) => {
6791
- // If empty, clear the value
6792
- if (!inputVal.trim()) {
6793
- onChange?.(undefined);
6794
- setInputValue('');
6795
- return;
7636
+ if (defaultTime?.hour !== null && defaultTime?.hour !== undefined) {
7637
+ return defaultTime.hour;
6796
7638
  }
6797
- // Try parsing with displayFormat first
6798
- let parsedDate = dayjs(inputVal, displayFormat, true);
6799
- // If that fails, try common date formats
6800
- if (!parsedDate.isValid()) {
6801
- parsedDate = dayjs(inputVal);
7639
+ return null;
7640
+ });
7641
+ const [minute, setMinute] = React.useState(() => {
7642
+ if (parsedValue) {
7643
+ return parsedValue.minute();
6802
7644
  }
6803
- // If still invalid, try parsing with dateFormat
6804
- if (!parsedDate.isValid()) {
6805
- parsedDate = dayjs(inputVal, dateFormat, true);
7645
+ if (defaultTime?.minute !== null && defaultTime?.minute !== undefined) {
7646
+ return defaultTime.minute;
6806
7647
  }
6807
- // If valid, check constraints and update
6808
- if (parsedDate.isValid()) {
6809
- const dateObj = parsedDate.tz(timezone).toDate();
6810
- // Check min/max constraints
6811
- if (minDate && dateObj < minDate) {
6812
- // Invalid: before minDate, reset to prop value
6813
- resetToPropValue();
6814
- return;
6815
- }
6816
- if (maxDate && dateObj > maxDate) {
6817
- // Invalid: after maxDate, reset to prop value
6818
- resetToPropValue();
6819
- return;
6820
- }
6821
- // Valid date - format and update
6822
- const formattedDate = parsedDate.tz(timezone).format(dateFormat);
6823
- const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
6824
- onChange?.(formattedDate);
6825
- setInputValue(formattedDisplay);
7648
+ return null;
7649
+ });
7650
+ const [second, setSecond] = React.useState(() => {
7651
+ if (parsedValue) {
7652
+ return parsedValue.second();
6826
7653
  }
6827
- else {
6828
- // Invalid date - reset to prop value
6829
- resetToPropValue();
7654
+ if (defaultTime?.second !== null && defaultTime?.second !== undefined) {
7655
+ return defaultTime.second;
6830
7656
  }
6831
- };
6832
- // Helper function to reset input to prop value
6833
- const resetToPropValue = () => {
6834
- if (value) {
6835
- const formatted = typeof value === 'string'
6836
- ? dayjs(value).tz(timezone).isValid()
6837
- ? dayjs(value).tz(timezone).format(displayFormat)
6838
- : ''
6839
- : dayjs(value).tz(timezone).format(displayFormat);
6840
- setInputValue(formatted);
7657
+ return showSeconds ? 0 : null;
7658
+ });
7659
+ const [meridiem, setMeridiem] = React.useState(() => {
7660
+ if (parsedValue) {
7661
+ const h = parsedValue.hour();
7662
+ return h < 12 ? 'am' : 'pm';
6841
7663
  }
6842
- else {
6843
- setInputValue('');
7664
+ if (defaultTime?.meridiem !== null && defaultTime?.meridiem !== undefined) {
7665
+ return defaultTime.meridiem;
6844
7666
  }
6845
- };
6846
- const handleInputChange = (e) => {
6847
- // Only update the input value, don't parse yet
6848
- setInputValue(e.target.value);
6849
- };
6850
- const handleInputBlur = () => {
6851
- // Parse and validate when input loses focus
6852
- parseAndValidateInput(inputValue);
6853
- };
6854
- const handleKeyDown = (e) => {
6855
- // Parse and validate when Enter is pressed
6856
- if (e.key === 'Enter') {
6857
- e.preventDefault();
6858
- parseAndValidateInput(inputValue);
7667
+ return is24Hour ? null : 'am';
7668
+ });
7669
+ // Popover state - separate for date, time, and timezone
7670
+ const [datePopoverOpen, setDatePopoverOpen] = React.useState(false);
7671
+ const [timePopoverOpen, setTimePopoverOpen] = React.useState(false);
7672
+ const [timezonePopoverOpen, setTimezonePopoverOpen] = React.useState(false);
7673
+ const [calendarPopoverOpen, setCalendarPopoverOpen] = React.useState(false);
7674
+ // Timezone offset state (controlled or uncontrolled)
7675
+ const [internalTimezoneOffset, setInternalTimezoneOffset] = React.useState(() => {
7676
+ if (controlledTimezoneOffset !== undefined) {
7677
+ return controlledTimezoneOffset;
7678
+ }
7679
+ if (parsedValue) {
7680
+ return parsedValue.format('Z');
7681
+ }
7682
+ // Default to +08:00
7683
+ return '+08:00';
7684
+ });
7685
+ // Use controlled prop if provided, otherwise use internal state
7686
+ const timezoneOffset = controlledTimezoneOffset ?? internalTimezoneOffset;
7687
+ // Update internal state when controlled prop changes
7688
+ React.useEffect(() => {
7689
+ if (controlledTimezoneOffset !== undefined) {
7690
+ setInternalTimezoneOffset(controlledTimezoneOffset);
6859
7691
  }
6860
- };
6861
- const handleDateSelected = ({ date }) => {
6862
- const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
6863
- onChange?.(formattedDate);
6864
- setOpen(false);
6865
- };
6866
- const datePickerContent = (jsxRuntime.jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay }));
6867
- return (jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(InputGroup, { endElement: jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdDateRange, {}) }) }) }), children: jsxRuntime.jsx(react.Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) }) }))] }));
6868
- }
6869
-
6870
- dayjs.extend(utc);
6871
- dayjs.extend(timezone);
6872
- function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
6873
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
6874
- onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6875
- placeholder: 'HH:mm:ss',
6876
- emptyMessage: 'No time found',
6877
- }, }) {
6878
- // Generate time options (every 15 minutes, seconds always 0)
6879
- const timeOptions = React.useMemo(() => {
6880
- const options = [];
6881
- // Get start time for comparison if provided
6882
- let startDateTime = null;
6883
- let shouldFilterByDate = false;
6884
- if (startTime && selectedDate) {
6885
- const startDateObj = dayjs(startTime).tz(timezone);
6886
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6887
- if (startDateObj.isValid() && selectedDateObj.isValid()) {
6888
- startDateTime = startDateObj;
6889
- // Only filter if dates are the same
6890
- shouldFilterByDate =
6891
- startDateObj.format('YYYY-MM-DD') ===
6892
- selectedDateObj.format('YYYY-MM-DD');
7692
+ }, [controlledTimezoneOffset]);
7693
+ // Sync timezone offset when value changes (only if uncontrolled)
7694
+ React.useEffect(() => {
7695
+ if (controlledTimezoneOffset === undefined && parsedValue) {
7696
+ const offsetFromValue = parsedValue.format('Z');
7697
+ if (offsetFromValue !== timezoneOffset) {
7698
+ setInternalTimezoneOffset(offsetFromValue);
6893
7699
  }
6894
7700
  }
6895
- for (let h = 0; h < 24; h++) {
6896
- for (let m = 0; m < 60; m += 15) {
6897
- const timeDisplay = `${h.toString().padStart(2, '0')}:${m
6898
- .toString()
6899
- .padStart(2, '0')}:00`;
6900
- // Filter out times that would result in negative duration (only when dates are the same)
6901
- if (startDateTime && selectedDate && shouldFilterByDate) {
6902
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6903
- const optionDateTime = selectedDateObj
6904
- .hour(h)
6905
- .minute(m)
6906
- .second(0)
6907
- .millisecond(0);
6908
- if (optionDateTime.isBefore(startDateTime)) {
6909
- continue; // Skip this option as it would result in negative duration
6910
- }
6911
- }
6912
- // Calculate duration if startTime is provided
6913
- let durationText;
6914
- if (startDateTime && selectedDate) {
6915
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6916
- const optionDateTime = selectedDateObj
6917
- .hour(h)
6918
- .minute(m)
6919
- .second(0)
6920
- .millisecond(0);
6921
- if (optionDateTime.isValid() &&
6922
- optionDateTime.isAfter(startDateTime)) {
6923
- const diffMs = optionDateTime.diff(startDateTime);
6924
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6925
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6926
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6927
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6928
- let diffText = '';
6929
- if (diffHours > 0) {
6930
- diffText = `${diffHours}h ${diffMinutes}m`;
6931
- }
6932
- else if (diffMinutes > 0) {
6933
- diffText = `${diffMinutes}m ${diffSeconds}s`;
6934
- }
6935
- else {
6936
- diffText = `${diffSeconds}s`;
6937
- }
6938
- durationText = `+${diffText}`;
6939
- }
6940
- }
6941
- }
6942
- options.push({
6943
- label: timeDisplay,
6944
- value: `${h}:${m}:0`,
6945
- hour: h,
6946
- minute: m,
6947
- second: 0,
6948
- searchText: timeDisplay, // Use base time without duration for searching
6949
- durationText,
6950
- });
6951
- }
7701
+ }, [parsedValue, controlledTimezoneOffset, timezoneOffset]);
7702
+ // Sync timezone offset when value changes
7703
+ // Generate timezone offset options (UTC-12 to UTC+14)
7704
+ const timezoneOffsetOptions = React.useMemo(() => {
7705
+ const options = [];
7706
+ for (let offset = -12; offset <= 14; offset++) {
7707
+ const sign = offset >= 0 ? '+' : '-';
7708
+ const hours = Math.abs(offset).toString().padStart(2, '0');
7709
+ const value = `${sign}${hours}:00`;
7710
+ const label = `UTC${sign}${hours}:00`;
7711
+ options.push({ value, label });
6952
7712
  }
6953
7713
  return options;
6954
- }, [startTime, selectedDate, timezone]);
6955
- const { contains } = react.useFilter({ sensitivity: 'base' });
6956
- const { collection, filter } = react.useListCollection({
6957
- initialItems: timeOptions,
6958
- itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
7714
+ }, []);
7715
+ // Create collection for Select
7716
+ const { collection: timezoneCollection } = react.useListCollection({
7717
+ initialItems: timezoneOffsetOptions,
7718
+ itemToString: (item) => item.label,
6959
7719
  itemToValue: (item) => item.value,
6960
- filter: contains,
6961
7720
  });
6962
- // Get current value string for combobox
6963
- const currentValue = React.useMemo(() => {
6964
- if (hour === null || minute === null || second === null) {
6965
- return '';
6966
- }
6967
- return `${hour}:${minute}:${second}`;
6968
- }, [hour, minute, second]);
6969
- // Calculate duration difference
6970
- const durationDiff = React.useMemo(() => {
6971
- if (!startTime ||
6972
- !selectedDate ||
6973
- hour === null ||
6974
- minute === null ||
6975
- second === null) {
6976
- return null;
6977
- }
6978
- const startDateObj = dayjs(startTime).tz(timezone);
6979
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6980
- const currentDateTime = selectedDateObj
6981
- .hour(hour)
6982
- .minute(minute)
6983
- .second(second ?? 0)
6984
- .millisecond(0);
6985
- if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6986
- return null;
6987
- }
6988
- const diffMs = currentDateTime.diff(startDateObj);
6989
- if (diffMs < 0) {
6990
- return null;
7721
+ // Ensure timezoneOffset value is valid (exists in collection)
7722
+ const validTimezoneOffset = React.useMemo(() => {
7723
+ if (!timezoneOffset)
7724
+ return undefined;
7725
+ const exists = timezoneOffsetOptions.some((opt) => opt.value === timezoneOffset);
7726
+ return exists ? timezoneOffset : undefined;
7727
+ }, [timezoneOffset, timezoneOffsetOptions]);
7728
+ // Date input state
7729
+ const [dateInputValue, setDateInputValue] = React.useState('');
7730
+ // Sync date input value with selected date
7731
+ React.useEffect(() => {
7732
+ if (selectedDate) {
7733
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7734
+ setDateInputValue(formatted);
6991
7735
  }
6992
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6993
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6994
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6995
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6996
- let diffText = '';
6997
- if (diffHours > 0) {
6998
- diffText = `${diffHours}h ${diffMinutes}m`;
6999
- }
7000
- else if (diffMinutes > 0) {
7001
- diffText = `${diffMinutes}m ${diffSeconds}s`;
7002
- }
7003
- else {
7004
- diffText = `${diffSeconds}s`;
7005
- }
7006
- return `+${diffText}`;
7736
+ else {
7737
+ setDateInputValue('');
7007
7738
  }
7008
- return null;
7009
- }, [hour, minute, second, startTime, selectedDate, timezone]);
7010
- const handleClear = () => {
7011
- setHour(null);
7012
- setMinute(null);
7013
- setSecond(null);
7014
- filter(''); // Reset filter to show all options
7015
- onChange({ hour: null, minute: null, second: null });
7016
- };
7017
- const handleValueChange = (details) => {
7018
- if (details.value.length === 0) {
7019
- handleClear();
7739
+ }, [selectedDate, tz]);
7740
+ // Parse and validate date input
7741
+ const parseAndValidateDateInput = (inputVal) => {
7742
+ // If empty, clear the value
7743
+ if (!inputVal.trim()) {
7744
+ setSelectedDate(null);
7745
+ updateDateTime(null, hour, minute, second, meridiem);
7020
7746
  return;
7021
7747
  }
7022
- const selectedValue = details.value[0];
7023
- const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
7024
- if (selectedOption) {
7025
- setHour(selectedOption.hour);
7026
- setMinute(selectedOption.minute);
7027
- setSecond(selectedOption.second);
7028
- filter(''); // Reset filter after selection
7029
- onChange({
7030
- hour: selectedOption.hour,
7031
- minute: selectedOption.minute,
7032
- second: selectedOption.second,
7033
- });
7034
- }
7035
- };
7036
- // Parse input value and update state
7037
- const parseAndCommitInput = (value) => {
7038
- const trimmedValue = value.trim();
7039
- // Filter the collection based on input
7040
- filter(trimmedValue);
7041
- if (!trimmedValue) {
7042
- return;
7748
+ // Try parsing with common date formats
7749
+ let parsedDate = dayjs(inputVal, 'YYYY-MM-DD', true);
7750
+ // If that fails, try other common formats
7751
+ if (!parsedDate.isValid()) {
7752
+ parsedDate = dayjs(inputVal);
7043
7753
  }
7044
- // Parse HH:mm:ss or HH:mm format
7045
- const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
7046
- const match = trimmedValue.match(timePattern);
7047
- if (match) {
7048
- const parsedHour = parseInt(match[1], 10);
7049
- const parsedMinute = parseInt(match[2], 10);
7050
- const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
7051
- // Validate ranges
7052
- if (parsedHour >= 0 &&
7053
- parsedHour <= 23 &&
7054
- parsedMinute >= 0 &&
7055
- parsedMinute <= 59 &&
7056
- parsedSecond >= 0 &&
7057
- parsedSecond <= 59) {
7058
- setHour(parsedHour);
7059
- setMinute(parsedMinute);
7060
- setSecond(parsedSecond);
7061
- onChange({
7062
- hour: parsedHour,
7063
- minute: parsedMinute,
7064
- second: parsedSecond,
7065
- });
7754
+ // If valid, check constraints and update
7755
+ if (parsedDate.isValid()) {
7756
+ const dateObj = parsedDate.tz(tz).toDate();
7757
+ // Check min/max constraints
7758
+ if (minDate && dateObj < minDate) {
7759
+ // Invalid: before minDate, reset to current selected date
7760
+ if (selectedDate) {
7761
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7762
+ setDateInputValue(formatted);
7763
+ }
7764
+ else {
7765
+ setDateInputValue('');
7766
+ }
7066
7767
  return;
7067
7768
  }
7068
- }
7069
- else {
7070
- // Try to parse formats like "123045" (HHmmss) or "1230" (HHmm)
7071
- const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
7072
- if (numbersOnly.length >= 4) {
7073
- const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
7074
- const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
7075
- const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
7076
- // Validate ranges
7077
- if (parsedHour >= 0 &&
7078
- parsedHour <= 23 &&
7079
- parsedMinute >= 0 &&
7080
- parsedMinute <= 59 &&
7081
- parsedSecond >= 0 &&
7082
- parsedSecond <= 59) {
7083
- setHour(parsedHour);
7084
- setMinute(parsedMinute);
7085
- setSecond(parsedSecond);
7086
- onChange({
7087
- hour: parsedHour,
7088
- minute: parsedMinute,
7089
- second: parsedSecond,
7090
- });
7091
- return;
7769
+ if (maxDate && dateObj > maxDate) {
7770
+ // Invalid: after maxDate, reset to current selected date
7771
+ if (selectedDate) {
7772
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7773
+ setDateInputValue(formatted);
7774
+ }
7775
+ else {
7776
+ setDateInputValue('');
7092
7777
  }
7778
+ return;
7093
7779
  }
7780
+ // Valid date - update selected date
7781
+ setSelectedDate(dateObj);
7782
+ updateDateTime(dateObj, hour, minute, second, meridiem);
7783
+ // Format and update input value
7784
+ const formatted = parsedDate.tz(tz).format('YYYY-MM-DD');
7785
+ setDateInputValue(formatted);
7094
7786
  }
7095
- // Parse failed, select first result
7096
- selectFirstResult();
7097
- };
7098
- // Select first result from filtered collection
7099
- const selectFirstResult = () => {
7100
- if (collection.items.length > 0) {
7101
- const firstItem = collection.items[0];
7102
- setHour(firstItem.hour);
7103
- setMinute(firstItem.minute);
7104
- setSecond(firstItem.second);
7105
- filter(''); // Reset filter after selection
7106
- onChange({
7107
- hour: firstItem.hour,
7108
- minute: firstItem.minute,
7109
- second: firstItem.second,
7110
- });
7787
+ else {
7788
+ // Invalid date - reset to current selected date
7789
+ if (selectedDate) {
7790
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7791
+ setDateInputValue(formatted);
7792
+ }
7793
+ else {
7794
+ setDateInputValue('');
7795
+ }
7111
7796
  }
7112
7797
  };
7113
- const handleInputValueChange = (details) => {
7114
- // Filter the collection based on input, but don't parse yet
7115
- filter(details.inputValue);
7798
+ const handleDateInputChange = (e) => {
7799
+ setDateInputValue(e.target.value);
7116
7800
  };
7117
- const handleFocus = (e) => {
7118
- // Select all text when focusing
7119
- e.target.select();
7801
+ const handleDateInputBlur = () => {
7802
+ parseAndValidateDateInput(dateInputValue);
7120
7803
  };
7121
- const handleBlur = (e) => {
7122
- // Parse and commit the input value when losing focus
7123
- const inputValue = e.target.value;
7124
- if (inputValue) {
7125
- parseAndCommitInput(inputValue);
7126
- }
7127
- };
7128
- const handleKeyDown = (e) => {
7129
- // Commit input on Enter key
7804
+ const handleDateInputKeyDown = (e) => {
7130
7805
  if (e.key === 'Enter') {
7131
7806
  e.preventDefault();
7132
- const inputValue = e.currentTarget.value;
7133
- if (inputValue) {
7134
- parseAndCommitInput(inputValue);
7135
- }
7136
- // Blur the input
7137
- e.currentTarget?.blur();
7807
+ parseAndValidateDateInput(dateInputValue);
7138
7808
  }
7139
7809
  };
7140
- return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: labels.placeholder, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: labels.emptyMessage }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsxRuntime.jsx(react.Text, { flex: 1, children: item.label }), item.durationText && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: item.durationText }) }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: durationDiff }) })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdCancel, {}) }) })] }) }));
7141
- }
7142
-
7143
- dayjs.extend(utc);
7144
- dayjs.extend(timezone);
7145
- function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
7146
- monthNamesShort: [
7147
- 'Jan',
7148
- 'Feb',
7149
- 'Mar',
7150
- 'Apr',
7151
- 'May',
7152
- 'Jun',
7153
- 'Jul',
7154
- 'Aug',
7155
- 'Sep',
7156
- 'Oct',
7157
- 'Nov',
7158
- 'Dec',
7159
- ],
7160
- weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
7161
- backButtonLabel: 'Back',
7162
- forwardButtonLabel: 'Next',
7163
- }, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, }) {
7164
- console.log('[DateTimePicker] Component initialized with props:', {
7165
- value,
7166
- format,
7167
- showSeconds,
7168
- timezone,
7169
- startTime,
7170
- minDate,
7171
- maxDate,
7172
- });
7173
- // Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
7174
- const getDateString = React.useCallback((val) => {
7175
- if (!val)
7176
- return '';
7177
- const dateObj = dayjs(val).tz(timezone);
7178
- return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
7179
- }, [timezone]);
7180
- const [selectedDate, setSelectedDate] = React.useState(getDateString(value));
7181
- // Helper to get time values from value prop with timezone
7182
- const getTimeFromValue = React.useCallback((val) => {
7183
- console.log('[DateTimePicker] getTimeFromValue called:', {
7184
- val,
7185
- timezone,
7186
- showSeconds,
7187
- });
7188
- if (!val) {
7189
- console.log('[DateTimePicker] No value provided, returning nulls');
7190
- return {
7191
- hour12: null,
7192
- minute: null,
7193
- meridiem: null,
7194
- hour24: null,
7195
- second: null,
7196
- };
7197
- }
7198
- const dateObj = dayjs(val).tz(timezone);
7199
- console.log('[DateTimePicker] Parsed date object:', {
7200
- original: val,
7201
- timezone,
7202
- isValid: dateObj.isValid(),
7203
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7204
- hour24: dateObj.hour(),
7205
- minute: dateObj.minute(),
7206
- second: dateObj.second(),
7207
- });
7208
- if (!dateObj.isValid()) {
7209
- console.log('[DateTimePicker] Invalid date object, returning nulls');
7210
- return {
7211
- hour12: null,
7212
- minute: null,
7213
- meridiem: null,
7214
- hour24: null,
7215
- second: null,
7216
- };
7217
- }
7218
- const hour24Value = dateObj.hour();
7219
- const hour12Value = hour24Value % 12 || 12;
7220
- const minuteValue = dateObj.minute();
7221
- const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
7222
- const secondValue = showSeconds ? dateObj.second() : null;
7223
- const result = {
7224
- hour12: hour12Value,
7225
- minute: minuteValue,
7226
- meridiem: meridiemValue,
7227
- hour24: hour24Value,
7228
- second: secondValue,
7229
- };
7230
- console.log('[DateTimePicker] Extracted time values:', result);
7231
- return result;
7232
- }, [timezone, showSeconds]);
7233
- const initialTime = getTimeFromValue(value);
7234
- console.log('[DateTimePicker] Initial time from value:', {
7235
- value,
7236
- initialTime,
7237
- });
7238
- // Time state for 12-hour format
7239
- const [hour12, setHour12] = React.useState(initialTime.hour12);
7240
- const [minute, setMinute] = React.useState(initialTime.minute);
7241
- const [meridiem, setMeridiem] = React.useState(initialTime.meridiem);
7242
- // Time state for 24-hour format
7243
- const [hour24, setHour24] = React.useState(initialTime.hour24);
7244
- const [second, setSecond] = React.useState(initialTime.second);
7245
- // Sync selectedDate and time states when value prop changes
7246
- React.useEffect(() => {
7247
- console.log('[DateTimePicker] useEffect triggered - value changed:', {
7248
- value,
7249
- timezone,
7250
- format,
7251
- });
7252
- // If value is null, undefined, or invalid, clear all fields
7253
- if (!value || value === null || value === undefined) {
7254
- console.log('[DateTimePicker] Value is null/undefined, clearing all fields');
7255
- setSelectedDate('');
7256
- setHour12(null);
7257
- setMinute(null);
7258
- setMeridiem(null);
7259
- setHour24(null);
7260
- setSecond(null);
7261
- return;
7262
- }
7263
- // Check if value is valid
7264
- const dateObj = dayjs(value).tz(timezone);
7265
- if (!dateObj.isValid()) {
7266
- console.log('[DateTimePicker] Invalid value, clearing all fields');
7267
- setSelectedDate('');
7268
- setHour12(null);
7269
- setMinute(null);
7270
- setMeridiem(null);
7271
- setHour24(null);
7272
- setSecond(null);
7273
- return;
7810
+ // Helper functions to get dates in the correct timezone
7811
+ const getToday = () => dayjs().tz(tz).startOf('day').toDate();
7812
+ const getYesterday = () => dayjs().tz(tz).subtract(1, 'day').startOf('day').toDate();
7813
+ const getTomorrow = () => dayjs().tz(tz).add(1, 'day').startOf('day').toDate();
7814
+ const getPlus7Days = () => dayjs().tz(tz).add(7, 'day').startOf('day').toDate();
7815
+ // Check if a date is within min/max constraints
7816
+ const isDateValid = (date) => {
7817
+ if (minDate) {
7818
+ const minDateStart = dayjs(minDate).tz(tz).startOf('day').toDate();
7819
+ const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
7820
+ if (dateStart < minDateStart)
7821
+ return false;
7274
7822
  }
7275
- const dateString = getDateString(value);
7276
- console.log('[DateTimePicker] Setting selectedDate:', dateString);
7277
- setSelectedDate(dateString);
7278
- const timeData = getTimeFromValue(value);
7279
- console.log('[DateTimePicker] Updating time states:', {
7280
- timeData,
7281
- });
7282
- setHour12(timeData.hour12);
7283
- setMinute(timeData.minute);
7284
- setMeridiem(timeData.meridiem);
7285
- setHour24(timeData.hour24);
7286
- setSecond(timeData.second);
7287
- }, [value, getTimeFromValue, getDateString, timezone]);
7288
- const handleDateChange = (date) => {
7289
- console.log('[DateTimePicker] handleDateChange called:', {
7290
- date,
7291
- timezone,
7292
- showSeconds,
7293
- currentTimeStates: { hour12, minute, meridiem, hour24, second },
7294
- });
7295
- // If date is empty or invalid, clear all fields
7296
- if (!date || date === '') {
7297
- console.log('[DateTimePicker] Empty date, clearing all fields');
7298
- setSelectedDate('');
7299
- setHour12(null);
7300
- setMinute(null);
7301
- setMeridiem(null);
7302
- setHour24(null);
7303
- setSecond(null);
7304
- onChange?.(undefined);
7305
- return;
7823
+ if (maxDate) {
7824
+ const maxDateStart = dayjs(maxDate).tz(tz).startOf('day').toDate();
7825
+ const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
7826
+ if (dateStart > maxDateStart)
7827
+ return false;
7306
7828
  }
7307
- setSelectedDate(date);
7308
- // Parse the date string (YYYY-MM-DD) in the specified timezone
7309
- const dateObj = dayjs.tz(date, timezone);
7310
- console.log('[DateTimePicker] Parsed date object:', {
7311
- date,
7312
- timezone,
7313
- isValid: dateObj.isValid(),
7314
- isoString: dateObj.toISOString(),
7315
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7316
- });
7317
- if (!dateObj.isValid()) {
7318
- console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
7319
- setSelectedDate('');
7320
- setHour12(null);
7321
- setMinute(null);
7322
- setMeridiem(null);
7323
- setHour24(null);
7324
- setSecond(null);
7325
- onChange?.(undefined);
7326
- return;
7829
+ return true;
7830
+ };
7831
+ // Handle quick action button clicks
7832
+ const handleQuickActionClick = (date) => {
7833
+ if (isDateValid(date)) {
7834
+ setSelectedDate(date);
7835
+ updateDateTime(date, hour, minute, second, meridiem);
7836
+ // Close the calendar popover if open
7837
+ setCalendarPopoverOpen(false);
7327
7838
  }
7328
- // When showSeconds is false, ignore seconds from the date
7329
- if (!showSeconds) {
7330
- const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
7331
- console.log('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
7332
- updateDateTime(dateWithoutSeconds);
7839
+ };
7840
+ // Display text for buttons
7841
+ const dateDisplayText = React.useMemo(() => {
7842
+ if (!selectedDate)
7843
+ return 'Select date';
7844
+ return dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7845
+ }, [selectedDate, tz]);
7846
+ const timeDisplayText = React.useMemo(() => {
7847
+ if (hour === null || minute === null)
7848
+ return 'Select time';
7849
+ if (is24Hour) {
7850
+ // 24-hour format: never show meridiem, always use 24-hour format (0-23)
7851
+ const hour24 = hour >= 0 && hour <= 23 ? hour : hour % 24;
7852
+ const s = second ?? 0;
7853
+ if (showSeconds) {
7854
+ return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
7855
+ }
7856
+ return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
7333
7857
  }
7334
7858
  else {
7335
- const dateWithSeconds = dateObj.toISOString();
7336
- console.log('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
7337
- updateDateTime(dateWithSeconds);
7859
+ // 12-hour format: always show meridiem (AM/PM)
7860
+ const hour12 = hour >= 1 && hour <= 12 ? hour : hour % 12;
7861
+ if (meridiem === null)
7862
+ return 'Select time';
7863
+ const hourDisplay = hour12.toString();
7864
+ const minuteDisplay = minute.toString().padStart(2, '0');
7865
+ return `${hourDisplay}:${minuteDisplay} ${meridiem.toUpperCase()}`;
7866
+ }
7867
+ }, [hour, minute, second, meridiem, is24Hour, showSeconds]);
7868
+ const timezoneDisplayText = React.useMemo(() => {
7869
+ if (!showTimezoneSelector)
7870
+ return '';
7871
+ // Show offset as is (e.g., "+08:00")
7872
+ return timezoneOffset;
7873
+ }, [timezoneOffset, showTimezoneSelector]);
7874
+ // Update selectedDate when value changes externally
7875
+ React.useEffect(() => {
7876
+ if (parsedValue) {
7877
+ setSelectedDate(parsedValue.toDate());
7878
+ setHour(parsedValue.hour());
7879
+ setMinute(parsedValue.minute());
7880
+ setSecond(parsedValue.second());
7881
+ if (!is24Hour) {
7882
+ const h = parsedValue.hour();
7883
+ setMeridiem(h < 12 ? 'am' : 'pm');
7884
+ }
7338
7885
  }
7339
- };
7340
- const handleTimeChange = (timeData) => {
7341
- console.log('[DateTimePicker] handleTimeChange called:', {
7342
- timeData,
7343
- format,
7344
- selectedDate,
7345
- timezone,
7346
- });
7347
- if (format === 'iso-date-time') {
7348
- const data = timeData;
7349
- console.log('[DateTimePicker] ISO format - setting 24-hour time:', data);
7350
- setHour24(data.hour);
7351
- setMinute(data.minute);
7352
- if (showSeconds) {
7353
- setSecond(data.second ?? null);
7886
+ }, [parsedValue, is24Hour]);
7887
+ // Combine date and time and call onChange
7888
+ const updateDateTime = (newDate, newHour, newMinute, newSecond, newMeridiem, timezoneOffsetOverride) => {
7889
+ if (!newDate || newHour === null || newMinute === null) {
7890
+ onChange?.(undefined);
7891
+ return;
7892
+ }
7893
+ // Convert 12-hour to 24-hour if needed
7894
+ let hour24 = newHour;
7895
+ if (!is24Hour && newMeridiem) {
7896
+ // In 12-hour format, hour should be 1-12
7897
+ // If hour is > 12, it might already be in 24-hour format, convert it first
7898
+ let hour12 = newHour;
7899
+ if (newHour > 12) {
7900
+ // Hour is in 24-hour format, convert to 12-hour first
7901
+ if (newHour === 12) {
7902
+ hour12 = 12;
7903
+ }
7904
+ else {
7905
+ hour12 = newHour - 12;
7906
+ }
7907
+ }
7908
+ // Now convert 12-hour to 24-hour format (0-23)
7909
+ if (newMeridiem === 'am') {
7910
+ if (hour12 === 12) {
7911
+ hour24 = 0; // 12 AM = 0:00
7912
+ }
7913
+ else {
7914
+ hour24 = hour12; // 1-11 AM = 1-11
7915
+ }
7354
7916
  }
7355
7917
  else {
7356
- // Ignore seconds - always set to null when showSeconds is false
7357
- setSecond(null);
7918
+ // PM
7919
+ if (hour12 === 12) {
7920
+ hour24 = 12; // 12 PM = 12:00
7921
+ }
7922
+ else {
7923
+ hour24 = hour12 + 12; // 1-11 PM = 13-23
7924
+ }
7358
7925
  }
7359
7926
  }
7360
- else {
7361
- const data = timeData;
7362
- console.log('[DateTimePicker] 12-hour format - setting time:', data);
7363
- setHour12(data.hour);
7364
- setMinute(data.minute);
7365
- setMeridiem(data.meridiem);
7366
- }
7367
- // Use selectedDate if valid, otherwise clear all fields
7368
- if (!selectedDate || !dayjs(selectedDate).isValid()) {
7369
- console.log('[DateTimePicker] No valid selectedDate, clearing all fields');
7370
- setSelectedDate('');
7371
- setHour12(null);
7372
- setMinute(null);
7373
- setMeridiem(null);
7374
- setHour24(null);
7375
- setSecond(null);
7927
+ else if (!is24Hour && !newMeridiem) {
7928
+ // If in 12-hour mode but no meridiem, assume the hour is already in 12-hour format
7929
+ // and default to AM (or keep as is if it's a valid 12-hour value)
7930
+ // This shouldn't happen in normal flow, but handle it gracefully
7931
+ hour24 = newHour;
7932
+ }
7933
+ // If timezone selector is enabled, create date-time without timezone conversion
7934
+ // to ensure the selected timestamp matches the picker values exactly
7935
+ if (showTimezoneSelector) {
7936
+ // Use override if provided, otherwise use state value
7937
+ const offsetToUse = timezoneOffsetOverride ?? timezoneOffset;
7938
+ // Create date-time from the Date object without timezone conversion
7939
+ // Extract year, month, day from the date
7940
+ const year = newDate.getFullYear();
7941
+ const month = newDate.getMonth();
7942
+ const day = newDate.getDate();
7943
+ // Create a date-time string with the exact values from the picker
7944
+ const formattedDateTime = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}T${String(hour24).padStart(2, '0')}:${String(newMinute).padStart(2, '0')}:${String(newSecond ?? 0).padStart(2, '0')}`;
7945
+ onChange?.(`${formattedDateTime}${offsetToUse}`);
7946
+ return;
7947
+ }
7948
+ // Normal mode: use timezone conversion
7949
+ let dateTime = dayjs(newDate)
7950
+ .tz(tz)
7951
+ .hour(hour24)
7952
+ .minute(newMinute)
7953
+ .second(newSecond ?? 0)
7954
+ .millisecond(0);
7955
+ if (!dateTime.isValid()) {
7376
7956
  onChange?.(undefined);
7377
7957
  return;
7378
7958
  }
7379
- const dateObj = dayjs(selectedDate).tz(timezone);
7380
- if (dateObj.isValid()) {
7381
- updateDateTime(dateObj.toISOString(), timeData);
7959
+ // Format based on format prop
7960
+ if (format === 'iso-date-time') {
7961
+ onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ss'));
7382
7962
  }
7383
7963
  else {
7384
- console.warn('[DateTimePicker] Invalid date object in handleTimeChange, clearing fields');
7385
- setSelectedDate('');
7386
- setHour12(null);
7387
- setMinute(null);
7388
- setMeridiem(null);
7389
- setHour24(null);
7390
- setSecond(null);
7391
- onChange?.(undefined);
7964
+ // date-time format with timezone
7965
+ onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ssZ'));
7392
7966
  }
7393
7967
  };
7394
- const updateDateTime = (date, timeData) => {
7395
- console.log('[DateTimePicker] updateDateTime called:', {
7396
- date,
7397
- timeData,
7398
- format,
7399
- currentStates: { hour12, minute, meridiem, hour24, second },
7400
- });
7401
- if (!date || date === null || date === undefined) {
7402
- console.log('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
7403
- setSelectedDate('');
7404
- setHour12(null);
7405
- setMinute(null);
7406
- setMeridiem(null);
7407
- setHour24(null);
7408
- setSecond(null);
7409
- onChange?.(undefined);
7410
- return;
7968
+ // Handle date selection
7969
+ const handleDateSelected = ({ date, }) => {
7970
+ setSelectedDate(date);
7971
+ updateDateTime(date, hour, minute, second, meridiem);
7972
+ };
7973
+ // Handle time change
7974
+ const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
7975
+ setHour(newHour);
7976
+ setMinute(newMinute);
7977
+ if (is24Hour) {
7978
+ setSecond(newSecond);
7411
7979
  }
7412
- // use dayjs to convert the date to the timezone
7413
- const dateObj = dayjs(date).tz(timezone);
7414
- if (!dateObj.isValid()) {
7415
- console.warn('[DateTimePicker] Invalid date object in updateDateTime, clearing fields:', date);
7416
- setSelectedDate('');
7417
- setHour12(null);
7418
- setMinute(null);
7419
- setMeridiem(null);
7420
- setHour24(null);
7421
- setSecond(null);
7422
- onChange?.(undefined);
7423
- return;
7980
+ else {
7981
+ setMeridiem(newMeridiem);
7424
7982
  }
7425
- const newDate = dateObj.toDate();
7426
- if (format === 'iso-date-time') {
7427
- const data = timeData;
7428
- // Use timeData values if provided, otherwise fall back to current state
7429
- // But if timeData is explicitly provided with nulls, we need to check if all are null
7430
- const h = data !== undefined ? data.hour : hour24;
7431
- const m = data !== undefined ? data.minute : minute;
7432
- // Always ignore seconds when showSeconds is false - set to 0
7433
- const s = showSeconds
7434
- ? data !== undefined
7435
- ? data.second ?? null
7436
- : second ?? 0
7437
- : 0;
7438
- // If all time values are null, clear the value
7439
- if (h === null && m === null && (showSeconds ? s === null : true)) {
7440
- console.log('[DateTimePicker] All time values are null, clearing value');
7441
- onChange?.(undefined);
7442
- return;
7983
+ if (selectedDate) {
7984
+ updateDateTime(selectedDate, newHour, newMinute, newSecond, newMeridiem);
7985
+ }
7986
+ };
7987
+ // Calendar hook
7988
+ const calendarProps = useCalendar({
7989
+ selected: selectedDate || undefined,
7990
+ date: selectedDate || undefined,
7991
+ minDate,
7992
+ maxDate,
7993
+ monthsToDisplay: 1,
7994
+ onDateSelected: handleDateSelected,
7995
+ });
7996
+ // Convert DateTimePickerLabels to DatePickerLabels format
7997
+ const calendarLabels = React.useMemo(() => ({
7998
+ monthNamesShort: labels.monthNamesShort || [
7999
+ 'Jan',
8000
+ 'Feb',
8001
+ 'Mar',
8002
+ 'Apr',
8003
+ 'May',
8004
+ 'Jun',
8005
+ 'Jul',
8006
+ 'Aug',
8007
+ 'Sep',
8008
+ 'Oct',
8009
+ 'Nov',
8010
+ 'Dec',
8011
+ ],
8012
+ weekdayNamesShort: labels.weekdayNamesShort || [
8013
+ 'Sun',
8014
+ 'Mon',
8015
+ 'Tue',
8016
+ 'Wed',
8017
+ 'Thu',
8018
+ 'Fri',
8019
+ 'Sat',
8020
+ ],
8021
+ backButtonLabel: labels.backButtonLabel || 'Back',
8022
+ forwardButtonLabel: labels.forwardButtonLabel || 'Forward',
8023
+ todayLabel: quickActionLabels.today || 'Today',
8024
+ yesterdayLabel: quickActionLabels.yesterday || 'Yesterday',
8025
+ tomorrowLabel: quickActionLabels.tomorrow || 'Tomorrow',
8026
+ }), [labels, quickActionLabels]);
8027
+ // Generate time options
8028
+ const timeOptions = React.useMemo(() => {
8029
+ const options = [];
8030
+ // Get start time for comparison if provided
8031
+ let startDateTime = null;
8032
+ let shouldFilterByDate = false;
8033
+ if (startTime && selectedDate) {
8034
+ const startDateObj = dayjs(startTime).tz(tz);
8035
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8036
+ if (startDateObj.isValid() && selectedDateObj.isValid()) {
8037
+ startDateTime = startDateObj;
8038
+ shouldFilterByDate =
8039
+ startDateObj.format('YYYY-MM-DD') ===
8040
+ selectedDateObj.format('YYYY-MM-DD');
8041
+ }
8042
+ }
8043
+ if (is24Hour) {
8044
+ // Generate 24-hour format options
8045
+ for (let h = 0; h < 24; h++) {
8046
+ for (let m = 0; m < 60; m += 15) {
8047
+ // Filter out times that would result in negative duration
8048
+ if (startDateTime && selectedDate && shouldFilterByDate) {
8049
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8050
+ const optionDateTime = selectedDateObj
8051
+ .hour(h)
8052
+ .minute(m)
8053
+ .second(0)
8054
+ .millisecond(0);
8055
+ if (optionDateTime.isBefore(startDateTime)) {
8056
+ continue;
8057
+ }
8058
+ }
8059
+ // Calculate duration if startTime is provided
8060
+ let durationText;
8061
+ if (startDateTime && selectedDate) {
8062
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8063
+ const optionDateTime = selectedDateObj
8064
+ .hour(h)
8065
+ .minute(m)
8066
+ .second(0)
8067
+ .millisecond(0);
8068
+ if (optionDateTime.isValid() &&
8069
+ optionDateTime.isAfter(startDateTime)) {
8070
+ const diffMs = optionDateTime.diff(startDateTime);
8071
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
8072
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
8073
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
8074
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
8075
+ let diffText = '';
8076
+ if (diffHours > 0) {
8077
+ diffText = `${diffHours}h ${diffMinutes}m`;
8078
+ }
8079
+ else if (diffMinutes > 0) {
8080
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
8081
+ }
8082
+ else {
8083
+ diffText = `${diffSeconds}s`;
8084
+ }
8085
+ durationText = `+${diffText}`;
8086
+ }
8087
+ }
8088
+ }
8089
+ const s = showSeconds ? 0 : 0;
8090
+ const timeDisplay = showSeconds
8091
+ ? `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`
8092
+ : `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
8093
+ options.push({
8094
+ label: timeDisplay,
8095
+ value: `${h}:${m}:${s}`,
8096
+ hour: h,
8097
+ minute: m,
8098
+ second: s,
8099
+ searchText: timeDisplay,
8100
+ durationText,
8101
+ });
8102
+ }
7443
8103
  }
7444
- console.log('[DateTimePicker] ISO format - setting time on date:', {
7445
- h,
7446
- m,
7447
- s,
7448
- showSeconds,
7449
- });
7450
- if (h !== null)
7451
- newDate.setHours(h);
7452
- if (m !== null)
7453
- newDate.setMinutes(m);
7454
- newDate.setSeconds(s ?? 0);
7455
8104
  }
7456
8105
  else {
7457
- const data = timeData;
7458
- console.log('[DateTimePicker] Processing 12-hour format:', {
7459
- 'data !== undefined': data !== undefined,
7460
- 'data?.hour': data?.hour,
7461
- 'data?.minute': data?.minute,
7462
- 'data?.meridiem': data?.meridiem,
7463
- 'current hour12': hour12,
7464
- 'current minute': minute,
7465
- 'current meridiem': meridiem,
7466
- });
7467
- // Use timeData values if provided, otherwise fall back to current state
7468
- const h = data !== undefined ? data.hour : hour12;
7469
- const m = data !== undefined ? data.minute : minute;
7470
- const mer = data !== undefined ? data.meridiem : meridiem;
7471
- console.log('[DateTimePicker] Resolved time values:', { h, m, mer });
7472
- // If all time values are null, clear the value
7473
- if (h === null && m === null && mer === null) {
7474
- console.log('[DateTimePicker] All time values are null, clearing value');
7475
- onChange?.(undefined);
7476
- return;
8106
+ // Generate 12-hour format options
8107
+ for (let h = 1; h <= 12; h++) {
8108
+ for (let m = 0; m < 60; m += 15) {
8109
+ for (const mer of ['am', 'pm']) {
8110
+ // Convert 12-hour to 24-hour for comparison
8111
+ let hour24 = h;
8112
+ if (mer === 'am' && h === 12)
8113
+ hour24 = 0;
8114
+ else if (mer === 'pm' && h < 12)
8115
+ hour24 = h + 12;
8116
+ // Filter out times that would result in negative duration
8117
+ if (startDateTime && selectedDate && shouldFilterByDate) {
8118
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8119
+ const optionDateTime = selectedDateObj
8120
+ .hour(hour24)
8121
+ .minute(m)
8122
+ .second(0)
8123
+ .millisecond(0);
8124
+ if (optionDateTime.isBefore(startDateTime)) {
8125
+ continue;
8126
+ }
8127
+ }
8128
+ // Calculate duration if startTime is provided
8129
+ let durationText;
8130
+ if (startDateTime && selectedDate) {
8131
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8132
+ const optionDateTime = selectedDateObj
8133
+ .hour(hour24)
8134
+ .minute(m)
8135
+ .second(0)
8136
+ .millisecond(0);
8137
+ if (optionDateTime.isValid() &&
8138
+ optionDateTime.isAfter(startDateTime)) {
8139
+ const diffMs = optionDateTime.diff(startDateTime);
8140
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
8141
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
8142
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
8143
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
8144
+ let diffText = '';
8145
+ if (diffHours > 0) {
8146
+ diffText = `${diffHours}h ${diffMinutes}m`;
8147
+ }
8148
+ else if (diffMinutes > 0) {
8149
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
8150
+ }
8151
+ else {
8152
+ diffText = `${diffSeconds}s`;
8153
+ }
8154
+ durationText = `+${diffText}`;
8155
+ }
8156
+ }
8157
+ }
8158
+ const hourDisplay = h.toString();
8159
+ const minuteDisplay = m.toString().padStart(2, '0');
8160
+ const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
8161
+ options.push({
8162
+ label: timeDisplay,
8163
+ value: `${h}:${m}:${mer}`,
8164
+ hour: h,
8165
+ minute: m,
8166
+ meridiem: mer,
8167
+ searchText: timeDisplay,
8168
+ durationText,
8169
+ });
8170
+ }
8171
+ }
7477
8172
  }
7478
- console.log('[DateTimePicker] 12-hour format - converting time:', {
7479
- h,
7480
- m,
7481
- mer,
8173
+ // Sort 12-hour options by time
8174
+ return options.sort((a, b) => {
8175
+ const a12 = a;
8176
+ const b12 = b;
8177
+ let hour24A = a12.hour;
8178
+ if (a12.meridiem === 'am' && a12.hour === 12)
8179
+ hour24A = 0;
8180
+ else if (a12.meridiem === 'pm' && a12.hour < 12)
8181
+ hour24A = a12.hour + 12;
8182
+ let hour24B = b12.hour;
8183
+ if (b12.meridiem === 'am' && b12.hour === 12)
8184
+ hour24B = 0;
8185
+ else if (b12.meridiem === 'pm' && b12.hour < 12)
8186
+ hour24B = b12.hour + 12;
8187
+ if (hour24A !== hour24B) {
8188
+ return hour24A - hour24B;
8189
+ }
8190
+ return a12.minute - b12.minute;
7482
8191
  });
7483
- if (h !== null && mer !== null) {
7484
- let hour24 = h;
7485
- if (mer === 'am' && h === 12)
7486
- hour24 = 0;
7487
- else if (mer === 'pm' && h < 12)
7488
- hour24 = h + 12;
7489
- console.log('[DateTimePicker] Converted to 24-hour:', {
7490
- h,
7491
- mer,
7492
- hour24,
7493
- });
7494
- newDate.setHours(hour24);
8192
+ }
8193
+ return options;
8194
+ }, [startTime, selectedDate, tz, is24Hour, showSeconds]);
8195
+ // Time picker combobox setup
8196
+ const itemToString = React.useMemo(() => {
8197
+ return (item) => {
8198
+ return item.searchText;
8199
+ };
8200
+ }, []);
8201
+ const { contains } = react.useFilter({ sensitivity: 'base' });
8202
+ const customTimeFilter = React.useMemo(() => {
8203
+ if (is24Hour) {
8204
+ return contains;
8205
+ }
8206
+ return (itemText, filterText) => {
8207
+ if (!filterText) {
8208
+ return true;
7495
8209
  }
7496
- else {
7497
- console.log('[DateTimePicker] Skipping hour update - h or mer is null:', {
7498
- h,
7499
- mer,
7500
- });
8210
+ const lowerItemText = itemText.toLowerCase();
8211
+ const lowerFilterText = filterText.toLowerCase();
8212
+ if (lowerItemText.includes(lowerFilterText)) {
8213
+ return true;
7501
8214
  }
7502
- if (m !== null) {
7503
- newDate.setMinutes(m);
8215
+ const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
8216
+ if (!item || !('meridiem' in item)) {
8217
+ return false;
7504
8218
  }
7505
- else {
7506
- console.log('[DateTimePicker] Skipping minute update - m is null');
8219
+ let hour24 = item.hour;
8220
+ if (item.meridiem === 'am' && item.hour === 12)
8221
+ hour24 = 0;
8222
+ else if (item.meridiem === 'pm' && item.hour < 12)
8223
+ hour24 = item.hour + 12;
8224
+ const hour24Str = hour24.toString().padStart(2, '0');
8225
+ const minuteStr = item.minute.toString().padStart(2, '0');
8226
+ const formats = [
8227
+ `${hour24Str}:${minuteStr}`,
8228
+ `${hour24Str}${minuteStr}`,
8229
+ hour24Str,
8230
+ `${hour24}:${minuteStr}`,
8231
+ hour24.toString(),
8232
+ ];
8233
+ return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
8234
+ lowerFilterText.includes(format.toLowerCase()));
8235
+ };
8236
+ }, [timeOptions, is24Hour, contains]);
8237
+ const { collection, filter } = react.useListCollection({
8238
+ initialItems: timeOptions,
8239
+ itemToString: itemToString,
8240
+ itemToValue: (item) => item.value,
8241
+ filter: customTimeFilter,
8242
+ });
8243
+ // Get current value string for combobox (must match option.value format)
8244
+ const currentTimeValue = React.useMemo(() => {
8245
+ if (is24Hour) {
8246
+ if (hour === null || minute === null) {
8247
+ return '';
7507
8248
  }
7508
- newDate.setSeconds(0);
7509
- }
7510
- const finalISO = dayjs(newDate).tz(timezone).toISOString();
7511
- console.log('[DateTimePicker] Final ISO string to emit:', {
7512
- newDate: newDate.toISOString(),
7513
- timezone,
7514
- finalISO,
7515
- });
7516
- onChange?.(finalISO);
7517
- };
7518
- const handleClear = () => {
7519
- setSelectedDate('');
7520
- setHour12(null);
7521
- setHour24(null);
7522
- setMinute(null);
7523
- setSecond(null);
7524
- setMeridiem(null);
7525
- onChange?.(undefined);
7526
- };
7527
- const isISO = format === 'iso-date-time';
7528
- // Normalize startTime to ignore milliseconds
7529
- const normalizedStartTime = startTime
7530
- ? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
7531
- : undefined;
7532
- // Determine minDate: prioritize explicit minDate prop, then fall back to startTime
7533
- const effectiveMinDate = minDate
7534
- ? minDate
7535
- : normalizedStartTime && dayjs(normalizedStartTime).tz(timezone).isValid()
7536
- ? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
7537
- : undefined;
7538
- // Log current state before render
7539
- React.useEffect(() => {
7540
- console.log('[DateTimePicker] Current state before render:', {
7541
- isISO,
7542
- hour12,
7543
- minute,
7544
- meridiem,
7545
- hour24,
7546
- second,
7547
- selectedDate,
7548
- normalizedStartTime,
7549
- timezone,
7550
- });
7551
- }, [
7552
- isISO,
7553
- hour12,
7554
- minute,
7555
- meridiem,
7556
- hour24,
7557
- second,
7558
- selectedDate,
7559
- normalizedStartTime,
7560
- timezone,
7561
- ]);
7562
- // Compute display text from current state
7563
- const displayText = React.useMemo(() => {
7564
- if (!selectedDate)
7565
- return null;
7566
- const dateObj = dayjs.tz(selectedDate, timezone);
7567
- if (!dateObj.isValid())
7568
- return null;
7569
- if (isISO) {
7570
- // For ISO format, use hour24, minute, second
7571
- if (hour24 === null || minute === null)
7572
- return null;
7573
- const dateTimeObj = dateObj
7574
- .hour(hour24)
7575
- .minute(minute)
7576
- .second(second ?? 0);
7577
- return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
8249
+ const s = second ?? 0;
8250
+ return `${hour}:${minute}:${s}`;
7578
8251
  }
7579
8252
  else {
7580
- // For 12-hour format, use hour12, minute, meridiem
7581
- if (hour12 === null || minute === null || meridiem === null)
7582
- return null;
7583
- // Convert to 24-hour format for dayjs
7584
- let hour24Value = hour12;
7585
- if (meridiem === 'am' && hour12 === 12)
7586
- hour24Value = 0;
7587
- else if (meridiem === 'pm' && hour12 < 12)
7588
- hour24Value = hour12 + 12;
7589
- const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
7590
- return dateTimeObj.format('YYYY-MM-DD hh:mm A');
8253
+ if (hour === null || minute === null || meridiem === null) {
8254
+ return '';
8255
+ }
8256
+ return `${hour}:${minute}:${meridiem}`;
8257
+ }
8258
+ }, [hour, minute, second, meridiem, is24Hour]);
8259
+ // Parse custom time input formats like "1400", "2pm", "14:00", "2:00 PM"
8260
+ const parseCustomTimeInput = (input) => {
8261
+ if (!input || !input.trim()) {
8262
+ return { hour: null, minute: null, second: null, meridiem: null };
8263
+ }
8264
+ const trimmed = input.trim().toLowerCase();
8265
+ // Try parsing 4-digit format without colon: "1400" -> 14:00
8266
+ const fourDigitMatch = trimmed.match(/^(\d{4})$/);
8267
+ if (fourDigitMatch) {
8268
+ const digits = fourDigitMatch[1];
8269
+ const hour = parseInt(digits.substring(0, 2), 10);
8270
+ const minute = parseInt(digits.substring(2, 4), 10);
8271
+ if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
8272
+ if (is24Hour) {
8273
+ return { hour, minute, second: 0, meridiem: null };
8274
+ }
8275
+ else {
8276
+ // Convert to 12-hour format
8277
+ let hour12 = hour;
8278
+ let meridiem;
8279
+ if (hour === 0) {
8280
+ hour12 = 12;
8281
+ meridiem = 'am';
8282
+ }
8283
+ else if (hour === 12) {
8284
+ hour12 = 12;
8285
+ meridiem = 'pm';
8286
+ }
8287
+ else if (hour > 12) {
8288
+ hour12 = hour - 12;
8289
+ meridiem = 'pm';
8290
+ }
8291
+ else {
8292
+ hour12 = hour;
8293
+ meridiem = 'am';
8294
+ }
8295
+ return { hour: hour12, minute, second: null, meridiem };
8296
+ }
8297
+ }
7591
8298
  }
7592
- }, [
7593
- selectedDate,
7594
- isISO,
7595
- hour12,
7596
- minute,
7597
- meridiem,
7598
- hour24,
7599
- second,
7600
- showSeconds,
7601
- timezone,
7602
- ]);
7603
- const timezoneOffset = React.useMemo(() => {
7604
- if (!selectedDate)
7605
- return null;
7606
- const dateObj = dayjs.tz(selectedDate, timezone);
7607
- return dateObj.isValid() ? dateObj.format('Z') : null;
7608
- }, [selectedDate, timezone]);
7609
- return (jsxRuntime.jsxs(react.Flex, { direction: "column", gap: 4, children: [jsxRuntime.jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
7610
- if (date) {
7611
- handleDateChange(date);
8299
+ // Try parsing hour with meridiem: "2pm", "14pm", "2am"
8300
+ const hourMeridiemMatch = trimmed.match(/^(\d{1,2})\s*(am|pm)$/);
8301
+ if (hourMeridiemMatch && !is24Hour) {
8302
+ const hour12 = parseInt(hourMeridiemMatch[1], 10);
8303
+ const meridiem = hourMeridiemMatch[2];
8304
+ if (hour12 >= 1 && hour12 <= 12) {
8305
+ return { hour: hour12, minute: 0, second: null, meridiem };
8306
+ }
8307
+ }
8308
+ // Try parsing 24-hour format with hour only: "14" -> 14:00
8309
+ const hourOnlyMatch = trimmed.match(/^(\d{1,2})$/);
8310
+ if (hourOnlyMatch && is24Hour) {
8311
+ const hour = parseInt(hourOnlyMatch[1], 10);
8312
+ if (hour >= 0 && hour <= 23) {
8313
+ return { hour, minute: 0, second: 0, meridiem: null };
8314
+ }
8315
+ }
8316
+ // Try parsing standard formats: "14:00", "2:00 PM"
8317
+ const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
8318
+ const match24 = trimmed.match(time24Pattern);
8319
+ if (match24) {
8320
+ const hour24 = parseInt(match24[1], 10);
8321
+ const minute = parseInt(match24[2], 10);
8322
+ const second = match24[3] ? parseInt(match24[3], 10) : 0;
8323
+ if (hour24 >= 0 &&
8324
+ hour24 <= 23 &&
8325
+ minute >= 0 &&
8326
+ minute <= 59 &&
8327
+ second >= 0 &&
8328
+ second <= 59) {
8329
+ if (is24Hour) {
8330
+ return { hour: hour24, minute, second, meridiem: null };
8331
+ }
8332
+ else {
8333
+ // Convert to 12-hour format
8334
+ let hour12 = hour24;
8335
+ let meridiem;
8336
+ if (hour24 === 0) {
8337
+ hour12 = 12;
8338
+ meridiem = 'am';
8339
+ }
8340
+ else if (hour24 === 12) {
8341
+ hour12 = 12;
8342
+ meridiem = 'pm';
8343
+ }
8344
+ else if (hour24 > 12) {
8345
+ hour12 = hour24 - 12;
8346
+ meridiem = 'pm';
7612
8347
  }
7613
8348
  else {
7614
- setSelectedDate('');
7615
- onChange?.(undefined);
8349
+ hour12 = hour24;
8350
+ meridiem = 'am';
8351
+ }
8352
+ return { hour: hour12, minute, second: null, meridiem };
8353
+ }
8354
+ }
8355
+ }
8356
+ // Try parsing 12-hour format: "2:00 PM", "2:00PM"
8357
+ const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm)$/;
8358
+ const match12 = trimmed.match(time12Pattern);
8359
+ if (match12 && !is24Hour) {
8360
+ const hour12 = parseInt(match12[1], 10);
8361
+ const minute = parseInt(match12[2], 10);
8362
+ const second = match12[3] ? parseInt(match12[3], 10) : null;
8363
+ const meridiem = match12[4];
8364
+ if (hour12 >= 1 &&
8365
+ hour12 <= 12 &&
8366
+ minute >= 0 &&
8367
+ minute <= 59 &&
8368
+ (second === null || (second >= 0 && second <= 59))) {
8369
+ return { hour: hour12, minute, second, meridiem };
8370
+ }
8371
+ }
8372
+ return { hour: null, minute: null, second: null, meridiem: null };
8373
+ };
8374
+ const handleTimeValueChange = (details) => {
8375
+ if (details.value.length === 0) {
8376
+ handleTimeChange(null, null, null, null);
8377
+ filter('');
8378
+ return;
8379
+ }
8380
+ const selectedValue = details.value[0];
8381
+ const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
8382
+ if (selectedOption) {
8383
+ filter('');
8384
+ if (is24Hour) {
8385
+ const opt24 = selectedOption;
8386
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
8387
+ }
8388
+ else {
8389
+ const opt12 = selectedOption;
8390
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
8391
+ }
8392
+ }
8393
+ };
8394
+ // Track the current input value for Enter key handling
8395
+ const [timeInputValue, setTimeInputValue] = React.useState('');
8396
+ const handleTimeInputChange = (details) => {
8397
+ // Store the input value and filter
8398
+ setTimeInputValue(details.inputValue);
8399
+ filter(details.inputValue);
8400
+ };
8401
+ const handleTimeInputKeyDown = (e) => {
8402
+ if (e.key === 'Enter') {
8403
+ e.preventDefault();
8404
+ // Use the stored input value
8405
+ const parsed = parseCustomTimeInput(timeInputValue);
8406
+ if (parsed.hour !== null && parsed.minute !== null) {
8407
+ if (is24Hour) {
8408
+ handleTimeChange(parsed.hour, parsed.minute, parsed.second, null);
8409
+ }
8410
+ else {
8411
+ if (parsed.meridiem !== null) {
8412
+ handleTimeChange(parsed.hour, parsed.minute, null, parsed.meridiem);
7616
8413
  }
7617
- }, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly: true }), jsxRuntime.jsxs(react.Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (jsxRuntime.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 })) : (jsxRuntime.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 })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsxRuntime.jsx(react.Icon, { as: fa6.FaTrash }) })] }), displayText && (jsxRuntime.jsxs(react.Flex, { gap: 2, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: displayText }), timezoneOffset && (jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezoneOffset })), jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
8414
+ }
8415
+ // Clear the filter and input value after applying
8416
+ filter('');
8417
+ setTimeInputValue('');
8418
+ // Close the popover if value is valid
8419
+ setTimePopoverOpen(false);
8420
+ }
8421
+ }
8422
+ };
8423
+ return (jsxRuntime.jsxs(react.Flex, { direction: "row", gap: 2, align: "center", children: [jsxRuntime.jsxs(react.Popover.Root, { open: datePopoverOpen, onOpenChange: (e) => setDatePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { size: "sm", variant: "outline", onClick: () => setDatePopoverOpen(true), justifyContent: "start", children: [jsxRuntime.jsx(md.MdDateRange, {}), dateDisplayText] }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsx(react.InputGroup, { endElement: jsxRuntime.jsxs(react.Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsxRuntime.jsx(md.MdDateRange, {}) }) }), jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", zIndex: 1500, children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(DatePickerContext.Provider, { value: { labels: calendarLabels }, children: jsxRuntime.jsx(Calendar, { ...calendarProps, firstDayOfWeek: 0 }) }) }) }) }) })] }), children: jsxRuntime.jsx(react.Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxRuntime.jsxs(react.Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsx(react.InputGroup, { endElement: jsxRuntime.jsxs(react.Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsxRuntime.jsx(md.MdDateRange, {}) }) }), jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", zIndex: 1700, children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(DatePickerContext.Provider, { value: { labels: calendarLabels }, children: jsxRuntime.jsx(Calendar, { ...calendarProps, firstDayOfWeek: 0 }) }) }) }) }) })] }), children: jsxRuntime.jsx(react.Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxRuntime.jsxs(react.Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }))] }), jsxRuntime.jsxs(react.Popover.Root, { open: timePopoverOpen, onOpenChange: (e) => setTimePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { size: "sm", variant: "outline", onClick: () => setTimePopoverOpen(true), justifyContent: "start", children: [jsxRuntime.jsx(bs.BsClock, {}), timeDisplayText] }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "300px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
8424
+ (is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: true, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: timePickerLabels?.emptyMessage ??
8425
+ 'No time found' }), collection.items.map((item) => {
8426
+ const option = item;
8427
+ return (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { justify: "space-between", align: "center", w: "100%", children: [jsxRuntime.jsx(react.Text, { children: option.label }), option.durationText && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, option.value));
8428
+ })] }) }) })] }) }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "300px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
8429
+ (is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: true, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: timePickerLabels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => {
8430
+ const option = item;
8431
+ return (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { justify: "space-between", align: "center", w: "100%", children: [jsxRuntime.jsx(react.Text, { children: option.label }), option.durationText && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, option.value));
8432
+ })] }) }) })] }) }) }) }) }))] }), showTimezoneSelector && (jsxRuntime.jsxs(react.Popover.Root, { open: timezonePopoverOpen, onOpenChange: (e) => setTimezonePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => setTimezonePopoverOpen(true), justifyContent: "start", children: timezoneDisplayText || 'Select timezone' }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "250px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Select.Root, { size: "sm", collection: timezoneCollection, value: validTimezoneOffset ? [validTimezoneOffset] : [], onValueChange: (e) => {
8433
+ const newOffset = e.value[0];
8434
+ if (newOffset) {
8435
+ // Update controlled or internal state
8436
+ if (onTimezoneOffsetChange) {
8437
+ onTimezoneOffsetChange(newOffset);
8438
+ }
8439
+ else {
8440
+ setInternalTimezoneOffset(newOffset);
8441
+ }
8442
+ // Update date-time with new offset (pass it directly to avoid stale state)
8443
+ if (selectedDate &&
8444
+ hour !== null &&
8445
+ minute !== null) {
8446
+ updateDateTime(selectedDate, hour, minute, second, meridiem, newOffset);
8447
+ }
8448
+ // Close popover after selection
8449
+ setTimezonePopoverOpen(false);
8450
+ }
8451
+ }, children: [jsxRuntime.jsxs(react.Select.Control, { children: [jsxRuntime.jsx(react.Select.Trigger, {}), jsxRuntime.jsx(react.Select.IndicatorGroup, { children: jsxRuntime.jsx(react.Select.Indicator, {}) })] }), jsxRuntime.jsx(react.Select.Positioner, { children: jsxRuntime.jsx(react.Select.Content, { children: timezoneCollection.items.map((item) => (jsxRuntime.jsxs(react.Select.Item, { item: item, children: [jsxRuntime.jsx(react.Select.ItemText, { children: item.label }), jsxRuntime.jsx(react.Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "250px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Select.Root, { size: "sm", collection: timezoneCollection, value: validTimezoneOffset ? [validTimezoneOffset] : [], onValueChange: (e) => {
8452
+ const newOffset = e.value[0];
8453
+ if (newOffset) {
8454
+ // Update controlled or internal state
8455
+ if (onTimezoneOffsetChange) {
8456
+ onTimezoneOffsetChange(newOffset);
8457
+ }
8458
+ else {
8459
+ setInternalTimezoneOffset(newOffset);
8460
+ }
8461
+ // Update date-time with new offset (pass it directly to avoid stale state)
8462
+ if (selectedDate &&
8463
+ hour !== null &&
8464
+ minute !== null) {
8465
+ updateDateTime(selectedDate, hour, minute, second, meridiem, newOffset);
8466
+ }
8467
+ // Close popover after selection
8468
+ setTimezonePopoverOpen(false);
8469
+ }
8470
+ }, children: [jsxRuntime.jsxs(react.Select.Control, { children: [jsxRuntime.jsx(react.Select.Trigger, {}), jsxRuntime.jsx(react.Select.IndicatorGroup, { children: jsxRuntime.jsx(react.Select.Indicator, {}) })] }), jsxRuntime.jsx(react.Select.Positioner, { children: jsxRuntime.jsx(react.Select.Content, { children: timezoneCollection.items.map((item) => (jsxRuntime.jsxs(react.Select.Item, { item: item, children: [jsxRuntime.jsx(react.Select.ItemText, { children: item.label }), jsxRuntime.jsx(react.Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }))] }))] }));
7618
8471
  }
7619
8472
 
7620
8473
  dayjs.extend(utc);
@@ -7625,14 +8478,15 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
7625
8478
  const formI18n = useFormI18n(column, prefix, schema);
7626
8479
  const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD HH:mm:ss',
7627
8480
  // with timezone
7628
- dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', } = schema;
8481
+ dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', dateTimePicker, } = schema;
7629
8482
  const isRequired = required?.some((columnId) => columnId === column);
7630
8483
  const colLabel = formI18n.colLabel;
7631
- const [open, setOpen] = React.useState(false);
8484
+ React.useState(false);
7632
8485
  const selectedDate = watch(colLabel);
7633
- const displayDate = selectedDate && dayjs(selectedDate).tz(timezone).isValid()
8486
+ selectedDate && dayjs(selectedDate).tz(timezone).isValid()
7634
8487
  ? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
7635
8488
  : '';
8489
+ // Set default date on mount if no value exists
7636
8490
  const dateTimePickerLabelsConfig = {
7637
8491
  monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
7638
8492
  'January',
@@ -7672,11 +8526,9 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
7672
8526
  else {
7673
8527
  setValue(colLabel, undefined);
7674
8528
  }
7675
- }, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels }));
8529
+ }, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels, showQuickActions: dateTimePicker?.showQuickActions ?? false, quickActionLabels: dateTimePicker?.quickActionLabels, showTimezoneSelector: dateTimePicker?.showTimezoneSelector ?? false }));
7676
8530
  return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
7677
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
7678
- setOpen(true);
7679
- }, justifyContent: 'start', children: [jsxRuntime.jsx(md.MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
8531
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: dateTimePickerContent }));
7680
8532
  };
7681
8533
 
7682
8534
  const SchemaRenderer = ({ schema, prefix, column, }) => {
@@ -7797,7 +8649,7 @@ const BooleanViewer = ({ schema, column, prefix, }) => {
7797
8649
  const value = watch(colLabel);
7798
8650
  const formI18n = useFormI18n(column, prefix, schema);
7799
8651
  return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
7800
- gridRow, children: [jsxRuntime.jsx(react.Text, { children: value ? formI18n.t('true') : formI18n.t('false') }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: 'red.400', children: formI18n.required() }))] }));
8652
+ gridRow, children: [jsxRuntime.jsx(react.Text, { children: value ? 'True' : 'False' }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: 'red.400', children: formI18n.required() }))] }));
7801
8653
  };
7802
8654
 
7803
8655
  const CustomViewer = ({ column, schema, prefix }) => {
@@ -7829,23 +8681,22 @@ const DateViewer = ({ column, schema, prefix }) => {
7829
8681
 
7830
8682
  const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
7831
8683
  const { watch, formState: { errors }, } = reactHookForm.useFormContext();
7832
- const formI18n = useFormI18n(column, prefix);
8684
+ const formI18n = useFormI18n(column, prefix, schema);
7833
8685
  const { required } = schema;
7834
8686
  const isRequired = required?.some((columnId) => columnId === column);
7835
- const { gridColumn = "span 12", gridRow = "span 1", renderDisplay } = schema;
8687
+ const { gridColumn = 'span 12', gridRow = 'span 1', renderDisplay } = schema;
7836
8688
  const colLabel = formI18n.colLabel;
7837
8689
  const watchEnum = watch(colLabel);
7838
8690
  const watchEnums = (watch(colLabel) ?? []);
7839
- return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: "stretch", gridColumn,
7840
- gridRow, children: [isMultiple && (jsxRuntime.jsx(react.Flex, { flexFlow: "wrap", gap: 1, children: watchEnums.map((enumValue) => {
8691
+ const renderDisplayFunction = renderDisplay || defaultRenderDisplay;
8692
+ return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
8693
+ gridRow, children: [isMultiple && (jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 1, children: watchEnums.map((enumValue) => {
7841
8694
  const item = enumValue;
7842
8695
  if (item === undefined) {
7843
8696
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: "undefined" });
7844
8697
  }
7845
- return (jsxRuntime.jsx(Tag, { size: "lg", children: !!renderDisplay === true
7846
- ? renderDisplay(item)
7847
- : formI18n.t(item) }, item));
7848
- }) })), !isMultiple && jsxRuntime.jsx(react.Text, { children: formI18n.t(watchEnum) }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: formI18n.required() }))] }));
8698
+ return (jsxRuntime.jsx(Tag, { size: "lg", children: renderDisplayFunction(item) }, item));
8699
+ }) })), !isMultiple && jsxRuntime.jsx(react.Text, { children: renderDisplayFunction(watchEnum) }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: 'red.400', children: formI18n.required() }))] }));
7849
8700
  };
7850
8701
 
7851
8702
  const FileViewer = ({ column, schema, prefix }) => {
@@ -7965,31 +8816,35 @@ const StringViewer = ({ column, schema, prefix, }) => {
7965
8816
 
7966
8817
  const TagViewer = ({ column, schema, prefix }) => {
7967
8818
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
7968
- const { serverUrl } = useSchemaContext();
7969
8819
  if (schema.properties == undefined) {
7970
- throw new Error("schema properties undefined when using DatePicker");
8820
+ throw new Error('schema properties undefined when using DatePicker');
7971
8821
  }
7972
- const { gridColumn, gridRow, in_table, object_id_column } = schema;
8822
+ const { gridColumn, gridRow, in_table, object_id_column, tagPicker } = schema;
7973
8823
  if (in_table === undefined) {
7974
- throw new Error("in_table is undefined when using TagPicker");
8824
+ throw new Error('in_table is undefined when using TagPicker');
7975
8825
  }
7976
8826
  if (object_id_column === undefined) {
7977
- throw new Error("object_id_column is undefined when using TagPicker");
8827
+ throw new Error('object_id_column is undefined when using TagPicker');
8828
+ }
8829
+ if (!tagPicker?.queryFn) {
8830
+ throw new Error('tagPicker.queryFn is required in schema. serverUrl has been removed.');
7978
8831
  }
7979
8832
  const query = reactQuery.useQuery({
7980
8833
  queryKey: [`tagpicker`, in_table],
7981
8834
  queryFn: async () => {
7982
- return await getTableData({
7983
- serverUrl,
7984
- in_table: "tables_tags_view",
8835
+ const result = await tagPicker.queryFn({
8836
+ in_table: 'tables_tags_view',
7985
8837
  where: [
7986
8838
  {
7987
- id: "table_name",
8839
+ id: 'table_name',
7988
8840
  value: [in_table],
7989
8841
  },
7990
8842
  ],
7991
8843
  limit: 100,
8844
+ offset: 0,
8845
+ searching: '',
7992
8846
  });
8847
+ return result.data || { data: [] };
7993
8848
  },
7994
8849
  staleTime: 10000,
7995
8850
  });
@@ -7997,17 +8852,19 @@ const TagViewer = ({ column, schema, prefix }) => {
7997
8852
  const existingTagsQuery = reactQuery.useQuery({
7998
8853
  queryKey: [`existing`, { in_table, object_id_column }, object_id],
7999
8854
  queryFn: async () => {
8000
- return await getTableData({
8001
- serverUrl,
8855
+ const result = await tagPicker.queryFn({
8002
8856
  in_table: in_table,
8003
8857
  where: [
8004
8858
  {
8005
8859
  id: object_id_column,
8006
- value: object_id[0],
8860
+ value: [object_id[0]],
8007
8861
  },
8008
8862
  ],
8009
8863
  limit: 100,
8864
+ offset: 0,
8865
+ searching: '',
8010
8866
  });
8867
+ return result.data || { data: [] };
8011
8868
  },
8012
8869
  enabled: object_id != undefined,
8013
8870
  staleTime: 10000,
@@ -8018,9 +8875,9 @@ const TagViewer = ({ column, schema, prefix }) => {
8018
8875
  if (!!object_id === false) {
8019
8876
  return jsxRuntime.jsx(jsxRuntime.Fragment, {});
8020
8877
  }
8021
- return (jsxRuntime.jsxs(react.Flex, { flexFlow: "column", gap: 4, gridColumn,
8878
+ return (jsxRuntime.jsxs(react.Flex, { flexFlow: 'column', gap: 4, gridColumn,
8022
8879
  gridRow, children: [isFetching && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "isFetching" }), isLoading && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "isLoading" }), isPending && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "isPending" }), isError && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "isError" }), dataList.map(({ parent_tag_name, all_tags, is_mutually_exclusive }) => {
8023
- return (jsxRuntime.jsxs(react.Flex, { flexFlow: "column", gap: 2, children: [jsxRuntime.jsx(react.Text, { children: parent_tag_name }), is_mutually_exclusive && (jsxRuntime.jsx(RadioCardRoot, { defaultValue: "next", variant: "surface", onValueChange: (tagIds) => {
8880
+ return (jsxRuntime.jsxs(react.Flex, { flexFlow: 'column', gap: 2, children: [jsxRuntime.jsx(react.Text, { children: parent_tag_name }), is_mutually_exclusive && (jsxRuntime.jsx(RadioCardRoot, { defaultValue: "next", variant: 'surface', onValueChange: (tagIds) => {
8024
8881
  const existedTags = Object.values(all_tags)
8025
8882
  .filter(({ id }) => {
8026
8883
  return existingTagList.some(({ tag_id }) => tag_id === id);
@@ -8032,20 +8889,20 @@ const TagViewer = ({ column, schema, prefix }) => {
8032
8889
  tagIds.value,
8033
8890
  ]);
8034
8891
  setValue(`${column}.${parent_tag_name}.old`, existedTags);
8035
- }, children: jsxRuntime.jsx(react.Flex, { flexFlow: "wrap", gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8892
+ }, children: jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8036
8893
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
8037
- return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: "0 0 0%", disabled: true }, `${tagName}-${id}`));
8894
+ return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: '0 0 0%', disabled: true }, `${tagName}-${id}`));
8038
8895
  }
8039
- return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: "0 0 0%", colorPalette: "blue" }, `${tagName}-${id}`));
8896
+ return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: '0 0 0%', colorPalette: 'blue' }, `${tagName}-${id}`));
8040
8897
  }) }) })), !is_mutually_exclusive && (jsxRuntime.jsx(react.CheckboxGroup, { onValueChange: (tagIds) => {
8041
8898
  setValue(`${column}.${parent_tag_name}.current`, tagIds);
8042
- }, children: jsxRuntime.jsx(react.Flex, { flexFlow: "wrap", gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8899
+ }, children: jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8043
8900
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
8044
- return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: "0 0 0%", disabled: true, colorPalette: "blue" }, `${tagName}-${id}`));
8901
+ return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: '0 0 0%', disabled: true, colorPalette: 'blue' }, `${tagName}-${id}`));
8045
8902
  }
8046
- return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: "0 0 0%" }, `${tagName}-${id}`));
8903
+ return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: '0 0 0%' }, `${tagName}-${id}`));
8047
8904
  }) }) }))] }, `tag-${parent_tag_name}`));
8048
- }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: (errors[`${column}`]?.message ?? "No error message") }))] }));
8905
+ }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: 'red.400', children: (errors[`${column}`]?.message ?? 'No error message') }))] }));
8049
8906
  };
8050
8907
 
8051
8908
  const TextAreaViewer = ({ column, schema, prefix, }) => {
@@ -8252,6 +9109,17 @@ const FormBody = () => {
8252
9109
 
8253
9110
  const FormTitle = () => {
8254
9111
  const { schema } = useSchemaContext();
9112
+ // Debug log when form title is missing
9113
+ if (!schema.title) {
9114
+ console.debug('[Form Title] Missing title in root schema. Add title property to schema.', {
9115
+ schema: {
9116
+ type: schema.type,
9117
+ properties: schema.properties
9118
+ ? Object.keys(schema.properties)
9119
+ : undefined,
9120
+ },
9121
+ });
9122
+ }
8255
9123
  return jsxRuntime.jsx(react.Heading, { children: schema.title ?? 'Form' });
8256
9124
  };
8257
9125
 
@@ -9420,6 +10288,7 @@ exports.CardHeader = CardHeader;
9420
10288
  exports.DataDisplay = DataDisplay;
9421
10289
  exports.DataTable = DataTable;
9422
10290
  exports.DataTableServer = DataTableServer;
10291
+ exports.DatePickerContext = DatePickerContext;
9423
10292
  exports.DatePickerInput = DatePickerInput;
9424
10293
  exports.DefaultCardTitle = DefaultCardTitle;
9425
10294
  exports.DefaultForm = DefaultForm;