@bsol-oss/react-datatable5 13.0.1-beta.7 → 13.0.1-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -29,9 +29,9 @@ var reactHookForm = require('react-hook-form');
29
29
  var Ajv = require('ajv');
30
30
  var addFormats = require('ajv-formats');
31
31
  var dayjs = require('dayjs');
32
- var utc = require('dayjs/plugin/utc');
33
- var timezone = require('dayjs/plugin/timezone');
34
32
  var customParseFormat = require('dayjs/plugin/customParseFormat');
33
+ var timezone = require('dayjs/plugin/timezone');
34
+ var utc = require('dayjs/plugin/utc');
35
35
  var ti = require('react-icons/ti');
36
36
  var matchSorterUtils = require('@tanstack/match-sorter-utils');
37
37
 
@@ -4680,7 +4680,7 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
4680
4680
  }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, showHelperButtons = true, }) {
4681
4681
  const [open, setOpen] = React.useState(false);
4682
4682
  const [inputValue, setInputValue] = React.useState('');
4683
- // Update input value when prop value changes
4683
+ // Sync inputValue with value prop changes
4684
4684
  React.useEffect(() => {
4685
4685
  if (value) {
4686
4686
  const formatted = typeof value === 'string'
@@ -4693,7 +4693,7 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
4693
4693
  else {
4694
4694
  setInputValue('');
4695
4695
  }
4696
- }, [value, displayFormat, timezone]);
4696
+ }, [value, timezone, displayFormat]);
4697
4697
  // Convert value to Date object for DatePicker
4698
4698
  const selectedDate = value
4699
4699
  ? typeof value === 'string'
@@ -4775,7 +4775,14 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
4775
4775
  }
4776
4776
  };
4777
4777
  const handleDateSelected = ({ date }) => {
4778
+ console.debug('[DatePickerInput] handleDateSelected called:', {
4779
+ date: date.toISOString(),
4780
+ timezone,
4781
+ dateFormat,
4782
+ formattedDate: dayjs(date).tz(timezone).format(dateFormat),
4783
+ });
4778
4784
  const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
4785
+ console.debug('[DatePickerInput] Calling onChange with formatted date:', formattedDate);
4779
4786
  onChange?.(formattedDate);
4780
4787
  setOpen(false);
4781
4788
  };
@@ -5089,7 +5096,7 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
5089
5096
  const watchEnum = watch(colLabel);
5090
5097
  const watchEnums = (watch(colLabel) ?? []);
5091
5098
  const dataList = schema.enum ?? [];
5092
- // Helper function to render enum value
5099
+ // Helper function to render enum value (returns ReactNode)
5093
5100
  // If renderDisplay is provided, use it; otherwise show the enum string value directly
