@bsol-oss/react-datatable5 13.0.1-beta.1 → 13.0.1-beta.11

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,106 +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
- /**
5655
- * Load initial values for IdPicker fields into idMap
5656
- * Uses customQueryFn if available, otherwise falls back to getTableData
5657
- *
5658
- * @param params - Configuration for loading initial values
5659
- * @returns Promise with fetched data and idMap
5660
- */
5661
- const loadInitialValues = async ({ ids, foreign_key, serverUrl, setIdMap, }) => {
5662
- if (!ids || ids.length === 0) {
5663
- return { data: { data: [], count: 0 }, idMap: {} };
5664
- }
5665
- const { table, column: column_ref, customQueryFn } = foreign_key;
5666
- // Filter out IDs that are already in idMap (optional optimization)
5667
- // For now, we'll fetch all requested IDs to ensure consistency
5668
- if (customQueryFn) {
5669
- const { data, idMap: returnedIdMap } = await customQueryFn({
5670
- searching: '',
5671
- limit: ids.length,
5672
- offset: 0,
5673
- where: [
5674
- {
5675
- id: column_ref,
5676
- value: ids.length === 1 ? ids[0] : ids, // CustomQueryFn accepts string | string[]
5677
- },
5678
- ],
5679
- });
5680
- // Update idMap with returned values
5681
- if (returnedIdMap && Object.keys(returnedIdMap).length > 0) {
5682
- setIdMap((state) => {
5683
- return { ...state, ...returnedIdMap };
5684
- });
5685
- }
5686
- return { data, idMap: returnedIdMap || {} };
5687
- }
5688
- // Fallback to default getTableData
5689
- const data = await getTableData({
5690
- serverUrl,
5691
- searching: '',
5692
- in_table: table,
5693
- limit: ids.length,
5694
- offset: 0,
5695
- where: [
5696
- {
5697
- id: column_ref,
5698
- value: ids, // Always pass as array
5699
- },
5700
- ],
5701
- });
5702
- // Build idMap from fetched data
5703
- const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
5704
- return [
5705
- item[column_ref],
5706
- {
5707
- ...item,
5708
- },
5709
- ];
5710
- }));
5711
- // Update idMap state
5712
- setIdMap((state) => {
5713
- return { ...state, ...newMap };
5714
- });
5715
- return { data: data, idMap: newMap };
5716
- };
5717
6012
  const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5718
6013
  const { watch, getValues, formState: { errors }, setValue, } = reactHookForm.useFormContext();
5719
- const { serverUrl, idMap, setIdMap, idPickerLabels, insideDialog } = useSchemaContext();
5720
- 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
+ }
5721
6021
  const { table, column: column_ref, customQueryFn, } = foreign_key;
5722
6022
  const [searchText, setSearchText] = React.useState('');
5723
6023
  const [debouncedSearchText, setDebouncedSearchText] = React.useState('');
@@ -5762,60 +6062,58 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5762
6062
  const missingIdsKey = React.useMemo(() => {
5763
6063
  return JSON.stringify([...missingIds].sort());
5764
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]);
5765
6072
  // Query to fetch initial values that are missing from idMap
5766
6073
  // This query runs automatically when missingIds.length > 0 and updates idMap
5767
6074
  const initialValuesQuery = reactQuery.useQuery({
5768
- queryKey: [`idpicker-initial`, column, missingIdsKey],
6075
+ queryKey: [`idpicker-initial`, column, missingIdsKey, idMapStateKey],
5769
6076
  queryFn: async () => {
5770
6077
  if (missingIds.length === 0) {
5771
6078
  return { data: [], count: 0 };
5772
6079
  }
5773
- // Use the reusable loadInitialValues function
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 };
6084
+ }
5774
6085
  const result = await loadInitialValues({
5775
6086
  ids: missingIds,
5776
6087
  foreign_key: foreign_key,
5777
- serverUrl,
5778
6088
  setIdMap,
5779
6089
  });
5780
6090
  return result.data;
5781
6091
  },
5782
6092
  enabled: missingIds.length > 0, // Only fetch if there are missing IDs
5783
- 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
5784
6096
  });
5785
6097
  const { isLoading: isLoadingInitialValues, isFetching: isFetchingInitialValues, } = initialValuesQuery;
5786
6098
  // Query for search results (async loading)
5787
6099
  const query = reactQuery.useQuery({
5788
6100
  queryKey: [`idpicker`, { column, searchText: debouncedSearchText, limit }],
5789
6101
  queryFn: async () => {
5790
- if (customQueryFn) {
5791
- const { data, idMap } = await customQueryFn({
5792
- searching: debouncedSearchText ?? '',
5793
- limit: limit,
5794
- offset: 0,
5795
- });
5796
- setIdMap((state) => {
5797
- return { ...state, ...idMap };
5798
- });
5799
- 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.`);
5800
6105
  }
5801
- const data = await getTableData({
5802
- serverUrl,
6106
+ const { data, idMap } = await customQueryFn({
5803
6107
  searching: debouncedSearchText ?? '',
5804
- in_table: table,
5805
6108
  limit: limit,
5806
6109
  offset: 0,
5807
6110
  });
5808
- const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
5809
- return [
5810
- item[column_ref],
5811
- {
5812
- ...item,
5813
- },
5814
- ];
5815
- }));
5816
- setIdMap((state) => {
5817
- return { ...state, ...newMap };
5818
- });
6111
+ // Update idMap with returned values
6112
+ if (idMap && Object.keys(idMap).length > 0) {
6113
+ setIdMap((state) => {
6114
+ return { ...state, ...idMap };
6115
+ });
6116
+ }
5819
6117
  return data;
5820
6118
  },
5821
6119
  enabled: true, // Always enabled for combobox
@@ -5847,17 +6145,51 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5847
6145
  // Depend on idMapKey which only changes when items we care about change
5848
6146
  // eslint-disable-next-line react-hooks/exhaustive-deps
5849
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
+ };
5850
6167
  // Transform data for combobox collection
5851
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)
5852
6170
  // raw item is stored for custom rendering
5853
6171
  // Also include items from idMap that match currentValue (for initial values display)
5854
6172
  const comboboxItems = React.useMemo(() => {
5855
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
+ };
5856
6186
  const itemsFromDataList = dataList.map((item) => {
5857
6187
  const rendered = renderFn(item);
6188
+ const label = typeof rendered === 'string' ? rendered : JSON.stringify(item); // Use string for filtering
5858
6189
  return {
5859
- label: typeof rendered === 'string' ? rendered : JSON.stringify(item), // Use string for filtering
5860
- 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),
5861
6193
  raw: item,
5862
6194
  };
5863
6195
  });
@@ -5866,25 +6198,28 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5866
6198
  const itemsFromIdMap = idMapItems
5867
6199
  .map((item) => {
5868
6200
  // Check if this item is already in itemsFromDataList
5869
- const alreadyIncluded = itemsFromDataList.some((i) => i.value === String(item[column_ref]));
6201
+ const alreadyIncluded = itemsFromDataList.some((i) => i.value === itemToValueFn(item));
5870
6202
  if (alreadyIncluded)
5871
6203
  return null;
5872
6204
  const rendered = renderFn(item);
6205
+ const label = typeof rendered === 'string' ? rendered : JSON.stringify(item);
5873
6206
  return {
5874
- label: typeof rendered === 'string' ? rendered : JSON.stringify(item),
5875
- value: String(item[column_ref]),
6207
+ label,
6208
+ displayLabel: getDisplayString(rendered, label), // String representation for input display
6209
+ value: itemToValueFn(item),
5876
6210
  raw: item,
5877
6211
  };
5878
6212
  })
5879
6213
  .filter((item) => item !== null);
5880
6214
  return [...itemsFromIdMap, ...itemsFromDataList];
5881
- }, [dataList, column_ref, renderDisplay, idMapItems]);
6215
+ }, [dataList, column_ref, renderDisplay, idMapItems, itemToValueFn]);
5882
6216
  // Use filter hook for combobox
5883
6217
  const { contains } = react.useFilter({ sensitivity: 'base' });
5884
6218
  // Create collection for combobox
6219
+ // itemToString uses displayLabel to show rendered display in input when selected
5885
6220
  const { collection, filter, set } = react.useListCollection({
5886
6221
  initialItems: comboboxItems,
5887
- itemToString: (item) => item.label,
6222
+ itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
5888
6223
  itemToValue: (item) => item.value,
5889
6224
  filter: contains,
5890
6225
  });
@@ -5939,6 +6274,10 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5939
6274
  idPickerLabels,
5940
6275
  insideDialog: insideDialog ?? false,
5941
6276
  renderDisplay,
6277
+ itemToValue: itemToValueFn,
6278
+ itemToString: itemToStringFn,
6279
+ loadInitialValues: loadInitialValues ??
6280
+ (async () => ({ data: { data: [], count: 0 }, idMap: {} })), // Fallback if not provided
5942
6281
  column_ref,
5943
6282
  errors,
5944
6283
  setValue,
@@ -5947,60 +6286,69 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5947
6286
 
5948
6287
  const IdPickerSingle = ({ column, schema, prefix, }) => {
5949
6288
  const formI18n = useFormI18n(column, prefix, schema);
5950
- const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, } = schema;
6289
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
5951
6290
  const isRequired = required?.some((columnId) => columnId === column);
5952
- 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({
5953
6292
  column,
5954
6293
  schema,
5955
6294
  prefix,
5956
6295
  isMultiple: false,
5957
6296
  });
5958
- const handleInputValueChange = (details) => {
5959
- setSearchText(details.inputValue);
5960
- };
5961
- const handleValueChange = (details) => {
5962
- setValue(colLabel, details.value[0] || '');
5963
- };
5964
- const renderDisplayFunction = renderDisplayFn || renderDisplay || defaultRenderDisplay;
5965
- return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5966
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [currentValue.length > 0 && (jsxRuntime.jsx(react.Flex, { mb: 2, children: (() => {
5967
- const id = currentValue[0];
5968
- const item = idMap[id];
5969
- // Show loading skeleton while fetching initial values
5970
- if (item === undefined &&
5971
- (isLoadingInitialValues || isFetchingInitialValues) &&
5972
- missingIds.includes(id)) {
5973
- return jsxRuntime.jsx(react.Skeleton, { height: "24px", width: "100px", borderRadius: "md" });
5974
- }
5975
- // Only show "not found" if we're not loading and item is still missing
5976
- if (item === undefined) {
5977
- return (jsxRuntime.jsx(react.Text, { fontSize: "sm", children: idPickerLabels?.undefined ?? 'Undefined' }));
5978
- }
5979
- return jsxRuntime.jsx(react.Text, { fontSize: "sm", children: renderDisplayFunction(item) });
5980
- })() })), 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
5981
- ? { strategy: 'fixed', hideWhenDetached: true }
5982
- : 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: () => {
5983
- setValue(colLabel, '');
5984
- } })), 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 ? (
5985
6342
  // Show skeleton items to prevent UI shift
5986
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
5987
6344
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
5988
6345
  : idPickerLabels?.initialResults ??
5989
- '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
5990
- ? renderDisplayFunction(item.raw)
5991
- : 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 ? (
5992
- // Show skeleton items to prevent UI shift
5993
- 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
5994
- ? idPickerLabels?.emptySearchResult ?? 'No results found'
5995
- : idPickerLabels?.initialResults ??
5996
- '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
5997
- ? renderDisplayFunction(item.raw)
5998
- : 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}`))) })) }) }) }))] }) }));
5999
6347
  };
6000
6348
 
6001
6349
  const IdPickerMultiple = ({ column, schema, prefix, }) => {
6002
6350
  const formI18n = useFormI18n(column, prefix, schema);
6003
- const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, } = schema;
6351
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
6004
6352
  const isRequired = required?.some((columnId) => columnId === column);
