@bsol-oss/react-datatable5 13.0.1-beta.1 → 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,106 +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
- /**
5635
- * Load initial values for IdPicker fields into idMap
5636
- * Uses customQueryFn if available, otherwise falls back to getTableData
5637
- *
5638
- * @param params - Configuration for loading initial values
5639
- * @returns Promise with fetched data and idMap
5640
- */
5641
- const loadInitialValues = async ({ ids, foreign_key, serverUrl, setIdMap, }) => {
5642
- if (!ids || ids.length === 0) {
5643
- return { data: { data: [], count: 0 }, idMap: {} };
5644
- }
5645
- const { table, column: column_ref, customQueryFn } = foreign_key;
5646
- // Filter out IDs that are already in idMap (optional optimization)
5647
- // For now, we'll fetch all requested IDs to ensure consistency
5648
- if (customQueryFn) {
5649
- const { data, idMap: returnedIdMap } = await customQueryFn({
5650
- searching: '',
5651
- limit: ids.length,
5652
- offset: 0,
5653
- where: [
5654
- {
5655
- id: column_ref,
5656
- value: ids.length === 1 ? ids[0] : ids, // CustomQueryFn accepts string | string[]
5657
- },
5658
- ],
5659
- });
5660
- // Update idMap with returned values
5661
- if (returnedIdMap && Object.keys(returnedIdMap).length > 0) {
5662
- setIdMap((state) => {
5663
- return { ...state, ...returnedIdMap };
5664
- });
5665
- }
5666
- return { data, idMap: returnedIdMap || {} };
5667
- }
5668
- // Fallback to default getTableData
5669
- const data = await getTableData({
5670
- serverUrl,
5671
- searching: '',
5672
- in_table: table,
5673
- limit: ids.length,
5674
- offset: 0,
5675
- where: [
5676
- {
5677
- id: column_ref,
5678
- value: ids, // Always pass as array
5679
- },
5680
- ],
5681
- });
5682
- // Build idMap from fetched data
5683
- const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
5684
- return [
5685
- item[column_ref],
5686
- {
5687
- ...item,
5688
- },
5689
- ];
5690
- }));
5691
- // Update idMap state
5692
- setIdMap((state) => {
5693
- return { ...state, ...newMap };
5694
- });
5695
- return { data: data, idMap: newMap };
5696
- };
5697
5992
  const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5698
5993
  const { watch, getValues, formState: { errors }, setValue, } = useFormContext();
5699
- const { serverUrl, idMap, setIdMap, idPickerLabels, insideDialog } = useSchemaContext();
5700
- 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
+ }
5701
6001
  const { table, column: column_ref, customQueryFn, } = foreign_key;
5702
6002
  const [searchText, setSearchText] = useState('');
5703
6003
  const [debouncedSearchText, setDebouncedSearchText] = useState('');
@@ -5742,60 +6042,58 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5742
6042
  const missingIdsKey = useMemo(() => {
5743
6043
  return JSON.stringify([...missingIds].sort());
5744
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]);
5745
6052
  // Query to fetch initial values that are missing from idMap
5746
6053
  // This query runs automatically when missingIds.length > 0 and updates idMap
5747
6054
  const initialValuesQuery = useQuery({
5748
- queryKey: [`idpicker-initial`, column, missingIdsKey],
6055
+ queryKey: [`idpicker-initial`, column, missingIdsKey, idMapStateKey],
5749
6056
  queryFn: async () => {
5750
6057
  if (missingIds.length === 0) {
5751
6058
  return { data: [], count: 0 };
5752
6059
  }
5753
- // Use the reusable loadInitialValues function
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 };
6064
+ }
5754
6065
  const result = await loadInitialValues({
5755
6066
  ids: missingIds,
5756
6067
  foreign_key: foreign_key,
5757
- serverUrl,
5758
6068
  setIdMap,
5759
6069
  });
5760
6070
  return result.data;
5761
6071
  },
5762
6072
  enabled: missingIds.length > 0, // Only fetch if there are missing IDs
5763
- 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
5764
6076
  });
5765
6077
  const { isLoading: isLoadingInitialValues, isFetching: isFetchingInitialValues, } = initialValuesQuery;
5766
6078
  // Query for search results (async loading)
5767
6079
  const query = useQuery({
5768
6080
  queryKey: [`idpicker`, { column, searchText: debouncedSearchText, limit }],
5769
6081
  queryFn: async () => {
5770
- if (customQueryFn) {
5771
- const { data, idMap } = await customQueryFn({
5772
- searching: debouncedSearchText ?? '',
5773
- limit: limit,
5774
- offset: 0,
5775
- });
5776
- setIdMap((state) => {
5777
- return { ...state, ...idMap };
5778
- });
5779
- 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.`);
5780
6085
  }
5781
- const data = await getTableData({
5782
- serverUrl,
6086
+ const { data, idMap } = await customQueryFn({
5783
6087
  searching: debouncedSearchText ?? '',
5784
- in_table: table,
5785
6088
  limit: limit,
5786
6089
  offset: 0,
5787
6090
  });
5788
- const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
5789
- return [
5790
- item[column_ref],
5791
- {
5792
- ...item,
5793
- },
5794
- ];
5795
- }));
5796
- setIdMap((state) => {
5797
- return { ...state, ...newMap };
5798
- });
6091
+ // Update idMap with returned values
6092
+ if (idMap && Object.keys(idMap).length > 0) {
6093
+ setIdMap((state) => {
6094
+ return { ...state, ...idMap };
6095
+ });
6096
+ }
5799
6097
  return data;
5800
6098
  },
5801
6099
  enabled: true, // Always enabled for combobox
@@ -5827,17 +6125,51 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5827
6125
  // Depend on idMapKey which only changes when items we care about change
5828
6126
  // eslint-disable-next-line react-hooks/exhaustive-deps
5829
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
+ };
5830
6147
  // Transform data for combobox collection
5831
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)
5832
6150
  // raw item is stored for custom rendering
5833
6151
  // Also include items from idMap that match currentValue (for initial values display)
5834
6152
  const comboboxItems = useMemo(() => {
5835
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
+ };
5836
6166
  const itemsFromDataList = dataList.map((item) => {
5837
6167
  const rendered = renderFn(item);
6168
+ const label = typeof rendered === 'string' ? rendered : JSON.stringify(item); // Use string for filtering
5838
6169
  return {
5839
- label: typeof rendered === 'string' ? rendered : JSON.stringify(item), // Use string for filtering
5840
- 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),
5841
6173
  raw: item,
5842
6174
  };
5843
6175
  });
@@ -5846,25 +6178,28 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5846
6178
  const itemsFromIdMap = idMapItems
5847
6179
  .map((item) => {
5848
6180
  // Check if this item is already in itemsFromDataList
5849
- const alreadyIncluded = itemsFromDataList.some((i) => i.value === String(item[column_ref]));
6181
+ const alreadyIncluded = itemsFromDataList.some((i) => i.value === itemToValueFn(item));
5850
6182
  if (alreadyIncluded)
5851
6183
  return null;
5852
6184
  const rendered = renderFn(item);
6185
+ const label = typeof rendered === 'string' ? rendered : JSON.stringify(item);
5853
6186
  return {
5854
- label: typeof rendered === 'string' ? rendered : JSON.stringify(item),
5855
- value: String(item[column_ref]),
6187
+ label,
6188
+ displayLabel: getDisplayString(rendered, label), // String representation for input display
6189
+ value: itemToValueFn(item),
5856
6190
  raw: item,
5857
6191
  };
5858
6192
  })
5859
6193
  .filter((item) => item !== null);
5860
6194
  return [...itemsFromIdMap, ...itemsFromDataList];
5861
- }, [dataList, column_ref, renderDisplay, idMapItems]);
6195
+ }, [dataList, column_ref, renderDisplay, idMapItems, itemToValueFn]);
5862
6196
  // Use filter hook for combobox
5863
6197
  const { contains } = useFilter({ sensitivity: 'base' });
5864
6198
  // Create collection for combobox
6199
+ // itemToString uses displayLabel to show rendered display in input when selected
5865
6200
  const { collection, filter, set } = useListCollection({
5866
6201
  initialItems: comboboxItems,
5867
- itemToString: (item) => item.label,
6202
+ itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
5868
6203
  itemToValue: (item) => item.value,
5869
6204
  filter: contains,
5870
6205
  });
@@ -5919,6 +6254,10 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5919
6254
  idPickerLabels,
5920
6255
  insideDialog: insideDialog ?? false,
5921
6256
  renderDisplay,
6257
+ itemToValue: itemToValueFn,
6258
+ itemToString: itemToStringFn,
6259
+ loadInitialValues: loadInitialValues ??
6260
+ (async () => ({ data: { data: [], count: 0 }, idMap: {} })), // Fallback if not provided
5922
6261
  column_ref,
5923
6262
  errors,
5924
6263
  setValue,
@@ -5927,60 +6266,69 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5927
6266
 
5928
6267
  const IdPickerSingle = ({ column, schema, prefix, }) => {
5929
6268
  const formI18n = useFormI18n(column, prefix, schema);
5930
- const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, } = schema;
6269
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
5931
6270
  const isRequired = required?.some((columnId) => columnId === column);
5932
- 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({
5933
6272
  column,
5934
6273
  schema,
5935
6274
  prefix,
5936
6275
  isMultiple: false,
5937
6276
  });
5938
- const handleInputValueChange = (details) => {
5939
- setSearchText(details.inputValue);
5940
- };
5941
- const handleValueChange = (details) => {
5942
- setValue(colLabel, details.value[0] || '');
5943
- };
5944
- const renderDisplayFunction = renderDisplayFn || renderDisplay || defaultRenderDisplay;
5945
- return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5946
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [currentValue.length > 0 && (jsx(Flex, { mb: 2, children: (() => {
5947
- const id = currentValue[0];
5948
- const item = idMap[id];
5949
- // Show loading skeleton while fetching initial values
5950
- if (item === undefined &&
5951
- (isLoadingInitialValues || isFetchingInitialValues) &&
5952
- missingIds.includes(id)) {
5953
- return jsx(Skeleton, { height: "24px", width: "100px", borderRadius: "md" });
5954
- }
5955
- // Only show "not found" if we're not loading and item is still missing
5956
- if (item === undefined) {
5957
- return (jsx(Text, { fontSize: "sm", children: idPickerLabels?.undefined ?? 'Undefined' }));
5958
- }
5959
- return jsx(Text, { fontSize: "sm", children: renderDisplayFunction(item) });
5960
- })() })), jsxs(Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: false, closeOnSelect: true, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: insideDialog
5961
- ? { strategy: 'fixed', hideWhenDetached: true }
5962
- : 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: () => {
5963
- setValue(colLabel, '');
5964
- } })), 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 ? (
5965
6322
  // Show skeleton items to prevent UI shift
5966
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
5967
6324
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
5968
6325
  : idPickerLabels?.initialResults ??
5969
- 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplayFunction === true
5970
- ? renderDisplayFunction(item.raw)
5971
- : 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 ? (
5972
- // Show skeleton items to prevent UI shift
5973
- 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
5974
- ? idPickerLabels?.emptySearchResult ?? 'No results found'
5975
- : idPickerLabels?.initialResults ??
5976
- 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplayFunction === true
5977
- ? renderDisplayFunction(item.raw)
5978
- : 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}`))) })) }) }) }))] }) }));
5979
6327
  };