5094
5101
  const renderEnumValue = (value) => {
5095
5102
  if (renderDisplay) {
@@ -5098,6 +5105,35 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
5098
5105
  // If no renderDisplay provided, show the enum string value directly
5099
5106
  return value;
5100
5107
  };
5108
+ // Helper function to get string representation for input display
5109
+ // Converts ReactNode to string for combobox input display
5110
+ const getDisplayString = (value) => {
5111
+ if (renderDisplay) {
5112
+ const rendered = renderDisplay(value);
5113
+ // If renderDisplay returns a string, use it directly
5114
+ if (typeof rendered === 'string') {
5115
+ return rendered;
5116
+ }
5117
+ // If it's a React element, try to extract text content
5118
+ // For now, fallback to the raw value if we can't extract text
5119
+ // In most cases, renderDisplay should return a string or simple element
5120
+ if (typeof rendered === 'object' &&
5121
+ rendered !== null &&
5122
+ 'props' in rendered) {
5123
+ const props = rendered.props;
5124
+ // Try to extract text from React element props
5125
+ if (props?.children) {
5126
+ const children = props.children;
5127
+ if (typeof children === 'string') {
5128
+ return children;
5129
+ }
5130
+ }
5131
+ }
5132
+ // Fallback: use raw value if we can't extract string
5133
+ return value;
5134
+ }
5135
+ return value;
5136
+ };
5101
5137
  // Debug log when renderDisplay is missing
5102
5138
  if (!renderDisplay) {
5103
5139
  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.`, {
@@ -5113,19 +5149,29 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
5113
5149
  : watchEnum
5114
5150
  ? [watchEnum]
5115
5151
  : [];
5152
+ // Track input focus state for single selection
5153
+ const [isInputFocused, setIsInputFocused] = React.useState(false);
5154
+ // Get the selected value for single selection display
5155
+ const selectedSingleValue = !isMultiple && watchEnum ? watchEnum : null;
5156
+ const selectedSingleRendered = selectedSingleValue
5157
+ ? renderEnumValue(selectedSingleValue)
5158
+ : null;
5159
+ const isSelectedSingleValueString = typeof selectedSingleRendered === 'string';
5116
5160
  const comboboxItems = React.useMemo(() => {
5117
5161
  return dataList.map((item) => ({
5118
5162
  label: item, // Internal: used for search/filtering only
5119
5163
  value: item,
5120
5164
  raw: item, // Passed to renderEnumValue for UI rendering
5165
+ displayLabel: getDisplayString(item), // Used for input display when selected
5121
5166
  }));
5122
- }, [dataList]);
5167
+ }, [dataList, renderDisplay]);
5123
5168
  // Use filter hook for combobox
5124
5169
  const { contains } = react.useFilter({ sensitivity: 'base' });
5125
5170
  // Create collection for combobox
5171
+ // itemToString uses displayLabel to show rendered display in input when selected
5126
5172
  const { collection, filter } = react.useListCollection({
5127
5173
  initialItems: comboboxItems,
5128
- itemToString: (item) => item.label,
5174
+ itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
5129
5175
  itemToValue: (item) => item.value,
5130
5176
  filter: contains,
5131
5177
  });
@@ -5163,7 +5209,26 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
5163
5209
  }, children: renderEnumValue(enumValue) }, enumValue));
5164
5210
  }) })), jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: insideDialog
5165
5211
  ? { strategy: 'fixed', hideWhenDetached: true }
5166
- : undefined, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.Combobox.Input, { placeholder: enumPickerLabels?.typeToSearch ?? 'Type to search' }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [!isMultiple && currentValue.length > 0 && (jsxRuntime.jsx(react.Combobox.ClearTrigger, { onClick: () => {
5212
+ : undefined, children: [jsxRuntime.jsxs(react.Combobox.Control, { position: "relative", children: [!isMultiple &&
5213
+ selectedSingleValue &&
5214
+ !isInputFocused &&
5215
+ !isSelectedSingleValueString &&
5216
+ selectedSingleRendered && (jsxRuntime.jsx(react.Box, { position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", pointerEvents: "none", zIndex: 1, maxWidth: "calc(100% - 60px)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: selectedSingleRendered })), jsxRuntime.jsx(react.Combobox.Input, { placeholder: !isMultiple && selectedSingleValue && !isInputFocused
5217
+ ? undefined
5218
+ : enumPickerLabels?.typeToSearch ?? 'Type to search', onFocus: () => setIsInputFocused(true), onBlur: () => setIsInputFocused(false), style: {
5219
+ color: !isMultiple &&
5220
+ selectedSingleValue &&
5221
+ !isInputFocused &&
5222
+ !isSelectedSingleValueString
5223
+ ? 'transparent'
5224
+ : undefined,
5225
+ caretColor: !isMultiple &&
5226
+ selectedSingleValue &&
5227
+ !isInputFocused &&
5228
+ !isSelectedSingleValueString
5229
+ ? 'transparent'
5230
+ : undefined,
5231
+ } }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [!isMultiple && currentValue.length > 0 && (jsxRuntime.jsx(react.Combobox.ClearTrigger, { onClick: () => {
5167
5232
  setValue(colLabel, '');
5168
5233
  } })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }), insideDialog ? (jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [showTotalAndLimit && (jsxRuntime.jsx(react.Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? 'Total'}: ${collection.items.length}` })), collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ?? 'No results found' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: renderEnumValue(item.raw) }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [showTotalAndLimit && (jsxRuntime.jsx(react.Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? 'Total'}: ${collection.items.length}` })), collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ?? 'No results found' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: renderEnumValue(item.raw) }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) }) }))] })] }));
5169
5234
  };
@@ -5907,15 +5972,46 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5907
5972
  }) })] }));
5908
5973
  };
5909
5974
 
5910
- // Default renderDisplay function that stringifies JSON
5975
+ // Default renderDisplay function that intelligently displays items
5976
+ // If item is an object, tries to find common display fields (name, title, label, etc.)
5977
+ // Otherwise falls back to JSON.stringify
5911
5978
  const defaultRenderDisplay = (item) => {
5979
+ // Check if item is an object (not null, not array, not primitive)
5980
+ if (item !== null &&
5981
+ typeof item === 'object' &&
5982
+ !Array.isArray(item) &&
5983
+ !(item instanceof Date)) {
5984
+ const obj = item;
5985
+ // Try common display fields in order of preference
5986
+ const displayFields = [
5987
+ 'name',
5988
+ 'title',
5989
+ 'label',
5990
+ 'displayName',
5991
+ 'display_name',
5992
+ 'text',
5993
+ 'value',
5994
+ ];
5995
+ for (const field of displayFields) {
5996
+ if (obj[field] !== undefined && obj[field] !== null) {
5997
+ const value = obj[field];
5998
+ // Return the value if it's a string or number, otherwise stringify it
5999
+ if (typeof value === 'string' || typeof value === 'number') {
6000
+ return String(value);
6001
+ }
6002
+ }
6003
+ }
6004
+ // If no display field found, fall back to JSON.stringify
6005
+ return JSON.stringify(item);
6006
+ }
6007
+ // For non-objects (primitives, arrays, dates), use JSON.stringify
5912
6008
  return JSON.stringify(item);
5913
6009
  };
5914
6010
 
5915
6011
  const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
5916
6012
  const { watch, getValues, formState: { errors }, setValue, } = reactHookForm.useFormContext();
5917
6013
  const { idMap, setIdMap, idPickerLabels, insideDialog } = useSchemaContext();
5918
- const { renderDisplay, loadInitialValues, foreign_key, variant } = schema;
6014
+ const { renderDisplay, itemToValue: schemaItemToValue, loadInitialValues, foreign_key, variant, } = schema;
5919
6015
  // loadInitialValues should be provided in schema for id-picker fields
5920
6016
  // It's used to load the record of the id so the display is human-readable
5921
6017
  if (variant === 'id-picker' && !loadInitialValues) {
@@ -6048,17 +6144,51 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
6048
6144
  // Depend on idMapKey which only changes when items we care about change
6049
6145
  // eslint-disable-next-line react-hooks/exhaustive-deps
6050
6146
  }, [currentValueKey, idMapKey]);
6147
+ // Default itemToValue function: extract value from item using column_ref
6148
+ const defaultItemToValue = (item) => String(item[column_ref]);
6149
+ // Use schema's itemToValue if provided, otherwise use default
6150
+ const itemToValueFn = schemaItemToValue
6151
+ ? (item) => schemaItemToValue(item)
6152
+ : defaultItemToValue;
6153
+ // itemToString function: convert item to readable string using renderDisplay
6154
+ // This ensures items can always be displayed as readable strings in the combobox
6155
+ const renderFn = renderDisplay || defaultRenderDisplay;
6156
+ const itemToStringFn = (item) => {
6157
+ const rendered = renderFn(item);
6158
+ // If already a string or number, return it
6159
+ if (typeof rendered === 'string')
6160
+ return rendered;
6161
+ if (typeof rendered === 'number')
6162
+ return String(rendered);
6163
+ // For ReactNode, fall back to defaultRenderDisplay which converts to string
6164
+ return String(defaultRenderDisplay(item));
6165
+ };
6051
6166
  // Transform data for combobox collection
6052
6167
  // label is used for filtering/searching (must be a string)
6168
+ // displayLabel is used for input display when selected (string representation of rendered display)
6053
6169
  // raw item is stored for custom rendering
6054
6170
  // Also include items from idMap that match currentValue (for initial values display)
6055
6171
  const comboboxItems = React.useMemo(() => {
6056
6172
  const renderFn = renderDisplay || defaultRenderDisplay;
6173
+ // Helper to convert rendered display to string for displayLabel
6174
+ // For ReactNodes (non-string/number), we can't safely stringify due to circular refs
6175
+ // So we use the label (which is already a string) as fallback
6176
+ const getDisplayString = (rendered, fallbackLabel) => {
6177
+ if (typeof rendered === 'string')
6178
+ return rendered;
6179
+ if (typeof rendered === 'number')
6180
+ return String(rendered);
6181
+ // For ReactNode, use the fallback label (which is already a string representation)
6182
+ // The actual ReactNode will be rendered in the overlay, not in the input
6183
+ return fallbackLabel;
6184
+ };
6057
6185
  const itemsFromDataList = dataList.map((item) => {
6058
6186
  const rendered = renderFn(item);
6187
+ const label = typeof rendered === 'string' ? rendered : JSON.stringify(item); // Use string for filtering
6059
6188
  return {
6060
- label: typeof rendered === 'string' ? rendered : JSON.stringify(item), // Use string for filtering
6061
- value: String(item[column_ref]),
6189
+ label, // Use string for filtering
6190
+ displayLabel: getDisplayString(rendered, label), // String representation for input display
6191
+ value: itemToValueFn(item),
6062
6192
  raw: item,
6063
6193
  };
6064
6194
  });
@@ -6067,25 +6197,28 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
6067
6197
  const itemsFromIdMap = idMapItems
6068
6198
  .map((item) => {
6069
6199
  // Check if this item is already in itemsFromDataList
6070
- const alreadyIncluded = itemsFromDataList.some((i) => i.value === String(item[column_ref]));
6200
+ const alreadyIncluded = itemsFromDataList.some((i) => i.value === itemToValueFn(item));
6071
6201
  if (alreadyIncluded)
6072
6202
  return null;
6073
6203
  const rendered = renderFn(item);
6204
+ const label = typeof rendered === 'string' ? rendered : JSON.stringify(item);
6074
6205
  return {
6075
- label: typeof rendered === 'string' ? rendered : JSON.stringify(item),
6076
- value: String(item[column_ref]),
6206
+ label,
6207
+ displayLabel: getDisplayString(rendered, label), // String representation for input display
6208
+ value: itemToValueFn(item),
6077
6209
  raw: item,
6078
6210
  };
6079
6211
  })
6080
6212
  .filter((item) => item !== null);
6081
6213
  return [...itemsFromIdMap, ...itemsFromDataList];
6082
- }, [dataList, column_ref, renderDisplay, idMapItems]);
6214
+ }, [dataList, column_ref, renderDisplay, idMapItems, itemToValueFn]);
6083
6215
  // Use filter hook for combobox
6084
6216
  const { contains } = react.useFilter({ sensitivity: 'base' });
6085
6217
  // Create collection for combobox
6218
+ // itemToString uses displayLabel to show rendered display in input when selected
6086
6219
  const { collection, filter, set } = react.useListCollection({
6087
6220
  initialItems: comboboxItems,
6088
- itemToString: (item) => item.label,
6221
+ itemToString: (item) => item.displayLabel, // Use displayLabel for selected value display
6089
6222
  itemToValue: (item) => item.value,
6090
6223
  filter: contains,
6091
6224
  });
@@ -6140,6 +6273,8 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
6140
6273
  idPickerLabels,
6141
6274
  insideDialog: insideDialog ?? false,
6142
6275
  renderDisplay,
6276
+ itemToValue: itemToValueFn,
6277
+ itemToString: itemToStringFn,
6143
6278
  loadInitialValues: loadInitialValues ??
6144
6279
  (async () => ({ data: { data: [], count: 0 }, idMap: {} })), // Fallback if not provided
6145
6280
  column_ref,
@@ -6150,60 +6285,69 @@ const useIdPickerData = ({ column, schema, prefix, isMultiple, }) => {
6150
6285
 
6151
6286
  const IdPickerSingle = ({ column, schema, prefix, }) => {
6152
6287
  const formI18n = useFormI18n(column, prefix, schema);
6153
- const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, } = schema;
6288
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
6154
6289
  const isRequired = required?.some((columnId) => columnId === column);
6155
- const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, isLoadingInitialValues, isFetchingInitialValues, missingIds, collection, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, errors, setValue, } = useIdPickerData({
6290
+ const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, collection, filter, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, itemToValue, itemToString, errors, setValue, } = useIdPickerData({
6156
6291
  column,
6157
6292
  schema,
6158
6293
  prefix,
6159
6294
  isMultiple: false,
6160
6295
  });
6161
- const handleInputValueChange = (details) => {
6162
- setSearchText(details.inputValue);
6163
- };
6164
- const handleValueChange = (details) => {
6165
- setValue(colLabel, details.value[0] || '');
6166
- };
6167
- const renderDisplayFunction = renderDisplayFn || renderDisplay || defaultRenderDisplay;
6168
- return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6169
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [currentValue.length > 0 && (jsxRuntime.jsx(react.Flex, { mb: 2, children: (() => {
6170
- const id = currentValue[0];
6171
- const item = idMap[id];
6172
- // Show loading skeleton while fetching initial values
6173
- if (item === undefined &&
6174
- (isLoadingInitialValues || isFetchingInitialValues) &&
6175
- missingIds.includes(id)) {
6176
- return jsxRuntime.jsx(react.Skeleton, { height: "24px", width: "100px", borderRadius: "md" });
6177
- }
6178
- // Only show "not found" if we're not loading and item is still missing
6179
- if (item === undefined) {
6180
- return (jsxRuntime.jsx(react.Text, { fontSize: "sm", children: idPickerLabels?.undefined ?? 'Undefined' }));
6181
- }
6182
- return jsxRuntime.jsx(react.Text, { fontSize: "sm", children: renderDisplayFunction(item) });
6183
- })() })), jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: false, closeOnSelect: true, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: insideDialog
6184
- ? { strategy: 'fixed', hideWhenDetached: true }
6185
- : undefined, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? 'Type to search' }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && jsxRuntime.jsx(react.Spinner, { size: "xs" }), isError && (jsxRuntime.jsx(react.Icon, { color: "fg.error", children: jsxRuntime.jsx(bi.BiError, {}) })), currentValue.length > 0 && (jsxRuntime.jsx(react.Combobox.ClearTrigger, { onClick: () => {
6186
- setValue(colLabel, '');
6187
- } })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }), insideDialog ? (jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6296
+ // Get the selected value for single selection display
6297
+ const selectedId = currentValue.length > 0 ? currentValue[0] : null;
6298
+ const selectedItem = selectedId
6299
+ ? idMap[selectedId]
6300
+ : undefined;
6301
+ // Use itemToValue to get the combobox value from the selected item, or use the ID directly
6302
+ const comboboxValue = selectedItem
6303
+ ? itemToString(selectedItem)
6304
+ : selectedId || '';
6305
+ // itemToString is available from the hook and can be used to get a readable string
6306
+ // representation of any item. The collection's itemToString is automatically used
6307
+ // by the combobox to display selected values.
6308
+ // Use useCombobox hook to control input value
6309
+ const combobox = react.useCombobox({
6310
+ collection,
6311
+ value: [comboboxValue],
6312
+ onInputValueChange(e) {
6313
+ setSearchText(e.inputValue);
6314
+ filter(e.inputValue);
6315
+ },
6316
+ onValueChange(e) {
6317
+ setValue(colLabel, e.value[0] || '');
6318
+ // Clear the input value after selection
6319
+ setSearchText('');
6320
+ },
6321
+ multiple: false,
6322
+ closeOnSelect: true,
6323
+ openOnClick: true,
6324
+ invalid: !!errors[colLabel],
6325
+ });
6326
+ // Use renderDisplay from hook (which comes from schema) or fallback to default
6327
+ const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
6328
+ // Get the selected value for single selection display (already computed above)
6329
+ const selectedRendered = selectedItem
6330
+ ? renderDisplayFunction(selectedItem)
6331
+ : null;
6332
+ return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6333
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Combobox.RootProvider, { value: combobox, width: "100%", children: [jsxRuntime.jsx(react.Show, { when: selectedId && selectedRendered, children: jsxRuntime.jsxs(react.HStack, { justifyContent: 'space-between', children: [jsxRuntime.jsx(react.Box, { children: selectedRendered }), currentValue.length > 0 && (jsxRuntime.jsx(react.Button, { variant: "ghost", size: "sm", onClick: () => {
6334
+ setValue(colLabel, '');
6335
+ }, children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(bi.BiX, {}) }) }))] }) }), jsxRuntime.jsx(react.Show, { when: !selectedId || !selectedRendered, children: jsxRuntime.jsxs(react.Combobox.Control, { position: "relative", children: [jsxRuntime.jsx(react.Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? 'Type to search' }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && jsxRuntime.jsx(react.Spinner, { size: "xs" }), isError && (jsxRuntime.jsx(react.Icon, { color: "fg.error", children: jsxRuntime.jsx(bi.BiError, {}) })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }) }), insideDialog ? (jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6336
+ // Show skeleton items to prevent UI shift
6337
+ jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
6338
+ ? idPickerLabels?.emptySearchResult ?? 'No results found'
6339
+ : idPickerLabels?.initialResults ??
6340
+ 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6188
6341
  // Show skeleton items to prevent UI shift
6189
6342
  jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
6190
6343
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
6191
6344
  : idPickerLabels?.initialResults ??
6192
- 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplayFunction === true
6193
- ? renderDisplayFunction(item.raw)
6194
- : item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6195
- // Show skeleton items to prevent UI shift
6196
- jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
6197
- ? idPickerLabels?.emptySearchResult ?? 'No results found'
6198
- : idPickerLabels?.initialResults ??
6199
- 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplayFunction === true
6200
- ? renderDisplayFunction(item.raw)
6201
- : item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
6345
+ 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsx(react.Combobox.Item, { item: item, children: renderDisplayFunction(item.raw) }, item.value ?? `item-${index}`))) })) }) }) }))] }) }));
6202
6346
  };
6203
6347
 
6204
6348
  const IdPickerMultiple = ({ column, schema, prefix, }) => {
6205
6349
  const formI18n = useFormI18n(column, prefix, schema);
6206
- const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, } = schema;
6350
+ const { required, gridColumn = 'span 12', gridRow = 'span 1' } = schema;
6207
6351
  const isRequired = required?.some((columnId) => columnId === column);
6208
6352
  const { colLabel, currentValue, searchText, setSearchText, isLoading, isFetching, isPending, isError, isSearching, isLoadingInitialValues, isFetchingInitialValues, missingIds, collection, idMap, idPickerLabels, insideDialog, renderDisplay: renderDisplayFn, errors, setValue, } = useIdPickerData({
6209
6353
  column,
@@ -6217,7 +6361,8 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
6217
6361
  const handleValueChange = (details) => {
6218
6362
  setValue(colLabel, details.value);
6219
6363
  };
6220
- const renderDisplayFunction = renderDisplayFn || renderDisplay || defaultRenderDisplay;
6364
+ // Use renderDisplay from hook (which comes from schema) or fallback to default
6365
+ const renderDisplayFunction = renderDisplayFn || defaultRenderDisplay;
6221
6366
  return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6222
6367
  gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [currentValue.length > 0 && (jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 1, mb: 2, children: currentValue.map((id) => {
6223
6368
  const item = idMap[id];
@@ -6242,16 +6387,12 @@ const IdPickerMultiple = ({ column, schema, prefix, }) => {
6242
6387
  jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
6243
6388
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
6244
6389
  : idPickerLabels?.initialResults ??
6245
- 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplayFunction === true
6246
- ? renderDisplayFunction(item.raw)
6247
- : item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6390
+ 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.emptySearchResult ?? 'Loading failed' })) : isFetching || isLoading || isPending || isSearching ? (
6248
6391
  // Show skeleton items to prevent UI shift
6249
6392
  jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
6250
6393
  ? idPickerLabels?.emptySearchResult ?? 'No results found'
6251
6394
  : idPickerLabels?.initialResults ??
6252
- 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplayFunction === true
6253
- ? renderDisplayFunction(item.raw)
6254
- : item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
6395
+ 'Start typing to search' })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [renderDisplayFunction(item.raw), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
6255
6396
  };
6256
6397
 
6257
6398
  const NumberInputRoot = React__namespace.forwardRef(function NumberInput(props, ref) {
@@ -6628,11 +6769,193 @@ const TextAreaInput = ({ column, schema, prefix, }) => {
6628
6769
 
6629
6770
  dayjs.extend(utc);
6630
6771
  dayjs.extend(timezone);
6631
- const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem, onChange = () => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6632
- placeholder: 'hh:mm AM/PM',
6633
- emptyMessage: 'No time found',
6634
- }, }) => {
6635
- // Generate time options (every 15 minutes in 12-hour format)
6772
+ const TimePicker$1 = (props) => {
6773
+ const { format = '12h', value: controlledValue, onChange: controlledOnChange, hour: uncontrolledHour, setHour: uncontrolledSetHour, minute: uncontrolledMinute, setMinute: uncontrolledSetMinute, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6774
+ placeholder: format === '24h' ? 'HH:mm:ss' : 'hh:mm AM/PM',
6775
+ emptyMessage: 'No time found',
6776
+ }, onTimeChange, } = props;
6777
+ const is24Hour = format === '24h';
6778
+ const uncontrolledMeridiem = is24Hour ? undefined : props.meridiem;
6779
+ const uncontrolledSetMeridiem = is24Hour ? undefined : props.setMeridiem;
6780
+ const uncontrolledSecond = is24Hour ? props.second : undefined;
6781
+ const uncontrolledSetSecond = is24Hour ? props.setSecond : undefined;
6782
+ // Determine if we're in controlled mode
6783
+ const isControlled = controlledValue !== undefined;
6784
+ // Parse time string to extract hour, minute, second, meridiem
6785
+ const parseTimeString = (timeStr) => {
6786
+ if (!timeStr || !timeStr.trim()) {
6787
+ return { hour: null, minute: null, second: null, meridiem: null };
6788
+ }
6789
+ // Remove timezone suffix if present (e.g., "14:30:00Z" -> "14:30:00")
6790
+ const timeWithoutTz = timeStr.replace(/[Z+-]\d{2}:?\d{2}$/, '').trim();
6791
+ // Try parsing 24-hour format: "HH:mm:ss" or "HH:mm"
6792
+ const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
6793
+ const match24 = timeWithoutTz.match(time24Pattern);
6794
+ if (match24) {
6795
+ const hour24 = parseInt(match24[1], 10);
6796
+ const minute = parseInt(match24[2], 10);
6797
+ const second = match24[3] ? parseInt(match24[3], 10) : 0;
6798
+ if (hour24 >= 0 &&
6799
+ hour24 <= 23 &&
6800
+ minute >= 0 &&
6801
+ minute <= 59 &&
6802
+ second >= 0 &&
6803
+ second <= 59) {
6804
+ if (is24Hour) {
6805
+ return { hour: hour24, minute, second, meridiem: null };
6806
+ }
6807
+ else {
6808
+ // Convert to 12-hour format
6809
+ let hour12 = hour24;
6810
+ let meridiem;
6811
+ if (hour24 === 0) {
6812
+ hour12 = 12;
6813
+ meridiem = 'am';
6814
+ }
6815
+ else if (hour24 === 12) {
6816
+ hour12 = 12;
6817
+ meridiem = 'pm';
6818
+ }
6819
+ else if (hour24 > 12) {
6820
+ hour12 = hour24 - 12;
6821
+ meridiem = 'pm';
6822
+ }
6823
+ else {
6824
+ hour12 = hour24;
6825
+ meridiem = 'am';
6826
+ }
6827
+ return { hour: hour12, minute, second: null, meridiem };
6828
+ }
6829
+ }
6830
+ }
6831
+ // Try parsing 12-hour format: "hh:mm AM/PM" or "hh:mm:ss AM/PM"
6832
+ const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm|AM|PM)$/i;
6833
+ const match12 = timeWithoutTz.match(time12Pattern);
6834
+ if (match12 && !is24Hour) {
6835
+ const hour12 = parseInt(match12[1], 10);
6836
+ const minute = parseInt(match12[2], 10);
6837
+ const second = match12[3] ? parseInt(match12[3], 10) : null;
6838
+ const meridiem = match12[4].toLowerCase();
6839
+ if (hour12 >= 1 &&
6840
+ hour12 <= 12 &&
6841
+ minute >= 0 &&
6842
+ minute <= 59 &&
6843
+ (second === null || (second >= 0 && second <= 59))) {
6844
+ return { hour: hour12, minute, second, meridiem };
6845
+ }
6846
+ }
6847
+ return { hour: null, minute: null, second: null, meridiem: null };
6848
+ };
6849
+ // Format time values to time string
6850
+ const formatTimeString = (hour, minute, second, meridiem) => {
6851
+ if (hour === null || minute === null) {
6852
+ return undefined;
6853
+ }
6854
+ if (is24Hour) {
6855
+ const h = hour.toString().padStart(2, '0');
6856
+ const m = minute.toString().padStart(2, '0');
6857
+ const s = (second ?? 0).toString().padStart(2, '0');
6858
+ return `${h}:${m}:${s}`;
6859
+ }
6860
+ else {
6861
+ if (meridiem === null) {
6862
+ return undefined;
6863
+ }
6864
+ const h = hour.toString();
6865
+ const m = minute.toString().padStart(2, '0');
6866
+ return `${h}:${m} ${meridiem.toUpperCase()}`;
6867
+ }
6868
+ };
6869
+ // Internal state for controlled mode
6870
+ const [internalHour, setInternalHour] = React.useState(null);
6871
+ const [internalMinute, setInternalMinute] = React.useState(null);
6872
+ const [internalSecond, setInternalSecond] = React.useState(null);
6873
+ const [internalMeridiem, setInternalMeridiem] = React.useState(null);
6874
+ // Use controlled or uncontrolled values
6875
+ const hour = isControlled ? internalHour : uncontrolledHour ?? null;
6876
+ const minute = isControlled ? internalMinute : uncontrolledMinute ?? null;
6877
+ const second = isControlled ? internalSecond : uncontrolledSecond ?? null;
6878
+ const meridiem = isControlled
6879
+ ? internalMeridiem
6880
+ : uncontrolledMeridiem ?? null;
6881
+ // Setters that work for both modes
6882
+ const setHour = isControlled
6883
+ ? setInternalHour
6884
+ : uncontrolledSetHour || (() => { });
6885
+ const setMinute = isControlled
6886
+ ? setInternalMinute
6887
+ : uncontrolledSetMinute || (() => { });
6888
+ const setSecond = isControlled
6889
+ ? setInternalSecond
6890
+ : uncontrolledSetSecond || (() => { });
6891
+ const setMeridiem = isControlled
6892
+ ? setInternalMeridiem
6893
+ : uncontrolledSetMeridiem || (() => { });
6894
+ // Sync internal state with controlled value prop
6895
+ const prevValueRef = React.useRef(controlledValue);
6896
+ React.useEffect(() => {
6897
+ if (!isControlled)
6898
+ return;
6899
+ if (prevValueRef.current === controlledValue) {
6900
+ return;
6901
+ }
6902
+ prevValueRef.current = controlledValue;
6903
+ const parsed = parseTimeString(controlledValue);
6904
+ setInternalHour(parsed.hour);
6905
+ setInternalMinute(parsed.minute);
6906
+ if (is24Hour) {
6907
+ setInternalSecond(parsed.second);
6908
+ }
6909
+ else {
6910
+ setInternalMeridiem(parsed.meridiem);
6911
+ }
6912
+ }, [controlledValue, isControlled, is24Hour]);
6913
+ // Wrapper onChange that calls both controlled and uncontrolled onChange
6914
+ const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
6915
+ if (isControlled) {
6916
+ const timeString = formatTimeString(newHour, newMinute, newSecond, newMeridiem);
6917
+ controlledOnChange?.(timeString);
6918
+ }
6919
+ else {
6920
+ // Call legacy onTimeChange if provided
6921
+ if (onTimeChange) {
6922
+ if (is24Hour) {
6923
+ const timeChange24h = onTimeChange;
6924
+ timeChange24h({
6925
+ hour: newHour,
6926
+ minute: newMinute,
6927
+ second: newSecond,
6928
+ });
6929
+ }
6930
+ else {
6931
+ const timeChange12h = onTimeChange;
6932
+ timeChange12h({
6933
+ hour: newHour,
6934
+ minute: newMinute,
6935
+ meridiem: newMeridiem,
6936
+ });
6937
+ }
6938
+ }
6939
+ }
6940
+ };
6941
+ const [inputValue, setInputValue] = React.useState('');
6942
+ // Sync inputValue with current time
6943
+ React.useEffect(() => {
6944
+ if (is24Hour && second !== undefined) {
6945
+ if (hour !== null && minute !== null && second !== null) {
6946
+ const formatted = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
6947
+ setInputValue(formatted);
6948
+ }
6949
+ else {
6950
+ setInputValue('');
6951
+ }
6952
+ }
6953
+ else {
6954
+ // 12-hour format - input is managed by combobox
6955
+ setInputValue('');
6956
+ }
6957
+ }, [hour, minute, second, is24Hour]);
6958
+ // Generate time options based on format
6636
6959
  const timeOptions = React.useMemo(() => {
6637
6960
  const options = [];
6638
6961
  // Get start time for comparison if provided
@@ -6643,32 +6966,25 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6643
6966
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6644
6967
  if (startDateObj.isValid() && selectedDateObj.isValid()) {
6645
6968
  startDateTime = startDateObj;
6646
- // Only filter if dates are the same
6647
6969
  shouldFilterByDate =
6648
6970
  startDateObj.format('YYYY-MM-DD') ===
6649
6971
  selectedDateObj.format('YYYY-MM-DD');
6650
6972
  }
6651
6973
  }
6652
- // Generate 12-hour format options (1-12 for hours, AM/PM)
6653
- for (let h = 1; h <= 12; h++) {
6654
- for (let m = 0; m < 60; m += 15) {
6655
- for (const mer of ['am', 'pm']) {
6656
- // Convert 12-hour to 24-hour for comparison
6657
- let hour24 = h;
6658
- if (mer === 'am' && h === 12)
6659
- hour24 = 0;
6660
- else if (mer === 'pm' && h < 12)
6661
- hour24 = h + 12;
6662
- // Filter out times that would result in negative duration (only when dates are the same)
6974
+ if (is24Hour) {
6975
+ // Generate 24-hour format options (0-23 for hours)
6976
+ for (let h = 0; h < 24; h++) {
6977
+ for (let m = 0; m < 60; m += 15) {
6978
+ // Filter out times that would result in negative duration
6663
6979
  if (startDateTime && selectedDate && shouldFilterByDate) {
6664
6980
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6665
6981
  const optionDateTime = selectedDateObj
6666
- .hour(hour24)
6982
+ .hour(h)
6667
6983
  .minute(m)
6668
6984
  .second(0)
6669
6985
  .millisecond(0);
6670
6986
  if (optionDateTime.isBefore(startDateTime)) {
6671
- continue; // Skip this option as it would result in negative duration
6987
+ continue;
6672
6988
  }
6673
6989
  }
6674
6990
  // Calculate duration if startTime is provided
@@ -6676,7 +6992,7 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6676
6992
  if (startDateTime && selectedDate) {
6677
6993
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6678
6994
  const optionDateTime = selectedDateObj
6679
- .hour(hour24)
6995
+ .hour(h)
6680
6996
  .minute(m)
6681
6997
  .second(0)
6682
6998
  .millisecond(0);
@@ -6701,62 +7017,133 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6701
7017
  }
6702
7018
  }
6703
7019
  }
6704
- const hourDisplay = h.toString();
6705
- const minuteDisplay = m.toString().padStart(2, '0');
6706
- const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
7020
+ const timeDisplay = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`;
6707
7021
  options.push({
6708
7022
  label: timeDisplay,
6709
- value: `${h}:${m}:${mer}`,
7023
+ value: `${h}:${m}:0`,
6710
7024
  hour: h,
6711
7025
  minute: m,
6712
- meridiem: mer,
6713
- searchText: timeDisplay, // Use base time without duration for searching
7026
+ second: 0,
7027
+ searchText: timeDisplay,
6714
7028
  durationText,
6715
7029
  });
6716
7030
  }
6717
7031
  }
6718
7032
  }
6719
- // Sort options by time (convert to 24-hour for proper chronological sorting)
6720
- return options.sort((a, b) => {
6721
- // Convert 12-hour to 24-hour for comparison
6722
- let hour24A = a.hour;
6723
- if (a.meridiem === 'am' && a.hour === 12)
6724
- hour24A = 0;
6725
- else if (a.meridiem === 'pm' && a.hour < 12)
6726
- hour24A = a.hour + 12;
6727
- let hour24B = b.hour;
6728
- if (b.meridiem === 'am' && b.hour === 12)
6729
- hour24B = 0;
6730
- else if (b.meridiem === 'pm' && b.hour < 12)
6731
- hour24B = b.hour + 12;
6732
- // Compare by hour first, then minute
6733
- if (hour24A !== hour24B) {
6734
- return hour24A - hour24B;
6735
- }
6736
- return a.minute - b.minute;
6737
- });
6738
- }, [startTime, selectedDate, timezone]);
7033
+ else {
7034
+ // Generate 12-hour format options (1-12 for hours, AM/PM)
7035
+ for (let h = 1; h <= 12; h++) {
7036
+ for (let m = 0; m < 60; m += 15) {
7037
+ for (const mer of ['am', 'pm']) {
7038
+ // Convert 12-hour to 24-hour for comparison
7039
+ let hour24 = h;
7040
+ if (mer === 'am' && h === 12)
7041
+ hour24 = 0;
7042
+ else if (mer === 'pm' && h < 12)
7043
+ hour24 = h + 12;
7044
+ // Filter out times that would result in negative duration
7045
+ if (startDateTime && selectedDate && shouldFilterByDate) {
7046
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
7047
+ const optionDateTime = selectedDateObj
7048
+ .hour(hour24)
7049
+ .minute(m)
7050
+ .second(0)
7051
+ .millisecond(0);
7052
+ if (optionDateTime.isBefore(startDateTime)) {
7053
+ continue;
7054
+ }
7055
+ }
7056
+ // Calculate duration if startTime is provided
7057
+ let durationText;
7058
+ if (startDateTime && selectedDate) {
7059
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
7060
+ const optionDateTime = selectedDateObj
7061
+ .hour(hour24)
7062
+ .minute(m)
7063
+ .second(0)
7064
+ .millisecond(0);
7065
+ if (optionDateTime.isValid() &&
7066
+ optionDateTime.isAfter(startDateTime)) {
7067
+ const diffMs = optionDateTime.diff(startDateTime);
7068
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
7069
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
7070
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
7071
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
7072
+ let diffText = '';
7073
+ if (diffHours > 0) {
7074
+ diffText = `${diffHours}h ${diffMinutes}m`;
7075
+ }
7076
+ else if (diffMinutes > 0) {
7077
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
7078
+ }
7079
+ else {
7080
+ diffText = `${diffSeconds}s`;
7081
+ }
7082
+ durationText = `+${diffText}`;
7083
+ }
7084
+ }
7085
+ }
7086
+ const hourDisplay = h.toString();
7087
+ const minuteDisplay = m.toString().padStart(2, '0');
7088
+ const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
7089
+ options.push({
7090
+ label: timeDisplay,
7091
+ value: `${h}:${m}:${mer}`,
7092
+ hour: h,
7093
+ minute: m,
7094
+ meridiem: mer,
7095
+ searchText: timeDisplay,
7096
+ durationText,
7097
+ });
7098
+ }
7099
+ }
7100
+ }
7101
+ // Sort 12-hour options by time (convert to 24-hour for proper chronological sorting)
7102
+ return options.sort((a, b) => {
7103
+ const a12 = a;
7104
+ const b12 = b;
7105
+ let hour24A = a12.hour;
7106
+ if (a12.meridiem === 'am' && a12.hour === 12)
7107
+ hour24A = 0;
7108
+ else if (a12.meridiem === 'pm' && a12.hour < 12)
7109
+ hour24A = a12.hour + 12;
7110
+ let hour24B = b12.hour;
7111
+ if (b12.meridiem === 'am' && b12.hour === 12)
7112
+ hour24B = 0;
7113
+ else if (b12.meridiem === 'pm' && b12.hour < 12)
7114
+ hour24B = b12.hour + 12;
7115
+ if (hour24A !== hour24B) {
7116
+ return hour24A - hour24B;
7117
+ }
7118
+ return a12.minute - b12.minute;
7119
+ });
7120
+ }
7121
+ return options;
7122
+ }, [startTime, selectedDate, timezone, is24Hour]);
6739
7123
  // itemToString returns only the clean display text (no metadata)
6740
7124
  const itemToString = React.useMemo(() => {
6741
7125
  return (item) => {
6742
- return item.searchText; // Clean display text only
7126
+ return item.searchText;
6743
7127
  };
6744
7128
  }, []);
6745
- // Custom filter function that filters by time and supports 24-hour format input
7129
+ // Custom filter function
7130
+ const { contains } = react.useFilter({ sensitivity: 'base' });
6746
7131
  const customTimeFilter = React.useMemo(() => {
7132
+ if (is24Hour) {
7133
+ return contains; // Simple contains filter for 24-hour format
7134
+ }
7135
+ // For 12-hour format, support both 12-hour and 24-hour input
6747
7136
  return (itemText, filterText) => {
6748
7137
  if (!filterText) {
6749
- return true; // Show all items when no filter
7138
+ return true;
6750
7139
  }
6751
7140
  const lowerItemText = itemText.toLowerCase();
6752
7141
  const lowerFilterText = filterText.toLowerCase();
6753
- // First, try matching against the display text (12-hour format)
6754
7142
  if (lowerItemText.includes(lowerFilterText)) {
6755
7143
  return true;
6756
7144
  }
6757
- // Find the corresponding item to check 24-hour format matches
6758
7145
  const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
6759
- if (!item) {
7146
+ if (!item || !('meridiem' in item)) {
6760
7147
  return false;
6761
7148
  }
6762
7149
  // Convert item to 24-hour format for matching
@@ -6767,18 +7154,17 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6767
7154
  hour24 = item.hour + 12;
6768
7155
  const hour24Str = hour24.toString().padStart(2, '0');
6769
7156
  const minuteStr = item.minute.toString().padStart(2, '0');
6770
- // Check if filterText matches 24-hour format variations
6771
7157
  const formats = [
6772
- `${hour24Str}:${minuteStr}`, // "13:30"
6773
- `${hour24Str}${minuteStr}`, // "1330"
6774
- hour24Str, // "13"
6775
- `${hour24}:${minuteStr}`, // "13:30" (without padding)
6776
- hour24.toString(), // "13" (without padding)
7158
+ `${hour24Str}:${minuteStr}`,
7159
+ `${hour24Str}${minuteStr}`,
7160
+ hour24Str,
7161
+ `${hour24}:${minuteStr}`,
7162
+ hour24.toString(),
6777
7163
  ];
6778
7164
  return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
6779
7165
  lowerFilterText.includes(format.toLowerCase()));
6780
7166
  };
6781
- }, [timeOptions]);
7167
+ }, [timeOptions, is24Hour, contains]);
6782
7168
  const { collection, filter } = react.useListCollection({
6783
7169
  initialItems: timeOptions,
6784
7170
  itemToString: itemToString,
@@ -6787,32 +7173,48 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6787
7173
  });
6788
7174
  // Get current value string for combobox
6789
7175
  const currentValue = React.useMemo(() => {
6790
- if (hour === null || minute === null || meridiem === null) {
6791
- return '';
7176
+ if (is24Hour) {
7177
+ if (hour === null || minute === null || second === null) {
7178
+ return '';
7179
+ }
7180
+ return `${hour}:${minute}:${second}`;
7181
+ }
7182
+ else {
7183
+ if (hour === null || minute === null || meridiem === null) {
7184
+ return '';
7185
+ }
7186
+ return `${hour}:${minute}:${meridiem}`;
6792
7187
  }
6793
- return `${hour}:${minute}:${meridiem}`;
6794
- }, [hour, minute, meridiem]);
7188
+ }, [hour, minute, second, meridiem, is24Hour]);
6795
7189
  // Calculate duration difference
6796
7190
  const durationDiff = React.useMemo(() => {
6797
- if (!startTime ||
6798
- !selectedDate ||
6799
- hour === null ||
6800
- minute === null ||
6801
- meridiem === null) {
7191
+ if (!startTime || !selectedDate || hour === null || minute === null) {
6802
7192
  return null;
6803
7193
  }
7194
+ if (is24Hour) {
7195
+ if (second === null)
7196
+ return null;
7197
+ }
7198
+ else {
7199
+ if (meridiem === null)
7200
+ return null;
7201
+ }
6804
7202
  const startDateObj = dayjs(startTime).tz(timezone);
6805
7203
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6806
- // Convert 12-hour to 24-hour format
7204
+ // Convert to 24-hour format
6807
7205
  let hour24 = hour;
6808
- if (meridiem === 'am' && hour === 12)
6809
- hour24 = 0;
6810
- else if (meridiem === 'pm' && hour < 12)
6811
- hour24 = hour + 12;
7206
+ if (!is24Hour && meridiem) {
7207
+ if (meridiem === 'am' && hour === 12)
7208
+ hour24 = 0;
7209
+ else if (meridiem === 'pm' && hour < 12)
7210
+ hour24 = hour + 12;
7211
+ }
6812
7212
  const currentDateTime = selectedDateObj
6813
7213
  .hour(hour24)
6814
7214
  .minute(minute)
6815
- .second(0)
7215
+ .second(is24Hour && second !== null && second !== undefined
7216
+ ? second
7217
+ : 0)
6816
7218
  .millisecond(0);
6817
7219
  if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6818
7220
  return null;
@@ -6838,13 +7240,28 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6838
7240
  return `+${diffText}`;
6839
7241
  }
6840
7242
  return null;
6841
- }, [hour, minute, meridiem, startTime, selectedDate, timezone]);
7243
+ }, [
7244
+ hour,
7245
+ minute,
7246
+ second,
7247
+ meridiem,
7248
+ startTime,
7249
+ selectedDate,
7250
+ timezone,
7251
+ is24Hour,
7252
+ ]);
6842
7253
  const handleClear = () => {
6843
7254
  setHour(null);
6844
7255
  setMinute(null);
6845
- setMeridiem(null);
6846
- filter(''); // Reset filter to show all options
6847
- onChange({ hour: null, minute: null, meridiem: null });
7256
+ if (is24Hour && setSecond) {
7257
+ setSecond(null);
7258
+ handleTimeChange(null, null, null, null);
7259
+ }
7260
+ else if (!is24Hour && setMeridiem) {
7261
+ setMeridiem(null);
7262
+ handleTimeChange(null, null, null, null);
7263
+ }
7264
+ filter('');
6848
7265
  };
6849
7266
  const handleValueChange = (details) => {
6850
7267
  if (details.value.length === 0) {
@@ -6856,112 +7273,165 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6856
7273
  if (selectedOption) {
6857
7274
  setHour(selectedOption.hour);
6858
7275
  setMinute(selectedOption.minute);
6859
- setMeridiem(selectedOption.meridiem);
6860
- filter(''); // Reset filter after selection
6861
- onChange({
6862
- hour: selectedOption.hour,
6863
- minute: selectedOption.minute,
6864
- meridiem: selectedOption.meridiem,
6865
- });
7276
+ filter('');
7277
+ if (is24Hour) {
7278
+ const opt24 = selectedOption;
7279
+ if (setSecond)
7280
+ setSecond(opt24.second);
7281
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
7282
+ }
7283
+ else {
7284
+ const opt12 = selectedOption;
7285
+ if (setMeridiem)
7286
+ setMeridiem(opt12.meridiem);
7287
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
7288
+ }
6866
7289
  }
6867
7290
  };
6868
7291
  // Parse input value and update state
6869
7292
  const parseAndCommitInput = (value) => {
6870
7293
  const trimmedValue = value.trim();
6871
- // Filter the collection based on input
6872
7294
  filter(trimmedValue);
6873
7295
  if (!trimmedValue) {
6874
7296
  return;
6875
7297
  }
6876
- // Parse 24-hour format first (e.g., "13:30", "14:00", "1330", "1400", "9:05", "905")
6877
- const timePattern24Hour = /^(\d{1,2}):?(\d{2})$/;
6878
- const match24Hour = trimmedValue.match(timePattern24Hour);
6879
- if (match24Hour) {
6880
- const parsedHour24 = parseInt(match24Hour[1], 10);
6881
- const parsedMinute = parseInt(match24Hour[2], 10);
6882
- // Validate 24-hour format ranges
6883
- if (parsedHour24 >= 0 &&
6884
- parsedHour24 <= 23 &&
6885
- parsedMinute >= 0 &&
6886
- parsedMinute <= 59) {
6887
- // Convert 24-hour to 12-hour format
6888
- let hour12;
6889
- let meridiem;
6890
- if (parsedHour24 === 0) {
6891
- hour12 = 12;
6892
- meridiem = 'am';
6893
- }
6894
- else if (parsedHour24 === 12) {
6895
- hour12 = 12;
6896
- meridiem = 'pm';
6897
- }
6898
- else if (parsedHour24 > 12) {
6899
- hour12 = parsedHour24 - 12;
6900
- meridiem = 'pm';
7298
+ if (is24Hour) {
7299
+ // Parse 24-hour format: "HH:mm:ss" or "HH:mm" or "HHmmss" or "HHmm"
7300
+ const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
7301
+ const match = trimmedValue.match(timePattern);
7302
+ if (match) {
7303
+ const parsedHour = parseInt(match[1], 10);
7304
+ const parsedMinute = parseInt(match[2], 10);
7305
+ const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
7306
+ if (parsedHour >= 0 &&
7307
+ parsedHour <= 23 &&
7308
+ parsedMinute >= 0 &&
7309
+ parsedMinute <= 59 &&
7310
+ parsedSecond >= 0 &&
7311
+ parsedSecond <= 59) {
7312
+ setHour(parsedHour);
7313
+ setMinute(parsedMinute);
7314
+ if (setSecond)
7315
+ setSecond(parsedSecond);
7316
+ handleTimeChange(parsedHour, parsedMinute, parsedSecond, null);
7317
+ return;
6901
7318
  }
6902
- else {
6903
- hour12 = parsedHour24;
6904
- meridiem = 'am';
7319
+ }
7320
+ // Try numbers only format: "123045" or "1230"
7321
+ const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
7322
+ if (numbersOnly.length >= 4) {
7323
+ const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
7324
+ const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
7325
+ const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
7326
+ if (parsedHour >= 0 &&
7327
+ parsedHour <= 23 &&
7328
+ parsedMinute >= 0 &&
7329
+ parsedMinute <= 59 &&
7330
+ parsedSecond >= 0 &&
7331
+ parsedSecond <= 59) {
7332
+ setHour(parsedHour);
7333
+ setMinute(parsedMinute);
7334
+ if (setSecond)
7335
+ setSecond(parsedSecond);
7336
+ handleTimeChange(parsedHour, parsedMinute, parsedSecond, null);
7337
+ return;
6905
7338
  }
6906
- setHour(hour12);
6907
- setMinute(parsedMinute);
6908
- setMeridiem(meridiem);
6909
- onChange({
6910
- hour: hour12,
6911
- minute: parsedMinute,
6912
- meridiem: meridiem,
6913
- });
6914
- return;
6915
7339
  }
6916
7340
  }
6917
- // Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
6918
- const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
6919
- const match12Hour = trimmedValue.match(timePattern12Hour);
6920
- if (match12Hour) {
6921
- const parsedHour = parseInt(match12Hour[1], 10);
6922
- const parsedMinute = parseInt(match12Hour[2], 10);
6923
- const parsedMeridiem = match12Hour[3].toLowerCase();
6924
- // Validate ranges
6925
- if (parsedHour >= 1 &&
6926
- parsedHour <= 12 &&
6927
- parsedMinute >= 0 &&
6928
- parsedMinute <= 59) {
6929
- setHour(parsedHour);
6930
- setMinute(parsedMinute);
6931
- setMeridiem(parsedMeridiem);
6932
- onChange({
6933
- hour: parsedHour,
6934
- minute: parsedMinute,
6935
- meridiem: parsedMeridiem,
6936
- });
6937
- return;
7341
+ else {
7342
+ // Parse 24-hour format first (e.g., "13:30", "14:00", "1330", "1400")
7343
+ const timePattern24Hour = /^(\d{1,2}):?(\d{2})$/;
7344
+ const match24Hour = trimmedValue.match(timePattern24Hour);
7345
+ if (match24Hour) {
7346
+ const parsedHour24 = parseInt(match24Hour[1], 10);
7347
+ const parsedMinute = parseInt(match24Hour[2], 10);
7348
+ if (parsedHour24 >= 0 &&
7349
+ parsedHour24 <= 23 &&
7350
+ parsedMinute >= 0 &&
7351
+ parsedMinute <= 59) {
7352
+ // Convert 24-hour to 12-hour format
7353
+ let hour12;
7354
+ let meridiem;
7355
+ if (parsedHour24 === 0) {
7356
+ hour12 = 12;
7357
+ meridiem = 'am';
7358
+ }
7359
+ else if (parsedHour24 === 12) {
7360
+ hour12 = 12;
7361
+ meridiem = 'pm';
7362
+ }
7363
+ else if (parsedHour24 > 12) {
7364
+ hour12 = parsedHour24 - 12;
7365
+ meridiem = 'pm';
7366
+ }
7367
+ else {
7368
+ hour12 = parsedHour24;
7369
+ meridiem = 'am';
7370
+ }
7371
+ setHour(hour12);
7372
+ setMinute(parsedMinute);
7373
+ if (setMeridiem)
7374
+ setMeridiem(meridiem);
7375
+ handleTimeChange(hour12, parsedMinute, null, meridiem);
7376
+ return;
7377
+ }
6938
7378
  }
6939
- }
6940
- // Try to parse formats like "130pm" or "130 pm" (without colon)
6941
- const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
6942
- const matchNoColon = trimmedValue.match(timePatternNoColon);
6943
- if (matchNoColon) {
6944
- const numbersOnly = matchNoColon[1];
6945
- const parsedMeridiem = matchNoColon[2].toLowerCase();
6946
- if (numbersOnly.length >= 3) {
6947
- const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
6948
- const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
6949
- // Validate ranges
7379
+ // Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
7380
+ const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
7381
+ const match12Hour = trimmedValue.match(timePattern12Hour);
7382
+ if (match12Hour) {
7383
+ const parsedHour = parseInt(match12Hour[1], 10);
7384
+ const parsedMinute = parseInt(match12Hour[2], 10);
7385
+ const parsedMeridiem = match12Hour[3].toLowerCase();
6950
7386
  if (parsedHour >= 1 &&
6951
7387
  parsedHour <= 12 &&
6952
7388
  parsedMinute >= 0 &&
6953
7389
  parsedMinute <= 59) {
6954
7390
  setHour(parsedHour);
6955
7391
  setMinute(parsedMinute);
6956
- setMeridiem(parsedMeridiem);
6957
- onChange({
6958
- hour: parsedHour,
6959
- minute: parsedMinute,
6960
- meridiem: parsedMeridiem,
6961
- });
7392
+ if (setMeridiem)
7393
+ setMeridiem(parsedMeridiem);
7394
+ handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
7395
+ return;
7396
+ }
7397
+ }
7398
+ // Parse formats like "12am" or "1pm" (hour only with meridiem, no minutes)
7399
+ const timePatternHourOnly = /^(\d{1,2})\s*(am|pm|AM|PM)$/i;
7400
+ const matchHourOnly = trimmedValue.match(timePatternHourOnly);
7401
+ if (matchHourOnly) {
7402
+ const parsedHour = parseInt(matchHourOnly[1], 10);
7403
+ const parsedMeridiem = matchHourOnly[2].toLowerCase();
7404
+ if (parsedHour >= 1 && parsedHour <= 12) {
7405
+ setHour(parsedHour);
7406
+ setMinute(0); // Default to 0 minutes when only hour is provided
7407
+ if (setMeridiem)
7408
+ setMeridiem(parsedMeridiem);
7409
+ handleTimeChange(parsedHour, 0, null, parsedMeridiem);
6962
7410
  return;
6963
7411
  }
6964
7412
  }
7413
+ // Try to parse formats like "130pm" or "130 pm" (without colon, with minutes)
7414
+ const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
7415
+ const matchNoColon = trimmedValue.match(timePatternNoColon);
7416
+ if (matchNoColon) {
7417
+ const numbersOnly = matchNoColon[1];
7418
+ const parsedMeridiem = matchNoColon[2].toLowerCase();
7419
+ if (numbersOnly.length >= 3) {
7420
+ const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
7421
+ const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
7422
+ if (parsedHour >= 1 &&
7423
+ parsedHour <= 12 &&
7424
+ parsedMinute >= 0 &&
7425
+ parsedMinute <= 59) {
7426
+ setHour(parsedHour);
7427
+ setMinute(parsedMinute);
7428
+ if (setMeridiem)
7429
+ setMeridiem(parsedMeridiem);
7430
+ handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
7431
+ return;
7432
+ }
7433
+ }
7434
+ }
6965
7435
  }
6966
7436
  // Parse failed, select first result
6967
7437
  selectFirstResult();
@@ -6972,55 +7442,84 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6972
7442
  const firstItem = collection.items[0];
6973
7443
  setHour(firstItem.hour);
6974
7444
  setMinute(firstItem.minute);
6975
- setMeridiem(firstItem.meridiem);
6976
- filter(''); // Reset filter after selection
6977
- onChange({
6978
- hour: firstItem.hour,
6979
- minute: firstItem.minute,
6980
- meridiem: firstItem.meridiem,
6981
- });
7445
+ filter('');
7446
+ if (is24Hour) {
7447
+ const opt24 = firstItem;
7448
+ if (setSecond)
7449
+ setSecond(opt24.second);
7450
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
7451
+ }
7452
+ else {
7453
+ const opt12 = firstItem;
7454
+ if (setMeridiem)
7455
+ setMeridiem(opt12.meridiem);
7456
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
7457
+ }
6982
7458
  }
6983
7459
  };
6984
7460
  const handleInputValueChange = (details) => {
6985
- // Filter the collection based on input, but don't parse yet
7461
+ if (is24Hour) {
7462
+ setInputValue(details.inputValue);
7463
+ }
6986
7464
  filter(details.inputValue);
6987
7465
  };
6988
7466
  const handleFocus = (e) => {
6989
- // Select all text when focusing
6990
7467
  e.target.select();
6991
7468
  };
6992
7469
  const handleBlur = (e) => {
6993
- // Parse and commit the input value when losing focus
6994
- const inputValue = e.target.value;
6995
- if (inputValue) {
6996
- parseAndCommitInput(inputValue);
7470
+ const inputVal = e.target.value;
7471
+ if (is24Hour) {
7472
+ setInputValue(inputVal);
7473
+ }
7474
+ if (inputVal) {
7475
+ parseAndCommitInput(inputVal);
6997
7476
  }
6998
7477
  };
6999
7478
  const handleKeyDown = (e) => {
7000
- // Commit input on Enter key
7001
7479
  if (e.key === 'Enter') {
7002
7480
  e.preventDefault();
7003
- const inputValue = e.currentTarget.value;
7004
- if (inputValue) {
7005
- parseAndCommitInput(inputValue);
7481
+ const inputVal = e.currentTarget.value;
7482
+ if (is24Hour) {
7483
+ setInputValue(inputVal);
7484
+ }
7485
+ if (inputVal) {
7486
+ parseAndCommitInput(inputVal);
7006
7487
  }
7007
- // Blur the input
7008
7488
  e.currentTarget?.blur();
7009
7489
  }
7010
7490
  };
7011
- return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: labels?.placeholder ?? 'hh:mm AM/PM', onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: labels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsxRuntime.jsx(react.Text, { flex: 1, children: item.label }), item.durationText && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: item.durationText }) }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: durationDiff }) })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdCancel, {}) }) })] }) }));
7491
+ return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { value: is24Hour ? inputValue : undefined, placeholder: labels?.placeholder ?? (is24Hour ? 'HH:mm:ss' : 'hh:mm AM/PM'), onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: labels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsxRuntime.jsx(react.Text, { flex: 1, children: item.label }), item.durationText && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: item.durationText }) }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: durationDiff }) }))] }) }));
7012
7492
  };
7013
7493
 
7014
7494
  dayjs.extend(timezone);
7015
7495
  const TimePicker = ({ column, schema, prefix }) => {
7016
7496
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
7017
7497
  const { timezone, insideDialog, timePickerLabels } = useSchemaContext();
7018
- const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', } = schema;
7498
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', startTimeField, selectedDateField, } = schema;
7019
7499
  const isRequired = required?.some((columnId) => columnId === column);
7020
7500
  const colLabel = `${prefix}${column}`;
7021
7501
  const formI18n = useFormI18n(column, prefix, schema);
7022
7502
  const [open, setOpen] = React.useState(false);
7023
7503
  const value = watch(colLabel);
7504
+ // Watch startTime and selectedDate fields for offset calculation
7505
+ const startTimeValue = startTimeField
7506
+ ? watch(`${prefix}${startTimeField}`)
7507
+ : undefined;
7508
+ const selectedDateValue = selectedDateField
7509
+ ? watch(`${prefix}${selectedDateField}`)
7510
+ : undefined;
7511
+ // Convert to ISO string format for startTime if it's a date-time string
7512
+ const startTime = startTimeValue
7513
+ ? dayjs(startTimeValue).tz(timezone).isValid()
7514
+ ? dayjs(startTimeValue).tz(timezone).toISOString()
7515
+ : undefined
7516
+ : undefined;
7517
+ // Convert selectedDate to YYYY-MM-DD format
7518
+ const selectedDate = selectedDateValue
7519
+ ? dayjs(selectedDateValue).tz(timezone).isValid()
7520
+ ? dayjs(selectedDateValue).tz(timezone).format('YYYY-MM-DD')
7521
+ : undefined
7522
+ : undefined;
7024
7523
  const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
7025
7524
  ? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
7026
7525
  : '';
@@ -7076,941 +7575,860 @@ const TimePicker = ({ column, schema, prefix }) => {
7076
7575
  return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
7077
7576
  gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
7078
7577
  setOpen(true);
7079
- }, justifyContent: 'start', children: [jsxRuntime.jsx(io.IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsxRuntime.jsx(react.Popover.Body, { overflow: "visible", children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { children: jsxRuntime.jsx(react.Popover.Body, { children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) }) }))] }) }));
7578
+ }, justifyContent: 'start', children: [jsxRuntime.jsx(io.IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsxRuntime.jsx(react.Popover.Body, { overflow: "visible", children: jsxRuntime.jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: startTime, selectedDate: selectedDate, timezone: timezone, portalled: false, labels: timePickerLabels }) }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { children: jsxRuntime.jsx(react.Popover.Body, { children: jsxRuntime.jsx(TimePicker$1, { format: "12h", hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: startTime, selectedDate: selectedDate, timezone: timezone, portalled: false, labels: timePickerLabels }) }) }) }) }))] }) }));
7080
7579
  };
7081
7580
 
7082
7581
  dayjs.extend(utc);
7083
7582
  dayjs.extend(timezone);
