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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, CheckboxCard as CheckboxCard$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Clipboard, Badge, Link, Tag as Tag$1, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, Skeleton, NumberInput, Show, RadioCard, CheckboxGroup, InputGroup as InputGroup$1, Center, Heading, Stack } from '@chakra-ui/react';
2
+ import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, CheckboxCard as CheckboxCard$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Clipboard, Badge, Link, Tag as Tag$1, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, useCombobox, Show, Skeleton, NumberInput, RadioCard, CheckboxGroup, InputGroup as InputGroup$1, Select, Center, Heading, Stack } from '@chakra-ui/react';
3
3
  import { AiOutlineColumnWidth } from 'react-icons/ai';
4
4
  import * as React from 'react';
5
5
  import { createContext, useContext, useState, useMemo, useCallback, useEffect, useRef, forwardRef } from 'react';
6
6
  import { LuX, LuCheck, LuChevronRight, LuCopy, LuExternalLink, LuSearch, LuImage, LuFile } from 'react-icons/lu';
7
7
  import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineChecklist, MdClear, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdDateRange } from 'react-icons/md';
8
- import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
9
- import { BiDownArrow, BiUpArrow, BiError } from 'react-icons/bi';
8
+ import { FaUpDown, FaGripLinesVertical } from 'react-icons/fa6';
9
+ import { BiDownArrow, BiUpArrow, BiX, BiError } from 'react-icons/bi';
10
10
  import { CgClose, CgTrash } from 'react-icons/cg';
11
11
  import { HiMiniEllipsisHorizontal, HiChevronLeft, HiChevronRight } from 'react-icons/hi2';
12
12
  import { IoMdEye, IoMdCheckbox, IoMdClock } from 'react-icons/io';
@@ -28,10 +28,10 @@ import { FormProvider, useFormContext, useForm as useForm$1 } from 'react-hook-f
28
28
  import Ajv from 'ajv';
29
29
  import addFormats from 'ajv-formats';
30
30
  import dayjs from 'dayjs';
31
- import utc from 'dayjs/plugin/utc';
31
+ import customParseFormat from 'dayjs/plugin/customParseFormat';
32
32
  import timezone from 'dayjs/plugin/timezone';
33
+ import utc from 'dayjs/plugin/utc';
33
34
  import { TiDeleteOutline } from 'react-icons/ti';
34
- import customParseFormat from 'dayjs/plugin/customParseFormat';
35
35
  import { rankItem } from '@tanstack/match-sorter-utils';
36
36
 