5980
6328
 
5981
6329
  const IdPickerMultiple = ({ column, schema, prefix, }) => {
5982
6330
  const formI18n = useFormI18n(column, prefix, schema);
5983
- const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, } = schema;
6331
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
5984
6332
  const isRequired = required?.some((columnId) => columnId === column);
5985
6333
  const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, isLoadingInitialValues, isFetchingInitialValues, missingIds, collection, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, errors, setValue, } = useIdPickerData({
5986
6334
  column,
@@ -5994,7 +6342,8 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
5994
6342
  const handleValueChange = (details) => {
5995
6343
  setValue(colLabel, details.value);
5996
6344
  };
5997
- const renderDisplayFunction = renderDisplayFn || renderDisplay || defaultRenderDisplay;
6345
+ // Use renderDisplay from hook (which comes from schema) or fallback to default
6346
+ const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
5998
6347
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5999
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) => {
6000
6349
  const item = idMap[id];
@@ -6019,16 +6368,12 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
6019
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
6020
6369
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
6021
6370
  : idPickerLabels?.initialResults ??
6022
- 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplayFunction === true
6023
- ? renderDisplayFunction(item.raw)
6024
- : 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 ? (
6025
6372
  // Show skeleton items to prevent UI shift
6026
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
6027
6374
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
6028
6375
  : idPickerLabels?.initialResults ??
6029
- 'Start typing to search' })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplayFunction === true
6030
- ? renderDisplayFunction(item.raw)
6031
- : 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}`))) })) }) }) }))] })] }));
6032
6377
  };
6033
6378
 
6034
6379
  const NumberInputRoot = React.forwardRef(function NumberInput$1(props, ref) {
@@ -6212,31 +6557,35 @@ RadioCard.ItemIndicator;
6212
6557
 
6213
6558
  const TagPicker = ({ column, schema, prefix }) => {
6214
6559
  const { watch, formState: { errors }, setValue, } = useFormContext();
6215
- const { serverUrl } = useSchemaContext();
6216
6560
  if (schema.properties == undefined) {
6217
- throw new Error("schema properties undefined when using DatePicker");
6561
+ throw new Error('schema properties undefined when using DatePicker');
6218
6562
  }
6219
- const { gridColumn, gridRow, in_table, object_id_column } = schema;
6563
+ const { gridColumn, gridRow, in_table, object_id_column, tagPicker } = schema;
6220
6564
  if (in_table === undefined) {
6221
- throw new Error("in_table is undefined when using TagPicker");
6565
+ throw new Error('in_table is undefined when using TagPicker');
6222
6566
  }
6223
6567
  if (object_id_column === undefined) {
6224
- 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.');
6225
6572
  }
6226
6573
  const query = useQuery({
6227
6574
  queryKey: [`tagpicker`, in_table],
6228
6575
  queryFn: async () => {
6229
- return await getTableData({
6230
- serverUrl,
6231
- in_table: "tables_tags_view",
6576
+ const result = await tagPicker.queryFn({
6577
+ in_table: 'tables_tags_view',
6232
6578
  where: [
6233
6579
  {
6234
- id: "table_name",
6580
+ id: 'table_name',
6235
6581
  value: [in_table],
6236
6582
  },
6237
6583
  ],
6238
6584
  limit: 100,
6585
+ offset: 0,
6586
+ searching: '',
6239
6587
  });
6588
+ return result.data || { data: [] };
6240
6589
  },
6241
6590
  staleTime: 10000,
6242
6591
  });
@@ -6244,17 +6593,19 @@ const TagPicker = ({ column, schema, prefix }) => {
6244
6593
  const existingTagsQuery = useQuery({
6245
6594
  queryKey: [`existing`, { in_table, object_id_column }, object_id],
6246
6595
  queryFn: async () => {
6247
- return await getTableData({
6248
- serverUrl,
6596
+ const result = await tagPicker.queryFn({
6249
6597
  in_table: in_table,
6250
6598
  where: [
6251
6599
  {
6252
6600
  id: object_id_column,
6253
- value: object_id[0],
6601
+ value: [object_id[0]],
6254
6602
  },
6255
6603
  ],
6256
6604
  limit: 100,
6605
+ offset: 0,
6606
+ searching: '',
6257
6607
  });
6608
+ return result.data || { data: [] };
6258
6609
  },
6259
6610
  enabled: object_id != undefined,
6260
6611
  staleTime: 10000,
@@ -6265,9 +6616,9 @@ const TagPicker = ({ column, schema, prefix }) => {
6265
6616
  if (!!object_id === false) {
6266
6617
  return jsx(Fragment, {});
6267
6618
  }
6268
- return (jsxs(Flex, { flexFlow: "column", gap: 4, gridColumn,
6619
+ return (jsxs(Flex, { flexFlow: 'column', gap: 4, gridColumn,
6269
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 }) => {
6270
- 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) => {
6271
6622
  const existedTags = Object.values(all_tags)
6272
6623
  .filter(({ id }) => {
6273
6624
  return existingTagList.some(({ tag_id }) => tag_id === id);
@@ -6279,20 +6630,20 @@ const TagPicker = ({ column, schema, prefix }) => {
6279
6630
  tagIds.value,
6280
6631
  ]);
6281
6632
  setValue(`${column}.${parent_tag_name}.old`, existedTags);
6282
- }, 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 }]) => {
6283
6634
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
6284
- 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}`));
6285
6636
  }
6286
- 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}`));
6287
6638
  }) }) })), !is_mutually_exclusive && (jsx(CheckboxGroup, { onValueChange: (tagIds) => {
6288
6639
  setValue(`${column}.${parent_tag_name}.current`, tagIds);
6289
- }, 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 }]) => {
6290
6641
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
6291
- 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}`));
6292
6643
  }
