@bsol-oss/react-datatable5 13.0.1-beta.8 → 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.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, CheckboxCard as CheckboxCard$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Clipboard, Badge, Link, Tag as Tag$1, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, useCombobox, Show, Skeleton, NumberInput, RadioCard, CheckboxGroup, InputGroup as InputGroup$1, Center, Heading, Stack } from '@chakra-ui/react';
2
+ import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, CheckboxCard as CheckboxCard$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Clipboard, Badge, Link, Tag as Tag$1, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, useCombobox, Show, Skeleton, NumberInput, RadioCard, CheckboxGroup, InputGroup as InputGroup$1, Select, Center, Heading, Stack } from '@chakra-ui/react';
3
3
  import { AiOutlineColumnWidth } from 'react-icons/ai';
4
4
  import * as React from 'react';
5
5
  import { createContext, useContext, useState, useMemo, useCallback, useEffect, useRef, forwardRef } from 'react';
6
6
  import { LuX, LuCheck, LuChevronRight, LuCopy, LuExternalLink, LuSearch, LuImage, LuFile } from 'react-icons/lu';
7
7
  import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineChecklist, MdClear, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdDateRange } from 'react-icons/md';
8
- import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
8
+ import { FaUpDown, FaGripLinesVertical } from 'react-icons/fa6';
9
9
  import { BiDownArrow, BiUpArrow, BiX, BiError } from 'react-icons/bi';
10
10
  import { CgClose, CgTrash } from 'react-icons/cg';
11
11
  import { HiMiniEllipsisHorizontal, HiChevronLeft, HiChevronRight } from 'react-icons/hi2';
@@ -28,9 +28,9 @@ import { FormProvider, useFormContext, useForm as useForm$1 } from 'react-hook-f
28
28
  import Ajv from 'ajv';
29
29
  import addFormats from 'ajv-formats';
30
30
  import dayjs from 'dayjs';
31
- import utc from 'dayjs/plugin/utc';
32
- import timezone from 'dayjs/plugin/timezone';
33
31
  import customParseFormat from 'dayjs/plugin/customParseFormat';
32
+ import timezone from 'dayjs/plugin/timezone';
33
+ import utc from 'dayjs/plugin/utc';
34
34
  import { TiDeleteOutline } from 'react-icons/ti';
35
35
  import { rankItem } from '@tanstack/match-sorter-utils';
36
36
 
@@ -4660,7 +4660,7 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
4660
4660
  }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, showHelperButtons = true, }) {
4661
4661
  const [open, setOpen] = useState(false);
4662
4662
  const [inputValue, setInputValue] = useState('');
4663
- // Update input value when prop value changes
4663
+ // Sync inputValue with value prop changes
4664
4664
  useEffect(() => {
4665
4665
  if (value) {
4666
4666
  const formatted = typeof value === 'string'
@@ -4673,7 +4673,7 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
4673
4673
  else {
4674
4674
  setInputValue('');
4675
4675
  }
4676
- }, [value, displayFormat, timezone]);
4676
+ }, [value, timezone, displayFormat]);
4677
4677
  // Convert value to Date object for DatePicker
4678
4678
  const selectedDate = value
4679
4679
  ? typeof value === 'string'
@@ -4755,7 +4755,14 @@ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateF
4755
4755
  }
4756
4756
  };
4757
4757
  const handleDateSelected = ({ date }) => {
4758
+ console.debug('[DatePickerInput] handleDateSelected called:', {
4759
+ date: date.toISOString(),
4760
+ timezone,
4761
+ dateFormat,
4762
+ formattedDate: dayjs(date).tz(timezone).format(dateFormat),
4763
+ });
4758
4764
  const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
4765
+ console.debug('[DatePickerInput] Calling onChange with formatted date:', formattedDate);
4759
4766
  onChange?.(formattedDate);
4760
4767
  setOpen(false);
4761
4768
  };