6005
6353
  const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, isLoadingInitialValues, isFetchingInitialValues, missingIds, collection, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, errors, setValue, } = useIdPickerData({
6006
6354
  column,
@@ -6014,7 +6362,8 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
6014
6362
  const handleValueChange = (details) => {
6015
6363
  setValue(colLabel, details.value);
6016
6364
  };
6017
- const renderDisplayFunction = renderDisplayFn || renderDisplay || defaultRenderDisplay;
6365
+ // Use renderDisplay from hook (which comes from schema) or fallback to default
6366
+ const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
6018
6367
  return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6019
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) => {
6020
6369
  const item = idMap[id];
@@ -6039,16 +6388,12 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
6039
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
6040
6389
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
6041
6390
  : idPickerLabels?.initialResults ??
6042
- '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
6043
- ? renderDisplayFunction(item.raw)
6044
- : 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 ? (
6045
6392
  // Show skeleton items to prevent UI shift
6046
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
6047
6394
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
6048
6395
  : idPickerLabels?.initialResults ??
6049
- '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
6050
- ? renderDisplayFunction(item.raw)
6051
- : 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}`))) })) }) }) }))] })] }));
6052
6397
  };
6053
6398
 
6054
6399
  const NumberInputRoot = React__namespace.forwardRef(function NumberInput(props, ref) {
@@ -6232,31 +6577,35 @@ react.RadioCard.ItemIndicator;
6232
6577
 
6233
6578
  const TagPicker = ({ column, schema, prefix }) => {
6234
6579
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
6235
- const { serverUrl } = useSchemaContext();
6236
6580
  if (schema.properties == undefined) {
6237
- throw new Error("schema properties undefined when using DatePicker");
6581
+ throw new Error('schema properties undefined when using DatePicker');
6238
6582
  }
6239
- const { gridColumn, gridRow, in_table, object_id_column } = schema;
6583
+ const { gridColumn, gridRow, in_table, object_id_column, tagPicker } = schema;
6240
6584
  if (in_table === undefined) {
6241
- throw new Error("in_table is undefined when using TagPicker");
6585
+ throw new Error('in_table is undefined when using TagPicker');
6242
6586
  }
6243
6587
  if (object_id_column === undefined) {
6244
- 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.');
6245
6592
  }
6246
6593
  const query = reactQuery.useQuery({
6247
6594
  queryKey: [`tagpicker`, in_table],
6248
6595
  queryFn: async () => {
6249
- return await getTableData({
6250
- serverUrl,
6251
- in_table: "tables_tags_view",
6596
+ const result = await tagPicker.queryFn({
6597
+ in_table: 'tables_tags_view',
6252
6598
  where: [
6253
6599
  {
6254
- id: "table_name",
6600
+ id: 'table_name',
6255
6601
  value: [in_table],
6256
6602
  },
6257
6603
  ],
6258
6604
  limit: 100,
6605
+ offset: 0,
6606
+ searching: '',
6259
6607
  });
6608
+ return result.data || { data: [] };
6260
6609
  },
6261
6610
  staleTime: 10000,
6262
6611
  });
@@ -6264,17 +6613,19 @@ const TagPicker = ({ column, schema, prefix }) => {
6264
6613
  const existingTagsQuery = reactQuery.useQuery({
6265
6614
  queryKey: [`existing`, { in_table, object_id_column }, object_id],
6266
6615
  queryFn: async () => {
6267
- return await getTableData({
6268
- serverUrl,
6616
+ const result = await tagPicker.queryFn({
6269
6617
  in_table: in_table,
6270
6618
  where: [
6271
6619
  {
6272
6620
  id: object_id_column,
6273
- value: object_id[0],
6621
+ value: [object_id[0]],
6274
6622
  },
6275
6623
  ],
6276
6624
  limit: 100,
6625
+ offset: 0,
6626
+ searching: '',
6277
6627
  });
6628
+ return result.data || { data: [] };
6278
6629
  },
6279
6630
  enabled: object_id != undefined,
6280
6631
  staleTime: 10000,
@@ -6285,9 +6636,9 @@ const TagPicker = ({ column, schema, prefix }) => {
6285
6636
  if (!!object_id === false) {
6286
6637
  return jsxRuntime.jsx(jsxRuntime.Fragment, {});
6287
6638
  }
6288
- return (jsxRuntime.jsxs(react.Flex, { flexFlow: "column", gap: 4, gridColumn,
6639
+ return (jsxRuntime.jsxs(react.Flex, { flexFlow: 'column', gap: 4, gridColumn,
6289
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 }) => {
6290
- 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) => {
6291
6642
  const existedTags = Object.values(all_tags)
6292
6643
  .filter(({ id }) => {
6293
6644
  return existingTagList.some(({ tag_id }) => tag_id === id);
@@ -6299,20 +6650,20 @@ const TagPicker = ({ column, schema, prefix }) => {
6299
6650
  tagIds.value,
6300
6651
  ]);
6301
6652
  setValue(`${column}.${parent_tag_name}.old`, existedTags);
6302
- }, 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 }]) => {
6303
6654
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
6304
- 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}`));
6305
6656
  }
6306
- 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}`));
6307
6658
  }) }) })), !is_mutually_exclusive && (jsxRuntime.jsx(react.CheckboxGroup, { onValueChange: (tagIds) => {
6308
6659
  setValue(`${column}.${parent_tag_name}.current`, tagIds);
6309
- }, 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 }]) => {
6310
6661
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
6311
- 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}`));
6312
6663
  }
6313
- 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}`));
6314
6665
  }) }) }))] }, `tag-${parent_tag_name}`));
6315
- }), 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') }))] }));
6316
6667
  };
6317
6668
 
6318
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) => {
@@ -6419,11 +6770,193 @@ const TextAreaInput = ({ column, schema, prefix, }) => {
6419
6770
 
6420
6771
  dayjs.extend(utc);
6421
6772
  dayjs.extend(timezone);
6422
- const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem, onChange = () => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6423
- placeholder: 'hh:mm AM/PM',
6424
- emptyMessage: 'No time found',
6425
- }, }) => {
6426
- // 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
6427
6960
  const timeOptions = React.useMemo(() => {
6428
6961
  const options = [];
6429
6962
  // Get start time for comparison if provided
@@ -6434,32 +6967,25 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6434
6967
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6435
6968
  if (startDateObj.isValid() && selectedDateObj.isValid()) {
6436
6969
  startDateTime = startDateObj;
6437
- // Only filter if dates are the same
6438
6970
  shouldFilterByDate =
6439
6971
  startDateObj.format('YYYY-MM-DD') ===
6440
6972
  selectedDateObj.format('YYYY-MM-DD');
6441
6973
  }
6442
6974
  }
6443
- // Generate 12-hour format options (1-12 for hours, AM/PM)
6444
- for (let h = 1; h <= 12; h++) {
6445
- for (let m = 0; m < 60; m += 15) {
6446
- for (const mer of ['am', 'pm']) {
6447
- // Convert 12-hour to 24-hour for comparison
6448
- let hour24 = h;
6449
- if (mer === 'am' && h === 12)
6450
- hour24 = 0;
6451
- else if (mer === 'pm' && h < 12)
6452
- hour24 = h + 12;
6453
- // 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
6454
6980
  if (startDateTime && selectedDate && shouldFilterByDate) {
6455
6981
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6456
6982
  const optionDateTime = selectedDateObj
6457
- .hour(hour24)
6983
+ .hour(h)
6458
6984
  .minute(m)
6459
6985
  .second(0)
6460
6986
  .millisecond(0);
6461
6987
  if (optionDateTime.isBefore(startDateTime)) {
6462
- continue; // Skip this option as it would result in negative duration
6988
+ continue;
6463
6989
  }
6464
6990
  }
6465
6991
  // Calculate duration if startTime is provided
@@ -6467,7 +6993,7 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6467
6993
  if (startDateTime && selectedDate) {
6468
6994
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6469
6995
  const optionDateTime = selectedDateObj
6470
- .hour(hour24)
6996
+ .hour(h)
6471
6997
  .minute(m)
6472
6998
  .second(0)
6473
6999
  .millisecond(0);
@@ -6492,58 +7018,204 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6492
7018
  }
6493
7019
  }
6494
7020
  }
6495
- const hourDisplay = h.toString();
6496
- const minuteDisplay = m.toString().padStart(2, '0');
6497
- const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
7021
+ const timeDisplay = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`;
6498
7022
  options.push({
6499
7023
  label: timeDisplay,
6500
- value: `${h}:${m}:${mer}`,
7024
+ value: `${h}:${m}:0`,
6501
7025
  hour: h,
6502
7026
  minute: m,
6503
- meridiem: mer,
6504
- searchText: timeDisplay, // Use base time without duration for searching
7027
+ second: 0,
7028
+ searchText: timeDisplay,
6505
7029
  durationText,
6506
7030
  });
6507
7031
  }
6508
7032
  }
6509
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
+ }
6510
7122
  return options;
6511
- }, [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
6512
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]);
6513
7169
  const { collection, filter } = react.useListCollection({
6514
7170
  initialItems: timeOptions,
6515
- itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
7171
+ itemToString: itemToString,
6516
7172
  itemToValue: (item) => item.value,
6517
- filter: contains,
7173
+ filter: customTimeFilter,
6518
7174
  });
6519
7175
  // Get current value string for combobox