7084
- function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
7085
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
7086
- onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
7087
- placeholder: 'HH:mm:ss',
7088
- emptyMessage: 'No time found',
7089
- }, }) {
7090
- // Generate time options (every 15 minutes, seconds always 0)
7091
- const timeOptions = React.useMemo(() => {
7092
- const options = [];
7093
- // Get start time for comparison if provided
7094
- let startDateTime = null;
7095
- let shouldFilterByDate = false;
7096
- if (startTime && selectedDate) {
7097
- const startDateObj = dayjs(startTime).tz(timezone);
7098
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
7099
- if (startDateObj.isValid() && selectedDateObj.isValid()) {
7100
- startDateTime = startDateObj;
7101
- // Only filter if dates are the same
7102
- shouldFilterByDate =
7103
- startDateObj.format('YYYY-MM-DD') ===
7104
- selectedDateObj.format('YYYY-MM-DD');
7105
- }
7106
- }
7107
- for (let h = 0; h < 24; h++) {
7108
- for (let m = 0; m < 60; m += 15) {
7109
- const timeDisplay = `${h.toString().padStart(2, '0')}:${m
7110
- .toString()
7111
- .padStart(2, '0')}:00`;
7112
- // Filter out times that would result in negative duration (only when dates are the same)
7113
- if (startDateTime && selectedDate && shouldFilterByDate) {
7114
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
7115
- const optionDateTime = selectedDateObj
7116
- .hour(h)
7117
- .minute(m)
7118
- .second(0)
7119
- .millisecond(0);
7120
- if (optionDateTime.isBefore(startDateTime)) {
7121
- continue; // Skip this option as it would result in negative duration
7122
- }
7123
- }
7124
- // Calculate duration if startTime is provided
7125
- let durationText;
7126
- if (startDateTime && selectedDate) {
7127
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
7128
- const optionDateTime = selectedDateObj
7129
- .hour(h)
7130
- .minute(m)
7131
- .second(0)
7132
- .millisecond(0);
7133
- if (optionDateTime.isValid() &&
7134
- optionDateTime.isAfter(startDateTime)) {
7135
- const diffMs = optionDateTime.diff(startDateTime);
7136
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
7137
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
7138
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
7139
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
7140
- let diffText = '';
7141
- if (diffHours > 0) {
7142
- diffText = `${diffHours}h ${diffMinutes}m`;
7143
- }
7144
- else if (diffMinutes > 0) {
7145
- diffText = `${diffMinutes}m ${diffSeconds}s`;
7146
- }
7147
- else {
7148
- diffText = `${diffSeconds}s`;
7149
- }
7150
- durationText = `+${diffText}`;
7151
- }
7152
- }
7153
- }
7154
- options.push({
7155
- label: timeDisplay,
7156
- value: `${h}:${m}:0`,
7157
- hour: h,
7158
- minute: m,
7159
- second: 0,
7160
- searchText: timeDisplay, // Use base time without duration for searching
7161
- durationText,
7162
- });
7163
- }
7164
- }
7165
- return options;
7166
- }, [startTime, selectedDate, timezone]);
7167
- const { contains } = react.useFilter({ sensitivity: 'base' });
7168
- const { collection, filter } = react.useListCollection({
7169
- initialItems: timeOptions,
7170
- itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
7171
- itemToValue: (item) => item.value,
7172
- filter: contains,
7173
- });
7174
- // Get current value string for combobox
7175
- const currentValue = React.useMemo(() => {
7176
- if (hour === null || minute === null || second === null) {
7177
- return '';
7178
- }
7179
- return `${hour}:${minute}:${second}`;
7180
- }, [hour, minute, second]);
7181
- // Calculate duration difference
7182
- const durationDiff = React.useMemo(() => {
7183
- if (!startTime ||
7184
- !selectedDate ||
7185
- hour === null ||
7186
- minute === null ||
7187
- second === null) {
7583
+ dayjs.extend(customParseFormat);
7584
+ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
7585
+ monthNamesShort: [
7586
+ 'January',
7587
+ 'February',
7588
+ 'March',
7589
+ 'April',
7590
+ 'May',
7591
+ 'June',
7592
+ 'July',
7593
+ 'August',
7594
+ 'September',
7595
+ 'October',
7596
+ 'November',
7597
+ 'December',
7598
+ ],
7599
+ weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
7600
+ backButtonLabel: 'Back',
7601
+ forwardButtonLabel: 'Forward',
7602
+ }, timePickerLabels, timezone: tz = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, showQuickActions = false, quickActionLabels = {
7603
+ yesterday: 'Yesterday',
7604
+ today: 'Today',
7605
+ tomorrow: 'Tomorrow',
7606
+ plus7Days: '+7 Days',
7607
+ }, showTimezoneSelector = false, }) {
7608
+ const is24Hour = format === 'iso-date-time' || showSeconds;
7609
+ const { monthNamesShort = [
7610
+ 'January',
7611
+ 'February',
7612
+ 'March',
7613
+ 'April',
7614
+ 'May',
7615
+ 'June',
7616
+ 'July',
7617
+ 'August',
7618
+ 'September',
7619
+ 'October',
7620
+ 'November',
7621
+ 'December',
7622
+ ], weekdayNamesShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], backButtonLabel = 'Back', forwardButtonLabel = 'Forward', } = labels;
7623
+ // Parse value to get date and time
7624
+ const parsedValue = React.useMemo(() => {
7625
+ if (!value)
7188
7626
  return null;
7189
- }
7190
- const startDateObj = dayjs(startTime).tz(timezone);
7191
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
7192
- const currentDateTime = selectedDateObj
7193
- .hour(hour)
7194
- .minute(minute)
7195
- .second(second ?? 0)
7196
- .millisecond(0);
7197
- if (!startDateObj.isValid() || !currentDateTime.isValid()) {
7627
+ const dateObj = dayjs(value).tz(tz);
7628
+ if (!dateObj.isValid())
7198
7629
  return null;
7630
+ return dateObj;
7631
+ }, [value, tz]);
7632
+ // Initialize date state
7633
+ const [selectedDate, setSelectedDate] = React.useState(() => {
7634
+ if (parsedValue) {
7635
+ return parsedValue.toDate();
7199
7636
  }
7200
- const diffMs = currentDateTime.diff(startDateObj);
7201
- if (diffMs < 0) {
7202
- return null;
7637
+ if (defaultDate) {
7638
+ const defaultDateObj = dayjs(defaultDate).tz(tz);
7639
+ return defaultDateObj.isValid() ? defaultDateObj.toDate() : new Date();
7203
7640
  }
7204
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
7205
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
7206
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
7207
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
7208
- let diffText = '';
7209
- if (diffHours > 0) {
7210
- diffText = `${diffHours}h ${diffMinutes}m`;
7211
- }
7212
- else if (diffMinutes > 0) {
7213
- diffText = `${diffMinutes}m ${diffSeconds}s`;
7214
- }
7215
- else {
7216
- diffText = `${diffSeconds}s`;
7217
- }
7218
- return `+${diffText}`;
7641
+ return new Date();
7642
+ });
7643
+ // Initialize time state
7644
+ const [hour, setHour] = React.useState(() => {
7645
+ if (parsedValue) {
7646
+ return parsedValue.hour();
7647
+ }
7648
+ if (defaultTime?.hour !== null && defaultTime?.hour !== undefined) {
7649
+ return defaultTime.hour;
7219
7650
  }
7220
7651
  return null;
7221
- }, [hour, minute, second, startTime, selectedDate, timezone]);
7222
- const handleClear = () => {
7223
- setHour(null);
7224
- setMinute(null);
7225
- setSecond(null);
7226
- filter(''); // Reset filter to show all options
7227
- onChange({ hour: null, minute: null, second: null });
7228
- };
7229
- const handleValueChange = (details) => {
7230
- if (details.value.length === 0) {
7231
- handleClear();
7232
- return;
7652
+ });
7653
+ const [minute, setMinute] = React.useState(() => {
7654
+ if (parsedValue) {
7655
+ return parsedValue.minute();
7233
7656
  }
7234
- const selectedValue = details.value[0];
7235
- const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
7236
- if (selectedOption) {
7237
- setHour(selectedOption.hour);
7238
- setMinute(selectedOption.minute);
7239
- setSecond(selectedOption.second);
7240
- filter(''); // Reset filter after selection
7241
- onChange({
7242
- hour: selectedOption.hour,
7243
- minute: selectedOption.minute,
7244
- second: selectedOption.second,
7245
- });
7657
+ if (defaultTime?.minute !== null && defaultTime?.minute !== undefined) {
7658
+ return defaultTime.minute;
7246
7659
  }
7247
- };
7248
- // Parse input value and update state
7249
- const parseAndCommitInput = (value) => {
7250
- const trimmedValue = value.trim();
7251
- // Filter the collection based on input
7252
- filter(trimmedValue);
7253
- if (!trimmedValue) {
7660
+ return null;
7661
+ });
7662
+ const [second, setSecond] = React.useState(() => {
7663
+ if (parsedValue) {
7664
+ return parsedValue.second();
7665
+ }
7666
+ if (defaultTime?.second !== null && defaultTime?.second !== undefined) {
7667
+ return defaultTime.second;
7668
+ }
7669
+ return showSeconds ? 0 : null;
7670
+ });
7671
+ const [meridiem, setMeridiem] = React.useState(() => {
7672
+ if (parsedValue) {
7673
+ const h = parsedValue.hour();
7674
+ return h < 12 ? 'am' : 'pm';
7675
+ }
7676
+ if (defaultTime?.meridiem !== null && defaultTime?.meridiem !== undefined) {
7677
+ return defaultTime.meridiem;
7678
+ }
7679
+ return is24Hour ? null : 'am';
7680
+ });
7681
+ // Popover state - separate for date, time, and timezone
7682
+ const [datePopoverOpen, setDatePopoverOpen] = React.useState(false);
7683
+ const [timePopoverOpen, setTimePopoverOpen] = React.useState(false);
7684
+ const [timezonePopoverOpen, setTimezonePopoverOpen] = React.useState(false);
7685
+ const [calendarPopoverOpen, setCalendarPopoverOpen] = React.useState(false);
7686
+ // Timezone offset state
7687
+ const [timezoneOffset, setTimezoneOffset] = React.useState(() => {
7688
+ if (parsedValue) {
7689
+ return parsedValue.format('Z');
7690
+ }
7691
+ // Default to +08:00
7692
+ return '+08:00';
7693
+ });
7694
+ // Sync timezone offset when value changes
7695
+ // Generate timezone offset options (UTC-12 to UTC+14)
7696
+ const timezoneOffsetOptions = React.useMemo(() => {
7697
+ const options = [];
7698
+ for (let offset = -12; offset <= 14; offset++) {
7699
+ const sign = offset >= 0 ? '+' : '-';
7700
+ const hours = Math.abs(offset).toString().padStart(2, '0');
7701
+ const value = `${sign}${hours}:00`;
7702
+ const label = `UTC${sign}${hours}:00`;
7703
+ options.push({ value, label });
7704
+ }
7705
+ return options;
7706
+ }, []);
7707
+ // Create collection for Select
7708
+ const { collection: timezoneCollection } = react.useListCollection({
7709
+ initialItems: timezoneOffsetOptions,
7710
+ itemToString: (item) => item.label,
7711
+ itemToValue: (item) => item.value,
7712
+ });
7713
+ // Date input state
7714
+ const [dateInputValue, setDateInputValue] = React.useState('');
7715
+ // Sync date input value with selected date
7716
+ React.useEffect(() => {
7717
+ if (selectedDate) {
7718
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7719
+ setDateInputValue(formatted);
7720
+ }
7721
+ else {
7722
+ setDateInputValue('');
7723
+ }
7724
+ }, [selectedDate, tz]);
7725
+ // Parse and validate date input
7726
+ const parseAndValidateDateInput = (inputVal) => {
7727
+ // If empty, clear the value
7728
+ if (!inputVal.trim()) {
7729
+ setSelectedDate(null);
7730
+ updateDateTime(null, hour, minute, second, meridiem);
7254
7731
  return;
7255
7732
  }
7256
- // Parse HH:mm:ss or HH:mm format
7257
- const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
7258
- const match = trimmedValue.match(timePattern);
7259
- if (match) {
7260
- const parsedHour = parseInt(match[1], 10);
7261
- const parsedMinute = parseInt(match[2], 10);
7262
- const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
7263
- // Validate ranges
7264
- if (parsedHour >= 0 &&
7265
- parsedHour <= 23 &&
7266
- parsedMinute >= 0 &&
7267
- parsedMinute <= 59 &&
7268
- parsedSecond >= 0 &&
7269
- parsedSecond <= 59) {
7270
- setHour(parsedHour);
7271
- setMinute(parsedMinute);
7272
- setSecond(parsedSecond);
7273
- onChange({
7274
- hour: parsedHour,
7275
- minute: parsedMinute,
7276
- second: parsedSecond,
7277
- });
7733
+ // Try parsing with common date formats
7734
+ let parsedDate = dayjs(inputVal, 'YYYY-MM-DD', true);
7735
+ // If that fails, try other common formats
7736
+ if (!parsedDate.isValid()) {
7737
+ parsedDate = dayjs(inputVal);
7738
+ }
7739
+ // If valid, check constraints and update
7740
+ if (parsedDate.isValid()) {
7741
+ const dateObj = parsedDate.tz(tz).toDate();
7742
+ // Check min/max constraints
7743
+ if (minDate && dateObj < minDate) {
7744
+ // Invalid: before minDate, reset to current selected date
7745
+ if (selectedDate) {
7746
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7747
+ setDateInputValue(formatted);
7748
+ }
7749
+ else {
7750
+ setDateInputValue('');
7751
+ }
7278
7752
  return;
7279
7753
  }
7280
- }
7281
- else {
7282
- // Try to parse formats like "123045" (HHmmss) or "1230" (HHmm)
7283
- const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
7284
- if (numbersOnly.length >= 4) {
7285
- const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
7286
- const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
7287
- const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
7288
- // Validate ranges
7289
- if (parsedHour >= 0 &&
7290
- parsedHour <= 23 &&
7291
- parsedMinute >= 0 &&
7292
- parsedMinute <= 59 &&
7293
- parsedSecond >= 0 &&
7294
- parsedSecond <= 59) {
7295
- setHour(parsedHour);
7296
- setMinute(parsedMinute);
7297
- setSecond(parsedSecond);
7298
- onChange({
7299
- hour: parsedHour,
7300
- minute: parsedMinute,
7301
- second: parsedSecond,
7302
- });
7303
- return;
7754
+ if (maxDate && dateObj > maxDate) {
7755
+ // Invalid: after maxDate, reset to current selected date
7756
+ if (selectedDate) {
7757
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7758
+ setDateInputValue(formatted);
7304
7759
  }
7760
+ else {
7761
+ setDateInputValue('');
7762
+ }
7763
+ return;
7305
7764
  }
7306
- }
7307
- // Parse failed, select first result
7308
- selectFirstResult();
7309
- };
7310
- // Select first result from filtered collection
7311
- const selectFirstResult = () => {
7312
- if (collection.items.length > 0) {
7313
- const firstItem = collection.items[0];
7314
- setHour(firstItem.hour);
7315
- setMinute(firstItem.minute);
7316
- setSecond(firstItem.second);
7317
- filter(''); // Reset filter after selection
7318
- onChange({
7319
- hour: firstItem.hour,
7320
- minute: firstItem.minute,
7321
- second: firstItem.second,
7322
- });
7323
- }
7324
- };
7325
- const [inputValue, setInputValue] = React.useState('');
7326
- // Sync inputValue with currentValue when time changes externally
7327
- React.useEffect(() => {
7328
- if (hour !== null && minute !== null && second !== null) {
7329
- const formattedValue = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
7330
- setInputValue(formattedValue);
7765
+ // Valid date - update selected date
7766
+ setSelectedDate(dateObj);
7767
+ updateDateTime(dateObj, hour, minute, second, meridiem);
7768
+ // Format and update input value
7769
+ const formatted = parsedDate.tz(tz).format('YYYY-MM-DD');
7770
+ setDateInputValue(formatted);
7331
7771
  }
7332
7772
  else {
7333
- setInputValue('');
7773
+ // Invalid date - reset to current selected date
7774
+ if (selectedDate) {
7775
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7776
+ setDateInputValue(formatted);
7777
+ }
7778
+ else {
7779
+ setDateInputValue('');
7780
+ }
7334
7781
  }
7335
- }, [hour, minute, second]);
7336
- const handleInputValueChange = (details) => {
7337
- // Update local input value state
7338
- setInputValue(details.inputValue);
7339
- // Filter the collection based on input, but don't parse yet
7340
- filter(details.inputValue);
7341
7782
  };
7342
- const handleFocus = (e) => {
7343
- // Select all text when focusing
7344
- e.target.select();
7783
+ const handleDateInputChange = (e) => {
7784
+ setDateInputValue(e.target.value);
7345
7785
  };
7346
- const handleBlur = (e) => {
7347
- // Parse and commit the input value when losing focus
7348
- const inputVal = e.target.value;
7349
- setInputValue(inputVal);
7350
- if (inputVal) {
7351
- parseAndCommitInput(inputVal);
7352
- }
7786
+ const handleDateInputBlur = () => {
7787
+ parseAndValidateDateInput(dateInputValue);
7353
7788
  };
7354
- const handleKeyDown = (e) => {
7355
- // Commit input on Enter key
7789
+ const handleDateInputKeyDown = (e) => {
7356
7790
  if (e.key === 'Enter') {
7357
7791
  e.preventDefault();
7358
- const inputVal = e.currentTarget.value;
7359
- setInputValue(inputVal);
7360
- if (inputVal) {
7361
- parseAndCommitInput(inputVal);
7362
- }
7363
- // Blur the input
7364
- e.currentTarget?.blur();
7792
+ parseAndValidateDateInput(dateInputValue);
7365
7793
  }
7366
7794
  };
7367
- return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { value: inputValue, placeholder: labels.placeholder, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: labels.emptyMessage }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsxRuntime.jsx(react.Text, { flex: 1, children: item.label }), item.durationText && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: item.durationText }) }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: durationDiff }) })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdCancel, {}) }) })] }) }));
7368
- }
7369
-
7370
- dayjs.extend(utc);
7371
- dayjs.extend(timezone);
7372
- function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
7373
- monthNamesShort: [
7374
- 'Jan',
7375
- 'Feb',
7376
- 'Mar',
7377
- 'Apr',
7378
- 'May',
7379
- 'Jun',
7380
- 'Jul',
7381
- 'Aug',
7382
- 'Sep',
7383
- 'Oct',
7384
- 'Nov',
7385
- 'Dec',
7386
- ],
7387
- weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
7388
- backButtonLabel: 'Back',
7389
- forwardButtonLabel: 'Next',
7390
- }, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, }) {
7391
- console.log('[DateTimePicker] Component initialized with props:', {
7392
- value,
7393
- format,
7394
- showSeconds,
7395
- timezone,
7396
- startTime,
7397
- minDate,
7398
- maxDate,
7399
- });
7400
- // Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
7401
- const getDateString = React.useCallback((val) => {
7402
- if (!val)
7403
- return '';
7404
- const dateObj = dayjs(val).tz(timezone);
7405
- return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
7406
- }, [timezone]);
7407
- const [selectedDate, setSelectedDate] = React.useState(getDateString(value));
7408
- // Helper to get time values from value prop with timezone
7409
- const getTimeFromValue = React.useCallback((val) => {
7410
- console.log('[DateTimePicker] getTimeFromValue called:', {
7411
- val,
7412
- timezone,
7413
- showSeconds,
7414
- });
7415
- if (!val) {
7416
- console.log('[DateTimePicker] No value provided, returning nulls');
7417
- return {
7418
- hour12: null,
7419
- minute: null,
7420
- meridiem: null,
7421
- hour24: null,
7422
- second: null,
7423
- };
7424
- }
7425
- const dateObj = dayjs(val).tz(timezone);
7426
- console.log('[DateTimePicker] Parsed date object:', {
7427
- original: val,
7428
- timezone,
7429
- isValid: dateObj.isValid(),
7430
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7431
- hour24: dateObj.hour(),
7432
- minute: dateObj.minute(),
7433
- second: dateObj.second(),
7434
- });
7435
- if (!dateObj.isValid()) {
7436
- console.log('[DateTimePicker] Invalid date object, returning nulls');
7437
- return {
7438
- hour12: null,
7439
- minute: null,
7440
- meridiem: null,
7441
- hour24: null,
7442
- second: null,
7443
- };
7444
- }
7445
- const hour24Value = dateObj.hour();
7446
- const hour12Value = hour24Value % 12 || 12;
7447
- const minuteValue = dateObj.minute();
7448
- const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
7449
- const secondValue = showSeconds ? dateObj.second() : null;
7450
- const result = {
7451
- hour12: hour12Value,
7452
- minute: minuteValue,
7453
- meridiem: meridiemValue,
7454
- hour24: hour24Value,
7455
- second: secondValue,
7456
- };
7457
- console.log('[DateTimePicker] Extracted time values:', result);
7458
- return result;
7459
- }, [timezone, showSeconds]);
7460
- const initialTime = getTimeFromValue(value);
7461
- console.log('[DateTimePicker] Initial time from value:', {
7462
- value,
7463
- initialTime,
7464
- });
7465
- // Normalize startTime to ignore milliseconds (needed for effectiveDefaultDate calculation)
7466
- const normalizedStartTime = startTime
7467
- ? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
7468
- : undefined;
7469
- // Calculate effective defaultDate: use prop if provided, otherwise use startTime date, otherwise use today
7470
- const effectiveDefaultDate = React.useMemo(() => {
7471
- if (defaultDate) {
7472
- return defaultDate;
7473
- }
7474
- if (normalizedStartTime &&
7475
- dayjs(normalizedStartTime).tz(timezone).isValid()) {
7476
- return dayjs(normalizedStartTime).tz(timezone).format('YYYY-MM-DD');
7477
- }
7478
- return dayjs().tz(timezone).format('YYYY-MM-DD');
7479
- }, [defaultDate, normalizedStartTime, timezone]);
7480
- // Initialize time with default values if no value is provided
7481
- const getInitialTimeValues = () => {
7482
- if (value && initialTime.hour12 !== null) {
7483
- return initialTime;
7484
- }
7485
- // If no value or no time in value, use defaultTime or 00:00
7486
- if (defaultTime) {
7487
- if (format === 'iso-date-time') {
7488
- const defaultTime24 = defaultTime;
7489
- return {
7490
- hour12: null,
7491
- minute: defaultTime24.minute ?? 0,
7492
- meridiem: null,
7493
- hour24: defaultTime24.hour ?? 0,
7494
- second: showSeconds ? defaultTime24.second ?? 0 : null,
7495
- };
7496
- }
7497
- else {
7498
- const defaultTime12 = defaultTime;
7499
- return {
7500
- hour12: defaultTime12.hour ?? 12,
7501
- minute: defaultTime12.minute ?? 0,
7502
- meridiem: defaultTime12.meridiem ?? 'am',
7503
- hour24: null,
7504
- second: null,
7505
- };
7506
- }
7507
- }
7508
- // Default to 00:00
7509
- if (format === 'iso-date-time') {
7510
- return {
7511
- hour12: null,
7512
- minute: 0,
7513
- meridiem: null,
7514
- hour24: 0,
7515
- second: showSeconds ? 0 : null,
7516
- };
7795
+ // Helper functions to get dates in the correct timezone
7796
+ const getToday = () => dayjs().tz(tz).startOf('day').toDate();
7797
+ const getYesterday = () => dayjs().tz(tz).subtract(1, 'day').startOf('day').toDate();
7798
+ const getTomorrow = () => dayjs().tz(tz).add(1, 'day').startOf('day').toDate();
7799
+ const getPlus7Days = () => dayjs().tz(tz).add(7, 'day').startOf('day').toDate();
7800
+ // Check if a date is within min/max constraints
7801
+ const isDateValid = (date) => {
7802
+ if (minDate) {
7803
+ const minDateStart = dayjs(minDate).tz(tz).startOf('day').toDate();
7804
+ const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
7805
+ if (dateStart < minDateStart)
7806
+ return false;
7517
7807
  }
7518
- else {
7519
- return {
7520
- hour12: 12,
7521
- minute: 0,
7522
- meridiem: 'am',
7523
- hour24: null,
7524
- second: null,
7525
- };
7808
+ if (maxDate) {
7809
+ const maxDateStart = dayjs(maxDate).tz(tz).startOf('day').toDate();
7810
+ const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
7811
+ if (dateStart > maxDateStart)
7812
+ return false;
7526
7813
  }
7814
+ return true;
7527
7815
  };
7528
- const initialTimeValues = getInitialTimeValues();
7529
- // Time state for 12-hour format
7530
- const [hour12, setHour12] = React.useState(initialTimeValues.hour12);
7531
- const [minute, setMinute] = React.useState(initialTimeValues.minute);
7532
- const [meridiem, setMeridiem] = React.useState(initialTimeValues.meridiem);
7533
- // Time state for 24-hour format
7534
- const [hour24, setHour24] = React.useState(initialTimeValues.hour24);
7535
- const [second, setSecond] = React.useState(initialTimeValues.second);
7536
- // Sync selectedDate and time states when value prop changes
7537
- React.useEffect(() => {
7538
- console.log('[DateTimePicker] useEffect triggered - value changed:', {
7539
- value,
7540
- timezone,
7541
- format,
7542
- });
7543
- // If value is null, undefined, or invalid, clear date but keep default time values
7544
- if (!value || value === null || value === undefined) {
7545
- console.log('[DateTimePicker] Value is null/undefined, clearing date but keeping default time');
7546
- setSelectedDate('');
7547
- // Keep default time values instead of clearing them
7548
- if (format === 'iso-date-time') {
7549
- setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
7550
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7551
- setSecond(showSeconds
7552
- ? defaultTime
7553
- ? defaultTime.second ?? 0
7554
- : 0
7555
- : null);
7556
- }
7557
- else {
7558
- setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
7559
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7560
- setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
7561
- }
7562
- return;
7816
+ // Handle quick action button clicks
7817
+ const handleQuickActionClick = (date) => {
7818
+ if (isDateValid(date)) {
7819
+ setSelectedDate(date);
7820
+ updateDateTime(date, hour, minute, second, meridiem);
7821
+ // Close the calendar popover if open
7822
+ setCalendarPopoverOpen(false);
7563
7823
  }
7564
- // Check if value is valid
7565
- const dateObj = dayjs(value).tz(timezone);
7566
- if (!dateObj.isValid()) {
7567
- console.log('[DateTimePicker] Invalid value, clearing date but keeping default time');
7568
- setSelectedDate('');
7569
- // Keep default time values instead of clearing them
7570
- if (format === 'iso-date-time') {
7571
- setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
7572
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7573
- setSecond(showSeconds
7574
- ? defaultTime
7575
- ? defaultTime.second ?? 0
7576
- : 0
7577
- : null);
7578
- }
7579
- else {
7580
- setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
7581
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7582
- setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
7824
+ };
7825
+ // Display text for buttons
7826
+ const dateDisplayText = React.useMemo(() => {
7827
+ if (!selectedDate)
7828
+ return 'Select date';
7829
+ return dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7830
+ }, [selectedDate, tz]);
7831
+ const timeDisplayText = React.useMemo(() => {
7832
+ if (hour === null || minute === null)
7833
+ return 'Select time';
7834
+ if (is24Hour) {
7835
+ // 24-hour format: never show meridiem, always use 24-hour format (0-23)
7836
+ const hour24 = hour >= 0 && hour <= 23 ? hour : hour % 24;
7837
+ const s = second ?? 0;
7838
+ if (showSeconds) {
7839
+ return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
7583
7840
  }
7584
- return;
7841
+ return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
7585
7842
  }
7586
- const dateString = getDateString(value);
7587
- console.log('[DateTimePicker] Setting selectedDate:', dateString);
7588
- setSelectedDate(dateString);
7589
- const timeData = getTimeFromValue(value);
7590
- console.log('[DateTimePicker] Updating time states:', {
7591
- timeData,
7592
- });
7593
- setHour12(timeData.hour12);
7594
- setMinute(timeData.minute);
7595
- setMeridiem(timeData.meridiem);
7596
- setHour24(timeData.hour24);
7597
- setSecond(timeData.second);
7598
- }, [value, getTimeFromValue, getDateString, timezone]);
7599
- const handleDateChange = (date) => {
7600
- console.log('[DateTimePicker] handleDateChange called:', {
7601
- date,
7602
- timezone,
7603
- showSeconds,
7604
- currentTimeStates: { hour12, minute, meridiem, hour24, second },
7605
- });
7606
- // If date is empty or invalid, clear all fields
7607
- if (!date || date === '') {
7608
- console.log('[DateTimePicker] Empty date, clearing all fields');
7609
- setSelectedDate('');
7610
- setHour12(null);
7611
- setMinute(null);
7612
- setMeridiem(null);
7613
- setHour24(null);
7614
- setSecond(null);
7615
- onChange?.(undefined);
7616
- return;
7617
- }
7618
- setSelectedDate(date);
7619
- // Parse the date string (YYYY-MM-DD) in the specified timezone
7620
- const dateObj = dayjs.tz(date, timezone);
7621
- console.log('[DateTimePicker] Parsed date object:', {
7622
- date,
7623
- timezone,
7624
- isValid: dateObj.isValid(),
7625
- isoString: dateObj.toISOString(),
7626
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7627
- });
7628
- if (!dateObj.isValid()) {
7629
- console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
7630
- setSelectedDate('');
7631
- setHour12(null);
7632
- setMinute(null);
7633
- setMeridiem(null);
7634
- setHour24(null);
7635
- setSecond(null);
7843
+ else {
7844
+ // 12-hour format: always show meridiem (AM/PM)
7845
+ const hour12 = hour >= 1 && hour <= 12 ? hour : hour % 12;
7846
+ if (meridiem === null)
7847
+ return 'Select time';
7848
+ const hourDisplay = hour12.toString();
7849
+ const minuteDisplay = minute.toString().padStart(2, '0');
7850
+ return `${hourDisplay}:${minuteDisplay} ${meridiem.toUpperCase()}`;
7851
+ }
7852
+ }, [hour, minute, second, meridiem, is24Hour, showSeconds]);
7853
+ const timezoneDisplayText = React.useMemo(() => {
7854
+ if (!showTimezoneSelector)
7855
+ return '';
7856
+ // Show offset as is (e.g., "+08:00")
7857
+ return timezoneOffset;
7858
+ }, [timezoneOffset, showTimezoneSelector]);
7859
+ // Update selectedDate when value changes externally
7860
+ React.useEffect(() => {
7861
+ if (parsedValue) {
7862
+ setSelectedDate(parsedValue.toDate());
7863
+ setHour(parsedValue.hour());
7864
+ setMinute(parsedValue.minute());
7865
+ setSecond(parsedValue.second());
7866
+ if (!is24Hour) {
7867
+ const h = parsedValue.hour();
7868
+ setMeridiem(h < 12 ? 'am' : 'pm');
7869
+ }
7870
+ }
7871
+ }, [parsedValue, is24Hour]);
7872
+ // Combine date and time and call onChange
7873
+ const updateDateTime = (newDate, newHour, newMinute, newSecond, newMeridiem, timezoneOffsetOverride) => {
7874
+ if (!newDate || newHour === null || newMinute === null) {
7636
7875
  onChange?.(undefined);
7637
7876
  return;
7638
7877
  }
7639
- // Check if time values are null - if so, use defaultTime or set to 00:00
7640
- const hasTimeValues = format === 'iso-date-time'
7641
- ? hour24 !== null || minute !== null
7642
- : hour12 !== null || minute !== null || meridiem !== null;
7643
- let timeDataToUse = undefined;
7644
- if (!hasTimeValues) {
7645
- // Use defaultTime if provided, otherwise default to 00:00
7646
- if (defaultTime) {
7647
- console.log('[DateTimePicker] No time values set, using defaultTime');
7648
- if (format === 'iso-date-time') {
7649
- const defaultTime24 = defaultTime;
7650
- setHour24(defaultTime24.hour ?? 0);
7651
- setMinute(defaultTime24.minute ?? 0);
7652
- if (showSeconds) {
7653
- setSecond(defaultTime24.second ?? 0);
7654
- }
7655
- timeDataToUse = {
7656
- hour: defaultTime24.hour ?? 0,
7657
- minute: defaultTime24.minute ?? 0,
7658
- second: showSeconds ? defaultTime24.second ?? 0 : undefined,
7659
- };
7878
+ // Convert 12-hour to 24-hour if needed
7879
+ let hour24 = newHour;
7880
+ if (!is24Hour && newMeridiem) {
7881
+ // In 12-hour format, hour should be 1-12
7882
+ // If hour is > 12, it might already be in 24-hour format, convert it first
7883
+ let hour12 = newHour;
7884
+ if (newHour > 12) {
7885
+ // Hour is in 24-hour format, convert to 12-hour first
7886
+ if (newHour === 12) {
7887
+ hour12 = 12;
7660
7888
  }
7661
7889
  else {
7662
- const defaultTime12 = defaultTime;
7663
- setHour12(defaultTime12.hour ?? 12);
7664
- setMinute(defaultTime12.minute ?? 0);
7665
- setMeridiem(defaultTime12.meridiem ?? 'am');
7666
- timeDataToUse = {
7667
- hour: defaultTime12.hour ?? 12,
7668
- minute: defaultTime12.minute ?? 0,
7669
- meridiem: defaultTime12.meridiem ?? 'am',
7670
- };
7890
+ hour12 = newHour - 12;
7891
+ }
7892
+ }
7893
+ // Now convert 12-hour to 24-hour format (0-23)
7894
+ if (newMeridiem === 'am') {
7895
+ if (hour12 === 12) {
7896
+ hour24 = 0; // 12 AM = 0:00
7897
+ }
7898
+ else {
7899
+ hour24 = hour12; // 1-11 AM = 1-11
7671
7900
  }
7672
7901
  }
7673
7902
  else {
7674
- console.log('[DateTimePicker] No time values set, defaulting to 00:00');
7675
- if (format === 'iso-date-time') {
7676
- setHour24(0);
7677
- setMinute(0);
7678
- if (showSeconds) {
7679
- setSecond(0);
7680
- }
7681
- timeDataToUse = {
7682
- hour: 0,
7683
- minute: 0,
7684
- second: showSeconds ? 0 : undefined,
7685
- };
7903
+ // PM
7904
+ if (hour12 === 12) {
7905
+ hour24 = 12; // 12 PM = 12:00
7686
7906
  }
7687
7907
  else {
7688
- setHour12(12);
7689
- setMinute(0);
7690
- setMeridiem('am');
7691
- timeDataToUse = {
7692
- hour: 12,
7693
- minute: 0,
7694
- meridiem: 'am',
7695
- };
7908
+ hour24 = hour12 + 12; // 1-11 PM = 13-23
7696
7909
  }
7697
7910
  }
7698
7911
  }
7699
- // When showSeconds is false, ignore seconds from the date
7700
- if (!showSeconds) {
7701
- const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
7702
- console.log('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
7703
- updateDateTime(dateWithoutSeconds, timeDataToUse);
7912
+ else if (!is24Hour && !newMeridiem) {
7913
+ // If in 12-hour mode but no meridiem, assume the hour is already in 12-hour format
7914
+ // and default to AM (or keep as is if it's a valid 12-hour value)
7915
+ // This shouldn't happen in normal flow, but handle it gracefully
7916
+ hour24 = newHour;
7917
+ }
7918
+ // If timezone selector is enabled, create date-time without timezone conversion
7919
+ // to ensure the selected timestamp matches the picker values exactly
7920
+ if (showTimezoneSelector) {
7921
+ // Use override if provided, otherwise use state value
7922
+ const offsetToUse = timezoneOffsetOverride ?? timezoneOffset;
7923
+ // Create date-time from the Date object without timezone conversion
7924
+ // Extract year, month, day from the date
7925
+ const year = newDate.getFullYear();
7926
+ const month = newDate.getMonth();
7927
+ const day = newDate.getDate();
7928
+ // Create a date-time string with the exact values from the picker
7929
+ 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')}`;
7930
+ onChange?.(`${formattedDateTime}${offsetToUse}`);
7931
+ return;
7704
7932
  }
7705
- else {
7706
- const dateWithSeconds = dateObj.toISOString();
7707
- console.log('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
7708
- updateDateTime(dateWithSeconds, timeDataToUse);
7933
+ // Normal mode: use timezone conversion
7934
+ let dateTime = dayjs(newDate)
7935
+ .tz(tz)
7936
+ .hour(hour24)
7937
+ .minute(newMinute)
7938
+ .second(newSecond ?? 0)
7939
+ .millisecond(0);
7940
+ if (!dateTime.isValid()) {
7941
+ onChange?.(undefined);
7942
+ return;
7709
7943
  }
7710
- };
7711
- const handleTimeChange = (timeData) => {
7712
- console.log('[DateTimePicker] handleTimeChange called:', {
7713
- timeData,
7714
- format,
7715
- selectedDate,
7716
- timezone,
7717
- });
7944
+ // Format based on format prop
7718
7945
  if (format === 'iso-date-time') {
7719
- const data = timeData;
7720
- console.log('[DateTimePicker] ISO format - setting 24-hour time:', data);
7721
- setHour24(data.hour);
7722
- setMinute(data.minute);
7723
- if (showSeconds) {
7724
- setSecond(data.second ?? null);
7725
- }
7726
- else {
7727
- // Ignore seconds - always set to null when showSeconds is false
7728
- setSecond(null);
7729
- }
7946
+ onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ss'));
7730
7947
  }
7731
7948
  else {
7732
- const data = timeData;
7733
- console.log('[DateTimePicker] 12-hour format - setting time:', data);
7734
- setHour12(data.hour);
7735
- setMinute(data.minute);
7736
- setMeridiem(data.meridiem);
7737
- }
7738
- // Use selectedDate if valid, otherwise use effectiveDefaultDate or clear all fields
7739
- if (!selectedDate || !dayjs(selectedDate).isValid()) {
7740
- // If effectiveDefaultDate is available, use it instead of clearing
7741
- if (effectiveDefaultDate && dayjs(effectiveDefaultDate).isValid()) {
7742
- console.log('[DateTimePicker] No valid selectedDate, using effectiveDefaultDate:', effectiveDefaultDate);
7743
- setSelectedDate(effectiveDefaultDate);
7744
- const dateObj = dayjs(effectiveDefaultDate).tz(timezone);
7745
- if (dateObj.isValid()) {
7746
- updateDateTime(dateObj.toISOString(), timeData);
7747
- }
7748
- else {
7749
- console.warn('[DateTimePicker] Invalid effectiveDefaultDate, clearing fields');
7750
- setSelectedDate('');
7751
- setHour12(null);
7752
- setMinute(null);
7753
- setMeridiem(null);
7754
- setHour24(null);
7755
- setSecond(null);
7756
- onChange?.(undefined);
7757
- }
7758
- return;
7759
- }
7760
- else {
7761
- console.log('[DateTimePicker] No valid selectedDate and no effectiveDefaultDate, keeping time values but no date');
7762
- // Keep the time values that were just set, but don't set a date
7763
- // This should rarely happen as effectiveDefaultDate always defaults to today
7764
- setSelectedDate('');
7765
- onChange?.(undefined);
7766
- return;
7767
- }
7949
+ // date-time format with timezone
7950
+ onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ssZ'));
7768
7951
  }
7769
- const dateObj = dayjs(selectedDate).tz(timezone);
7770
- if (dateObj.isValid()) {
7771
- updateDateTime(dateObj.toISOString(), timeData);
7952
+ };
7953
+ // Handle date selection
7954
+ const handleDateSelected = ({ date, }) => {
7955
+ setSelectedDate(date);
7956
+ updateDateTime(date, hour, minute, second, meridiem);
7957
+ };
7958
+ // Handle time change
7959
+ const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
7960
+ setHour(newHour);
7961
+ setMinute(newMinute);
7962
+ if (is24Hour) {
7963
+ setSecond(newSecond);
7772
7964
  }
7773
7965
  else {
7774
- console.warn('[DateTimePicker] Invalid date object in handleTimeChange, clearing fields');
7775
- setSelectedDate('');
7776
- setHour12(null);
7777
- setMinute(null);
7778
- setMeridiem(null);
7779
- setHour24(null);
7780
- setSecond(null);
7781
- onChange?.(undefined);
7966
+ setMeridiem(newMeridiem);
7782
7967
  }
7783
- };
7784
- const updateDateTime = (date, timeData) => {
7785
- console.log('[DateTimePicker] updateDateTime called:', {
7786
- date,
7787
- timeData,
7788
- format,
7789
- currentStates: { hour12, minute, meridiem, hour24, second },
7790
- });
7791
- if (!date || date === null || date === undefined) {
7792
- console.log('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
7793
- setSelectedDate('');
7794
- setHour12(null);
7795
- setMinute(null);
7796
- setMeridiem(null);
7797
- setHour24(null);
7798
- setSecond(null);
7799
- onChange?.(undefined);
7800
- return;
7968
+ if (selectedDate) {
7969
+ updateDateTime(selectedDate, newHour, newMinute, newSecond, newMeridiem);
7801
7970
  }
7802
- // use dayjs to convert the date to the timezone
7803
- const dateObj = dayjs(date).tz(timezone);
7804
- if (!dateObj.isValid()) {
7805
- console.warn('[DateTimePicker] Invalid date object in updateDateTime, clearing fields:', date);
7806
- setSelectedDate('');
7807
- setHour12(null);
7808
- setMinute(null);
7809
- setMeridiem(null);
7810
- setHour24(null);
7811
- setSecond(null);
7812
- onChange?.(undefined);
7813
- return;
7971
+ };
7972
+ // Calendar hook
7973
+ const calendarProps = useCalendar({
7974
+ selected: selectedDate || undefined,
7975
+ date: selectedDate || undefined,
7976
+ minDate,
7977
+ maxDate,
7978
+ monthsToDisplay: 1,
7979
+ onDateSelected: handleDateSelected,
7980
+ });
7981
+ // Generate time options
7982
+ const timeOptions = React.useMemo(() => {
7983
+ const options = [];
7984
+ // Get start time for comparison if provided
7985
+ let startDateTime = null;
7986
+ let shouldFilterByDate = false;
7987
+ if (startTime && selectedDate) {
7988
+ const startDateObj = dayjs(startTime).tz(tz);
7989
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
7990
+ if (startDateObj.isValid() && selectedDateObj.isValid()) {
7991
+ startDateTime = startDateObj;
7992
+ shouldFilterByDate =
7993
+ startDateObj.format('YYYY-MM-DD') ===
7994
+ selectedDateObj.format('YYYY-MM-DD');
7995
+ }
7814
7996
  }
7815
- const newDate = dateObj.toDate();
7816
- if (format === 'iso-date-time') {
7817
- const data = timeData;
7818
- // Use timeData values if provided, otherwise fall back to current state
7819
- // But if timeData is explicitly provided with nulls, we need to check if all are null
7820
- const h = data !== undefined ? data.hour : hour24;
7821
- const m = data !== undefined ? data.minute : minute;
7822
- // Always ignore seconds when showSeconds is false - set to 0
7823
- const s = showSeconds
7824
- ? data !== undefined
7825
- ? data.second ?? null
7826
- : second ?? 0
7827
- : 0;
7828
- // If all time values are null, clear the value
7829
- if (h === null && m === null && (showSeconds ? s === null : true)) {
7830
- console.log('[DateTimePicker] All time values are null, clearing value');
7831
- onChange?.(undefined);
7832
- return;
7997
+ if (is24Hour) {
7998
+ // Generate 24-hour format options
7999
+ for (let h = 0; h < 24; h++) {
8000
+ for (let m = 0; m < 60; m += 15) {
8001
+ // Filter out times that would result in negative duration
8002
+ if (startDateTime && selectedDate && shouldFilterByDate) {
8003
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8004
+ const optionDateTime = selectedDateObj
8005
+ .hour(h)
8006
+ .minute(m)
8007
+ .second(0)
8008
+ .millisecond(0);
8009
+ if (optionDateTime.isBefore(startDateTime)) {
8010
+ continue;
8011
+ }
8012
+ }
8013
+ // Calculate duration if startTime is provided
8014
+ let durationText;
8015
+ if (startDateTime && selectedDate) {
8016
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8017
+ const optionDateTime = selectedDateObj
8018
+ .hour(h)
8019
+ .minute(m)
8020
+ .second(0)
8021
+ .millisecond(0);
8022
+ if (optionDateTime.isValid() &&
8023
+ optionDateTime.isAfter(startDateTime)) {
8024
+ const diffMs = optionDateTime.diff(startDateTime);
8025
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
8026
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
8027
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
8028
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
8029
+ let diffText = '';
8030
+ if (diffHours > 0) {
8031
+ diffText = `${diffHours}h ${diffMinutes}m`;
8032
+ }
8033
+ else if (diffMinutes > 0) {
8034
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
8035
+ }
8036
+ else {
8037
+ diffText = `${diffSeconds}s`;
8038
+ }
8039
+ durationText = `+${diffText}`;
8040
+ }
8041
+ }
8042
+ }
8043
+ const s = showSeconds ? 0 : 0;
8044
+ const timeDisplay = showSeconds
8045
+ ? `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`
8046
+ : `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
8047
+ options.push({
8048
+ label: timeDisplay,
8049
+ value: `${h}:${m}:${s}`,
8050
+ hour: h,
8051
+ minute: m,
8052
+ second: s,
8053
+ searchText: timeDisplay,
8054
+ durationText,
8055
+ });
8056
+ }
7833
8057
  }
7834
- console.log('[DateTimePicker] ISO format - setting time on date:', {
7835
- h,
7836
- m,
7837
- s,
7838
- showSeconds,
7839
- });
7840
- if (h !== null)
7841
- newDate.setHours(h);
7842
- if (m !== null)
7843
- newDate.setMinutes(m);
7844
- newDate.setSeconds(s ?? 0);
7845
8058
  }
7846
8059
  else {
7847
- const data = timeData;
7848
- console.log('[DateTimePicker] Processing 12-hour format:', {
7849
- 'data !== undefined': data !== undefined,
7850
- 'data?.hour': data?.hour,
7851
- 'data?.minute': data?.minute,
7852
- 'data?.meridiem': data?.meridiem,
7853
- 'current hour12': hour12,
7854
- 'current minute': minute,
7855
- 'current meridiem': meridiem,
7856
- });
7857
- // Use timeData values if provided, otherwise fall back to current state
7858
- const h = data !== undefined ? data.hour : hour12;
7859
- const m = data !== undefined ? data.minute : minute;
7860
- const mer = data !== undefined ? data.meridiem : meridiem;
7861
- console.log('[DateTimePicker] Resolved time values:', { h, m, mer });
7862
- // If all time values are null, clear the value
7863
- if (h === null && m === null && mer === null) {
7864
- console.log('[DateTimePicker] All time values are null, clearing value');
7865
- onChange?.(undefined);
7866
- return;
8060
+ // Generate 12-hour format options
8061
+ for (let h = 1; h <= 12; h++) {
8062
+ for (let m = 0; m < 60; m += 15) {
8063
+ for (const mer of ['am', 'pm']) {
8064
+ // Convert 12-hour to 24-hour for comparison
8065
+ let hour24 = h;
8066
+ if (mer === 'am' && h === 12)
8067
+ hour24 = 0;
8068
+ else if (mer === 'pm' && h < 12)
8069
+ hour24 = h + 12;
8070
+ // Filter out times that would result in negative duration
8071
+ if (startDateTime && selectedDate && shouldFilterByDate) {
8072
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8073
+ const optionDateTime = selectedDateObj
8074
+ .hour(hour24)
8075
+ .minute(m)
8076
+ .second(0)
8077
+ .millisecond(0);
8078
+ if (optionDateTime.isBefore(startDateTime)) {
8079
+ continue;
8080
+ }
8081
+ }
8082
+ // Calculate duration if startTime is provided
8083
+ let durationText;
8084
+ if (startDateTime && selectedDate) {
8085
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8086
+ const optionDateTime = selectedDateObj
8087
+ .hour(hour24)
8088
+ .minute(m)
8089
+ .second(0)
8090
+ .millisecond(0);
8091
+ if (optionDateTime.isValid() &&
8092
+ optionDateTime.isAfter(startDateTime)) {
8093
+ const diffMs = optionDateTime.diff(startDateTime);
8094
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
8095
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
8096
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
8097
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
8098
+ let diffText = '';
8099
+ if (diffHours > 0) {
8100
+ diffText = `${diffHours}h ${diffMinutes}m`;
8101
+ }
8102
+ else if (diffMinutes > 0) {
8103
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
8104
+ }
8105
+ else {
8106
+ diffText = `${diffSeconds}s`;
8107
+ }
8108
+ durationText = `+${diffText}`;
8109
+ }
8110
+ }
8111
+ }
8112
+ const hourDisplay = h.toString();
8113
+ const minuteDisplay = m.toString().padStart(2, '0');
8114
+ const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
8115
+ options.push({
8116
+ label: timeDisplay,
8117
+ value: `${h}:${m}:${mer}`,
8118
+ hour: h,
8119
+ minute: m,
8120
+ meridiem: mer,
8121
+ searchText: timeDisplay,
8122
+ durationText,
8123
+ });
8124
+ }
8125
+ }
7867
8126
  }
7868
- console.log('[DateTimePicker] 12-hour format - converting time:', {
7869
- h,
7870
- m,
7871
- mer,
8127
+ // Sort 12-hour options by time
8128
+ return options.sort((a, b) => {
8129
+ const a12 = a;
8130
+ const b12 = b;
8131
+ let hour24A = a12.hour;
8132
+ if (a12.meridiem === 'am' && a12.hour === 12)
8133
+ hour24A = 0;
8134
+ else if (a12.meridiem === 'pm' && a12.hour < 12)
8135
+ hour24A = a12.hour + 12;
8136
+ let hour24B = b12.hour;
8137
+ if (b12.meridiem === 'am' && b12.hour === 12)
8138
+ hour24B = 0;
8139
+ else if (b12.meridiem === 'pm' && b12.hour < 12)
8140
+ hour24B = b12.hour + 12;
8141
+ if (hour24A !== hour24B) {
8142
+ return hour24A - hour24B;
8143
+ }
8144
+ return a12.minute - b12.minute;
7872
8145
  });
7873
- if (h !== null && mer !== null) {
7874
- let hour24 = h;
7875
- if (mer === 'am' && h === 12)
7876
- hour24 = 0;
7877
- else if (mer === 'pm' && h < 12)
7878
- hour24 = h + 12;
7879
- console.log('[DateTimePicker] Converted to 24-hour:', {
7880
- h,
7881
- mer,
7882
- hour24,
7883
- });
7884
- newDate.setHours(hour24);
8146
+ }
8147
+ return options;
8148
+ }, [startTime, selectedDate, tz, is24Hour, showSeconds]);
8149
+ // Time picker combobox setup
8150
+ const itemToString = React.useMemo(() => {
8151
+ return (item) => {
8152
+ return item.searchText;
8153
+ };
8154
+ }, []);
8155
+ const { contains } = react.useFilter({ sensitivity: 'base' });
8156
+ const customTimeFilter = React.useMemo(() => {
8157
+ if (is24Hour) {
8158
+ return contains;
8159
+ }
8160
+ return (itemText, filterText) => {
8161
+ if (!filterText) {
8162
+ return true;
7885
8163
  }
7886
- else {
7887
- console.log('[DateTimePicker] Skipping hour update - h or mer is null:', {
7888
- h,
7889
- mer,
7890
- });
8164
+ const lowerItemText = itemText.toLowerCase();
8165
+ const lowerFilterText = filterText.toLowerCase();
8166
+ if (lowerItemText.includes(lowerFilterText)) {
8167
+ return true;
7891
8168
  }
7892
- if (m !== null) {
7893
- newDate.setMinutes(m);
8169
+ const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
8170
+ if (!item || !('meridiem' in item)) {
8171
+ return false;
7894
8172
  }
7895
- else {
7896
- console.log('[DateTimePicker] Skipping minute update - m is null');
8173
+ let hour24 = item.hour;
8174
+ if (item.meridiem === 'am' && item.hour === 12)
8175
+ hour24 = 0;
8176
+ else if (item.meridiem === 'pm' && item.hour < 12)
8177
+ hour24 = item.hour + 12;
8178
+ const hour24Str = hour24.toString().padStart(2, '0');
8179
+ const minuteStr = item.minute.toString().padStart(2, '0');
8180
+ const formats = [
8181
+ `${hour24Str}:${minuteStr}`,
8182
+ `${hour24Str}${minuteStr}`,
8183
+ hour24Str,
8184
+ `${hour24}:${minuteStr}`,
8185
+ hour24.toString(),
8186
+ ];
8187
+ return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
8188
+ lowerFilterText.includes(format.toLowerCase()));
8189
+ };
8190
+ }, [timeOptions, is24Hour, contains]);
8191
+ const { collection, filter } = react.useListCollection({
8192
+ initialItems: timeOptions,
8193
+ itemToString: itemToString,
8194
+ itemToValue: (item) => item.value,
8195
+ filter: customTimeFilter,
8196
+ });
8197
+ // Get current value string for combobox (must match option.value format)
8198
+ const currentTimeValue = React.useMemo(() => {
8199
+ if (is24Hour) {
8200
+ if (hour === null || minute === null) {
8201
+ return '';
7897
8202
  }
7898
- newDate.setSeconds(0);
7899
- }
7900
- const finalISO = dayjs(newDate).tz(timezone).toISOString();
7901
- console.log('[DateTimePicker] Final ISO string to emit:', {
7902
- newDate: newDate.toISOString(),
7903
- timezone,
7904
- finalISO,
7905
- });
7906
- onChange?.(finalISO);
7907
- };
7908
- const handleClear = () => {
7909
- setSelectedDate('');
7910
- // Reset to default time values instead of clearing them
7911
- if (format === 'iso-date-time') {
7912
- setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
7913
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7914
- setSecond(showSeconds
7915
- ? defaultTime
7916
- ? defaultTime.second ?? 0
7917
- : 0
7918
- : null);
8203
+ const s = second ?? 0;
8204
+ return `${hour}:${minute}:${s}`;
7919
8205
  }
7920
8206
  else {
7921
- setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
7922
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7923
- setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
7924
- }
7925
- onChange?.(undefined);
7926
- };
7927
- const isISO = format === 'iso-date-time';
7928
- // Determine minDate: prioritize explicit minDate prop, then fall back to startTime
7929
- const effectiveMinDate = minDate
7930
- ? minDate
7931
- : normalizedStartTime && dayjs(normalizedStartTime).tz(timezone).isValid()
7932
- ? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
7933
- : undefined;
7934
- // Log current state before render
7935
- React.useEffect(() => {
7936
- console.log('[DateTimePicker] Current state before render:', {
7937
- isISO,
7938
- hour12,
7939
- minute,
7940
- meridiem,
7941
- hour24,
7942
- second,
7943
- selectedDate,
7944
- normalizedStartTime,
7945
- timezone,
7946
- });
7947
- }, [
7948
- isISO,
7949
- hour12,
7950
- minute,
7951
- meridiem,
7952
- hour24,
7953
- second,
7954
- selectedDate,
7955
- normalizedStartTime,
7956
- timezone,
7957
- ]);
7958
- // Compute display text from current state
7959
- const displayText = React.useMemo(() => {
7960
- if (!selectedDate)
7961
- return null;
7962
- const dateObj = dayjs.tz(selectedDate, timezone);
7963
- if (!dateObj.isValid())
7964
- return null;
7965
- if (isISO) {
7966
- // For ISO format, use hour24, minute, second
7967
- if (hour24 === null || minute === null)
7968
- return null;
7969
- const dateTimeObj = dateObj
7970
- .hour(hour24)
7971
- .minute(minute)
7972
- .second(second ?? 0);
7973
- return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
8207
+ if (hour === null || minute === null || meridiem === null) {
8208
+ return '';
8209
+ }
8210
+ return `${hour}:${minute}:${meridiem}`;
8211
+ }
8212
+ }, [hour, minute, second, meridiem, is24Hour]);
8213
+ // Parse custom time input formats like "1400", "2pm", "14:00", "2:00 PM"
8214
+ const parseCustomTimeInput = (input) => {
8215
+ if (!input || !input.trim()) {
8216
+ return { hour: null, minute: null, second: null, meridiem: null };
8217
+ }
8218
+ const trimmed = input.trim().toLowerCase();
8219
+ // Try parsing 4-digit format without colon: "1400" -> 14:00
8220
+ const fourDigitMatch = trimmed.match(/^(\d{4})$/);
8221
+ if (fourDigitMatch) {
8222
+ const digits = fourDigitMatch[1];
8223
+ const hour = parseInt(digits.substring(0, 2), 10);
8224
+ const minute = parseInt(digits.substring(2, 4), 10);
8225
+ if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
8226
+ if (is24Hour) {
8227
+ return { hour, minute, second: 0, meridiem: null };
8228
+ }
8229
+ else {
8230
+ // Convert to 12-hour format
8231
+ let hour12 = hour;
8232
+ let meridiem;
8233
+ if (hour === 0) {
8234
+ hour12 = 12;
8235
+ meridiem = 'am';
8236
+ }
8237
+ else if (hour === 12) {
8238
+ hour12 = 12;
8239
+ meridiem = 'pm';
8240
+ }
8241
+ else if (hour > 12) {
8242
+ hour12 = hour - 12;
8243
+ meridiem = 'pm';
8244
+ }
8245
+ else {
8246
+ hour12 = hour;
8247
+ meridiem = 'am';
8248
+ }
8249
+ return { hour: hour12, minute, second: null, meridiem };
8250
+ }
8251
+ }
7974
8252
  }
7975
- else {
7976
- // For 12-hour format, use hour12, minute, meridiem
7977
- if (hour12 === null || minute === null || meridiem === null)
7978
- return null;
7979
- // Convert to 24-hour format for dayjs
7980
- let hour24Value = hour12;
7981
- if (meridiem === 'am' && hour12 === 12)
7982
- hour24Value = 0;
7983
- else if (meridiem === 'pm' && hour12 < 12)
7984
- hour24Value = hour12 + 12;
7985
- const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
7986
- return dateTimeObj.format('YYYY-MM-DD hh:mm A');
8253
+ // Try parsing hour with meridiem: "2pm", "14pm", "2am"
8254
+ const hourMeridiemMatch = trimmed.match(/^(\d{1,2})\s*(am|pm)$/);
8255
+ if (hourMeridiemMatch && !is24Hour) {
8256
+ const hour12 = parseInt(hourMeridiemMatch[1], 10);
8257
+ const meridiem = hourMeridiemMatch[2];
8258
+ if (hour12 >= 1 && hour12 <= 12) {
8259
+ return { hour: hour12, minute: 0, second: null, meridiem };
8260
+ }
7987
8261
  }
7988
- }, [
7989
- selectedDate,
7990
- isISO,
7991
- hour12,
7992
- minute,
7993
- meridiem,
7994
- hour24,
7995
- second,
7996
- showSeconds,
7997
- timezone,
7998
- ]);
7999
- const timezoneOffset = React.useMemo(() => {
8000
- if (!selectedDate)
8001
- return null;
8002
- const dateObj = dayjs.tz(selectedDate, timezone);
8003
- return dateObj.isValid() ? dateObj.format('Z') : null;
8004
- }, [selectedDate, timezone]);
8005
- return (jsxRuntime.jsxs(react.Flex, { direction: "column", gap: 2, children: [jsxRuntime.jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
8006
- if (date) {
8007
- handleDateChange(date);
8262
+ // Try parsing 24-hour format with hour only: "14" -> 14:00
8263
+ const hourOnlyMatch = trimmed.match(/^(\d{1,2})$/);
8264
+ if (hourOnlyMatch && is24Hour) {
8265
+ const hour = parseInt(hourOnlyMatch[1], 10);
8266
+ if (hour >= 0 && hour <= 23) {
8267
+ return { hour, minute: 0, second: 0, meridiem: null };
8268
+ }
8269
+ }
8270
+ // Try parsing standard formats: "14:00", "2:00 PM"
8271
+ const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
8272
+ const match24 = trimmed.match(time24Pattern);
8273
+ if (match24) {
8274
+ const hour24 = parseInt(match24[1], 10);
8275
+ const minute = parseInt(match24[2], 10);
8276
+ const second = match24[3] ? parseInt(match24[3], 10) : 0;
8277
+ if (hour24 >= 0 &&
8278
+ hour24 <= 23 &&
8279
+ minute >= 0 &&
8280
+ minute <= 59 &&
8281
+ second >= 0 &&
8282
+ second <= 59) {
8283
+ if (is24Hour) {
8284
+ return { hour: hour24, minute, second, meridiem: null };
8285
+ }
8286
+ else {
8287
+ // Convert to 12-hour format
8288
+ let hour12 = hour24;
8289
+ let meridiem;
8290
+ if (hour24 === 0) {
8291
+ hour12 = 12;
8292
+ meridiem = 'am';
8293
+ }
8294
+ else if (hour24 === 12) {
8295
+ hour12 = 12;
8296
+ meridiem = 'pm';
8297
+ }
8298
+ else if (hour24 > 12) {
8299
+ hour12 = hour24 - 12;
8300
+ meridiem = 'pm';
8008
8301
  }
8009
8302
  else {
8010
- setSelectedDate('');
8011
- onChange?.(undefined);
8303
+ hour12 = hour24;
8304
+ meridiem = 'am';
8305
+ }
8306
+ return { hour: hour12, minute, second: null, meridiem };
8307
+ }
8308
+ }
8309
+ }
8310
+ // Try parsing 12-hour format: "2:00 PM", "2:00PM"
8311
+ const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm)$/;
8312
+ const match12 = trimmed.match(time12Pattern);
8313
+ if (match12 && !is24Hour) {
8314
+ const hour12 = parseInt(match12[1], 10);
8315
+ const minute = parseInt(match12[2], 10);
8316
+ const second = match12[3] ? parseInt(match12[3], 10) : null;
8317
+ const meridiem = match12[4];
8318
+ if (hour12 >= 1 &&
8319
+ hour12 <= 12 &&
8320
+ minute >= 0 &&
8321
+ minute <= 59 &&
8322
+ (second === null || (second >= 0 && second <= 59))) {
8323
+ return { hour: hour12, minute, second, meridiem };
8324
+ }
8325
+ }
8326
+ return { hour: null, minute: null, second: null, meridiem: null };
8327
+ };
8328
+ const handleTimeValueChange = (details) => {
8329
+ if (details.value.length === 0) {
8330
+ handleTimeChange(null, null, null, null);
8331
+ filter('');
8332
+ return;
8333
+ }
8334
+ const selectedValue = details.value[0];
8335
+ const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
8336
+ if (selectedOption) {
8337
+ filter('');
8338
+ if (is24Hour) {
8339
+ const opt24 = selectedOption;
8340
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
8341
+ }
8342
+ else {
8343
+ const opt12 = selectedOption;
8344
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
8345
+ }
8346
+ }
8347
+ };
8348
+ // Track the current input value for Enter key handling
8349
+ const [timeInputValue, setTimeInputValue] = React.useState('');
8350
+ const handleTimeInputChange = (details) => {
8351
+ // Store the input value and filter
8352
+ setTimeInputValue(details.inputValue);
8353
+ filter(details.inputValue);
8354
+ };
8355
+ const handleTimeInputKeyDown = (e) => {
8356
+ if (e.key === 'Enter') {
8357
+ e.preventDefault();
8358
+ // Use the stored input value
8359
+ const parsed = parseCustomTimeInput(timeInputValue);
8360
+ if (parsed.hour !== null && parsed.minute !== null) {
8361
+ if (is24Hour) {
8362
+ handleTimeChange(parsed.hour, parsed.minute, parsed.second, null);
8363
+ }
8364
+ else {
8365
+ if (parsed.meridiem !== null) {
8366
+ handleTimeChange(parsed.hour, parsed.minute, null, parsed.meridiem);
8012
8367
  }
8013
- }, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly: false }), jsxRuntime.jsxs(react.Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 2, children: [isISO ? (jsxRuntime.jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: showSeconds ? second : null, setSecond: showSeconds ? setSecond : () => { }, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })) : (jsxRuntime.jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsxRuntime.jsx(react.Icon, { as: fa6.FaTrash }) })] }), displayText && (jsxRuntime.jsxs(react.Flex, { gap: 2, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: displayText }), timezoneOffset && (jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezoneOffset })), jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
8368
+ }
8369
+ // Clear the filter and input value after applying
8370
+ filter('');
8371
+ setTimeInputValue('');
8372
+ // Close the popover if value is valid
8373
+ setTimePopoverOpen(false);
8374
+ }
8375
+ }
8376
+ };
8377
+ // Calendar rendering
8378
+ const renderCalendar = () => {
8379
+ const { calendars, getBackProps, getForwardProps, getDateProps } = calendarProps;
8380
+ if (calendars.length === 0)
8381
+ return null;
8382
+ const calendar = calendars[0];
8383
+ return (jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsxs(react.Grid, { templateColumns: 'repeat(4, auto)', justifyContent: 'center', children: [jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getBackProps({ offset: 12 }), children: '<<' }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getBackProps(), children: backButtonLabel }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getForwardProps(), children: forwardButtonLabel }), jsxRuntime.jsx(react.Button, { variant: 'ghost', ...getForwardProps({ offset: 12 }), children: '>>' })] }), jsxRuntime.jsx(react.Grid, { justifyContent: 'center', children: jsxRuntime.jsxs(react.Text, { children: [monthNamesShort[calendar.month], " ", calendar.year] }) }), jsxRuntime.jsx(react.Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: [0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
8384
+ return (jsxRuntime.jsx(react.Text, { textAlign: 'center', fontWeight: "semibold", minW: "40px", children: weekdayNamesShort[weekdayNum] }, `header-${weekdayNum}`));
8385
+ }) }), calendar.weeks.map((week, weekIndex) => (jsxRuntime.jsx(react.Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: week.map((dateObj, dayIndex) => {
8386
+ if (!dateObj) {
8387
+ return (jsxRuntime.jsx("div", { style: { minWidth: '40px' } }, `empty-${dayIndex}`));
8388
+ }
8389
+ const { date, selected, selectable, isCurrentMonth } = dateObj;
8390
+ const dateProps = getDateProps({
8391
+ dateObj,
8392
+ });
8393
+ return (jsxRuntime.jsx(react.Button, { variant: selected ? 'solid' : 'ghost', colorPalette: selected ? 'blue' : undefined, size: "sm", minW: "40px", disabled: !selectable, opacity: isCurrentMonth ? 1 : 0.4, ...dateProps, children: date.getDate() }, `${date.getTime()}`));
8394
+ }) }, `week-${weekIndex}`)))] }));
8395
+ };
8396
+ return (jsxRuntime.jsxs(react.Flex, { direction: "row", gap: 2, align: "center", children: [jsxRuntime.jsxs(react.Popover.Root, { open: datePopoverOpen, onOpenChange: (e) => setDatePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { size: "sm", variant: "outline", onClick: () => setDatePopoverOpen(true), justifyContent: "start", children: [jsxRuntime.jsx(md.MdDateRange, {}), dateDisplayText] }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsx(react.InputGroup, { endElement: jsxRuntime.jsxs(react.Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsxRuntime.jsx(md.MdDateRange, {}) }) }), jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: renderCalendar() }) }) })] }), children: jsxRuntime.jsx(react.Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxRuntime.jsxs(react.Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsxs(react.Grid, { gap: 4, children: [jsxRuntime.jsx(react.InputGroup, { endElement: jsxRuntime.jsxs(react.Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsxRuntime.jsx(md.MdDateRange, {}) }) }), jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: renderCalendar() }) }) })] }), children: jsxRuntime.jsx(react.Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxRuntime.jsxs(react.Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }))] }), jsxRuntime.jsxs(react.Popover.Root, { open: timePopoverOpen, onOpenChange: (e) => setTimePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { size: "sm", variant: "outline", onClick: () => setTimePopoverOpen(true), justifyContent: "start", children: [jsxRuntime.jsx(bs.BsClock, {}), timeDisplayText] }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "300px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
8397
+ (is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: true, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: timePickerLabels?.emptyMessage ??
8398
+ 'No time found' }), collection.items.map((item) => {
8399
+ const option = item;
8400
+ return (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { justify: "space-between", align: "center", w: "100%", children: [jsxRuntime.jsx(react.Text, { children: option.label }), option.durationText && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, option.value));
8401
+ })] }) }) })] }) }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "300px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
8402
+ (is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: true, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: timePickerLabels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => {
8403
+ const option = item;
8404
+ return (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { justify: "space-between", align: "center", w: "100%", children: [jsxRuntime.jsx(react.Text, { children: option.label }), option.durationText && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, option.value));
8405
+ })] }) }) })] }) }) }) }) }))] }), showTimezoneSelector && (jsxRuntime.jsxs(react.Popover.Root, { open: timezonePopoverOpen, onOpenChange: (e) => setTimezonePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.Button, { size: "sm", variant: "outline", onClick: () => setTimezonePopoverOpen(true), justifyContent: "start", children: timezoneDisplayText || 'Select timezone' }) }), portalled ? (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "250px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Select.Root, { size: "sm", collection: timezoneCollection, value: timezoneOffset ? [timezoneOffset] : [], onValueChange: (e) => {
8406
+ const newOffset = e.value[0];
8407
+ if (newOffset) {
8408
+ setTimezoneOffset(newOffset);
8409
+ // Update date-time with new offset
8410
+ if (selectedDate &&
8411
+ hour !== null &&
8412
+ minute !== null) {
8413
+ updateDateTime(selectedDate, hour, minute, second, meridiem);
8414
+ }
8415
+ // Close popover after selection
8416
+ setTimezonePopoverOpen(false);
8417
+ }
8418
+ }, children: [jsxRuntime.jsxs(react.Select.Control, { children: [jsxRuntime.jsx(react.Select.Trigger, {}), jsxRuntime.jsx(react.Select.IndicatorGroup, { children: jsxRuntime.jsx(react.Select.Indicator, {}) })] }), jsxRuntime.jsx(react.Select.Positioner, { children: jsxRuntime.jsx(react.Select.Content, { children: timezoneCollection.items.map((item) => (jsxRuntime.jsxs(react.Select.Item, { item: item, children: [jsxRuntime.jsx(react.Select.ItemText, { children: item.label }), jsxRuntime.jsx(react.Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }) })) : (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "250px", children: jsxRuntime.jsx(react.Popover.Body, { p: 4, children: jsxRuntime.jsx(react.Grid, { gap: 2, children: jsxRuntime.jsxs(react.Select.Root, { size: "sm", collection: timezoneCollection, value: timezoneOffset ? [timezoneOffset] : [], onValueChange: (e) => {
8419
+ const newOffset = e.value[0];
8420
+ if (newOffset) {
8421
+ setTimezoneOffset(newOffset);
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: [jsxRuntime.jsxs(react.Select.Control, { children: [jsxRuntime.jsx(react.Select.Trigger, {}), jsxRuntime.jsx(react.Select.IndicatorGroup, { children: jsxRuntime.jsx(react.Select.Indicator, {}) })] }), jsxRuntime.jsx(react.Select.Positioner, { children: jsxRuntime.jsx(react.Select.Content, { children: timezoneCollection.items.map((item) => (jsxRuntime.jsxs(react.Select.Item, { item: item, children: [jsxRuntime.jsx(react.Select.ItemText, { children: item.label }), jsxRuntime.jsx(react.Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }))] }))] }));
8014
8432
  }
8015
8433
 
8016
8434
  dayjs.extend(utc);
@@ -8024,11 +8442,12 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
8024
8442
  dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', } = schema;
8025
8443
  const isRequired = required?.some((columnId) => columnId === column);
8026
8444
  const colLabel = formI18n.colLabel;
8027
- const [open, setOpen] = React.useState(false);
8445
+ React.useState(false);
8028
8446
  const selectedDate = watch(colLabel);
8029
- const displayDate = selectedDate && dayjs(selectedDate).tz(timezone).isValid()
8447
+ selectedDate && dayjs(selectedDate).tz(timezone).isValid()
8030
8448
  ? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
8031
8449
  : '';
8450
+ // Set default date on mount if no value exists
8032
8451
  const dateTimePickerLabelsConfig = {
8033
8452
  monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
8034
8453
  'January',
@@ -8070,9 +8489,7 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
8070
8489
  }
8071
8490
  }, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels }));
8072
8491
  return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
8073
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
8074
- setOpen(true);
8075
- }, justifyContent: 'start', children: [jsxRuntime.jsx(md.MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
8492
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: dateTimePickerContent }));
8076
8493
  };
8077
8494
 
8078
8495
  const SchemaRenderer = ({ schema, prefix, column, }) => {