@@ -6742,11 +6749,193 @@ const TextAreaInput = ({ column, schema, prefix, }) => {
6742
6749
 
6743
6750
  dayjs.extend(utc);
6744
6751
  dayjs.extend(timezone);
6745
- const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem, onChange = () => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6746
- placeholder: 'hh:mm AM/PM',
6747
- emptyMessage: 'No time found',
6748
- }, }) => {
6749
- // Generate time options (every 15 minutes in 12-hour format)
6752
+ const TimePicker$1 = (props) => {
6753
+ const { format = '12h', value: controlledValue, onChange: controlledOnChange, hour: uncontrolledHour, setHour: uncontrolledSetHour, minute: uncontrolledMinute, setMinute: uncontrolledSetMinute, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
6754
+ placeholder: format === '24h' ? 'HH:mm:ss' : 'hh:mm AM/PM',
6755
+ emptyMessage: 'No time found',
6756
+ }, onTimeChange, } = props;
6757
+ const is24Hour = format === '24h';
6758
+ const uncontrolledMeridiem = is24Hour ? undefined : props.meridiem;
6759
+ const uncontrolledSetMeridiem = is24Hour ? undefined : props.setMeridiem;
6760
+ const uncontrolledSecond = is24Hour ? props.second : undefined;
6761
+ const uncontrolledSetSecond = is24Hour ? props.setSecond : undefined;
6762
+ // Determine if we're in controlled mode
6763
+ const isControlled = controlledValue !== undefined;
6764
+ // Parse time string to extract hour, minute, second, meridiem
6765
+ const parseTimeString = (timeStr) => {
6766
+ if (!timeStr || !timeStr.trim()) {
6767
+ return { hour: null, minute: null, second: null, meridiem: null };
6768
+ }
6769
+ // Remove timezone suffix if present (e.g., "14:30:00Z" -> "14:30:00")
6770
+ const timeWithoutTz = timeStr.replace(/[Z+-]\d{2}:?\d{2}$/, '').trim();
6771
+ // Try parsing 24-hour format: "HH:mm:ss" or "HH:mm"
6772
+ const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
6773
+ const match24 = timeWithoutTz.match(time24Pattern);
6774
+ if (match24) {
6775
+ const hour24 = parseInt(match24[1], 10);
6776
+ const minute = parseInt(match24[2], 10);
6777
+ const second = match24[3] ? parseInt(match24[3], 10) : 0;
6778
+ if (hour24 >= 0 &&
6779
+ hour24 <= 23 &&
6780
+ minute >= 0 &&
6781
+ minute <= 59 &&
6782
+ second >= 0 &&
6783
+ second <= 59) {
6784
+ if (is24Hour) {
6785
+ return { hour: hour24, minute, second, meridiem: null };
6786
+ }
6787
+ else {
6788
+ // Convert to 12-hour format
6789
+ let hour12 = hour24;
6790
+ let meridiem;
6791
+ if (hour24 === 0) {
6792
+ hour12 = 12;
6793
+ meridiem = 'am';
6794
+ }
6795
+ else if (hour24 === 12) {
6796
+ hour12 = 12;
6797
+ meridiem = 'pm';
6798
+ }
6799
+ else if (hour24 > 12) {
6800
+ hour12 = hour24 - 12;
6801
+ meridiem = 'pm';
6802
+ }
6803
+ else {
6804
+ hour12 = hour24;
6805
+ meridiem = 'am';
6806
+ }
6807
+ return { hour: hour12, minute, second: null, meridiem };
6808
+ }
6809
+ }
6810
+ }
6811
+ // Try parsing 12-hour format: "hh:mm AM/PM" or "hh:mm:ss AM/PM"
6812
+ const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm|AM|PM)$/i;
6813
+ const match12 = timeWithoutTz.match(time12Pattern);
6814
+ if (match12 && !is24Hour) {
6815
+ const hour12 = parseInt(match12[1], 10);
6816
+ const minute = parseInt(match12[2], 10);
6817
+ const second = match12[3] ? parseInt(match12[3], 10) : null;
6818
+ const meridiem = match12[4].toLowerCase();
6819
+ if (hour12 >= 1 &&
6820
+ hour12 <= 12 &&
6821
+ minute >= 0 &&
6822
+ minute <= 59 &&
6823
+ (second === null || (second >= 0 && second <= 59))) {
6824
+ return { hour: hour12, minute, second, meridiem };
6825
+ }
6826
+ }
6827
+ return { hour: null, minute: null, second: null, meridiem: null };
6828
+ };
6829
+ // Format time values to time string
6830
+ const formatTimeString = (hour, minute, second, meridiem) => {
6831
+ if (hour === null || minute === null) {
6832
+ return undefined;
6833
+ }
6834
+ if (is24Hour) {
6835
+ const h = hour.toString().padStart(2, '0');
6836
+ const m = minute.toString().padStart(2, '0');
6837
+ const s = (second ?? 0).toString().padStart(2, '0');
6838
+ return `${h}:${m}:${s}`;
6839
+ }
6840
+ else {
6841
+ if (meridiem === null) {
6842
+ return undefined;
6843
+ }
6844
+ const h = hour.toString();
6845
+ const m = minute.toString().padStart(2, '0');
6846
+ return `${h}:${m} ${meridiem.toUpperCase()}`;
6847
+ }
6848
+ };
6849
+ // Internal state for controlled mode
6850
+ const [internalHour, setInternalHour] = useState(null);
6851
+ const [internalMinute, setInternalMinute] = useState(null);
6852
+ const [internalSecond, setInternalSecond] = useState(null);
6853
+ const [internalMeridiem, setInternalMeridiem] = useState(null);
6854
+ // Use controlled or uncontrolled values
6855
+ const hour = isControlled ? internalHour : uncontrolledHour ?? null;
6856
+ const minute = isControlled ? internalMinute : uncontrolledMinute ?? null;
6857
+ const second = isControlled ? internalSecond : uncontrolledSecond ?? null;
6858
+ const meridiem = isControlled
6859
+ ? internalMeridiem
6860
+ : uncontrolledMeridiem ?? null;
6861
+ // Setters that work for both modes
6862
+ const setHour = isControlled
6863
+ ? setInternalHour
6864
+ : uncontrolledSetHour || (() => { });
6865
+ const setMinute = isControlled
6866
+ ? setInternalMinute
6867
+ : uncontrolledSetMinute || (() => { });
6868
+ const setSecond = isControlled
6869
+ ? setInternalSecond
6870
+ : uncontrolledSetSecond || (() => { });
6871
+ const setMeridiem = isControlled
6872
+ ? setInternalMeridiem
6873
+ : uncontrolledSetMeridiem || (() => { });
6874
+ // Sync internal state with controlled value prop
6875
+ const prevValueRef = useRef(controlledValue);
6876
+ useEffect(() => {
6877
+ if (!isControlled)
6878
+ return;
6879
+ if (prevValueRef.current === controlledValue) {
6880
+ return;
6881
+ }
6882
+ prevValueRef.current = controlledValue;
6883
+ const parsed = parseTimeString(controlledValue);
6884
+ setInternalHour(parsed.hour);
6885
+ setInternalMinute(parsed.minute);
6886
+ if (is24Hour) {
6887
+ setInternalSecond(parsed.second);
6888
+ }
6889
+ else {
6890
+ setInternalMeridiem(parsed.meridiem);
6891
+ }
6892
+ }, [controlledValue, isControlled, is24Hour]);
6893
+ // Wrapper onChange that calls both controlled and uncontrolled onChange
6894
+ const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
6895
+ if (isControlled) {
6896
+ const timeString = formatTimeString(newHour, newMinute, newSecond, newMeridiem);
6897
+ controlledOnChange?.(timeString);
6898
+ }
6899
+ else {
6900
+ // Call legacy onTimeChange if provided
6901
+ if (onTimeChange) {
6902
+ if (is24Hour) {
6903
+ const timeChange24h = onTimeChange;
6904
+ timeChange24h({
6905
+ hour: newHour,
6906
+ minute: newMinute,
6907
+ second: newSecond,
6908
+ });
6909
+ }
6910
+ else {
6911
+ const timeChange12h = onTimeChange;
6912
+ timeChange12h({
6913
+ hour: newHour,
6914
+ minute: newMinute,
6915
+ meridiem: newMeridiem,
6916
+ });
6917
+ }
6918
+ }
6919
+ }
6920
+ };
6921
+ const [inputValue, setInputValue] = useState('');
6922
+ // Sync inputValue with current time
6923
+ useEffect(() => {
6924
+ if (is24Hour && second !== undefined) {
6925
+ if (hour !== null && minute !== null && second !== null) {
6926
+ const formatted = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
6927
+ setInputValue(formatted);
6928
+ }
6929
+ else {
6930
+ setInputValue('');
6931
+ }
6932
+ }
6933
+ else {
6934
+ // 12-hour format - input is managed by combobox
6935
+ setInputValue('');
6936
+ }
6937
+ }, [hour, minute, second, is24Hour]);
6938
+ // Generate time options based on format
6750
6939
  const timeOptions = useMemo(() => {
6751
6940
  const options = [];
6752
6941
  // Get start time for comparison if provided
@@ -6757,32 +6946,25 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6757
6946
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6758
6947
  if (startDateObj.isValid() && selectedDateObj.isValid()) {
6759
6948
  startDateTime = startDateObj;
6760
- // Only filter if dates are the same
6761
6949
  shouldFilterByDate =
6762
6950
  startDateObj.format('YYYY-MM-DD') ===
6763
6951
  selectedDateObj.format('YYYY-MM-DD');
6764
6952
  }
6765
6953
  }
6766
- // Generate 12-hour format options (1-12 for hours, AM/PM)
6767
- for (let h = 1; h <= 12; h++) {
6768
- for (let m = 0; m < 60; m += 15) {
6769
- for (const mer of ['am', 'pm']) {
6770
- // Convert 12-hour to 24-hour for comparison
6771
- let hour24 = h;
6772
- if (mer === 'am' && h === 12)
6773
- hour24 = 0;
6774
- else if (mer === 'pm' && h < 12)
6775
- hour24 = h + 12;
6776
- // Filter out times that would result in negative duration (only when dates are the same)
6954
+ if (is24Hour) {
6955
+ // Generate 24-hour format options (0-23 for hours)
6956
+ for (let h = 0; h < 24; h++) {
6957
+ for (let m = 0; m < 60; m += 15) {
6958
+ // Filter out times that would result in negative duration
6777
6959
  if (startDateTime && selectedDate && shouldFilterByDate) {
6778
6960
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6779
6961
  const optionDateTime = selectedDateObj
6780
- .hour(hour24)
6962
+ .hour(h)
6781
6963
  .minute(m)
6782
6964
  .second(0)
6783
6965
  .millisecond(0);
6784
6966
  if (optionDateTime.isBefore(startDateTime)) {
6785
- continue; // Skip this option as it would result in negative duration
6967
+ continue;
6786
6968
  }
6787
6969
  }
6788
6970
  // Calculate duration if startTime is provided
@@ -6790,7 +6972,7 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6790
6972
  if (startDateTime && selectedDate) {
6791
6973
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6792
6974
  const optionDateTime = selectedDateObj
6793
- .hour(hour24)
6975
+ .hour(h)
6794
6976
  .minute(m)
6795
6977
  .second(0)
6796
6978
  .millisecond(0);
@@ -6815,62 +6997,133 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6815
6997
  }
6816
6998
  }
6817
6999
  }
6818
- const hourDisplay = h.toString();
6819
- const minuteDisplay = m.toString().padStart(2, '0');
6820
- const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
7000
+ const timeDisplay = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`;
6821
7001
  options.push({
6822
7002
  label: timeDisplay,
6823
- value: `${h}:${m}:${mer}`,
7003
+ value: `${h}:${m}:0`,
6824
7004
  hour: h,
6825
7005
  minute: m,
6826
- meridiem: mer,
6827
- searchText: timeDisplay, // Use base time without duration for searching
7006
+ second: 0,
7007
+ searchText: timeDisplay,
6828
7008
  durationText,
6829
7009
  });
6830
7010
  }
6831
7011
  }
6832
7012
  }
6833
- // Sort options by time (convert to 24-hour for proper chronological sorting)
6834
- return options.sort((a, b) => {
6835
- // Convert 12-hour to 24-hour for comparison
6836
- let hour24A = a.hour;
6837
- if (a.meridiem === 'am' && a.hour === 12)
6838
- hour24A = 0;
6839
- else if (a.meridiem === 'pm' && a.hour < 12)
6840
- hour24A = a.hour + 12;
6841
- let hour24B = b.hour;
6842
- if (b.meridiem === 'am' && b.hour === 12)
6843
- hour24B = 0;
6844
- else if (b.meridiem === 'pm' && b.hour < 12)
6845
- hour24B = b.hour + 12;
6846
- // Compare by hour first, then minute
6847
- if (hour24A !== hour24B) {
6848
- return hour24A - hour24B;
6849
- }
6850
- return a.minute - b.minute;
6851
- });
6852
- }, [startTime, selectedDate, timezone]);
7013
+ else {
7014
+ // Generate 12-hour format options (1-12 for hours, AM/PM)
7015
+ for (let h = 1; h <= 12; h++) {
7016
+ for (let m = 0; m < 60; m += 15) {
7017
+ for (const mer of ['am', 'pm']) {
7018
+ // Convert 12-hour to 24-hour for comparison
7019
+ let hour24 = h;
7020
+ if (mer === 'am' && h === 12)
7021
+ hour24 = 0;
7022
+ else if (mer === 'pm' && h < 12)
7023
+ hour24 = h + 12;
7024
+ // Filter out times that would result in negative duration
7025
+ if (startDateTime && selectedDate && shouldFilterByDate) {
7026
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
7027
+ const optionDateTime = selectedDateObj
7028
+ .hour(hour24)
7029
+ .minute(m)
7030
+ .second(0)
7031
+ .millisecond(0);
7032
+ if (optionDateTime.isBefore(startDateTime)) {
7033
+ continue;
7034
+ }
7035
+ }
7036
+ // Calculate duration if startTime is provided
7037
+ let durationText;
7038
+ if (startDateTime && selectedDate) {
7039
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
7040
+ const optionDateTime = selectedDateObj
7041
+ .hour(hour24)
7042
+ .minute(m)
7043
+ .second(0)
7044
+ .millisecond(0);
7045
+ if (optionDateTime.isValid() &&
7046
+ optionDateTime.isAfter(startDateTime)) {
7047
+ const diffMs = optionDateTime.diff(startDateTime);
7048
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
7049
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
7050
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
7051
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
7052
+ let diffText = '';
7053
+ if (diffHours > 0) {
7054
+ diffText = `${diffHours}h ${diffMinutes}m`;
7055
+ }
7056
+ else if (diffMinutes > 0) {
7057
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
7058
+ }
7059
+ else {
7060
+ diffText = `${diffSeconds}s`;
7061
+ }
7062
+ durationText = `+${diffText}`;
7063
+ }
7064
+ }
7065
+ }
7066
+ const hourDisplay = h.toString();
7067
+ const minuteDisplay = m.toString().padStart(2, '0');
7068
+ const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
7069
+ options.push({
7070
+ label: timeDisplay,
7071
+ value: `${h}:${m}:${mer}`,
7072
+ hour: h,
7073
+ minute: m,
7074
+ meridiem: mer,
7075
+ searchText: timeDisplay,
7076
+ durationText,
7077
+ });
7078
+ }
7079
+ }
7080
+ }
7081
+ // Sort 12-hour options by time (convert to 24-hour for proper chronological sorting)
7082
+ return options.sort((a, b) => {
7083
+ const a12 = a;
7084
+ const b12 = b;
7085
+ let hour24A = a12.hour;
7086
+ if (a12.meridiem === 'am' && a12.hour === 12)
7087
+ hour24A = 0;
7088
+ else if (a12.meridiem === 'pm' && a12.hour < 12)
7089
+ hour24A = a12.hour + 12;
7090
+ let hour24B = b12.hour;
7091
+ if (b12.meridiem === 'am' && b12.hour === 12)
7092
+ hour24B = 0;
7093
+ else if (b12.meridiem === 'pm' && b12.hour < 12)
7094
+ hour24B = b12.hour + 12;
7095
+ if (hour24A !== hour24B) {
7096
+ return hour24A - hour24B;
7097
+ }
7098
+ return a12.minute - b12.minute;
7099
+ });
7100
+ }
7101
+ return options;
7102
+ }, [startTime, selectedDate, timezone, is24Hour]);
6853
7103
  // itemToString returns only the clean display text (no metadata)
6854
7104
  const itemToString = useMemo(() => {
6855
7105
  return (item) => {
6856
- return item.searchText; // Clean display text only
7106
+ return item.searchText;
6857
7107
  };
6858
7108
  }, []);
6859
- // Custom filter function that filters by time and supports 24-hour format input
7109
+ // Custom filter function
7110
+ const { contains } = useFilter({ sensitivity: 'base' });
6860
7111
  const customTimeFilter = useMemo(() => {
7112
+ if (is24Hour) {
7113
+ return contains; // Simple contains filter for 24-hour format
7114
+ }
7115
+ // For 12-hour format, support both 12-hour and 24-hour input
6861
7116
  return (itemText, filterText) => {
6862
7117
  if (!filterText) {
6863
- return true; // Show all items when no filter
7118
+ return true;
6864
7119
  }
6865
7120
  const lowerItemText = itemText.toLowerCase();
6866
7121
  const lowerFilterText = filterText.toLowerCase();
6867
- // First, try matching against the display text (12-hour format)
6868
7122
  if (lowerItemText.includes(lowerFilterText)) {
6869
7123
  return true;
6870
7124
  }
6871
- // Find the corresponding item to check 24-hour format matches
6872
7125
  const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
6873
- if (!item) {
7126
+ if (!item || !('meridiem' in item)) {
6874
7127
  return false;
6875
7128
  }
6876
7129
  // Convert item to 24-hour format for matching
@@ -6881,18 +7134,17 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6881
7134
  hour24 = item.hour + 12;
6882
7135
  const hour24Str = hour24.toString().padStart(2, '0');
6883
7136
  const minuteStr = item.minute.toString().padStart(2, '0');
6884
- // Check if filterText matches 24-hour format variations
6885
7137
  const formats = [
6886
- `${hour24Str}:${minuteStr}`, // "13:30"
6887
- `${hour24Str}${minuteStr}`, // "1330"
6888
- hour24Str, // "13"
6889
- `${hour24}:${minuteStr}`, // "13:30" (without padding)
6890
- hour24.toString(), // "13" (without padding)
7138
+ `${hour24Str}:${minuteStr}`,
7139
+ `${hour24Str}${minuteStr}`,
7140
+ hour24Str,
7141
+ `${hour24}:${minuteStr}`,
7142
+ hour24.toString(),
6891
7143
  ];
6892
7144
  return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
6893
7145
  lowerFilterText.includes(format.toLowerCase()));
6894
7146
  };
6895
- }, [timeOptions]);
7147
+ }, [timeOptions, is24Hour, contains]);
6896
7148
  const { collection, filter } = useListCollection({
6897
7149
  initialItems: timeOptions,
6898
7150
  itemToString: itemToString,
@@ -6901,32 +7153,48 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6901
7153
  });
6902
7154
  // Get current value string for combobox
6903
7155
  const currentValue = useMemo(() => {
6904
- if (hour === null || minute === null || meridiem === null) {
6905
- return '';
7156
+ if (is24Hour) {
7157
+ if (hour === null || minute === null || second === null) {
7158
+ return '';
7159
+ }
7160
+ return `${hour}:${minute}:${second}`;
7161
+ }
7162
+ else {
7163
+ if (hour === null || minute === null || meridiem === null) {
7164
+ return '';
7165
+ }
7166
+ return `${hour}:${minute}:${meridiem}`;
6906
7167
  }
6907
- return `${hour}:${minute}:${meridiem}`;
6908
- }, [hour, minute, meridiem]);
7168
+ }, [hour, minute, second, meridiem, is24Hour]);
6909
7169
  // Calculate duration difference
6910
7170
  const durationDiff = useMemo(() => {
6911
- if (!startTime ||
6912
- !selectedDate ||
6913
- hour === null ||
6914
- minute === null ||
6915
- meridiem === null) {
7171
+ if (!startTime || !selectedDate || hour === null || minute === null) {
6916
7172
  return null;
6917
7173
  }
7174
+ if (is24Hour) {
7175
+ if (second === null)
7176
+ return null;
7177
+ }
7178
+ else {
7179
+ if (meridiem === null)
7180
+ return null;
7181
+ }
6918
7182
  const startDateObj = dayjs(startTime).tz(timezone);
6919
7183
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6920
- // Convert 12-hour to 24-hour format
7184
+ // Convert to 24-hour format
6921
7185
  let hour24 = hour;
6922
- if (meridiem === 'am' && hour === 12)
6923
- hour24 = 0;
6924
- else if (meridiem === 'pm' && hour < 12)
6925
- hour24 = hour + 12;
7186
+ if (!is24Hour && meridiem) {
7187
+ if (meridiem === 'am' && hour === 12)
7188
+ hour24 = 0;
7189
+ else if (meridiem === 'pm' && hour < 12)
7190
+ hour24 = hour + 12;
7191
+ }
6926
7192
  const currentDateTime = selectedDateObj
6927
7193
  .hour(hour24)
6928
7194
  .minute(minute)
6929
- .second(0)
7195
+ .second(is24Hour && second !== null && second !== undefined
7196
+ ? second
7197
+ : 0)
6930
7198
  .millisecond(0);
6931
7199
  if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6932
7200
  return null;
@@ -6952,13 +7220,28 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6952
7220
  return `+${diffText}`;
6953
7221
  }
6954
7222
  return null;
6955
- }, [hour, minute, meridiem, startTime, selectedDate, timezone]);
7223
+ }, [
7224
+ hour,
7225
+ minute,
7226
+ second,
7227
+ meridiem,
7228
+ startTime,
7229
+ selectedDate,
7230
+ timezone,
7231
+ is24Hour,
7232
+ ]);
6956
7233
  const handleClear = () => {
6957
7234
  setHour(null);
6958
7235
  setMinute(null);
6959
- setMeridiem(null);
6960
- filter(''); // Reset filter to show all options
6961
- onChange({ hour: null, minute: null, meridiem: null });
7236
+ if (is24Hour && setSecond) {
7237
+ setSecond(null);
7238
+ handleTimeChange(null, null, null, null);
7239
+ }
7240
+ else if (!is24Hour && setMeridiem) {
7241
+ setMeridiem(null);
7242
+ handleTimeChange(null, null, null, null);
7243
+ }
7244
+ filter('');
6962
7245
  };
6963
7246
  const handleValueChange = (details) => {
6964
7247
  if (details.value.length === 0) {
@@ -6970,112 +7253,165 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
6970
7253
  if (selectedOption) {
6971
7254
  setHour(selectedOption.hour);
6972
7255
  setMinute(selectedOption.minute);
6973
- setMeridiem(selectedOption.meridiem);
6974
- filter(''); // Reset filter after selection
6975
- onChange({
6976
- hour: selectedOption.hour,
6977
- minute: selectedOption.minute,
6978
- meridiem: selectedOption.meridiem,
6979
- });
7256
+ filter('');
7257
+ if (is24Hour) {
7258
+ const opt24 = selectedOption;
7259
+ if (setSecond)
7260
+ setSecond(opt24.second);
7261
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
7262
+ }
7263
+ else {
7264
+ const opt12 = selectedOption;
7265
+ if (setMeridiem)
7266
+ setMeridiem(opt12.meridiem);
7267
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
7268
+ }
6980
7269
  }
6981
7270
  };
6982
7271
  // Parse input value and update state
6983
7272
  const parseAndCommitInput = (value) => {
6984
7273
  const trimmedValue = value.trim();
6985
- // Filter the collection based on input
6986
7274
  filter(trimmedValue);
6987
7275
  if (!trimmedValue) {
6988
7276
  return;
6989
7277
  }
6990
- // Parse 24-hour format first (e.g., "13:30", "14:00", "1330", "1400", "9:05", "905")
6991
- const timePattern24Hour = /^(\d{1,2}):?(\d{2})$/;
6992
- const match24Hour = trimmedValue.match(timePattern24Hour);
6993
- if (match24Hour) {
6994
- const parsedHour24 = parseInt(match24Hour[1], 10);
6995
- const parsedMinute = parseInt(match24Hour[2], 10);
6996
- // Validate 24-hour format ranges
6997
- if (parsedHour24 >= 0 &&
6998
- parsedHour24 <= 23 &&
6999
- parsedMinute >= 0 &&
7000
- parsedMinute <= 59) {
7001
- // Convert 24-hour to 12-hour format
7002
- let hour12;
7003
- let meridiem;
7004
- if (parsedHour24 === 0) {
7005
- hour12 = 12;
7006
- meridiem = 'am';
7007
- }
7008
- else if (parsedHour24 === 12) {
7009
- hour12 = 12;
7010
- meridiem = 'pm';
7011
- }
7012
- else if (parsedHour24 > 12) {
7013
- hour12 = parsedHour24 - 12;
7014
- meridiem = 'pm';
7278
+ if (is24Hour) {
7279
+ // Parse 24-hour format: "HH:mm:ss" or "HH:mm" or "HHmmss" or "HHmm"
7280
+ const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
7281
+ const match = trimmedValue.match(timePattern);
7282
+ if (match) {
7283
+ const parsedHour = parseInt(match[1], 10);
7284
+ const parsedMinute = parseInt(match[2], 10);
7285
+ const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
7286
+ if (parsedHour >= 0 &&
7287
+ parsedHour <= 23 &&
7288
+ parsedMinute >= 0 &&
7289
+ parsedMinute <= 59 &&
7290
+ parsedSecond >= 0 &&
7291
+ parsedSecond <= 59) {
7292
+ setHour(parsedHour);
7293
+ setMinute(parsedMinute);
7294
+ if (setSecond)
7295
+ setSecond(parsedSecond);
7296
+ handleTimeChange(parsedHour, parsedMinute, parsedSecond, null);
7297
+ return;
7015
7298
  }
7016
- else {
7017
- hour12 = parsedHour24;
7018
- meridiem = 'am';
7299
+ }
7300
+ // Try numbers only format: "123045" or "1230"
7301
+ const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
7302
+ if (numbersOnly.length >= 4) {
7303
+ const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
7304
+ const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
7305
+ const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 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;
7019
7318
  }
7020
- setHour(hour12);
7021
- setMinute(parsedMinute);
7022
- setMeridiem(meridiem);
7023
- onChange({
7024
- hour: hour12,
7025
- minute: parsedMinute,
7026
- meridiem: meridiem,
7027
- });
7028
- return;
7029
7319
  }
7030
7320
  }
7031
- // Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
7032
- const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
7033
- const match12Hour = trimmedValue.match(timePattern12Hour);
7034
- if (match12Hour) {
7035
- const parsedHour = parseInt(match12Hour[1], 10);
7036
- const parsedMinute = parseInt(match12Hour[2], 10);
7037
- const parsedMeridiem = match12Hour[3].toLowerCase();
7038
- // Validate ranges
7039
- if (parsedHour >= 1 &&
7040
- parsedHour <= 12 &&
7041
- parsedMinute >= 0 &&
7042
- parsedMinute <= 59) {
7043
- setHour(parsedHour);
7044
- setMinute(parsedMinute);
7045
- setMeridiem(parsedMeridiem);
7046
- onChange({
7047
- hour: parsedHour,
7048
- minute: parsedMinute,
7049
- meridiem: parsedMeridiem,
7050
- });
7051
- return;
7321
+ else {
7322
+ // Parse 24-hour format first (e.g., "13:30", "14:00", "1330", "1400")
7323
+ const timePattern24Hour = /^(\d{1,2}):?(\d{2})$/;
7324
+ const match24Hour = trimmedValue.match(timePattern24Hour);
7325
+ if (match24Hour) {
7326
+ const parsedHour24 = parseInt(match24Hour[1], 10);
7327
+ const parsedMinute = parseInt(match24Hour[2], 10);
7328
+ if (parsedHour24 >= 0 &&
7329
+ parsedHour24 <= 23 &&
7330
+ parsedMinute >= 0 &&
7331
+ parsedMinute <= 59) {
7332
+ // Convert 24-hour to 12-hour format
7333
+ let hour12;
7334
+ let meridiem;
7335
+ if (parsedHour24 === 0) {
7336
+ hour12 = 12;
7337
+ meridiem = 'am';
7338
+ }
7339
+ else if (parsedHour24 === 12) {
7340
+ hour12 = 12;
7341
+ meridiem = 'pm';
7342
+ }
7343
+ else if (parsedHour24 > 12) {
7344
+ hour12 = parsedHour24 - 12;
7345
+ meridiem = 'pm';
7346
+ }
7347
+ else {
7348
+ hour12 = parsedHour24;
7349
+ meridiem = 'am';
7350
+ }
7351
+ setHour(hour12);
7352
+ setMinute(parsedMinute);
7353
+ if (setMeridiem)
7354
+ setMeridiem(meridiem);
7355
+ handleTimeChange(hour12, parsedMinute, null, meridiem);
7356
+ return;
7357
+ }
7052
7358
  }
7053
- }
7054
- // Try to parse formats like "130pm" or "130 pm" (without colon)
7055
- const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
7056
- const matchNoColon = trimmedValue.match(timePatternNoColon);
7057
- if (matchNoColon) {
7058
- const numbersOnly = matchNoColon[1];
7059
- const parsedMeridiem = matchNoColon[2].toLowerCase();
7060
- if (numbersOnly.length >= 3) {
7061
- const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
7062
- const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
7063
- // Validate ranges
7359
+ // Parse formats like "1:30 PM", "1:30PM", "1:30 pm", "1:30pm"
7360
+ const timePattern12Hour = /^(\d{1,2}):(\d{1,2})\s*(am|pm|AM|PM)$/i;
7361
+ const match12Hour = trimmedValue.match(timePattern12Hour);
7362
+ if (match12Hour) {
7363
+ const parsedHour = parseInt(match12Hour[1], 10);
7364
+ const parsedMinute = parseInt(match12Hour[2], 10);
7365
+ const parsedMeridiem = match12Hour[3].toLowerCase();
7064
7366
  if (parsedHour >= 1 &&
7065
7367
  parsedHour <= 12 &&
7066
7368
  parsedMinute >= 0 &&
7067
7369
  parsedMinute <= 59) {
7068
7370
  setHour(parsedHour);
7069
7371
  setMinute(parsedMinute);
7070
- setMeridiem(parsedMeridiem);
7071
- onChange({
7072
- hour: parsedHour,
7073
- minute: parsedMinute,
7074
- meridiem: parsedMeridiem,
7075
- });
7372
+ if (setMeridiem)
7373
+ setMeridiem(parsedMeridiem);
7374
+ handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
7375
+ return;
7376
+ }
7377
+ }
7378
+ // Parse formats like "12am" or "1pm" (hour only with meridiem, no minutes)
7379
+ const timePatternHourOnly = /^(\d{1,2})\s*(am|pm|AM|PM)$/i;
7380
+ const matchHourOnly = trimmedValue.match(timePatternHourOnly);
7381
+ if (matchHourOnly) {
7382
+ const parsedHour = parseInt(matchHourOnly[1], 10);
7383
+ const parsedMeridiem = matchHourOnly[2].toLowerCase();
7384
+ if (parsedHour >= 1 && parsedHour <= 12) {
7385
+ setHour(parsedHour);
7386
+ setMinute(0); // Default to 0 minutes when only hour is provided
7387
+ if (setMeridiem)
7388
+ setMeridiem(parsedMeridiem);
7389
+ handleTimeChange(parsedHour, 0, null, parsedMeridiem);
7076
7390
  return;
7077
7391
  }
7078
7392
  }
7393
+ // Try to parse formats like "130pm" or "130 pm" (without colon, with minutes)
7394
+ const timePatternNoColon = /^(\d{1,4})\s*(am|pm|AM|PM)$/i;
7395
+ const matchNoColon = trimmedValue.match(timePatternNoColon);
7396
+ if (matchNoColon) {
7397
+ const numbersOnly = matchNoColon[1];
7398
+ const parsedMeridiem = matchNoColon[2].toLowerCase();
7399
+ if (numbersOnly.length >= 3) {
7400
+ const parsedHour = parseInt(numbersOnly.slice(0, -2), 10);
7401
+ const parsedMinute = parseInt(numbersOnly.slice(-2), 10);
7402
+ if (parsedHour >= 1 &&
7403
+ parsedHour <= 12 &&
7404
+ parsedMinute >= 0 &&
7405
+ parsedMinute <= 59) {
7406
+ setHour(parsedHour);
7407
+ setMinute(parsedMinute);
7408
+ if (setMeridiem)
7409
+ setMeridiem(parsedMeridiem);
7410
+ handleTimeChange(parsedHour, parsedMinute, null, parsedMeridiem);
7411
+ return;
7412
+ }
7413
+ }
7414
+ }
7079
7415
  }
7080
7416
  // Parse failed, select first result
7081
7417
  selectFirstResult();
@@ -7086,55 +7422,84 @@ const TimePicker$1 = ({ hour, setHour, minute, setMinute, meridiem, setMeridiem,
7086
7422
  const firstItem = collection.items[0];
7087
7423
  setHour(firstItem.hour);
7088
7424
  setMinute(firstItem.minute);
7089
- setMeridiem(firstItem.meridiem);
7090
- filter(''); // Reset filter after selection
7091
- onChange({
7092
- hour: firstItem.hour,
7093
- minute: firstItem.minute,
7094
- meridiem: firstItem.meridiem,
7095
- });
7425
+ filter('');
7426
+ if (is24Hour) {
7427
+ const opt24 = firstItem;
7428
+ if (setSecond)
7429
+ setSecond(opt24.second);
7430
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
7431
+ }
7432
+ else {
7433
+ const opt12 = firstItem;
7434
+ if (setMeridiem)
7435
+ setMeridiem(opt12.meridiem);
7436
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
7437
+ }
7096
7438
  }
7097
7439
  };
7098
7440
  const handleInputValueChange = (details) => {
7099
- // Filter the collection based on input, but don't parse yet
7441
+ if (is24Hour) {
7442
+ setInputValue(details.inputValue);
7443
+ }
7100
7444
  filter(details.inputValue);
7101
7445
  };
7102
7446
  const handleFocus = (e) => {
7103
- // Select all text when focusing
7104
7447
  e.target.select();
7105
7448
  };
7106
7449
  const handleBlur = (e) => {
7107
- // Parse and commit the input value when losing focus
7108
- const inputValue = e.target.value;
7109
- if (inputValue) {
7110
- parseAndCommitInput(inputValue);
7450
+ const inputVal = e.target.value;
7451
+ if (is24Hour) {
7452
+ setInputValue(inputVal);
7453
+ }
7454
+ if (inputVal) {
7455
+ parseAndCommitInput(inputVal);
7111
7456
  }
7112
7457
  };
7113
7458
  const handleKeyDown = (e) => {
7114
- // Commit input on Enter key
7115
7459
  if (e.key === 'Enter') {
7116
7460
  e.preventDefault();
7117
- const inputValue = e.currentTarget.value;
7118
- if (inputValue) {
7119
- parseAndCommitInput(inputValue);
7461
+ const inputVal = e.currentTarget.value;
7462
+ if (is24Hour) {
7463
+ setInputValue(inputVal);
7464
+ }
7465
+ if (inputVal) {
7466
+ parseAndCommitInput(inputVal);
7120
7467
  }
7121
- // Blur the input
7122
7468
  e.currentTarget?.blur();
7123
7469
  }
7124
7470
  };
7125
- return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: labels?.placeholder ?? 'hh:mm AM/PM', onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: labels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }) }));
7471
+ return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { value: is24Hour ? inputValue : undefined, placeholder: labels?.placeholder ?? (is24Hour ? 'HH:mm:ss' : 'hh:mm AM/PM'), onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: labels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) }))] }) }));
7126
7472
  };
7127
7473
 
7128
7474
  dayjs.extend(timezone);
7129
7475
  const TimePicker = ({ column, schema, prefix }) => {
7130
7476
  const { watch, formState: { errors }, setValue, } = useFormContext();
7131
7477
  const { timezone, insideDialog, timePickerLabels } = useSchemaContext();
7132
- const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', } = schema;
7478
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', timeFormat = 'HH:mm:ssZ', displayTimeFormat = 'hh:mm A', startTimeField, selectedDateField, } = schema;
7133
7479
  const isRequired = required?.some((columnId) => columnId === column);
7134
7480
  const colLabel = `${prefix}${column}`;
7135
7481
  const formI18n = useFormI18n(column, prefix, schema);
7136
7482
  const [open, setOpen] = useState(false);
7137
7483
  const value = watch(colLabel);
7484
+ // Watch startTime and selectedDate fields for offset calculation
7485
+ const startTimeValue = startTimeField
7486
+ ? watch(`${prefix}${startTimeField}`)
7487
+ : undefined;
7488
+ const selectedDateValue = selectedDateField
7489
+ ? watch(`${prefix}${selectedDateField}`)
7490
+ : undefined;
7491
+ // Convert to ISO string format for startTime if it's a date-time string
7492
+ const startTime = startTimeValue
7493
+ ? dayjs(startTimeValue).tz(timezone).isValid()
7494
+ ? dayjs(startTimeValue).tz(timezone).toISOString()
7495
+ : undefined
7496
+ : undefined;
7497
+ // Convert selectedDate to YYYY-MM-DD format
7498
+ const selectedDate = selectedDateValue
7499
+ ? dayjs(selectedDateValue).tz(timezone).isValid()
7500
+ ? dayjs(selectedDateValue).tz(timezone).format('YYYY-MM-DD')
7501
+ : undefined
7502
+ : undefined;
7138
7503
  const displayedTime = dayjs(`1970-01-01T${value}`).tz(timezone).isValid()
7139
7504
  ? dayjs(`1970-01-01T${value}`).tz(timezone).format(displayTimeFormat)
7140
7505
  : '';
@@ -7190,941 +7555,860 @@ const TimePicker = ({ column, schema, prefix }) => {
7190
7555
  return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
7191
7556
  gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
7192
7557
  setOpen(true);
7193
- }, justifyContent: 'start', children: [jsx(IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsx(Popover.Body, { overflow: "visible", children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { children: jsx(Popover.Body, { children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, labels: timePickerLabels }) }) }) }) }))] }) }));
7558
+ }, justifyContent: 'start', children: [jsx(IoMdClock, {}), !!value ? `${displayedTime}` : ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { maxH: "70vh", overflowY: "auto", children: jsx(Popover.Body, { overflow: "visible", children: jsx(TimePicker$1, { hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: startTime, selectedDate: selectedDate, timezone: timezone, portalled: false, labels: timePickerLabels }) }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { children: jsx(Popover.Body, { children: jsx(TimePicker$1, { format: "12h", hour: hour, setHour: setHour, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: startTime, selectedDate: selectedDate, timezone: timezone, portalled: false, labels: timePickerLabels }) }) }) }) }))] }) }));
7194
7559
  };
7195
7560
 
7196
7561
  dayjs.extend(utc);
7197
7562
  dayjs.extend(timezone);
7198
- function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
7199
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
7200
- onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, labels = {
7201
- placeholder: 'HH:mm:ss',
7202
- emptyMessage: 'No time found',
7203
- }, }) {
7204
- // Generate time options (every 15 minutes, seconds always 0)
7205
- const timeOptions = useMemo(() => {
7206
- const options = [];
7207
- // Get start time for comparison if provided
7208
- let startDateTime = null;
7209
- let shouldFilterByDate = false;
7210
- if (startTime && selectedDate) {
7211
- const startDateObj = dayjs(startTime).tz(timezone);
7212
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
7213
- if (startDateObj.isValid() && selectedDateObj.isValid()) {
7214
- startDateTime = startDateObj;
7215
- // Only filter if dates are the same
7216
- shouldFilterByDate =
7217
- startDateObj.format('YYYY-MM-DD') ===
7218
- selectedDateObj.format('YYYY-MM-DD');
7219
- }
7563
+ dayjs.extend(customParseFormat);
7564
+ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
7565
+ monthNamesShort: [
7566
+ 'January',
7567
+ 'February',
7568
+ 'March',
7569
+ 'April',
7570
+ 'May',
7571
+ 'June',
7572
+ 'July',
7573
+ 'August',
7574
+ 'September',
7575
+ 'October',
7576
+ 'November',
7577
+ 'December',
7578
+ ],
7579
+ weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
7580
+ backButtonLabel: 'Back',
7581
+ forwardButtonLabel: 'Forward',
7582
+ }, timePickerLabels, timezone: tz = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, showQuickActions = false, quickActionLabels = {
7583
+ yesterday: 'Yesterday',
7584
+ today: 'Today',
7585
+ tomorrow: 'Tomorrow',
7586
+ plus7Days: '+7 Days',
7587
+ }, showTimezoneSelector = false, }) {
7588
+ const is24Hour = format === 'iso-date-time' || showSeconds;
7589
+ const { monthNamesShort = [
7590
+ 'January',
7591
+ 'February',
7592
+ 'March',
7593
+ 'April',
7594
+ 'May',
7595
+ 'June',
7596
+ 'July',
7597
+ 'August',
7598
+ 'September',
7599
+ 'October',
7600
+ 'November',
7601
+ 'December',
7602
+ ], weekdayNamesShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], backButtonLabel = 'Back', forwardButtonLabel = 'Forward', } = labels;
7603
+ // Parse value to get date and time
7604
+ const parsedValue = useMemo(() => {
7605
+ if (!value)
7606
+ return null;
7607
+ const dateObj = dayjs(value).tz(tz);
7608
+ if (!dateObj.isValid())
7609
+ return null;
7610
+ return dateObj;
7611
+ }, [value, tz]);
7612
+ // Initialize date state
7613
+ const [selectedDate, setSelectedDate] = useState(() => {
7614
+ if (parsedValue) {
7615
+ return parsedValue.toDate();
7220
7616
  }
7221
- for (let h = 0; h < 24; h++) {
7222
- for (let m = 0; m < 60; m += 15) {
7223
- const timeDisplay = `${h.toString().padStart(2, '0')}:${m
7224
- .toString()
7225
- .padStart(2, '0')}:00`;
7226
- // Filter out times that would result in negative duration (only when dates are the same)
7227
- if (startDateTime && selectedDate && shouldFilterByDate) {
7228
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
7229
- const optionDateTime = selectedDateObj
7230
- .hour(h)
7231
- .minute(m)
7232
- .second(0)
7233
- .millisecond(0);
7234
- if (optionDateTime.isBefore(startDateTime)) {
7235
- continue; // Skip this option as it would result in negative duration
7236
- }
7237
- }
7238
- // Calculate duration if startTime is provided
7239
- let durationText;
7240
- if (startDateTime && selectedDate) {
7241
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
7242
- const optionDateTime = selectedDateObj
7243
- .hour(h)
7244
- .minute(m)
7245
- .second(0)
7246
- .millisecond(0);
7247
- if (optionDateTime.isValid() &&
7248
- optionDateTime.isAfter(startDateTime)) {
7249
- const diffMs = optionDateTime.diff(startDateTime);
7250
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
7251
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
7252
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
7253
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
7254
- let diffText = '';
7255
- if (diffHours > 0) {
7256
- diffText = `${diffHours}h ${diffMinutes}m`;
7257
- }
7258
- else if (diffMinutes > 0) {
7259
- diffText = `${diffMinutes}m ${diffSeconds}s`;
7260
- }
7261
- else {
7262
- diffText = `${diffSeconds}s`;
7263
- }
7264
- durationText = `+${diffText}`;
7265
- }
7266
- }
7267
- }
7268
- options.push({
7269
- label: timeDisplay,
7270
- value: `${h}:${m}:0`,
7271
- hour: h,
7272
- minute: m,
7273
- second: 0,
7274
- searchText: timeDisplay, // Use base time without duration for searching
7275
- durationText,
7276
- });
7277
- }
7617
+ if (defaultDate) {
7618
+ const defaultDateObj = dayjs(defaultDate).tz(tz);
7619
+ return defaultDateObj.isValid() ? defaultDateObj.toDate() : new Date();
7278
7620
  }
7279
- return options;
7280
- }, [startTime, selectedDate, timezone]);
7281
- const { contains } = useFilter({ sensitivity: 'base' });
7282
- const { collection, filter } = useListCollection({
7283
- initialItems: timeOptions,
7284
- itemToString: (item) => item.searchText, // Use searchText (without duration) for filtering
7285
- itemToValue: (item) => item.value,
7286
- filter: contains,
7621
+ return new Date();
7287
7622
  });
7288
- // Get current value string for combobox
7289
- const currentValue = useMemo(() => {
7290
- if (hour === null || minute === null || second === null) {
7291
- return '';
7292
- }
7293
- return `${hour}:${minute}:${second}`;
7294
- }, [hour, minute, second]);
7295
- // Calculate duration difference
7296
- const durationDiff = useMemo(() => {
7297
- if (!startTime ||
7298
- !selectedDate ||
7299
- hour === null ||
7300
- minute === null ||
7301
- second === null) {
7302
- return null;
7623
+ // Initialize time state
7624
+ const [hour, setHour] = useState(() => {
7625
+ if (parsedValue) {
7626
+ return parsedValue.hour();
7303
7627
  }
7304
- const startDateObj = dayjs(startTime).tz(timezone);
7305
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
7306
- const currentDateTime = selectedDateObj
7307
- .hour(hour)
7308
- .minute(minute)
7309
- .second(second ?? 0)
7310
- .millisecond(0);
7311
- if (!startDateObj.isValid() || !currentDateTime.isValid()) {
7312
- return null;
7628
+ if (defaultTime?.hour !== null && defaultTime?.hour !== undefined) {
7629
+ return defaultTime.hour;
7313
7630
  }
7314
- const diffMs = currentDateTime.diff(startDateObj);
7315
- if (diffMs < 0) {
7316
- return null;
7631
+ return null;
7632
+ });
7633
+ const [minute, setMinute] = useState(() => {
7634
+ if (parsedValue) {
7635
+ return parsedValue.minute();
7317
7636
  }
7318
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
7319
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
7320
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
7321
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
7322
- let diffText = '';
7323
- if (diffHours > 0) {
7324
- diffText = `${diffHours}h ${diffMinutes}m`;
7325
- }
7326
- else if (diffMinutes > 0) {
7327
- diffText = `${diffMinutes}m ${diffSeconds}s`;
7328
- }
7329
- else {
7330
- diffText = `${diffSeconds}s`;
7331
- }
7332
- return `+${diffText}`;
7637
+ if (defaultTime?.minute !== null && defaultTime?.minute !== undefined) {
7638
+ return defaultTime.minute;
7333
7639
  }
7334
7640
  return null;
7335
- }, [hour, minute, second, startTime, selectedDate, timezone]);
7336
- const handleClear = () => {
7337
- setHour(null);
7338
- setMinute(null);
7339
- setSecond(null);
7340
- filter(''); // Reset filter to show all options
7341
- onChange({ hour: null, minute: null, second: null });
7342
- };
7343
- const handleValueChange = (details) => {
7344
- if (details.value.length === 0) {
7345
- handleClear();
7346
- return;
7641
+ });
7642
+ const [second, setSecond] = useState(() => {
7643
+ if (parsedValue) {
7644
+ return parsedValue.second();
7347
7645
  }
7348
- const selectedValue = details.value[0];
7349
- const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
7350
- if (selectedOption) {
7351
- setHour(selectedOption.hour);
7352
- setMinute(selectedOption.minute);
7353
- setSecond(selectedOption.second);
7354
- filter(''); // Reset filter after selection
7355
- onChange({
7356
- hour: selectedOption.hour,
7357
- minute: selectedOption.minute,
7358
- second: selectedOption.second,
7359
- });
7646
+ if (defaultTime?.second !== null && defaultTime?.second !== undefined) {
7647
+ return defaultTime.second;
7360
7648
  }
7361
- };
7362
- // Parse input value and update state
7363
- const parseAndCommitInput = (value) => {
7364
- const trimmedValue = value.trim();
7365
- // Filter the collection based on input
7366
- filter(trimmedValue);
7367
- if (!trimmedValue) {
7649
+ return showSeconds ? 0 : null;
7650
+ });
7651
+ const [meridiem, setMeridiem] = useState(() => {
7652
+ if (parsedValue) {
7653
+ const h = parsedValue.hour();
7654
+ return h < 12 ? 'am' : 'pm';
7655
+ }
7656
+ if (defaultTime?.meridiem !== null && defaultTime?.meridiem !== undefined) {
7657
+ return defaultTime.meridiem;
7658
+ }
7659
+ return is24Hour ? null : 'am';
7660
+ });
7661
+ // Popover state - separate for date, time, and timezone
7662
+ const [datePopoverOpen, setDatePopoverOpen] = useState(false);
7663
+ const [timePopoverOpen, setTimePopoverOpen] = useState(false);
7664
+ const [timezonePopoverOpen, setTimezonePopoverOpen] = useState(false);
7665
+ const [calendarPopoverOpen, setCalendarPopoverOpen] = useState(false);
7666
+ // Timezone offset state
7667
+ const [timezoneOffset, setTimezoneOffset] = useState(() => {
7668
+ if (parsedValue) {
7669
+ return parsedValue.format('Z');
7670
+ }
7671
+ // Default to +08:00
7672
+ return '+08:00';
7673
+ });
7674
+ // Sync timezone offset when value changes
7675
+ // Generate timezone offset options (UTC-12 to UTC+14)
7676
+ const timezoneOffsetOptions = useMemo(() => {
7677
+ const options = [];
7678
+ for (let offset = -12; offset <= 14; offset++) {
7679
+ const sign = offset >= 0 ? '+' : '-';
7680
+ const hours = Math.abs(offset).toString().padStart(2, '0');
7681
+ const value = `${sign}${hours}:00`;
7682
+ const label = `UTC${sign}${hours}:00`;
7683
+ options.push({ value, label });
7684
+ }
7685
+ return options;
7686
+ }, []);
7687
+ // Create collection for Select
7688
+ const { collection: timezoneCollection } = useListCollection({
7689
+ initialItems: timezoneOffsetOptions,
7690
+ itemToString: (item) => item.label,
7691
+ itemToValue: (item) => item.value,
7692
+ });
7693
+ // Date input state
7694
+ const [dateInputValue, setDateInputValue] = useState('');
7695
+ // Sync date input value with selected date
7696
+ useEffect(() => {
7697
+ if (selectedDate) {
7698
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7699
+ setDateInputValue(formatted);
7700
+ }
7701
+ else {
7702
+ setDateInputValue('');
7703
+ }
7704
+ }, [selectedDate, tz]);
7705
+ // Parse and validate date input
7706
+ const parseAndValidateDateInput = (inputVal) => {
7707
+ // If empty, clear the value
7708
+ if (!inputVal.trim()) {
7709
+ setSelectedDate(null);
7710
+ updateDateTime(null, hour, minute, second, meridiem);
7368
7711
  return;
7369
7712
  }
7370
- // Parse HH:mm:ss or HH:mm format
7371
- const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
7372
- const match = trimmedValue.match(timePattern);
7373
- if (match) {
7374
- const parsedHour = parseInt(match[1], 10);
7375
- const parsedMinute = parseInt(match[2], 10);
7376
- const parsedSecond = match[3] ? parseInt(match[3], 10) : 0;
7377
- // Validate ranges
7378
- if (parsedHour >= 0 &&
7379
- parsedHour <= 23 &&
7380
- parsedMinute >= 0 &&
7381
- parsedMinute <= 59 &&
7382
- parsedSecond >= 0 &&
7383
- parsedSecond <= 59) {
7384
- setHour(parsedHour);
7385
- setMinute(parsedMinute);
7386
- setSecond(parsedSecond);
7387
- onChange({
7388
- hour: parsedHour,
7389
- minute: parsedMinute,
7390
- second: parsedSecond,
7391
- });
7713
+ // Try parsing with common date formats
7714
+ let parsedDate = dayjs(inputVal, 'YYYY-MM-DD', true);
7715
+ // If that fails, try other common formats
7716
+ if (!parsedDate.isValid()) {
7717
+ parsedDate = dayjs(inputVal);
7718
+ }
7719
+ // If valid, check constraints and update
7720
+ if (parsedDate.isValid()) {
7721
+ const dateObj = parsedDate.tz(tz).toDate();
7722
+ // Check min/max constraints
7723
+ if (minDate && dateObj < minDate) {
7724
+ // Invalid: before minDate, reset to current selected date
7725
+ if (selectedDate) {
7726
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7727
+ setDateInputValue(formatted);
7728
+ }
7729
+ else {
7730
+ setDateInputValue('');
7731
+ }
7392
7732
  return;
7393
7733
  }
7394
- }
7395
- else {
7396
- // Try to parse formats like "123045" (HHmmss) or "1230" (HHmm)
7397
- const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
7398
- if (numbersOnly.length >= 4) {
7399
- const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
7400
- const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
7401
- const parsedSecond = numbersOnly.length >= 6 ? parseInt(numbersOnly.slice(4, 6), 10) : 0;
7402
- // Validate ranges
7403
- if (parsedHour >= 0 &&
7404
- parsedHour <= 23 &&
7405
- parsedMinute >= 0 &&
7406
- parsedMinute <= 59 &&
7407
- parsedSecond >= 0 &&
7408
- parsedSecond <= 59) {
7409
- setHour(parsedHour);
7410
- setMinute(parsedMinute);
7411
- setSecond(parsedSecond);
7412
- onChange({
7413
- hour: parsedHour,
7414
- minute: parsedMinute,
7415
- second: parsedSecond,
7416
- });
7417
- return;
7734
+ if (maxDate && dateObj > maxDate) {
7735
+ // Invalid: after maxDate, reset to current selected date
7736
+ if (selectedDate) {
7737
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7738
+ setDateInputValue(formatted);
7418
7739
  }
7740
+ else {
7741
+ setDateInputValue('');
7742
+ }
7743
+ return;
7419
7744
  }
7420
- }
7421
- // Parse failed, select first result
7422
- selectFirstResult();
7423
- };
7424
- // Select first result from filtered collection
7425
- const selectFirstResult = () => {
7426
- if (collection.items.length > 0) {
7427
- const firstItem = collection.items[0];
7428
- setHour(firstItem.hour);
7429
- setMinute(firstItem.minute);
7430
- setSecond(firstItem.second);
7431
- filter(''); // Reset filter after selection
7432
- onChange({
7433
- hour: firstItem.hour,
7434
- minute: firstItem.minute,
7435
- second: firstItem.second,
7436
- });
7437
- }
7438
- };
7439
- const [inputValue, setInputValue] = useState('');
7440
- // Sync inputValue with currentValue when time changes externally
7441
- useEffect(() => {
7442
- if (hour !== null && minute !== null && second !== null) {
7443
- const formattedValue = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
7444
- setInputValue(formattedValue);
7745
+ // Valid date - update selected date
7746
+ setSelectedDate(dateObj);
7747
+ updateDateTime(dateObj, hour, minute, second, meridiem);
7748
+ // Format and update input value
7749
+ const formatted = parsedDate.tz(tz).format('YYYY-MM-DD');
7750
+ setDateInputValue(formatted);
7445
7751
  }
7446
7752
  else {
7447
- setInputValue('');
7753
+ // Invalid date - reset to current selected date
7754
+ if (selectedDate) {
7755
+ const formatted = dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7756
+ setDateInputValue(formatted);
7757
+ }
7758
+ else {
7759
+ setDateInputValue('');
7760
+ }
7448
7761
  }
7449
- }, [hour, minute, second]);
7450
- const handleInputValueChange = (details) => {
7451
- // Update local input value state
7452
- setInputValue(details.inputValue);
7453
- // Filter the collection based on input, but don't parse yet
7454
- filter(details.inputValue);
7455
7762
  };
7456
- const handleFocus = (e) => {
7457
- // Select all text when focusing
7458
- e.target.select();
7763
+ const handleDateInputChange = (e) => {
7764
+ setDateInputValue(e.target.value);
7459
7765
  };
7460
- const handleBlur = (e) => {
7461
- // Parse and commit the input value when losing focus
7462
- const inputVal = e.target.value;
7463
- setInputValue(inputVal);
7464
- if (inputVal) {
7465
- parseAndCommitInput(inputVal);
7466
- }
7766
+ const handleDateInputBlur = () => {
7767
+ parseAndValidateDateInput(dateInputValue);
7467
7768
  };
7468
- const handleKeyDown = (e) => {
7469
- // Commit input on Enter key
7769
+ const handleDateInputKeyDown = (e) => {
7470
7770
  if (e.key === 'Enter') {
7471
7771
  e.preventDefault();
7472
- const inputVal = e.currentTarget.value;
7473
- setInputValue(inputVal);
7474
- if (inputVal) {
7475
- parseAndCommitInput(inputVal);
7476
- }
7477
- // Blur the input
7478
- e.currentTarget?.blur();
7772
+ parseAndValidateDateInput(dateInputValue);
7479
7773
  }
7480
7774
  };
7481
- return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { value: inputValue, placeholder: labels.placeholder, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: !portalled, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: labels.emptyMessage }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsx(Text, { flex: 1, children: item.label }), item.durationText && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: item.durationText }) }))] }), jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsx(Tag$1.Root, { size: "sm", children: jsx(Tag$1.Label, { children: durationDiff }) })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }) }));
7482
- }
7483
-
7484
- dayjs.extend(utc);
7485
- dayjs.extend(timezone);
7486
- function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds = false, labels = {
7487
- monthNamesShort: [
7488
- 'Jan',
7489
- 'Feb',
7490
- 'Mar',
7491
- 'Apr',
7492
- 'May',
7493
- 'Jun',
7494
- 'Jul',
7495
- 'Aug',
7496
- 'Sep',
7497
- 'Oct',
7498
- 'Nov',
7499
- 'Dec',
7500
- ],
7501
- weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
7502
- backButtonLabel: 'Back',
7503
- forwardButtonLabel: 'Next',
7504
- }, timePickerLabels, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, defaultDate, defaultTime, }) {
7505
- console.debug('[DateTimePicker] Component initialized with props:', {
7506
- value,
7507
- format,
7508
- showSeconds,
7509
- timezone,
7510
- startTime,
7511
- minDate,
7512
- maxDate,
7513
- });
7514
- // Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
7515
- const getDateString = useCallback((val) => {
7516
- if (!val)
7517
- return '';
7518
- const dateObj = dayjs(val).tz(timezone);
7519
- return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
7520
- }, [timezone]);
7521
- const [selectedDate, setSelectedDate] = useState(getDateString(value));
7522
- // Helper to get time values from value prop with timezone
7523
- const getTimeFromValue = useCallback((val) => {
7524
- console.debug('[DateTimePicker] getTimeFromValue called:', {
7525
- val,
7526
- timezone,
7527
- showSeconds,
7528
- });
7529
- if (!val) {
7530
- console.debug('[DateTimePicker] No value provided, returning nulls');
7531
- return {
7532
- hour12: null,
7533
- minute: null,
7534
- meridiem: null,
7535
- hour24: null,
7536
- second: null,
7537
- };
7775
+ // Helper functions to get dates in the correct timezone
7776
+ const getToday = () => dayjs().tz(tz).startOf('day').toDate();
7777
+ const getYesterday = () => dayjs().tz(tz).subtract(1, 'day').startOf('day').toDate();
7778
+ const getTomorrow = () => dayjs().tz(tz).add(1, 'day').startOf('day').toDate();
7779
+ const getPlus7Days = () => dayjs().tz(tz).add(7, 'day').startOf('day').toDate();
7780
+ // Check if a date is within min/max constraints
7781
+ const isDateValid = (date) => {
7782
+ if (minDate) {
7783
+ const minDateStart = dayjs(minDate).tz(tz).startOf('day').toDate();
7784
+ const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
7785
+ if (dateStart < minDateStart)
7786
+ return false;
7538
7787
  }
7539
- const dateObj = dayjs(val).tz(timezone);
7540
- console.debug('[DateTimePicker] Parsed date object:', {
7541
- original: val,
7542
- timezone,
7543
- isValid: dateObj.isValid(),
7544
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7545
- hour24: dateObj.hour(),
7546
- minute: dateObj.minute(),
7547
- second: dateObj.second(),
7548
- });
7549
- if (!dateObj.isValid()) {
7550
- console.debug('[DateTimePicker] Invalid date object, returning nulls');
7551
- return {
7552
- hour12: null,
7553
- minute: null,
7554
- meridiem: null,
7555
- hour24: null,
7556
- second: null,
7557
- };
7788
+ if (maxDate) {
7789
+ const maxDateStart = dayjs(maxDate).tz(tz).startOf('day').toDate();
7790
+ const dateStart = dayjs(date).tz(tz).startOf('day').toDate();
7791
+ if (dateStart > maxDateStart)
7792
+ return false;
7558
7793
  }
7559
- const hour24Value = dateObj.hour();
7560
- const hour12Value = hour24Value % 12 || 12;
7561
- const minuteValue = dateObj.minute();
7562
- const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
7563
- const secondValue = showSeconds ? dateObj.second() : null;
7564
- const result = {
7565
- hour12: hour12Value,
7566
- minute: minuteValue,
7567
- meridiem: meridiemValue,
7568
- hour24: hour24Value,
7569
- second: secondValue,
7570
- };
7571
- console.debug('[DateTimePicker] Extracted time values:', result);
7572
- return result;
7573
- }, [timezone, showSeconds]);
7574
- const initialTime = getTimeFromValue(value);
7575
- console.debug('[DateTimePicker] Initial time from value:', {
7576
- value,
7577
- initialTime,
7578
- });
7579
- // Normalize startTime to ignore milliseconds (needed for effectiveDefaultDate calculation)
7580
- const normalizedStartTime = startTime
7581
- ? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
7582
- : undefined;
7583
- // Calculate effective defaultDate: use prop if provided, otherwise use startTime date, otherwise use today
7584
- const effectiveDefaultDate = useMemo(() => {
7585
- if (defaultDate) {
7586
- return defaultDate;
7587
- }
7588
- if (normalizedStartTime &&
7589
- dayjs(normalizedStartTime).tz(timezone).isValid()) {
7590
- return dayjs(normalizedStartTime).tz(timezone).format('YYYY-MM-DD');
7591
- }
7592
- return dayjs().tz(timezone).format('YYYY-MM-DD');
7593
- }, [defaultDate, normalizedStartTime, timezone]);
7594
- // Initialize time with default values if no value is provided
7595
- const getInitialTimeValues = () => {
7596
- if (value && initialTime.hour12 !== null) {
7597
- return initialTime;
7598
- }
7599
- // If no value or no time in value, use defaultTime or 00:00
7600
- if (defaultTime) {
7601
- if (format === 'iso-date-time') {
7602
- const defaultTime24 = defaultTime;
7603
- return {
7604
- hour12: null,
7605
- minute: defaultTime24.minute ?? 0,
7606
- meridiem: null,
7607
- hour24: defaultTime24.hour ?? 0,
7608
- second: showSeconds ? defaultTime24.second ?? 0 : null,
7609
- };
7610
- }
7611
- else {
7612
- const defaultTime12 = defaultTime;
7613
- return {
7614
- hour12: defaultTime12.hour ?? 12,
7615
- minute: defaultTime12.minute ?? 0,
7616
- meridiem: defaultTime12.meridiem ?? 'am',
7617
- hour24: null,
7618
- second: null,
7619
- };
7620
- }
7794
+ return true;
7795
+ };
7796
+ // Handle quick action button clicks
7797
+ const handleQuickActionClick = (date) => {
7798
+ if (isDateValid(date)) {
7799
+ setSelectedDate(date);
7800
+ updateDateTime(date, hour, minute, second, meridiem);
7801
+ // Close the calendar popover if open
7802
+ setCalendarPopoverOpen(false);
7621
7803
  }
7622
- // Default to 00:00
7623
- if (format === 'iso-date-time') {
7624
- return {
7625
- hour12: null,
7626
- minute: 0,
7627
- meridiem: null,
7628
- hour24: 0,
7629
- second: showSeconds ? 0 : null,
7630
- };
7804
+ };
7805
+ // Display text for buttons
7806
+ const dateDisplayText = useMemo(() => {
7807
+ if (!selectedDate)
7808
+ return 'Select date';
7809
+ return dayjs(selectedDate).tz(tz).format('YYYY-MM-DD');
7810
+ }, [selectedDate, tz]);
7811
+ const timeDisplayText = useMemo(() => {
7812
+ if (hour === null || minute === null)
7813
+ return 'Select time';
7814
+ if (is24Hour) {
7815
+ // 24-hour format: never show meridiem, always use 24-hour format (0-23)
7816
+ const hour24 = hour >= 0 && hour <= 23 ? hour : hour % 24;
7817
+ const s = second ?? 0;
7818
+ if (showSeconds) {
7819
+ return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
7820
+ }
7821
+ return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
7631
7822
  }
7632
7823
  else {
7633
- return {
7634
- hour12: 12,
7635
- minute: 0,
7636
- meridiem: 'am',
7637
- hour24: null,
7638
- second: null,
7639
- };
7640
- }
7641
- };
7642
- const initialTimeValues = getInitialTimeValues();
7643
- // Time state for 12-hour format
7644
- const [hour12, setHour12] = useState(initialTimeValues.hour12);
7645
- const [minute, setMinute] = useState(initialTimeValues.minute);
7646
- const [meridiem, setMeridiem] = useState(initialTimeValues.meridiem);
7647
- // Time state for 24-hour format
7648
- const [hour24, setHour24] = useState(initialTimeValues.hour24);
7649
- const [second, setSecond] = useState(initialTimeValues.second);
7650
- // Sync selectedDate and time states when value prop changes
7824
+ // 12-hour format: always show meridiem (AM/PM)
7825
+ const hour12 = hour >= 1 && hour <= 12 ? hour : hour % 12;
7826
+ if (meridiem === null)
7827
+ return 'Select time';
7828
+ const hourDisplay = hour12.toString();
7829
+ const minuteDisplay = minute.toString().padStart(2, '0');
7830
+ return `${hourDisplay}:${minuteDisplay} ${meridiem.toUpperCase()}`;
7831
+ }
7832
+ }, [hour, minute, second, meridiem, is24Hour, showSeconds]);
7833
+ const timezoneDisplayText = useMemo(() => {
7834
+ if (!showTimezoneSelector)
7835
+ return '';
7836
+ // Show offset as is (e.g., "+08:00")
7837
+ return timezoneOffset;
7838
+ }, [timezoneOffset, showTimezoneSelector]);
7839
+ // Update selectedDate when value changes externally
7651
7840
  useEffect(() => {
7652
- console.debug('[DateTimePicker] useEffect triggered - value changed:', {
7653
- value,
7654
- timezone,
7655
- format,
7656
- });
7657
- // If value is null, undefined, or invalid, clear date but keep default time values
7658
- if (!value || value === null || value === undefined) {
7659
- console.debug('[DateTimePicker] Value is null/undefined, clearing date but keeping default time');
7660
- setSelectedDate('');
7661
- // Keep default time values instead of clearing them
7662
- if (format === 'iso-date-time') {
7663
- setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
7664
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7665
- setSecond(showSeconds
7666
- ? defaultTime
7667
- ? defaultTime.second ?? 0
7668
- : 0
7669
- : null);
7670
- }
7671
- else {
7672
- setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
7673
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7674
- setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
7841
+ if (parsedValue) {
7842
+ setSelectedDate(parsedValue.toDate());
7843
+ setHour(parsedValue.hour());
7844
+ setMinute(parsedValue.minute());
7845
+ setSecond(parsedValue.second());
7846
+ if (!is24Hour) {
7847
+ const h = parsedValue.hour();
7848
+ setMeridiem(h < 12 ? 'am' : 'pm');
7675
7849
  }
7850
+ }
7851
+ }, [parsedValue, is24Hour]);
7852
+ // Combine date and time and call onChange
7853
+ const updateDateTime = (newDate, newHour, newMinute, newSecond, newMeridiem, timezoneOffsetOverride) => {
7854
+ if (!newDate || newHour === null || newMinute === null) {
7855
+ onChange?.(undefined);
7676
7856
  return;
7677
7857
  }
7678
- // Check if value is valid
7679
- const dateObj = dayjs(value).tz(timezone);
7680
- if (!dateObj.isValid()) {
7681
- console.debug('[DateTimePicker] Invalid value, clearing date but keeping default time');
7682
- setSelectedDate('');
7683
- // Keep default time values instead of clearing them
7684
- if (format === 'iso-date-time') {
7685
- setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
7686
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7687
- setSecond(showSeconds
7688
- ? defaultTime
7689
- ? defaultTime.second ?? 0
7690
- : 0
7691
- : null);
7858
+ // Convert 12-hour to 24-hour if needed
7859
+ let hour24 = newHour;
7860
+ if (!is24Hour && newMeridiem) {
7861
+ // In 12-hour format, hour should be 1-12
7862
+ // If hour is > 12, it might already be in 24-hour format, convert it first
7863
+ let hour12 = newHour;
7864
+ if (newHour > 12) {
7865
+ // Hour is in 24-hour format, convert to 12-hour first
7866
+ if (newHour === 12) {
7867
+ hour12 = 12;
7868
+ }
7869
+ else {
7870
+ hour12 = newHour - 12;
7871
+ }
7872
+ }
7873
+ // Now convert 12-hour to 24-hour format (0-23)
7874
+ if (newMeridiem === 'am') {
7875
+ if (hour12 === 12) {
7876
+ hour24 = 0; // 12 AM = 0:00
7877
+ }
7878
+ else {
7879
+ hour24 = hour12; // 1-11 AM = 1-11
7880
+ }
7692
7881
  }
7693
7882
  else {
7694
- setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
7695
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
7696
- setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
7883
+ // PM
7884
+ if (hour12 === 12) {
7885
+ hour24 = 12; // 12 PM = 12:00
7886
+ }
7887
+ else {
7888
+ hour24 = hour12 + 12; // 1-11 PM = 13-23
7889
+ }
7697
7890
  }
7891
+ }
7892
+ else if (!is24Hour && !newMeridiem) {
7893
+ // If in 12-hour mode but no meridiem, assume the hour is already in 12-hour format
7894
+ // and default to AM (or keep as is if it's a valid 12-hour value)
7895
+ // This shouldn't happen in normal flow, but handle it gracefully
7896
+ hour24 = newHour;
7897
+ }
7898
+ // If timezone selector is enabled, create date-time without timezone conversion
7899
+ // to ensure the selected timestamp matches the picker values exactly
7900
+ if (showTimezoneSelector) {
7901
+ // Use override if provided, otherwise use state value
7902
+ const offsetToUse = timezoneOffsetOverride ?? timezoneOffset;
7903
+ // Create date-time from the Date object without timezone conversion
7904
+ // Extract year, month, day from the date
7905
+ const year = newDate.getFullYear();
7906
+ const month = newDate.getMonth();
7907
+ const day = newDate.getDate();
7908
+ // Create a date-time string with the exact values from the picker
7909
+ 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')}`;
7910
+ onChange?.(`${formattedDateTime}${offsetToUse}`);
7698
7911
  return;
7699
7912
  }
7700
- const dateString = getDateString(value);
7701
- console.debug('[DateTimePicker] Setting selectedDate:', dateString);
7702
- setSelectedDate(dateString);
7703
- const timeData = getTimeFromValue(value);
7704
- console.debug('[DateTimePicker] Updating time states:', {
7705
- timeData,
7706
- });
7707
- setHour12(timeData.hour12);
7708
- setMinute(timeData.minute);
7709
- setMeridiem(timeData.meridiem);
7710
- setHour24(timeData.hour24);
7711
- setSecond(timeData.second);
7712
- }, [value, getTimeFromValue, getDateString, timezone]);
7713
- const handleDateChange = (date) => {
7714
- console.debug('[DateTimePicker] handleDateChange called:', {
7715
- date,
7716
- timezone,
7717
- showSeconds,
7718
- currentTimeStates: { hour12, minute, meridiem, hour24, second },
7719
- });
7720
- // If date is empty or invalid, clear all fields
7721
- if (!date || date === '') {
7722
- console.debug('[DateTimePicker] Empty date, clearing all fields');
7723
- setSelectedDate('');
7724
- setHour12(null);
7725
- setMinute(null);
7726
- setMeridiem(null);
7727
- setHour24(null);
7728
- setSecond(null);
7913
+ // Normal mode: use timezone conversion
7914
+ let dateTime = dayjs(newDate)
7915
+ .tz(tz)
7916
+ .hour(hour24)
7917
+ .minute(newMinute)
7918
+ .second(newSecond ?? 0)
7919
+ .millisecond(0);
7920
+ if (!dateTime.isValid()) {
7729
7921
  onChange?.(undefined);
7730
7922
  return;
7731
7923
  }
7924
+ // Format based on format prop
7925
+ if (format === 'iso-date-time') {
7926
+ onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ss'));
7927
+ }
7928
+ else {
7929
+ // date-time format with timezone
7930
+ onChange?.(dateTime.format('YYYY-MM-DDTHH:mm:ssZ'));
7931
+ }
7932
+ };
7933
+ // Handle date selection
7934
+ const handleDateSelected = ({ date, }) => {
7732
7935
  setSelectedDate(date);
7733
- // Parse the date string (YYYY-MM-DD) in the specified timezone
7734
- const dateObj = dayjs.tz(date, timezone);
7735
- console.debug('[DateTimePicker] Parsed date object:', {
7736
- date,
7737
- timezone,
7738
- isValid: dateObj.isValid(),
7739
- isoString: dateObj.toISOString(),
7740
- formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
7741
- });
7742
- if (!dateObj.isValid()) {
7743
- console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
7744
- setSelectedDate('');
7745
- setHour12(null);
7746
- setMinute(null);
7747
- setMeridiem(null);
7748
- setHour24(null);
7749
- setSecond(null);
7750
- onChange?.(undefined);
7751
- return;
7936
+ updateDateTime(date, hour, minute, second, meridiem);
7937
+ };
7938
+ // Handle time change
7939
+ const handleTimeChange = (newHour, newMinute, newSecond, newMeridiem) => {
7940
+ setHour(newHour);
7941
+ setMinute(newMinute);
7942
+ if (is24Hour) {
7943
+ setSecond(newSecond);
7944
+ }
7945
+ else {
7946
+ setMeridiem(newMeridiem);
7947
+ }
7948
+ if (selectedDate) {
7949
+ updateDateTime(selectedDate, newHour, newMinute, newSecond, newMeridiem);
7950
+ }
7951
+ };
7952
+ // Calendar hook
7953
+ const calendarProps = useCalendar({
7954
+ selected: selectedDate || undefined,
7955
+ date: selectedDate || undefined,
7956
+ minDate,
7957
+ maxDate,
7958
+ monthsToDisplay: 1,
7959
+ onDateSelected: handleDateSelected,
7960
+ });
7961
+ // Generate time options
7962
+ const timeOptions = useMemo(() => {
7963
+ const options = [];
7964
+ // Get start time for comparison if provided
7965
+ let startDateTime = null;
7966
+ let shouldFilterByDate = false;
7967
+ if (startTime && selectedDate) {
7968
+ const startDateObj = dayjs(startTime).tz(tz);
7969
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
7970
+ if (startDateObj.isValid() && selectedDateObj.isValid()) {
7971
+ startDateTime = startDateObj;
7972
+ shouldFilterByDate =
7973
+ startDateObj.format('YYYY-MM-DD') ===
7974
+ selectedDateObj.format('YYYY-MM-DD');
7975
+ }
7752
7976
  }
7753
- // Check if time values are null - if so, use defaultTime or set to 00:00
7754
- const hasTimeValues = format === 'iso-date-time'
7755
- ? hour24 !== null || minute !== null
7756
- : hour12 !== null || minute !== null || meridiem !== null;
7757
- let timeDataToUse = undefined;
7758
- if (!hasTimeValues) {
7759
- // Use defaultTime if provided, otherwise default to 00:00
7760
- if (defaultTime) {
7761
- console.debug('[DateTimePicker] No time values set, using defaultTime');
7762
- if (format === 'iso-date-time') {
7763
- const defaultTime24 = defaultTime;
7764
- setHour24(defaultTime24.hour ?? 0);
7765
- setMinute(defaultTime24.minute ?? 0);
7766
- if (showSeconds) {
7767
- setSecond(defaultTime24.second ?? 0);
7977
+ if (is24Hour) {
7978
+ // Generate 24-hour format options
7979
+ for (let h = 0; h < 24; h++) {
7980
+ for (let m = 0; m < 60; m += 15) {
7981
+ // Filter out times that would result in negative duration
7982
+ if (startDateTime && selectedDate && shouldFilterByDate) {
7983
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
7984
+ const optionDateTime = selectedDateObj
7985
+ .hour(h)
7986
+ .minute(m)
7987
+ .second(0)
7988
+ .millisecond(0);
7989
+ if (optionDateTime.isBefore(startDateTime)) {
7990
+ continue;
7991
+ }
7768
7992
  }
7769
- timeDataToUse = {
7770
- hour: defaultTime24.hour ?? 0,
7771
- minute: defaultTime24.minute ?? 0,
7772
- second: showSeconds ? defaultTime24.second ?? 0 : undefined,
7773
- };
7774
- }
7775
- else {
7776
- const defaultTime12 = defaultTime;
7777
- setHour12(defaultTime12.hour ?? 12);
7778
- setMinute(defaultTime12.minute ?? 0);
7779
- setMeridiem(defaultTime12.meridiem ?? 'am');
7780
- timeDataToUse = {
7781
- hour: defaultTime12.hour ?? 12,
7782
- minute: defaultTime12.minute ?? 0,
7783
- meridiem: defaultTime12.meridiem ?? 'am',
7784
- };
7993
+ // Calculate duration if startTime is provided
7994
+ let durationText;
7995
+ if (startDateTime && selectedDate) {
7996
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
7997
+ const optionDateTime = selectedDateObj
7998
+ .hour(h)
7999
+ .minute(m)
8000
+ .second(0)
8001
+ .millisecond(0);
8002
+ if (optionDateTime.isValid() &&
8003
+ optionDateTime.isAfter(startDateTime)) {
8004
+ const diffMs = optionDateTime.diff(startDateTime);
8005
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
8006
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
8007
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
8008
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
8009
+ let diffText = '';
8010
+ if (diffHours > 0) {
8011
+ diffText = `${diffHours}h ${diffMinutes}m`;
8012
+ }
8013
+ else if (diffMinutes > 0) {
8014
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
8015
+ }
8016
+ else {
8017
+ diffText = `${diffSeconds}s`;
8018
+ }
8019
+ durationText = `+${diffText}`;
8020
+ }
8021
+ }
8022
+ }
8023
+ const s = showSeconds ? 0 : 0;
8024
+ const timeDisplay = showSeconds
8025
+ ? `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00`
8026
+ : `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
8027
+ options.push({
8028
+ label: timeDisplay,
8029
+ value: `${h}:${m}:${s}`,
8030
+ hour: h,
8031
+ minute: m,
8032
+ second: s,
8033
+ searchText: timeDisplay,
8034
+ durationText,
8035
+ });
7785
8036
  }
7786
8037
  }
7787
- else {
7788
- console.debug('[DateTimePicker] No time values set, defaulting to 00:00');
7789
- if (format === 'iso-date-time') {
7790
- setHour24(0);
7791
- setMinute(0);
7792
- if (showSeconds) {
7793
- setSecond(0);
8038
+ }
8039
+ else {
8040
+ // Generate 12-hour format options
8041
+ for (let h = 1; h <= 12; h++) {
8042
+ for (let m = 0; m < 60; m += 15) {
8043
+ for (const mer of ['am', 'pm']) {
8044
+ // Convert 12-hour to 24-hour for comparison
8045
+ let hour24 = h;
8046
+ if (mer === 'am' && h === 12)
8047
+ hour24 = 0;
8048
+ else if (mer === 'pm' && h < 12)
8049
+ hour24 = h + 12;
8050
+ // Filter out times that would result in negative duration
8051
+ if (startDateTime && selectedDate && shouldFilterByDate) {
8052
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8053
+ const optionDateTime = selectedDateObj
8054
+ .hour(hour24)
8055
+ .minute(m)
8056
+ .second(0)
8057
+ .millisecond(0);
8058
+ if (optionDateTime.isBefore(startDateTime)) {
8059
+ continue;
8060
+ }
8061
+ }
8062
+ // Calculate duration if startTime is provided
8063
+ let durationText;
8064
+ if (startDateTime && selectedDate) {
8065
+ const selectedDateObj = dayjs(selectedDate).tz(tz);
8066
+ const optionDateTime = selectedDateObj
8067
+ .hour(hour24)
8068
+ .minute(m)
8069
+ .second(0)
8070
+ .millisecond(0);
8071
+ if (optionDateTime.isValid() &&
8072
+ optionDateTime.isAfter(startDateTime)) {
8073
+ const diffMs = optionDateTime.diff(startDateTime);
8074
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
8075
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
8076
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
8077
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
8078
+ let diffText = '';
8079
+ if (diffHours > 0) {
8080
+ diffText = `${diffHours}h ${diffMinutes}m`;
8081
+ }
8082
+ else if (diffMinutes > 0) {
8083
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
8084
+ }
8085
+ else {
8086
+ diffText = `${diffSeconds}s`;
8087
+ }
8088
+ durationText = `+${diffText}`;
8089
+ }
8090
+ }
8091
+ }
8092
+ const hourDisplay = h.toString();
8093
+ const minuteDisplay = m.toString().padStart(2, '0');
8094
+ const timeDisplay = `${hourDisplay}:${minuteDisplay} ${mer.toUpperCase()}`;
8095
+ options.push({
8096
+ label: timeDisplay,
8097
+ value: `${h}:${m}:${mer}`,
8098
+ hour: h,
8099
+ minute: m,
8100
+ meridiem: mer,
8101
+ searchText: timeDisplay,
8102
+ durationText,
8103
+ });
7794
8104
  }
7795
- timeDataToUse = {
7796
- hour: 0,
7797
- minute: 0,
7798
- second: showSeconds ? 0 : undefined,
7799
- };
7800
- }
7801
- else {
7802
- setHour12(12);
7803
- setMinute(0);
7804
- setMeridiem('am');
7805
- timeDataToUse = {
7806
- hour: 12,
7807
- minute: 0,
7808
- meridiem: 'am',
7809
- };
7810
8105
  }
7811
8106
  }
8107
+ // Sort 12-hour options by time
8108
+ return options.sort((a, b) => {
8109
+ const a12 = a;
8110
+ const b12 = b;
8111
+ let hour24A = a12.hour;
8112
+ if (a12.meridiem === 'am' && a12.hour === 12)
8113
+ hour24A = 0;
8114
+ else if (a12.meridiem === 'pm' && a12.hour < 12)
8115
+ hour24A = a12.hour + 12;
8116
+ let hour24B = b12.hour;
8117
+ if (b12.meridiem === 'am' && b12.hour === 12)
8118
+ hour24B = 0;
8119
+ else if (b12.meridiem === 'pm' && b12.hour < 12)
8120
+ hour24B = b12.hour + 12;
8121
+ if (hour24A !== hour24B) {
8122
+ return hour24A - hour24B;
8123
+ }
8124
+ return a12.minute - b12.minute;
8125
+ });
7812
8126
  }
7813
- // When showSeconds is false, ignore seconds from the date
7814
- if (!showSeconds) {
7815
- const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
7816
- console.debug('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
7817
- updateDateTime(dateWithoutSeconds, timeDataToUse);
7818
- }
7819
- else {
7820
- const dateWithSeconds = dateObj.toISOString();
7821
- console.debug('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
7822
- updateDateTime(dateWithSeconds, timeDataToUse);
8127
+ return options;
8128
+ }, [startTime, selectedDate, tz, is24Hour, showSeconds]);
8129
+ // Time picker combobox setup
8130
+ const itemToString = useMemo(() => {
8131
+ return (item) => {
8132
+ return item.searchText;
8133
+ };
8134
+ }, []);
8135
+ const { contains } = useFilter({ sensitivity: 'base' });
8136
+ const customTimeFilter = useMemo(() => {
8137
+ if (is24Hour) {
8138
+ return contains;
7823
8139
  }
7824
- };
7825
- const handleTimeChange = (timeData) => {
7826
- console.debug('[DateTimePicker] handleTimeChange called:', {
7827
- timeData,
7828
- format,
7829
- selectedDate,
7830
- timezone,
7831
- });
7832
- if (format === 'iso-date-time') {
7833
- const data = timeData;
7834
- console.debug('[DateTimePicker] ISO format - setting 24-hour time:', data);
7835
- setHour24(data.hour);
7836
- setMinute(data.minute);
7837
- if (showSeconds) {
7838
- setSecond(data.second ?? null);
8140
+ return (itemText, filterText) => {
8141
+ if (!filterText) {
8142
+ return true;
7839
8143
  }
7840
- else {
7841
- // Ignore seconds - always set to null when showSeconds is false
7842
- setSecond(null);
8144
+ const lowerItemText = itemText.toLowerCase();
8145
+ const lowerFilterText = filterText.toLowerCase();
8146
+ if (lowerItemText.includes(lowerFilterText)) {
8147
+ return true;
8148
+ }
8149
+ const item = timeOptions.find((opt) => opt.searchText.toLowerCase() === lowerItemText);
8150
+ if (!item || !('meridiem' in item)) {
8151
+ return false;
7843
8152
  }
8153
+ let hour24 = item.hour;
8154
+ if (item.meridiem === 'am' && item.hour === 12)
8155
+ hour24 = 0;
8156
+ else if (item.meridiem === 'pm' && item.hour < 12)
8157
+ hour24 = item.hour + 12;
8158
+ const hour24Str = hour24.toString().padStart(2, '0');
8159
+ const minuteStr = item.minute.toString().padStart(2, '0');
8160
+ const formats = [
8161
+ `${hour24Str}:${minuteStr}`,
8162
+ `${hour24Str}${minuteStr}`,
8163
+ hour24Str,
8164
+ `${hour24}:${minuteStr}`,
8165
+ hour24.toString(),
8166
+ ];
8167
+ return formats.some((format) => format.toLowerCase().includes(lowerFilterText) ||
8168
+ lowerFilterText.includes(format.toLowerCase()));
8169
+ };
8170
+ }, [timeOptions, is24Hour, contains]);
8171
+ const { collection, filter } = useListCollection({
8172
+ initialItems: timeOptions,
8173
+ itemToString: itemToString,
8174
+ itemToValue: (item) => item.value,
8175
+ filter: customTimeFilter,
8176
+ });
8177
+ // Get current value string for combobox (must match option.value format)
8178
+ const currentTimeValue = useMemo(() => {
8179
+ if (is24Hour) {
8180
+ if (hour === null || minute === null) {
8181
+ return '';
8182
+ }
8183
+ const s = second ?? 0;
8184
+ return `${hour}:${minute}:${s}`;
7844
8185
  }
7845
8186
  else {
7846
- const data = timeData;
7847
- console.debug('[DateTimePicker] 12-hour format - setting time:', data);
7848
- setHour12(data.hour);
7849
- setMinute(data.minute);
7850
- setMeridiem(data.meridiem);
7851
- }
7852
- // Use selectedDate if valid, otherwise use effectiveDefaultDate or clear all fields
7853
- if (!selectedDate || !dayjs(selectedDate).isValid()) {
7854
- // If effectiveDefaultDate is available, use it instead of clearing
7855
- if (effectiveDefaultDate && dayjs(effectiveDefaultDate).isValid()) {
7856
- console.debug('[DateTimePicker] No valid selectedDate, using effectiveDefaultDate:', effectiveDefaultDate);
7857
- setSelectedDate(effectiveDefaultDate);
7858
- const dateObj = dayjs(effectiveDefaultDate).tz(timezone);
7859
- if (dateObj.isValid()) {
7860
- updateDateTime(dateObj.toISOString(), timeData);
8187
+ if (hour === null || minute === null || meridiem === null) {
8188
+ return '';
8189
+ }
8190
+ return `${hour}:${minute}:${meridiem}`;
8191
+ }
8192
+ }, [hour, minute, second, meridiem, is24Hour]);
8193
+ // Parse custom time input formats like "1400", "2pm", "14:00", "2:00 PM"
8194
+ const parseCustomTimeInput = (input) => {
8195
+ if (!input || !input.trim()) {
8196
+ return { hour: null, minute: null, second: null, meridiem: null };
8197
+ }
8198
+ const trimmed = input.trim().toLowerCase();
8199
+ // Try parsing 4-digit format without colon: "1400" -> 14:00
8200
+ const fourDigitMatch = trimmed.match(/^(\d{4})$/);
8201
+ if (fourDigitMatch) {
8202
+ const digits = fourDigitMatch[1];
8203
+ const hour = parseInt(digits.substring(0, 2), 10);
8204
+ const minute = parseInt(digits.substring(2, 4), 10);
8205
+ if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
8206
+ if (is24Hour) {
8207
+ return { hour, minute, second: 0, meridiem: null };
7861
8208
  }
7862
8209
  else {
7863
- console.warn('[DateTimePicker] Invalid effectiveDefaultDate, clearing fields');
7864
- setSelectedDate('');
7865
- setHour12(null);
7866
- setMinute(null);
7867
- setMeridiem(null);
7868
- setHour24(null);
7869
- setSecond(null);
7870
- onChange?.(undefined);
8210
+ // Convert to 12-hour format
8211
+ let hour12 = hour;
8212
+ let meridiem;
8213
+ if (hour === 0) {
8214
+ hour12 = 12;
8215
+ meridiem = 'am';
8216
+ }
8217
+ else if (hour === 12) {
8218
+ hour12 = 12;
8219
+ meridiem = 'pm';
8220
+ }
8221
+ else if (hour > 12) {
8222
+ hour12 = hour - 12;
8223
+ meridiem = 'pm';
8224
+ }
8225
+ else {
8226
+ hour12 = hour;
8227
+ meridiem = 'am';
8228
+ }
8229
+ return { hour: hour12, minute, second: null, meridiem };
7871
8230
  }
7872
- return;
7873
8231
  }
7874
- else {
7875
- console.debug('[DateTimePicker] No valid selectedDate and no effectiveDefaultDate, keeping time values but no date');
7876
- // Keep the time values that were just set, but don't set a date
7877
- // This should rarely happen as effectiveDefaultDate always defaults to today
7878
- setSelectedDate('');
7879
- onChange?.(undefined);
7880
- return;
8232
+ }
8233
+ // Try parsing hour with meridiem: "2pm", "14pm", "2am"
8234
+ const hourMeridiemMatch = trimmed.match(/^(\d{1,2})\s*(am|pm)$/);
8235
+ if (hourMeridiemMatch && !is24Hour) {
8236
+ const hour12 = parseInt(hourMeridiemMatch[1], 10);
8237
+ const meridiem = hourMeridiemMatch[2];
8238
+ if (hour12 >= 1 && hour12 <= 12) {
8239
+ return { hour: hour12, minute: 0, second: null, meridiem };
7881
8240
  }
7882
8241
  }
7883
- const dateObj = dayjs(selectedDate).tz(timezone);
7884
- if (dateObj.isValid()) {
7885
- updateDateTime(dateObj.toISOString(), timeData);
8242
+ // Try parsing 24-hour format with hour only: "14" -> 14:00
8243
+ const hourOnlyMatch = trimmed.match(/^(\d{1,2})$/);
8244
+ if (hourOnlyMatch && is24Hour) {
8245
+ const hour = parseInt(hourOnlyMatch[1], 10);
8246
+ if (hour >= 0 && hour <= 23) {
8247
+ return { hour, minute: 0, second: 0, meridiem: null };
8248
+ }
7886
8249
  }
7887
- else {
7888
- console.warn('[DateTimePicker] Invalid date object in handleTimeChange, clearing fields');
7889
- setSelectedDate('');
7890
- setHour12(null);
7891
- setMinute(null);
7892
- setMeridiem(null);
7893
- setHour24(null);
7894
- setSecond(null);
7895
- onChange?.(undefined);
8250
+ // Try parsing standard formats: "14:00", "2:00 PM"
8251
+ const time24Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
8252
+ const match24 = trimmed.match(time24Pattern);
8253
+ if (match24) {
8254
+ const hour24 = parseInt(match24[1], 10);
8255
+ const minute = parseInt(match24[2], 10);
8256
+ const second = match24[3] ? parseInt(match24[3], 10) : 0;
8257
+ if (hour24 >= 0 &&
8258
+ hour24 <= 23 &&
8259
+ minute >= 0 &&
8260
+ minute <= 59 &&
8261
+ second >= 0 &&
8262
+ second <= 59) {
8263
+ if (is24Hour) {
8264
+ return { hour: hour24, minute, second, meridiem: null };
8265
+ }
8266
+ else {
8267
+ // Convert to 12-hour format
8268
+ let hour12 = hour24;
8269
+ let meridiem;
8270
+ if (hour24 === 0) {
8271
+ hour12 = 12;
8272
+ meridiem = 'am';
8273
+ }
8274
+ else if (hour24 === 12) {
8275
+ hour12 = 12;
8276
+ meridiem = 'pm';
8277
+ }
8278
+ else if (hour24 > 12) {
8279
+ hour12 = hour24 - 12;
8280
+ meridiem = 'pm';
8281
+ }
8282
+ else {
8283
+ hour12 = hour24;
8284
+ meridiem = 'am';
8285
+ }
8286
+ return { hour: hour12, minute, second: null, meridiem };
8287
+ }
8288
+ }
7896
8289
  }
7897
- };
7898
- const updateDateTime = (date, timeData) => {
7899
- console.debug('[DateTimePicker] updateDateTime called:', {
7900
- date,
7901
- timeData,
7902
- format,
7903
- currentStates: { hour12, minute, meridiem, hour24, second },
7904
- });
7905
- if (!date || date === null || date === undefined) {
7906
- console.debug('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
7907
- setSelectedDate('');
7908
- setHour12(null);
7909
- setMinute(null);
7910
- setMeridiem(null);
7911
- setHour24(null);
7912
- setSecond(null);
7913
- onChange?.(undefined);
7914
- return;
8290
+ // Try parsing 12-hour format: "2:00 PM", "2:00PM"
8291
+ const time12Pattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm)$/;
8292
+ const match12 = trimmed.match(time12Pattern);
8293
+ if (match12 && !is24Hour) {
8294
+ const hour12 = parseInt(match12[1], 10);
8295
+ const minute = parseInt(match12[2], 10);
8296
+ const second = match12[3] ? parseInt(match12[3], 10) : null;
8297
+ const meridiem = match12[4];
8298
+ if (hour12 >= 1 &&
8299
+ hour12 <= 12 &&
8300
+ minute >= 0 &&
8301
+ minute <= 59 &&
8302
+ (second === null || (second >= 0 && second <= 59))) {
8303
+ return { hour: hour12, minute, second, meridiem };
8304
+ }
7915
8305
  }
7916
- // use dayjs to convert the date to the timezone
7917
- const dateObj = dayjs(date).tz(timezone);
7918
- if (!dateObj.isValid()) {
7919
- console.warn('[DateTimePicker] Invalid date object in updateDateTime, clearing fields:', date);
7920
- setSelectedDate('');
7921
- setHour12(null);
7922
- setMinute(null);
7923
- setMeridiem(null);
7924
- setHour24(null);
7925
- setSecond(null);
7926
- onChange?.(undefined);
8306
+ return { hour: null, minute: null, second: null, meridiem: null };
8307
+ };
8308
+ const handleTimeValueChange = (details) => {
8309
+ if (details.value.length === 0) {
8310
+ handleTimeChange(null, null, null, null);
8311
+ filter('');
7927
8312
  return;
7928
8313
  }
7929
- const newDate = dateObj.toDate();
7930
- if (format === 'iso-date-time') {
7931
- const data = timeData;
7932
- // Use timeData values if provided, otherwise fall back to current state
7933
- // But if timeData is explicitly provided with nulls, we need to check if all are null
7934
- const h = data !== undefined ? data.hour : hour24;
7935
- const m = data !== undefined ? data.minute : minute;
7936
- // Always ignore seconds when showSeconds is false - set to 0
7937
- const s = showSeconds
7938
- ? data !== undefined
7939
- ? data.second ?? null
7940
- : second ?? 0
7941
- : 0;
7942
- // If all time values are null, clear the value
7943
- if (h === null && m === null && (showSeconds ? s === null : true)) {
7944
- console.debug('[DateTimePicker] All time values are null, clearing value');
7945
- onChange?.(undefined);
7946
- return;
7947
- }
7948
- console.debug('[DateTimePicker] ISO format - setting time on date:', {
7949
- h,
7950
- m,
7951
- s,
7952
- showSeconds,
7953
- });
7954
- if (h !== null)
7955
- newDate.setHours(h);
7956
- if (m !== null)
7957
- newDate.setMinutes(m);
7958
- newDate.setSeconds(s ?? 0);
7959
- }
7960
- else {
7961
- const data = timeData;
7962
- console.debug('[DateTimePicker] Processing 12-hour format:', {
7963
- 'data !== undefined': data !== undefined,
7964
- 'data?.hour': data?.hour,
7965
- 'data?.minute': data?.minute,
7966
- 'data?.meridiem': data?.meridiem,
7967
- 'current hour12': hour12,
7968
- 'current minute': minute,
7969
- 'current meridiem': meridiem,
7970
- });
7971
- // Use timeData values if provided, otherwise fall back to current state
7972
- const h = data !== undefined ? data.hour : hour12;
7973
- const m = data !== undefined ? data.minute : minute;
7974
- const mer = data !== undefined ? data.meridiem : meridiem;
7975
- console.debug('[DateTimePicker] Resolved time values:', { h, m, mer });
7976
- // If all time values are null, clear the value
7977
- if (h === null && m === null && mer === null) {
7978
- console.debug('[DateTimePicker] All time values are null, clearing value');
7979
- onChange?.(undefined);
7980
- return;
7981
- }
7982
- console.debug('[DateTimePicker] 12-hour format - converting time:', {
7983
- h,
7984
- m,
7985
- mer,
7986
- });
7987
- if (h !== null && mer !== null) {
7988
- let hour24 = h;
7989
- if (mer === 'am' && h === 12)
7990
- hour24 = 0;
7991
- else if (mer === 'pm' && h < 12)
7992
- hour24 = h + 12;
7993
- console.debug('[DateTimePicker] Converted to 24-hour:', {
7994
- h,
7995
- mer,
7996
- hour24,
7997
- });
7998
- newDate.setHours(hour24);
7999
- }
8000
- else {
8001
- console.debug('[DateTimePicker] Skipping hour update - h or mer is null:', {
8002
- h,
8003
- mer,
8004
- });
8005
- }
8006
- if (m !== null) {
8007
- newDate.setMinutes(m);
8314
+ const selectedValue = details.value[0];
8315
+ const selectedOption = timeOptions.find((opt) => opt.value === selectedValue);
8316
+ if (selectedOption) {
8317
+ filter('');
8318
+ if (is24Hour) {
8319
+ const opt24 = selectedOption;
8320
+ handleTimeChange(opt24.hour, opt24.minute, opt24.second, null);
8008
8321
  }
8009
8322
  else {
8010
- console.debug('[DateTimePicker] Skipping minute update - m is null');
8323
+ const opt12 = selectedOption;
8324
+ handleTimeChange(opt12.hour, opt12.minute, null, opt12.meridiem);
8011
8325
  }
8012
- newDate.setSeconds(0);
8013
8326
  }
8014
- const finalISO = dayjs(newDate).tz(timezone).toISOString();
8015
- console.debug('[DateTimePicker] Final ISO string to emit:', {
8016
- newDate: newDate.toISOString(),
8017
- timezone,
8018
- finalISO,
8019
- });
8020
- onChange?.(finalISO);
8021
8327
  };
8022
- const handleClear = () => {
8023
- setSelectedDate('');
8024
- // Reset to default time values instead of clearing them
8025
- if (format === 'iso-date-time') {
8026
- setHour24(defaultTime ? defaultTime.hour ?? 0 : 0);
8027
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
8028
- setSecond(showSeconds
8029
- ? defaultTime
8030
- ? defaultTime.second ?? 0
8031
- : 0
8032
- : null);
8033
- }
8034
- else {
8035
- setHour12(defaultTime ? defaultTime.hour ?? 12 : 12);
8036
- setMinute(defaultTime ? defaultTime.minute ?? 0 : 0);
8037
- setMeridiem(defaultTime ? defaultTime.meridiem ?? 'am' : 'am');
8038
- }
8039
- onChange?.(undefined);
8040
- };
8041
- const isISO = format === 'iso-date-time';
8042
- // Determine minDate: prioritize explicit minDate prop, then fall back to startTime
8043
- const effectiveMinDate = minDate
8044
- ? minDate
8045
- : normalizedStartTime && dayjs(normalizedStartTime).tz(timezone).isValid()
8046
- ? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
8047
- : undefined;
8048
- // Log current state before render
8049
- useEffect(() => {
8050
- console.debug('[DateTimePicker] Current state before render:', {
8051
- isISO,
8052
- hour12,
8053
- minute,
8054
- meridiem,
8055
- hour24,
8056
- second,
8057
- selectedDate,
8058
- normalizedStartTime,
8059
- timezone,
8060
- });
8061
- }, [
8062
- isISO,
8063
- hour12,
8064
- minute,
8065
- meridiem,
8066
- hour24,
8067
- second,
8068
- selectedDate,
8069
- normalizedStartTime,
8070
- timezone,
8071
- ]);
8072
- // Compute display text from current state
8073
- const displayText = useMemo(() => {
8074
- if (!selectedDate)
8075
- return null;
8076
- const dateObj = dayjs.tz(selectedDate, timezone);
8077
- if (!dateObj.isValid())
8078
- return null;
8079
- if (isISO) {
8080
- // For ISO format, use hour24, minute, second
8081
- if (hour24 === null || minute === null)
8082
- return null;
8083
- const dateTimeObj = dateObj
8084
- .hour(hour24)
8085
- .minute(minute)
8086
- .second(second ?? 0);
8087
- return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
8088
- }
8089
- else {
8090
- // For 12-hour format, use hour12, minute, meridiem
8091
- if (hour12 === null || minute === null || meridiem === null)
8092
- return null;
8093
- // Convert to 24-hour format for dayjs
8094
- let hour24Value = hour12;
8095
- if (meridiem === 'am' && hour12 === 12)
8096
- hour24Value = 0;
8097
- else if (meridiem === 'pm' && hour12 < 12)
8098
- hour24Value = hour12 + 12;
8099
- const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
8100
- return dateTimeObj.format('YYYY-MM-DD hh:mm A');
8328
+ // Track the current input value for Enter key handling
8329
+ const [timeInputValue, setTimeInputValue] = useState('');
8330
+ const handleTimeInputChange = (details) => {
8331
+ // Store the input value and filter
8332
+ setTimeInputValue(details.inputValue);
8333
+ filter(details.inputValue);
8334
+ };
8335
+ const handleTimeInputKeyDown = (e) => {
8336
+ if (e.key === 'Enter') {
8337
+ e.preventDefault();
8338
+ // Use the stored input value
8339
+ const parsed = parseCustomTimeInput(timeInputValue);
8340
+ if (parsed.hour !== null && parsed.minute !== null) {
8341
+ if (is24Hour) {
8342
+ handleTimeChange(parsed.hour, parsed.minute, parsed.second, null);
8343
+ }
8344
+ else {
8345
+ if (parsed.meridiem !== null) {
8346
+ handleTimeChange(parsed.hour, parsed.minute, null, parsed.meridiem);
8347
+ }
8348
+ }
8349
+ // Clear the filter and input value after applying
8350
+ filter('');
8351
+ setTimeInputValue('');
8352
+ // Close the popover if value is valid
8353
+ setTimePopoverOpen(false);
8354
+ }
8101
8355
  }
8102
- }, [
8103
- selectedDate,
8104
- isISO,
8105
- hour12,
8106
- minute,
8107
- meridiem,
8108
- hour24,
8109
- second,
8110
- showSeconds,
8111
- timezone,
8112
- ]);
8113
- const timezoneOffset = useMemo(() => {
8114
- if (!selectedDate)
8356
+ };
8357
+ // Calendar rendering
8358
+ const renderCalendar = () => {
8359
+ const { calendars, getBackProps, getForwardProps, getDateProps } = calendarProps;
8360
+ if (calendars.length === 0)
8115
8361
  return null;
8116
- const dateObj = dayjs.tz(selectedDate, timezone);
8117
- return dateObj.isValid() ? dateObj.format('Z') : null;
8118
- }, [selectedDate, timezone]);
8119
- return (jsxs(Flex, { direction: "column", gap: 2, children: [jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
8120
- if (date) {
8121
- handleDateChange(date);
8122
- }
8123
- else {
8124
- setSelectedDate('');
8125
- onChange?.(undefined);
8126
- }
8127
- }, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly: false }), jsxs(Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 2, children: [isISO ? (jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: showSeconds ? second : null, setSecond: showSeconds ? setSecond : () => { }, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })) : (jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled, labels: timePickerLabels })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsx(Icon, { as: FaTrash }) })] }), displayText && (jsxs(Flex, { gap: 2, children: [jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: displayText }), timezoneOffset && (jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezoneOffset })), jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
8362
+ const calendar = calendars[0];
8363
+ return (jsxs(Grid, { gap: 4, children: [jsxs(Grid, { templateColumns: 'repeat(4, auto)', justifyContent: 'center', children: [jsx(Button$1, { variant: 'ghost', ...getBackProps({ offset: 12 }), children: '<<' }), jsx(Button$1, { variant: 'ghost', ...getBackProps(), children: backButtonLabel }), jsx(Button$1, { variant: 'ghost', ...getForwardProps(), children: forwardButtonLabel }), jsx(Button$1, { variant: 'ghost', ...getForwardProps({ offset: 12 }), children: '>>' })] }), jsx(Grid, { justifyContent: 'center', children: jsxs(Text, { children: [monthNamesShort[calendar.month], " ", calendar.year] }) }), jsx(Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: [0, 1, 2, 3, 4, 5, 6].map((weekdayNum) => {
8364
+ return (jsx(Text, { textAlign: 'center', fontWeight: "semibold", minW: "40px", children: weekdayNamesShort[weekdayNum] }, `header-${weekdayNum}`));
8365
+ }) }), calendar.weeks.map((week, weekIndex) => (jsx(Grid, { templateColumns: 'repeat(7, auto)', justifyContent: 'center', children: week.map((dateObj, dayIndex) => {
8366
+ if (!dateObj) {
8367
+ return (jsx("div", { style: { minWidth: '40px' } }, `empty-${dayIndex}`));
8368
+ }
8369
+ const { date, selected, selectable, isCurrentMonth } = dateObj;
8370
+ const dateProps = getDateProps({
8371
+ dateObj,
8372
+ });
8373
+ return (jsx(Button$1, { 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()}`));
8374
+ }) }, `week-${weekIndex}`)))] }));
8375
+ };
8376
+ return (jsxs(Flex, { direction: "row", gap: 2, align: "center", children: [jsxs(Popover.Root, { open: datePopoverOpen, onOpenChange: (e) => setDatePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button$1, { size: "sm", variant: "outline", onClick: () => setDatePopoverOpen(true), justifyContent: "start", children: [jsx(MdDateRange, {}), dateDisplayText] }) }), portalled ? (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsx(Popover.Body, { p: 4, children: jsxs(Grid, { gap: 4, children: [jsx(InputGroup$1, { endElement: jsxs(Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsx(Button$1, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsx(MdDateRange, {}) }) }), jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsx(Popover.Body, { p: 4, children: renderCalendar() }) }) })] }), children: jsx(Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxs(Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }) })) : (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", children: jsx(Popover.Body, { p: 4, children: jsxs(Grid, { gap: 4, children: [jsx(InputGroup$1, { endElement: jsxs(Popover.Root, { open: calendarPopoverOpen, onOpenChange: (e) => setCalendarPopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsx(Button$1, { variant: "ghost", size: "xs", "aria-label": "Open calendar", onClick: () => setCalendarPopoverOpen(true), children: jsx(MdDateRange, {}) }) }), jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "350px", minH: "25rem", children: jsx(Popover.Body, { p: 4, children: renderCalendar() }) }) })] }), children: jsx(Input, { value: dateInputValue, onChange: handleDateInputChange, onBlur: handleDateInputBlur, onKeyDown: handleDateInputKeyDown, placeholder: "YYYY-MM-DD" }) }), showQuickActions && (jsxs(Grid, { templateColumns: "repeat(4, 1fr)", gap: 2, children: [jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getYesterday()), disabled: !isDateValid(getYesterday()), children: quickActionLabels.yesterday }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getToday()), disabled: !isDateValid(getToday()), children: quickActionLabels.today }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getTomorrow()), disabled: !isDateValid(getTomorrow()), children: quickActionLabels.tomorrow }), jsx(Button$1, { size: "sm", variant: "outline", onClick: () => handleQuickActionClick(getPlus7Days()), disabled: !isDateValid(getPlus7Days()), children: quickActionLabels.plus7Days })] }))] }) }) }) }))] }), jsxs(Popover.Root, { open: timePopoverOpen, onOpenChange: (e) => setTimePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button$1, { size: "sm", variant: "outline", onClick: () => setTimePopoverOpen(true), justifyContent: "start", children: [jsx(BsClock, {}), timeDisplayText] }) }), portalled ? (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "300px", children: jsx(Popover.Body, { p: 4, children: jsx(Grid, { gap: 2, children: jsxs(Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
8377
+ (is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: true, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: timePickerLabels?.emptyMessage ??
8378
+ 'No time found' }), collection.items.map((item) => {
8379
+ const option = item;
8380
+ return (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { justify: "space-between", align: "center", w: "100%", children: [jsx(Text, { children: option.label }), option.durationText && (jsx(Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsx(Combobox.ItemIndicator, {})] }, option.value));
8381
+ })] }) }) })] }) }) }) }) }) })) : (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "300px", children: jsx(Popover.Body, { p: 4, children: jsx(Grid, { gap: 2, children: jsxs(Combobox.Root, { value: currentTimeValue ? [currentTimeValue] : [], onValueChange: handleTimeValueChange, onInputValueChange: handleTimeInputChange, collection: collection, allowCustomValue: true, children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: timePickerLabels?.placeholder ??
8382
+ (is24Hour ? 'HH:mm' : 'hh:mm AM/PM'), onKeyDown: handleTimeInputKeyDown }) }), jsx(Combobox.IndicatorGroup, { children: jsx(Combobox.Trigger, {}) })] }), jsx(Portal, { disabled: true, children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: timePickerLabels?.emptyMessage ?? 'No time found' }), collection.items.map((item) => {
8383
+ const option = item;
8384
+ return (jsxs(Combobox.Item, { item: item, children: [jsxs(Flex, { justify: "space-between", align: "center", w: "100%", children: [jsx(Text, { children: option.label }), option.durationText && (jsx(Text, { fontSize: "xs", color: "gray.500", children: option.durationText }))] }), jsx(Combobox.ItemIndicator, {})] }, option.value));
8385
+ })] }) }) })] }) }) }) }) }))] }), showTimezoneSelector && (jsxs(Popover.Root, { open: timezonePopoverOpen, onOpenChange: (e) => setTimezonePopoverOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsx(Button$1, { size: "sm", variant: "outline", onClick: () => setTimezonePopoverOpen(true), justifyContent: "start", children: timezoneDisplayText || 'Select timezone' }) }), portalled ? (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "250px", children: jsx(Popover.Body, { p: 4, children: jsx(Grid, { gap: 2, children: jsxs(Select.Root, { size: "sm", collection: timezoneCollection, value: timezoneOffset ? [timezoneOffset] : [], onValueChange: (e) => {
8386
+ const newOffset = e.value[0];
8387
+ if (newOffset) {
8388
+ setTimezoneOffset(newOffset);
8389
+ // Update date-time with new offset
8390
+ if (selectedDate &&
8391
+ hour !== null &&
8392
+ minute !== null) {
8393
+ updateDateTime(selectedDate, hour, minute, second, meridiem);
8394
+ }
8395
+ // Close popover after selection
8396
+ setTimezonePopoverOpen(false);
8397
+ }
8398
+ }, children: [jsxs(Select.Control, { children: [jsx(Select.Trigger, {}), jsx(Select.IndicatorGroup, { children: jsx(Select.Indicator, {}) })] }), jsx(Select.Positioner, { children: jsx(Select.Content, { children: timezoneCollection.items.map((item) => (jsxs(Select.Item, { item: item, children: [jsx(Select.ItemText, { children: item.label }), jsx(Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }) })) : (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "250px", children: jsx(Popover.Body, { p: 4, children: jsx(Grid, { gap: 2, children: jsxs(Select.Root, { size: "sm", collection: timezoneCollection, value: timezoneOffset ? [timezoneOffset] : [], onValueChange: (e) => {
8399
+ const newOffset = e.value[0];
8400
+ if (newOffset) {
8401
+ setTimezoneOffset(newOffset);
8402
+ // Update date-time with new offset (pass it directly to avoid stale state)
8403
+ if (selectedDate &&
8404
+ hour !== null &&
8405
+ minute !== null) {
8406
+ updateDateTime(selectedDate, hour, minute, second, meridiem, newOffset);
8407
+ }
8408
+ // Close popover after selection
8409
+ setTimezonePopoverOpen(false);
8410
+ }
8411
+ }, children: [jsxs(Select.Control, { children: [jsx(Select.Trigger, {}), jsx(Select.IndicatorGroup, { children: jsx(Select.Indicator, {}) })] }), jsx(Select.Positioner, { children: jsx(Select.Content, { children: timezoneCollection.items.map((item) => (jsxs(Select.Item, { item: item, children: [jsx(Select.ItemText, { children: item.label }), jsx(Select.ItemIndicator, {})] }, item.value))) }) })] }) }) }) }) }))] }))] }));
8128
8412
  }
8129
8413
 
8130
8414
  dayjs.extend(utc);
@@ -8138,11 +8422,12 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
8138
8422
  dateFormat = 'YYYY-MM-DD[T]HH:mm:ssZ', } = schema;
8139
8423
  const isRequired = required?.some((columnId) => columnId === column);
8140
8424
  const colLabel = formI18n.colLabel;
8141
- const [open, setOpen] = useState(false);
8425
+ useState(false);
8142
8426
  const selectedDate = watch(colLabel);
8143
- const displayDate = selectedDate && dayjs(selectedDate).tz(timezone).isValid()
8427
+ selectedDate && dayjs(selectedDate).tz(timezone).isValid()
8144
8428
  ? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
8145
8429
  : '';
8430
+ // Set default date on mount if no value exists
8146
8431
  const dateTimePickerLabelsConfig = {
8147
8432
  monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
8148
8433
  'January',
@@ -8184,9 +8469,7 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
8184
8469
  }
8185
8470
  }, timezone: timezone, labels: dateTimePickerLabelsConfig, timePickerLabels: timePickerLabels }));
8186
8471
  return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
8187
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(Popover.Trigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
8188
- setOpen(true);
8189
- }, justifyContent: 'start', children: [jsx(MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "350px", minH: "10rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
8472
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: dateTimePickerContent }));
8190
8473
  };
8191
8474
 
8192
8475
  const SchemaRenderer = ({ schema, prefix, column, }) => {