6520
7176
  const currentValue = React.useMemo(() => {
6521
- if (hour === null || minute === null || meridiem === null) {
6522
- 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}`;
6523
7188
  }
6524
- return `${hour}:${minute}:${meridiem}`;
6525
- }, [hour, minute, meridiem]);
7189
+ }, [hour, minute, second, meridiem, is24Hour]);
6526
7190
  // Calculate duration difference
6527
7191
  const durationDiff = React.useMemo(() => {
6528
- if (!startTime ||
6529
- !selectedDate ||
6530
- hour === null ||
6531
- minute === null ||
6532
- meridiem === null) {
7192
+ if (!startTime || !selectedDate || hour === null || minute === null) {
6533
7193
  return null;
6534
7194
  }
7195
+ if (is24Hour) {
7196
+ if (second === null)
7197
+ return null;
7198
+ }
7199
+ else {
7200
+ if (meridiem === null)
7201
+ return null;
7202
+ }
6535
7203
  const startDateObj = dayjs(startTime).tz(timezone);
6536
7204
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6537
- // Convert 12-hour to 24-hour format
7205
+ // Convert to 24-hour format
6538
7206
  let hour24 = hour;
6539
- if (meridiem === 'am' && hour === 12)
6540
- hour24 = 0;
6541
- else if (meridiem === 'pm' && hour < 12)
6542
- 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
+ }
6543
7213
  const currentDateTime = selectedDateObj
6544
7214
  .hour(hour24)
6545
7215
  .minute(minute)
6546
- .second(0)
7216
+ .second(is24Hour && second !== null && second !== undefined
7217
+ ? second
7218
+ : 0)
6547
7219
  .millisecond(0);
6548
7220
  if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6549
7221
  return null;
@@ -6569,13 +7241,28 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6569
7241
  return `+${diffText}`;
6570
7242
  }
6571
7243
  return null;
6572
- }, [hour, minute, meridiem, startTime, selectedDate, timezone]);
7244
+ }, [
7245
+ hour,
7246
+ minute,
7247
+ second,
7248
+ meridiem,
7249
+ startTime,
7250
+ selectedDate,
7251
+ timezone,
7252
+ is24Hour,
7253
+ ]);
6573
7254
  const handleClear = () => {
6574
7255
  setHour(null);
6575
7256
  setMinute(null);
6576
- setMeridiem(null);
6577
- filter(''); // Reset filter to show all options
6578
- 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('');
6579
7266
  };
6580
7267
  const handleValueChange = (details) => {
6581
7268
  if (details.value.length === 0) {
@@ -6587,71 +7274,165 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6587
7274
  if (selectedOption) {
6588
7275
  setHour(selectedOption.hour);
6589
7276
  setMinute(selectedOption.minute);
6590
- setMeridiem(selectedOption.meridiem);
6591
- filter(''); // Reset filter after selection
6592
- onChange({
6593
- hour: selectedOption.hour,
6594
- minute: selectedOption.minute,
6595
- meridiem: selectedOption.meridiem,
6596
- });
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
+ }
6597
7290
  }
6598
7291
  };
6599
7292
  // Parse input value and update state
6600
7293
  const parseAndCommitInput = (value) => {
6601
7294
  const trimmedValue = value.trim();
6602
- // Filter the collection based on input
6603
7295
  filter(trimmedValue);
6604
7296
  if (!trimmedValue) {
6605
7297
  return;
6606
7298
  }
6607
- // Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
6608
- const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
6609
- const match12Hour = trimmedValue.match(timePattern12Hour);
6610
- if (match12Hour) {
6611
- const parsedHour = parseInt(match12Hour[1], 10);
6612
- const parsedMinute = parseInt(match12Hour[2], 10);
6613
- const parsedMeridiem = match12Hour[3].toLowerCase();
6614
- // Validate ranges
6615
- if (parsedHour >= 1 &&
6616
- parsedHour <= 12 &&
6617
- parsedMinute >= 0 &&
6618
- parsedMinute <= 59) {
6619
- setHour(parsedHour);
6620
- setMinute(parsedMinute);
6621
- setMeridiem(parsedMeridiem);
6622
- onChange({
6623
- hour: parsedHour,
6624
- minute: parsedMinute,
6625
- meridiem: parsedMeridiem,
6626
- });
6627
- 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
+ }
6628
7340
  }
6629
7341
  }
6630
- // Try to parse formats like "130pm" or "130 pm" (without colon)
6631
- const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
6632
- const matchNoColon = trimmedValue.match(timePatternNoColon);
6633
- if (matchNoColon) {
6634
- const numbersOnly = matchNoColon[1];
6635
- const parsedMeridiem = matchNoColon[2].toLowerCase();
6636
- if (numbersOnly.length >= 3) {
6637
- const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
6638
- const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
6639
- // 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();
6640
7387
  if (parsedHour >= 1 &&
6641
7388
  parsedHour <= 12 &&
6642
7389
  parsedMinute >= 0 &&
6643
7390
  parsedMinute <= 59) {
6644
7391
  setHour(parsedHour);
6645
7392
  setMinute(parsedMinute);
6646
- setMeridiem(parsedMeridiem);
6647
- onChange({
6648
- hour: parsedHour,
6649
- minute: parsedMinute,
6650
- meridiem: parsedMeridiem,
6651
- });
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);
6652
7411
  return;
6653
7412
  }
6654
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
+ }
6655
7436
  }
6656
7437
  // Parse failed, select first result
6657
7438
  selectFirstResult();
@@ -6662,58 +7443,87 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6662
7443
  const firstItem = collection.items[0];
6663
7444
  setHour(firstItem.hour);
6664
7445
  setMinute(firstItem.minute);
6665
- setMeridiem(firstItem.meridiem);
6666
- filter(''); // Reset filter after selection
6667
- onChange({
6668
- hour: firstItem.hour,
6669
- minute: firstItem.minute,
6670
- meridiem: firstItem.meridiem,
6671
- });
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
+ }
6672
7459
  }
6673
7460
  };
6674
7461
  const handleInputValueChange = (details) => {
6675
- // Filter the collection based on input, but don't parse yet
7462
+ if (is24Hour) {
7463
+ setInputValue(details.inputValue);
7464
+ }
6676
7465
  filter(details.inputValue);
6677
7466
  };
6678
7467
  const handleFocus = (e) => {
6679
- // Select all text when focusing
6680
7468
  e.target.select();
6681
7469
  };
6682
7470
  const handleBlur = (e) => {
6683
- // Parse and commit the input value when losing focus
6684
- const inputValue = e.target.value;
6685
- if (inputValue) {
6686
- parseAndCommitInput(inputValue);
7471
+ const inputVal = e.target.value;
7472
+ if (is24Hour) {
7473
+ setInputValue(inputVal);
7474
+ }
7475
+ if (inputVal) {
7476
+ parseAndCommitInput(inputVal);
6687
7477
  }
6688
7478
  };
6689
7479
  const handleKeyDown = (e) => {
6690
- // Commit input on Enter key
6691
7480
  if (e.key === 'Enter') {
6692
7481
  e.preventDefault();
6693
- const inputValue = e.currentTarget.value;
6694
- if (inputValue) {
6695
- parseAndCommitInput(inputValue);
7482
+ const inputVal = e.currentTarget.value;
7483
+ if (is24Hour) {
7484
+ setInputValue(inputVal);
7485
+ }
7486
+ if (inputVal) {
7487
+ parseAndCommitInput(inputVal);
6696
7488
  }
6697
- // Blur the input
6698
7489
  e.currentTarget?.blur();
6699
7490
  }
6700
7491
  };
6701
- 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 }) }))] }) }));
6702
7493
  };
6703
7494
 
6704
7495
  dayjs.extend(timezone);
6705
7496
  const TimePicker = ({ column, schema, prefix }) => {
6706
7497
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
6707
7498
  const { timezone, insideDialog, timePickerLabels } = useSchemaContext();
6708
- 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;
6709
7500
  const isRequired = required?.some((columnId) => columnId === column);
6710
7501
  const colLabel = `${prefix}${column}`;
6711
7502
  const formI18n = useFormI18n(column, prefix, schema);
6712
7503
  const [open, setOpen] = React.useState(false);
6713
7504
  const value = watch(colLabel);
6714
- const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
6715
- ? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
6716
- : '';
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
+ : '';
6717
7527
  // Parse the initial time parts from the time string (HH:mm:ssZ)
6718
7528
  const parseTime = (time) => {
6719
7529
  if (!time)
@@ -6766,884 +7576,900 @@ const TimePicker = ({ column, schema, prefix }) => {
6766
7576
  return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6767
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: () => {
6768
7578
  setOpen(true);
6769
- }, 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 }) }) }) }) }))] }) }));
6770
7580
  };
6771
7581
 
6772
7582
  dayjs.extend(utc);
6773
7583
  dayjs.extend(timezone);
6774
7584
  dayjs.extend(customParseFormat);
6775
- 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 = {
6776
7586
  monthNamesShort: [
6777
- 'Jan',
6778
- 'Feb',
6779
- 'Mar',
6780
- 'Apr',
7587
+ 'January',
7588
+ 'February',
7589
+ 'March',
7590
+ 'April',
6781
7591
  'May',
6782
- 'Jun',
6783
- 'Jul',
6784
- 'Aug',
6785
- 'Sep',
6786
- 'Oct',
6787
- 'Nov',
6788
- 'Dec',
7592
+ 'June',
7593
+ 'July',
7594
+ 'August',
7595
+ 'September',
7596
+ 'October',
7597
+ 'November',
7598
+ 'December',
6789
7599
  ],
6790
7600
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
6791
7601
  backButtonLabel: 'Back',
6792
- forwardButtonLabel: 'Next',
6793
- }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, }) {
6794
- const [open, setOpen] = React.useState(false);
6795
- const [inputValue, setInputValue] = React.useState('');
6796
- // Update input value when prop value changes
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();
7635
+ }
7636
+ if (defaultTime?.hour !== null && defaultTime?.hour !== undefined) {
7637
+ return defaultTime.hour;
7638
+ }
7639
+ return null;
7640
+ });
7641
+ const [minute, setMinute] = React.useState(() => {
7642
+ if (parsedValue) {
7643
+ return parsedValue.minute();
7644
+ }
7645
+ if (defaultTime?.minute !== null && defaultTime?.minute !== undefined) {
7646
+ return defaultTime.minute;
7647
+ }
7648
+ return null;
7649
+ });
7650
+ const [second, setSecond] = React.useState(() => {
7651
+ if (parsedValue) {
7652
+ return parsedValue.second();
7653
+ }
7654
+ if (defaultTime?.second !== null && defaultTime?.second !== undefined) {
7655
+ return defaultTime.second;
7656
+ }
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';
7663
+ }
7664
+ if (defaultTime?.meridiem !== null && defaultTime?.meridiem !== undefined) {
7665
+ return defaultTime.meridiem;
7666
+ }
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
6797
7688
  React.useEffect(() => {
6798
- if (value) {
6799
- const formatted = typeof value === 'string'
6800
- ? dayjs(value).tz(timezone).isValid()
6801
- ? dayjs(value).tz(timezone).format(displayFormat)
6802
- : ''
6803
- : dayjs(value).tz(timezone).format(displayFormat);
6804
- setInputValue(formatted);
7689
+ if (controlledTimezoneOffset !== undefined) {
7690
+ setInternalTimezoneOffset(controlledTimezoneOffset);
7691
+ }
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);
7699
+ }
7700
+ }
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 });
7712
+ }
7713
+ return options;
7714
+ }, []);
7715
+ // Create collection for Select
7716
+ const { collection: timezoneCollection } = react.useListCollection({
7717
+ initialItems: timezoneOffsetOptions,
7718
+ itemToString: (item) => item.label,
7719
+ itemToValue: (item) => item.value,
7720
+ });
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);
6805
7735
  }
6806
7736
  else {
6807
- setInputValue('');
7737
+ setDateInputValue('');
6808
7738
  }
6809
- }, [value, displayFormat, timezone]);
6810
- // Convert value to Date object for DatePicker
6811
- const selectedDate = value
6812
- ? typeof value === 'string'
6813
- ? dayjs(value).tz(timezone).isValid()
6814
- ? dayjs(value).tz(timezone).toDate()
6815
- : new Date()
6816
- : value
6817
- : new Date();
6818
- // Shared function to parse and validate input value
6819
- const parseAndValidateInput = (inputVal) => {
7739
+ }, [selectedDate, tz]);
7740
+ // Parse and validate date input
7741
+ const parseAndValidateDateInput = (inputVal) => {
6820
7742
  // If empty, clear the value
6821
7743
  if (!inputVal.trim()) {
6822
- onChange?.(undefined);
6823
- setInputValue('');
7744
+ setSelectedDate(null);
7745
+ updateDateTime(null, hour, minute, second, meridiem);
6824
7746
  return;
6825
7747
  }
6826
- // Try parsing with displayFormat first
6827
- let parsedDate = dayjs(inputVal, displayFormat, true);
6828
- // If that fails, try common date formats
7748
+ // Try parsing with common date formats
7749
+ let parsedDate = dayjs(inputVal, 'YYYY-MM-DD', true);
7750
+ // If that fails, try other common formats
6829
7751
  if (!parsedDate.isValid()) {
6830
7752
  parsedDate = dayjs(inputVal);
6831
7753
  }
6832
- // If still invalid, try parsing with dateFormat
6833
- if (!parsedDate.isValid()) {
6834
- parsedDate = dayjs(inputVal, dateFormat, true);
6835
- }
6836
7754
  // If valid, check constraints and update
6837
7755
  if (parsedDate.isValid()) {
6838
- const dateObj = parsedDate.tz(timezone).toDate();
7756
+ const dateObj = parsedDate.tz(tz).toDate();
6839
7757
  // Check min/max constraints
6840
7758
  if (minDate && dateObj < minDate) {
6841
- // Invalid: before minDate, reset to prop value
6842
- resetToPropValue();
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
+ }
6843
7767
  return;
6844
7768
  }
6845
7769
  if (maxDate && dateObj > maxDate) {
6846
- // Invalid: after maxDate, reset to prop value
6847
- resetToPropValue();
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('');
7777
+ }
6848
7778
  return;
6849
7779
  }
6850
- // Valid date - format and update
6851
- const formattedDate = parsedDate.tz(timezone).format(dateFormat);
6852
- const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
6853
- onChange?.(formattedDate);
6854
- setInputValue(formattedDisplay);
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);
6855
7786
  }
6856
7787
  else {
6857
- // Invalid date - reset to prop value
6858
- resetToPropValue();
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
+ }
6859
7796
  }
6860
7797
  };
6861
- // Helper function to reset input to prop value
6862
- const resetToPropValue = () => {
6863
- if (value) {
6864
- const formatted = typeof value === 'string'
6865
- ? dayjs(value).tz(timezone).isValid()
6866
- ? dayjs(value).tz(timezone).format(displayFormat)
6867
- : ''
6868
- : dayjs(value).tz(timezone).format(displayFormat);
6869
- setInputValue(formatted);
6870
- }
6871
- else {
6872
- setInputValue('');
6873
- }
7798
+ const handleDateInputChange = (e) => {
7799
+ setDateInputValue(e.target.value);
6874
7800
  };
6875
- const handleInputChange = (e) => {
6876
- // Only update the input value, don't parse yet
6877
- setInputValue(e.target.value);
7801
+ const handleDateInputBlur = () => {
7802
+ parseAndValidateDateInput(dateInputValue);
6878
7803
  };
6879
- const handleInputBlur = () => {
6880
- // Parse and validate when input loses focus
6881
- parseAndValidateInput(inputValue);
6882
- };
6883
- const handleKeyDown = (e) => {
6884
- // Parse and validate when Enter is pressed
7804
+ const handleDateInputKeyDown = (e) => {
6885
7805
  if (e.key === 'Enter') {
6886
7806
  e.preventDefault();
6887
- parseAndValidateInput(inputValue);
7807
+ parseAndValidateDateInput(dateInputValue);
6888
7808
  }
6889
7809
  };
6890
- const handleDateSelected = ({ date }) => {
6891
- const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
6892
- onChange?.(formattedDate);
6893
- setOpen(false);
6894
- };
6895
- const datePickerContent = (jsxRuntime.jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay }));
6896
- 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 }) }) }) }))] }));
6897
- }
6898
-
6899
- dayjs.extend(utc);
6900
- dayjs.extend(timezone);
6901
- function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
6902
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
6903
- onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6904
- placeholder: 'HH:mm:ss',
6905
- emptyMessage: 'No time found',
6906
- }, }) {
6907
- // Generate time options (every 15 minutes, seconds always 0)
6908
- const timeOptions = React.useMemo(() => {
6909
- const options = [];
6910
- // Get start time for comparison if provided
6911
- let startDateTime = null;
6912
- let shouldFilterByDate = false;
6913
- if (startTime && selectedDate) {
6914
- const startDateObj = dayjs(startTime).tz(timezone);
6915
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6916
- if (startDateObj.isValid() && selectedDateObj.isValid()) {
6917
- startDateTime = startDateObj;
6918
- // Only filter if dates are the same
6919
- shouldFilterByDate =
6920
- startDateObj.format('YYYY-MM-DD') ===
6921
- selectedDateObj.format('YYYY-MM-DD');
6922
- }
6923
- }
6924
- for (let h = 0; h < 24; h++) {
6925
- for (let m = 0; m < 60; m += 15) {
6926
- const timeDisplay = `${h.toString().padStart(2, '0')}:${m
6927
- .toString()
6928
- .padStart(2, '0')}:00`;
6929
- // Filter out times that would result in negative duration (only when dates are the same)
6930
- if (startDateTime && selectedDate && shouldFilterByDate) {
6931
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6932
- const optionDateTime = selectedDateObj
6933
- .hour(h)
6934
- .minute(m)
6935
- .second(0)
6936
- .millisecond(0);
6937
- if (optionDateTime.isBefore(startDateTime)) {
6938
- continue; // Skip this option as it would result in negative duration
6939
- }
6940
- }
6941
- // Calculate duration if startTime is provided
6942
- let durationText;
6943
- if (startDateTime && selectedDate) {
6944
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6945
- const optionDateTime = selectedDateObj
6946
- .hour(h)
6947
- .minute(m)
6948
- .second(0)
6949
- .millisecond(0);
6950
- if (optionDateTime.isValid() &&
6951
- optionDateTime.isAfter(startDateTime)) {
6952
- const diffMs = optionDateTime.diff(startDateTime);
6953
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6954
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6955
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6956
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6957
- let diffText = '';
6958
- if (diffHours > 0) {
6959
- diffText = `${diffHours}h ${diffMinutes}m`;
6960
- }
6961
- else if (diffMinutes > 0) {
6962
- diffText = `${diffMinutes}m ${diffSeconds}s`;
6963
- }
6964
- else {
6965
- diffText = `${diffSeconds}s`;
6966
- }
6967
- durationText = `+${diffText}`;
6968
- }
6969
- }
6970
- }
6971
- options.push({
6972
- label: timeDisplay,
6973
- value: `${h}:${m}:0`,
6974
- hour: h,
6975
- minute: m,
6976
- second: 0,
6977
- searchText: timeDisplay, // Use base time without duration for searching
6978
- durationText,
6979
- });
6980
- }
6981
- }
6982
- return options;
6983
- }, [startTime, selectedDate, timezone]);
6984
- const { contains } = react.useFilter({ sensitivity: 'base' });
6985
- const { collection, filter } = react.useListCollection({
6986
- initialItems: timeOptions,
6987
- itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
6988
- itemToValue: (item) => item.value,
6989
- filter: contains,
6990
- });
6991
- // Get current value string for combobox
6992
- const currentValue = React.useMemo(() => {
6993
- if (hour === null || minute === null || second === null) {
6994
- return '';
6995
- }
6996
- return `${hour}:${minute}:${second}`;
6997
- }, [hour, minute, second]);
6998
- // Calculate duration difference
6999
- const durationDiff = React.useMemo(() => {
7000
- if (!startTime ||
7001
- !selectedDate ||
7002
- hour === null ||
7003
- minute === null ||
7004
- second === null) {
7005
- return null;
7006
- }
7007
- const startDateObj = dayjs(startTime).tz(timezone);
7008
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
7009
- const currentDateTime = selectedDateObj
7010
- .hour(hour)
7011
- .minute(minute)
7012
- .second(second ?? 0)
7013
- .millisecond(0);
7014
- if (!startDateObj.isValid() || !currentDateTime.isValid()) {
7015
- return null;
7016
- }
7017
- const diffMs = currentDateTime.diff(startDateObj);
7018
- if (diffMs < 0) {
7019
- return null;
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;
7020
7822
  }
7021
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
7022
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
7023
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
7024
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
7025
- let diffText = '';
7026
- if (diffHours > 0) {
7027
- diffText = `${diffHours}h ${diffMinutes}m`;
7028
- }
7029
- else if (diffMinutes > 0) {
7030
- diffText = `${diffMinutes}m ${diffSeconds}s`;
7031
- }
7032
- else {
7033
- diffText = `${diffSeconds}s`;
7034
- }
7035
- return `+${diffText}`;
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;
7036
7828
  }
7037
- return null;
7038
- }, [hour, minute, second, startTime, selectedDate, timezone]);
7039
- const handleClear = () => {
7040
- setHour(null);
7041
- setMinute(null);
7042
- setSecond(null);
7043
- filter(''); // Reset filter to show all options
7044
- onChange({ hour: null, minute: null, second: null });
7829
+ return true;
7045
7830
  };
7046
- const handleValueChange = (details) => {
7047
- if (details.value.length === 0) {
7048
- handleClear();
7049
- return;
7050
- }
7051
- const selectedValue = details.value[0];
7052
- const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
7053
- if (selectedOption) {
7054
- setHour(selectedOption.hour);
7055
- setMinute(selectedOption.minute);
7056
- setSecond(selectedOption.second);
7057
- filter(''); // Reset filter after selection
7058
- onChange({
7059
- hour: selectedOption.hour,
7060
- minute: selectedOption.minute,
7061
- second: selectedOption.second,
7062
- });
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);
7063
7838
  }
7064
7839
  };
7065
- // Parse input value and update state
7066
- const parseAndCommitInput = (value) => {
7067
- const trimmedValue = value.trim();
7068
- // Filter the collection based on input
7069
- filter(trimmedValue);
7070
- if (!trimmedValue) {
7071
- return;
7072
- }
7073
- // Parse HH:mm:ss or HH:mm format
7074
- const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
7075
- const match = trimmedValue.match(timePattern);
7076
- if (match) {
7077
- const parsedHour = parseInt(match[1], 10);
7078
- const parsedMinute = parseInt(match[2], 10);
7079
- const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
7080
- // Validate ranges
7081
- if (parsedHour >= 0 &&
7082
- parsedHour <= 23 &&
7083
- parsedMinute >= 0 &&
7084
- parsedMinute <= 59 &&
7085
- parsedSecond >= 0 &&
7086
- parsedSecond <= 59) {
7087
- setHour(parsedHour);
7088
- setMinute(parsedMinute);
7089
- setSecond(parsedSecond);
7090
- onChange({
7091
- hour: parsedHour,
7092
- minute: parsedMinute,
7093
- second: parsedSecond,
7094
- });
7095
- return;
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')}`;
7096
7855
  }
7856
+ return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
7097
7857
  }