37
37
  const DataTableContext = createContext({
@@ -3694,7 +3694,7 @@ const TextWithCopy = ({ text, globalFilter, highlightedText, }) => {
3694
3694
  const displayText = highlightedText !== undefined
3695
3695
  ? highlightedText
3696
3696
  : highlightText$1(textValue, globalFilter);
3697
- return (jsxs(HStack, { gap: 2, alignItems: "center", children: [jsx(Text, { as: "span", children: displayText }), jsx(Clipboard.Root, { value: textValue, children: jsx(Clipboard.Trigger, { asChild: true, children: jsx(IconButton, { size: "xs", variant: "ghost", "aria-label": "Copy", fontSize: "1em", children: jsx(Clipboard.Indicator, { copied: jsx(LuCheck, {}), children: jsx(LuCopy, {}) }) }) }) })] }));
3697
+ return (jsxs(HStack, { gap: 2, alignItems: "center", children: [jsx(Text, { as: "span", children: displayText }), jsx(Clipboard.Root, { value: textValue, children: jsx(Clipboard.Trigger, { asChild: true, children: jsx(IconButton, { size: "2xs", variant: "ghost", "aria-label": "Copy", fontSize: "1em", children: jsx(Clipboard.Indicator, { copied: jsx(LuCheck, {}), children: jsx(LuCopy, {}) }) }) }) })] }));
3698
3698
  };
3699
3699
 
3700
3700
  // Helper function to highlight matching text
@@ -4016,7 +4016,6 @@ const getColumns = ({ schema, include = [], ignore = [], width = [], meta = {},
4016
4016
  //@ts-expect-error TODO: find appropriate type
4017
4017
  const SchemaFormContext = createContext({
4018
4018
  schema: {},
4019
- serverUrl: '',
4020
4019
  requestUrl: '',
4021
4020
  order: [],
4022
4021
  ignore: [],
@@ -4127,6 +4126,22 @@ const convertAjvErrorsToFieldErrors = (errors, schema) => {
4127
4126
  // Get the schema node for this field to check for custom error messages
4128
4127
  const fieldSchema = getSchemaNodeForField(schema, fieldName);
4129
4128
  const customMessage = fieldSchema?.errorMessages?.[error.keyword];
4129
+ // Debug log when error message is missing
4130
+ if (!customMessage) {
4131
+ console.debug(`[Form Validation] Missing error message for field '${fieldName}' with keyword '${error.keyword}'. Add errorMessages.${error.keyword} to schema for field '${fieldName}'`, {
4132
+ fieldName,
4133
+ keyword: error.keyword,
4134
+ instancePath: error.instancePath,
4135
+ schemaPath: error.schemaPath,
4136
+ params: error.params,
4137
+ fieldSchema: fieldSchema
4138
+ ? {
4139
+ type: fieldSchema.type,
4140
+ errorMessages: fieldSchema.errorMessages,
4141
+ }
4142
+ : undefined,
4143
+ });
4144
+ }
4130
4145
  // Provide helpful fallback message if no custom message is provided
4131
4146
  const fallbackMessage = customMessage ||
4132
4147
  `Missing error message for ${error.keyword}. Add errorMessages.${error.keyword} to schema for field '${fieldName}'`;
@@ -4256,7 +4271,7 @@ const idPickerSanityCheck = (column, foreign_key) => {
4256
4271
  throw new Error(`The key column does not exist in properties of column ${column} when using id-picker.`);
4257
4272
  }
4258
4273
  };
4259
- const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer, customSuccessRenderer, displayConfig = {
4274
+ const FormRoot = ({ schema, idMap, setIdMap, form, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer, customSuccessRenderer, displayConfig = {
4260
4275
  showSubmitButton: true,
4261
4276
  showResetButton: true,
4262
4277
  showTitle: true,
@@ -4297,9 +4312,11 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
4297
4312
  }
4298
4313
  };
4299
4314
  const defaultSubmitPromise = (data) => {
4315
+ if (!requestOptions.url) {
4316
+ throw new Error('requestOptions.url is required when onSubmit is not provided');
4317
+ }
4300
4318
  const options = {
4301
4319
  method: 'POST',
4302
- url: `${serverUrl}`,
4303
4320
  data: clearEmptyString(data),
4304
4321
  ...requestOptions,
4305
4322
  };
@@ -4317,7 +4334,6 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
4317
4334
  };
4318
4335
  return (jsx(SchemaFormContext.Provider, { value: {
4319
4336
  schema,
4320
- serverUrl,
4321
4337
  order,
4322
4338
  ignore,
4323
4339
  include,
@@ -4357,39 +4373,31 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
4357
4373
  }, children: jsx(FormProvider, { ...form, children: children }) }));
4358
4374
  };
4359
4375
 
4360
- function removeIndex(str) {
4361
- return str.replace(/\.\d+\./g, ".");
4362
- }
4363
-
4364
4376
  /**
4365
- * Custom hook for form field labels and fallback text.
4366
- * Automatically handles colLabel construction and removeIndex logic.
4367
- * Uses schema.title when available, otherwise falls back to translate function.
4377
+ * Custom hook for form field labels.
4378
+ * Automatically handles colLabel construction.
4379
+ * Uses schema.title for labels and schema.errorMessages for error messages.
4368
4380
  *
4369
4381
  * @param column - The column name
4370
4382
  * @param prefix - The prefix for the field (usually empty string or parent path)
4371
- * @param schema - Optional schema object with title property
4383
+ * @param schema - Required schema object with title and errorMessages properties
4372
4384
  * @returns Object with label helper functions
4373
4385
  *
4374
4386
  * @example
4375
4387
  * ```tsx
4376
4388
  * const formI18n = useFormI18n(column, prefix, schema);
4377
4389
  *
4378
- * // Get field label (prefers schema.title)
4390
+ * // Get field label (from schema.title)
4379
4391
  * <Field label={formI18n.label()} />
4380
4392
  *
4381
- * // Get required error message
4393
+ * // Get required error message (from schema.errorMessages?.required)
4382
4394
  * <Text>{formI18n.required()}</Text>
4383
4395
  *
4384
- * // Get custom text
4385
- * <Text>{formI18n.t('add_more')}</Text>
4386
- *
4387
4396
  * // Access the raw colLabel
4388
4397
  * const colLabel = formI18n.colLabel;
4389
4398
  * ```
4390
4399
  */
4391
4400
  const useFormI18n = (column, prefix = '', schema) => {
4392
- const { translate } = useSchemaContext();
4393
4401
  const colLabel = `${prefix}${column}`;
4394
4402
  return {
4395
4403
  /**
@@ -4397,36 +4405,55 @@ const useFormI18n = (column, prefix = '', schema) => {
4397
4405
  */
4398
4406
  colLabel,
4399
4407
  /**
4400
- * Get the field label from schema title prop, or fall back to translate function
4401
- * Uses schema.title if available, otherwise: translate.t(removeIndex(`${colLabel}.field_label`))
4408
+ * Get the field label from schema title property.
4409
+ * Logs a debug message if title is missing.
4402
4410
  */
4403
- label: (options) => {
4404
- if (schema?.title) {
4411
+ label: () => {
4412
+ if (schema.title) {
4405
4413
  return schema.title;
4406
4414
  }
4407
- return translate.t(removeIndex(`${colLabel}.field_label`), options);
4408
- },
4409
- /**
4410
- * Get the required error message
4411
- * Equivalent to: translate.t(removeIndex(`${colLabel}.field_required`))
4412
- */
4413
- required: (options) => {
4414
- return translate.t(removeIndex(`${colLabel}.field_required`), options);
4415
+ // Debug log when field title is missing
4416
+ console.debug(`[Form Field Label] Missing title for field '${colLabel}'. Add title property to schema for field '${colLabel}'.`, {
4417
+ fieldName: column,
4418
+ colLabel,
4419
+ prefix,
4420
+ schema: {
4421
+ type: schema.type,
4422
+ errorMessages: schema.errorMessages
4423
+ ? Object.keys(schema.errorMessages)
4424
+ : undefined,
4425
+ },
4426
+ });
4427
+ // Return column name as fallback
4428
+ return column;
4415
4429
  },
4416
4430
  /**
4417
- * Get text for any custom key relative to the field
4418
- * Equivalent to: translate.t(removeIndex(`${colLabel}.${key}`))
4419
- *
4420
- * @param key - The key suffix (e.g., 'add_more', 'total', etc.)
4421
- * @param options - Optional options (e.g., defaultValue, interpolation variables)
4431
+ * Get the required error message from schema.errorMessages?.required.
4432
+ * Returns a helpful fallback message if not provided.
4422
4433
  */
4423
- t: (key, options) => {
4424
- return translate.t(removeIndex(`${colLabel}.${key}`), options);
4434
+ required: () => {
4435
+ const errorMessage = schema.errorMessages?.required;
4436
+ if (errorMessage) {
4437
+ return errorMessage;
4438
+ }
4439
+ // Debug log when error message is missing
4440
+ console.debug(`[Form Field Required] Missing error message for required field '${colLabel}'. Add errorMessages.required to schema for field '${colLabel}'.`, {
4441
+ fieldName: column,
4442
+ colLabel,
4443
+ prefix,
4444
+ schema: {
4445
+ type: schema.type,
4446
+ title: schema.title,
4447
+ required: schema.required,
4448
+ hasErrorMessages: !!schema.errorMessages,
4449
+ errorMessageKeys: schema.errorMessages
4450
+ ? Object.keys(schema.errorMessages)
4451
+ : undefined,
4452
+ },
4453
+ });
4454
+ // Return helpful fallback message
4455
+ return `Missing error message for required. Add errorMessages.required to schema for field '${colLabel}'`;
4425
4456
  },
4426
- /**
4427
- * Access to the original translate object for edge cases
4428
- */
4429
- translate,
4430
4457
  };
4431
4458
  };
4432
4459
 
@@ -4501,52 +4528,56 @@ const Calendar = ({ calendars, getBackProps, getForwardProps, getDateProps, firs
4501
4528
  const { labels } = useContext(DatePickerContext);
4502
4529
  const { monthNamesShort, weekdayNamesShort, backButtonLabel, forwardButtonLabel, } = labels;
4503
4530
  if (calendars.length) {
4504
- return (jsxs(Grid, { children: [jsxs(Grid, { templateColumns: 'repeat(4, auto)', justifyContent: 'center', children: [jsx(Button$1, { variant: 'ghost', ...getBackProps({
4505
- calendars,
4506
- offset: 12,
4507
- }), children: '<<' }), jsx(Button$1, { variant: 'ghost', ...getBackProps({ calendars }), children: backButtonLabel }), jsx(Button$1, { variant: 'ghost', ...getForwardProps({ calendars }), children: forwardButtonLabel }), jsx(Button$1, { variant: 'ghost', ...getForwardProps({
4508
- calendars,
4509
- offset: 12,
4510
- }), children: '>>' })] }), jsx(Grid, { templateColumns: 'repeat(2, auto)', justifyContent: 'center', children: calendars.map((calendar) => (jsxs(Grid, { gap: 4, children: [jsxs(Grid, { justifyContent: 'center', children: [monthNamesShort[calendar.month], " ", calendar.year] }), jsxs(Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: [[0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
4511
- const weekday = (weekdayNum + firstDayOfWeek) % 7;
4512
- return (jsx(Text, { textAlign: 'center', children: weekdayNamesShort[weekday] }, `${calendar.month}${calendar.year}${weekday}`));
4513
- }), calendar.weeks.map((week, weekIndex) => week.map((dateObj, index) => {
4514
- const key = `${calendar.month}${calendar.year}${weekIndex}${index}`;
4515
- if (!dateObj) {
4516
- return jsx(Grid, {}, key);
4531
+ return (jsx(Grid, { children: jsx(Grid, { templateColumns: 'repeat(2, auto)', justifyContent: 'center', children: calendars.map((calendar) => (jsxs(Grid, { gap: 2, children: [jsxs(Grid, { templateColumns: 'repeat(6, auto)', justifyContent: 'center', alignItems: 'center', gap: 2, children: [jsx(Button$1, { variant: 'ghost', size: 'sm', colorPalette: 'gray', ...getBackProps({ calendars }), children: '<' }), jsx(Text, { textAlign: 'center', children: monthNamesShort[calendar.month] }), jsx(Button$1, { variant: 'ghost', size: 'sm', colorPalette: 'gray', ...getForwardProps({ calendars }), children: '>' }), jsx(Button$1, { variant: 'ghost', size: 'sm', colorPalette: 'gray', ...getBackProps({
4532
+ calendars,
4533
+ offset: 12,
4534
+ }), children: '<' }), jsx(Text, { textAlign: 'center', children: calendar.year }), jsx(Button$1, { variant: 'ghost', size: 'sm', colorPalette: 'gray', ...getForwardProps({
4535
+ calendars,
4536
+ offset: 12,
4537
+ }), children: '>' })] }), jsxs(Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: [[0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
4538
+ const weekday = (weekdayNum + firstDayOfWeek) % 7;
4539
+ return (jsx(Text, { textAlign: 'center', children: weekdayNamesShort[weekday] }, `${calendar.month}${calendar.year}${weekday}`));
4540
+ }), calendar.weeks.map((week, weekIndex) => week.map((dateObj, index) => {
4541
+ const key = `${calendar.month}${calendar.year}${weekIndex}${index}`;
4542
+ if (!dateObj) {
4543
+ return jsx(Grid, {}, key);
4544
+ }
4545
+ const { date, selected, selectable, today, isCurrentMonth, } = dateObj;
4546
+ const getDateColor = ({ today, selected, selectable, }) => {
4547
+ if (!selectable) {
4548
+ return 'gray';
4517
4549
  }
4518
- const { date, selected, selectable, today, isCurrentMonth, } = dateObj;
4519
- const getDateColor = ({ today, selected, selectable, }) => {
4520
- if (!selectable) {
4521
- return 'gray';
4522
- }
4523
- if (selected) {
4524
- return 'blue';
4525
- }
4526
- if (today) {
4527
- return 'green';
4528
- }
4529
- return '';
4530
- };
4531
- const getVariant = ({ today, selected, selectable, }) => {
4532
- if (!selectable) {
4533
- return 'surface';
4534
- }
4535
- if (selected) {
4536
- return 'solid';
4537
- }
4538
- if (today) {
4539
- return 'surface';
4540
- }
4541
- return 'ghost';
4542
- };
4543
- const color = getDateColor({ today, selected, selectable });
4544
- const variant = getVariant({ today, selected, selectable });
4545
- return (jsx(Button$1, { variant: variant, colorPalette: color, opacity: isCurrentMonth ? 1 : 0.4, ...getDateProps({ dateObj }), children: selectable ? date.getDate() : 'X' }, key));
4546
- }))] })] }, `${calendar.month}${calendar.year}`))) })] }));
4550
+ if (selected) {
4551
+ return 'blue';
4552
+ }
4553
+ if (today) {
4554
+ return 'green';
4555
+ }
4556
+ return '';
4557
+ };
4558
+ const getVariant = ({ today, selected, selectable, }) => {
4559
+ if (!selectable) {
4560
+ return 'surface';
4561
+ }
4562
+ if (selected) {
4563
+ return 'surface';
4564
+ }
4565
+ if (today) {
4566
+ return 'outline';
4567
+ }
4568
+ return 'ghost';
4569
+ };
4570
+ const color = getDateColor({ today, selected, selectable });
4571
+ const variant = getVariant({ today, selected, selectable });
4572
+ return (jsx(Button$1, { variant: variant, colorPalette: color, size: 'xs', opacity: isCurrentMonth ? 1 : 0.4, ...getDateProps({ dateObj }), children: selectable ? date.getDate() : 'X' }, key));
4573
+ }))] })] }, `${calendar.month}${calendar.year}`))) }) }));
4547
4574
  }
4548
4575
  return null;
4549
4576
  };
4577
+
4578
+ dayjs.extend(utc);
4579
+ dayjs.extend(timezone);
4580
+ dayjs.extend(customParseFormat);
4550
4581
  const DatePickerContext = createContext({
4551
4582
  labels: {
4552
4583
  monthNamesShort: [
@@ -4566,6 +4597,9 @@ const DatePickerContext = createContext({
4566
4597
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
4567
4598
  backButtonLabel: 'Back',
4568
4599
  forwardButtonLabel: 'Next',
4600
+ todayLabel: 'Today',
4601
+ yesterdayLabel: 'Yesterday',
4602
+ tomorrowLabel: 'Tomorrow',
4569
4603
  },
4570
4604
  });
4571
4605
  const DatePicker$1 = ({ labels = {
@@ -4586,6 +4620,9 @@ const DatePicker$1 = ({ labels = {
4586
4620
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
4587
4621
  backButtonLabel: 'Back',
4588
4622
  forwardButtonLabel: 'Next',
4623
+ todayLabel: 'Today',
4624
+ yesterdayLabel: 'Yesterday',
4625
+ tomorrowLabel: 'Tomorrow',
4589
4626
  }, onDateSelected, selected, firstDayOfWeek, showOutsideDays, date, minDate, maxDate, monthsToDisplay, render, }) => {
4590
4627
  const calendarData = useCalendar({
4591
4628
  onDateSelected,
@@ -4600,9 +4637,171 @@ const DatePicker$1 = ({ labels = {
4600
4637
  return (jsx(DatePickerContext.Provider, { value: { labels }, children: render ? (render(calendarData)) : (jsx(Calendar, { ...calendarData,
4601
4638
  firstDayOfWeek })) }));
4602
4639
  };
4640
+ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
4641
+ monthNamesShort: [
4642
+ 'Jan',
4643
+ 'Feb',
4644
+ 'Mar',
4645
+ 'Apr',
4646
+ 'May',
4647
+ 'Jun',
4648
+ 'Jul',
4649
+ 'Aug',
4650
+ 'Sep',
4651
+ 'Oct',
4652
+ 'Nov',
4653
+ 'Dec',
4654
+ ],
4655
+ weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
4656
+ backButtonLabel: 'Back',
4657
+ forwardButtonLabel: 'Next',
4658
+ todayLabel: 'Today',
4659
+ yesterdayLabel: 'Yesterday',
4660
+ tomorrowLabel: 'Tomorrow',
4661
+ }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, showHelperButtons = true, }) {
4662
+ const [open, setOpen] = useState(false);
4663
+ const [inputValue, setInputValue] = useState('');
4664
+ // Sync inputValue with value prop changes
4665
+ useEffect(() => {
4666
+ if (value) {
4667
+ const formatted = typeof value === 'string'
4668
+ ? dayjs(value).tz(timezone).isValid()
4669
+ ? dayjs(value).tz(timezone).format(displayFormat)
4670
+ : ''
4671
+ : dayjs(value).tz(timezone).format(displayFormat);
4672
+ setInputValue(formatted);
4673
+ }
4674
+ else {
4675
+ setInputValue('');
4676
+ }
4677
+ }, [value, timezone, displayFormat]);
4678
+ // Convert value to Date object for DatePicker
4679
+ const selectedDate = value
4680
+ ? typeof value === 'string'
4681
+ ? dayjs(value).tz(timezone).isValid()
4682
+ ? dayjs(value).tz(timezone).toDate()
4683
+ : new Date()
4684
+ : value
4685
+ : new Date();
4686
+ // Shared function to parse and validate input value
4687
+ const parseAndValidateInput = (inputVal) => {
4688
+ // If empty, clear the value
4689
+ if (!inputVal.trim()) {
4690
+ onChange?.(undefined);
4691
+ setInputValue('');
4692
+ return;
4693
+ }
4694
+ // Try parsing with displayFormat first
4695
+ let parsedDate = dayjs(inputVal, displayFormat, true);
4696
+ // If that fails, try common date formats
4697
+ if (!parsedDate.isValid()) {
4698
+ parsedDate = dayjs(inputVal);
4699
+ }
4700
+ // If still invalid, try parsing with dateFormat
4701
+ if (!parsedDate.isValid()) {
4702
+ parsedDate = dayjs(inputVal, dateFormat, true);
4703
+ }
4704
+ // If valid, check constraints and update
4705
+ if (parsedDate.isValid()) {
4706
+ const dateObj = parsedDate.tz(timezone).toDate();
4707
+ // Check min/max constraints
4708
+ if (minDate && dateObj < minDate) {
4709
+ // Invalid: before minDate, reset to prop value
4710
+ resetToPropValue();
4711
+ return;
4712
+ }
4713
+ if (maxDate && dateObj > maxDate) {
4714
+ // Invalid: after maxDate, reset to prop value
4715
+ resetToPropValue();
4716
+ return;
4717
+ }
4718
+ // Valid date - format and update
4719
+ const formattedDate = parsedDate.tz(timezone).format(dateFormat);
4720
+ const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
4721
+ onChange?.(formattedDate);
4722
+ setInputValue(formattedDisplay);
4723
+ }
4724
+ else {
4725
+ // Invalid date - reset to prop value
4726
+ resetToPropValue();
4727
+ }
4728
+ };
4729
+ // Helper function to reset input to prop value
4730
+ const resetToPropValue = () => {
4731
+ if (value) {
4732
+ const formatted = typeof value === 'string'
4733
+ ? dayjs(value).tz(timezone).isValid()
4734
+ ? dayjs(value).tz(timezone).format(displayFormat)
4735
+ : ''
4736
+ : dayjs(value).tz(timezone).format(displayFormat);
4737
+ setInputValue(formatted);
4738
+ }
4739
+ else {
4740
+ setInputValue('');
4741
+ }
4742
+ };
4743
+ const handleInputChange = (e) => {
4744
+ // Only update the input value, don't parse yet
4745
+ setInputValue(e.target.value);
4746
+ };
4747
+ const handleInputBlur = () => {
4748
+ // Parse and validate when input loses focus
4749
+ parseAndValidateInput(inputValue);
4750
+ };
4751
+ const handleKeyDown = (e) => {
4752
+ // Parse and validate when Enter is pressed
4753
+ if (e.key === 'Enter') {
4754
+ e.preventDefault();
4755
+ parseAndValidateInput(inputValue);
4756
+ }
4757
+ };
4758
+ const handleDateSelected = ({ date }) => {
4759
+ console.debug('[DatePickerInput] handleDateSelected called:', {
4760
+ date: date.toISOString(),
4761
+ timezone,
4762
+ dateFormat,
4763
+ formattedDate: dayjs(date).tz(timezone).format(dateFormat),
4764
+ });
4765
+ const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
4766
+ console.debug('[DatePickerInput] Calling onChange with formatted date:', formattedDate);
4767
+ onChange?.(formattedDate);
4768
+ setOpen(false);
4769
+ };
4770
+ // Helper function to get dates in the correct timezone
4771
+ const getToday = () => dayjs().tz(timezone).startOf('day').toDate();
4772
+ const getYesterday = () => dayjs().tz(timezone).subtract(1, 'day').startOf('day').toDate();
4773
+ const getTomorrow = () => dayjs().tz(timezone).add(1, 'day').startOf('day').toDate();
4774
+ // Check if a date is within min/max constraints
4775
+ const isDateValid = (date) => {
4776
+ if (minDate) {
4777
+ const minDateStart = dayjs(minDate).tz(timezone).startOf('day').toDate();
4778
+ const dateStart = dayjs(date).tz(timezone).startOf('day').toDate();
4779
+ if (dateStart < minDateStart)
4780
+ return false;
4781
+ }
4782
+ if (maxDate) {
4783
+ const maxDateStart = dayjs(maxDate).tz(timezone).startOf('day').toDate();
4784
+ const dateStart = dayjs(date).tz(timezone).startOf('day').toDate();
4785
+ if (dateStart > maxDateStart)
4786
+ return false;
4787
+ }
4788
+ return true;
4789
+ };
4790
+ const handleHelperButtonClick = (date) => {
4791
+ if (isDateValid(date)) {
4792
+ handleDateSelected({ date });
4793
+ }
4794
+ };
4795
+ const today = getToday();
4796
+ const yesterday = getYesterday();
4797
+ const tomorrow = getTomorrow();
4798
+ const datePickerContent = (jsxs(Grid, { gap: 2, children: [showHelperButtons && (jsxs(Grid, { templateColumns: "repeat(3, 1fr)", gap: 2, children: [jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(yesterday), disabled: !isDateValid(yesterday), children: labels.yesterdayLabel ?? 'Yesterday' }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(today), disabled: !isDateValid(today), children: labels.todayLabel ?? 'Today' }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleHelperButtonClick(tomorrow), disabled: !isDateValid(tomorrow), children: labels.tomorrowLabel ?? 'Tomorrow' })] })), jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay })] }));
4799
+ return (jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(InputGroup, { endElement: jsx(Popover.Trigger, { asChild: true, children: jsx(IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsx(Icon, { children: jsx(MdDateRange, {}) }) }) }), children: jsx(Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }));
4800
+ }
4603
4801
 
4604
4802
  dayjs.extend(utc);
4605
4803
  dayjs.extend(timezone);
4804
+ dayjs.extend(customParseFormat);
4606
4805
  const DatePicker = ({ column, schema, prefix }) => {
4607
4806
  const { watch, formState: { errors }, setValue, } = useFormContext();
4608
4807
  const { timezone, dateTimePickerLabels, insideDialog } = useSchemaContext();
@@ -4611,15 +4810,29 @@ const DatePicker = ({ column, schema, prefix }) => {
4611
4810
  const isRequired = required?.some((columnId) => columnId === column);
4612
4811
  const colLabel = formI18n.colLabel;
4613
4812
  const [open, setOpen] = useState(false);
4813
+ const [inputValue, setInputValue] = useState('');
4614
4814
  const selectedDate = watch(colLabel);
4615
- const displayDate = dayjs(selectedDate)
4616
- .tz(timezone)
4617
- .format(displayDateFormat);
4815
+ // Update input value when form value changes
4816
+ useEffect(() => {
4817
+ if (selectedDate) {
4818
+ const parsedDate = dayjs(selectedDate).tz(timezone);
4819
+ if (parsedDate.isValid()) {
4820
+ const formatted = parsedDate.format(displayDateFormat);
4821
+ setInputValue(formatted);
4822
+ }
4823
+ else {
4824
+ setInputValue('');
4825
+ }
4826
+ }
4827
+ else {
4828
+ setInputValue('');
4829
+ }
4830
+ }, [selectedDate, displayDateFormat, timezone]);
4831
+ // Format and validate existing value
4618
4832
  useEffect(() => {
4619
4833
  try {
4620
4834
  if (selectedDate) {
4621
4835
  // Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
4622
- // For example, parse as UTC:
4623
4836
  const parsedDate = dayjs(selectedDate).tz(timezone);
4624
4837
  if (!parsedDate.isValid())
4625
4838
  return;
@@ -4637,7 +4850,7 @@ const DatePicker = ({ column, schema, prefix }) => {
4637
4850
  catch (e) {
4638
4851
  console.error(e);
4639
4852
  }
4640
- }, [selectedDate, dateFormat, colLabel, setValue]);
4853
+ }, [selectedDate, dateFormat, colLabel, setValue, timezone]);
4641
4854
  const datePickerLabels = {
4642
4855
  monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
4643
4856
  'January',
@@ -4665,14 +4878,92 @@ const DatePicker = ({ column, schema, prefix }) => {
4665
4878
  backButtonLabel: dateTimePickerLabels?.backButtonLabel ?? 'Back',
4666
4879
  forwardButtonLabel: dateTimePickerLabels?.forwardButtonLabel ?? 'Forward',
4667
4880
  };
4668
- const datePickerContent = (jsx(DatePicker$1, { selected: new Date(selectedDate), onDateSelected: ({ date }) => {
4669
- setValue(colLabel, dayjs(date).format(dateFormat));
4670
- setOpen(false);
4671
- }, labels: datePickerLabels }));
4881
+ // Convert value to Date object for DatePicker
4882
+ const selectedDateObj = selectedDate
4883
+ ? dayjs(selectedDate).tz(timezone).isValid()
4884
+ ? dayjs(selectedDate).tz(timezone).toDate()
4885
+ : new Date()
4886
+ : new Date();
4887
+ // Shared function to parse and validate input value
4888
+ const parseAndValidateInput = (inputVal) => {
4889
+ // If empty, clear the value
4890
+ if (!inputVal.trim()) {
4891
+ setValue(colLabel, undefined, {
4892
+ shouldValidate: true,
4893
+ shouldDirty: true,
4894
+ });
4895
+ setInputValue('');
4896
+ return;
4897
+ }
4898
+ // Try parsing with displayDateFormat first
4899
+ let parsedDate = dayjs(inputVal, displayDateFormat, true);
4900
+ // If that fails, try common date formats
4901
+ if (!parsedDate.isValid()) {
4902
+ parsedDate = dayjs(inputVal);
4903
+ }
4904
+ // If still invalid, try parsing with dateFormat
4905
+ if (!parsedDate.isValid()) {
4906
+ parsedDate = dayjs(inputVal, dateFormat, true);
4907
+ }
4908
+ // If valid, format and update
4909
+ if (parsedDate.isValid()) {
4910
+ const formattedDate = parsedDate.tz(timezone).format(dateFormat);
4911
+ const formattedDisplay = parsedDate
4912
+ .tz(timezone)
4913
+ .format(displayDateFormat);
4914
+ setValue(colLabel, formattedDate, {
4915
+ shouldValidate: true,
4916
+ shouldDirty: true,
4917
+ });
4918
+ setInputValue(formattedDisplay);
4919
+ }
4920
+ else {
4921
+ // Invalid date - reset to prop value
4922
+ resetToPropValue();
4923
+ }
4924
+ };
4925
+ // Helper function to reset input to prop value
4926
+ const resetToPropValue = () => {
4927
+ if (selectedDate) {
4928
+ const parsedDate = dayjs(selectedDate).tz(timezone);
4929
+ if (parsedDate.isValid()) {
4930
+ const formatted = parsedDate.format(displayDateFormat);
4931
+ setInputValue(formatted);
4932
+ }
4933
+ else {
4934
+ setInputValue('');
4935
+ }
4936
+ }
4937
+ else {
4938
+ setInputValue('');
4939
+ }
4940
+ };
4941
+ const handleInputChange = (e) => {
4942
+ // Only update the input value, don't parse yet
4943
+ setInputValue(e.target.value);
4944
+ };
4945
+ const handleInputBlur = () => {
4946
+ // Parse and validate when input loses focus
4947
+ parseAndValidateInput(inputValue);
4948
+ };
4949
+ const handleKeyDown = (e) => {
4950
+ // Parse and validate when Enter is pressed
4951
+ if (e.key === 'Enter') {
4952
+ e.preventDefault();
4953
+ parseAndValidateInput(inputValue);
4954
+ }
4955
+ };
4956
+ const handleDateSelected = ({ date }) => {
4957
+ const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
4958
+ setValue(colLabel, formattedDate, {
4959
+ shouldValidate: true,
4960
+ shouldDirty: true,
4961
+ });
4962
+ setOpen(false);
4963
+ };
4964
+ const datePickerContent = (jsx(DatePicker$1, { selected: selectedDateObj, onDateSelected: handleDateSelected, labels: datePickerLabels }));
4672
4965
  return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4673
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
4674
- setOpen(true);
4675
- }, justifyContent: 'start', children: [jsx(MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }) }));
4966
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(InputGroup, { endElement: jsx(Popover.Trigger, { asChild: true, children: jsx(IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsx(Icon, { children: jsx(MdDateRange, {}) }) }) }), children: jsx(Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: formI18n.label(), size: "sm" }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }) }));
4676
4967
  };
4677
4968
 
4678
4969
  dayjs.extend(utc);
@@ -4680,7 +4971,7 @@ dayjs.extend(timezone);
4680
4971
  const DateRangePicker = ({ column, schema, prefix, }) => {
4681
4972
  const { watch, formState: { errors }, setValue, } = useFormContext();
4682
4973
  const { timezone, insideDialog } = useSchemaContext();
4683
- const formI18n = useFormI18n(column, prefix);
4974
+ const formI18n = useFormI18n(column, prefix, schema);
4684
4975
  const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
4685
4976
  const isRequired = required?.some((columnId) => columnId === column);
4686
4977
  const colLabel = formI18n.colLabel;
@@ -4786,27 +5077,82 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
4786
5077
  const watchEnum = watch(colLabel);
4787
5078
  const watchEnums = (watch(colLabel) ?? []);
4788
5079
  const dataList = schema.enum ?? [];
5080
+ // Helper function to render enum value (returns ReactNode)
5081
+ // If renderDisplay is provided, use it; otherwise show the enum string value directly
5082
+ const renderEnumValue = (value) => {
5083
+ if (renderDisplay) {
5084
+ return renderDisplay(value);
5085
+ }
5086
+ // If no renderDisplay provided, show the enum string value directly
5087
+ return value;
5088
+ };
5089
+ // Helper function to get string representation for input display
5090
+ // Converts ReactNode to string for combobox input display
5091
+ const getDisplayString = (value) => {
5092
+ if (renderDisplay) {
5093
+ const rendered = renderDisplay(value);
5094
+ // If renderDisplay returns a string, use it directly
5095
+ if (typeof rendered === 'string') {
5096
+ return rendered;
5097
+ }
5098
+ // If it's a React element, try to extract text content
5099
+ // For now, fallback to the raw value if we can't extract text
5100
+ // In most cases, renderDisplay should return a string or simple element
5101
+ if (typeof rendered === 'object' &&
5102
+ rendered !== null &&
5103
+ 'props' in rendered) {
5104
+ const props = rendered.props;
5105
+ // Try to extract text from React element props
5106
+ if (props?.children) {
5107
+ const children = props.children;
5108
+ if (typeof children === 'string') {
5109
+ return children;
5110
+ }
5111
+ }
5112
+ }
5113
+ // Fallback: use raw value if we can't extract string
5114
+ return value;
5115
+ }
5116
+ return value;
5117
+ };
5118
+ // Debug log when renderDisplay is missing
5119
+ if (!renderDisplay) {
5120
+ 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.`, {
5121
+ fieldName: column,
5122
+ colLabel,
5123
+ prefix,
5124
+ enumValues: dataList,
5125
+ });
5126
+ }
4789
5127
  // Current value for combobox (array format)
4790
5128
  const currentValue = isMultiple
4791
5129
  ? watchEnums.filter((val) => val != null && val !== '')
4792
5130
  : watchEnum
4793
5131
  ? [watchEnum]
4794
5132
  : [];
4795
- // Transform enum data for combobox collection
5133
+ // Track input focus state for single selection
5134
+ const [isInputFocused, setIsInputFocused] = useState(false);
5135
+ // Get the selected value for single selection display
5136
+ const selectedSingleValue = !isMultiple && watchEnum ? watchEnum : null;
5137
+ const selectedSingleRendered = selectedSingleValue
5138
+ ? renderEnumValue(selectedSingleValue)
5139
+ : null;
5140
+ const isSelectedSingleValueString = typeof selectedSingleRendered === 'string';
4796
5141
  const comboboxItems = useMemo(() => {
4797
5142
  return dataList.map((item) => ({
4798
- label: !!renderDisplay === true
4799
- ? String(renderDisplay(item))
4800
- : formI18n.t(item),
5143
+ label: item, // Internal: used for search/filtering only
4801
5144
  value: item,
5145
+ raw: item, // Passed to renderEnumValue for UI rendering
5146
+ displayLabel: getDisplayString(item), // Used for input display when selected
4802
5147
  }));
4803
- }, [dataList, renderDisplay, formI18n]);
5148
+ }, [dataList, renderDisplay]);
4804
5149
  // Use filter hook for combobox
4805
5150
  const { contains } = useFilter({ sensitivity: 'base' });
4806
5151
  // Create collection for combobox
5152
+ // itemToString uses displayLabel to show rendered display in input when selected
4807
5153
  const { collection, filter } = useListCollection({
4808
5154
  initialItems: comboboxItems,
4809
- itemToString: (item) => item.label,
5155
+ itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
4810
5156
  itemToValue: (item) => item.value,
4811
5157
  filter: contains,
4812
5158
  });
@@ -4830,9 +5176,7 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
4830
5176
  setValue(colLabel, details.value);
4831
5177
  }
4832
5178
  }, children: jsx(HStack, { gap: "6", children: dataList.map((item) => {
4833
- return (jsxs(RadioGroup$1.Item, { value: item, children: [jsx(RadioGroup$1.ItemHiddenInput, {}), jsx(RadioGroup$1.ItemIndicator, {}), jsx(RadioGroup$1.ItemText, { children: !!renderDisplay === true
4834
- ? renderDisplay(item)
4835
- : formI18n.t(item) })] }, `${colLabel}-${item}`));
5179
+ return (jsxs(RadioGroup$1.Item, { value: item, children: [jsx(RadioGroup$1.ItemHiddenInput, {}), jsx(RadioGroup$1.ItemIndicator, {}), jsx(RadioGroup$1.ItemText, { children: renderEnumValue(item) })] }, `${colLabel}-${item}`));
4836
5180
  }) }) }) }));
4837
5181
  }
4838
5182
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
@@ -4843,16 +5187,31 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
4843
5187
  return (jsx(Tag, { size: "lg", closable: true, onClick: () => {
4844
5188
  const newValue = currentValue.filter((val) => val !== enumValue);
4845
5189
  setValue(colLabel, newValue);
4846
- }, children: !!renderDisplay === true
4847
- ? renderDisplay(enumValue)
4848
- : formI18n.t(enumValue) }, enumValue));
5190
+ }, children: renderEnumValue(enumValue) }, enumValue));
4849
5191
  }) })), jsxs(Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: insideDialog
4850
5192
  ? { strategy: 'fixed', hideWhenDetached: true }
4851
- : undefined, children: [jsxs(Combobox.Control, { children: [jsx(Combobox.Input, { placeholder: enumPickerLabels?.typeToSearch ?? formI18n.t('type_to_search') }), jsxs(Combobox.IndicatorGroup, { children: [!isMultiple && currentValue.length > 0 && (jsx(Combobox.ClearTrigger, { onClick: () => {
5193
+ : undefined, children: [jsxs(Combobox.Control, { position: "relative", children: [!isMultiple &&
5194
+ selectedSingleValue &&
5195
+ !isInputFocused &&
5196
+ !isSelectedSingleValueString &&
5197
+ selectedSingleRendered && (jsx(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 })), jsx(Combobox.Input, { placeholder: !isMultiple && selectedSingleValue && !isInputFocused
5198
+ ? undefined
5199
+ : enumPickerLabels?.typeToSearch ?? 'Type to search', onFocus: () => setIsInputFocused(true), onBlur: () => setIsInputFocused(false), style: {
5200
+ color: !isMultiple &&
5201
+ selectedSingleValue &&
5202
+ !isInputFocused &&
5203
+ !isSelectedSingleValueString
5204
+ ? 'transparent'
5205
+ : undefined,
5206
+ caretColor: !isMultiple &&
5207
+ selectedSingleValue &&
5208
+ !isInputFocused &&
5209
+ !isSelectedSingleValueString
5210
+ ? 'transparent'
5211
+ : undefined,
5212
+ } }), jsxs(Combobox.IndicatorGroup, { children: [!isMultiple && currentValue.length > 0 && (jsx(Combobox.ClearTrigger, { onClick: () => {
4852
5213
  setValue(colLabel, '');
4853
- } })), jsx(Combobox.Trigger, {})] })] }), insideDialog ? (jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [showTotalAndLimit && (jsx(Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? formI18n.t('total')}: ${collection.items.length}` })), collection.items.length === 0 ? (jsx(Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ??
4854
- formI18n.t('empty_search_result') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [showTotalAndLimit && (jsx(Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? formI18n.t('total')}: ${collection.items.length}` })), collection.items.length === 0 ? (jsx(Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ??
4855
- formI18n.t('empty_search_result') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) }) }))] })] }));
5214
+ } })), jsx(Combobox.Trigger, {})] })] }), insideDialog ? (jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [showTotalAndLimit && (jsx(Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? 'Total'}: ${collection.items.length}` })), collection.items.length === 0 ? (jsx(Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ?? 'No results found' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: renderEnumValue(item.raw) }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [showTotalAndLimit && (jsx(Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? 'Total'}: ${collection.items.length}` })), collection.items.length === 0 ? (jsx(Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ?? 'No results found' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: renderEnumValue(item.raw) }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) }) }))] })] }));
4856
5215
  };
4857
5216
 
4858
5217
  function isEnteringWindow(_ref) {
@@ -5325,7 +5684,7 @@ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, en
5325
5684
  }) })) }))] }));
5326
5685
  };
5327
5686
 
5328
- function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, colLabel, }) {
5687
+ function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, }) {
5329
5688
  const [selectedFile, setSelectedFile] = useState(undefined);
5330
5689
  const [activeTab, setActiveTab] = useState('browse');
5331
5690
  const [uploadingFiles, setUploadingFiles] = useState(new Set());
@@ -5409,7 +5768,7 @@ function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly =
5409
5768
  const FilePicker = ({ column, schema, prefix }) => {
5410
5769
  const { setValue, formState: { errors }, watch, } = useFormContext();
5411
5770
  const { filePickerLabels } = useSchemaContext();
5412
- const formI18n = useFormI18n(column, prefix);
5771
+ const formI18n = useFormI18n(column, prefix, schema);
5413
5772
  const { required, gridColumn = 'span 12', gridRow = 'span 1', type, } = schema;
5414
5773
  const isRequired = required?.some((columnId) => columnId === column);
5415
5774
  const isSingleSelect = type === 'string';
@@ -5467,7 +5826,7 @@ const FilePicker = ({ column, schema, prefix }) => {
5467
5826
  const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => cur.name === name));
5468
5827
  setValue(colLabel, [...currentFiles, ...newFiles]);
5469
5828
  }
5470
- }, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }) }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
5829
+ }, placeholder: filePickerLabels?.fileDropzone ?? 'Drop files here' }) }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
5471
5830
  const fileIdentifier = getFileIdentifier(file, index);
5472
5831
  const fileName = getFileName(file);
5473
5832
  const fileSize = getFileSize(file);
@@ -5485,7 +5844,7 @@ const FilePicker = ({ column, schema, prefix }) => {
5485
5844
  const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5486
5845
  const { setValue, formState: { errors }, watch, } = useFormContext();
5487
5846
  const { filePickerLabels } = useSchemaContext();
5488
- const formI18n = useFormI18n(column, prefix);
5847
+ const formI18n = useFormI18n(column, prefix, schema);
5489
5848
  const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
5490
5849
  const isRequired = required?.some((columnId) => columnId === column);
5491
5850
  const isSingleSelect = type === 'string';
@@ -5578,11 +5937,7 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5578
5937
  }
5579
5938
  };
5580
5939
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5581
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsx(VStack, { align: "stretch", gap: 2, children: jsx(Button$1, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
5582
- formI18n.t('browse_library') ??
5583
- 'Browse from Library' }) }), jsx(MediaBrowserDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
5584
- filePickerLabels?.dialogTitle ??
5585
- 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, onUploadFile: onUploadFile, enableUpload: enableUpload, labels: filePickerLabels, colLabel: colLabel }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
5940
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsx(VStack, { align: "stretch", gap: 2, children: jsx(Button$1, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ?? 'Browse from Library' }) }), jsx(MediaBrowserDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ?? formI18n.label() ?? 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, onUploadFile: onUploadFile, enableUpload: enableUpload, labels: filePickerLabels, colLabel: colLabel }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
5586
5941
  const file = fileMap.get(fileId);
5587
5942
  const isImage = file
5588
5943
  ? /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name)
@@ -5598,43 +5953,51 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5598
5953
  }) })] }));
5599
5954
  };
5600
5955
 
5601
- const getTableData = async ({ serverUrl, in_table, searching = "", where = [], limit = 10, offset = 0, }) => {
5602
- if (serverUrl === undefined || serverUrl.length == 0) {
5603
- throw new Error("The serverUrl is missing");
5604
- }
5605
- if (in_table === undefined || in_table.length == 0) {
5606
- throw new Error("The in_table is missing");
5607
- }
5608
- const options = {
5609
- method: "GET",
5610
- url: `${serverUrl}/api/g/${in_table}`,
5611
- params: {
5612
- searching,
5613
- where,
5614
- limit,
5615
- offset
5616
- },
5617
- };
5618
- try {
5619
- const { data } = await axios.request(options);
5620
- console.log(data);
5621
- return data;
5622
- }
5623
- catch (error) {
5624
- console.error(error);
5625
- throw error;
5626
- }
5627
- };
5628
-
5629
- // Default renderDisplay function that stringifies JSON
5956
+ // Default renderDisplay function that intelligently displays items
5957
+ // If item is an object, tries to find common display fields (name, title, label, etc.)
5958
+ // Otherwise falls back to JSON.stringify
5630
5959
  const defaultRenderDisplay = (item) => {
5960
+ // Check if item is an object (not null, not array, not primitive)
5961
+ if (item !== null &&
5962
+ typeof item === 'object' &&
5963
+ !Array.isArray(item) &&
5964
+ !(item instanceof Date)) {
5965
+ const obj = item;
5966
+ // Try common display fields in order of preference
5967
+ const displayFields = [
5968
+ 'name',
5969
+ 'title',
5970
+ 'label',
5971
+ 'displayName',
5972
+ 'display_name',
5973
+ 'text',
5974
+ 'value',
5975
+ ];
5976
+ for (const field of displayFields) {
5977
+ if (obj[field] !== undefined && obj[field] !== null) {
5978
+ const value = obj[field];
5979
+ // Return the value if it's a string or number, otherwise stringify it
5980
+ if (typeof value === 'string' || typeof value === 'number') {
5981
+ return String(value);
5982
+ }
5983
+ }
5984
+ }
5985
+ // If no display field found, fall back to JSON.stringify
5986
+ return JSON.stringify(item);
5987
+ }
5988
+ // For non-objects (primitives, arrays, dates), use JSON.stringify
5631
5989
  return JSON.stringify(item);
5632
5990
  };
5633
5991
 
5634
5992
  const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5635
5993
  const { watch, getValues, formState: { errors }, setValue, } = useFormContext();
5636
- const { serverUrl, idMap, setIdMap, idPickerLabels, insideDialog } = useSchemaContext();
5637
- const { renderDisplay, foreign_key } = schema;
5994
+ const { idMap, setIdMap, idPickerLabels, insideDialog } = useSchemaContext();
5995
+ const { renderDisplay, itemToValue: schemaItemToValue, loadInitialValues, foreign_key, variant, } = schema;
5996
+ // loadInitialValues should be provided in schema for id-picker fields
5997
+ // It's used to load the record of the id so the display is human-readable
5998
+ if (variant === 'id-picker' && !loadInitialValues) {
5999
+ 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.`);
6000
+ }
5638
6001
  const { table, column: column_ref, customQueryFn, } = foreign_key;
5639
6002
  const [searchText, setSearchText] = useState('');
5640
6003
  const [debouncedSearchText, setDebouncedSearchText] = useState('');
@@ -5679,94 +6042,58 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5679
6042
  const missingIdsKey = useMemo(() => {
5680
6043
  return JSON.stringify([...missingIds].sort());
5681
6044
  }, [missingIds]);
6045
+ // Include idMap state in query key to force refetch when idMap is reset (e.g., on remount from another page)
6046
+ // This ensures the query runs even if React Query has cached data for the same missing IDs
6047
+ const idMapStateKey = useMemo(() => {
6048
+ // Create a key based on whether the required IDs are in idMap
6049
+ const hasRequiredIds = currentValue.every((id) => idMap[id]);
6050
+ return hasRequiredIds ? 'complete' : 'incomplete';
6051
+ }, [currentValue, idMap]);
5682
6052
  // Query to fetch initial values that are missing from idMap
5683
6053
  // This query runs automatically when missingIds.length > 0 and updates idMap
5684
6054
  const initialValuesQuery = useQuery({
5685
- queryKey: [`idpicker-initial`, column, missingIdsKey],
6055
+ queryKey: [`idpicker-initial`, column, missingIdsKey, idMapStateKey],
5686
6056
  queryFn: async () => {
5687
6057
  if (missingIds.length === 0) {
5688
6058
  return { data: [], count: 0 };
5689
6059
  }
5690
- if (customQueryFn) {
5691
- const { data, idMap } = await customQueryFn({
5692
- searching: '',
5693
- limit: missingIds.length,
5694
- offset: 0,
5695
- where: [
5696
- {
5697
- id: column_ref,
5698
- value: missingIds.length === 1 ? missingIds[0] : missingIds,
5699
- },
5700
- ],
5701
- });
5702
- setIdMap((state) => {
5703
- return { ...state, ...idMap };
5704
- });
5705
- return data;
6060
+ // Use schema's loadInitialValues (required for id-picker)
6061
+ if (!loadInitialValues) {
6062
+ console.warn(`loadInitialValues is required in schema for IdPicker field '${column}'. Returning empty idMap.`);
6063
+ return { data: [], count: 0 };
5706
6064
  }
5707
- const data = await getTableData({
5708
- serverUrl,
5709
- searching: '',
5710
- in_table: table,
5711
- limit: missingIds.length,
5712
- offset: 0,
5713
- where: [
5714
- {
5715
- id: column_ref,
5716
- value: missingIds.length === 1 ? missingIds[0] : missingIds,
5717
- },
5718
- ],
6065
+ const result = await loadInitialValues({
6066
+ ids: missingIds,
6067
+ foreign_key: foreign_key,
6068
+ setIdMap,
5719
6069
  });
5720
- const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
5721
- return [
5722
- item[column_ref],
5723
- {
5724
- ...item,
5725
- },
5726
- ];
5727
- }));
5728
- setIdMap((state) => {
5729
- return { ...state, ...newMap };
5730
- });
5731
- return data;
6070
+ return result.data;
5732
6071
  },
5733
6072
  enabled: missingIds.length > 0, // Only fetch if there are missing IDs
5734
- staleTime: 300000,
6073
+ staleTime: 0, // Always consider data stale to refetch on remount
6074
+ refetchOnMount: true, // Always refetch when component remounts (e.g., from another page)
6075
+ refetchOnWindowFocus: false, // Don't refetch on window focus
5735
6076
  });
5736
6077
  const { isLoading: isLoadingInitialValues, isFetching: isFetchingInitialValues, } = initialValuesQuery;
5737
6078
  // Query for search results (async loading)
5738
6079
  const query = useQuery({
5739
6080
  queryKey: [`idpicker`, { column, searchText: debouncedSearchText, limit }],
5740
6081
  queryFn: async () => {
5741
- if (customQueryFn) {
5742
- const { data, idMap } = await customQueryFn({
5743
- searching: debouncedSearchText ?? '',
5744
- limit: limit,
5745
- offset: 0,
5746
- });
5747
- setIdMap((state) => {
5748
- return { ...state, ...idMap };
5749
- });
5750
- return data;
6082
+ // customQueryFn is required when serverUrl is not available
6083
+ if (!customQueryFn) {
6084
+ throw new Error(`customQueryFn is required in foreign_key for table ${table}. serverUrl has been removed.`);
5751
6085
  }
5752
- const data = await getTableData({
5753
- serverUrl,
6086
+ const { data, idMap } = await customQueryFn({
5754
6087
  searching: debouncedSearchText ?? '',
5755
- in_table: table,
5756
6088
  limit: limit,
5757
6089
  offset: 0,
5758
6090
  });
5759
- const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
5760
- return [
5761
- item[column_ref],
5762
- {
5763
- ...item,
5764
- },
5765
- ];
5766
- }));
5767
- setIdMap((state) => {
5768
- return { ...state, ...newMap };
5769
- });
6091
+ // Update idMap with returned values
6092
+ if (idMap && Object.keys(idMap).length > 0) {
6093
+ setIdMap((state) => {
6094
+ return { ...state, ...idMap };
6095
+ });
6096
+ }
5770
6097
  return data;
5771
6098
  },
5772
6099
  enabled: true, // Always enabled for combobox
@@ -5798,17 +6125,51 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5798
6125
  // Depend on idMapKey which only changes when items we care about change
5799
6126
  // eslint-disable-next-line react-hooks/exhaustive-deps
5800
6127
  }, [currentValueKey, idMapKey]);
6128
+ // Default itemToValue function: extract value from item using column_ref
6129
+ const defaultItemToValue = (item) => String(item[column_ref]);
6130
+ // Use schema's itemToValue if provided, otherwise use default
6131
+ const itemToValueFn = schemaItemToValue
6132
+ ? (item) => schemaItemToValue(item)
6133
+ : defaultItemToValue;
6134
+ // itemToString function: convert item to readable string using renderDisplay
6135
+ // This ensures items can always be displayed as readable strings in the combobox
6136
+ const renderFn = renderDisplay || defaultRenderDisplay;
6137
+ const itemToStringFn = (item) => {
6138
+ const rendered = renderFn(item);
6139
+ // If already a string or number, return it
6140
+ if (typeof rendered === 'string')
6141
+ return rendered;
6142
+ if (typeof rendered === 'number')
6143
+ return String(rendered);
6144
+ // For ReactNode, fall back to defaultRenderDisplay which converts to string
6145
+ return String(defaultRenderDisplay(item));
6146
+ };
5801
6147
  // Transform data for combobox collection
5802
6148
  // label is used for filtering/searching (must be a string)
6149
+ // displayLabel is used for input display when selected (string representation of rendered display)
5803
6150
  // raw item is stored for custom rendering
5804
6151
  // Also include items from idMap that match currentValue (for initial values display)
5805
6152
  const comboboxItems = useMemo(() => {
5806
6153
  const renderFn = renderDisplay || defaultRenderDisplay;
6154
+ // Helper to convert rendered display to string for displayLabel
6155
+ // For ReactNodes (non-string/number), we can't safely stringify due to circular refs
6156
+ // So we use the label (which is already a string) as fallback
6157
+ const getDisplayString = (rendered, fallbackLabel) => {
6158
+ if (typeof rendered === 'string')
6159
+ return rendered;
6160
+ if (typeof rendered === 'number')
6161
+ return String(rendered);
6162
+ // For ReactNode, use the fallback label (which is already a string representation)
6163
+ // The actual ReactNode will be rendered in the overlay, not in the input
6164
+ return fallbackLabel;
6165
+ };
5807
6166
  const itemsFromDataList = dataList.map((item) => {
5808
6167
  const rendered = renderFn(item);
6168
+ const label = typeof rendered === 'string' ? rendered : JSON.stringify(item); // Use string for filtering
5809
6169
  return {
5810
- label: typeof rendered === 'string' ? rendered : JSON.stringify(item), // Use string for filtering
5811
- value: String(item[column_ref]),
6170
+ label, // Use string for filtering
6171
+ displayLabel: getDisplayString(rendered, label), // String representation for input display
6172
+ value: itemToValueFn(item),
5812
6173
  raw: item,
5813
6174
  };
5814
6175
  });
@@ -5817,25 +6178,28 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5817
6178
  const itemsFromIdMap = idMapItems
5818
6179
  .map((item) => {
5819
6180
  // Check if this item is already in itemsFromDataList
5820
- const alreadyIncluded = itemsFromDataList.some((i) => i.value === String(item[column_ref]));
6181
+ const alreadyIncluded = itemsFromDataList.some((i) => i.value === itemToValueFn(item));
5821
6182
  if (alreadyIncluded)
5822
6183
  return null;
5823
6184
  const rendered = renderFn(item);
6185
+ const label = typeof rendered === 'string' ? rendered : JSON.stringify(item);
5824
6186
  return {
5825
- label: typeof rendered === 'string' ? rendered : JSON.stringify(item),
5826
- value: String(item[column_ref]),
6187
+ label,
6188
+ displayLabel: getDisplayString(rendered, label), // String representation for input display
6189
+ value: itemToValueFn(item),
5827
6190
  raw: item,
5828
6191
  };
5829
6192
  })
5830
6193
  .filter((item) => item !== null);
5831
6194
  return [...itemsFromIdMap, ...itemsFromDataList];
5832
- }, [dataList, column_ref, renderDisplay, idMapItems]);
6195
+ }, [dataList, column_ref, renderDisplay, idMapItems, itemToValueFn]);
5833
6196
  // Use filter hook for combobox
5834
6197
  const { contains } = useFilter({ sensitivity: 'base' });
5835
6198
  // Create collection for combobox
6199
+ // itemToString uses displayLabel to show rendered display in input when selected
5836
6200
  const { collection, filter, set } = useListCollection({
5837
6201
  initialItems: comboboxItems,
5838
- itemToString: (item) => item.label,
6202
+ itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
5839
6203
  itemToValue: (item) => item.value,
5840
6204
  filter: contains,
5841
6205
  });
@@ -5890,6 +6254,10 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5890
6254
  idPickerLabels,
5891
6255
  insideDialog: insideDialog ?? false,
5892
6256
  renderDisplay,
6257
+ itemToValue: itemToValueFn,
6258
+ itemToString: itemToStringFn,
6259
+ loadInitialValues: loadInitialValues ??
6260
+ (async () => ({ data: { data: [], count: 0 }, idMap: {} })), // Fallback if not provided
5893
6261
  column_ref,
5894
6262
  errors,
5895
6263
  setValue,
@@ -5898,60 +6266,69 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5898
6266
 
5899
6267
  const IdPickerSingle = ({ column, schema, prefix, }) => {
5900
6268
  const formI18n = useFormI18n(column, prefix, schema);
5901
- const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, } = schema;
6269
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
5902
6270
  const isRequired = required?.some((columnId) => columnId === column);
5903
- const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, isLoadingInitialValues, isFetchingInitialValues, missingIds, collection, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, errors, setValue, } = useIdPickerData({
6271
+ const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, collection, filter, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, itemToValue, itemToString, errors, setValue, } = useIdPickerData({
5904
6272
  column,
5905
6273
  schema,
5906
6274
  prefix,
5907
6275
  isMultiple: false,
5908
6276
  });
5909
- const handleInputValueChange = (details) => {
5910
- setSearchText(details.inputValue);
5911
- };
5912
- const handleValueChange = (details) => {
5913
- setValue(colLabel, details.value[0] || '');
5914
- };
5915
- const renderDisplayFunction = renderDisplayFn || renderDisplay || defaultRenderDisplay;
5916
- return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5917
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [currentValue.length > 0 && (jsx(Flex, { mb: 2, children: (() => {
5918
- const id = currentValue[0];
5919
- const item = idMap[id];
5920
- // Show loading skeleton while fetching initial values
5921
- if (item === undefined &&
5922
- (isLoadingInitialValues || isFetchingInitialValues) &&
5923
- missingIds.includes(id)) {
5924
- return jsx(Skeleton, { height: "24px", width: "100px", borderRadius: "md" });
5925
- }
5926
- // Only show "not found" if we're not loading and item is still missing
5927
- if (item === undefined) {
5928
- return (jsx(Text, { fontSize: "sm", children: idPickerLabels?.undefined ?? 'Undefined' }));
5929
- }
5930
- return jsx(Text, { fontSize: "sm", children: renderDisplayFunction(item) });
5931
- })() })), jsxs(Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: false, closeOnSelect: true, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: insideDialog
5932
- ? { strategy: 'fixed', hideWhenDetached: true }
5933
- : undefined, children: [jsxs(Combobox.Control, { children: [jsx(Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? 'Type to search' }), jsxs(Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && jsx(Spinner, { size: "xs" }), isError && (jsx(Icon, { color: "fg.error", children: jsx(BiError, {}) })), currentValue.length > 0 && (jsx(Combobox.ClearTrigger, { onClick: () => {
5934
- setValue(colLabel, '');
5935
- } })), jsx(Combobox.Trigger, {})] })] }), insideDialog ? (jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6277
+ // Get the selected value for single selection display
6278
+ const selectedId = currentValue.length > 0 ? currentValue[0] : null;
6279
+ const selectedItem = selectedId
6280
+ ? idMap[selectedId]
6281
+ : undefined;
6282
+ // Use itemToValue to get the combobox value from the selected item, or use the ID directly
6283
+ const comboboxValue = selectedItem
6284
+ ? itemToString(selectedItem)
6285
+ : selectedId || '';
6286
+ // itemToString is available from the hook and can be used to get a readable string
6287
+ // representation of any item. The collection's itemToString is automatically used
6288
+ // by the combobox to display selected values.
6289
+ // Use useCombobox hook to control input value
6290
+ const combobox = useCombobox({
6291
+ collection,
6292
+ value: [comboboxValue],
6293
+ onInputValueChange(e) {
6294
+ setSearchText(e.inputValue);
6295
+ filter(e.inputValue);
6296
+ },
6297
+ onValueChange(e) {
6298
+ setValue(colLabel, e.value[0] || '');
6299
+ // Clear the input value after selection
6300
+ setSearchText('');
6301
+ },
6302
+ multiple: false,
6303
+ closeOnSelect: true,
6304
+ openOnClick: true,
6305
+ invalid: !!errors[colLabel],
6306
+ });
6307
+ // Use renderDisplay from hook (which comes from schema) or fallback to default
6308
+ const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
6309
+ // Get the selected value for single selection display (already computed above)
6310
+ const selectedRendered = selectedItem
6311
+ ? renderDisplayFunction(selectedItem)
6312
+ : null;
6313
+ return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6314
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Combobox.RootProvider, { value: combobox, width: "100%", children: [jsx(Show, { when: selectedId && selectedRendered, children: jsxs(HStack, { justifyContent: 'space-between', children: [jsx(Box, { children: selectedRendered }), currentValue.length > 0 && (jsx(Button$1, { variant: "ghost", size: "sm", onClick: () => {
6315
+ setValue(colLabel, '');
6316
+ }, children: jsx(Icon, { children: jsx(BiX, {}) }) }))] }) }), jsx(Show, { when: !selectedId || !selectedRendered, children: jsxs(Combobox.Control, { position: "relative", children: [jsx(Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? 'Type to search' }), jsxs(Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && jsx(Spinner, { size: "xs" }), isError && (jsx(Icon, { color: "fg.error", children: jsx(BiError, {}) })), jsx(Combobox.Trigger, {})] })] }) }), insideDialog ? (jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6317
+ // Show skeleton items to prevent UI shift
6318
+ jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
6319
+ ? idPickerLabels?.emptySearchResult ?? 'No results found'
6320
+ : idPickerLabels?.initialResults ??
6321
+ 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
5936
6322
  // Show skeleton items to prevent UI shift
5937
6323
  jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
5938
6324
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
5939
6325
  : idPickerLabels?.initialResults ??
5940
- 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplayFunction === true
5941
- ? renderDisplayFunction(item.raw)
5942
- : item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
5943
- // Show skeleton items to prevent UI shift
5944
- jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
5945
- ? idPickerLabels?.emptySearchResult ?? 'No results found'
5946
- : idPickerLabels?.initialResults ??
5947
- 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplayFunction === true
5948
- ? renderDisplayFunction(item.raw)
5949
- : item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
6326
+ 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsx(Combobox.Item, { item: item, children: renderDisplayFunction(item.raw) }, item.value ?? `item-${index}`))) })) }) }) }))] }) }));
5950
6327
  };