6293
- 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}`));
6294
6645
  }) }) }))] }, `tag-${parent_tag_name}`));
6295
- }), 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') }))] }));
6296
6647
  };
6297
6648
 
6298
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) => {
@@ -6399,11 +6750,193 @@ const TextAreaInput = ({ column, schema, prefix, }) => {
6399
6750
 
6400
6751
  dayjs.extend(utc);
6401
6752
  dayjs.extend(timezone);
6402
- const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem, onChange = () => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6403
- placeholder: 'hh:mm AM/PM',
6404
- emptyMessage: 'No time found',
6405
- }, }) => {
6406
- // 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
6407
6940
  const timeOptions = useMemo(() => {
6408
6941
  const options = [];
6409
6942
  // Get start time for comparison if provided
@@ -6414,32 +6947,25 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6414
6947
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6415
6948
  if (startDateObj.isValid() && selectedDateObj.isValid()) {
6416
6949
  startDateTime = startDateObj;
6417
- // Only filter if dates are the same
6418
6950
  shouldFilterByDate =
6419
6951
  startDateObj.format('YYYY-MM-DD') ===
6420
6952
  selectedDateObj.format('YYYY-MM-DD');
6421
6953
  }
6422
6954
  }
6423
- // Generate 12-hour format options (1-12 for hours, AM/PM)
6424
- for (let h = 1; h <= 12; h++) {
6425
- for (let m = 0; m < 60; m += 15) {
6426
- for (const mer of ['am', 'pm']) {
6427
- // Convert 12-hour to 24-hour for comparison
6428
- let hour24 = h;
6429
- if (mer === 'am' && h === 12)
6430
- hour24 = 0;
6431
- else if (mer === 'pm' && h < 12)
6432
- hour24 = h + 12;
6433
- // 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
6434
6960
  if (startDateTime && selectedDate && shouldFilterByDate) {
6435
6961
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6436
6962
  const optionDateTime = selectedDateObj
6437
- .hour(hour24)
6963
+ .hour(h)
6438
6964
  .minute(m)
6439
6965
  .second(0)
6440
6966
  .millisecond(0);
6441
6967
  if (optionDateTime.isBefore(startDateTime)) {
6442
- continue; // Skip this option as it would result in negative duration
6968
+ continue;
6443
6969
  }
6444
6970
  }
6445
6971
  // Calculate duration if startTime is provided
@@ -6447,7 +6973,7 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6447
6973
  if (startDateTime && selectedDate) {
6448
6974
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6449
6975
  const optionDateTime = selectedDateObj
6450
- .hour(hour24)
6976
+ .hour(h)
6451
6977
  .minute(m)
6452
6978
  .second(0)
6453
6979
  .millisecond(0);
@@ -6472,58 +6998,204 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6472
6998
  }
6473
6999
  }
6474
7000
  }
6475
- const hourDisplay = h.toString();
6476
- const minuteDisplay = m.toString().padStart(2, '0');
6477
- const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
7001
+ const timeDisplay = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`;
6478
7002
  options.push({
6479
7003
  label: timeDisplay,
6480
- value: `${h}:${m}:${mer}`,
7004
+ value: `${h}:${m}:0`,
6481
7005
  hour: h,
6482
7006
  minute: m,
6483
- meridiem: mer,
6484
- searchText: timeDisplay, // Use base time without duration for searching
7007
+ second: 0,
7008
+ searchText: timeDisplay,
6485
7009
  durationText,
6486
7010
  });
6487
7011
  }
6488
7012
  }
6489
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
+ }
6490
7102
  return options;
6491
- }, [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
6492
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]);
6493
7149
  const { collection, filter } = useListCollection({
6494
7150
  initialItems: timeOptions,
6495
- itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
7151
+ itemToString: itemToString,
6496
7152
  itemToValue: (item) => item.value,
6497
- filter: contains,
7153
+ filter: customTimeFilter,
6498
7154
  });
6499
7155
  // Get current value string for combobox
6500
7156
  const currentValue = useMemo(() => {
6501
- if (hour === null || minute === null || meridiem === null) {
6502
- 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}`;
6503
7168
  }
6504
- return `${hour}:${minute}:${meridiem}`;
6505
- }, [hour, minute, meridiem]);
7169
+ }, [hour, minute, second, meridiem, is24Hour]);
6506
7170
  // Calculate duration difference
6507
7171
  const durationDiff = useMemo(() => {
6508
- if (!startTime ||
6509
- !selectedDate ||
6510
- hour === null ||
6511
- minute === null ||
6512
- meridiem === null) {
7172
+ if (!startTime || !selectedDate || hour === null || minute === null) {
6513
7173
  return null;
6514
7174
  }
7175
+ if (is24Hour) {
7176
+ if (second === null)
7177
+ return null;
7178
+ }
7179
+ else {
7180
+ if (meridiem === null)
7181
+ return null;
7182
+ }
6515
7183
  const startDateObj = dayjs(startTime).tz(timezone);
6516
7184
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6517
- // Convert 12-hour to 24-hour format
7185
+ // Convert to 24-hour format
6518
7186
  let hour24 = hour;
6519
- if (meridiem === 'am' && hour === 12)
6520
- hour24 = 0;
6521
- else if (meridiem === 'pm' && hour < 12)
6522
- 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
+ }
6523
7193
  const currentDateTime = selectedDateObj
6524
7194
  .hour(hour24)
6525
7195
  .minute(minute)
6526
- .second(0)
7196
+ .second(is24Hour && second !== null && second !== undefined
7197
+ ? second
7198
+ : 0)
6527
7199
  .millisecond(0);
6528
7200
  if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6529
7201
  return null;
@@ -6549,13 +7221,28 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6549
7221
  return `+${diffText}`;
6550
7222
  }
6551
7223
  return null;
6552
- }, [hour, minute, meridiem, startTime, selectedDate, timezone]);
7224
+ }, [
7225
+ hour,
7226
+ minute,
7227
+ second,
7228
+ meridiem,
7229
+ startTime,
7230
+ selectedDate,
7231
+ timezone,
7232
+ is24Hour,
7233
+ ]);
6553
7234
  const handleClear = () => {
6554
7235
  setHour(null);
6555
7236
  setMinute(null);
6556
- setMeridiem(null);
6557
- filter(''); // Reset filter to show all options
6558
- 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('');
6559
7246
  };
6560
7247
  const handleValueChange = (details) => {
6561
7248
  if (details.value.length === 0) {
@@ -6567,71 +7254,165 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6567
7254
  if (selectedOption) {
6568
7255
  setHour(selectedOption.hour);
6569
7256
  setMinute(selectedOption.minute);
6570
- setMeridiem(selectedOption.meridiem);
6571
- filter(''); // Reset filter after selection
6572
- onChange({
6573
- hour: selectedOption.hour,
6574
- minute: selectedOption.minute,
6575
- meridiem: selectedOption.meridiem,
6576
- });
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
+ }
6577
7270
  }
6578
7271
  };
6579
7272
  // Parse input value and update state
6580
7273
  const parseAndCommitInput = (value) => {
6581
7274
  const trimmedValue = value.trim();
6582
- // Filter the collection based on input
6583
7275
  filter(trimmedValue);
6584
7276
  if (!trimmedValue) {
6585
7277
  return;
6586
7278
  }
6587
- // Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
6588
- const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
6589
- const match12Hour = trimmedValue.match(timePattern12Hour);
6590
- if (match12Hour) {
6591
- const parsedHour = parseInt(match12Hour[1], 10);
6592
- const parsedMinute = parseInt(match12Hour[2], 10);
6593
- const parsedMeridiem = match12Hour[3].toLowerCase();
6594
- // Validate ranges
6595
- if (parsedHour >= 1 &&
6596
- parsedHour <= 12 &&
6597
- parsedMinute >= 0 &&
6598
- parsedMinute <= 59) {
6599
- setHour(parsedHour);
6600
- setMinute(parsedMinute);
6601
- setMeridiem(parsedMeridiem);
6602
- onChange({
6603
- hour: parsedHour,
6604
- minute: parsedMinute,
6605
- meridiem: parsedMeridiem,
6606
- });
6607
- 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
+ }
6608
7320
  }
6609
7321
  }
6610
- // Try to parse formats like "130pm" or "130 pm" (without colon)
6611
- const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
6612
- const matchNoColon = trimmedValue.match(timePatternNoColon);
6613
- if (matchNoColon) {
6614
- const numbersOnly = matchNoColon[1];
6615
- const parsedMeridiem = matchNoColon[2].toLowerCase();
6616
- if (numbersOnly.length >= 3) {
6617
- const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
6618
- const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
6619
- // 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();
6620
7367
  if (parsedHour >= 1 &&
6621
7368
  parsedHour <= 12 &&
6622
7369
  parsedMinute >= 0 &&
6623
7370
  parsedMinute <= 59) {
6624
7371
  setHour(parsedHour);
6625
7372
  setMinute(parsedMinute);
6626
- setMeridiem(parsedMeridiem);
6627
- onChange({
6628
- hour: parsedHour,
6629
- minute: parsedMinute,
6630
- meridiem: parsedMeridiem,
6631
- });
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);
6632
7391
  return;
6633
7392
  }
6634
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
+ }
6635
7416
  }
6636
7417
  // Parse failed, select first result
6637
7418
  selectFirstResult();
@@ -6642,58 +7423,87 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6642
7423
  const firstItem = collection.items[0];
6643
7424
  setHour(firstItem.hour);
6644
7425
  setMinute(firstItem.minute);
6645
- setMeridiem(firstItem.meridiem);
6646
- filter(''); // Reset filter after selection
6647
- onChange({
6648
- hour: firstItem.hour,
6649
- minute: firstItem.minute,
6650
- meridiem: firstItem.meridiem,
6651
- });
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
+ }
6652
7439
  }
6653
7440
  };
6654
7441
  const handleInputValueChange = (details) => {
6655
- // Filter the collection based on input, but don't parse yet
7442
+ if (is24Hour) {
7443
+ setInputValue(details.inputValue);
7444
+ }
6656
7445
  filter(details.inputValue);
6657
7446
  };
6658
7447
  const handleFocus = (e) => {
6659
- // Select all text when focusing
6660
7448
  e.target.select();
6661
7449
  };
6662
7450
  const handleBlur = (e) => {
6663
- // Parse and commit the input value when losing focus
6664
- const inputValue = e.target.value;
6665
- if (inputValue) {
6666
- parseAndCommitInput(inputValue);
7451
+ const inputVal = e.target.value;
7452
+ if (is24Hour) {
7453
+ setInputValue(inputVal);
7454
+ }
7455
+ if (inputVal) {
7456
+ parseAndCommitInput(inputVal);
6667
7457
  }
6668
7458
  };
6669
7459
  const handleKeyDown = (e) => {
6670
- // Commit input on Enter key
6671
7460
  if (e.key === 'Enter') {
6672
7461
  e.preventDefault();
6673
- const inputValue = e.currentTarget.value;
6674
- if (inputValue) {
6675
- parseAndCommitInput(inputValue);
7462
+ const inputVal = e.currentTarget.value;
7463
+ if (is24Hour) {
7464
+ setInputValue(inputVal);
7465
+ }
7466
+ if (inputVal) {
7467
+ parseAndCommitInput(inputVal);
6676
7468
  }
6677
- // Blur the input
6678
7469
  e.currentTarget?.blur();
6679
7470
  }
6680
7471
  };
6681
- 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 }) }))] }) }));
6682
7473
  };
6683
7474
 
6684
7475
  dayjs.extend(timezone);
6685
7476
  const TimePicker = ({ column, schema, prefix }) => {
6686
7477
  const { watch, formState: { errors }, setValue, } = useFormContext();
6687
7478
  const { timezone, insideDialog, timePickerLabels } = useSchemaContext();
6688
- 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;
6689
7480
  const isRequired = required?.some((columnId) => columnId === column);
6690
7481
  const colLabel = `${prefix}${column}`;
6691
7482
  const formI18n = useFormI18n(column, prefix, schema);
6692
7483
  const [open, setOpen] = useState(false);
6693
7484
  const value = watch(colLabel);
6694
- const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
6695
- ? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
6696
- : '';
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
+ : '';
6697
7507
  // Parse the initial time parts from the time string (HH:mm:ssZ)
6698
7508
  const parseTime = (time) => {
6699
7509
  if (!time)
@@ -6746,884 +7556,898 @@ const TimePicker = ({ column, schema, prefix }) => {
6746
7556
  return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6747
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: () => {
6748
7558
  setOpen(true);
6749
- }, 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 }) }) }) }) }))] }) }));
6750
7560
  };
6751
7561
 
6752
7562
  dayjs.extend(utc);
6753
7563
  dayjs.extend(timezone);
6754
7564
  dayjs.extend(customParseFormat);
6755
- 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 = {
6756
7566
  monthNamesShort: [
6757
- 'Jan',
6758
- 'Feb',
6759
- 'Mar',
6760
- 'Apr',
7567
+ 'January',
7568
+ 'February',
7569
+ 'March',
7570
+ 'April',
6761
7571
  'May',
6762
- 'Jun',
6763
- 'Jul',
6764
- 'Aug',
6765
- 'Sep',
6766
- 'Oct',
6767
- 'Nov',
6768
- 'Dec',
7572
+ 'June',
7573
+ 'July',
7574
+ 'August',
7575
+ 'September',
7576
+ 'October',
7577
+ 'November',
7578
+ 'December',
6769
7579
  ],
6770
7580
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
6771
7581
  backButtonLabel: 'Back',
6772
- forwardButtonLabel: 'Next',
6773
- }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, }) {
6774
- const [open, setOpen] = useState(false);
6775
- const [inputValue, setInputValue] = useState('');
6776
- // Update input value when prop value changes
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();
7615
+ }
7616
+ if (defaultTime?.hour !== null && defaultTime?.hour !== undefined) {
7617
+ return defaultTime.hour;
7618
+ }
7619
+ return null;
7620
+ });
7621
+ const [minute, setMinute] = useState(() => {
7622
+ if (parsedValue) {
7623
+ return parsedValue.minute();
7624
+ }
7625
+ if (defaultTime?.minute !== null && defaultTime?.minute !== undefined) {
7626
+ return defaultTime.minute;
7627
+ }
7628
+ return null;
7629
+ });
7630
+ const [second, setSecond] = useState(() => {
7631
+ if (parsedValue) {
7632
+ return parsedValue.second();
7633
+ }
7634
+ if (defaultTime?.second !== null && defaultTime?.second !== undefined) {
7635
+ return defaultTime.second;
7636
+ }
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';
7643
+ }
7644
+ if (defaultTime?.meridiem !== null && defaultTime?.meridiem !== undefined) {
7645
+ return defaultTime.meridiem;
7646
+ }
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
6777
7668
  useEffect(() => {
6778
- if (value) {
6779
- const formatted = typeof value === 'string'
6780
- ? dayjs(value).tz(timezone).isValid()
6781
- ? dayjs(value).tz(timezone).format(displayFormat)
6782
- : ''
6783
- : dayjs(value).tz(timezone).format(displayFormat);
6784
- setInputValue(formatted);
7669
+ if (controlledTimezoneOffset !== undefined) {
7670
+ setInternalTimezoneOffset(controlledTimezoneOffset);
7671
+ }
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);
7679
+ }
7680
+ }
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 });
7692
+ }
7693
+ return options;
7694
+ }, []);
7695
+ // Create collection for Select
7696
+ const { collection: timezoneCollection } = useListCollection({
7697
+ initialItems: timezoneOffsetOptions,
7698
+ itemToString: (item) => item.label,
7699
+ itemToValue: (item) => item.value,
7700
+ });
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);
6785
7715
  }
6786
7716
  else {
6787
- setInputValue('');
7717
+ setDateInputValue('');
6788
7718
  }
6789
- }, [value, displayFormat, timezone]);
6790
- // Convert value to Date object for DatePicker
6791
- const selectedDate = value
6792
- ? typeof value === 'string'
6793
- ? dayjs(value).tz(timezone).isValid()
6794
- ? dayjs(value).tz(timezone).toDate()
6795
- : new Date()
6796
- : value
6797
- : new Date();
6798
- // Shared function to parse and validate input value
6799
- const parseAndValidateInput = (inputVal) => {
7719
+ }, [selectedDate, tz]);
7720
+ // Parse and validate date input
7721
+ const parseAndValidateDateInput = (inputVal) => {
6800
7722
  // If empty, clear the value
6801
7723
  if (!inputVal.trim()) {
6802
- onChange?.(undefined);
6803
- setInputValue('');
7724
+ setSelectedDate(null);
7725
+ updateDateTime(null, hour, minute, second, meridiem);
6804
7726
  return;
6805
7727
  }
6806
- // Try parsing with displayFormat first
6807
- let parsedDate = dayjs(inputVal, displayFormat, true);
6808
- // If that fails, try common date formats
7728
+ // Try parsing with common date formats
7729
+ let parsedDate = dayjs(inputVal, 'YYYY-MM-DD', true);
7730
+ // If that fails, try other common formats
6809
7731
  if (!parsedDate.isValid()) {
6810
7732
  parsedDate = dayjs(inputVal);
6811
7733
  }
6812
- // If still invalid, try parsing with dateFormat
6813
- if (!parsedDate.isValid()) {
6814
- parsedDate = dayjs(inputVal, dateFormat, true);
6815
- }
6816
7734
  // If valid, check constraints and update
6817
7735
  if (parsedDate.isValid()) {
6818
- const dateObj = parsedDate.tz(timezone).toDate();
7736
+ const dateObj = parsedDate.tz(tz).toDate();
6819
7737
  // Check min/max constraints
6820
7738
  if (minDate && dateObj < minDate) {
6821
- // Invalid: before minDate, reset to prop value
6822
- resetToPropValue();
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
+ }
6823
7747
  return;
6824
7748
  }
6825
7749
  if (maxDate && dateObj > maxDate) {
6826
- // Invalid: after maxDate, reset to prop value
6827
- resetToPropValue();
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('');
7757
+ }
6828
7758
  return;
6829
7759
  }
6830
- // Valid date - format and update
6831
- const formattedDate = parsedDate.tz(timezone).format(dateFormat);
6832
- const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
6833
- onChange?.(formattedDate);
6834
- setInputValue(formattedDisplay);
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);
6835
7766
  }
6836
7767
  else {
6837
- // Invalid date - reset to prop value
6838
- resetToPropValue();
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
+ }
6839
7776
  }
6840
7777
  };
6841
- // Helper function to reset input to prop value
6842
- const resetToPropValue = () => {
6843
- if (value) {
6844
- const formatted = typeof value === 'string'
6845
- ? dayjs(value).tz(timezone).isValid()
6846
- ? dayjs(value).tz(timezone).format(displayFormat)
6847
- : ''
6848
- : dayjs(value).tz(timezone).format(displayFormat);
6849
- setInputValue(formatted);
6850
- }
6851
- else {
6852
- setInputValue('');
6853
- }
7778
+ const handleDateInputChange = (e) => {
7779
+ setDateInputValue(e.target.value);
6854
7780
  };
6855
- const handleInputChange = (e) => {
6856
- // Only update the input value, don't parse yet
6857
- setInputValue(e.target.value);
7781
+ const handleDateInputBlur = () => {
7782
+ parseAndValidateDateInput(dateInputValue);
6858
7783
  };
6859
- const handleInputBlur = () => {
6860
- // Parse and validate when input loses focus
6861
- parseAndValidateInput(inputValue);
6862
- };
6863
- const handleKeyDown = (e) => {
6864
- // Parse and validate when Enter is pressed
7784
+ const handleDateInputKeyDown = (e) => {
6865
7785
  if (e.key === 'Enter') {
6866
7786
  e.preventDefault();
6867
- parseAndValidateInput(inputValue);
7787
+ parseAndValidateDateInput(dateInputValue);
6868
7788
  }
6869
7789
  };
6870
- const handleDateSelected = ({ date }) => {
6871
- const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
6872
- onChange?.(formattedDate);
6873
- setOpen(false);
6874
- };
6875
- const datePickerContent = (jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay }));
6876
- 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 }) }) }) }))] }));
6877
- }
6878
-
6879
- dayjs.extend(utc);
6880
- dayjs.extend(timezone);
6881
- function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
6882
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
6883
- onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6884
- placeholder: 'HH:mm:ss',
6885
- emptyMessage: 'No time found',
6886
- }, }) {
6887
- // Generate time options (every 15 minutes, seconds always 0)
6888
- const timeOptions = useMemo(() => {
6889
- const options = [];
6890
- // Get start time for comparison if provided
6891
- let startDateTime = null;
6892
- let shouldFilterByDate = false;
6893
- if (startTime && selectedDate) {
6894
- const startDateObj = dayjs(startTime).tz(timezone);
6895
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6896
- if (startDateObj.isValid() && selectedDateObj.isValid()) {
6897
- startDateTime = startDateObj;
6898
- // Only filter if dates are the same
6899
- shouldFilterByDate =
6900
- startDateObj.format('YYYY-MM-DD') ===
6901
- selectedDateObj.format('YYYY-MM-DD');
6902
- }
6903
- }
6904
- for (let h = 0; h < 24; h++) {
6905
- for (let m = 0; m < 60; m += 15) {
6906
- const timeDisplay = `${h.toString().padStart(2, '0')}:${m
6907
- .toString()
6908
- .padStart(2, '0')}:00`;
6909
- // Filter out times that would result in negative duration (only when dates are the same)
6910
- if (startDateTime && selectedDate && shouldFilterByDate) {
6911
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6912
- const optionDateTime = selectedDateObj
6913
- .hour(h)
6914
- .minute(m)
6915
- .second(0)
6916
- .millisecond(0);
6917
- if (optionDateTime.isBefore(startDateTime)) {
6918
- continue; // Skip this option as it would result in negative duration
6919
- }
6920
- }
6921
- // Calculate duration if startTime is provided
6922
- let durationText;
6923
- if (startDateTime && selectedDate) {
6924
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6925
- const optionDateTime = selectedDateObj
6926
- .hour(h)
6927
- .minute(m)
6928
- .second(0)
6929
- .millisecond(0);
6930
- if (optionDateTime.isValid() &&
6931
- optionDateTime.isAfter(startDateTime)) {
6932
- const diffMs = optionDateTime.diff(startDateTime);
6933
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6934
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6935
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6936
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6937
- let diffText = '';
6938
- if (diffHours > 0) {
6939
- diffText = `${diffHours}h ${diffMinutes}m`;
6940
- }
6941
- else if (diffMinutes > 0) {
6942
- diffText = `${diffMinutes}m ${diffSeconds}s`;
6943
- }
6944
- else {
6945
- diffText = `${diffSeconds}s`;
6946
- }
6947
- durationText = `+${diffText}`;
6948
- }
6949
- }
6950
- }
6951
- options.push({
6952
- label: timeDisplay,
6953
- value: `${h}:${m}:0`,
6954
- hour: h,
6955
- minute: m,
6956
- second: 0,
6957
- searchText: timeDisplay, // Use base time without duration for searching
6958
- durationText,
6959
- });
6960
- }
6961
- }
6962
- return options;
6963
- }, [startTime, selectedDate, timezone]);
6964
- const { contains } = useFilter({ sensitivity: 'base' });
6965
- const { collection, filter } = useListCollection({
6966
- initialItems: timeOptions,
6967
- itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
6968
- itemToValue: (item) => item.value,
6969
- filter: contains,
6970
- });
6971
- // Get current value string for combobox
6972
- const currentValue = useMemo(() => {
6973
- if (hour === null || minute === null || second === null) {
6974
- return '';
6975
- }
6976
- return `${hour}:${minute}:${second}`;
6977
- }, [hour, minute, second]);
6978
- // Calculate duration difference
6979
- const durationDiff = useMemo(() => {
6980
- if (!startTime ||
6981
- !selectedDate ||
6982
- hour === null ||
6983
- minute === null ||
6984
- second === null) {
6985
- return null;
6986
- }
6987
- const startDateObj = dayjs(startTime).tz(timezone);
6988
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6989
- const currentDateTime = selectedDateObj
6990
- .hour(hour)
6991
- .minute(minute)
6992
- .second(second ?? 0)
6993
- .millisecond(0);
6994
- if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6995
- return null;
6996
- }
6997
- const diffMs = currentDateTime.diff(startDateObj);
6998
- if (diffMs < 0) {
6999
- return null;
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;
7000
7802
  }
7001
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
7002
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
7003
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
7004
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
7005
- let diffText = '';
7006
- if (diffHours > 0) {
7007
- diffText = `${diffHours}h ${diffMinutes}m`;
7008
- }
7009
- else if (diffMinutes > 0) {
7010
- diffText = `${diffMinutes}m ${diffSeconds}s`;
7011
- }
7012
- else {
7013
- diffText = `${diffSeconds}s`;
7014
- }
7015
- return `+${diffText}`;
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;
7016
7808
  }
7017
- return null;
7018
- }, [hour, minute, second, startTime, selectedDate, timezone]);
7019
- const handleClear = () => {
7020
- setHour(null);
7021
- setMinute(null);
7022
- setSecond(null);
7023
- filter(''); // Reset filter to show all options
7024
- onChange({ hour: null, minute: null, second: null });
7809
+ return true;
7025
7810
  };
7026
- const handleValueChange = (details) => {
7027
- if (details.value.length === 0) {
7028
- handleClear();
7029
- return;
7030
- }
7031
- const selectedValue = details.value[0];
7032
- const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
7033
- if (selectedOption) {
7034
- setHour(selectedOption.hour);
7035
- setMinute(selectedOption.minute);
7036
- setSecond(selectedOption.second);
7037
- filter(''); // Reset filter after selection
7038
- onChange({
7039
- hour: selectedOption.hour,
7040
- minute: selectedOption.minute,
7041
- second: selectedOption.second,
7042
- });
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);
7043
7818
  }
7044
7819
  };
7045
- // Parse input value and update state
7046
- const parseAndCommitInput = (value) => {
7047
- const trimmedValue = value.trim();
7048
- // Filter the collection based on input
7049
- filter(trimmedValue);
7050
- if (!trimmedValue) {
7051
- return;
7052
- }
7053
- // Parse HH:mm:ss or HH:mm format
7054
- const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
7055
- const match = trimmedValue.match(timePattern);
7056
- if (match) {
7057
- const parsedHour = parseInt(match[1], 10);
7058
- const parsedMinute = parseInt(match[2], 10);
7059
- const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
7060
- // Validate ranges
7061
- if (parsedHour >= 0 &&
7062
- parsedHour <= 23 &&
7063
- parsedMinute >= 0 &&
7064
- parsedMinute <= 59 &&
7065
- parsedSecond >= 0 &&
7066
- parsedSecond <= 59) {
7067
- setHour(parsedHour);
7068
- setMinute(parsedMinute);
7069
- setSecond(parsedSecond);
7070
- onChange({
7071
- hour: parsedHour,
7072
- minute: parsedMinute,
7073
- second: parsedSecond,
7074
- });
7075
- return;
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')}`;
7076
7835
  }
7836
+ return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
7077
7837
  }
7078
7838
  else {
7079
- // Try to parse formats like "123045" (HHmmss) or "1230" (HHmm)
7080
- const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
7081
- if (numbersOnly.length >= 4) {
7082
- const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
7083
- const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
7084
- const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
7085
- // Validate ranges
7086
- if (parsedHour >= 0 &&
7087
- parsedHour <= 23 &&
7088
- parsedMinute >= 0 &&
7089
- parsedMinute <= 59 &&
7090
- parsedSecond >= 0 &&
7091
- parsedSecond <= 59) {
7092
- setHour(parsedHour);
7093
- setMinute(parsedMinute);
7094
- setSecond(parsedSecond);
7095
- onChange({
7096
- hour: parsedHour,
7097
- minute: parsedMinute,
7098
- second: parsedSecond,
7099
- });
7100
- return;
7101
- }
7102
- }
7103
- }
7104
- // Parse failed, select first result
7105
- selectFirstResult();
7106
- };
7107
- // Select first result from filtered collection
7108
- const selectFirstResult = () => {
7109
- if (collection.items.length > 0) {
7110
- const firstItem = collection.items[0];
7111
- setHour(firstItem.hour);
7112
- setMinute(firstItem.minute);
7113
- setSecond(firstItem.second);
7114
- filter(''); // Reset filter after selection
7115
- onChange({
7116
- hour: firstItem.hour,
7117
- minute: firstItem.minute,
7118
- second: firstItem.second,
7119
- });
7120
- }
7121
- };
7122
- const handleInputValueChange = (details) => {
7123
- // Filter the collection based on input, but don't parse yet
7124
- filter(details.inputValue);
7125
- };
7126
- const handleFocus = (e) => {
7127
- // Select all text when focusing
7128
- e.target.select();
7129
- };
7130
- const handleBlur = (e) => {
7131
- // Parse and commit the input value when losing focus
7132
- const inputValue = e.target.value;
7133
- if (inputValue) {
7134
- parseAndCommitInput(inputValue);
7135
- }
7136
- };
7137
- const handleKeyDown = (e) => {
7138
- // Commit input on Enter key
7139
- if (e.key === 'Enter') {
7140
- e.preventDefault();
7141
- const inputValue = e.currentTarget.value;
7142
- if (inputValue) {
7143
- parseAndCommitInput(inputValue);
7144
- }
7145
- // Blur the input
7146
- e.currentTarget?.blur();
7147
- }
7148
- };
7149
- 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, {}) }) })] }) }));
7150
- }
7151
-
7152
- dayjs.extend(utc);
7153
- dayjs.extend(timezone);
7154
- function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
7155
- monthNamesShort: [
7156
- 'Jan',
7157
- 'Feb',
7158
- 'Mar',
7159
- 'Apr',
7160
- 'May',
7161
- 'Jun',
7162
- 'Jul',
7163
- 'Aug',
7164
- 'Sep',
7165
- 'Oct',
7166
- 'Nov',
7167
- 'Dec',
7168
- ],
7169
- weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
7170
- backButtonLabel: 'Back',
7171
- forwardButtonLabel: 'Next',
7172
- }, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, }) {
7173
- console.log('[DateTimePicker] Component initialized with props:', {
7174
- value,
7175
- format,
7176
- showSeconds,
7177
- timezone,
7178
- startTime,
7179
- minDate,
7180
- maxDate,
7181
- });
7182
- // Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
7183
- const getDateString = useCallback((val) => {
7184
- if (!val)
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)
7185
7850
  return '';
7186
- const dateObj = dayjs(val).tz(timezone);
7187
- return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
7188
- }, [timezone]);
7189
- const [selectedDate, setSelectedDate] = useState(getDateString(value));
7190
- // Helper to get time values from value prop with timezone
7191
- const getTimeFromValue = useCallback((val) => {
7192
- console.log('[DateTimePicker] getTimeFromValue called:', {
7193
- val,
7194
- timezone,
7195
- showSeconds,
7196
- });
7197
- if (!val) {
7198
- console.log('[DateTimePicker] No value provided, returning nulls');
7199
- return {
7200
- hour12: null,
7201
- minute: null,
7202
- meridiem: null,
7203
- hour24: null,
7204
- second: null,
7205
- };
7206
- }
7207
- const dateObj = dayjs(val).tz(timezone);
7208
- console.log('[DateTimePicker] Parsed date object:', {
7209
- original: val,
7210
- timezone,
7211
- isValid: dateObj.isValid(),
7212
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7213
- hour24: dateObj.hour(),
7214
- minute: dateObj.minute(),
7215
- second: dateObj.second(),
7216
- });
7217
- if (!dateObj.isValid()) {
7218
- console.log('[DateTimePicker] Invalid date object, returning nulls');
7219
- return {
7220
- hour12: null,
7221
- minute: null,
7222
- meridiem: null,
7223
- hour24: null,
7224
- second: null,
7225
- };
7226
- }
7227
- const hour24Value = dateObj.hour();
7228
- const hour12Value = hour24Value % 12 || 12;
7229
- const minuteValue = dateObj.minute();
7230
- const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
7231
- const secondValue = showSeconds ? dateObj.second() : null;
7232
- const result = {
7233
- hour12: hour12Value,
7234
- minute: minuteValue,
7235
- meridiem: meridiemValue,
7236
- hour24: hour24Value,
7237
- second: secondValue,
7238
- };
7239
- console.log('[DateTimePicker] Extracted time values:', result);
7240
- return result;
7241
- }, [timezone, showSeconds]);
7242
- const initialTime = getTimeFromValue(value);
7243
- console.log('[DateTimePicker] Initial time from value:', {
7244
- value,
7245
- initialTime,
7246
- });
7247
- // Time state for 12-hour format
7248
- const [hour12, setHour12] = useState(initialTime.hour12);
7249
- const [minute, setMinute] = useState(initialTime.minute);
7250
- const [meridiem, setMeridiem] = useState(initialTime.meridiem);
7251
- // Time state for 24-hour format
7252
- const [hour24, setHour24] = useState(initialTime.hour24);
7253
- const [second, setSecond] = useState(initialTime.second);
7254
- // Sync selectedDate and time states when value prop changes
7851
+ // Show offset as is (e.g., "+08:00")
7852
+ return timezoneOffset;
7853
+ }, [timezoneOffset, showTimezoneSelector]);
7854
+ // Update selectedDate when value changes externally
7255
7855
  useEffect(() => {
7256
- console.log('[DateTimePicker] useEffect triggered - value changed:', {
7257
- value,
7258
- timezone,
7259
- format,
7260
- });
7261
- // If value is null, undefined, or invalid, clear all fields
7262
- if (!value || value === null || value === undefined) {
7263
- console.log('[DateTimePicker] Value is null/undefined, clearing all fields');
7264
- setSelectedDate('');
7265
- setHour12(null);
7266
- setMinute(null);
7267
- setMeridiem(null);
7268
- setHour24(null);
7269
- setSecond(null);
7270
- return;
7271
- }
7272
- // Check if value is valid
7273
- const dateObj = dayjs(value).tz(timezone);
7274
- if (!dateObj.isValid()) {
7275
- console.log('[DateTimePicker] Invalid value, clearing all fields');
7276
- setSelectedDate('');
7277
- setHour12(null);
7278
- setMinute(null);
7279
- setMeridiem(null);
7280
- setHour24(null);
7281
- setSecond(null);
7282
- return;
7283
- }
7284
- const dateString = getDateString(value);
7285
- console.log('[DateTimePicker] Setting selectedDate:', dateString);
7286
- setSelectedDate(dateString);
7287
- const timeData = getTimeFromValue(value);
7288
- console.log('[DateTimePicker] Updating time states:', {
7289
- timeData,
7290
- });
7291
- setHour12(timeData.hour12);
7292
- setMinute(timeData.minute);
7293
- setMeridiem(timeData.meridiem);
7294
- setHour24(timeData.hour24);
7295
- setSecond(timeData.second);
7296
- }, [value, getTimeFromValue, getDateString, timezone]);
7297
- const handleDateChange = (date) => {
7298
- console.log('[DateTimePicker] handleDateChange called:', {
7299
- date,
7300
- timezone,
7301
- showSeconds,
7302
- currentTimeStates: { hour12, minute, meridiem, hour24, second },
7303
- });
7304
- // If date is empty or invalid, clear all fields
7305
- if (!date || date === '') {
7306
- console.log('[DateTimePicker] Empty date, clearing all fields');
7307
- setSelectedDate('');
7308
- setHour12(null);
7309
- setMinute(null);
7310
- setMeridiem(null);
7311
- setHour24(null);
7312
- setSecond(null);
7313
- onChange?.(undefined);
7314
- return;
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
+ }
7315
7865
  }
7316
- setSelectedDate(date);
7317
- // Parse the date string (YYYY-MM-DD) in the specified timezone
7318
- const dateObj = dayjs.tz(date, timezone);
7319
- console.log('[DateTimePicker] Parsed date object:', {
7320
- date,
7321
- timezone,
7322
- isValid: dateObj.isValid(),
7323
- isoString: dateObj.toISOString(),
7324
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7325
- });
7326
- if (!dateObj.isValid()) {
7327
- console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
7328
- setSelectedDate('');
7329
- setHour12(null);
7330
- setMinute(null);
7331
- setMeridiem(null);
7332
- setHour24(null);
7333
- setSecond(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) {
7334
7870
  onChange?.(undefined);
7335
7871
  return;
7336
7872
  }
7337
- // When showSeconds is false, ignore seconds from the date
7338
- if (!showSeconds) {
7339
- const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
7340
- console.log('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
7341
- updateDateTime(dateWithoutSeconds);
7342
- }
7343
- else {
7344
- const dateWithSeconds = dateObj.toISOString();
7345
- console.log('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
7346
- updateDateTime(dateWithSeconds);
7347
- }
7348
- };
7349
- const handleTimeChange = (timeData) => {
7350
- console.log('[DateTimePicker] handleTimeChange called:', {
7351
- timeData,
7352
- format,
7353
- selectedDate,
7354
- timezone,
7355
- });
7356
- if (format === 'iso-date-time') {
7357
- const data = timeData;
7358
- console.log('[DateTimePicker] ISO format - setting 24-hour time:', data);
7359
- setHour24(data.hour);
7360
- setMinute(data.minute);
7361
- if (showSeconds) {
7362
- setSecond(data.second ?? null);
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
+ }
7363
7896
  }
7364
7897
  else {
7365
- // Ignore seconds - always set to null when showSeconds is false
7366
- 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
+ }
7367
7905
  }
7368
7906
  }
7369
- else {
7370
- const data = timeData;
7371
- console.log('[DateTimePicker] 12-hour format - setting time:', data);
7372
- setHour12(data.hour);
7373
- setMinute(data.minute);
7374
- setMeridiem(data.meridiem);
7375
- }
7376
- // Use selectedDate if valid, otherwise clear all fields
7377
- if (!selectedDate || !dayjs(selectedDate).isValid()) {
7378
- console.log('[DateTimePicker] No valid selectedDate, clearing all fields');
7379
- setSelectedDate('');
7380
- setHour12(null);
7381
- setMinute(null);
7382
- setMeridiem(null);
7383
- setHour24(null);
7384
- 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()) {
7385
7936
  onChange?.(undefined);
7386
7937
  return;
7387
7938
  }
7388
- const dateObj = dayjs(selectedDate).tz(timezone);
7389
- if (dateObj.isValid()) {
7390
- 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'));
7391
7942
  }
7392
7943
  else {
7393
- console.warn('[DateTimePicker] Invalid date object in handleTimeChange, clearing fields');
7394
- setSelectedDate('');
7395
- setHour12(null);
7396
- setMinute(null);
7397
- setMeridiem(null);
7398
- setHour24(null);
7399
- setSecond(null);
7400
- onChange?.(undefined);
7944
+ // date-time format with timezone
7945
+ onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ssZ'));
7401
7946
  }
7402
7947
  };
7403
- const updateDateTime = (date, timeData) => {
7404
- console.log('[DateTimePicker] updateDateTime called:', {
7405
- date,
7406
- timeData,
7407
- format,
7408
- currentStates: { hour12, minute, meridiem, hour24, second },
7409
- });
7410
- if (!date || date === null || date === undefined) {
7411
- console.log('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
7412
- setSelectedDate('');
7413
- setHour12(null);
7414
- setMinute(null);
7415
- setMeridiem(null);
7416
- setHour24(null);
7417
- setSecond(null);
7418
- onChange?.(undefined);
7419
- 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);
7420
7959
  }
7421
- // use dayjs to convert the date to the timezone
7422
- const dateObj = dayjs(date).tz(timezone);
7423
- if (!dateObj.isValid()) {
7424
- console.warn('[DateTimePicker] Invalid date object in updateDateTime, clearing fields:', date);
7425
- setSelectedDate('');
7426
- setHour12(null);
7427
- setMinute(null);
7428
- setMeridiem(null);
7429
- setHour24(null);
7430
- setSecond(null);
7431
- onChange?.(undefined);
7432
- return;
7960
+ else {
7961
+ setMeridiem(newMeridiem);
7433
7962
  }
7434
- const newDate = dateObj.toDate();
7435
- if (format === 'iso-date-time') {
7436
- const data = timeData;
7437
- // Use timeData values if provided, otherwise fall back to current state
7438
- // But if timeData is explicitly provided with nulls, we need to check if all are null
7439
- const h = data !== undefined ? data.hour : hour24;
7440
- const m = data !== undefined ? data.minute : minute;
7441
- // Always ignore seconds when showSeconds is false - set to 0
7442
- const s = showSeconds
7443
- ? data !== undefined
7444
- ? data.second ?? null
7445
- : second ?? 0
7446
- : 0;
7447
- // If all time values are null, clear the value
7448
- if (h === null && m === null && (showSeconds ? s === null : true)) {
7449
- console.log('[DateTimePicker] All time values are null, clearing value');
7450
- onChange?.(undefined);
7451
- 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
+ }
7452
8083
  }
7453
- console.log('[DateTimePicker] ISO format - setting time on date:', {
7454
- h,
7455
- m,
7456
- s,
7457
- showSeconds,
7458
- });
7459
- if (h !== null)
7460
- newDate.setHours(h);
7461
- if (m !== null)
7462
- newDate.setMinutes(m);
7463
- newDate.setSeconds(s ?? 0);
7464
8084
  }
7465
8085
  else {
7466
- const data = timeData;
7467
- console.log('[DateTimePicker] Processing 12-hour format:', {
7468
- 'data !== undefined': data !== undefined,
7469
- 'data?.hour': data?.hour,
7470
- 'data?.minute': data?.minute,
7471
- 'data?.meridiem': data?.meridiem,
7472
- 'current hour12': hour12,
7473
- 'current minute': minute,
7474
- 'current meridiem': meridiem,
7475
- });
7476
- // Use timeData values if provided, otherwise fall back to current state
7477
- const h = data !== undefined ? data.hour : hour12;
7478
- const m = data !== undefined ? data.minute : minute;
7479
- const mer = data !== undefined ? data.meridiem : meridiem;
7480
- console.log('[DateTimePicker] Resolved time values:', { h, m, mer });
7481
- // If all time values are null, clear the value
7482
- if (h === null && m === null && mer === null) {
7483
- console.log('[DateTimePicker] All time values are null, clearing value');
7484
- onChange?.(undefined);
7485
- 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
+ }
7486
8152
  }
7487
- console.log('[DateTimePicker] 12-hour format - converting time:', {
7488
- h,
7489
- m,
7490
- 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;
7491
8171
  });
7492
- if (h !== null && mer !== null) {
7493
- let hour24 = h;
7494
- if (mer === 'am' && h === 12)
7495
- hour24 = 0;
7496
- else if (mer === 'pm' && h < 12)
7497
- hour24 = h + 12;
7498
- console.log('[DateTimePicker] Converted to 24-hour:', {
7499
- h,
7500
- mer,
7501
- hour24,
7502
- });
7503
- 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;
7504
8189
  }
7505
- else {
7506
- console.log('[DateTimePicker] Skipping hour update - h or mer is null:', {
7507
- h,
7508
- mer,
7509
- });
8190
+ const lowerItemText = itemText.toLowerCase();
8191
+ const lowerFilterText = filterText.toLowerCase();
8192
+ if (lowerItemText.includes(lowerFilterText)) {
8193
+ return true;
7510
8194
  }
7511
- if (m !== null) {
7512
- newDate.setMinutes(m);
8195
+ const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
8196
+ if (!item || !('meridiem' in item)) {
8197
+ return false;
7513
8198
  }
7514
- else {
7515
- 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 '';
7516
8228
  }
7517
- newDate.setSeconds(0);
7518
- }
7519
- const finalISO = dayjs(newDate).tz(timezone).toISOString();
7520
- console.log('[DateTimePicker] Final ISO string to emit:', {
7521
- newDate: newDate.toISOString(),
7522
- timezone,
7523
- finalISO,
7524
- });
7525
- onChange?.(finalISO);
7526
- };
7527
- const handleClear = () => {
7528
- setSelectedDate('');
7529
- setHour12(null);
7530
- setHour24(null);
7531
- setMinute(null);
7532
- setSecond(null);
7533
- setMeridiem(null);
7534
- onChange?.(undefined);
7535
- };
7536
- const isISO = format === 'iso-date-time';
7537
- // Normalize startTime to ignore milliseconds
7538
- const normalizedStartTime = startTime
7539
- ? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
7540
- : undefined;
7541
- // Determine minDate: prioritize explicit minDate prop, then fall back to startTime
7542
- const effectiveMinDate = minDate
7543
- ? minDate
7544
- : normalizedStartTime && dayjs(normalizedStartTime).tz(timezone).isValid()
7545
- ? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
7546
- : undefined;
7547
- // Log current state before render
7548
- useEffect(() => {
7549
- console.log('[DateTimePicker] Current state before render:', {
7550
- isISO,
7551
- hour12,
7552
- minute,
7553
- meridiem,
7554
- hour24,
7555
- second,
7556
- selectedDate,
7557
- normalizedStartTime,
7558
- timezone,
7559
- });
7560
- }, [
7561
- isISO,
7562
- hour12,
7563
- minute,
7564
- meridiem,
7565
- hour24,
7566
- second,
7567
- selectedDate,
7568
- normalizedStartTime,
7569
- timezone,
7570
- ]);
7571
- // Compute display text from current state
7572
- const displayText = useMemo(() => {
7573
- if (!selectedDate)
7574
- return null;
7575
- const dateObj = dayjs.tz(selectedDate, timezone);
7576
- if (!dateObj.isValid())
7577
- return null;
7578
- if (isISO) {
7579
- // For ISO format, use hour24, minute, second
7580
- if (hour24 === null || minute === null)
7581
- return null;
7582
- const dateTimeObj = dateObj
7583
- .hour(hour24)
7584
- .minute(minute)
7585
- .second(second ?? 0);
7586
- 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}`;
7587
8231
  }
7588
8232
  else {
7589
- // For 12-hour format, use hour12, minute, meridiem
7590
- if (hour12 === null || minute === null || meridiem === null)
7591
- return null;
7592
- // Convert to 24-hour format for dayjs
7593
- let hour24Value = hour12;
7594
- if (meridiem === 'am' && hour12 === 12)
7595
- hour24Value = 0;
7596
- else if (meridiem === 'pm' && hour12 < 12)
7597
- hour24Value = hour12 + 12;
7598
- const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
7599
- 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
+ }
7600
8278
  }
7601
- }, [
7602
- selectedDate,
7603
- isISO,
7604
- hour12,
7605
- minute,
7606
- meridiem,
7607
- hour24,
7608
- second,
7609
- showSeconds,
7610
- timezone,
7611
- ]);
7612
- const timezoneOffset = useMemo(() => {
7613
- if (!selectedDate)
7614
- return null;
7615
- const dateObj = dayjs.tz(selectedDate, timezone);
7616
- return dateObj.isValid() ? dateObj.format('Z') : null;
7617
- }, [selectedDate, timezone]);
7618
- return (jsxs(Flex, { direction: "column", gap: 4, children: [jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
7619
- if (date) {
7620
- 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';
7621
8327
  }
7622
8328
  else {
7623
- setSelectedDate('');
7624
- 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);
7625
8393
  }
7626
- }, 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))) }) })] }) }) }) }) }))] }))] }));
7627
8451
  }
7628
8452
 
7629
8453
  dayjs.extend(utc);
@@ -7634,14 +8458,15 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
7634
8458
  const formI18n = useFormI18n(column, prefix, schema);
7635
8459
  const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD HH:mm:ss',
7636
8460
  // with timezone
7637
- dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', } = schema;
8461
+ dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', dateTimePicker, } = schema;
7638
8462
  const isRequired = required?.some((columnId) => columnId === column);
7639
8463
  const colLabel = formI18n.colLabel;
7640
- const [open, setOpen] = useState(false);
8464
+ useState(false);
7641
8465
  const selectedDate = watch(colLabel);
7642
- const displayDate = selectedDate && dayjs(selectedDate).tz(timezone).isValid()
8466
+ selectedDate && dayjs(selectedDate).tz(timezone).isValid()
7643
8467
  ? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
7644
8468
  : '';
8469
+ // Set default date on mount if no value exists
7645
8470
  const dateTimePickerLabelsConfig = {
7646
8471
  monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
7647
8472
  'January',
@@ -7681,11 +8506,9 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
7681
8506
  else {
7682
8507
  setValue(colLabel, undefined);
7683
8508
  }
7684
- }, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels }));
8509
+ }, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels, showQuickActions: dateTimePicker?.showQuickActions ?? false, quickActionLabels: dateTimePicker?.quickActionLabels, showTimezoneSelector: dateTimePicker?.showTimezoneSelector ?? false }));
7685
8510
  return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
7686
- 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: () => {
7687
- setOpen(true);
7688
- }, 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 }));
7689
8512
  };
7690
8513
 
7691
8514
  const SchemaRenderer = ({ schema, prefix, column, }) => {
@@ -7806,7 +8629,7 @@ const BooleanViewer = ({ schema, column, prefix, }) => {
7806
8629
  const value = watch(colLabel);
7807
8630
  const formI18n = useFormI18n(column, prefix, schema);
7808
8631
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
7809
- 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() }))] }));
7810
8633
  };
7811
8634
 
7812
8635
  const CustomViewer = ({ column, schema, prefix }) => {
@@ -7838,23 +8661,22 @@ const DateViewer = ({ column, schema, prefix }) => {
7838
8661
 
7839
8662
  const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
7840
8663
  const { watch, formState: { errors }, } = useFormContext();
7841
- const formI18n = useFormI18n(column, prefix);
8664
+ const formI18n = useFormI18n(column, prefix, schema);
7842
8665
  const { required } = schema;
7843
8666
  const isRequired = required?.some((columnId) => columnId === column);
7844
- const { gridColumn = "span 12", gridRow = "span 1", renderDisplay } = schema;
8667
+ const { gridColumn = 'span 12', gridRow = 'span 1', renderDisplay } = schema;
7845
8668
  const colLabel = formI18n.colLabel;
7846
8669
  const watchEnum = watch(colLabel);
7847
8670
  const watchEnums = (watch(colLabel) ?? []);
7848
- return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: "stretch", gridColumn,
7849
- 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) => {
7850
8674
  const item = enumValue;
7851
8675
  if (item === undefined) {
7852
8676
  return jsx(Fragment, { children: "undefined" });
7853
8677
  }
7854
- return (jsx(Tag, { size: "lg", children: !!renderDisplay === true
7855
- ? renderDisplay(item)
7856
- : formI18n.t(item) }, item));
7857
- }) })), !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() }))] }));
7858
8680
  };
7859
8681
 
7860
8682
  const FileViewer = ({ column, schema, prefix }) => {
@@ -7974,31 +8796,35 @@ const StringViewer = ({ column, schema, prefix, }) => {
7974
8796
 
7975
8797
  const TagViewer = ({ column, schema, prefix }) => {
7976
8798
  const { watch, formState: { errors }, setValue, } = useFormContext();
7977
- const { serverUrl } = useSchemaContext();
7978
8799
  if (schema.properties == undefined) {
7979
- throw new Error("schema properties undefined when using DatePicker");
8800
+ throw new Error('schema properties undefined when using DatePicker');
7980
8801
  }
7981
- const { gridColumn, gridRow, in_table, object_id_column } = schema;
8802
+ const { gridColumn, gridRow, in_table, object_id_column, tagPicker } = schema;
7982
8803
  if (in_table === undefined) {
7983
- throw new Error("in_table is undefined when using TagPicker");
8804
+ throw new Error('in_table is undefined when using TagPicker');
7984
8805
  }
7985
8806
  if (object_id_column === undefined) {
7986
- 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.');
7987
8811
  }
7988
8812
  const query = useQuery({
7989
8813
  queryKey: [`tagpicker`, in_table],
7990
8814
  queryFn: async () => {
7991
- return await getTableData({
7992
- serverUrl,
7993
- in_table: "tables_tags_view",
8815
+ const result = await tagPicker.queryFn({
8816
+ in_table: 'tables_tags_view',
7994
8817
  where: [
7995
8818
  {
7996
- id: "table_name",
8819
+ id: 'table_name',
7997
8820
  value: [in_table],
7998
8821
  },
7999
8822
  ],
8000
8823
  limit: 100,
8824
+ offset: 0,
8825
+ searching: '',
8001
8826
  });
8827
+ return result.data || { data: [] };
8002
8828
  },
8003
8829
  staleTime: 10000,
8004
8830
  });
@@ -8006,17 +8832,19 @@ const TagViewer = ({ column, schema, prefix }) => {
8006
8832
  const existingTagsQuery = useQuery({
8007
8833
  queryKey: [`existing`, { in_table, object_id_column }, object_id],
8008
8834
  queryFn: async () => {
8009
- return await getTableData({
8010
- serverUrl,
8835
+ const result = await tagPicker.queryFn({
8011
8836
  in_table: in_table,
8012
8837
  where: [
8013
8838
  {
8014
8839
  id: object_id_column,
8015
- value: object_id[0],
8840
+ value: [object_id[0]],
8016
8841
  },
8017
8842
  ],
8018
8843
  limit: 100,
8844
+ offset: 0,
8845
+ searching: '',
8019
8846
  });
8847
+ return result.data || { data: [] };
8020
8848
  },
8021
8849
  enabled: object_id != undefined,
8022
8850
  staleTime: 10000,
@@ -8027,9 +8855,9 @@ const TagViewer = ({ column, schema, prefix }) => {
8027
8855
  if (!!object_id === false) {
8028
8856
  return jsx(Fragment, {});
8029
8857
  }
8030
- return (jsxs(Flex, { flexFlow: "column", gap: 4, gridColumn,
8858
+ return (jsxs(Flex, { flexFlow: 'column', gap: 4, gridColumn,
8031
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 }) => {
8032
- 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) => {
8033
8861
  const existedTags = Object.values(all_tags)
8034
8862
  .filter(({ id }) => {
8035
8863
  return existingTagList.some(({ tag_id }) => tag_id === id);
@@ -8041,20 +8869,20 @@ const TagViewer = ({ column, schema, prefix }) => {
8041
8869
  tagIds.value,
8042
8870
  ]);
8043
8871
  setValue(`${column}.${parent_tag_name}.old`, existedTags);
8044
- }, 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 }]) => {
8045
8873
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
8046
- 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}`));
8047
8875
  }
8048
- 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}`));
8049
8877
  }) }) })), !is_mutually_exclusive && (jsx(CheckboxGroup, { onValueChange: (tagIds) => {
8050
8878
  setValue(`${column}.${parent_tag_name}.current`, tagIds);
8051
- }, 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 }]) => {
8052
8880
  if (existingTagList.some(({ tag_id }) => tag_id === id)) {
8053
- 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}`));
8054
8882
  }
8055
- 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}`));
8056
8884
  }) }) }))] }, `tag-${parent_tag_name}`));
8057
- }), 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') }))] }));
8058
8886
  };
8059
8887
 
8060
8888
  const TextAreaViewer = ({ column, schema, prefix, }) => {
@@ -8261,6 +9089,17 @@ const FormBody = () => {
8261
9089
 
8262
9090
  const FormTitle = () => {
8263
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
+ }
8264
9103
  return jsx(Heading, { children: schema.title ?? 'Form' });
8265
9104
  };
8266
9105
 
@@ -9424,4 +10263,4 @@ function DataTableServer({ columns, enableRowSelection = true, enableMultiRowSel
9424
10263
  }, children: jsx(DataTableServerContext.Provider, { value: { url: url ?? '', query }, children: children }) }));
9425
10264
  }
9426
10265
 
9427
- 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 };