7098
7858
  else {
7099
- // Try to parse formats like "123045" (HHmmss) or "1230" (HHmm)
7100
- const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
7101
- if (numbersOnly.length >= 4) {
7102
- const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
7103
- const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
7104
- const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
7105
- // Validate ranges
7106
- if (parsedHour >= 0 &&
7107
- parsedHour <= 23 &&
7108
- parsedMinute >= 0 &&
7109
- parsedMinute <= 59 &&
7110
- parsedSecond >= 0 &&
7111
- parsedSecond <= 59) {
7112
- setHour(parsedHour);
7113
- setMinute(parsedMinute);
7114
- setSecond(parsedSecond);
7115
- onChange({
7116
- hour: parsedHour,
7117
- minute: parsedMinute,
7118
- second: parsedSecond,
7119
- });
7120
- return;
7121
- }
7122
- }
7123
- }
7124
- // Parse failed, select first result
7125
- selectFirstResult();
7126
- };
7127
- // Select first result from filtered collection
7128
- const selectFirstResult = () => {
7129
- if (collection.items.length > 0) {
7130
- const firstItem = collection.items[0];
7131
- setHour(firstItem.hour);
7132
- setMinute(firstItem.minute);
7133
- setSecond(firstItem.second);
7134
- filter(''); // Reset filter after selection
7135
- onChange({
7136
- hour: firstItem.hour,
7137
- minute: firstItem.minute,
7138
- second: firstItem.second,
7139
- });
7140
- }
7141
- };
7142
- const handleInputValueChange = (details) => {
7143
- // Filter the collection based on input, but don't parse yet
7144
- filter(details.inputValue);
7145
- };
7146
- const handleFocus = (e) => {
7147
- // Select all text when focusing
7148
- e.target.select();
7149
- };
7150
- const handleBlur = (e) => {
7151
- // Parse and commit the input value when losing focus
7152
- const inputValue = e.target.value;
7153
- if (inputValue) {
7154
- parseAndCommitInput(inputValue);
7155
- }
7156
- };
7157
- const handleKeyDown = (e) => {
7158
- // Commit input on Enter key
7159
- if (e.key === 'Enter') {
7160
- e.preventDefault();
7161
- const inputValue = e.currentTarget.value;
7162
- if (inputValue) {
7163
- parseAndCommitInput(inputValue);
7164
- }
7165
- // Blur the input
7166
- e.currentTarget?.blur();
7167
- }
7168
- };
7169
- 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, {}) }) })] }) }));
7170
- }
7171
-
7172
- dayjs.extend(utc);
7173
- dayjs.extend(timezone);
7174
- function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
7175
- monthNamesShort: [
7176
- 'Jan',
7177
- 'Feb',
7178
- 'Mar',
7179
- 'Apr',
7180
- 'May',
7181
- 'Jun',
7182
- 'Jul',
7183
- 'Aug',
7184
- 'Sep',
7185
- 'Oct',
7186
- 'Nov',
7187
- 'Dec',
7188
- ],
7189
- weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
7190
- backButtonLabel: 'Back',
7191
- forwardButtonLabel: 'Next',
7192
- }, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, }) {
7193
- console.log('[DateTimePicker] Component initialized with props:', {
7194
- value,
7195
- format,
7196
- showSeconds,
7197
- timezone,
7198
- startTime,
7199
- minDate,
7200
- maxDate,
7201
- });
7202
- // Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
7203
- const getDateString = React.useCallback((val) => {
7204
- if (!val)
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)
7205
7870
  return '';
7206
- const dateObj = dayjs(val).tz(timezone);
7207
- return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
7208
- }, [timezone]);
7209
- const [selectedDate, setSelectedDate] = React.useState(getDateString(value));
7210
- // Helper to get time values from value prop with timezone
7211
- const getTimeFromValue = React.useCallback((val) => {
7212
- console.log('[DateTimePicker] getTimeFromValue called:', {
7213
- val,
7214
- timezone,
7215
- showSeconds,
7216
- });
7217
- if (!val) {
7218
- console.log('[DateTimePicker] No value provided, returning nulls');
7219
- return {
7220
- hour12: null,
7221
- minute: null,
7222
- meridiem: null,
7223
- hour24: null,
7224
- second: null,
7225
- };
7226
- }
7227
- const dateObj = dayjs(val).tz(timezone);
7228
- console.log('[DateTimePicker] Parsed date object:', {
7229
- original: val,
7230
- timezone,
7231
- isValid: dateObj.isValid(),
7232
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7233
- hour24: dateObj.hour(),
7234
- minute: dateObj.minute(),
7235
- second: dateObj.second(),
7236
- });
7237
- if (!dateObj.isValid()) {
7238
- console.log('[DateTimePicker] Invalid date object, returning nulls');
7239
- return {
7240
- hour12: null,
7241
- minute: null,
7242
- meridiem: null,
7243
- hour24: null,
7244
- second: null,
7245
- };
7246
- }
7247
- const hour24Value = dateObj.hour();
7248
- const hour12Value = hour24Value % 12 || 12;
7249
- const minuteValue = dateObj.minute();
7250
- const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
7251
- const secondValue = showSeconds ? dateObj.second() : null;
7252
- const result = {
7253
- hour12: hour12Value,
7254
- minute: minuteValue,
7255
- meridiem: meridiemValue,
7256
- hour24: hour24Value,
7257
- second: secondValue,
7258
- };
7259
- console.log('[DateTimePicker] Extracted time values:', result);
7260
- return result;
7261
- }, [timezone, showSeconds]);
7262
- const initialTime = getTimeFromValue(value);
7263
- console.log('[DateTimePicker] Initial time from value:', {
7264
- value,
7265
- initialTime,
7266
- });
7267
- // Time state for 12-hour format
7268
- const [hour12, setHour12] = React.useState(initialTime.hour12);
7269
- const [minute, setMinute] = React.useState(initialTime.minute);
7270
- const [meridiem, setMeridiem] = React.useState(initialTime.meridiem);
7271
- // Time state for 24-hour format
7272
- const [hour24, setHour24] = React.useState(initialTime.hour24);
7273
- const [second, setSecond] = React.useState(initialTime.second);
7274
- // Sync selectedDate and time states when value prop changes
7871
+ // Show offset as is (e.g., "+08:00")
7872
+ return timezoneOffset;
7873
+ }, [timezoneOffset, showTimezoneSelector]);
7874
+ // Update selectedDate when value changes externally
7275
7875
  React.useEffect(() => {
7276
- console.log('[DateTimePicker] useEffect triggered - value changed:', {
7277
- value,
7278
- timezone,
7279
- format,
7280
- });
7281
- // If value is null, undefined, or invalid, clear all fields
7282
- if (!value || value === null || value === undefined) {
7283
- console.log('[DateTimePicker] Value is null/undefined, clearing all fields');
7284
- setSelectedDate('');
7285
- setHour12(null);
7286
- setMinute(null);
7287
- setMeridiem(null);
7288
- setHour24(null);
7289
- setSecond(null);
7290
- return;
7291
- }
7292
- // Check if value is valid
7293
- const dateObj = dayjs(value).tz(timezone);
7294
- if (!dateObj.isValid()) {
7295
- console.log('[DateTimePicker] Invalid value, clearing all fields');
7296
- setSelectedDate('');
7297
- setHour12(null);
7298
- setMinute(null);
7299
- setMeridiem(null);
7300
- setHour24(null);
7301
- setSecond(null);
7302
- return;
7303
- }
7304
- const dateString = getDateString(value);
7305
- console.log('[DateTimePicker] Setting selectedDate:', dateString);
7306
- setSelectedDate(dateString);
7307
- const timeData = getTimeFromValue(value);
7308
- console.log('[DateTimePicker] Updating time states:', {
7309
- timeData,
7310
- });
7311
- setHour12(timeData.hour12);
7312
- setMinute(timeData.minute);
7313
- setMeridiem(timeData.meridiem);
7314
- setHour24(timeData.hour24);
7315
- setSecond(timeData.second);
7316
- }, [value, getTimeFromValue, getDateString, timezone]);
7317
- const handleDateChange = (date) => {
7318
- console.log('[DateTimePicker] handleDateChange called:', {
7319
- date,
7320
- timezone,
7321
- showSeconds,
7322
- currentTimeStates: { hour12, minute, meridiem, hour24, second },
7323
- });
7324
- // If date is empty or invalid, clear all fields
7325
- if (!date || date === '') {
7326
- console.log('[DateTimePicker] Empty date, clearing all fields');
7327
- setSelectedDate('');
7328
- setHour12(null);
7329
- setMinute(null);
7330
- setMeridiem(null);
7331
- setHour24(null);
7332
- setSecond(null);
7333
- onChange?.(undefined);
7334
- return;
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
+ }
7335
7885
  }
7336
- setSelectedDate(date);
7337
- // Parse the date string (YYYY-MM-DD) in the specified timezone
7338
- const dateObj = dayjs.tz(date, timezone);
7339
- console.log('[DateTimePicker] Parsed date object:', {
7340
- date,
7341
- timezone,
7342
- isValid: dateObj.isValid(),
7343
- isoString: dateObj.toISOString(),
7344
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7345
- });
7346
- if (!dateObj.isValid()) {
7347
- console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
7348
- setSelectedDate('');
7349
- setHour12(null);
7350
- setMinute(null);
7351
- setMeridiem(null);
7352
- setHour24(null);
7353
- setSecond(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) {
7354
7890
  onChange?.(undefined);
7355
7891
  return;
7356
7892
  }
7357
- // When showSeconds is false, ignore seconds from the date
7358
- if (!showSeconds) {
7359
- const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
7360
- console.log('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
7361
- updateDateTime(dateWithoutSeconds);
7362
- }
7363
- else {
7364
- const dateWithSeconds = dateObj.toISOString();
7365
- console.log('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
7366
- updateDateTime(dateWithSeconds);
7367
- }
7368
- };
7369
- const handleTimeChange = (timeData) => {
7370
- console.log('[DateTimePicker] handleTimeChange called:', {
7371
- timeData,
7372
- format,
7373
- selectedDate,
7374
- timezone,
7375
- });
7376
- if (format === 'iso-date-time') {
7377
- const data = timeData;
7378
- console.log('[DateTimePicker] ISO format - setting 24-hour time:', data);
7379
- setHour24(data.hour);
7380
- setMinute(data.minute);
7381
- if (showSeconds) {
7382
- setSecond(data.second ?? null);
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
+ }
7383
7916
  }
7384
7917
  else {
7385
- // Ignore seconds - always set to null when showSeconds is false
7386
- 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
+ }
7387
7925
  }
7388
7926
  }
7389
- else {
7390
- const data = timeData;
7391
- console.log('[DateTimePicker] 12-hour format - setting time:', data);
7392
- setHour12(data.hour);
7393
- setMinute(data.minute);
7394
- setMeridiem(data.meridiem);
7395
- }
7396
- // Use selectedDate if valid, otherwise clear all fields
7397
- if (!selectedDate || !dayjs(selectedDate).isValid()) {
7398
- console.log('[DateTimePicker] No valid selectedDate, clearing all fields');
7399
- setSelectedDate('');
7400
- setHour12(null);
7401
- setMinute(null);
7402
- setMeridiem(null);
7403
- setHour24(null);
7404
- 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
+ // Ensure offset format is correct (should be +HH:mm or -HH:mm, not ending with Z)
7946
+ const cleanOffset = offsetToUse.replace(/Z$/, '');
7947
+ onChange?.(`${formattedDateTime}${cleanOffset}`);
7948
+ return;
7949
+ }
7950
+ // Normal mode: use timezone conversion
7951
+ let dateTime = dayjs(newDate)
7952
+ .tz(tz)
7953
+ .hour(hour24)
7954
+ .minute(newMinute)
7955
+ .second(newSecond ?? 0)
7956
+ .millisecond(0);
7957
+ if (!dateTime.isValid()) {
7405
7958
  onChange?.(undefined);
7406
7959
  return;
7407
7960
  }
7408
- const dateObj = dayjs(selectedDate).tz(timezone);
7409
- if (dateObj.isValid()) {
7410
- updateDateTime(dateObj.toISOString(), timeData);
7961
+ // Format based on format prop
7962
+ if (format === 'iso-date-time') {
7963
+ onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ss'));
7411
7964
  }
7412
7965
  else {
7413
- console.warn('[DateTimePicker] Invalid date object in handleTimeChange, clearing fields');
7414
- setSelectedDate('');
7415
- setHour12(null);
7416
- setMinute(null);
7417
- setMeridiem(null);
7418
- setHour24(null);
7419
- setSecond(null);
7420
- onChange?.(undefined);
7966
+ // date-time format with timezone
7967
+ onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ssZ'));
7421
7968
  }
7422
7969
  };
7423
- const updateDateTime = (date, timeData) => {
7424
- console.log('[DateTimePicker] updateDateTime called:', {
7425
- date,
7426
- timeData,
7427
- format,
7428
- currentStates: { hour12, minute, meridiem, hour24, second },
7429
- });
7430
- if (!date || date === null || date === undefined) {
7431
- console.log('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
7432
- setSelectedDate('');
7433
- setHour12(null);
7434
- setMinute(null);
7435
- setMeridiem(null);
7436
- setHour24(null);
7437
- setSecond(null);
7438
- onChange?.(undefined);
7439
- return;
7970
+ // Handle date selection
7971
+ const handleDateSelected = ({ date, }) => {
7972
+ setSelectedDate(date);
7973
+ updateDateTime(date, hour, minute, second, meridiem);
7974
+ };
7975
+ // Handle time change
7976
+ const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
7977
+ setHour(newHour);
7978
+ setMinute(newMinute);
7979
+ if (is24Hour) {
7980
+ setSecond(newSecond);
7440
7981
  }
7441
- // use dayjs to convert the date to the timezone
7442
- const dateObj = dayjs(date).tz(timezone);
7443
- if (!dateObj.isValid()) {
7444
- console.warn('[DateTimePicker] Invalid date object in updateDateTime, clearing fields:', date);
7445
- setSelectedDate('');
7446
- setHour12(null);
7447
- setMinute(null);
7448
- setMeridiem(null);
7449
- setHour24(null);
7450
- setSecond(null);
7451
- onChange?.(undefined);
7452
- return;
7982
+ else {
7983
+ setMeridiem(newMeridiem);
7453
7984
  }
7454
- const newDate = dateObj.toDate();
7455
- if (format === 'iso-date-time') {
7456
- const data = timeData;
7457
- // Use timeData values if provided, otherwise fall back to current state
7458
- // But if timeData is explicitly provided with nulls, we need to check if all are null
7459
- const h = data !== undefined ? data.hour : hour24;
7460
- const m = data !== undefined ? data.minute : minute;
7461
- // Always ignore seconds when showSeconds is false - set to 0
7462
- const s = showSeconds
7463
- ? data !== undefined
7464
- ? data.second ?? null
7465
- : second ?? 0
7466
- : 0;
7467
- // If all time values are null, clear the value
7468
- if (h === null && m === null && (showSeconds ? s === null : true)) {
7469
- console.log('[DateTimePicker] All time values are null, clearing value');
7470
- onChange?.(undefined);
7471
- return;
7985
+ if (selectedDate) {
7986
+ updateDateTime(selectedDate, newHour, newMinute, newSecond, newMeridiem);
7987
+ }
7988
+ };
7989
+ // Calendar hook
7990
+ const calendarProps = useCalendar({
7991
+ selected: selectedDate || undefined,
7992
+ date: selectedDate || undefined,
7993
+ minDate,
7994
+ maxDate,
7995
+ monthsToDisplay: 1,
7996
+ onDateSelected: handleDateSelected,
7997
+ });
7998
+ // Convert DateTimePickerLabels to DatePickerLabels format
7999
+ const calendarLabels = React.useMemo(() => ({
8000
+ monthNamesShort: labels.monthNamesShort || [
8001
+ 'Jan',
8002
+ 'Feb',
8003
+ 'Mar',
8004
+ 'Apr',
8005
+ 'May',
8006
+ 'Jun',
8007
+ 'Jul',
8008
+ 'Aug',
8009
+ 'Sep',
8010
+ 'Oct',
8011
+ 'Nov',
8012
+ 'Dec',
8013
+ ],
8014
+ weekdayNamesShort: labels.weekdayNamesShort || [
8015
+ 'Sun',
8016
+ 'Mon',
8017
+ 'Tue',
8018
+ 'Wed',
8019
+ 'Thu',
8020
+ 'Fri',
8021
+ 'Sat',
8022
+ ],
8023
+ backButtonLabel: labels.backButtonLabel || 'Back',
8024
+ forwardButtonLabel: labels.forwardButtonLabel || 'Forward',
8025
+ todayLabel: quickActionLabels.today || 'Today',
8026
+ yesterdayLabel: quickActionLabels.yesterday || 'Yesterday',
8027
+ tomorrowLabel: quickActionLabels.tomorrow || 'Tomorrow',
8028
+ }), [labels, quickActionLabels]);
8029
+ // Generate time options
8030
+ const timeOptions = React.useMemo(() => {
8031
+ const options = [];
8032
+ // Get start time for comparison if provided
8033
+ let startDateTime = null;
8034
+ let shouldFilterByDate = false;
8035
+ if (startTime && selectedDate) {
8036
+ const startDateObj = dayjs(startTime).tz(tz);
8037
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8038
+ if (startDateObj.isValid() && selectedDateObj.isValid()) {
8039
+ startDateTime = startDateObj;
8040
+ shouldFilterByDate =
8041
+ startDateObj.format('YYYY-MM-DD') ===
8042
+ selectedDateObj.format('YYYY-MM-DD');
8043
+ }
8044
+ }
8045
+ if (is24Hour) {
8046
+ // Generate 24-hour format options
8047
+ for (let h = 0; h < 24; h++) {
8048
+ for (let m = 0; m < 60; m += 15) {
8049
+ // Filter out times that would result in negative duration
8050
+ if (startDateTime && selectedDate && shouldFilterByDate) {
8051
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8052
+ const optionDateTime = selectedDateObj
8053
+ .hour(h)
8054
+ .minute(m)
8055
+ .second(0)
8056
+ .millisecond(0);
8057
+ if (optionDateTime.isBefore(startDateTime)) {
8058
+ continue;
8059
+ }
8060
+ }
8061
+ // Calculate duration if startTime is provided
8062
+ let durationText;
8063
+ if (startDateTime && selectedDate) {
8064
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8065
+ const optionDateTime = selectedDateObj
8066
+ .hour(h)
8067
+ .minute(m)
8068
+ .second(0)
8069
+ .millisecond(0);
8070
+ if (optionDateTime.isValid() &&
8071
+ optionDateTime.isAfter(startDateTime)) {
8072
+ const diffMs = optionDateTime.diff(startDateTime);
8073
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
8074
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
8075
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
8076
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
8077
+ let diffText = '';
8078
+ if (diffHours > 0) {
8079
+ diffText = `${diffHours}h ${diffMinutes}m`;
8080
+ }
8081
+ else if (diffMinutes > 0) {
8082
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
8083
+ }
8084
+ else {
8085
+ diffText = `${diffSeconds}s`;
8086
+ }
8087
+ durationText = `+${diffText}`;
8088
+ }
8089
+ }
8090
+ }
8091
+ const s = showSeconds ? 0 : 0;
8092
+ const timeDisplay = showSeconds
8093
+ ? `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`
8094
+ : `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
8095
+ options.push({
8096
+ label: timeDisplay,
8097
+ value: `${h}:${m}:${s}`,
8098
+ hour: h,
8099
+ minute: m,
8100
+ second: s,
8101
+ searchText: timeDisplay,
8102
+ durationText,
8103
+ });
8104
+ }
7472
8105
  }
7473
- console.log('[DateTimePicker] ISO format - setting time on date:', {
7474
- h,
7475
- m,
7476
- s,
7477
- showSeconds,
7478
- });
7479
- if (h !== null)
7480
- newDate.setHours(h);
7481
- if (m !== null)
7482
- newDate.setMinutes(m);
7483
- newDate.setSeconds(s ?? 0);
7484
8106
  }
7485
8107
  else {
7486
- const data = timeData;
7487
- console.log('[DateTimePicker] Processing 12-hour format:', {
7488
- 'data !== undefined': data !== undefined,
7489
- 'data?.hour': data?.hour,
7490
- 'data?.minute': data?.minute,
7491
- 'data?.meridiem': data?.meridiem,
7492
- 'current hour12': hour12,
7493
- 'current minute': minute,
7494
- 'current meridiem': meridiem,
7495
- });
7496
- // Use timeData values if provided, otherwise fall back to current state
7497
- const h = data !== undefined ? data.hour : hour12;
7498
- const m = data !== undefined ? data.minute : minute;
7499
- const mer = data !== undefined ? data.meridiem : meridiem;
7500
- console.log('[DateTimePicker] Resolved time values:', { h, m, mer });
7501
- // If all time values are null, clear the value
7502
- if (h === null && m === null && mer === null) {
7503
- console.log('[DateTimePicker] All time values are null, clearing value');
7504
- onChange?.(undefined);
7505
- return;
8108
+ // Generate 12-hour format options
8109
+ for (let h = 1; h <= 12; h++) {
8110
+ for (let m = 0; m < 60; m += 15) {
8111
+ for (const mer of ['am', 'pm']) {
8112
+ // Convert 12-hour to 24-hour for comparison
8113
+ let hour24 = h;
8114
+ if (mer === 'am' && h === 12)
8115
+ hour24 = 0;
8116
+ else if (mer === 'pm' && h < 12)
8117
+ hour24 = h + 12;
8118
+ // Filter out times that would result in negative duration
8119
+ if (startDateTime && selectedDate && shouldFilterByDate) {
8120
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8121
+ const optionDateTime = selectedDateObj
8122
+ .hour(hour24)
8123
+ .minute(m)
8124
+ .second(0)
8125
+ .millisecond(0);
8126
+ if (optionDateTime.isBefore(startDateTime)) {
8127
+ continue;
8128
+ }
8129
+ }
8130
+ // Calculate duration if startTime is provided
8131
+ let durationText;
8132
+ if (startDateTime && selectedDate) {
8133
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8134
+ const optionDateTime = selectedDateObj
8135
+ .hour(hour24)
8136
+ .minute(m)
8137
+ .second(0)
8138
+ .millisecond(0);
8139
+ if (optionDateTime.isValid() &&
8140
+ optionDateTime.isAfter(startDateTime)) {
8141
+ const diffMs = optionDateTime.diff(startDateTime);
8142
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
8143
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
8144
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
8145
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
8146
+ let diffText = '';
8147
+ if (diffHours > 0) {
8148
+ diffText = `${diffHours}h ${diffMinutes}m`;
8149
+ }
8150
+ else if (diffMinutes > 0) {
8151
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
8152
+ }
8153
+ else {
8154
+ diffText = `${diffSeconds}s`;
8155
+ }
8156
+ durationText = `+${diffText}`;
8157
+ }
8158
+ }
8159
+ }
8160
+ const hourDisplay = h.toString();
8161
+ const minuteDisplay = m.toString().padStart(2, '0');
8162
+ const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
8163
+ options.push({
8164
+ label: timeDisplay,
8165
+ value: `${h}:${m}:${mer}`,
8166
+ hour: h,
8167
+ minute: m,
8168
+ meridiem: mer,
8169
+ searchText: timeDisplay,
8170
+ durationText,
8171
+ });
8172
+ }
8173
+ }
7506
8174
  }
7507
- console.log('[DateTimePicker] 12-hour format - converting time:', {
7508
- h,
7509
- m,
7510
- mer,
8175
+ // Sort 12-hour options by time
8176
+ return options.sort((a, b) => {
8177
+ const a12 = a;
8178
+ const b12 = b;
8179
+ let hour24A = a12.hour;
8180
+ if (a12.meridiem === 'am' && a12.hour === 12)
8181
+ hour24A = 0;
8182
+ else if (a12.meridiem === 'pm' && a12.hour < 12)
8183
+ hour24A = a12.hour + 12;
8184
+ let hour24B = b12.hour;
8185
+ if (b12.meridiem === 'am' && b12.hour === 12)
8186
+ hour24B = 0;
8187
+ else if (b12.meridiem === 'pm' && b12.hour < 12)
8188
+ hour24B = b12.hour + 12;
8189
+ if (hour24A !== hour24B) {
8190
+ return hour24A - hour24B;
8191
+ }
8192
+ return a12.minute - b12.minute;
7511
8193
  });
7512
- if (h !== null && mer !== null) {
7513
- let hour24 = h;
7514
- if (mer === 'am' && h === 12)
7515
- hour24 = 0;
7516
- else if (mer === 'pm' && h < 12)
7517
- hour24 = h + 12;
7518
- console.log('[DateTimePicker] Converted to 24-hour:', {
7519
- h,
7520
- mer,
7521
- hour24,
7522
- });
7523
- newDate.setHours(hour24);
8194
+ }
8195
+ return options;
8196
+ }, [startTime, selectedDate, tz, is24Hour, showSeconds]);
8197
+ // Time picker combobox setup
8198
+ const itemToString = React.useMemo(() => {
8199
+ return (item) => {
8200
+ return item.searchText;
8201
+ };
8202
+ }, []);
8203
+ const { contains } = react.useFilter({ sensitivity: 'base' });
8204
+ const customTimeFilter = React.useMemo(() => {
8205
+ if (is24Hour) {
8206
+ return contains;
8207
+ }
8208
+ return (itemText, filterText) => {
8209
+ if (!filterText) {
8210
+ return true;
7524
8211
  }
7525
- else {
7526
- console.log('[DateTimePicker] Skipping hour update - h or mer is null:', {
7527
- h,
7528
- mer,
7529
- });
8212
+ const lowerItemText = itemText.toLowerCase();
8213
+ const lowerFilterText = filterText.toLowerCase();
8214
+ if (lowerItemText.includes(lowerFilterText)) {
8215
+ return true;
7530
8216
  }
7531
- if (m !== null) {
7532
- newDate.setMinutes(m);
8217
+ const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
8218
+ if (!item || !('meridiem' in item)) {
8219
+ return false;
7533
8220
  }
7534
- else {
7535
- console.log('[DateTimePicker] Skipping minute update - m is null');
8221
+ let hour24 = item.hour;
8222
+ if (item.meridiem === 'am' && item.hour === 12)
8223
+ hour24 = 0;
8224
+ else if (item.meridiem === 'pm' && item.hour < 12)
8225
+ hour24 = item.hour + 12;
8226
+ const hour24Str = hour24.toString().padStart(2, '0');
8227
+ const minuteStr = item.minute.toString().padStart(2, '0');
8228
+ const formats = [
8229
+ `${hour24Str}:${minuteStr}`,
8230
+ `${hour24Str}${minuteStr}`,
8231
+ hour24Str,
8232
+ `${hour24}:${minuteStr}`,
8233
+ hour24.toString(),
8234
+ ];
8235
+ return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
8236
+ lowerFilterText.includes(format.toLowerCase()));
8237
+ };
8238
+ }, [timeOptions, is24Hour, contains]);
8239
+ const { collection, filter } = react.useListCollection({
8240
+ initialItems: timeOptions,
8241
+ itemToString: itemToString,
8242
+ itemToValue: (item) => item.value,
8243
+ filter: customTimeFilter,
8244
+ });
8245
+ // Get current value string for combobox (must match option.value format)
8246
+ const currentTimeValue = React.useMemo(() => {
8247
+ if (is24Hour) {
8248
+ if (hour === null || minute === null) {
8249
+ return '';
7536
8250
  }
7537
- newDate.setSeconds(0);
7538
- }
7539
- const finalISO = dayjs(newDate).tz(timezone).toISOString();
7540
- console.log('[DateTimePicker] Final ISO string to emit:', {
7541
- newDate: newDate.toISOString(),
7542
- timezone,
7543
- finalISO,
7544
- });
7545
- onChange?.(finalISO);
7546
- };
7547
- const handleClear = () => {
7548
- setSelectedDate('');
7549
- setHour12(null);
7550
- setHour24(null);
7551
- setMinute(null);
7552
- setSecond(null);
7553
- setMeridiem(null);
7554
- onChange?.(undefined);
7555
- };
7556
- const isISO = format === 'iso-date-time';
7557
- // Normalize startTime to ignore milliseconds
7558
- const normalizedStartTime = startTime
7559
- ? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
7560
- : undefined;
7561
- // Determine minDate: prioritize explicit minDate prop, then fall back to startTime
7562
- const effectiveMinDate = minDate
7563
- ? minDate
7564
- : normalizedStartTime && dayjs(normalizedStartTime).tz(timezone).isValid()
7565
- ? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
7566
- : undefined;
7567
- // Log current state before render
7568
- React.useEffect(() => {
7569
- console.log('[DateTimePicker] Current state before render:', {
7570
- isISO,
7571
- hour12,
7572
- minute,
7573
- meridiem,
7574
- hour24,
7575
- second,
7576
- selectedDate,
7577
- normalizedStartTime,
7578
- timezone,
7579
- });
7580
- }, [
7581
- isISO,
7582
- hour12,
7583
- minute,
7584
- meridiem,
7585
- hour24,
7586
- second,
7587
- selectedDate,
7588
- normalizedStartTime,
7589
- timezone,
7590
- ]);
7591
- // Compute display text from current state
7592
- const displayText = React.useMemo(() => {
7593
- if (!selectedDate)
7594
- return null;
7595
- const dateObj = dayjs.tz(selectedDate, timezone);
7596
- if (!dateObj.isValid())
7597
- return null;
7598
- if (isISO) {
7599
- // For ISO format, use hour24, minute, second
7600
- if (hour24 === null || minute === null)
7601
- return null;
7602
- const dateTimeObj = dateObj
7603
- .hour(hour24)
7604
- .minute(minute)
7605
- .second(second ?? 0);
7606
- return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
8251
+ const s = second ?? 0;
8252
+ return `${hour}:${minute}:${s}`;
7607
8253
  }
7608
8254
  else {
7609
- // For 12-hour format, use hour12, minute, meridiem
7610
- if (hour12 === null || minute === null || meridiem === null)
7611
- return null;
7612
- // Convert to 24-hour format for dayjs
7613
- let hour24Value = hour12;
7614
- if (meridiem === 'am' && hour12 === 12)
7615
- hour24Value = 0;
7616
- else if (meridiem === 'pm' && hour12 < 12)
7617
- hour24Value = hour12 + 12;
7618
- const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
7619
- return dateTimeObj.format('YYYY-MM-DD hh:mm A');
8255
+ if (hour === null || minute === null || meridiem === null) {
8256
+ return '';
8257
+ }
8258
+ return `${hour}:${minute}:${meridiem}`;
8259
+ }
8260
+ }, [hour, minute, second, meridiem, is24Hour]);
8261
+ // Parse custom time input formats like "1400", "2pm", "14:00", "2:00 PM"
8262
+ const parseCustomTimeInput = (input) => {
8263
+ if (!input || !input.trim()) {
8264
+ return { hour: null, minute: null, second: null, meridiem: null };
8265
+ }
8266
+ const trimmed = input.trim().toLowerCase();
8267
+ // Try parsing 4-digit format without colon: "1400" -> 14:00
8268
+ const fourDigitMatch = trimmed.match(/^(\d{4})$/);
8269
+ if (fourDigitMatch) {
8270
+ const digits = fourDigitMatch[1];
8271
+ const hour = parseInt(digits.substring(0, 2), 10);
8272
+ const minute = parseInt(digits.substring(2, 4), 10);
8273
+ if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
8274
+ if (is24Hour) {
8275
+ return { hour, minute, second: 0, meridiem: null };
8276
+ }
8277
+ else {
8278
+ // Convert to 12-hour format
8279
+ let hour12 = hour;
8280
+ let meridiem;
8281
+ if (hour === 0) {
8282
+ hour12 = 12;
8283
+ meridiem = 'am';
8284
+ }
8285
+ else if (hour === 12) {
8286
+ hour12 = 12;
8287
+ meridiem = 'pm';
8288
+ }
8289
+ else if (hour > 12) {
8290
+ hour12 = hour - 12;
8291
+ meridiem = 'pm';
8292
+ }
8293
+ else {
8294
+ hour12 = hour;
8295
+ meridiem = 'am';
8296
+ }
8297
+ return { hour: hour12, minute, second: null, meridiem };
8298
+ }
8299
+ }
7620
8300
  }
7621
- }, [
7622
- selectedDate,
7623
- isISO,
7624
- hour12,
7625
- minute,
7626
- meridiem,
7627
- hour24,
7628
- second,
7629
- showSeconds,
7630
- timezone,
7631
- ]);
7632
- const timezoneOffset = React.useMemo(() => {
7633
- if (!selectedDate)
7634
- return null;
7635
- const dateObj = dayjs.tz(selectedDate, timezone);
7636
- return dateObj.isValid() ? dateObj.format('Z') : null;
7637
- }, [selectedDate, timezone]);
7638
- return (jsxRuntime.jsxs(react.Flex, { direction: "column", gap: 4, children: [jsxRuntime.jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
7639
- if (date) {
7640
- handleDateChange(date);
8301
+ // Try parsing hour with meridiem: "2pm", "14pm", "2am"
8302
+ const hourMeridiemMatch = trimmed.match(/^(\d{1,2})\s*(am|pm)$/);
8303
+ if (hourMeridiemMatch && !is24Hour) {
8304
+ const hour12 = parseInt(hourMeridiemMatch[1], 10);
8305
+ const meridiem = hourMeridiemMatch[2];
8306
+ if (hour12 >= 1 && hour12 <= 12) {
8307
+ return { hour: hour12, minute: 0, second: null, meridiem };
8308
+ }
8309
+ }
8310
+ // Try parsing 24-hour format with hour only: "14" -> 14:00
8311
+ const hourOnlyMatch = trimmed.match(/^(\d{1,2})$/);
8312
+ if (hourOnlyMatch && is24Hour) {
8313
+ const hour = parseInt(hourOnlyMatch[1], 10);
8314
+ if (hour >= 0 && hour <= 23) {
8315
+ return { hour, minute: 0, second: 0, meridiem: null };
8316
+ }
8317
+ }
8318
+ // Try parsing standard formats: "14:00", "2:00 PM"
8319
+ const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
8320
+ const match24 = trimmed.match(time24Pattern);
8321
+ if (match24) {
8322
+ const hour24 = parseInt(match24[1], 10);
8323
+ const minute = parseInt(match24[2], 10);
8324
+ const second = match24[3] ? parseInt(match24[3], 10) : 0;
8325
+ if (hour24 >= 0 &&
8326
+ hour24 <= 23 &&
8327
+ minute >= 0 &&
8328
+ minute <= 59 &&
8329
+ second >= 0 &&
8330
+ second <= 59) {
8331
+ if (is24Hour) {
8332
+ return { hour: hour24, minute, second, meridiem: null };
8333
+ }
8334
+ else {
8335
+ // Convert to 12-hour format
8336
+ let hour12 = hour24;
8337
+ let meridiem;
8338
+ if (hour24 === 0) {
8339
+ hour12 = 12;
8340
+ meridiem = 'am';
8341
+ }
8342
+ else if (hour24 === 12) {
8343
+ hour12 = 12;
8344
+ meridiem = 'pm';
8345
+ }
8346
+ else if (hour24 > 12) {
8347
+ hour12 = hour24 - 12;
8348
+ meridiem = 'pm';
7641
8349
  }
7642
8350
  else {
7643
- setSelectedDate('');
7644
- onChange?.(undefined);
8351
+ hour12 = hour24;
8352
+ meridiem = 'am';
8353
+ }
8354
+ return { hour: hour12, minute, second: null, meridiem };
8355
+ }
8356
+ }
8357
+ }
8358
+ // Try parsing 12-hour format: "2:00 PM", "2:00PM"
8359
+ const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm)$/;
8360
+ const match12 = trimmed.match(time12Pattern);
8361
+ if (match12 && !is24Hour) {
8362
+ const hour12 = parseInt(match12[1], 10);
8363
+ const minute = parseInt(match12[2], 10);
8364
+ const second = match12[3] ? parseInt(match12[3], 10) : null;
8365
+ const meridiem = match12[4];
8366
+ if (hour12 >= 1 &&
8367
+ hour12 <= 12 &&
8368
+ minute >= 0 &&
8369
+ minute <= 59 &&
8370
+ (second === null || (second >= 0 && second <= 59))) {
8371
+ return { hour: hour12, minute, second, meridiem };
8372
+ }
8373
+ }
8374
+ return { hour: null, minute: null, second: null, meridiem: null };
8375
+ };
8376
+ const handleTimeValueChange = (details) => {
8377
+ if (details.value.length === 0) {
8378
+ handleTimeChange(null, null, null, null);
8379
+ filter('');
8380
+ return;
8381
+ }
8382
+ const selectedValue = details.value[0];
8383
+ const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
8384
+ if (selectedOption) {
8385
+ filter('');
8386
+ if (is24Hour) {
8387
+ const opt24 = selectedOption;
8388
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
8389
+ }
8390
+ else {
8391
+ const opt12 = selectedOption;
8392
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
8393
+ }
8394
+ }
8395
+ };
8396
+ // Track the current input value for Enter key handling
8397
+ const [timeInputValue, setTimeInputValue] = React.useState('');
8398
+ const handleTimeInputChange = (details) => {
8399
+ // Store the input value and filter
8400
+ setTimeInputValue(details.inputValue);
8401
+ filter(details.inputValue);
8402
+ };
8403
+ const handleTimeInputKeyDown = (e) => {
8404
+ if (e.key === 'Enter') {
8405
+ e.preventDefault();
8406
+ // Use the stored input value
8407
+ const parsed = parseCustomTimeInput(timeInputValue);
8408
+ if (parsed.hour !== null && parsed.minute !== null) {
8409
+ if (is24Hour) {
8410
+ handleTimeChange(parsed.hour, parsed.minute, parsed.second, null);
8411
+ }
8412
+ else {
8413
+ if (parsed.meridiem !== null) {
8414
+ handleTimeChange(parsed.hour, parsed.minute, null, parsed.meridiem);
7645
8415
  }
7646
- }, 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 })] }))] }));
8416
+ }
8417
+ // Clear the filter and input value after applying
8418
+ filter('');
8419
+ setTimeInputValue('');
8420
+ // Close the popover if value is valid
8421
+ setTimePopoverOpen(false);
8422
+ }
8423
+ }
8424
+ };
8425
+ 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 ??
8426
+ (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 ??
8427
+ 'No time found' }), collection.items.map((item) => {
8428
+ const option = item;
8429
+ 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));
8430
+ })] }) }) })] }) }) }) }) }) })) : (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 ??
8431
+ (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) => {
8432
+ const option = item;
8433
+ 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));
8434
+ })] }) }) })] }) }) }) }) }))] }), 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) => {
8435
+ const newOffset = e.value[0];
8436
+ if (newOffset) {
8437
+ // Update controlled or internal state
8438
+ if (onTimezoneOffsetChange) {
8439
+ onTimezoneOffsetChange(newOffset);
8440
+ }
8441
+ else {
8442
+ setInternalTimezoneOffset(newOffset);
8443
+ }
8444
+ // Update date-time with new offset (pass it directly to avoid stale state)
8445
+ if (selectedDate &&
8446
+ hour !== null &&
8447
+ minute !== null) {
8448
+ updateDateTime(selectedDate, hour, minute, second, meridiem, newOffset);
8449
+ }
8450
+ // Close popover after selection
8451
+ setTimezonePopoverOpen(false);
8452
+ }
8453
+ }, 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) => {
8454
+ const newOffset = e.value[0];
8455
+ if (newOffset) {
8456
+ // Update controlled or internal state
8457
+ if (onTimezoneOffsetChange) {
8458
+ onTimezoneOffsetChange(newOffset);
8459
+ }
8460
+ else {
8461
+ setInternalTimezoneOffset(newOffset);
8462
+ }
8463
+ // Update date-time with new offset (pass it directly to avoid stale state)
8464
+ if (selectedDate &&
8465
+ hour !== null &&
8466
+ minute !== null) {
8467
+ updateDateTime(selectedDate, hour, minute, second, meridiem, newOffset);
8468
+ }
8469
+ // Close popover after selection
8470
+ setTimezonePopoverOpen(false);
8471
+ }
8472
+ }, 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))) }) })] }) }) }) }) }))] }))] }));
7647
8473
  }
7648
8474
 
7649
8475
  dayjs.extend(utc);
@@ -7654,14 +8480,15 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
7654
8480
  const formI18n = useFormI18n(column, prefix, schema);
7655
8481
  const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD HH:mm:ss',
7656
8482
  // with timezone
7657
- dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', } = schema;
8483
+ dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', dateTimePicker, } = schema;
7658
8484
  const isRequired = required?.some((columnId) => columnId === column);
7659
8485
  const colLabel = formI18n.colLabel;
7660
- const [open, setOpen] = React.useState(false);
8486
+ React.useState(false);
7661
8487
  const selectedDate = watch(colLabel);
7662
- const displayDate = selectedDate && dayjs(selectedDate).tz(timezone).isValid()
8488
+ selectedDate && dayjs(selectedDate).tz(timezone).isValid()
7663
8489
  ? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
7664
8490
  : '';
8491
+ // Set default date on mount if no value exists
7665
8492
  const dateTimePickerLabelsConfig = {
7666
8493
  monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
7667
8494
  'January',
@@ -7701,11 +8528,10 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
7701
8528
  else {
7702
8529
  setValue(colLabel, undefined);
7703
8530
  }
7704
- }, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels }));
8531
+ }, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels, showQuickActions: dateTimePicker?.showQuickActions ?? false, quickActionLabels: dateTimePickerLabels?.quickActionLabels ??
8532
+ dateTimePicker?.quickActionLabels, showTimezoneSelector: dateTimePicker?.showTimezoneSelector ?? false }));
7705
8533
  return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
7706
- 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: () => {
7707
- setOpen(true);
7708
- }, 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 }) }) }) }))] }) }));
8534
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: dateTimePickerContent }));
7709
8535
  };
7710
8536
 
7711
8537
  const SchemaRenderer = ({ schema, prefix, column, }) => {
@@ -7826,7 +8652,7 @@ const BooleanViewer = ({ schema, column, prefix, }) => {
7826
8652
  const value = watch(colLabel);
7827
8653
  const formI18n = useFormI18n(column, prefix, schema);
7828
8654
  return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
7829
- 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() }))] }));
8655
+ gridRow, children: [jsxRuntime.jsx(react.Text, { children: value ? 'True' : 'False' }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: 'red.400', children: formI18n.required() }))] }));
7830
8656
  };
7831
8657
 
7832
8658
  const CustomViewer = ({ column, schema, prefix }) => {
@@ -7858,23 +8684,22 @@ const DateViewer = ({ column, schema, prefix }) => {
7858
8684
 
7859
8685
  const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
7860
8686
  const { watch, formState: { errors }, } = reactHookForm.useFormContext();
7861
- const formI18n = useFormI18n(column, prefix);
8687
+ const formI18n = useFormI18n(column, prefix, schema);
7862
8688
  const { required } = schema;
7863
8689
  const isRequired = required?.some((columnId) => columnId === column);
7864
- const { gridColumn = "span 12", gridRow = "span 1", renderDisplay } = schema;
8690
+ const { gridColumn = 'span 12', gridRow = 'span 1', renderDisplay } = schema;
7865
8691
  const colLabel = formI18n.colLabel;
7866
8692
  const watchEnum = watch(colLabel);
7867
8693
  const watchEnums = (watch(colLabel) ?? []);
7868
- return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: "stretch", gridColumn,
7869
- gridRow, children: [isMultiple && (jsxRuntime.jsx(react.Flex, { flexFlow: "wrap", gap: 1, children: watchEnums.map((enumValue) => {
8694
+ const renderDisplayFunction = renderDisplay || defaultRenderDisplay;
8695
+ return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
8696
+ gridRow, children: [isMultiple && (jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 1, children: watchEnums.map((enumValue) => {
7870
8697
  const item = enumValue;
7871
8698
  if (item === undefined) {
7872
8699
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: "undefined" });
7873
8700
  }
7874
- return (jsxRuntime.jsx(Tag, { size: "lg", children: !!renderDisplay === true
7875
- ? renderDisplay(item)
7876
- : formI18n.t(item) }, item));
7877
- }) })), !isMultiple && jsxRuntime.jsx(react.Text, { children: formI18n.t(watchEnum) }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: formI18n.required() }))] }));
8701
+ return (jsxRuntime.jsx(Tag, { size: "lg", children: renderDisplayFunction(item) }, item));
8702
+ }) })), !isMultiple && jsxRuntime.jsx(react.Text, { children: renderDisplayFunction(watchEnum) }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: 'red.400', children: formI18n.required() }))] }));
7878
8703
  };
7879
8704
 
7880
8705
  const FileViewer = ({ column, schema, prefix }) => {
@@ -7994,31 +8819,35 @@ const StringViewer = ({ column, schema, prefix, }) => {
7994
8819
 
7995
8820
  const TagViewer = ({ column, schema, prefix }) => {
7996
8821
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
7997
- const { serverUrl } = useSchemaContext();
7998
8822
  if (schema.properties == undefined) {
7999
- throw new Error("schema properties undefined when using DatePicker");
8823
+ throw new Error('schema properties undefined when using DatePicker');
8000
8824
  }
8001
- const { gridColumn, gridRow, in_table, object_id_column } = schema;
8825
+ const { gridColumn, gridRow, in_table, object_id_column, tagPicker } = schema;
8002
8826
  if (in_table === undefined) {
8003
- throw new Error("in_table is undefined when using TagPicker");
8827
+ throw new Error('in_table is undefined when using TagPicker');
8004
8828
  }
8005
8829
  if (object_id_column === undefined) {
8006
- throw new Error("object_id_column is undefined when using TagPicker");
8830
+ throw new Error('object_id_column is undefined when using TagPicker');
8831
+ }
8832
+ if (!tagPicker?.queryFn) {
8833
+ throw new Error('tagPicker.queryFn is required in schema. serverUrl has been removed.');
8007
8834
  }
8008
8835
  const query = reactQuery.useQuery({
8009
8836
  queryKey: [`tagpicker`, in_table],
8010
8837
  queryFn: async () => {
8011
- return await getTableData({
8012
- serverUrl,
8013
- in_table: "tables_tags_view",
8838
+ const result = await tagPicker.queryFn({
8839
+ in_table: 'tables_tags_view',
8014
8840
  where: [
8015
8841
  {
8016
- id: "table_name",
8842
+ id: 'table_name',
8017
8843
  value: [in_table],
8018
8844
  },
8019
8845
  ],
8020
8846
  limit: 100,
8847
+ offset: 0,
8848
+ searching: '',
8021
8849
  });
8850
+ return result.data || { data: [] };
8022
8851
  },
8023
8852
  staleTime: 10000,
8024
8853
  });
@@ -8026,17 +8855,19 @@ const TagViewer = ({ column, schema, prefix }) => {
8026
8855
  const existingTagsQuery = reactQuery.useQuery({
8027
8856
  queryKey: [`existing`, { in_table, object_id_column }, object_id],
8028
8857
  queryFn: async () => {
8029
- return await getTableData({
8030
- serverUrl,
8858
+ const result = await tagPicker.queryFn({
8031
8859
  in_table: in_table,
8032
8860
  where: [
8033
8861
  {
8034
8862
  id: object_id_column,
8035
- value: object_id[0],
8863
+ value: [object_id[0]],
8036
8864
  },
8037
8865
  ],
8038
8866
  limit: 100,
8867
+ offset: 0,
8868
+ searching: '',
8039
8869
  });
8870
+ return result.data || { data: [] };
8040
8871
  },
8041
8872
  enabled: object_id != undefined,
8042
8873
  staleTime: 10000,
@@ -8047,9 +8878,9 @@ const TagViewer = ({ column, schema, prefix }) => {
8047
8878
  if (!!object_id === false) {
8048
8879
  return jsxRuntime.jsx(jsxRuntime.Fragment, {});
8049
8880
  }
8050
- return (jsxRuntime.jsxs(react.Flex, { flexFlow: "column", gap: 4, gridColumn,
8881
+ return (jsxRuntime.jsxs(react.Flex, { flexFlow: 'column', gap: 4, gridColumn,
8051
8882
  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 }) => {
8052
- 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) => {
8883
+ 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) => {
8053
8884
  const existedTags = Object.values(all_tags)
8054
8885
  .filter(({ id }) => {
8055
8886
  return existingTagList.some(({ tag_id }) => tag_id === id);
@@ -8061,20 +8892,20 @@ const TagViewer = ({ column, schema, prefix }) => {
8061
8892
  tagIds.value,
8062
8893
  ]);
8063
8894
  setValue(`${column}.${parent_tag_name}.old`, existedTags);
8064
- }, children: jsxRuntime.jsx(react.Flex, { flexFlow: "wrap", gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8895
+ }, children: jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8065
8896
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
8066
- return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: "0 0 0%", disabled: true }, `${tagName}-${id}`));
8897
+ return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: '0 0 0%', disabled: true }, `${tagName}-${id}`));
8067
8898
  }
8068
- return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: "0 0 0%", colorPalette: "blue" }, `${tagName}-${id}`));
8899
+ return (jsxRuntime.jsx(RadioCardItem, { label: tagName, value: id, flex: '0 0 0%', colorPalette: 'blue' }, `${tagName}-${id}`));
8069
8900
  }) }) })), !is_mutually_exclusive && (jsxRuntime.jsx(react.CheckboxGroup, { onValueChange: (tagIds) => {
8070
8901
  setValue(`${column}.${parent_tag_name}.current`, tagIds);
8071
- }, children: jsxRuntime.jsx(react.Flex, { flexFlow: "wrap", gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8902
+ }, children: jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8072
8903
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
8073
- return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: "0 0 0%", disabled: true, colorPalette: "blue" }, `${tagName}-${id}`));
8904
+ return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: '0 0 0%', disabled: true, colorPalette: 'blue' }, `${tagName}-${id}`));
8074
8905
  }
8075
- return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: "0 0 0%" }, `${tagName}-${id}`));
8906
+ return (jsxRuntime.jsx(CheckboxCard, { label: tagName, value: id, flex: '0 0 0%' }, `${tagName}-${id}`));
8076
8907
  }) }) }))] }, `tag-${parent_tag_name}`));
8077
- }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: (errors[`${column}`]?.message ?? "No error message") }))] }));
8908
+ }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: 'red.400', children: (errors[`${column}`]?.message ?? 'No error message') }))] }));
8078
8909
  };
8079
8910
 
8080
8911
  const TextAreaViewer = ({ column, schema, prefix, }) => {
@@ -8281,6 +9112,17 @@ const FormBody = () => {
8281
9112
 
8282
9113
  const FormTitle = () => {
8283
9114
  const { schema } = useSchemaContext();
9115
+ // Debug log when form title is missing
9116
+ if (!schema.title) {
9117
+ console.debug('[Form Title] Missing title in root schema. Add title property to schema.', {
9118
+ schema: {
9119
+ type: schema.type,
9120
+ properties: schema.properties
9121
+ ? Object.keys(schema.properties)
9122
+ : undefined,
9123
+ },
9124
+ });
9125
+ }
8284
9126
  return jsxRuntime.jsx(react.Heading, { children: schema.title ?? 'Form' });
8285
9127
  };
8286
9128
 
@@ -9449,6 +10291,7 @@ exports.CardHeader = CardHeader;
9449
10291
  exports.DataDisplay = DataDisplay;
9450
10292
  exports.DataTable = DataTable;
9451
10293
  exports.DataTableServer = DataTableServer;
10294
+ exports.DatePickerContext = DatePickerContext;
9452
10295
  exports.DatePickerInput = DatePickerInput;
9453
10296
  exports.DefaultCardTitle = DefaultCardTitle;
9454
10297
  exports.DefaultForm = DefaultForm;