5951
6328
 
5952
6329
  const IdPickerMultiple = ({ column, schema, prefix, }) => {
5953
6330
  const formI18n = useFormI18n(column, prefix, schema);
5954
- const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, } = schema;
6331
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
5955
6332
  const isRequired = required?.some((columnId) => columnId === column);
5956
6333
  const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, isLoadingInitialValues, isFetchingInitialValues, missingIds, collection, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, errors, setValue, } = useIdPickerData({
5957
6334
  column,
@@ -5965,7 +6342,8 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
5965
6342
  const handleValueChange = (details) => {
5966
6343
  setValue(colLabel, details.value);
5967
6344
  };
5968
- const renderDisplayFunction = renderDisplayFn || renderDisplay || defaultRenderDisplay;
6345
+ // Use renderDisplay from hook (which comes from schema) or fallback to default
6346
+ const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
5969
6347
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5970
6348
  gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [currentValue.length > 0 && (jsx(Flex, { flexFlow: 'wrap', gap: 1, mb: 2, children: currentValue.map((id) => {
5971
6349
  const item = idMap[id];
@@ -5990,16 +6368,12 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
5990
6368
  jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
5991
6369
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
5992
6370
  : idPickerLabels?.initialResults ??
5993
- 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplayFunction === true
5994
- ? renderDisplayFunction(item.raw)
5995
- : item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6371
+ 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
5996
6372
  // Show skeleton items to prevent UI shift
5997
6373
  jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
5998
6374
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
5999
6375
  : idPickerLabels?.initialResults ??
6000
- 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplayFunction === true
6001
- ? renderDisplayFunction(item.raw)
6002
- : item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
6376
+ 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
6003
6377
  };
6004
6378
 
6005
6379
  const NumberInputRoot = React.forwardRef(function NumberInput$1(props, ref) {
@@ -6183,31 +6557,35 @@ RadioCard.ItemIndicator;
6183
6557
 
6184
6558
  const TagPicker = ({ column, schema, prefix }) => {
6185
6559
  const { watch, formState: { errors }, setValue, } = useFormContext();
6186
- const { serverUrl } = useSchemaContext();
6187
6560
  if (schema.properties == undefined) {
6188
- throw new Error("schema properties undefined when using DatePicker");
6561
+ throw new Error('schema properties undefined when using DatePicker');
6189
6562
  }
6190
- const { gridColumn, gridRow, in_table, object_id_column } = schema;
6563
+ const { gridColumn, gridRow, in_table, object_id_column, tagPicker } = schema;
6191
6564
  if (in_table === undefined) {
6192
- throw new Error("in_table is undefined when using TagPicker");
6565
+ throw new Error('in_table is undefined when using TagPicker');
6193
6566
  }
6194
6567
  if (object_id_column === undefined) {
6195
- throw new Error("object_id_column is undefined when using TagPicker");
6568
+ throw new Error('object_id_column is undefined when using TagPicker');
6569
+ }
6570
+ if (!tagPicker?.queryFn) {
6571
+ throw new Error('tagPicker.queryFn is required in schema. serverUrl has been removed.');
6196
6572
  }
6197
6573
  const query = useQuery({
6198
6574
  queryKey: [`tagpicker`, in_table],
6199
6575
  queryFn: async () => {
6200
- return await getTableData({
6201
- serverUrl,
6202
- in_table: "tables_tags_view",
6576
+ const result = await tagPicker.queryFn({
6577
+ in_table: 'tables_tags_view',
6203
6578
  where: [
6204
6579
  {
6205
- id: "table_name",
6580
+ id: 'table_name',
6206
6581
  value: [in_table],
6207
6582
  },
6208
6583
  ],
6209
6584
  limit: 100,
6585
+ offset: 0,
6586
+ searching: '',
6210
6587
  });
6588
+ return result.data || { data: [] };
6211
6589
  },
6212
6590
  staleTime: 10000,
6213
6591
  });
@@ -6215,17 +6593,19 @@ const TagPicker = ({ column, schema, prefix }) => {
6215
6593
  const existingTagsQuery = useQuery({
6216
6594
  queryKey: [`existing`, { in_table, object_id_column }, object_id],
6217
6595
  queryFn: async () => {
6218
- return await getTableData({
6219
- serverUrl,
6596
+ const result = await tagPicker.queryFn({
6220
6597
  in_table: in_table,
6221
6598
  where: [
6222
6599
  {
6223
6600
  id: object_id_column,
6224
- value: object_id[0],
6601
+ value: [object_id[0]],
6225
6602
  },
6226
6603
  ],
6227
6604
  limit: 100,
6605
+ offset: 0,
6606
+ searching: '',
6228
6607
  });
6608
+ return result.data || { data: [] };
6229
6609
  },
6230
6610
  enabled: object_id != undefined,
6231
6611
  staleTime: 10000,
@@ -6236,9 +6616,9 @@ const TagPicker = ({ column, schema, prefix }) => {
6236
6616
  if (!!object_id === false) {
6237
6617
  return jsx(Fragment, {});
6238
6618
  }
6239
- return (jsxs(Flex, { flexFlow: "column", gap: 4, gridColumn,
6619
+ return (jsxs(Flex, { flexFlow: 'column', gap: 4, gridColumn,
6240
6620
  gridRow, children: [isFetching && jsx(Fragment, { children: "isFetching" }), isLoading && jsx(Fragment, { children: "isLoading" }), isPending && jsx(Fragment, { children: "isPending" }), isError && jsx(Fragment, { children: "isError" }), dataList.map(({ parent_tag_name, all_tags, is_mutually_exclusive }) => {
6241
- return (jsxs(Flex, { flexFlow: "column", gap: 2, children: [jsx(Text, { children: parent_tag_name }), is_mutually_exclusive && (jsx(RadioCardRoot, { defaultValue: "next", variant: "surface", onValueChange: (tagIds) => {
6621
+ return (jsxs(Flex, { flexFlow: 'column', gap: 2, children: [jsx(Text, { children: parent_tag_name }), is_mutually_exclusive && (jsx(RadioCardRoot, { defaultValue: "next", variant: 'surface', onValueChange: (tagIds) => {
6242
6622
  const existedTags = Object.values(all_tags)
6243
6623
  .filter(({ id }) => {
6244
6624
  return existingTagList.some(({ tag_id }) => tag_id === id);
@@ -6250,20 +6630,20 @@ const TagPicker = ({ column, schema, prefix }) => {
6250
6630
  tagIds.value,
6251
6631
  ]);
6252
6632
  setValue(`${column}.${parent_tag_name}.old`, existedTags);
6253
- }, children: jsx(Flex, { flexFlow: "wrap", gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
6633
+ }, children: jsx(Flex, { flexFlow: 'wrap', gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
6254
6634
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
6255
- return (jsx(RadioCardItem, { label: tagName, value: id, flex: "0 0 0%", disabled: true }, `${tagName}-${id}`));
6635
+ return (jsx(RadioCardItem, { label: tagName, value: id, flex: '0 0 0%', disabled: true }, `${tagName}-${id}`));
6256
6636
  }
6257
- return (jsx(RadioCardItem, { label: tagName, value: id, flex: "0 0 0%", colorPalette: "blue" }, `${tagName}-${id}`));
6637
+ return (jsx(RadioCardItem, { label: tagName, value: id, flex: '0 0 0%', colorPalette: 'blue' }, `${tagName}-${id}`));
6258
6638
  }) }) })), !is_mutually_exclusive && (jsx(CheckboxGroup, { onValueChange: (tagIds) => {
6259
6639
  setValue(`${column}.${parent_tag_name}.current`, tagIds);
6260
- }, children: jsx(Flex, { flexFlow: "wrap", gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
6640
+ }, children: jsx(Flex, { flexFlow: 'wrap', gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
6261
6641
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
6262
- return (jsx(CheckboxCard, { label: tagName, value: id, flex: "0 0 0%", disabled: true, colorPalette: "blue" }, `${tagName}-${id}`));
6642
+ return (jsx(CheckboxCard, { label: tagName, value: id, flex: '0 0 0%', disabled: true, colorPalette: 'blue' }, `${tagName}-${id}`));
6263
6643
  }
6264
- return (jsx(CheckboxCard, { label: tagName, value: id, flex: "0 0 0%" }, `${tagName}-${id}`));
6644
+ return (jsx(CheckboxCard, { label: tagName, value: id, flex: '0 0 0%' }, `${tagName}-${id}`));
6265
6645
  }) }) }))] }, `tag-${parent_tag_name}`));
6266
- }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: (errors[`${column}`]?.message ?? "No error message") }))] }));
6646
+ }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: (errors[`${column}`]?.message ?? 'No error message') }))] }));
6267
6647
  };
6268
6648
 
6269
6649
  const Textarea = 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) => {
@@ -6370,11 +6750,193 @@ const TextAreaInput = ({ column, schema, prefix, }) => {
6370
6750
 
6371
6751
  dayjs.extend(utc);
6372
6752
  dayjs.extend(timezone);
6373
- const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem, onChange = () => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6374
- placeholder: 'hh:mm AM/PM',
6375
- emptyMessage: 'No time found',
6376
- }, }) => {
6377
- // Generate time options (every 15 minutes in 12-hour format)
6753
+ const TimePicker$1 = (props) => {
6754
+ const { format = '12h', value: controlledValue, onChange: controlledOnChange, hour: uncontrolledHour, setHour: uncontrolledSetHour, minute: uncontrolledMinute, setMinute: uncontrolledSetMinute, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6755
+ placeholder: format === '24h' ? 'HH:mm:ss' : 'hh:mm AM/PM',
6756
+ emptyMessage: 'No time found',
6757
+ }, onTimeChange, } = props;
6758
+ const is24Hour = format === '24h';
6759
+ const uncontrolledMeridiem = is24Hour ? undefined : props.meridiem;
6760
+ const uncontrolledSetMeridiem = is24Hour ? undefined : props.setMeridiem;
6761
+ const uncontrolledSecond = is24Hour ? props.second : undefined;
6762
+ const uncontrolledSetSecond = is24Hour ? props.setSecond : undefined;
6763
+ // Determine if we're in controlled mode
6764
+ const isControlled = controlledValue !== undefined;
6765
+ // Parse time string to extract hour, minute, second, meridiem
6766
+ const parseTimeString = (timeStr) => {
6767
+ if (!timeStr || !timeStr.trim()) {
6768
+ return { hour: null, minute: null, second: null, meridiem: null };
6769
+ }
6770
+ // Remove timezone suffix if present (e.g., "14:30:00Z" -> "14:30:00")
6771
+ const timeWithoutTz = timeStr.replace(/[Z+-]\d{2}:?\d{2}$/, '').trim();
6772
+ // Try parsing 24-hour format: "HH:mm:ss" or "HH:mm"
6773
+ const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
6774
+ const match24 = timeWithoutTz.match(time24Pattern);
6775
+ if (match24) {
6776
+ const hour24 = parseInt(match24[1], 10);
6777
+ const minute = parseInt(match24[2], 10);
6778
+ const second = match24[3] ? parseInt(match24[3], 10) : 0;
6779
+ if (hour24 >= 0 &&
6780
+ hour24 <= 23 &&
6781
+ minute >= 0 &&
6782
+ minute <= 59 &&
6783
+ second >= 0 &&
6784
+ second <= 59) {
6785
+ if (is24Hour) {
6786
+ return { hour: hour24, minute, second, meridiem: null };
6787
+ }
6788
+ else {
6789
+ // Convert to 12-hour format
6790
+ let hour12 = hour24;
6791
+ let meridiem;
6792
+ if (hour24 === 0) {
6793
+ hour12 = 12;
6794
+ meridiem = 'am';
6795
+ }
6796
+ else if (hour24 === 12) {
6797
+ hour12 = 12;
6798
+ meridiem = 'pm';
6799
+ }
6800
+ else if (hour24 > 12) {
6801
+ hour12 = hour24 - 12;
6802
+ meridiem = 'pm';
6803
+ }
6804
+ else {
6805
+ hour12 = hour24;
6806
+ meridiem = 'am';
6807
+ }
6808
+ return { hour: hour12, minute, second: null, meridiem };
6809
+ }
6810
+ }
6811
+ }
6812
+ // Try parsing 12-hour format: "hh:mm AM/PM" or "hh:mm:ss AM/PM"
6813
+ const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm|AM|PM)$/i;
6814
+ const match12 = timeWithoutTz.match(time12Pattern);
6815
+ if (match12 && !is24Hour) {
6816
+ const hour12 = parseInt(match12[1], 10);
6817
+ const minute = parseInt(match12[2], 10);
6818
+ const second = match12[3] ? parseInt(match12[3], 10) : null;
6819
+ const meridiem = match12[4].toLowerCase();
6820
+ if (hour12 >= 1 &&
6821
+ hour12 <= 12 &&
6822
+ minute >= 0 &&
6823
+ minute <= 59 &&
6824
+ (second === null || (second >= 0 && second <= 59))) {
6825
+ return { hour: hour12, minute, second, meridiem };
6826
+ }
6827
+ }
6828
+ return { hour: null, minute: null, second: null, meridiem: null };
6829
+ };
6830
+ // Format time values to time string
6831
+ const formatTimeString = (hour, minute, second, meridiem) => {
6832
+ if (hour === null || minute === null) {
6833
+ return undefined;
6834
+ }
6835
+ if (is24Hour) {
6836
+ const h = hour.toString().padStart(2, '0');
6837
+ const m = minute.toString().padStart(2, '0');
6838
+ const s = (second ?? 0).toString().padStart(2, '0');
6839
+ return `${h}:${m}:${s}`;
6840
+ }
6841
+ else {
6842
+ if (meridiem === null) {
6843
+ return undefined;
6844
+ }
6845
+ const h = hour.toString();
6846
+ const m = minute.toString().padStart(2, '0');
6847
+ return `${h}:${m} ${meridiem.toUpperCase()}`;
6848
+ }
6849
+ };
6850
+ // Internal state for controlled mode
6851
+ const [internalHour, setInternalHour] = useState(null);
6852
+ const [internalMinute, setInternalMinute] = useState(null);
6853
+ const [internalSecond, setInternalSecond] = useState(null);
6854
+ const [internalMeridiem, setInternalMeridiem] = useState(null);
6855
+ // Use controlled or uncontrolled values
6856
+ const hour = isControlled ? internalHour : uncontrolledHour ?? null;
6857
+ const minute = isControlled ? internalMinute : uncontrolledMinute ?? null;
6858
+ const second = isControlled ? internalSecond : uncontrolledSecond ?? null;
6859
+ const meridiem = isControlled
6860
+ ? internalMeridiem
6861
+ : uncontrolledMeridiem ?? null;
6862
+ // Setters that work for both modes
6863
+ const setHour = isControlled
6864
+ ? setInternalHour
6865
+ : uncontrolledSetHour || (() => { });
6866
+ const setMinute = isControlled
6867
+ ? setInternalMinute
6868
+ : uncontrolledSetMinute || (() => { });
6869
+ const setSecond = isControlled
6870
+ ? setInternalSecond
6871
+ : uncontrolledSetSecond || (() => { });
6872
+ const setMeridiem = isControlled
6873
+ ? setInternalMeridiem
6874
+ : uncontrolledSetMeridiem || (() => { });
6875
+ // Sync internal state with controlled value prop
6876
+ const prevValueRef = useRef(controlledValue);
6877
+ useEffect(() => {
6878
+ if (!isControlled)
6879
+ return;
6880
+ if (prevValueRef.current === controlledValue) {
6881
+ return;
6882
+ }
6883
+ prevValueRef.current = controlledValue;
6884
+ const parsed = parseTimeString(controlledValue);
6885
+ setInternalHour(parsed.hour);
6886
+ setInternalMinute(parsed.minute);
6887
+ if (is24Hour) {
6888
+ setInternalSecond(parsed.second);
6889
+ }
6890
+ else {
6891
+ setInternalMeridiem(parsed.meridiem);
6892
+ }
6893
+ }, [controlledValue, isControlled, is24Hour]);
6894
+ // Wrapper onChange that calls both controlled and uncontrolled onChange
6895
+ const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
6896
+ if (isControlled) {
6897
+ const timeString = formatTimeString(newHour, newMinute, newSecond, newMeridiem);
6898
+ controlledOnChange?.(timeString);
6899
+ }
6900
+ else {
6901
+ // Call legacy onTimeChange if provided
6902
+ if (onTimeChange) {
6903
+ if (is24Hour) {
6904
+ const timeChange24h = onTimeChange;
6905
+ timeChange24h({
6906
+ hour: newHour,
6907
+ minute: newMinute,
6908
+ second: newSecond,
6909
+ });
6910
+ }
6911
+ else {
6912
+ const timeChange12h = onTimeChange;
6913
+ timeChange12h({
6914
+ hour: newHour,
6915
+ minute: newMinute,
6916
+ meridiem: newMeridiem,
6917
+ });
6918
+ }
6919
+ }
6920
+ }
6921
+ };
6922
+ const [inputValue, setInputValue] = useState('');
6923
+ // Sync inputValue with current time
6924
+ useEffect(() => {
6925
+ if (is24Hour && second !== undefined) {
6926
+ if (hour !== null && minute !== null && second !== null) {
6927
+ const formatted = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
6928
+ setInputValue(formatted);
6929
+ }
6930
+ else {
6931
+ setInputValue('');
6932
+ }
6933
+ }
6934
+ else {
6935
+ // 12-hour format - input is managed by combobox
6936
+ setInputValue('');
6937
+ }
6938
+ }, [hour, minute, second, is24Hour]);
6939
+ // Generate time options based on format
6378
6940
  const timeOptions = useMemo(() => {
6379
6941
  const options = [];
6380
6942
  // Get start time for comparison if provided
@@ -6385,32 +6947,25 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6385
6947
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6386
6948
  if (startDateObj.isValid() && selectedDateObj.isValid()) {
6387
6949
  startDateTime = startDateObj;
6388
- // Only filter if dates are the same
6389
6950
  shouldFilterByDate =
6390
6951
  startDateObj.format('YYYY-MM-DD') ===
6391
6952
  selectedDateObj.format('YYYY-MM-DD');
6392
6953
  }
6393
6954
  }
6394
- // Generate 12-hour format options (1-12 for hours, AM/PM)
6395
- for (let h = 1; h <= 12; h++) {
6396
- for (let m = 0; m < 60; m += 15) {
6397
- for (const mer of ['am', 'pm']) {
6398
- // Convert 12-hour to 24-hour for comparison
6399
- let hour24 = h;
6400
- if (mer === 'am' && h === 12)
6401
- hour24 = 0;
6402
- else if (mer === 'pm' && h < 12)
6403
- hour24 = h + 12;
6404
- // Filter out times that would result in negative duration (only when dates are the same)
6955
+ if (is24Hour) {
6956
+ // Generate 24-hour format options (0-23 for hours)
6957
+ for (let h = 0; h < 24; h++) {
6958
+ for (let m = 0; m < 60; m += 15) {
6959
+ // Filter out times that would result in negative duration
6405
6960
  if (startDateTime && selectedDate && shouldFilterByDate) {
6406
6961
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6407
6962
  const optionDateTime = selectedDateObj
6408
- .hour(hour24)
6963
+ .hour(h)
6409
6964
  .minute(m)
6410
6965
  .second(0)
6411
6966
  .millisecond(0);
6412
6967
  if (optionDateTime.isBefore(startDateTime)) {
6413
- continue; // Skip this option as it would result in negative duration
6968
+ continue;
6414
6969
  }
6415
6970
  }
6416
6971
  // Calculate duration if startTime is provided
@@ -6418,7 +6973,7 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6418
6973
  if (startDateTime && selectedDate) {
6419
6974
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6420
6975
  const optionDateTime = selectedDateObj
6421
- .hour(hour24)
6976
+ .hour(h)
6422
6977
  .minute(m)
6423
6978
  .second(0)
6424
6979
  .millisecond(0);
@@ -6443,58 +6998,204 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6443
6998
  }
6444
6999
  }
6445
7000
  }
6446
- const hourDisplay = h.toString();
6447
- const minuteDisplay = m.toString().padStart(2, '0');
6448
- const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
7001
+ const timeDisplay = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`;
6449
7002
  options.push({
6450
7003
  label: timeDisplay,
6451
- value: `${h}:${m}:${mer}`,
7004
+ value: `${h}:${m}:0`,
6452
7005
  hour: h,
6453
7006
  minute: m,
6454
- meridiem: mer,
6455
- searchText: timeDisplay, // Use base time without duration for searching
7007
+ second: 0,
7008
+ searchText: timeDisplay,
6456
7009
  durationText,
6457
7010
  });
6458
7011
  }
6459
7012
  }
6460
7013
  }
7014
+ else {
7015
+ // Generate 12-hour format options (1-12 for hours, AM/PM)
7016
+ for (let h = 1; h <= 12; h++) {
7017
+ for (let m = 0; m < 60; m += 15) {
7018
+ for (const mer of ['am', 'pm']) {
7019
+ // Convert 12-hour to 24-hour for comparison
7020
+ let hour24 = h;
7021
+ if (mer === 'am' && h === 12)
7022
+ hour24 = 0;
7023
+ else if (mer === 'pm' && h < 12)
7024
+ hour24 = h + 12;
7025
+ // Filter out times that would result in negative duration
7026
+ if (startDateTime && selectedDate && shouldFilterByDate) {
7027
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
7028
+ const optionDateTime = selectedDateObj
7029
+ .hour(hour24)
7030
+ .minute(m)
7031
+ .second(0)
7032
+ .millisecond(0);
7033
+ if (optionDateTime.isBefore(startDateTime)) {
7034
+ continue;
7035
+ }
7036
+ }
7037
+ // Calculate duration if startTime is provided
7038
+ let durationText;
7039
+ if (startDateTime && selectedDate) {
7040
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
7041
+ const optionDateTime = selectedDateObj
7042
+ .hour(hour24)
7043
+ .minute(m)
7044
+ .second(0)
7045
+ .millisecond(0);
7046
+ if (optionDateTime.isValid() &&
7047
+ optionDateTime.isAfter(startDateTime)) {
7048
+ const diffMs = optionDateTime.diff(startDateTime);
7049
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
7050
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
7051
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
7052
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
7053
+ let diffText = '';
7054
+ if (diffHours > 0) {
7055
+ diffText = `${diffHours}h ${diffMinutes}m`;
7056
+ }
7057
+ else if (diffMinutes > 0) {
7058
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
7059
+ }
7060
+ else {
7061
+ diffText = `${diffSeconds}s`;
7062
+ }
7063
+ durationText = `+${diffText}`;
7064
+ }
7065
+ }
7066
+ }
7067
+ const hourDisplay = h.toString();
7068
+ const minuteDisplay = m.toString().padStart(2, '0');
7069
+ const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
7070
+ options.push({
7071
+ label: timeDisplay,
7072
+ value: `${h}:${m}:${mer}`,
7073
+ hour: h,
7074
+ minute: m,
7075
+ meridiem: mer,
7076
+ searchText: timeDisplay,
7077
+ durationText,
7078
+ });
7079
+ }
7080
+ }
7081
+ }
7082
+ // Sort 12-hour options by time (convert to 24-hour for proper chronological sorting)
7083
+ return options.sort((a, b) => {
7084
+ const a12 = a;
7085
+ const b12 = b;
7086
+ let hour24A = a12.hour;
7087
+ if (a12.meridiem === 'am' && a12.hour === 12)
7088
+ hour24A = 0;
7089
+ else if (a12.meridiem === 'pm' && a12.hour < 12)
7090
+ hour24A = a12.hour + 12;
7091
+ let hour24B = b12.hour;
7092
+ if (b12.meridiem === 'am' && b12.hour === 12)
7093
+ hour24B = 0;
7094
+ else if (b12.meridiem === 'pm' && b12.hour < 12)
7095
+ hour24B = b12.hour + 12;
7096
+ if (hour24A !== hour24B) {
7097
+ return hour24A - hour24B;
7098
+ }
7099
+ return a12.minute - b12.minute;
7100
+ });
7101
+ }
6461
7102
  return options;
6462
- }, [startTime, selectedDate, timezone]);
7103
+ }, [startTime, selectedDate, timezone, is24Hour]);
7104
+ // itemToString returns only the clean display text (no metadata)
7105
+ const itemToString = useMemo(() => {
7106
+ return (item) => {
7107
+ return item.searchText;
7108
+ };
7109
+ }, []);
7110
+ // Custom filter function
6463
7111
  const { contains } = useFilter({ sensitivity: 'base' });
7112
+ const customTimeFilter = useMemo(() => {
7113
+ if (is24Hour) {
7114
+ return contains; // Simple contains filter for 24-hour format
7115
+ }
7116
+ // For 12-hour format, support both 12-hour and 24-hour input
7117
+ return (itemText, filterText) => {
7118
+ if (!filterText) {
7119
+ return true;
7120
+ }
7121
+ const lowerItemText = itemText.toLowerCase();
7122
+ const lowerFilterText = filterText.toLowerCase();
7123
+ if (lowerItemText.includes(lowerFilterText)) {
7124
+ return true;
7125
+ }
7126
+ const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
7127
+ if (!item || !('meridiem' in item)) {
7128
+ return false;
7129
+ }
7130
+ // Convert item to 24-hour format for matching
7131
+ let hour24 = item.hour;
7132
+ if (item.meridiem === 'am' && item.hour === 12)
7133
+ hour24 = 0;
7134
+ else if (item.meridiem === 'pm' && item.hour < 12)
7135
+ hour24 = item.hour + 12;
7136
+ const hour24Str = hour24.toString().padStart(2, '0');
7137
+ const minuteStr = item.minute.toString().padStart(2, '0');
7138
+ const formats = [
7139
+ `${hour24Str}:${minuteStr}`,
7140
+ `${hour24Str}${minuteStr}`,
7141
+ hour24Str,
7142
+ `${hour24}:${minuteStr}`,
7143
+ hour24.toString(),
7144
+ ];
7145
+ return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
7146
+ lowerFilterText.includes(format.toLowerCase()));
7147
+ };
7148
+ }, [timeOptions, is24Hour, contains]);
6464
7149
  const { collection, filter } = useListCollection({
6465
7150
  initialItems: timeOptions,
6466
- itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
7151
+ itemToString: itemToString,
6467
7152
  itemToValue: (item) => item.value,
6468
- filter: contains,
7153
+ filter: customTimeFilter,
6469
7154
  });
6470
7155
  // Get current value string for combobox
6471
7156
  const currentValue = useMemo(() => {
6472
- if (hour === null || minute === null || meridiem === null) {
6473
- return '';
7157
+ if (is24Hour) {
7158
+ if (hour === null || minute === null || second === null) {
7159
+ return '';
7160
+ }
7161
+ return `${hour}:${minute}:${second}`;
7162
+ }
7163
+ else {
7164
+ if (hour === null || minute === null || meridiem === null) {
7165
+ return '';
7166
+ }
7167
+ return `${hour}:${minute}:${meridiem}`;
6474
7168
  }
6475
- return `${hour}:${minute}:${meridiem}`;
6476
- }, [hour, minute, meridiem]);
7169
+ }, [hour, minute, second, meridiem, is24Hour]);
6477
7170
  // Calculate duration difference
6478
7171
  const durationDiff = useMemo(() => {
6479
- if (!startTime ||
6480
- !selectedDate ||
6481
- hour === null ||
6482
- minute === null ||
6483
- meridiem === null) {
7172
+ if (!startTime || !selectedDate || hour === null || minute === null) {
6484
7173
  return null;
6485
7174
  }
7175
+ if (is24Hour) {
7176
+ if (second === null)
7177
+ return null;
7178
+ }
7179
+ else {
7180
+ if (meridiem === null)
7181
+ return null;
7182
+ }
6486
7183
  const startDateObj = dayjs(startTime).tz(timezone);
6487
7184
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6488
- // Convert 12-hour to 24-hour format
7185
+ // Convert to 24-hour format
6489
7186
  let hour24 = hour;
6490
- if (meridiem === 'am' && hour === 12)
6491
- hour24 = 0;
6492
- else if (meridiem === 'pm' && hour < 12)
6493
- hour24 = hour + 12;
7187
+ if (!is24Hour && meridiem) {
7188
+ if (meridiem === 'am' && hour === 12)
7189
+ hour24 = 0;
7190
+ else if (meridiem === 'pm' && hour < 12)
7191
+ hour24 = hour + 12;
7192
+ }
6494
7193
  const currentDateTime = selectedDateObj
6495
7194
  .hour(hour24)
6496
7195
  .minute(minute)
6497
- .second(0)
7196
+ .second(is24Hour && second !== null && second !== undefined
7197
+ ? second
7198
+ : 0)
6498
7199
  .millisecond(0);
6499
7200
  if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6500
7201
  return null;
@@ -6520,13 +7221,28 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6520
7221
  return `+${diffText}`;
6521
7222
  }
6522
7223
  return null;
6523
- }, [hour, minute, meridiem, startTime, selectedDate, timezone]);
7224
+ }, [
7225
+ hour,
7226
+ minute,
7227
+ second,
7228
+ meridiem,
7229
+ startTime,
7230
+ selectedDate,
7231
+ timezone,
7232
+ is24Hour,
7233
+ ]);
6524
7234
  const handleClear = () => {
6525
7235
  setHour(null);
6526
7236
  setMinute(null);
6527
- setMeridiem(null);
6528
- filter(''); // Reset filter to show all options
6529
- onChange({ hour: null, minute: null, meridiem: null });
7237
+ if (is24Hour && setSecond) {
7238
+ setSecond(null);
7239
+ handleTimeChange(null, null, null, null);
7240
+ }
7241
+ else if (!is24Hour && setMeridiem) {
7242
+ setMeridiem(null);
7243
+ handleTimeChange(null, null, null, null);
7244
+ }
7245
+ filter('');
6530
7246
  };
6531
7247
  const handleValueChange = (details) => {
6532
7248
  if (details.value.length === 0) {
@@ -6538,71 +7254,165 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6538
7254
  if (selectedOption) {
6539
7255
  setHour(selectedOption.hour);
6540
7256
  setMinute(selectedOption.minute);
6541
- setMeridiem(selectedOption.meridiem);
6542
- filter(''); // Reset filter after selection
6543
- onChange({
6544
- hour: selectedOption.hour,
6545
- minute: selectedOption.minute,
6546
- meridiem: selectedOption.meridiem,
6547
- });
7257
+ filter('');
7258
+ if (is24Hour) {
7259
+ const opt24 = selectedOption;
7260
+ if (setSecond)
7261
+ setSecond(opt24.second);
7262
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
7263
+ }
7264
+ else {
7265
+ const opt12 = selectedOption;
7266
+ if (setMeridiem)
7267
+ setMeridiem(opt12.meridiem);
7268
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
7269
+ }
6548
7270
  }
6549
7271
  };
6550
7272
  // Parse input value and update state
6551
7273
  const parseAndCommitInput = (value) => {
6552
7274
  const trimmedValue = value.trim();
6553
- // Filter the collection based on input
6554
7275
  filter(trimmedValue);
6555
7276
  if (!trimmedValue) {
6556
7277
  return;
6557
7278
  }
6558
- // Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
6559
- const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
6560
- const match12Hour = trimmedValue.match(timePattern12Hour);
6561
- if (match12Hour) {
6562
- const parsedHour = parseInt(match12Hour[1], 10);
6563
- const parsedMinute = parseInt(match12Hour[2], 10);
6564
- const parsedMeridiem = match12Hour[3].toLowerCase();
6565
- // Validate ranges
6566
- if (parsedHour >= 1 &&
6567
- parsedHour <= 12 &&
6568
- parsedMinute >= 0 &&
6569
- parsedMinute <= 59) {
6570
- setHour(parsedHour);
6571
- setMinute(parsedMinute);
6572
- setMeridiem(parsedMeridiem);
6573
- onChange({
6574
- hour: parsedHour,
6575
- minute: parsedMinute,
6576
- meridiem: parsedMeridiem,
6577
- });
6578
- return;
7279
+ if (is24Hour) {
7280
+ // Parse 24-hour format: "HH:mm:ss" or "HH:mm" or "HHmmss" or "HHmm"
7281
+ const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
7282
+ const match = trimmedValue.match(timePattern);
7283
+ if (match) {
7284
+ const parsedHour = parseInt(match[1], 10);
7285
+ const parsedMinute = parseInt(match[2], 10);
7286
+ const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
7287
+ if (parsedHour >= 0 &&
7288
+ parsedHour <= 23 &&
7289
+ parsedMinute >= 0 &&
7290
+ parsedMinute <= 59 &&
7291
+ parsedSecond >= 0 &&
7292
+ parsedSecond <= 59) {
7293
+ setHour(parsedHour);
7294
+ setMinute(parsedMinute);
7295
+ if (setSecond)
7296
+ setSecond(parsedSecond);
7297
+ handleTimeChange(parsedHour, parsedMinute, parsedSecond, null);
7298
+ return;
7299
+ }
7300
+ }
7301
+ // Try numbers only format: "123045" or "1230"
7302
+ const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
7303
+ if (numbersOnly.length >= 4) {
7304
+ const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
7305
+ const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
7306
+ const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 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
+ }
6579
7320
  }
6580
7321
  }
6581
- // Try to parse formats like "130pm" or "130 pm" (without colon)
6582
- const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
6583
- const matchNoColon = trimmedValue.match(timePatternNoColon);
6584
- if (matchNoColon) {
6585
- const numbersOnly = matchNoColon[1];
6586
- const parsedMeridiem = matchNoColon[2].toLowerCase();
6587
- if (numbersOnly.length >= 3) {
6588
- const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
6589
- const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
6590
- // Validate ranges
7322
+ else {
7323
+ // Parse 24-hour format first (e.g., "13:30", "14:00", "1330", "1400")
7324
+ const timePattern24Hour = /^(\d{1,2}):?(\d{2})$/;
7325
+ const match24Hour = trimmedValue.match(timePattern24Hour);
7326
+ if (match24Hour) {
7327
+ const parsedHour24 = parseInt(match24Hour[1], 10);
7328
+ const parsedMinute = parseInt(match24Hour[2], 10);
7329
+ if (parsedHour24 >= 0 &&
7330
+ parsedHour24 <= 23 &&
7331
+ parsedMinute >= 0 &&
7332
+ parsedMinute <= 59) {
7333
+ // Convert 24-hour to 12-hour format
7334
+ let hour12;
7335
+ let meridiem;
7336
+ if (parsedHour24 === 0) {
7337
+ hour12 = 12;
7338
+ meridiem = 'am';
7339
+ }
7340
+ else if (parsedHour24 === 12) {
7341
+ hour12 = 12;
7342
+ meridiem = 'pm';
7343
+ }
7344
+ else if (parsedHour24 > 12) {
7345
+ hour12 = parsedHour24 - 12;
7346
+ meridiem = 'pm';
7347
+ }
7348
+ else {
7349
+ hour12 = parsedHour24;
7350
+ meridiem = 'am';
7351
+ }
7352
+ setHour(hour12);
7353
+ setMinute(parsedMinute);
7354
+ if (setMeridiem)
7355
+ setMeridiem(meridiem);
7356
+ handleTimeChange(hour12, parsedMinute, null, meridiem);
7357
+ return;
7358
+ }
7359
+ }
7360
+ // Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
7361
+ const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
7362
+ const match12Hour = trimmedValue.match(timePattern12Hour);
7363
+ if (match12Hour) {
7364
+ const parsedHour = parseInt(match12Hour[1], 10);
7365
+ const parsedMinute = parseInt(match12Hour[2], 10);
7366
+ const parsedMeridiem = match12Hour[3].toLowerCase();
6591
7367
  if (parsedHour >= 1 &&
6592
7368
  parsedHour <= 12 &&
6593
7369
  parsedMinute >= 0 &&
6594
7370
  parsedMinute <= 59) {
6595
7371
  setHour(parsedHour);
6596
7372
  setMinute(parsedMinute);
6597
- setMeridiem(parsedMeridiem);
6598
- onChange({
6599
- hour: parsedHour,
6600
- minute: parsedMinute,
6601
- meridiem: parsedMeridiem,
6602
- });
7373
+ if (setMeridiem)
7374
+ setMeridiem(parsedMeridiem);
7375
+ handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
7376
+ return;
7377
+ }
7378
+ }
7379
+ // Parse formats like "12am" or "1pm" (hour only with meridiem, no minutes)
7380
+ const timePatternHourOnly = /^(\d{1,2})\s*(am|pm|AM|PM)$/i;
7381
+ const matchHourOnly = trimmedValue.match(timePatternHourOnly);
7382
+ if (matchHourOnly) {
7383
+ const parsedHour = parseInt(matchHourOnly[1], 10);
7384
+ const parsedMeridiem = matchHourOnly[2].toLowerCase();
7385
+ if (parsedHour >= 1 && parsedHour <= 12) {
7386
+ setHour(parsedHour);
7387
+ setMinute(0); // Default to 0 minutes when only hour is provided
7388
+ if (setMeridiem)
7389
+ setMeridiem(parsedMeridiem);
7390
+ handleTimeChange(parsedHour, 0, null, parsedMeridiem);
6603
7391
  return;
6604
7392
  }
6605
7393
  }
7394
+ // Try to parse formats like "130pm" or "130 pm" (without colon, with minutes)
7395
+ const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
7396
+ const matchNoColon = trimmedValue.match(timePatternNoColon);
7397
+ if (matchNoColon) {
7398
+ const numbersOnly = matchNoColon[1];
7399
+ const parsedMeridiem = matchNoColon[2].toLowerCase();
7400
+ if (numbersOnly.length >= 3) {
7401
+ const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
7402
+ const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
7403
+ if (parsedHour >= 1 &&
7404
+ parsedHour <= 12 &&
7405
+ parsedMinute >= 0 &&
7406
+ parsedMinute <= 59) {
7407
+ setHour(parsedHour);
7408
+ setMinute(parsedMinute);
7409
+ if (setMeridiem)
7410
+ setMeridiem(parsedMeridiem);
7411
+ handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
7412
+ return;
7413
+ }
7414
+ }
7415
+ }
6606
7416
  }
6607
7417
  // Parse failed, select first result
6608
7418
  selectFirstResult();
@@ -6613,58 +7423,87 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6613
7423
  const firstItem = collection.items[0];
6614
7424
  setHour(firstItem.hour);
6615
7425
  setMinute(firstItem.minute);
6616
- setMeridiem(firstItem.meridiem);
6617
- filter(''); // Reset filter after selection
6618
- onChange({
6619
- hour: firstItem.hour,
6620
- minute: firstItem.minute,
6621
- meridiem: firstItem.meridiem,
6622
- });
7426
+ filter('');
7427
+ if (is24Hour) {
7428
+ const opt24 = firstItem;
7429
+ if (setSecond)
7430
+ setSecond(opt24.second);
7431
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
7432
+ }
7433
+ else {
7434
+ const opt12 = firstItem;
7435
+ if (setMeridiem)
7436
+ setMeridiem(opt12.meridiem);
7437
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
7438
+ }
6623
7439
  }
6624
7440
  };
6625
7441
  const handleInputValueChange = (details) => {
6626
- // Filter the collection based on input, but don't parse yet
7442
+ if (is24Hour) {
7443
+ setInputValue(details.inputValue);
7444
+ }
6627
7445
  filter(details.inputValue);
6628
7446
  };
6629
7447
  const handleFocus = (e) => {
6630
- // Select all text when focusing
6631
7448
  e.target.select();
6632
7449
  };
6633
7450
  const handleBlur = (e) => {
6634
- // Parse and commit the input value when losing focus
6635
- const inputValue = e.target.value;
6636
- if (inputValue) {
6637
- parseAndCommitInput(inputValue);
7451
+ const inputVal = e.target.value;
7452
+ if (is24Hour) {
7453
+ setInputValue(inputVal);
7454
+ }
7455
+ if (inputVal) {
7456
+ parseAndCommitInput(inputVal);
6638
7457
  }
6639
7458
  };
6640
7459
  const handleKeyDown = (e) => {
6641
- // Commit input on Enter key
6642
7460
  if (e.key === 'Enter') {
6643
7461
  e.preventDefault();
6644
- const inputValue = e.currentTarget.value;
6645
- if (inputValue) {
6646
- parseAndCommitInput(inputValue);
7462
+ const inputVal = e.currentTarget.value;
7463
+ if (is24Hour) {
7464
+ setInputValue(inputVal);
7465
+ }
7466
+ if (inputVal) {
7467
+ parseAndCommitInput(inputVal);
6647
7468
  }
6648
- // Blur the input
6649
7469
  e.currentTarget?.blur();
6650
7470
  }
6651
7471
  };
6652
- return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: labels?.placeholder ?? 'hh:mm AM/PM', onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: labels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }) }));
7472
+ return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { value: is24Hour ? inputValue : undefined, placeholder: labels?.placeholder ?? (is24Hour ? 'HH:mm:ss' : 'hh:mm AM/PM'), onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: labels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) }))] }) }));
6653
7473
  };
6654
7474
 
6655
7475
  dayjs.extend(timezone);
6656
7476
  const TimePicker = ({ column, schema, prefix }) => {
6657
7477
  const { watch, formState: { errors }, setValue, } = useFormContext();
6658
7478
  const { timezone, insideDialog, timePickerLabels } = useSchemaContext();
6659
- const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', } = schema;
7479
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', startTimeField, selectedDateField, } = schema;
6660
7480
  const isRequired = required?.some((columnId) => columnId === column);
6661
7481
  const colLabel = `${prefix}${column}`;
6662
7482
  const formI18n = useFormI18n(column, prefix, schema);
6663
7483
  const [open, setOpen] = useState(false);
6664
7484
  const value = watch(colLabel);
6665
- const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
6666
- ? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
6667
- : '';
7485
+ // Watch startTime and selectedDate fields for offset calculation
7486
+ const startTimeValue = startTimeField
7487
+ ? watch(`${prefix}${startTimeField}`)
7488
+ : undefined;
7489
+ const selectedDateValue = selectedDateField
7490
+ ? watch(`${prefix}${selectedDateField}`)
7491
+ : undefined;
7492
+ // Convert to ISO string format for startTime if it's a date-time string
7493
+ const startTime = startTimeValue
7494
+ ? dayjs(startTimeValue).tz(timezone).isValid()
7495
+ ? dayjs(startTimeValue).tz(timezone).toISOString()
7496
+ : undefined
7497
+ : undefined;
7498
+ // Convert selectedDate to YYYY-MM-DD format
7499
+ const selectedDate = selectedDateValue
7500
+ ? dayjs(selectedDateValue).tz(timezone).isValid()
7501
+ ? dayjs(selectedDateValue).tz(timezone).format('YYYY-MM-DD')
7502
+ : undefined
7503
+ : undefined;
7504
+ const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
7505
+ ? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
7506
+ : '';
6668
7507
  // Parse the initial time parts from the time string (HH:mm:ssZ)
6669
7508
  const parseTime = (time) => {
6670
7509
  if (!time)
@@ -6717,884 +7556,898 @@ const TimePicker = ({ column, schema, prefix }) => {
6717
7556
  return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6718
7557
  gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
6719
7558
  setOpen(true);
6720
- }, justifyContent: 'start', children: [jsx(IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsx(Popover.Body, { overflow: "visible", children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { children: jsx(Popover.Body, { children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) }) }))] }) }));
7559
+ }, justifyContent: 'start', children: [jsx(IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsx(Popover.Body, { overflow: "visible", children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: startTime, selectedDate: selectedDate, timezone: timezone, portalled: false, labels: timePickerLabels }) }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { children: jsx(Popover.Body, { children: 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 }) }) }) }) }))] }) }));
6721
7560
  };
6722
7561
 
6723
7562
  dayjs.extend(utc);
6724
7563
  dayjs.extend(timezone);
6725
7564
  dayjs.extend(customParseFormat);
6726
- function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
7565
+ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
6727
7566
  monthNamesShort: [
6728
- 'Jan',
6729
- 'Feb',
6730
- 'Mar',
6731
- 'Apr',
7567
+ 'January',
7568
+ 'February',
7569
+ 'March',
7570
+ 'April',
6732
7571
  'May',
6733
- 'Jun',
6734
- 'Jul',
6735
- 'Aug',
6736
- 'Sep',
6737
- 'Oct',
6738
- 'Nov',
6739
- 'Dec',
7572
+ 'June',
7573
+ 'July',
7574
+ 'August',
7575
+ 'September',
7576
+ 'October',
7577
+ 'November',
7578
+ 'December',
6740
7579
  ],
6741
7580
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
6742
7581
  backButtonLabel: 'Back',
6743
- forwardButtonLabel: 'Next',
6744
- }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, }) {
6745
- const [open, setOpen] = useState(false);
6746
- const [inputValue, setInputValue] = useState('');
6747
- // Update input value when prop value changes
6748
- useEffect(() => {
6749
- if (value) {
6750
- const formatted = typeof value === 'string'
6751
- ? dayjs(value).tz(timezone).isValid()
6752
- ? dayjs(value).tz(timezone).format(displayFormat)
6753
- : ''
6754
- : dayjs(value).tz(timezone).format(displayFormat);
6755
- setInputValue(formatted);
6756
- }
6757
- else {
6758
- setInputValue('');
7582
+ forwardButtonLabel: 'Forward',
7583
+ }, timePickerLabels, timezone: tz = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, showQuickActions = false, quickActionLabels = {
7584
+ yesterday: 'Yesterday',
7585
+ today: 'Today',
7586
+ tomorrow: 'Tomorrow',
7587
+ plus7Days: '+7 Days',
7588
+ }, showTimezoneSelector = false, timezoneOffset: controlledTimezoneOffset, onTimezoneOffsetChange, }) {
7589
+ const is24Hour = format === 'iso-date-time' || showSeconds;
7590
+ // Labels are used in calendarLabels useMemo
7591
+ // Parse value to get date and time
7592
+ const parsedValue = useMemo(() => {
7593
+ if (!value)
7594
+ return null;
7595
+ const dateObj = dayjs(value).tz(tz);
7596
+ if (!dateObj.isValid())
7597
+ return null;
7598
+ return dateObj;
7599
+ }, [value, tz]);
7600
+ // Initialize date state
7601
+ const [selectedDate, setSelectedDate] = useState(() => {
7602
+ if (parsedValue) {
7603
+ return parsedValue.toDate();
7604
+ }
7605
+ if (defaultDate) {
7606
+ const defaultDateObj = dayjs(defaultDate).tz(tz);
7607
+ return defaultDateObj.isValid() ? defaultDateObj.toDate() : new Date();
7608
+ }
7609
+ return new Date();
7610
+ });
7611
+ // Initialize time state
7612
+ const [hour, setHour] = useState(() => {
7613
+ if (parsedValue) {
7614
+ return parsedValue.hour();
6759
7615
  }
6760
- }, [value, displayFormat, timezone]);
6761
- // Convert value to Date object for DatePicker
6762
- const selectedDate = value
6763
- ? typeof value === 'string'
6764
- ? dayjs(value).tz(timezone).isValid()
6765
- ? dayjs(value).tz(timezone).toDate()
6766
- : new Date()
6767
- : value
6768
- : new Date();
6769
- // Shared function to parse and validate input value
6770
- const parseAndValidateInput = (inputVal) => {
6771
- // If empty, clear the value
6772
- if (!inputVal.trim()) {
6773
- onChange?.(undefined);
6774
- setInputValue('');
6775
- return;
7616
+ if (defaultTime?.hour !== null && defaultTime?.hour !== undefined) {
7617
+ return defaultTime.hour;
6776
7618
  }
6777
- // Try parsing with displayFormat first
6778
- let parsedDate = dayjs(inputVal, displayFormat, true);
6779
- // If that fails, try common date formats
6780
- if (!parsedDate.isValid()) {
6781
- parsedDate = dayjs(inputVal);
7619
+ return null;
7620
+ });
7621
+ const [minute, setMinute] = useState(() => {
7622
+ if (parsedValue) {
7623
+ return parsedValue.minute();
6782
7624
  }
6783
- // If still invalid, try parsing with dateFormat
6784
- if (!parsedDate.isValid()) {
6785
- parsedDate = dayjs(inputVal, dateFormat, true);
7625
+ if (defaultTime?.minute !== null && defaultTime?.minute !== undefined) {
7626
+ return defaultTime.minute;
6786
7627
  }
6787
- // If valid, check constraints and update
6788
- if (parsedDate.isValid()) {
6789
- const dateObj = parsedDate.tz(timezone).toDate();
6790
- // Check min/max constraints
6791
- if (minDate && dateObj < minDate) {
6792
- // Invalid: before minDate, reset to prop value
6793
- resetToPropValue();
6794
- return;
6795
- }
6796
- if (maxDate && dateObj > maxDate) {
6797
- // Invalid: after maxDate, reset to prop value
6798
- resetToPropValue();
6799
- return;
6800
- }
6801
- // Valid date - format and update
6802
- const formattedDate = parsedDate.tz(timezone).format(dateFormat);
6803
- const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
6804
- onChange?.(formattedDate);
6805
- setInputValue(formattedDisplay);
7628
+ return null;
7629
+ });
7630
+ const [second, setSecond] = useState(() => {
7631
+ if (parsedValue) {
7632
+ return parsedValue.second();
6806
7633
  }
6807
- else {
6808
- // Invalid date - reset to prop value
6809
- resetToPropValue();
7634
+ if (defaultTime?.second !== null && defaultTime?.second !== undefined) {
7635
+ return defaultTime.second;
6810
7636
  }
6811
- };
6812
- // Helper function to reset input to prop value
6813
- const resetToPropValue = () => {
6814
- if (value) {
6815
- const formatted = typeof value === 'string'
6816
- ? dayjs(value).tz(timezone).isValid()
6817
- ? dayjs(value).tz(timezone).format(displayFormat)
6818
- : ''
6819
- : dayjs(value).tz(timezone).format(displayFormat);
6820
- setInputValue(formatted);
7637
+ return showSeconds ? 0 : null;
7638
+ });
7639
+ const [meridiem, setMeridiem] = useState(() => {
7640
+ if (parsedValue) {
7641
+ const h = parsedValue.hour();
7642
+ return h < 12 ? 'am' : 'pm';
6821
7643
  }
6822
- else {
6823
- setInputValue('');
7644
+ if (defaultTime?.meridiem !== null && defaultTime?.meridiem !== undefined) {
7645
+ return defaultTime.meridiem;
6824
7646
  }
6825
- };
6826
- const handleInputChange = (e) => {
6827
- // Only update the input value, don't parse yet
6828
- setInputValue(e.target.value);
6829
- };
6830
- const handleInputBlur = () => {
6831
- // Parse and validate when input loses focus
6832
- parseAndValidateInput(inputValue);
6833
- };
6834
- const handleKeyDown = (e) => {
6835
- // Parse and validate when Enter is pressed
6836
- if (e.key === 'Enter') {
6837
- e.preventDefault();
6838
- parseAndValidateInput(inputValue);
7647
+ return is24Hour ? null : 'am';
7648
+ });
7649
+ // Popover state - separate for date, time, and timezone
7650
+ const [datePopoverOpen, setDatePopoverOpen] = useState(false);
7651
+ const [timePopoverOpen, setTimePopoverOpen] = useState(false);
7652
+ const [timezonePopoverOpen, setTimezonePopoverOpen] = useState(false);
7653
+ const [calendarPopoverOpen, setCalendarPopoverOpen] = useState(false);
7654
+ // Timezone offset state (controlled or uncontrolled)
7655
+ const [internalTimezoneOffset, setInternalTimezoneOffset] = useState(() => {
7656
+ if (controlledTimezoneOffset !== undefined) {
7657
+ return controlledTimezoneOffset;
7658
+ }
7659
+ if (parsedValue) {
7660
+ return parsedValue.format('Z');
7661
+ }
7662
+ // Default to +08:00
7663
+ return '+08:00';
7664
+ });
7665
+ // Use controlled prop if provided, otherwise use internal state
7666
+ const timezoneOffset = controlledTimezoneOffset ?? internalTimezoneOffset;
7667
+ // Update internal state when controlled prop changes
7668
+ useEffect(() => {
7669
+ if (controlledTimezoneOffset !== undefined) {
7670
+ setInternalTimezoneOffset(controlledTimezoneOffset);
6839
7671
  }
6840
- };
6841
- const handleDateSelected = ({ date }) => {
6842
- const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
6843
- onChange?.(formattedDate);
6844
- setOpen(false);
6845
- };
6846
- const datePickerContent = (jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay }));
6847
- return (jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(InputGroup, { endElement: jsx(Popover.Trigger, { asChild: true, children: jsx(IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsx(Icon, { children: jsx(MdDateRange, {}) }) }) }), children: jsx(Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }));
6848
- }
6849
-
6850
- dayjs.extend(utc);
6851
- dayjs.extend(timezone);
6852
- function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
6853
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
6854
- onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6855
- placeholder: 'HH:mm:ss',
6856
- emptyMessage: 'No time found',
6857
- }, }) {
6858
- // Generate time options (every 15 minutes, seconds always 0)
6859
- const timeOptions = useMemo(() => {
6860
- const options = [];
6861
- // Get start time for comparison if provided
6862
- let startDateTime = null;
6863
- let shouldFilterByDate = false;
6864
- if (startTime && selectedDate) {
6865
- const startDateObj = dayjs(startTime).tz(timezone);
6866
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6867
- if (startDateObj.isValid() && selectedDateObj.isValid()) {
6868
- startDateTime = startDateObj;
6869
- // Only filter if dates are the same
6870
- shouldFilterByDate =
6871
- startDateObj.format('YYYY-MM-DD') ===
6872
- selectedDateObj.format('YYYY-MM-DD');
7672
+ }, [controlledTimezoneOffset]);
7673
+ // Sync timezone offset when value changes (only if uncontrolled)
7674
+ useEffect(() => {
7675
+ if (controlledTimezoneOffset === undefined && parsedValue) {
7676
+ const offsetFromValue = parsedValue.format('Z');
7677
+ if (offsetFromValue !== timezoneOffset) {
7678
+ setInternalTimezoneOffset(offsetFromValue);
6873
7679
  }
6874
7680
  }
6875
- for (let h = 0; h < 24; h++) {
6876
- for (let m = 0; m < 60; m += 15) {
6877
- const timeDisplay = `${h.toString().padStart(2, '0')}:${m
6878
- .toString()
6879
- .padStart(2, '0')}:00`;
6880
- // Filter out times that would result in negative duration (only when dates are the same)
6881
- if (startDateTime && selectedDate && shouldFilterByDate) {
6882
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6883
- const optionDateTime = selectedDateObj
6884
- .hour(h)
6885
- .minute(m)
6886
- .second(0)
6887
- .millisecond(0);
6888
- if (optionDateTime.isBefore(startDateTime)) {
6889
- continue; // Skip this option as it would result in negative duration
6890
- }
6891
- }
6892
- // Calculate duration if startTime is provided
6893
- let durationText;
6894
- if (startDateTime && selectedDate) {
6895
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6896
- const optionDateTime = selectedDateObj
6897
- .hour(h)
6898
- .minute(m)
6899
- .second(0)
6900
- .millisecond(0);
6901
- if (optionDateTime.isValid() &&
6902
- optionDateTime.isAfter(startDateTime)) {
6903
- const diffMs = optionDateTime.diff(startDateTime);
6904
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6905
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6906
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6907
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6908
- let diffText = '';
6909
- if (diffHours > 0) {
6910
- diffText = `${diffHours}h ${diffMinutes}m`;
6911
- }
6912
- else if (diffMinutes > 0) {
6913
- diffText = `${diffMinutes}m ${diffSeconds}s`;
6914
- }
6915
- else {
6916
- diffText = `${diffSeconds}s`;
6917
- }
6918
- durationText = `+${diffText}`;
6919
- }
6920
- }
6921
- }
6922
- options.push({
6923
- label: timeDisplay,
6924
- value: `${h}:${m}:0`,
6925
- hour: h,
6926
- minute: m,
6927
- second: 0,
6928
- searchText: timeDisplay, // Use base time without duration for searching
6929
- durationText,
6930
- });
6931
- }
7681
+ }, [parsedValue, controlledTimezoneOffset, timezoneOffset]);
7682
+ // Sync timezone offset when value changes
7683
+ // Generate timezone offset options (UTC-12 to UTC+14)
7684
+ const timezoneOffsetOptions = useMemo(() => {
7685
+ const options = [];
7686
+ for (let offset = -12; offset <= 14; offset++) {
7687
+ const sign = offset >= 0 ? '+' : '-';
7688
+ const hours = Math.abs(offset).toString().padStart(2, '0');
7689
+ const value = `${sign}${hours}:00`;
7690
+ const label = `UTC${sign}${hours}:00`;
7691
+ options.push({ value, label });
6932
7692
  }
6933
7693
  return options;
6934
- }, [startTime, selectedDate, timezone]);
6935
- const { contains } = useFilter({ sensitivity: 'base' });
6936
- const { collection, filter } = useListCollection({
6937
- initialItems: timeOptions,
6938
- itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
7694
+ }, []);
7695
+ // Create collection for Select
7696
+ const { collection: timezoneCollection } = useListCollection({
7697
+ initialItems: timezoneOffsetOptions,
7698
+ itemToString: (item) => item.label,
6939
7699
  itemToValue: (item) => item.value,
6940
- filter: contains,
6941
7700
  });
6942
- // Get current value string for combobox
6943
- const currentValue = useMemo(() => {
6944
- if (hour === null || minute === null || second === null) {
6945
- return '';
6946
- }
6947
- return `${hour}:${minute}:${second}`;
6948
- }, [hour, minute, second]);
6949
- // Calculate duration difference
6950
- const durationDiff = useMemo(() => {
6951
- if (!startTime ||
6952
- !selectedDate ||
6953
- hour === null ||
6954
- minute === null ||
6955
- second === null) {
6956
- return null;
6957
- }
6958
- const startDateObj = dayjs(startTime).tz(timezone);
6959
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6960
- const currentDateTime = selectedDateObj
6961
- .hour(hour)
6962
- .minute(minute)
6963
- .second(second ?? 0)
6964
- .millisecond(0);
6965
- if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6966
- return null;
6967
- }
6968
- const diffMs = currentDateTime.diff(startDateObj);
6969
- if (diffMs < 0) {
6970
- return null;
7701
+ // Ensure timezoneOffset value is valid (exists in collection)
7702
+ const validTimezoneOffset = useMemo(() => {
7703
+ if (!timezoneOffset)
7704
+ return undefined;
7705
+ const exists = timezoneOffsetOptions.some((opt) => opt.value === timezoneOffset);
7706
+ return exists ? timezoneOffset : undefined;
7707
+ }, [timezoneOffset, timezoneOffsetOptions]);
7708
+ // Date input state
7709
+ const [dateInputValue, setDateInputValue] = useState('');
7710
+ // Sync date input value with selected date
7711
+ useEffect(() => {
7712
+ if (selectedDate) {
7713
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7714
+ setDateInputValue(formatted);
6971
7715
  }
6972
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6973
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6974
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6975
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6976
- let diffText = '';
6977
- if (diffHours > 0) {
6978
- diffText = `${diffHours}h ${diffMinutes}m`;
6979
- }
6980
- else if (diffMinutes > 0) {
6981
- diffText = `${diffMinutes}m ${diffSeconds}s`;
6982
- }
6983
- else {
6984
- diffText = `${diffSeconds}s`;
6985
- }
6986
- return `+${diffText}`;
7716
+ else {
7717
+ setDateInputValue('');
6987
7718
  }
6988
- return null;
6989
- }, [hour, minute, second, startTime, selectedDate, timezone]);
6990
- const handleClear = () => {
6991
- setHour(null);
6992
- setMinute(null);
6993
- setSecond(null);
6994
- filter(''); // Reset filter to show all options
6995
- onChange({ hour: null, minute: null, second: null });
6996
- };
6997
- const handleValueChange = (details) => {
6998
- if (details.value.length === 0) {
6999
- handleClear();
7719
+ }, [selectedDate, tz]);
7720
+ // Parse and validate date input
7721
+ const parseAndValidateDateInput = (inputVal) => {
7722
+ // If empty, clear the value
7723
+ if (!inputVal.trim()) {
7724
+ setSelectedDate(null);
7725
+ updateDateTime(null, hour, minute, second, meridiem);
7000
7726
  return;
7001
7727
  }
7002
- const selectedValue = details.value[0];
7003
- const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
7004
- if (selectedOption) {
7005
- setHour(selectedOption.hour);
7006
- setMinute(selectedOption.minute);
7007
- setSecond(selectedOption.second);
7008
- filter(''); // Reset filter after selection
7009
- onChange({
7010
- hour: selectedOption.hour,
7011
- minute: selectedOption.minute,
7012
- second: selectedOption.second,
7013
- });
7014
- }
7015
- };
7016
- // Parse input value and update state
7017
- const parseAndCommitInput = (value) => {
7018
- const trimmedValue = value.trim();
7019
- // Filter the collection based on input
7020
- filter(trimmedValue);
7021
- if (!trimmedValue) {
7022
- return;
7728
+ // Try parsing with common date formats
7729
+ let parsedDate = dayjs(inputVal, 'YYYY-MM-DD', true);
7730
+ // If that fails, try other common formats
7731
+ if (!parsedDate.isValid()) {
7732
+ parsedDate = dayjs(inputVal);
7023
7733
  }
7024
- // Parse HH:mm:ss or HH:mm format
7025
- const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
7026
- const match = trimmedValue.match(timePattern);
7027
- if (match) {
7028
- const parsedHour = parseInt(match[1], 10);
7029
- const parsedMinute = parseInt(match[2], 10);
7030
- const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
7031
- // Validate ranges
7032
- if (parsedHour >= 0 &&
7033
- parsedHour <= 23 &&
7034
- parsedMinute >= 0 &&
7035
- parsedMinute <= 59 &&
7036
- parsedSecond >= 0 &&
7037
- parsedSecond <= 59) {
7038
- setHour(parsedHour);
7039
- setMinute(parsedMinute);
7040
- setSecond(parsedSecond);
7041
- onChange({
7042
- hour: parsedHour,
7043
- minute: parsedMinute,
7044
- second: parsedSecond,
7045
- });
7734
+ // If valid, check constraints and update
7735
+ if (parsedDate.isValid()) {
7736
+ const dateObj = parsedDate.tz(tz).toDate();
7737
+ // Check min/max constraints
7738
+ if (minDate && dateObj < minDate) {
7739
+ // Invalid: before minDate, reset to current selected date
7740
+ if (selectedDate) {
7741
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7742
+ setDateInputValue(formatted);
7743
+ }
7744
+ else {
7745
+ setDateInputValue('');
7746
+ }
7046
7747
  return;
7047
7748
  }
7048
- }
7049
- else {
7050
- // Try to parse formats like "123045" (HHmmss) or "1230" (HHmm)
7051
- const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
7052
- if (numbersOnly.length >= 4) {
7053
- const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
7054
- const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
7055
- const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
7056
- // Validate ranges
7057
- if (parsedHour >= 0 &&
7058
- parsedHour <= 23 &&
7059
- parsedMinute >= 0 &&
7060
- parsedMinute <= 59 &&
7061
- parsedSecond >= 0 &&
7062
- parsedSecond <= 59) {
7063
- setHour(parsedHour);
7064
- setMinute(parsedMinute);
7065
- setSecond(parsedSecond);
7066
- onChange({
7067
- hour: parsedHour,
7068
- minute: parsedMinute,
7069
- second: parsedSecond,
7070
- });
7071
- return;
7749
+ if (maxDate && dateObj > maxDate) {
7750
+ // Invalid: after maxDate, reset to current selected date
7751
+ if (selectedDate) {
7752
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7753
+ setDateInputValue(formatted);
7754
+ }
7755
+ else {
7756
+ setDateInputValue('');
7072
7757
  }
7758
+ return;
7073
7759
  }
7760
+ // Valid date - update selected date
7761
+ setSelectedDate(dateObj);
7762
+ updateDateTime(dateObj, hour, minute, second, meridiem);
7763
+ // Format and update input value
7764
+ const formatted = parsedDate.tz(tz).format('YYYY-MM-DD');
7765
+ setDateInputValue(formatted);
7074
7766
  }
7075
- // Parse failed, select first result
7076
- selectFirstResult();
7077
- };
7078
- // Select first result from filtered collection
7079
- const selectFirstResult = () => {
7080
- if (collection.items.length > 0) {
7081
- const firstItem = collection.items[0];
7082
- setHour(firstItem.hour);
7083
- setMinute(firstItem.minute);
7084
- setSecond(firstItem.second);
7085
- filter(''); // Reset filter after selection
7086
- onChange({
7087
- hour: firstItem.hour,
7088
- minute: firstItem.minute,
7089
- second: firstItem.second,
7090
- });
7767
+ else {
7768
+ // Invalid date - reset to current selected date
7769
+ if (selectedDate) {
7770
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7771
+ setDateInputValue(formatted);
7772
+ }
7773
+ else {
7774
+ setDateInputValue('');
7775
+ }
7091
7776
  }
7092
7777
  };
7093
- const handleInputValueChange = (details) => {
7094
- // Filter the collection based on input, but don't parse yet
7095
- filter(details.inputValue);
7778
+ const handleDateInputChange = (e) => {
7779
+ setDateInputValue(e.target.value);
7096
7780
  };
7097
- const handleFocus = (e) => {
7098
- // Select all text when focusing
7099
- e.target.select();
7781
+ const handleDateInputBlur = () => {
7782
+ parseAndValidateDateInput(dateInputValue);
7100
7783
  };
7101
- const handleBlur = (e) => {
7102
- // Parse and commit the input value when losing focus
7103
- const inputValue = e.target.value;
7104
- if (inputValue) {
7105
- parseAndCommitInput(inputValue);
7106
- }
7107
- };
7108
- const handleKeyDown = (e) => {
7109
- // Commit input on Enter key
7784
+ const handleDateInputKeyDown = (e) => {
7110
7785
  if (e.key === 'Enter') {
7111
7786
  e.preventDefault();
7112
- const inputValue = e.currentTarget.value;
7113
- if (inputValue) {
7114
- parseAndCommitInput(inputValue);
7115
- }
7116
- // Blur the input
7117
- e.currentTarget?.blur();
7787
+ parseAndValidateDateInput(dateInputValue);
7118
7788
  }
7119
7789
  };
7120
- return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: labels.placeholder, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: labels.emptyMessage }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }) }));
7121
- }
7122
-
7123
- dayjs.extend(utc);
7124
- dayjs.extend(timezone);
7125
- function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
7126
- monthNamesShort: [
7127
- 'Jan',
7128
- 'Feb',
7129
- 'Mar',
7130
- 'Apr',
7131
- 'May',
7132
- 'Jun',
7133
- 'Jul',
7134
- 'Aug',
7135
- 'Sep',
7136
- 'Oct',
7137
- 'Nov',
7138
- 'Dec',
7139
- ],
7140
- weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
7141
- backButtonLabel: 'Back',
7142
- forwardButtonLabel: 'Next',
7143
- }, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, }) {
7144
- console.log('[DateTimePicker] Component initialized with props:', {
7145
- value,
7146
- format,
7147
- showSeconds,
7148
- timezone,
7149
- startTime,
7150
- minDate,
7151
- maxDate,
7152
- });
7153
- // Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
7154
- const getDateString = useCallback((val) => {
7155
- if (!val)
7156
- return '';
7157
- const dateObj = dayjs(val).tz(timezone);
7158
- return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
7159
- }, [timezone]);
7160
- const [selectedDate, setSelectedDate] = useState(getDateString(value));
7161
- // Helper to get time values from value prop with timezone
7162
- const getTimeFromValue = useCallback((val) => {
7163
- console.log('[DateTimePicker] getTimeFromValue called:', {
7164
- val,
7165
- timezone,
7166
- showSeconds,
7167
- });
7168
- if (!val) {
7169
- console.log('[DateTimePicker] No value provided, returning nulls');
7170
- return {
7171
- hour12: null,
7172
- minute: null,
7173
- meridiem: null,
7174
- hour24: null,
7175
- second: null,
7176
- };
7177
- }
7178
- const dateObj = dayjs(val).tz(timezone);
7179
- console.log('[DateTimePicker] Parsed date object:', {
7180
- original: val,
7181
- timezone,
7182
- isValid: dateObj.isValid(),
7183
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7184
- hour24: dateObj.hour(),
7185
- minute: dateObj.minute(),
7186
- second: dateObj.second(),
7187
- });
7188
- if (!dateObj.isValid()) {
7189
- console.log('[DateTimePicker] Invalid date object, returning nulls');
7190
- return {
7191
- hour12: null,
7192
- minute: null,
7193
- meridiem: null,
7194
- hour24: null,
7195
- second: null,
7196
- };
7197
- }
7198
- const hour24Value = dateObj.hour();
7199
- const hour12Value = hour24Value % 12 || 12;
7200
- const minuteValue = dateObj.minute();
7201
- const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
7202
- const secondValue = showSeconds ? dateObj.second() : null;
7203
- const result = {
7204
- hour12: hour12Value,
7205
- minute: minuteValue,
7206
- meridiem: meridiemValue,
7207
- hour24: hour24Value,
7208
- second: secondValue,
7209
- };
7210
- console.log('[DateTimePicker] Extracted time values:', result);
7211
- return result;
7212
- }, [timezone, showSeconds]);
7213
- const initialTime = getTimeFromValue(value);
7214
- console.log('[DateTimePicker] Initial time from value:', {
7215
- value,
7216
- initialTime,
7217
- });
7218
- // Time state for 12-hour format
7219
- const [hour12, setHour12] = useState(initialTime.hour12);
7220
- const [minute, setMinute] = useState(initialTime.minute);
7221
- const [meridiem, setMeridiem] = useState(initialTime.meridiem);
7222
- // Time state for 24-hour format
7223
- const [hour24, setHour24] = useState(initialTime.hour24);
7224
- const [second, setSecond] = useState(initialTime.second);
7225
- // Sync selectedDate and time states when value prop changes
7226
- useEffect(() => {
7227
- console.log('[DateTimePicker] useEffect triggered - value changed:', {
7228
- value,
7229
- timezone,
7230
- format,
7231
- });
7232
- // If value is null, undefined, or invalid, clear all fields
7233
- if (!value || value === null || value === undefined) {
7234
- console.log('[DateTimePicker] Value is null/undefined, clearing all fields');
7235
- setSelectedDate('');
7236
- setHour12(null);
7237
- setMinute(null);
7238
- setMeridiem(null);
7239
- setHour24(null);
7240
- setSecond(null);
7241
- return;
7242
- }
7243
- // Check if value is valid
7244
- const dateObj = dayjs(value).tz(timezone);
7245
- if (!dateObj.isValid()) {
7246
- console.log('[DateTimePicker] Invalid value, clearing all fields');
7247
- setSelectedDate('');
7248
- setHour12(null);
7249
- setMinute(null);
7250
- setMeridiem(null);
7251
- setHour24(null);
7252
- setSecond(null);
7253
- return;
7790
+ // Helper functions to get dates in the correct timezone
7791
+ const getToday = () => dayjs().tz(tz).startOf('day').toDate();
7792
+ const getYesterday = () => dayjs().tz(tz).subtract(1, 'day').startOf('day').toDate();
7793
+ const getTomorrow = () => dayjs().tz(tz).add(1, 'day').startOf('day').toDate();
7794
+ const getPlus7Days = () => dayjs().tz(tz).add(7, 'day').startOf('day').toDate();
7795
+ // Check if a date is within min/max constraints
7796
+ const isDateValid = (date) => {
7797
+ if (minDate) {
7798
+ const minDateStart = dayjs(minDate).tz(tz).startOf('day').toDate();
7799
+ const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
7800
+ if (dateStart < minDateStart)
7801
+ return false;
7254
7802
  }
7255
- const dateString = getDateString(value);
7256
- console.log('[DateTimePicker] Setting selectedDate:', dateString);
7257
- setSelectedDate(dateString);
7258
- const timeData = getTimeFromValue(value);
7259
- console.log('[DateTimePicker] Updating time states:', {
7260
- timeData,
7261
- });
7262
- setHour12(timeData.hour12);
7263
- setMinute(timeData.minute);
7264
- setMeridiem(timeData.meridiem);
7265
- setHour24(timeData.hour24);
7266
- setSecond(timeData.second);
7267
- }, [value, getTimeFromValue, getDateString, timezone]);
7268
- const handleDateChange = (date) => {
7269
- console.log('[DateTimePicker] handleDateChange called:', {
7270
- date,
7271
- timezone,
7272
- showSeconds,
7273
- currentTimeStates: { hour12, minute, meridiem, hour24, second },
7274
- });
7275
- // If date is empty or invalid, clear all fields
7276
- if (!date || date === '') {
7277
- console.log('[DateTimePicker] Empty date, clearing all fields');
7278
- setSelectedDate('');
7279
- setHour12(null);
7280
- setMinute(null);
7281
- setMeridiem(null);
7282
- setHour24(null);
7283
- setSecond(null);
7284
- onChange?.(undefined);
7285
- return;
7803
+ if (maxDate) {
7804
+ const maxDateStart = dayjs(maxDate).tz(tz).startOf('day').toDate();
7805
+ const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
7806
+ if (dateStart > maxDateStart)
7807
+ return false;
7286
7808
  }
7287
- setSelectedDate(date);
7288
- // Parse the date string (YYYY-MM-DD) in the specified timezone
7289
- const dateObj = dayjs.tz(date, timezone);
7290
- console.log('[DateTimePicker] Parsed date object:', {
7291
- date,
7292
- timezone,
7293
- isValid: dateObj.isValid(),
7294
- isoString: dateObj.toISOString(),
7295
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7296
- });
7297
- if (!dateObj.isValid()) {
7298
- console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
7299
- setSelectedDate('');
7300
- setHour12(null);
7301
- setMinute(null);
7302
- setMeridiem(null);
7303
- setHour24(null);
7304
- setSecond(null);
7305
- onChange?.(undefined);
7306
- return;
7809
+ return true;
7810
+ };
7811
+ // Handle quick action button clicks
7812
+ const handleQuickActionClick = (date) => {
7813
+ if (isDateValid(date)) {
7814
+ setSelectedDate(date);
7815
+ updateDateTime(date, hour, minute, second, meridiem);
7816
+ // Close the calendar popover if open
7817
+ setCalendarPopoverOpen(false);
7307
7818
  }
7308
- // When showSeconds is false, ignore seconds from the date
7309
- if (!showSeconds) {
7310
- const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
7311
- console.log('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
7312
- updateDateTime(dateWithoutSeconds);
7819
+ };
7820
+ // Display text for buttons
7821
+ const dateDisplayText = useMemo(() => {
7822
+ if (!selectedDate)
7823
+ return 'Select date';
7824
+ return dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7825
+ }, [selectedDate, tz]);
7826
+ const timeDisplayText = useMemo(() => {
7827
+ if (hour === null || minute === null)
7828
+ return 'Select time';
7829
+ if (is24Hour) {
7830
+ // 24-hour format: never show meridiem, always use 24-hour format (0-23)
7831
+ const hour24 = hour >= 0 && hour <= 23 ? hour : hour % 24;
7832
+ const s = second ?? 0;
7833
+ if (showSeconds) {
7834
+ return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
7835
+ }
7836
+ return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
7313
7837
  }
7314
7838
  else {
7315
- const dateWithSeconds = dateObj.toISOString();
7316
- console.log('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
7317
- updateDateTime(dateWithSeconds);
7839
+ // 12-hour format: always show meridiem (AM/PM)
7840
+ const hour12 = hour >= 1 && hour <= 12 ? hour : hour % 12;
7841
+ if (meridiem === null)
7842
+ return 'Select time';
7843
+ const hourDisplay = hour12.toString();
7844
+ const minuteDisplay = minute.toString().padStart(2, '0');
7845
+ return `${hourDisplay}:${minuteDisplay} ${meridiem.toUpperCase()}`;
7846
+ }
7847
+ }, [hour, minute, second, meridiem, is24Hour, showSeconds]);
7848
+ const timezoneDisplayText = useMemo(() => {
7849
+ if (!showTimezoneSelector)
7850
+ return '';
7851
+ // Show offset as is (e.g., "+08:00")
7852
+ return timezoneOffset;
7853
+ }, [timezoneOffset, showTimezoneSelector]);
7854
+ // Update selectedDate when value changes externally
7855
+ useEffect(() => {
7856
+ if (parsedValue) {
7857
+ setSelectedDate(parsedValue.toDate());
7858
+ setHour(parsedValue.hour());
7859
+ setMinute(parsedValue.minute());
7860
+ setSecond(parsedValue.second());
7861
+ if (!is24Hour) {
7862
+ const h = parsedValue.hour();
7863
+ setMeridiem(h < 12 ? 'am' : 'pm');
7864
+ }
7318
7865
  }
7319
- };
7320
- const handleTimeChange = (timeData) => {
7321
- console.log('[DateTimePicker] handleTimeChange called:', {
7322
- timeData,
7323
- format,
7324
- selectedDate,
7325
- timezone,
7326
- });
7327
- if (format === 'iso-date-time') {
7328
- const data = timeData;
7329
- console.log('[DateTimePicker] ISO format - setting 24-hour time:', data);
7330
- setHour24(data.hour);
7331
- setMinute(data.minute);
7332
- if (showSeconds) {
7333
- setSecond(data.second ?? null);
7866
+ }, [parsedValue, is24Hour]);
7867
+ // Combine date and time and call onChange
7868
+ const updateDateTime = (newDate, newHour, newMinute, newSecond, newMeridiem, timezoneOffsetOverride) => {
7869
+ if (!newDate || newHour === null || newMinute === null) {
7870
+ onChange?.(undefined);
7871
+ return;
7872
+ }
7873
+ // Convert 12-hour to 24-hour if needed
7874
+ let hour24 = newHour;
7875
+ if (!is24Hour && newMeridiem) {
7876
+ // In 12-hour format, hour should be 1-12
7877
+ // If hour is > 12, it might already be in 24-hour format, convert it first
7878
+ let hour12 = newHour;
7879
+ if (newHour > 12) {
7880
+ // Hour is in 24-hour format, convert to 12-hour first
7881
+ if (newHour === 12) {
7882
+ hour12 = 12;
7883
+ }
7884
+ else {
7885
+ hour12 = newHour - 12;
7886
+ }
7887
+ }
7888
+ // Now convert 12-hour to 24-hour format (0-23)
7889
+ if (newMeridiem === 'am') {
7890
+ if (hour12 === 12) {
7891
+ hour24 = 0; // 12 AM = 0:00
7892
+ }
7893
+ else {
7894
+ hour24 = hour12; // 1-11 AM = 1-11
7895
+ }
7334
7896
  }
7335
7897
  else {
7336
- // Ignore seconds - always set to null when showSeconds is false
7337
- setSecond(null);
7898
+ // PM
7899
+ if (hour12 === 12) {
7900
+ hour24 = 12; // 12 PM = 12:00
7901
+ }
7902
+ else {
7903
+ hour24 = hour12 + 12; // 1-11 PM = 13-23
7904
+ }
7338
7905
  }
7339
7906
  }
7340
- else {
7341
- const data = timeData;
7342
- console.log('[DateTimePicker] 12-hour format - setting time:', data);
7343
- setHour12(data.hour);
7344
- setMinute(data.minute);
7345
- setMeridiem(data.meridiem);
7346
- }
7347
- // Use selectedDate if valid, otherwise clear all fields
7348
- if (!selectedDate || !dayjs(selectedDate).isValid()) {
7349
- console.log('[DateTimePicker] No valid selectedDate, clearing all fields');
7350
- setSelectedDate('');
7351
- setHour12(null);
7352
- setMinute(null);
7353
- setMeridiem(null);
7354
- setHour24(null);
7355
- setSecond(null);
7907
+ else if (!is24Hour && !newMeridiem) {
7908
+ // If in 12-hour mode but no meridiem, assume the hour is already in 12-hour format
7909
+ // and default to AM (or keep as is if it's a valid 12-hour value)
7910
+ // This shouldn't happen in normal flow, but handle it gracefully
7911
+ hour24 = newHour;
7912
+ }
7913
+ // If timezone selector is enabled, create date-time without timezone conversion
7914
+ // to ensure the selected timestamp matches the picker values exactly
7915
+ if (showTimezoneSelector) {
7916
+ // Use override if provided, otherwise use state value
7917
+ const offsetToUse = timezoneOffsetOverride ?? timezoneOffset;
7918
+ // Create date-time from the Date object without timezone conversion
7919
+ // Extract year, month, day from the date
7920
+ const year = newDate.getFullYear();
7921
+ const month = newDate.getMonth();
7922
+ const day = newDate.getDate();
7923
+ // Create a date-time string with the exact values from the picker
7924
+ 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')}`;
7925
+ onChange?.(`${formattedDateTime}${offsetToUse}`);
7926
+ return;
7927
+ }
7928
+ // Normal mode: use timezone conversion
7929
+ let dateTime = dayjs(newDate)
7930
+ .tz(tz)
7931
+ .hour(hour24)
7932
+ .minute(newMinute)
7933
+ .second(newSecond ?? 0)
7934
+ .millisecond(0);
7935
+ if (!dateTime.isValid()) {
7356
7936
  onChange?.(undefined);
7357
7937
  return;
7358
7938
  }
7359
- const dateObj = dayjs(selectedDate).tz(timezone);
7360
- if (dateObj.isValid()) {
7361
- updateDateTime(dateObj.toISOString(), timeData);
7939
+ // Format based on format prop
7940
+ if (format === 'iso-date-time') {
7941
+ onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ss'));
7362
7942
  }
7363
7943
  else {
7364
- console.warn('[DateTimePicker] Invalid date object in handleTimeChange, clearing fields');
7365
- setSelectedDate('');
7366
- setHour12(null);
7367
- setMinute(null);
7368
- setMeridiem(null);
7369
- setHour24(null);
7370
- setSecond(null);
7371
- onChange?.(undefined);
7944
+ // date-time format with timezone
7945
+ onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ssZ'));
7372
7946
  }
7373
7947
  };
7374
- const updateDateTime = (date, timeData) => {
7375
- console.log('[DateTimePicker] updateDateTime called:', {
7376
- date,
7377
- timeData,
7378
- format,
7379
- currentStates: { hour12, minute, meridiem, hour24, second },
7380
- });
7381
- if (!date || date === null || date === undefined) {
7382
- console.log('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
7383
- setSelectedDate('');
7384
- setHour12(null);
7385
- setMinute(null);
7386
- setMeridiem(null);
7387
- setHour24(null);
7388
- setSecond(null);
7389
- onChange?.(undefined);
7390
- return;
7948
+ // Handle date selection
7949
+ const handleDateSelected = ({ date, }) => {
7950
+ setSelectedDate(date);
7951
+ updateDateTime(date, hour, minute, second, meridiem);
7952
+ };
7953
+ // Handle time change
7954
+ const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
7955
+ setHour(newHour);
7956
+ setMinute(newMinute);
7957
+ if (is24Hour) {
7958
+ setSecond(newSecond);
7391
7959
  }
7392
- // use dayjs to convert the date to the timezone
7393
- const dateObj = dayjs(date).tz(timezone);
7394
- if (!dateObj.isValid()) {
7395
- console.warn('[DateTimePicker] Invalid date object in updateDateTime, clearing fields:', date);
7396
- setSelectedDate('');
7397
- setHour12(null);
7398
- setMinute(null);
7399
- setMeridiem(null);
7400
- setHour24(null);
7401
- setSecond(null);
7402
- onChange?.(undefined);
7403
- return;
7960
+ else {
7961
+ setMeridiem(newMeridiem);
7404
7962
  }
7405
- const newDate = dateObj.toDate();
7406
- if (format === 'iso-date-time') {
7407
- const data = timeData;
7408
- // Use timeData values if provided, otherwise fall back to current state
7409
- // But if timeData is explicitly provided with nulls, we need to check if all are null
7410
- const h = data !== undefined ? data.hour : hour24;
7411
- const m = data !== undefined ? data.minute : minute;
7412
- // Always ignore seconds when showSeconds is false - set to 0
7413
- const s = showSeconds
7414
- ? data !== undefined
7415
- ? data.second ?? null
7416
- : second ?? 0
7417
- : 0;
7418
- // If all time values are null, clear the value
7419
- if (h === null && m === null && (showSeconds ? s === null : true)) {
7420
- console.log('[DateTimePicker] All time values are null, clearing value');
7421
- onChange?.(undefined);
7422
- return;
7963
+ if (selectedDate) {
7964
+ updateDateTime(selectedDate, newHour, newMinute, newSecond, newMeridiem);
7965
+ }
7966
+ };
7967
+ // Calendar hook
7968
+ const calendarProps = useCalendar({
7969
+ selected: selectedDate || undefined,
7970
+ date: selectedDate || undefined,
7971
+ minDate,
7972
+ maxDate,
7973
+ monthsToDisplay: 1,
7974
+ onDateSelected: handleDateSelected,
7975
+ });
7976
+ // Convert DateTimePickerLabels to DatePickerLabels format
7977
+ const calendarLabels = useMemo(() => ({
7978
+ monthNamesShort: labels.monthNamesShort || [
7979
+ 'Jan',
7980
+ 'Feb',
7981
+ 'Mar',
7982
+ 'Apr',
7983
+ 'May',
7984
+ 'Jun',
7985
+ 'Jul',
7986
+ 'Aug',
7987
+ 'Sep',
7988
+ 'Oct',
7989
+ 'Nov',
7990
+ 'Dec',
7991
+ ],
7992
+ weekdayNamesShort: labels.weekdayNamesShort || [
7993
+ 'Sun',
7994
+ 'Mon',
7995
+ 'Tue',
7996
+ 'Wed',
7997
+ 'Thu',
7998
+ 'Fri',
7999
+ 'Sat',
8000
+ ],
8001
+ backButtonLabel: labels.backButtonLabel || 'Back',
8002
+ forwardButtonLabel: labels.forwardButtonLabel || 'Forward',
8003
+ todayLabel: quickActionLabels.today || 'Today',
8004
+ yesterdayLabel: quickActionLabels.yesterday || 'Yesterday',
8005
+ tomorrowLabel: quickActionLabels.tomorrow || 'Tomorrow',
8006
+ }), [labels, quickActionLabels]);
8007
+ // Generate time options
8008
+ const timeOptions = useMemo(() => {
8009
+ const options = [];
8010
+ // Get start time for comparison if provided
8011
+ let startDateTime = null;
8012
+ let shouldFilterByDate = false;
8013
+ if (startTime && selectedDate) {
8014
+ const startDateObj = dayjs(startTime).tz(tz);
8015
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8016
+ if (startDateObj.isValid() && selectedDateObj.isValid()) {
8017
+ startDateTime = startDateObj;
8018
+ shouldFilterByDate =
8019
+ startDateObj.format('YYYY-MM-DD') ===
8020
+ selectedDateObj.format('YYYY-MM-DD');
8021
+ }
8022
+ }
8023
+ if (is24Hour) {
8024
+ // Generate 24-hour format options
8025
+ for (let h = 0; h < 24; h++) {
8026
+ for (let m = 0; m < 60; m += 15) {
8027
+ // Filter out times that would result in negative duration
8028
+ if (startDateTime && selectedDate && shouldFilterByDate) {
8029
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8030
+ const optionDateTime = selectedDateObj
8031
+ .hour(h)
8032
+ .minute(m)
8033
+ .second(0)
8034
+ .millisecond(0);
8035
+ if (optionDateTime.isBefore(startDateTime)) {
8036
+ continue;
8037
+ }
8038
+ }
8039
+ // Calculate duration if startTime is provided
8040
+ let durationText;
8041
+ if (startDateTime && selectedDate) {
8042
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8043
+ const optionDateTime = selectedDateObj
8044
+ .hour(h)
8045
+ .minute(m)
8046
+ .second(0)
8047
+ .millisecond(0);
8048
+ if (optionDateTime.isValid() &&
8049
+ optionDateTime.isAfter(startDateTime)) {
8050
+ const diffMs = optionDateTime.diff(startDateTime);
8051
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
8052
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
8053
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
8054
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
8055
+ let diffText = '';
8056
+ if (diffHours > 0) {
8057
+ diffText = `${diffHours}h ${diffMinutes}m`;
8058
+ }
8059
+ else if (diffMinutes > 0) {
8060
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
8061
+ }
8062
+ else {
8063
+ diffText = `${diffSeconds}s`;
8064
+ }
8065
+ durationText = `+${diffText}`;
8066
+ }
8067
+ }
8068
+ }
8069
+ const s = showSeconds ? 0 : 0;
8070
+ const timeDisplay = showSeconds
8071
+ ? `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`
8072
+ : `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
8073
+ options.push({
8074
+ label: timeDisplay,
8075
+ value: `${h}:${m}:${s}`,
8076
+ hour: h,
8077
+ minute: m,
8078
+ second: s,
8079
+ searchText: timeDisplay,
8080
+ durationText,
8081
+ });
8082
+ }
7423
8083
  }
7424
- console.log('[DateTimePicker] ISO format - setting time on date:', {
7425
- h,
7426
- m,
7427
- s,
7428
- showSeconds,
7429
- });
7430
- if (h !== null)
7431
- newDate.setHours(h);
7432
- if (m !== null)
7433
- newDate.setMinutes(m);
7434
- newDate.setSeconds(s ?? 0);
7435
8084
  }
7436
8085
  else {
7437
- const data = timeData;
7438
- console.log('[DateTimePicker] Processing 12-hour format:', {
7439
- 'data !== undefined': data !== undefined,
7440
- 'data?.hour': data?.hour,
7441
- 'data?.minute': data?.minute,
7442
- 'data?.meridiem': data?.meridiem,
7443
- 'current hour12': hour12,
7444
- 'current minute': minute,
7445
- 'current meridiem': meridiem,
7446
- });
7447
- // Use timeData values if provided, otherwise fall back to current state
7448
- const h = data !== undefined ? data.hour : hour12;
7449
- const m = data !== undefined ? data.minute : minute;
7450
- const mer = data !== undefined ? data.meridiem : meridiem;
7451
- console.log('[DateTimePicker] Resolved time values:', { h, m, mer });
7452
- // If all time values are null, clear the value
7453
- if (h === null && m === null && mer === null) {
7454
- console.log('[DateTimePicker] All time values are null, clearing value');
7455
- onChange?.(undefined);
7456
- return;
8086
+ // Generate 12-hour format options
8087
+ for (let h = 1; h <= 12; h++) {
8088
+ for (let m = 0; m < 60; m += 15) {
8089
+ for (const mer of ['am', 'pm']) {
8090
+ // Convert 12-hour to 24-hour for comparison
8091
+ let hour24 = h;
8092
+ if (mer === 'am' && h === 12)
8093
+ hour24 = 0;
8094
+ else if (mer === 'pm' && h < 12)
8095
+ hour24 = h + 12;
8096
+ // Filter out times that would result in negative duration
8097
+ if (startDateTime && selectedDate && shouldFilterByDate) {
8098
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8099
+ const optionDateTime = selectedDateObj
8100
+ .hour(hour24)
8101
+ .minute(m)
8102
+ .second(0)
8103
+ .millisecond(0);
8104
+ if (optionDateTime.isBefore(startDateTime)) {
8105
+ continue;
8106
+ }
8107
+ }
8108
+ // Calculate duration if startTime is provided
8109
+ let durationText;
8110
+ if (startDateTime && selectedDate) {
8111
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8112
+ const optionDateTime = selectedDateObj
8113
+ .hour(hour24)
8114
+ .minute(m)
8115
+ .second(0)
8116
+ .millisecond(0);
8117
+ if (optionDateTime.isValid() &&
8118
+ optionDateTime.isAfter(startDateTime)) {
8119
+ const diffMs = optionDateTime.diff(startDateTime);
8120
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
8121
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
8122
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
8123
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
8124
+ let diffText = '';
8125
+ if (diffHours > 0) {
8126
+ diffText = `${diffHours}h ${diffMinutes}m`;
8127
+ }
8128
+ else if (diffMinutes > 0) {
8129
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
8130
+ }
8131
+ else {
8132
+ diffText = `${diffSeconds}s`;
8133
+ }
8134
+ durationText = `+${diffText}`;
8135
+ }
8136
+ }
8137
+ }
8138
+ const hourDisplay = h.toString();
8139
+ const minuteDisplay = m.toString().padStart(2, '0');
8140
+ const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
8141
+ options.push({
8142
+ label: timeDisplay,
8143
+ value: `${h}:${m}:${mer}`,
8144
+ hour: h,
8145
+ minute: m,
8146
+ meridiem: mer,
8147
+ searchText: timeDisplay,
8148
+ durationText,
8149
+ });
8150
+ }
8151
+ }
7457
8152
  }
7458
- console.log('[DateTimePicker] 12-hour format - converting time:', {
7459
- h,
7460
- m,
7461
- mer,
8153
+ // Sort 12-hour options by time
8154
+ return options.sort((a, b) => {
8155
+ const a12 = a;
8156
+ const b12 = b;
8157
+ let hour24A = a12.hour;
8158
+ if (a12.meridiem === 'am' && a12.hour === 12)
8159
+ hour24A = 0;
8160
+ else if (a12.meridiem === 'pm' && a12.hour < 12)
8161
+ hour24A = a12.hour + 12;
8162
+ let hour24B = b12.hour;
8163
+ if (b12.meridiem === 'am' && b12.hour === 12)
8164
+ hour24B = 0;
8165
+ else if (b12.meridiem === 'pm' && b12.hour < 12)
8166
+ hour24B = b12.hour + 12;
8167
+ if (hour24A !== hour24B) {
8168
+ return hour24A - hour24B;
8169
+ }
8170
+ return a12.minute - b12.minute;
7462
8171
  });
7463
- if (h !== null && mer !== null) {
7464
- let hour24 = h;
7465
- if (mer === 'am' && h === 12)
7466
- hour24 = 0;
7467
- else if (mer === 'pm' && h < 12)
7468
- hour24 = h + 12;
7469
- console.log('[DateTimePicker] Converted to 24-hour:', {
7470
- h,
7471
- mer,
7472
- hour24,
7473
- });
7474
- newDate.setHours(hour24);
8172
+ }
8173
+ return options;
8174
+ }, [startTime, selectedDate, tz, is24Hour, showSeconds]);
8175
+ // Time picker combobox setup
8176
+ const itemToString = useMemo(() => {
8177
+ return (item) => {
8178
+ return item.searchText;
8179
+ };
8180
+ }, []);
8181
+ const { contains } = useFilter({ sensitivity: 'base' });
8182
+ const customTimeFilter = useMemo(() => {
8183
+ if (is24Hour) {
8184
+ return contains;
8185
+ }
8186
+ return (itemText, filterText) => {
8187
+ if (!filterText) {
8188
+ return true;
7475
8189
  }
7476
- else {
7477
- console.log('[DateTimePicker] Skipping hour update - h or mer is null:', {
7478
- h,
7479
- mer,
7480
- });
8190
+ const lowerItemText = itemText.toLowerCase();
8191
+ const lowerFilterText = filterText.toLowerCase();
8192
+ if (lowerItemText.includes(lowerFilterText)) {
8193
+ return true;
7481
8194
  }
7482
- if (m !== null) {
7483
- newDate.setMinutes(m);
8195
+ const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
8196
+ if (!item || !('meridiem' in item)) {
8197
+ return false;
7484
8198
  }
7485
- else {
7486
- console.log('[DateTimePicker] Skipping minute update - m is null');
8199
+ let hour24 = item.hour;
8200
+ if (item.meridiem === 'am' && item.hour === 12)
8201
+ hour24 = 0;
8202
+ else if (item.meridiem === 'pm' && item.hour < 12)
8203
+ hour24 = item.hour + 12;
8204
+ const hour24Str = hour24.toString().padStart(2, '0');
8205
+ const minuteStr = item.minute.toString().padStart(2, '0');
8206
+ const formats = [
8207
+ `${hour24Str}:${minuteStr}`,
8208
+ `${hour24Str}${minuteStr}`,
8209
+ hour24Str,
8210
+ `${hour24}:${minuteStr}`,
8211
+ hour24.toString(),
8212
+ ];
8213
+ return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
8214
+ lowerFilterText.includes(format.toLowerCase()));
8215
+ };
8216
+ }, [timeOptions, is24Hour, contains]);
8217
+ const { collection, filter } = useListCollection({
8218
+ initialItems: timeOptions,
8219
+ itemToString: itemToString,
8220
+ itemToValue: (item) => item.value,
8221
+ filter: customTimeFilter,
8222
+ });
8223
+ // Get current value string for combobox (must match option.value format)
8224
+ const currentTimeValue = useMemo(() => {
8225
+ if (is24Hour) {
8226
+ if (hour === null || minute === null) {
8227
+ return '';
7487
8228
  }
7488
- newDate.setSeconds(0);
7489
- }
7490
- const finalISO = dayjs(newDate).tz(timezone).toISOString();
7491
- console.log('[DateTimePicker] Final ISO string to emit:', {
7492
- newDate: newDate.toISOString(),
7493
- timezone,
7494
- finalISO,
7495
- });
7496
- onChange?.(finalISO);
7497
- };
7498
- const handleClear = () => {
7499
- setSelectedDate('');
7500
- setHour12(null);
7501
- setHour24(null);
7502
- setMinute(null);
7503
- setSecond(null);
7504
- setMeridiem(null);
7505
- onChange?.(undefined);
7506
- };
7507
- const isISO = format === 'iso-date-time';
7508
- // Normalize startTime to ignore milliseconds
7509
- const normalizedStartTime = startTime
7510
- ? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
7511
- : undefined;
7512
- // Determine minDate: prioritize explicit minDate prop, then fall back to startTime
7513
- const effectiveMinDate = minDate
7514
- ? minDate
7515
- : normalizedStartTime && dayjs(normalizedStartTime).tz(timezone).isValid()
7516
- ? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
7517
- : undefined;
7518
- // Log current state before render
7519
- useEffect(() => {
7520
- console.log('[DateTimePicker] Current state before render:', {
7521
- isISO,
7522
- hour12,
7523
- minute,
7524
- meridiem,
7525
- hour24,
7526
- second,
7527
- selectedDate,
7528
- normalizedStartTime,
7529
- timezone,
7530
- });
7531
- }, [
7532
- isISO,
7533
- hour12,
7534
- minute,
7535
- meridiem,
7536
- hour24,
7537
- second,
7538
- selectedDate,
7539
- normalizedStartTime,
7540
- timezone,
7541
- ]);
7542
- // Compute display text from current state
7543
- const displayText = useMemo(() => {
7544
- if (!selectedDate)
7545
- return null;
7546
- const dateObj = dayjs.tz(selectedDate, timezone);
7547
- if (!dateObj.isValid())
7548
- return null;
7549
- if (isISO) {
7550
- // For ISO format, use hour24, minute, second
7551
- if (hour24 === null || minute === null)
7552
- return null;
7553
- const dateTimeObj = dateObj
7554
- .hour(hour24)
7555
- .minute(minute)
7556
- .second(second ?? 0);
7557
- return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
8229
+ const s = second ?? 0;
8230
+ return `${hour}:${minute}:${s}`;
7558
8231
  }
7559
8232
  else {
7560
- // For 12-hour format, use hour12, minute, meridiem
7561
- if (hour12 === null || minute === null || meridiem === null)
7562
- return null;
7563
- // Convert to 24-hour format for dayjs
7564
- let hour24Value = hour12;
7565
- if (meridiem === 'am' && hour12 === 12)
7566
- hour24Value = 0;
7567
- else if (meridiem === 'pm' && hour12 < 12)
7568
- hour24Value = hour12 + 12;
7569
- const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
7570
- return dateTimeObj.format('YYYY-MM-DD hh:mm A');
8233
+ if (hour === null || minute === null || meridiem === null) {
8234
+ return '';
8235
+ }
8236
+ return `${hour}:${minute}:${meridiem}`;
8237
+ }
8238
+ }, [hour, minute, second, meridiem, is24Hour]);
8239
+ // Parse custom time input formats like "1400", "2pm", "14:00", "2:00 PM"
8240
+ const parseCustomTimeInput = (input) => {
8241
+ if (!input || !input.trim()) {
8242
+ return { hour: null, minute: null, second: null, meridiem: null };
8243
+ }
8244
+ const trimmed = input.trim().toLowerCase();
8245
+ // Try parsing 4-digit format without colon: "1400" -> 14:00
8246
+ const fourDigitMatch = trimmed.match(/^(\d{4})$/);
8247
+ if (fourDigitMatch) {
8248
+ const digits = fourDigitMatch[1];
8249
+ const hour = parseInt(digits.substring(0, 2), 10);
8250
+ const minute = parseInt(digits.substring(2, 4), 10);
8251
+ if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
8252
+ if (is24Hour) {
8253
+ return { hour, minute, second: 0, meridiem: null };
8254
+ }
8255
+ else {
8256
+ // Convert to 12-hour format
8257
+ let hour12 = hour;
8258
+ let meridiem;
8259
+ if (hour === 0) {
8260
+ hour12 = 12;
8261
+ meridiem = 'am';
8262
+ }
8263
+ else if (hour === 12) {
8264
+ hour12 = 12;
8265
+ meridiem = 'pm';
8266
+ }
8267
+ else if (hour > 12) {
8268
+ hour12 = hour - 12;
8269
+ meridiem = 'pm';
8270
+ }
8271
+ else {
8272
+ hour12 = hour;
8273
+ meridiem = 'am';
8274
+ }
8275
+ return { hour: hour12, minute, second: null, meridiem };
8276
+ }
8277
+ }
7571
8278
  }
7572
- }, [
7573
- selectedDate,
7574
- isISO,
7575
- hour12,
7576
- minute,
7577
- meridiem,
7578
- hour24,
7579
- second,
7580
- showSeconds,
7581
- timezone,
7582
- ]);
7583
- const timezoneOffset = useMemo(() => {
7584
- if (!selectedDate)
7585
- return null;
7586
- const dateObj = dayjs.tz(selectedDate, timezone);
7587
- return dateObj.isValid() ? dateObj.format('Z') : null;
7588
- }, [selectedDate, timezone]);
7589
- return (jsxs(Flex, { direction: "column", gap: 4, children: [jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
7590
- if (date) {
7591
- handleDateChange(date);
8279
+ // Try parsing hour with meridiem: "2pm", "14pm", "2am"
8280
+ const hourMeridiemMatch = trimmed.match(/^(\d{1,2})\s*(am|pm)$/);
8281
+ if (hourMeridiemMatch && !is24Hour) {
8282
+ const hour12 = parseInt(hourMeridiemMatch[1], 10);
8283
+ const meridiem = hourMeridiemMatch[2];
8284
+ if (hour12 >= 1 && hour12 <= 12) {
8285
+ return { hour: hour12, minute: 0, second: null, meridiem };
8286
+ }
8287
+ }
8288
+ // Try parsing 24-hour format with hour only: "14" -> 14:00
8289
+ const hourOnlyMatch = trimmed.match(/^(\d{1,2})$/);
8290
+ if (hourOnlyMatch && is24Hour) {
8291
+ const hour = parseInt(hourOnlyMatch[1], 10);
8292
+ if (hour >= 0 && hour <= 23) {
8293
+ return { hour, minute: 0, second: 0, meridiem: null };
8294
+ }
8295
+ }
8296
+ // Try parsing standard formats: "14:00", "2:00 PM"
8297
+ const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
8298
+ const match24 = trimmed.match(time24Pattern);
8299
+ if (match24) {
8300
+ const hour24 = parseInt(match24[1], 10);
8301
+ const minute = parseInt(match24[2], 10);
8302
+ const second = match24[3] ? parseInt(match24[3], 10) : 0;
8303
+ if (hour24 >= 0 &&
8304
+ hour24 <= 23 &&
8305
+ minute >= 0 &&
8306
+ minute <= 59 &&
8307
+ second >= 0 &&
8308
+ second <= 59) {
8309
+ if (is24Hour) {
8310
+ return { hour: hour24, minute, second, meridiem: null };
8311
+ }
8312
+ else {
8313
+ // Convert to 12-hour format
8314
+ let hour12 = hour24;
8315
+ let meridiem;
8316
+ if (hour24 === 0) {
8317
+ hour12 = 12;
8318
+ meridiem = 'am';
8319
+ }
8320
+ else if (hour24 === 12) {
8321
+ hour12 = 12;
8322
+ meridiem = 'pm';
8323
+ }
8324
+ else if (hour24 > 12) {
8325
+ hour12 = hour24 - 12;
8326
+ meridiem = 'pm';
7592
8327
  }
7593
8328
  else {
7594
- setSelectedDate('');
7595
- onChange?.(undefined);
8329
+ hour12 = hour24;
8330
+ meridiem = 'am';
8331
+ }
8332
+ return { hour: hour12, minute, second: null, meridiem };
8333
+ }
8334
+ }
8335
+ }
8336
+ // Try parsing 12-hour format: "2:00 PM", "2:00PM"
8337
+ const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm)$/;
8338
+ const match12 = trimmed.match(time12Pattern);
8339
+ if (match12 && !is24Hour) {
8340
+ const hour12 = parseInt(match12[1], 10);
8341
+ const minute = parseInt(match12[2], 10);
8342
+ const second = match12[3] ? parseInt(match12[3], 10) : null;
8343
+ const meridiem = match12[4];
8344
+ if (hour12 >= 1 &&
8345
+ hour12 <= 12 &&
8346
+ minute >= 0 &&
8347
+ minute <= 59 &&
8348
+ (second === null || (second >= 0 && second <= 59))) {
8349
+ return { hour: hour12, minute, second, meridiem };
8350
+ }
8351
+ }
8352
+ return { hour: null, minute: null, second: null, meridiem: null };
8353
+ };
8354
+ const handleTimeValueChange = (details) => {
8355
+ if (details.value.length === 0) {
8356
+ handleTimeChange(null, null, null, null);
8357
+ filter('');
8358
+ return;
8359
+ }
8360
+ const selectedValue = details.value[0];
8361
+ const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
8362
+ if (selectedOption) {
8363
+ filter('');
8364
+ if (is24Hour) {
8365
+ const opt24 = selectedOption;
8366
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
8367
+ }
8368
+ else {
8369
+ const opt12 = selectedOption;
8370
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
8371
+ }
8372
+ }
8373
+ };
8374
+ // Track the current input value for Enter key handling
8375
+ const [timeInputValue, setTimeInputValue] = useState('');
8376
+ const handleTimeInputChange = (details) => {
8377
+ // Store the input value and filter
8378
+ setTimeInputValue(details.inputValue);
8379
+ filter(details.inputValue);
8380
+ };
8381
+ const handleTimeInputKeyDown = (e) => {
8382
+ if (e.key === 'Enter') {
8383
+ e.preventDefault();
8384
+ // Use the stored input value
8385
+ const parsed = parseCustomTimeInput(timeInputValue);
8386
+ if (parsed.hour !== null && parsed.minute !== null) {
8387
+ if (is24Hour) {
8388
+ handleTimeChange(parsed.hour, parsed.minute, parsed.second, null);
8389
+ }
8390
+ else {
8391
+ if (parsed.meridiem !== null) {
8392
+ handleTimeChange(parsed.hour, parsed.minute, null, parsed.meridiem);
7596
8393
  }
7597
- }, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly: true }), jsxs(Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: showSeconds ? second : null, setSecond: showSeconds ? setSecond : () => { }, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })) : (jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsx(Icon, { as: FaTrash }) })] }), displayText && (jsxs(Flex, { gap: 2, children: [jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: displayText }), timezoneOffset && (jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezoneOffset })), jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
8394
+ }
8395
+ // Clear the filter and input value after applying
8396
+ filter('');
8397
+ setTimeInputValue('');
8398
+ // Close the popover if value is valid
8399
+ setTimePopoverOpen(false);
8400
+ }
8401
+ }
8402
+ };
8403
+ return (jsxs(Flex, { direction: "row", gap: 2, align: "center", children: [jsxs(Popover.Root, { open: datePopoverOpen, onOpenChange: (e) => setDatePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button$1, { size: "sm", variant: "outline", onClick: () => setDatePopoverOpen(true), justifyContent: "start", children: [jsx(MdDateRange, {}), dateDisplayText] }) }), portalled ? (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", children: jsx(Popover.Body, { p: 4, children: jsxs(Grid, { gap: 4, children: [jsx(InputGroup$1, { endElement: jsxs(Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsx(Button$1, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsx(MdDateRange, {}) }) }), jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", zIndex: 1500, children: jsx(Popover.Body, { p: 4, children: jsx(DatePickerContext.Provider, { value: { labels: calendarLabels }, children: jsx(Calendar, { ...calendarProps, firstDayOfWeek: 0 }) }) }) }) }) })] }), children: jsx(Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxs(Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }) })) : (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", children: jsx(Popover.Body, { p: 4, children: jsxs(Grid, { gap: 4, children: [jsx(InputGroup$1, { endElement: jsxs(Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsx(Button$1, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsx(MdDateRange, {}) }) }), jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", zIndex: 1700, children: jsx(Popover.Body, { p: 4, children: jsx(DatePickerContext.Provider, { value: { labels: calendarLabels }, children: jsx(Calendar, { ...calendarProps, firstDayOfWeek: 0 }) }) }) }) }) })] }), children: jsx(Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxs(Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }))] }), jsxs(Popover.Root, { open: timePopoverOpen, onOpenChange: (e) => setTimePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button$1, { size: "sm", variant: "outline", onClick: () => setTimePopoverOpen(true), justifyContent: "start", children: [jsx(BsClock, {}), timeDisplayText] }) }), portalled ? (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "300px", children: jsx(Popover.Body, { p: 4, children: jsx(Grid, { gap: 2, children: jsxs(Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
8404
+ (is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: true, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: timePickerLabels?.emptyMessage ??
8405
+ 'No time found' }), collection.items.map((item) => {
8406
+ const option = item;
8407
+ return (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { justify: "space-between", align: "center", w: "100%", children: [jsx(Text, { children: option.label }), option.durationText && (jsx(Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsx(Combobox.ItemIndicator, {})] }, option.value));
8408
+ })] }) }) })] }) }) }) }) }) })) : (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "300px", children: jsx(Popover.Body, { p: 4, children: jsx(Grid, { gap: 2, children: jsxs(Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
8409
+ (is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: true, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: timePickerLabels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => {
8410
+ const option = item;
8411
+ return (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { justify: "space-between", align: "center", w: "100%", children: [jsx(Text, { children: option.label }), option.durationText && (jsx(Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsx(Combobox.ItemIndicator, {})] }, option.value));
8412
+ })] }) }) })] }) }) }) }) }))] }), showTimezoneSelector && (jsxs(Popover.Root, { open: timezonePopoverOpen, onOpenChange: (e) => setTimezonePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsx(Button$1, { size: "sm", variant: "outline", onClick: () => setTimezonePopoverOpen(true), justifyContent: "start", children: timezoneDisplayText || 'Select timezone' }) }), portalled ? (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "250px", children: jsx(Popover.Body, { p: 4, children: jsx(Grid, { gap: 2, children: jsxs(Select.Root, { size: "sm", collection: timezoneCollection, value: validTimezoneOffset ? [validTimezoneOffset] : [], onValueChange: (e) => {
8413
+ const newOffset = e.value[0];
8414
+ if (newOffset) {
8415
+ // Update controlled or internal state
8416
+ if (onTimezoneOffsetChange) {
8417
+ onTimezoneOffsetChange(newOffset);
8418
+ }
8419
+ else {
8420
+ setInternalTimezoneOffset(newOffset);
8421
+ }
8422
+ // Update date-time with new offset (pass it directly to avoid stale state)
8423
+ if (selectedDate &&
8424
+ hour !== null &&
8425
+ minute !== null) {
8426
+ updateDateTime(selectedDate, hour, minute, second, meridiem, newOffset);
8427
+ }
8428
+ // Close popover after selection
8429
+ setTimezonePopoverOpen(false);
8430
+ }
8431
+ }, children: [jsxs(Select.Control, { children: [jsx(Select.Trigger, {}), jsx(Select.IndicatorGroup, { children: jsx(Select.Indicator, {}) })] }), jsx(Select.Positioner, { children: jsx(Select.Content, { children: timezoneCollection.items.map((item) => (jsxs(Select.Item, { item: item, children: [jsx(Select.ItemText, { children: item.label }), jsx(Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }) })) : (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "250px", children: jsx(Popover.Body, { p: 4, children: jsx(Grid, { gap: 2, children: jsxs(Select.Root, { size: "sm", collection: timezoneCollection, value: validTimezoneOffset ? [validTimezoneOffset] : [], onValueChange: (e) => {
8432
+ const newOffset = e.value[0];
8433
+ if (newOffset) {
8434
+ // Update controlled or internal state
8435
+ if (onTimezoneOffsetChange) {
8436
+ onTimezoneOffsetChange(newOffset);
8437
+ }
8438
+ else {
8439
+ setInternalTimezoneOffset(newOffset);
8440
+ }
8441
+ // Update date-time with new offset (pass it directly to avoid stale state)
8442
+ if (selectedDate &&
8443
+ hour !== null &&
8444
+ minute !== null) {
8445
+ updateDateTime(selectedDate, hour, minute, second, meridiem, newOffset);
8446
+ }
8447
+ // Close popover after selection
8448
+ setTimezonePopoverOpen(false);
8449
+ }
8450
+ }, children: [jsxs(Select.Control, { children: [jsx(Select.Trigger, {}), jsx(Select.IndicatorGroup, { children: jsx(Select.Indicator, {}) })] }), jsx(Select.Positioner, { children: jsx(Select.Content, { children: timezoneCollection.items.map((item) => (jsxs(Select.Item, { item: item, children: [jsx(Select.ItemText, { children: item.label }), jsx(Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }))] }))] }));
7598
8451
  }
7599
8452
 
7600
8453
  dayjs.extend(utc);
@@ -7605,14 +8458,15 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
7605
8458
  const formI18n = useFormI18n(column, prefix, schema);
7606
8459
  const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD HH:mm:ss',
7607
8460
  // with timezone
7608
- dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', } = schema;
8461
+ dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', dateTimePicker, } = schema;
7609
8462
  const isRequired = required?.some((columnId) => columnId === column);
7610
8463
  const colLabel = formI18n.colLabel;
7611
- const [open, setOpen] = useState(false);
8464
+ useState(false);
7612
8465
  const selectedDate = watch(colLabel);
7613
- const displayDate = selectedDate && dayjs(selectedDate).tz(timezone).isValid()
8466
+ selectedDate && dayjs(selectedDate).tz(timezone).isValid()
7614
8467
  ? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
7615
8468
  : '';
8469
+ // Set default date on mount if no value exists
7616
8470
  const dateTimePickerLabelsConfig = {
7617
8471
  monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
7618
8472
  'January',
@@ -7652,11 +8506,9 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
7652
8506
  else {
7653
8507
  setValue(colLabel, undefined);
7654
8508
  }
7655
- }, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels }));
8509
+ }, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels, showQuickActions: dateTimePicker?.showQuickActions ?? false, quickActionLabels: dateTimePicker?.quickActionLabels, showTimezoneSelector: dateTimePicker?.showTimezoneSelector ?? false }));
7656
8510
  return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
7657
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
7658
- setOpen(true);
7659
- }, justifyContent: 'start', children: [jsx(MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
8511
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: dateTimePickerContent }));
7660
8512
  };
7661
8513
 
7662
8514
  const SchemaRenderer = ({ schema, prefix, column, }) => {
@@ -7777,7 +8629,7 @@ const BooleanViewer = ({ schema, column, prefix, }) => {
7777
8629
  const value = watch(colLabel);
7778
8630
  const formI18n = useFormI18n(column, prefix, schema);
7779
8631
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
7780
- gridRow, children: [jsx(Text, { children: value ? formI18n.t('true') : formI18n.t('false') }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
8632
+ gridRow, children: [jsx(Text, { children: value ? 'True' : 'False' }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
7781
8633
  };
7782
8634
 
7783
8635
  const CustomViewer = ({ column, schema, prefix }) => {
@@ -7809,23 +8661,22 @@ const DateViewer = ({ column, schema, prefix }) => {
7809
8661
 
7810
8662
  const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
7811
8663
  const { watch, formState: { errors }, } = useFormContext();
7812
- const formI18n = useFormI18n(column, prefix);
8664
+ const formI18n = useFormI18n(column, prefix, schema);
7813
8665
  const { required } = schema;
7814
8666
  const isRequired = required?.some((columnId) => columnId === column);
7815
- const { gridColumn = "span 12", gridRow = "span 1", renderDisplay } = schema;
8667
+ const { gridColumn = 'span 12', gridRow = 'span 1', renderDisplay } = schema;
7816
8668
  const colLabel = formI18n.colLabel;
7817
8669
  const watchEnum = watch(colLabel);
7818
8670
  const watchEnums = (watch(colLabel) ?? []);
7819
- return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: "stretch", gridColumn,
7820
- gridRow, children: [isMultiple && (jsx(Flex, { flexFlow: "wrap", gap: 1, children: watchEnums.map((enumValue) => {
8671
+ const renderDisplayFunction = renderDisplay || defaultRenderDisplay;
8672
+ return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
8673
+ gridRow, children: [isMultiple && (jsx(Flex, { flexFlow: 'wrap', gap: 1, children: watchEnums.map((enumValue) => {
7821
8674
  const item = enumValue;
7822
8675
  if (item === undefined) {
7823
8676
  return jsx(Fragment, { children: "undefined" });
7824
8677
  }
7825
- return (jsx(Tag, { size: "lg", children: !!renderDisplay === true
7826
- ? renderDisplay(item)
7827
- : formI18n.t(item) }, item));
7828
- }) })), !isMultiple && jsx(Text, { children: formI18n.t(watchEnum) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: formI18n.required() }))] }));
8678
+ return (jsx(Tag, { size: "lg", children: renderDisplayFunction(item) }, item));
8679
+ }) })), !isMultiple && jsx(Text, { children: renderDisplayFunction(watchEnum) }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: formI18n.required() }))] }));
7829
8680
  };
7830
8681
 
7831
8682
  const FileViewer = ({ column, schema, prefix }) => {
@@ -7945,31 +8796,35 @@ const StringViewer = ({ column, schema, prefix, }) => {
7945
8796
 
7946
8797
  const TagViewer = ({ column, schema, prefix }) => {
7947
8798
  const { watch, formState: { errors }, setValue, } = useFormContext();
7948
- const { serverUrl } = useSchemaContext();
7949
8799
  if (schema.properties == undefined) {
7950
- throw new Error("schema properties undefined when using DatePicker");
8800
+ throw new Error('schema properties undefined when using DatePicker');
7951
8801
  }
7952
- const { gridColumn, gridRow, in_table, object_id_column } = schema;
8802
+ const { gridColumn, gridRow, in_table, object_id_column, tagPicker } = schema;
7953
8803
  if (in_table === undefined) {
7954
- throw new Error("in_table is undefined when using TagPicker");
8804
+ throw new Error('in_table is undefined when using TagPicker');
7955
8805
  }
7956
8806
  if (object_id_column === undefined) {
7957
- throw new Error("object_id_column is undefined when using TagPicker");
8807
+ throw new Error('object_id_column is undefined when using TagPicker');
8808
+ }
8809
+ if (!tagPicker?.queryFn) {
8810
+ throw new Error('tagPicker.queryFn is required in schema. serverUrl has been removed.');
7958
8811
  }
7959
8812
  const query = useQuery({
7960
8813
  queryKey: [`tagpicker`, in_table],
7961
8814
  queryFn: async () => {
7962
- return await getTableData({
7963
- serverUrl,
7964
- in_table: "tables_tags_view",
8815
+ const result = await tagPicker.queryFn({
8816
+ in_table: 'tables_tags_view',
7965
8817
  where: [
7966
8818
  {
7967
- id: "table_name",
8819
+ id: 'table_name',
7968
8820
  value: [in_table],
7969
8821
  },
7970
8822
  ],
7971
8823
  limit: 100,
8824
+ offset: 0,
8825
+ searching: '',
7972
8826
  });
8827
+ return result.data || { data: [] };
7973
8828
  },
7974
8829
  staleTime: 10000,
7975
8830
  });
@@ -7977,17 +8832,19 @@ const TagViewer = ({ column, schema, prefix }) => {
7977
8832
  const existingTagsQuery = useQuery({
7978
8833
  queryKey: [`existing`, { in_table, object_id_column }, object_id],
7979
8834
  queryFn: async () => {
7980
- return await getTableData({
7981
- serverUrl,
8835
+ const result = await tagPicker.queryFn({
7982
8836
  in_table: in_table,
7983
8837
  where: [
7984
8838
  {
7985
8839
  id: object_id_column,
7986
- value: object_id[0],
8840
+ value: [object_id[0]],
7987
8841
  },
7988
8842
  ],
7989
8843
  limit: 100,
8844
+ offset: 0,
8845
+ searching: '',
7990
8846
  });
8847
+ return result.data || { data: [] };
7991
8848
  },
7992
8849
  enabled: object_id != undefined,
7993
8850
  staleTime: 10000,
@@ -7998,9 +8855,9 @@ const TagViewer = ({ column, schema, prefix }) => {
7998
8855
  if (!!object_id === false) {
7999
8856
  return jsx(Fragment, {});
8000
8857
  }
8001
- return (jsxs(Flex, { flexFlow: "column", gap: 4, gridColumn,
8858
+ return (jsxs(Flex, { flexFlow: 'column', gap: 4, gridColumn,
8002
8859
  gridRow, children: [isFetching && jsx(Fragment, { children: "isFetching" }), isLoading && jsx(Fragment, { children: "isLoading" }), isPending && jsx(Fragment, { children: "isPending" }), isError && jsx(Fragment, { children: "isError" }), dataList.map(({ parent_tag_name, all_tags, is_mutually_exclusive }) => {
8003
- return (jsxs(Flex, { flexFlow: "column", gap: 2, children: [jsx(Text, { children: parent_tag_name }), is_mutually_exclusive && (jsx(RadioCardRoot, { defaultValue: "next", variant: "surface", onValueChange: (tagIds) => {
8860
+ return (jsxs(Flex, { flexFlow: 'column', gap: 2, children: [jsx(Text, { children: parent_tag_name }), is_mutually_exclusive && (jsx(RadioCardRoot, { defaultValue: "next", variant: 'surface', onValueChange: (tagIds) => {
8004
8861
  const existedTags = Object.values(all_tags)
8005
8862
  .filter(({ id }) => {
8006
8863
  return existingTagList.some(({ tag_id }) => tag_id === id);
@@ -8012,20 +8869,20 @@ const TagViewer = ({ column, schema, prefix }) => {
8012
8869
  tagIds.value,
8013
8870
  ]);
8014
8871
  setValue(`${column}.${parent_tag_name}.old`, existedTags);
8015
- }, children: jsx(Flex, { flexFlow: "wrap", gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8872
+ }, children: jsx(Flex, { flexFlow: 'wrap', gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8016
8873
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
8017
- return (jsx(RadioCardItem, { label: tagName, value: id, flex: "0 0 0%", disabled: true }, `${tagName}-${id}`));
8874
+ return (jsx(RadioCardItem, { label: tagName, value: id, flex: '0 0 0%', disabled: true }, `${tagName}-${id}`));
8018
8875
  }
8019
- return (jsx(RadioCardItem, { label: tagName, value: id, flex: "0 0 0%", colorPalette: "blue" }, `${tagName}-${id}`));
8876
+ return (jsx(RadioCardItem, { label: tagName, value: id, flex: '0 0 0%', colorPalette: 'blue' }, `${tagName}-${id}`));
8020
8877
  }) }) })), !is_mutually_exclusive && (jsx(CheckboxGroup, { onValueChange: (tagIds) => {
8021
8878
  setValue(`${column}.${parent_tag_name}.current`, tagIds);
8022
- }, children: jsx(Flex, { flexFlow: "wrap", gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8879
+ }, children: jsx(Flex, { flexFlow: 'wrap', gap: 2, children: Object.entries(all_tags).map(([tagName, { id }]) => {
8023
8880
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
8024
- return (jsx(CheckboxCard, { label: tagName, value: id, flex: "0 0 0%", disabled: true, colorPalette: "blue" }, `${tagName}-${id}`));
8881
+ return (jsx(CheckboxCard, { label: tagName, value: id, flex: '0 0 0%', disabled: true, colorPalette: 'blue' }, `${tagName}-${id}`));
8025
8882
  }
8026
- return (jsx(CheckboxCard, { label: tagName, value: id, flex: "0 0 0%" }, `${tagName}-${id}`));
8883
+ return (jsx(CheckboxCard, { label: tagName, value: id, flex: '0 0 0%' }, `${tagName}-${id}`));
8027
8884
  }) }) }))] }, `tag-${parent_tag_name}`));
8028
- }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: (errors[`${column}`]?.message ?? "No error message") }))] }));
8885
+ }), errors[`${column}`] && (jsx(Text, { color: 'red.400', children: (errors[`${column}`]?.message ?? 'No error message') }))] }));
8029
8886
  };
8030
8887
 
8031
8888
  const TextAreaViewer = ({ column, schema, prefix, }) => {
@@ -8232,6 +9089,17 @@ const FormBody = () => {
8232
9089
 
8233
9090
  const FormTitle = () => {
8234
9091
  const { schema } = useSchemaContext();
9092
+ // Debug log when form title is missing
9093
+ if (!schema.title) {
9094
+ console.debug('[Form Title] Missing title in root schema. Add title property to schema.', {
9095
+ schema: {
9096
+ type: schema.type,
9097
+ properties: schema.properties
9098
+ ? Object.keys(schema.properties)
9099
+ : undefined,
9100
+ },
9101
+ });
9102
+ }
8235
9103
  return jsx(Heading, { children: schema.title ?? 'Form' });
8236
9104
  };
8237
9105
 
@@ -9395,4 +10263,4 @@ function DataTableServer({ columns, enableRowSelection = true, enableMultiRowSel
9395
10263
  }, children: jsx(DataTableServerContext.Provider, { value: { url: url ?? '', query }, children: children }) }));
9396
10264
  }
9397
10265
 
9398
- export { CalendarDisplay, CardHeader, DataDisplay, DataTable, DataTableServer, DatePickerInput, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, MediaLibraryBrowser, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, SelectAllRowsToggle, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, defaultRenderDisplay, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
10266
+ export { CalendarDisplay, CardHeader, DataDisplay, DataTable, DataTableServer, DatePickerContext, DatePickerInput, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, MediaLibraryBrowser, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, SelectAllRowsToggle, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, defaultRenderDisplay, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };