@bsol-oss/react-datatable5 12.0.0-beta.90 → 12.0.0-beta.91

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,9 +1,9 @@
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, Tag as Tag$1, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, Skeleton, NumberInput, Show, RadioCard, CheckboxGroup, InputGroup as InputGroup$1, Center, Heading } 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, Skeleton, NumberInput, Show, RadioCard, CheckboxGroup, InputGroup as InputGroup$1, Center, Heading } from '@chakra-ui/react';
3
3
  import { AiOutlineColumnWidth } from 'react-icons/ai';
4
4
  import * as React from 'react';
5
- import React__default, { createContext, useContext, useState, useEffect, useRef, useMemo, forwardRef } from 'react';
6
- import { LuX, LuCheck, LuChevronRight, LuSearch, LuImage, LuFile } from 'react-icons/lu';
5
+ import React__default, { createContext, useContext, useState, useEffect, useRef, useMemo, forwardRef, useCallback } from 'react';
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
8
  import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
9
9
  import { BiDownArrow, BiUpArrow, BiError } from 'react-icons/bi';
@@ -33,6 +33,7 @@ import dayjs from 'dayjs';
33
33
  import utc from 'dayjs/plugin/utc';
34
34
  import timezone from 'dayjs/plugin/timezone';
35
35
  import { TiDeleteOutline } from 'react-icons/ti';
36
+ import customParseFormat from 'dayjs/plugin/customParseFormat';
36
37
  import { rankItem } from '@tanstack/match-sorter-utils';
37
38
 
38
39
  const DataTableContext = createContext({
@@ -3315,11 +3316,136 @@ const TableLoadingComponent = ({ render, }) => {
3315
3316
  return jsx(Fragment, { children: render(query.isLoading) });
3316
3317
  };
3317
3318
 
3318
- const TextCell = ({ label, containerProps = {}, textProps = {}, children, }) => {
3319
- if (label) {
3320
- return (jsx(Flex, { alignItems: "center", height: "100%", ...containerProps, children: jsx(Tooltip, { content: jsx(Text, { as: "span", overflow: "hidden", textOverflow: "ellipsis", children: label }), children: jsx(Text, { as: "span", overflow: "hidden", textOverflow: "ellipsis", wordBreak: "break-all", ...textProps, children: children }) }) }));
3319
+ // Helper function to highlight matching text
3320
+ const highlightText$1 = (text, searchTerm) => {
3321
+ if (!searchTerm || searchTerm.trim() === '') {
3322
+ return String(text);
3323
+ }
3324
+ const textStr = String(text);
3325
+ const searchLower = searchTerm.toLowerCase();
3326
+ const textLower = textStr.toLowerCase();
3327
+ const parts = [];
3328
+ let lastIndex = 0;
3329
+ let index = textLower.indexOf(searchLower, lastIndex);
3330
+ while (index !== -1) {
3331
+ // Add text before match
3332
+ if (index > lastIndex) {
3333
+ parts.push(textStr.substring(lastIndex, index));
3334
+ }
3335
+ // Add highlighted match
3336
+ parts.push(jsx(Text, { as: "mark", bg: {
3337
+ base: 'yellow.200',
3338
+ _dark: 'yellow.800',
3339
+ }, color: {
3340
+ base: 'gray.900',
3341
+ _dark: 'gray.100',
3342
+ }, px: 0.5, borderRadius: "sm", children: textStr.substring(index, index + searchTerm.length) }, index));
3343
+ lastIndex = index + searchTerm.length;
3344
+ index = textLower.indexOf(searchLower, lastIndex);
3345
+ }
3346
+ // Add remaining text
3347
+ if (lastIndex < textStr.length) {
3348
+ parts.push(textStr.substring(lastIndex));
3349
+ }
3350
+ return parts.length > 0 ? jsx(Fragment, { children: parts }) : textStr;
3351
+ };
3352
+ const TextWithCopy = ({ text, globalFilter, highlightedText, }) => {
3353
+ const textValue = String(text ?? '');
3354
+ const displayText = highlightedText !== undefined
3355
+ ? highlightedText
3356
+ : highlightText$1(textValue, globalFilter);
3357
+ return (jsxs(HStack, { gap: 2, alignItems: "center", children: [jsx(Text, { as: "span", children: displayText }), jsx(Clipboard.Root, { value: textValue, children: jsx(Clipboard.Trigger, { asChild: true, children: jsx(IconButton, { size: "xs", variant: "ghost", "aria-label": "Copy", children: jsx(Clipboard.Indicator, { copied: jsx(LuCheck, {}), children: jsx(LuCopy, {}) }) }) }) })] }));
3358
+ };
3359
+
3360
+ // Helper function to highlight matching text
3361
+ const highlightText = (text, searchTerm) => {
3362
+ if (!searchTerm || searchTerm.trim() === '') {
3363
+ return String(text);
3364
+ }
3365
+ const textStr = String(text);
3366
+ const searchLower = searchTerm.toLowerCase();
3367
+ const textLower = textStr.toLowerCase();
3368
+ const parts = [];
3369
+ let lastIndex = 0;
3370
+ let index = textLower.indexOf(searchLower, lastIndex);
3371
+ while (index !== -1) {
3372
+ // Add text before match
3373
+ if (index > lastIndex) {
3374
+ parts.push(textStr.substring(lastIndex, index));
3375
+ }
3376
+ // Add highlighted match
3377
+ parts.push(jsx(Text, { as: "mark", bg: {
3378
+ base: 'yellow.200',
3379
+ _dark: 'yellow.800',
3380
+ }, color: {
3381
+ base: 'gray.900',
3382
+ _dark: 'gray.100',
3383
+ }, px: 0.5, borderRadius: "sm", children: textStr.substring(index, index + searchTerm.length) }, index));
3384
+ lastIndex = index + searchTerm.length;
3385
+ index = textLower.indexOf(searchLower, lastIndex);
3386
+ }
3387
+ // Add remaining text
3388
+ if (lastIndex < textStr.length) {
3389
+ parts.push(textStr.substring(lastIndex));
3390
+ }
3391
+ return parts.length > 0 ? jsx(Fragment, { children: parts }) : textStr;
3392
+ };
3393
+ const RenderValue = ({ text, href, onClick, isCopyable, isBadge, badgeColor, colorPalette, globalFilter, }) => {
3394
+ const highlightedText = useMemo(() => highlightText(text ?? '', globalFilter), [text, globalFilter]);
3395
+ if (isBadge) {
3396
+ return (jsx(Badge, { colorPalette: colorPalette || badgeColor, children: highlightedText }));
3397
+ }
3398
+ // onClick takes precedence over href
3399
+ if (onClick) {
3400
+ return (jsx(Box, { as: "button", onClick: onClick, cursor: "pointer", textAlign: "left", _hover: {
3401
+ textDecoration: 'underline',
3402
+ color: {
3403
+ base: 'blue.500',
3404
+ _dark: 'blue.400',
3405
+ },
3406
+ }, transition: "all 0.2s", children: highlightedText }));
3321
3407
  }
3322
- return (jsx(Flex, { alignItems: "center", height: "100%", ...containerProps, children: jsx(Text, { as: "span", overflow: "hidden", textOverflow: "ellipsis", wordBreak: "break-all", ...textProps, children: children }) }));
3408
+ if (href) {
3409
+ return (jsxs(Link, { href: href, target: "_blank", rel: "noopener noreferrer", _hover: {
3410
+ textDecoration: 'underline',
3411
+ }, children: [highlightedText, " ", jsx(Icon, { as: LuExternalLink })] }));
3412
+ }
3413
+ if (isCopyable) {
3414
+ return (jsx(TextWithCopy, { text: text, globalFilter: globalFilter, highlightedText: highlightedText }));
3415
+ }
3416
+ return jsx(Fragment, { children: highlightedText });
3417
+ };
3418
+ const TextCell = ({ text, href, onClick, isCopyable, isBadge, badgeColor, colorPalette,
3419
+ // Legacy props
3420
+ label, containerProps = {}, textProps = {}, children, }) => {
3421
+ // Get globalFilter from context
3422
+ // If not in DataTable context, will use default empty string from context
3423
+ const { globalFilter } = useDataTableContext();
3424
+ // Legacy API: if children is provided, use old behavior
3425
+ if (children !== undefined) {
3426
+ const displayText = typeof children === 'string' || typeof children === 'number'
3427
+ ? String(children)
3428
+ : children;
3429
+ const highlightedDisplayText = typeof displayText === 'string' || typeof displayText === 'number'
3430
+ ? highlightText(displayText, globalFilter)
3431
+ : displayText;
3432
+ if (label) {
3433
+ return (jsx(Flex, { alignItems: 'center', height: '100%', ...containerProps, children: jsx(Tooltip, { content: jsx(Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', children: label }), children: jsx(Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', wordBreak: 'break-all', ...textProps, children: highlightedDisplayText }) }) }));
3434
+ }
3435
+ return (jsx(Flex, { alignItems: 'center', height: '100%', ...containerProps, children: jsx(Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', wordBreak: 'break-all', ...textProps, children: highlightedDisplayText }) }));
3436
+ }
3437
+ // New API: use text prop
3438
+ const displayValue = text ?? children;
3439
+ if (Array.isArray(displayValue)) {
3440
+ return (jsx(Flex, { gap: 2, flexWrap: "wrap", children: displayValue.map((item, index) => {
3441
+ const highlightedItem = highlightText(item, globalFilter);
3442
+ return (jsx(Badge, { colorPalette: colorPalette || badgeColor, children: highlightedItem }, index));
3443
+ }) }));
3444
+ }
3445
+ if (!!displayValue === false) {
3446
+ return (jsx(Text, { textOverflow: "ellipsis", whiteSpace: "nowrap", overflow: "hidden", wordBreak: "break-all", display: "flex", alignItems: "center", height: "100%", children: "-" }));
3447
+ }
3448
+ return (jsx(Box, { textOverflow: "ellipsis", whiteSpace: "nowrap", wordBreak: "break-all", overflow: "auto", display: "flex", alignItems: "center", height: "100%", children: jsx(RenderValue, { text: displayValue, href: href, onClick: onClick, isCopyable: isCopyable, isBadge: isBadge, badgeColor: badgeColor, colorPalette: colorPalette, globalFilter: globalFilter }) }));
3323
3449
  };
3324
3450
 
3325
3451
  const Tag = React.forwardRef(function Tag(props, ref) {
@@ -5295,15 +5421,15 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5295
5421
  // Check if we're currently searching (user typed but debounce hasn't fired yet)
5296
5422
  const isSearching = searchText !== debouncedSearchText;
5297
5423
  // Transform data for combobox collection
5424
+ // label is used for filtering/searching (must be a string)
5425
+ // raw item is stored for custom rendering
5298
5426
  const comboboxItems = useMemo(() => {
5299
5427
  return dataList.map((item) => ({
5300
- label: !!renderDisplay === true
5301
- ? String(renderDisplay(item))
5302
- : String(item[display_column] ?? ''),
5428
+ label: String(item[display_column] ?? ''), // Always use display_column for filtering
5303
5429
  value: String(item[column_ref]),
5304
5430
  raw: item,
5305
5431
  }));
5306
- }, [dataList, display_column, column_ref, renderDisplay]);
5432
+ }, [dataList, display_column, column_ref]);
5307
5433
  // Use filter hook for combobox
5308
5434
  const { contains } = useFilter({ sensitivity: 'base' });
5309
5435
  // Create collection for combobox
@@ -5362,13 +5488,17 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5362
5488
  ? idPickerLabels?.emptySearchResult ??
5363
5489
  formI18n.t('empty_search_result')
5364
5490
  : idPickerLabels?.initialResults ??
5365
- formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: formI18n.t('loading_failed') })) : isFetching || isLoading || isPending || isSearching ? (
5491
+ formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplay === true
5492
+ ? renderDisplay(item.raw)
5493
+ : item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: formI18n.t('loading_failed') })) : isFetching || isLoading || isPending || isSearching ? (
5366
5494
  // Show skeleton items to prevent UI shift
5367
5495
  jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
5368
5496
  ? idPickerLabels?.emptySearchResult ??
5369
5497
  formI18n.t('empty_search_result')
5370
5498
  : idPickerLabels?.initialResults ??
5371
- formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
5499
+ formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: !!renderDisplay === true
5500
+ ? renderDisplay(item.raw)
5501
+ : item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
5372
5502
  };
5373
5503
 
5374
5504
  const NumberInputRoot = React.forwardRef(function NumberInput$1(props, ref) {
@@ -5747,7 +5877,7 @@ meridiemLabel: _meridiemLabel = {
5747
5877
  pm: 'pm',
5748
5878
  },
5749
5879
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
5750
- onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedDate, }) {
5880
+ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedDate, portalled = true, }) {
5751
5881
  // Generate time options (every 15 minutes)
5752
5882
  const timeOptions = useMemo(() => {
5753
5883
  const options = [];
@@ -5770,12 +5900,10 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5770
5900
  for (let h = 1; h <= 12; h++) {
5771
5901
  for (let m = 0; m < 60; m += 15) {
5772
5902
  const hour24 = mer === 'am' ? (h === 12 ? 0 : h) : h === 12 ? 12 : h + 12;
5773
- const timeStr = dayjs()
5774
- .tz(timezone)
5775
- .hour(hour24)
5776
- .minute(m)
5777
- .format('HH:mmZ');
5778
- const displayTime = dayjs(`1970-01-01T${timeStr}`, 'HH:mmZ').format('hh:mm a');
5903
+ // Format time directly without using dayjs with dummy dates
5904
+ const formattedHour = h.toString().padStart(2, '0');
5905
+ const formattedMinute = m.toString().padStart(2, '0');
5906
+ const displayTime = `${formattedHour}:${formattedMinute} ${mer}`;
5779
5907
  // Filter out times that would result in negative duration (only when dates are the same)
5780
5908
  if (startDateTime && selectedDate && shouldFilterByDate) {
5781
5909
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
@@ -5788,8 +5916,8 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5788
5916
  continue; // Skip this option as it would result in negative duration
5789
5917
  }
5790
5918
  }
5791
- // Calculate and append duration if startTime is provided
5792
- let label = displayTime;
5919
+ // Calculate duration if startTime is provided
5920
+ let durationText;
5793
5921
  if (startDateTime && selectedDate) {
5794
5922
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
5795
5923
  const optionDateTime = selectedDateObj
@@ -5802,21 +5930,30 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5802
5930
  const diffMs = optionDateTime.diff(startDateTime);
5803
5931
  const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
5804
5932
  const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
5805
- if (diffHours > 0 || diffMinutes > 0) {
5806
- const diffText = diffHours > 0
5807
- ? `${diffHours}h ${diffMinutes}m`
5808
- : `${diffMinutes}m`;
5809
- label = `${displayTime} (+${diffText})`;
5933
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
5934
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
5935
+ let diffText = '';
5936
+ if (diffHours > 0) {
5937
+ diffText = `${diffHours}h ${diffMinutes}m`;
5938
+ }
5939
+ else if (diffMinutes > 0) {
5940
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
5941
+ }
5942
+ else {
5943
+ diffText = `${diffSeconds}s`;
5944
+ }
5945
+ durationText = `+${diffText}`;
5810
5946
  }
5811
5947
  }
5812
5948
  }
5813
5949
  options.push({
5814
- label,
5950
+ label: displayTime,
5815
5951
  value: `${h}:${m.toString().padStart(2, '0')}:${mer}`,
5816
5952
  hour: h,
5817
5953
  minute: m,
5818
5954
  meridiem: mer,
5819
5955
  searchText: displayTime, // Use base time without duration for searching
5956
+ durationText,
5820
5957
  });
5821
5958
  }
5822
5959
  }
@@ -5830,22 +5967,6 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5830
5967
  itemToValue: (item) => item.value,
5831
5968
  filter: contains,
5832
5969
  });
5833
- // Track input mode vs display mode
5834
- const [isInputMode, setIsInputMode] = useState(false);
5835
- const [inputValue, setInputValue] = useState('');
5836
- const inputRef = useRef(null);
5837
- // Switch to display mode when value is selected
5838
- useEffect(() => {
5839
- if (hour !== null && minute !== null && meridiem !== null) {
5840
- setIsInputMode(false);
5841
- }
5842
- }, [hour, minute, meridiem]);
5843
- // Focus input when switching to input mode
5844
- useEffect(() => {
5845
- if (isInputMode && inputRef.current) {
5846
- inputRef.current.focus();
5847
- }
5848
- }, [isInputMode]);
5849
5970
  // Get current value string for combobox
5850
5971
  const currentValue = useMemo(() => {
5851
5972
  if (hour === null || minute === null || meridiem === null) {
@@ -5853,14 +5974,14 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5853
5974
  }
5854
5975
  return `${hour}:${minute.toString().padStart(2, '0')}:${meridiem}`;
5855
5976
  }, [hour, minute, meridiem]);
5856
- // INPUT MODE: Show raw input text (no duration)
5857
- const inputModeText = useMemo(() => {
5858
- return inputValue;
5859
- }, [inputValue]);
5860
- // DISPLAY MODE: Show selected value with duration
5861
- const displayModeText = useMemo(() => {
5862
- if (hour === null || minute === null || meridiem === null) {
5863
- return '';
5977
+ // Calculate duration difference
5978
+ const durationDiff = useMemo(() => {
5979
+ if (!startTime ||
5980
+ !selectedDate ||
5981
+ hour === null ||
5982
+ minute === null ||
5983
+ meridiem === null) {
5984
+ return null;
5864
5985
  }
5865
5986
  const hour24 = meridiem === 'am'
5866
5987
  ? hour === 12
@@ -5869,45 +5990,42 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5869
5990
  : hour === 12
5870
5991
  ? 12
5871
5992
  : hour + 12;
5872
- const timeStr = dayjs()
5873
- .tz(timezone)
5993
+ const startDateObj = dayjs(startTime).tz(timezone);
5994
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
5995
+ const currentDateTime = selectedDateObj
5874
5996
  .hour(hour24)
5875
5997
  .minute(minute)
5876
- .format('HH:mmZ');
5877
- const timeDisplay = dayjs(`1970-01-01T${timeStr}`, 'HH:mmZ').format('hh:mm a');
5878
- // Add duration if startTime is provided
5879
- if (startTime && selectedDate) {
5880
- const startDateObj = dayjs(startTime).tz(timezone);
5881
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
5882
- const currentDateTime = selectedDateObj
5883
- .hour(hour24)
5884
- .minute(minute)
5885
- .second(0)
5886
- .millisecond(0);
5887
- if (startDateObj.isValid() && currentDateTime.isValid()) {
5888
- const diffMs = currentDateTime.diff(startDateObj);
5889
- if (diffMs >= 0) {
5890
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
5891
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
5892
- if (diffHours > 0 || diffMinutes > 0) {
5893
- const diffText = diffHours > 0
5894
- ? `${diffHours}h ${diffMinutes}m`
5895
- : `${diffMinutes}m`;
5896
- return `${timeDisplay} (+${diffText})`;
5897
- }
5898
- }
5998
+ .second(0)
5999
+ .millisecond(0);
6000
+ if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6001
+ return null;
6002
+ }
6003
+ const diffMs = currentDateTime.diff(startDateObj);
6004
+ if (diffMs < 0) {
6005
+ return null;
6006
+ }
6007
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6008
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6009
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6010
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6011
+ let diffText = '';
6012
+ if (diffHours > 0) {
6013
+ diffText = `${diffHours}h ${diffMinutes}m`;
6014
+ }
6015
+ else if (diffMinutes > 0) {
6016
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
6017
+ }
6018
+ else {
6019
+ diffText = `${diffSeconds}s`;
5899
6020
  }
6021
+ return `+${diffText}`;
5900
6022
  }
5901
- return timeDisplay;
5902
- }, [hour, minute, meridiem, timezone, startTime, selectedDate]);
5903
- // Choose text based on mode
5904
- const displayText = isInputMode ? inputModeText : displayModeText;
6023
+ return null;
6024
+ }, [hour, minute, meridiem, startTime, selectedDate, timezone]);
5905
6025
  const handleClear = () => {
5906
6026
  setHour(null);
5907
6027
  setMinute(null);
5908
6028
  setMeridiem(null);
5909
- setIsInputMode(false);
5910
- setInputValue('');
5911
6029
  filter(''); // Reset filter to show all options
5912
6030
  onChange({ hour: null, minute: null, meridiem: null });
5913
6031
  };
@@ -5922,8 +6040,6 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5922
6040
  setHour(selectedOption.hour);
5923
6041
  setMinute(selectedOption.minute);
5924
6042
  setMeridiem(selectedOption.meridiem);
5925
- setIsInputMode(false); // Switch to display mode
5926
- setInputValue('');
5927
6043
  filter(''); // Reset filter after selection
5928
6044
  onChange({
5929
6045
  hour: selectedOption.hour,
@@ -5932,41 +6048,16 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5932
6048
  });
5933
6049
  }
5934
6050
  };
5935
- // Handle Enter key to select first filtered option
5936
- const handleKeyDown = (e) => {
5937
- if (e.key === 'Enter' && collection.items.length > 0) {
5938
- e.preventDefault();
5939
- const firstOption = collection.items[0];
5940
- if (firstOption) {
5941
- const selectedOption = timeOptions.find((opt) => opt.value === firstOption.value);
5942
- if (selectedOption) {
5943
- setHour(selectedOption.hour);
5944
- setMinute(selectedOption.minute);
5945
- setMeridiem(selectedOption.meridiem);
5946
- setIsInputMode(false); // Switch to display mode
5947
- setInputValue('');
5948
- filter('');
5949
- onChange({
5950
- hour: selectedOption.hour,
5951
- minute: selectedOption.minute,
5952
- meridiem: selectedOption.meridiem,
5953
- });
5954
- }
5955
- }
5956
- }
5957
- };
5958
- const handleInputValueChange = (details) => {
5959
- const inputValue = details.inputValue.trim();
5960
- setInputValue(inputValue);
5961
- setIsInputMode(true); // Switch to input mode
6051
+ // Parse input value and update state
6052
+ const parseAndCommitInput = (value) => {
6053
+ const trimmedValue = value.trim();
5962
6054
  // Filter the collection based on input
5963
- filter(inputValue);
5964
- if (!inputValue) {
5965
- setIsInputMode(false);
6055
+ filter(trimmedValue);
6056
+ if (!trimmedValue) {
5966
6057
  return;
5967
6058
  }
5968
6059
  // Try to parse custom input using explicit regex patterns
5969
- const normalized = inputValue.toLowerCase().replace(/\s+/g, '');
6060
+ const normalized = trimmedValue.toLowerCase().replace(/\s+/g, '');
5970
6061
  // Pattern 1: 12-hour format with meridiem (e.g., "930pm", "1230am", "9:30pm", "12:30am")
5971
6062
  // Matches: 1-2 digits hour, optional colon, 2 digits minute, am/pm
5972
6063
  const pattern12HourWithMeridiem = /^(\d{1,2}):?(\d{2})(am|pm)$/;
@@ -5977,29 +6068,43 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5977
6068
  const parsedMeridiem = match12Hour[3];
5978
6069
  // Validate hour (1-12)
5979
6070
  if (parsedHour < 1 || parsedHour > 12) {
6071
+ // Parse failed, select first result
6072
+ selectFirstResult();
5980
6073
  return;
5981
6074
  }
5982
6075
  // Validate minute (0-59)
5983
- const validMinute = parsedMinute > 59 ? 0 : parsedMinute;
6076
+ if (parsedMinute < 0 || parsedMinute > 59) {
6077
+ // Parse failed, select first result
6078
+ selectFirstResult();
6079
+ return;
6080
+ }
5984
6081
  setHour(parsedHour);
5985
- setMinute(validMinute);
6082
+ setMinute(parsedMinute);
5986
6083
  setMeridiem(parsedMeridiem);
5987
6084
  onChange({
5988
6085
  hour: parsedHour,
5989
- minute: validMinute,
6086
+ minute: parsedMinute,
5990
6087
  meridiem: parsedMeridiem,
5991
6088
  });
5992
6089
  return;
5993
6090
  }
5994
6091
  // Pattern 2: 24-hour format (e.g., "2130", "09:30", "21:30")
5995
6092
  // Matches: 1-2 digits hour, optional colon, 2 digits minute
5996
- const pattern24Hour = /^(\d{2}):?(\d{2})$/;
6093
+ const pattern24Hour = /^(\d{1,2}):?(\d{2})$/;
5997
6094
  const match24Hour = normalized.match(pattern24Hour);
5998
6095
  if (match24Hour) {
5999
6096
  let parsedHour = parseInt(match24Hour[1], 10);
6000
6097
  const parsedMinute = parseInt(match24Hour[2], 10);
6001
6098
  // Validate hour (0-23)
6002
- if (parsedHour > 23) {
6099
+ if (parsedHour < 0 || parsedHour > 23) {
6100
+ // Parse failed, select first result
6101
+ selectFirstResult();
6102
+ return;
6103
+ }
6104
+ // Validate minute (0-59)
6105
+ if (parsedMinute < 0 || parsedMinute > 59) {
6106
+ // Parse failed, select first result
6107
+ selectFirstResult();
6003
6108
  return;
6004
6109
  }
6005
6110
  // Convert 24-hour to 12-hour format
@@ -6019,23 +6124,62 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
6019
6124
  else {
6020
6125
  parsedMeridiem = 'am';
6021
6126
  }
6022
- // Validate minute (0-59)
6023
- const validMinute = parsedMinute > 59 ? 0 : parsedMinute;
6024
6127
  setHour(parsedHour);
6025
- setMinute(validMinute);
6128
+ setMinute(parsedMinute);
6026
6129
  setMeridiem(parsedMeridiem);
6027
6130
  onChange({
6028
6131
  hour: parsedHour,
6029
- minute: validMinute,
6132
+ minute: parsedMinute,
6030
6133
  meridiem: parsedMeridiem,
6031
6134
  });
6032
6135
  return;
6033
6136
  }
6137
+ // Parse failed, select first result
6138
+ selectFirstResult();
6034
6139
  };
6035
- return (jsxs(Grid, { justifyContent: 'center', alignItems: 'center', templateColumns: '200px auto', gap: "2", width: "auto", minWidth: "250px", children: [jsxs(Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, width: "100%", children: [jsxs(Combobox.Control, { children: [isInputMode ? (jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { ref: inputRef, placeholder: "Select time", value: displayText, onKeyDown: handleKeyDown }) })) : (jsxs(Grid, { templateColumns: "auto 1fr auto", alignItems: "center", gap: 2, width: "100%", minHeight: "40px", px: 3, border: "1px solid", borderColor: "gray.200", borderRadius: "md", cursor: "pointer", onClick: () => setIsInputMode(true), children: [jsx(Icon, { children: jsx(BsClock, {}) }), jsx(Text, { fontSize: "sm", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: displayText || 'Select time' }), jsx(Combobox.Trigger, { onClick: (e) => {
6036
- e.stopPropagation();
6037
- setIsInputMode(true);
6038
- } })] })), isInputMode && (jsxs(Combobox.IndicatorGroup, { children: [jsx(Combobox.ClearTrigger, {}), jsx(Combobox.Trigger, {})] }))] }), jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: "No time found" }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [item.label, jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }));
6140
+ // Select first result from filtered collection
6141
+ const selectFirstResult = () => {
6142
+ if (collection.items.length > 0) {
6143
+ const firstItem = collection.items[0];
6144
+ setHour(firstItem.hour);
6145
+ setMinute(firstItem.minute);
6146
+ setMeridiem(firstItem.meridiem);
6147
+ filter(''); // Reset filter after selection
6148
+ onChange({
6149
+ hour: firstItem.hour,
6150
+ minute: firstItem.minute,
6151
+ meridiem: firstItem.meridiem,
6152
+ });
6153
+ }
6154
+ };
6155
+ const handleInputValueChange = (details) => {
6156
+ // Filter the collection based on input, but don't parse yet
6157
+ filter(details.inputValue);
6158
+ };
6159
+ const handleFocus = (e) => {
6160
+ // Select all text when focusing
6161
+ e.target.select();
6162
+ };
6163
+ const handleBlur = (e) => {
6164
+ // Parse and commit the input value when losing focus
6165
+ const inputValue = e.target.value;
6166
+ if (inputValue) {
6167
+ parseAndCommitInput(inputValue);
6168
+ }
6169
+ };
6170
+ const handleKeyDown = (e) => {
6171
+ // Commit input on Enter key
6172
+ if (e.key === 'Enter') {
6173
+ e.preventDefault();
6174
+ const inputValue = e.currentTarget.value;
6175
+ if (inputValue) {
6176
+ parseAndCommitInput(inputValue);
6177
+ }
6178
+ // Blur the input
6179
+ e.currentTarget?.blur();
6180
+ }
6181
+ };
6182
+ 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: "hh:mm a", 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: "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", colorPalette: "blue", 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, {}) }) })] }) }));
6039
6183
  }
6040
6184
 
6041
6185
  dayjs.extend(timezone);
@@ -6113,11 +6257,138 @@ const TimePicker = ({ column, schema, prefix }) => {
6113
6257
  } }) }) }) }) }))] }) }));
6114
6258
  };
6115
6259
 
6260
+ dayjs.extend(utc);
6261
+ dayjs.extend(timezone);
6262
+ dayjs.extend(customParseFormat);
6263
+ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
6264
+ monthNamesShort: [
6265
+ 'Jan',
6266
+ 'Feb',
6267
+ 'Mar',
6268
+ 'Apr',
6269
+ 'May',
6270
+ 'Jun',
6271
+ 'Jul',
6272
+ 'Aug',
6273
+ 'Sep',
6274
+ 'Oct',
6275
+ 'Nov',
6276
+ 'Dec',
6277
+ ],
6278
+ weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
6279
+ backButtonLabel: 'Back',
6280
+ forwardButtonLabel: 'Next',
6281
+ }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, }) {
6282
+ const [open, setOpen] = useState(false);
6283
+ const [inputValue, setInputValue] = useState('');
6284
+ // Update input value when prop value changes
6285
+ useEffect(() => {
6286
+ if (value) {
6287
+ const formatted = typeof value === 'string'
6288
+ ? dayjs(value).tz(timezone).isValid()
6289
+ ? dayjs(value).tz(timezone).format(displayFormat)
6290
+ : ''
6291
+ : dayjs(value).tz(timezone).format(displayFormat);
6292
+ setInputValue(formatted);
6293
+ }
6294
+ else {
6295
+ setInputValue('');
6296
+ }
6297
+ }, [value, displayFormat, timezone]);
6298
+ // Convert value to Date object for DatePicker
6299
+ const selectedDate = value
6300
+ ? typeof value === 'string'
6301
+ ? dayjs(value).tz(timezone).isValid()
6302
+ ? dayjs(value).tz(timezone).toDate()
6303
+ : new Date()
6304
+ : value
6305
+ : new Date();
6306
+ // Shared function to parse and validate input value
6307
+ const parseAndValidateInput = (inputVal) => {
6308
+ // If empty, clear the value
6309
+ if (!inputVal.trim()) {
6310
+ onChange?.(undefined);
6311
+ setInputValue('');
6312
+ return;
6313
+ }
6314
+ // Try parsing with displayFormat first
6315
+ let parsedDate = dayjs(inputVal, displayFormat, true);
6316
+ // If that fails, try common date formats
6317
+ if (!parsedDate.isValid()) {
6318
+ parsedDate = dayjs(inputVal);
6319
+ }
6320
+ // If still invalid, try parsing with dateFormat
6321
+ if (!parsedDate.isValid()) {
6322
+ parsedDate = dayjs(inputVal, dateFormat, true);
6323
+ }
6324
+ // If valid, check constraints and update
6325
+ if (parsedDate.isValid()) {
6326
+ const dateObj = parsedDate.tz(timezone).toDate();
6327
+ // Check min/max constraints
6328
+ if (minDate && dateObj < minDate) {
6329
+ // Invalid: before minDate, reset to prop value
6330
+ resetToPropValue();
6331
+ return;
6332
+ }
6333
+ if (maxDate && dateObj > maxDate) {
6334
+ // Invalid: after maxDate, reset to prop value
6335
+ resetToPropValue();
6336
+ return;
6337
+ }
6338
+ // Valid date - format and update
6339
+ const formattedDate = parsedDate.tz(timezone).format(dateFormat);
6340
+ const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
6341
+ onChange?.(formattedDate);
6342
+ setInputValue(formattedDisplay);
6343
+ }
6344
+ else {
6345
+ // Invalid date - reset to prop value
6346
+ resetToPropValue();
6347
+ }
6348
+ };
6349
+ // Helper function to reset input to prop value
6350
+ const resetToPropValue = () => {
6351
+ if (value) {
6352
+ const formatted = typeof value === 'string'
6353
+ ? dayjs(value).tz(timezone).isValid()
6354
+ ? dayjs(value).tz(timezone).format(displayFormat)
6355
+ : ''
6356
+ : dayjs(value).tz(timezone).format(displayFormat);
6357
+ setInputValue(formatted);
6358
+ }
6359
+ else {
6360
+ setInputValue('');
6361
+ }
6362
+ };
6363
+ const handleInputChange = (e) => {
6364
+ // Only update the input value, don't parse yet
6365
+ setInputValue(e.target.value);
6366
+ };
6367
+ const handleInputBlur = () => {
6368
+ // Parse and validate when input loses focus
6369
+ parseAndValidateInput(inputValue);
6370
+ };
6371
+ const handleKeyDown = (e) => {
6372
+ // Parse and validate when Enter is pressed
6373
+ if (e.key === 'Enter') {
6374
+ e.preventDefault();
6375
+ parseAndValidateInput(inputValue);
6376
+ }
6377
+ };
6378
+ const handleDateSelected = ({ date }) => {
6379
+ const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
6380
+ onChange?.(formattedDate);
6381
+ setOpen(false);
6382
+ };
6383
+ const datePickerContent = (jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay }));
6384
+ return (jsxs(Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsx(InputGroup, { endElement: jsx(Popover.Trigger, { asChild: true, children: jsx(IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsx(Icon, { children: jsx(MdDateRange, {}) }) }) }), children: jsx(Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minH: "25rem", children: jsx(Popover.Body, { children: datePickerContent }) }) }) }))] }));
6385
+ }
6386
+
6116
6387
  dayjs.extend(utc);
6117
6388
  dayjs.extend(timezone);
6118
6389
  function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
6119
6390
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
6120
- onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', }) {
6391
+ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, }) {
6121
6392
  // Generate time options (every 15 minutes, seconds always 0)
6122
6393
  const timeOptions = useMemo(() => {
6123
6394
  const options = [];
@@ -6152,8 +6423,8 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6152
6423
  continue; // Skip this option as it would result in negative duration
6153
6424
  }
6154
6425
  }
6155
- // Calculate and append duration if startTime is provided
6156
- let label = timeDisplay;
6426
+ // Calculate duration if startTime is provided
6427
+ let durationText;
6157
6428
  if (startDateTime && selectedDate) {
6158
6429
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6159
6430
  const optionDateTime = selectedDateObj
@@ -6178,17 +6449,18 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6178
6449
  else {
6179
6450
  diffText = `${diffSeconds}s`;
6180
6451
  }
6181
- label = `${timeDisplay} (+${diffText})`;
6452
+ durationText = `+${diffText}`;
6182
6453
  }
6183
6454
  }
6184
6455
  }
6185
6456
  options.push({
6186
- label,
6457
+ label: timeDisplay,
6187
6458
  value: `${h}:${m}:0`,
6188
6459
  hour: h,
6189
6460
  minute: m,
6190
6461
  second: 0,
6191
6462
  searchText: timeDisplay, // Use base time without duration for searching
6463
+ durationText,
6192
6464
  });
6193
6465
  }
6194
6466
  }
@@ -6208,46 +6480,46 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6208
6480
  }
6209
6481
  return `${hour}:${minute}:${second}`;
6210
6482
  }, [hour, minute, second]);
6211
- // Get display text for combobox
6212
- const displayText = useMemo(() => {
6213
- if (hour === null || minute === null || second === null) {
6214
- return '';
6483
+ // Calculate duration difference
6484
+ const durationDiff = useMemo(() => {
6485
+ if (!startTime ||
6486
+ !selectedDate ||
6487
+ hour === null ||
6488
+ minute === null ||
6489
+ second === null) {
6490
+ return null;
6215
6491
  }
6216
- const timeDisplay = `${hour.toString().padStart(2, '0')}:${minute
6217
- .toString()
6218
- .padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
6219
- // Show duration difference if startTime is provided
6220
- if (startTime && selectedDate) {
6221
- const startDateObj = dayjs(startTime).tz(timezone);
6222
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6223
- const currentDateTime = selectedDateObj
6224
- .hour(hour)
6225
- .minute(minute)
6226
- .second(second ?? 0)
6227
- .millisecond(0);
6228
- if (startDateObj.isValid() && currentDateTime.isValid()) {
6229
- const diffMs = currentDateTime.diff(startDateObj);
6230
- if (diffMs >= 0) {
6231
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6232
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6233
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6234
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6235
- let diffText = '';
6236
- if (diffHours > 0) {
6237
- diffText = `${diffHours}h ${diffMinutes}m`;
6238
- }
6239
- else if (diffMinutes > 0) {
6240
- diffText = `${diffMinutes}m ${diffSeconds}s`;
6241
- }
6242
- else {
6243
- diffText = `${diffSeconds}s`;
6244
- }
6245
- return `${timeDisplay} (+${diffText})`;
6246
- }
6247
- }
6492
+ const startDateObj = dayjs(startTime).tz(timezone);
6493
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
6494
+ const currentDateTime = selectedDateObj
6495
+ .hour(hour)
6496
+ .minute(minute)
6497
+ .second(second ?? 0)
6498
+ .millisecond(0);
6499
+ if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6500
+ return null;
6501
+ }
6502
+ const diffMs = currentDateTime.diff(startDateObj);
6503
+ if (diffMs < 0) {
6504
+ return null;
6505
+ }
6506
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6507
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6508
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6509
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6510
+ let diffText = '';
6511
+ if (diffHours > 0) {
6512
+ diffText = `${diffHours}h ${diffMinutes}m`;
6513
+ }
6514
+ else if (diffMinutes > 0) {
6515
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
6248
6516
  }
6517
+ else {
6518
+ diffText = `${diffSeconds}s`;
6519
+ }
6520
+ return `+${diffText}`;
6249
6521
  }
6250
- return timeDisplay;
6522
+ return null;
6251
6523
  }, [hour, minute, second, startTime, selectedDate, timezone]);
6252
6524
  const handleClear = () => {
6253
6525
  setHour(null);
@@ -6275,16 +6547,17 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6275
6547
  });
6276
6548
  }
6277
6549
  };
6278
- const handleInputValueChange = (details) => {
6279
- const inputValue = details.inputValue.trim();
6550
+ // Parse input value and update state
6551
+ const parseAndCommitInput = (value) => {
6552
+ const trimmedValue = value.trim();
6280
6553
  // Filter the collection based on input
6281
- filter(inputValue);
6282
- if (!inputValue) {
6554
+ filter(trimmedValue);
6555
+ if (!trimmedValue) {
6283
6556
  return;
6284
6557
  }
6285
6558
  // Parse HH:mm:ss or HH:mm format
6286
6559
  const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
6287
- const match = inputValue.match(timePattern);
6560
+ const match = trimmedValue.match(timePattern);
6288
6561
  if (match) {
6289
6562
  const parsedHour = parseInt(match[1], 10);
6290
6563
  const parsedMinute = parseInt(match[2], 10);
@@ -6304,11 +6577,12 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6304
6577
  minute: parsedMinute,
6305
6578
  second: parsedSecond,
6306
6579
  });
6580
+ return;
6307
6581
  }
6308
6582
  }
6309
6583
  else {
6310
6584
  // Try to parse formats like "123045" (HHmmss) or "1230" (HHmm)
6311
- const numbersOnly = inputValue.replace(/[^0-9]/g, '');
6585
+ const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
6312
6586
  if (numbersOnly.length >= 4) {
6313
6587
  const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
6314
6588
  const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
@@ -6328,11 +6602,56 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6328
6602
  minute: parsedMinute,
6329
6603
  second: parsedSecond,
6330
6604
  });
6605
+ return;
6331
6606
  }
6332
6607
  }
6333
6608
  }
6609
+ // Parse failed, select first result
6610
+ selectFirstResult();
6611
+ };
6612
+ // Select first result from filtered collection
6613
+ const selectFirstResult = () => {
6614
+ if (collection.items.length > 0) {
6615
+ const firstItem = collection.items[0];
6616
+ setHour(firstItem.hour);
6617
+ setMinute(firstItem.minute);
6618
+ setSecond(firstItem.second);
6619
+ filter(''); // Reset filter after selection
6620
+ onChange({
6621
+ hour: firstItem.hour,
6622
+ minute: firstItem.minute,
6623
+ second: firstItem.second,
6624
+ });
6625
+ }
6626
+ };
6627
+ const handleInputValueChange = (details) => {
6628
+ // Filter the collection based on input, but don't parse yet
6629
+ filter(details.inputValue);
6630
+ };
6631
+ const handleFocus = (e) => {
6632
+ // Select all text when focusing
6633
+ e.target.select();
6634
+ };
6635
+ const handleBlur = (e) => {
6636
+ // Parse and commit the input value when losing focus
6637
+ const inputValue = e.target.value;
6638
+ if (inputValue) {
6639
+ parseAndCommitInput(inputValue);
6640
+ }
6641
+ };
6642
+ const handleKeyDown = (e) => {
6643
+ // Commit input on Enter key
6644
+ if (e.key === 'Enter') {
6645
+ e.preventDefault();
6646
+ const inputValue = e.currentTarget.value;
6647
+ if (inputValue) {
6648
+ parseAndCommitInput(inputValue);
6649
+ }
6650
+ // Blur the input
6651
+ e.currentTarget?.blur();
6652
+ }
6334
6653
  };
6335
- return (jsx(Flex, { direction: "column", gap: 3, children: jsxs(Grid, { justifyContent: 'center', alignItems: 'center', templateColumns: '1fr auto', 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, width: "100%", children: [jsxs(Combobox.Control, { children: [jsx(InputGroup$1, { startElement: jsx(BsClock, {}), children: jsx(Combobox.Input, { placeholder: "HH:mm:ss", value: displayText }) }), jsxs(Combobox.IndicatorGroup, { children: [jsx(Combobox.ClearTrigger, {}), jsx(Combobox.Trigger, {})] })] }), jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [jsx(Combobox.Empty, { children: "No time found" }), collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [item.label, jsx(Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "ghost", children: jsx(Icon, { children: jsx(MdCancel, {}) }) })] }) }));
6654
+ 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: "HH:mm:ss", 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: "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, {}) }) })] }) }));
6336
6655
  }
6337
6656
 
6338
6657
  dayjs.extend(utc);
@@ -6355,30 +6674,193 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
6355
6674
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
6356
6675
  backButtonLabel: 'Back',
6357
6676
  forwardButtonLabel: 'Next',
6358
- }, timezone = 'Asia/Hong_Kong', startTime, }) {
6359
- const [selectedDate, setSelectedDate] = useState(value || '');
6677
+ }, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, }) {
6678
+ console.log('[DateTimePicker] Component initialized with props:', {
6679
+ value,
6680
+ format,
6681
+ showSeconds,
6682
+ timezone,
6683
+ startTime,
6684
+ minDate,
6685
+ maxDate,
6686
+ });
6687
+ // Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
6688
+ const getDateString = useCallback((val) => {
6689
+ if (!val)
6690
+ return '';
6691
+ const dateObj = dayjs(val).tz(timezone);
6692
+ return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
6693
+ }, [timezone]);
6694
+ const [selectedDate, setSelectedDate] = useState(getDateString(value));
6695
+ // Helper to get time values from value prop with timezone
6696
+ const getTimeFromValue = useCallback((val) => {
6697
+ console.log('[DateTimePicker] getTimeFromValue called:', {
6698
+ val,
6699
+ timezone,
6700
+ showSeconds,
6701
+ });
6702
+ if (!val) {
6703
+ console.log('[DateTimePicker] No value provided, returning nulls');
6704
+ return {
6705
+ hour12: null,
6706
+ minute: null,
6707
+ meridiem: null,
6708
+ hour24: null,
6709
+ second: null,
6710
+ };
6711
+ }
6712
+ const dateObj = dayjs(val).tz(timezone);
6713
+ console.log('[DateTimePicker] Parsed date object:', {
6714
+ original: val,
6715
+ timezone,
6716
+ isValid: dateObj.isValid(),
6717
+ formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
6718
+ hour24: dateObj.hour(),
6719
+ minute: dateObj.minute(),
6720
+ second: dateObj.second(),
6721
+ });
6722
+ if (!dateObj.isValid()) {
6723
+ console.log('[DateTimePicker] Invalid date object, returning nulls');
6724
+ return {
6725
+ hour12: null,
6726
+ minute: null,
6727
+ meridiem: null,
6728
+ hour24: null,
6729
+ second: null,
6730
+ };
6731
+ }
6732
+ const hour24Value = dateObj.hour();
6733
+ const hour12Value = hour24Value % 12 || 12;
6734
+ const minuteValue = dateObj.minute();
6735
+ const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
6736
+ const secondValue = showSeconds ? dateObj.second() : null;
6737
+ const result = {
6738
+ hour12: hour12Value,
6739
+ minute: minuteValue,
6740
+ meridiem: meridiemValue,
6741
+ hour24: hour24Value,
6742
+ second: secondValue,
6743
+ };
6744
+ console.log('[DateTimePicker] Extracted time values:', result);
6745
+ return result;
6746
+ }, [timezone, showSeconds]);
6747
+ const initialTime = getTimeFromValue(value);
6748
+ console.log('[DateTimePicker] Initial time from value:', {
6749
+ value,
6750
+ initialTime,
6751
+ });
6360
6752
  // Time state for 12-hour format
6361
- const [hour12, setHour12] = useState(value ? dayjs(value).hour() % 12 || 12 : null);
6362
- const [minute, setMinute] = useState(value ? dayjs(value).minute() : null);
6363
- const [meridiem, setMeridiem] = useState(value ? (dayjs(value).hour() >= 12 ? 'pm' : 'am') : null);
6753
+ const [hour12, setHour12] = useState(initialTime.hour12);
6754
+ const [minute, setMinute] = useState(initialTime.minute);
6755
+ const [meridiem, setMeridiem] = useState(initialTime.meridiem);
6364
6756
  // Time state for 24-hour format
6365
- const [hour24, setHour24] = useState(value ? dayjs(value).hour() : null);
6366
- const [second, setSecond] = useState(showSeconds && value ? dayjs(value).second() : null);
6757
+ const [hour24, setHour24] = useState(initialTime.hour24);
6758
+ const [second, setSecond] = useState(initialTime.second);
6759
+ // Sync selectedDate and time states when value prop changes
6760
+ useEffect(() => {
6761
+ console.log('[DateTimePicker] useEffect triggered - value changed:', {
6762
+ value,
6763
+ timezone,
6764
+ format,
6765
+ });
6766
+ // If value is null, undefined, or invalid, clear all fields
6767
+ if (!value || value === null || value === undefined) {
6768
+ console.log('[DateTimePicker] Value is null/undefined, clearing all fields');
6769
+ setSelectedDate('');
6770
+ setHour12(null);
6771
+ setMinute(null);
6772
+ setMeridiem(null);
6773
+ setHour24(null);
6774
+ setSecond(null);
6775
+ return;
6776
+ }
6777
+ // Check if value is valid
6778
+ const dateObj = dayjs(value).tz(timezone);
6779
+ if (!dateObj.isValid()) {
6780
+ console.log('[DateTimePicker] Invalid value, clearing all fields');
6781
+ setSelectedDate('');
6782
+ setHour12(null);
6783
+ setMinute(null);
6784
+ setMeridiem(null);
6785
+ setHour24(null);
6786
+ setSecond(null);
6787
+ return;
6788
+ }
6789
+ const dateString = getDateString(value);
6790
+ console.log('[DateTimePicker] Setting selectedDate:', dateString);
6791
+ setSelectedDate(dateString);
6792
+ const timeData = getTimeFromValue(value);
6793
+ console.log('[DateTimePicker] Updating time states:', {
6794
+ timeData,
6795
+ });
6796
+ setHour12(timeData.hour12);
6797
+ setMinute(timeData.minute);
6798
+ setMeridiem(timeData.meridiem);
6799
+ setHour24(timeData.hour24);
6800
+ setSecond(timeData.second);
6801
+ }, [value, getTimeFromValue, getDateString, timezone]);
6367
6802
  const handleDateChange = (date) => {
6803
+ console.log('[DateTimePicker] handleDateChange called:', {
6804
+ date,
6805
+ timezone,
6806
+ showSeconds,
6807
+ currentTimeStates: { hour12, minute, meridiem, hour24, second },
6808
+ });
6809
+ // If date is empty or invalid, clear all fields
6810
+ if (!date || date === '') {
6811
+ console.log('[DateTimePicker] Empty date, clearing all fields');
6812
+ setSelectedDate('');
6813
+ setHour12(null);
6814
+ setMinute(null);
6815
+ setMeridiem(null);
6816
+ setHour24(null);
6817
+ setSecond(null);
6818
+ onChange?.(undefined);
6819
+ return;
6820
+ }
6368
6821
  setSelectedDate(date);
6822
+ // Parse the date string (YYYY-MM-DD) in the specified timezone
6823
+ const dateObj = dayjs.tz(date, timezone);
6824
+ console.log('[DateTimePicker] Parsed date object:', {
6825
+ date,
6826
+ timezone,
6827
+ isValid: dateObj.isValid(),
6828
+ isoString: dateObj.toISOString(),
6829
+ formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
6830
+ });
6831
+ if (!dateObj.isValid()) {
6832
+ console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
6833
+ setSelectedDate('');
6834
+ setHour12(null);
6835
+ setMinute(null);
6836
+ setMeridiem(null);
6837
+ setHour24(null);
6838
+ setSecond(null);
6839
+ onChange?.(undefined);
6840
+ return;
6841
+ }
6369
6842
  // When showSeconds is false, ignore seconds from the date
6370
- const dateObj = dayjs(date).tz(timezone);
6371
- if (!showSeconds && dateObj.isValid()) {
6843
+ if (!showSeconds) {
6372
6844
  const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
6845
+ console.log('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
6373
6846
  updateDateTime(dateWithoutSeconds);
6374
6847
  }
6375
6848
  else {
6376
- updateDateTime(dateObj.toISOString());
6849
+ const dateWithSeconds = dateObj.toISOString();
6850
+ console.log('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
6851
+ updateDateTime(dateWithSeconds);
6377
6852
  }
6378
6853
  };
6379
6854
  const handleTimeChange = (timeData) => {
6855
+ console.log('[DateTimePicker] handleTimeChange called:', {
6856
+ timeData,
6857
+ format,
6858
+ selectedDate,
6859
+ timezone,
6860
+ });
6380
6861
  if (format === 'iso-date-time') {
6381
6862
  const data = timeData;
6863
+ console.log('[DateTimePicker] ISO format - setting 24-hour time:', data);
6382
6864
  setHour24(data.hour);
6383
6865
  setMinute(data.minute);
6384
6866
  if (showSeconds) {
@@ -6391,60 +6873,161 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
6391
6873
  }
6392
6874
  else {
6393
6875
  const data = timeData;
6876
+ console.log('[DateTimePicker] 12-hour format - setting time:', data);
6394
6877
  setHour12(data.hour);
6395
6878
  setMinute(data.minute);
6396
6879
  setMeridiem(data.meridiem);
6397
6880
  }
6398
- // Use selectedDate if valid, otherwise use today's date as fallback
6399
- const dateToUse = selectedDate && dayjs(selectedDate).isValid()
6400
- ? selectedDate
6401
- : dayjs().tz(timezone).toISOString();
6402
- const dateObj = dayjs(dateToUse).tz(timezone);
6881
+ // Use selectedDate if valid, otherwise clear all fields
6882
+ if (!selectedDate || !dayjs(selectedDate).isValid()) {
6883
+ console.log('[DateTimePicker] No valid selectedDate, clearing all fields');
6884
+ setSelectedDate('');
6885
+ setHour12(null);
6886
+ setMinute(null);
6887
+ setMeridiem(null);
6888
+ setHour24(null);
6889
+ setSecond(null);
6890
+ onChange?.(undefined);
6891
+ return;
6892
+ }
6893
+ const dateObj = dayjs(selectedDate).tz(timezone);
6403
6894
  if (dateObj.isValid()) {
6404
6895
  updateDateTime(dateObj.toISOString(), timeData);
6405
6896
  }
6897
+ else {
6898
+ console.warn('[DateTimePicker] Invalid date object in handleTimeChange, clearing fields');
6899
+ setSelectedDate('');
6900
+ setHour12(null);
6901
+ setMinute(null);
6902
+ setMeridiem(null);
6903
+ setHour24(null);
6904
+ setSecond(null);
6905
+ onChange?.(undefined);
6906
+ }
6406
6907
  };
6407
6908
  const updateDateTime = (date, timeData) => {
6408
- if (!date) {
6909
+ console.log('[DateTimePicker] updateDateTime called:', {
6910
+ date,
6911
+ timeData,
6912
+ format,
6913
+ currentStates: { hour12, minute, meridiem, hour24, second },
6914
+ });
6915
+ if (!date || date === null || date === undefined) {
6916
+ console.log('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
6917
+ setSelectedDate('');
6918
+ setHour12(null);
6919
+ setMinute(null);
6920
+ setMeridiem(null);
6921
+ setHour24(null);
6922
+ setSecond(null);
6409
6923
  onChange?.(undefined);
6410
6924
  return;
6411
6925
  }
6412
6926
  // use dayjs to convert the date to the timezone
6413
6927
  const dateObj = dayjs(date).tz(timezone);
6414
6928
  if (!dateObj.isValid()) {
6929
+ console.warn('[DateTimePicker] Invalid date object in updateDateTime, clearing fields:', date);
6930
+ setSelectedDate('');
6931
+ setHour12(null);
6932
+ setMinute(null);
6933
+ setMeridiem(null);
6934
+ setHour24(null);
6935
+ setSecond(null);
6936
+ onChange?.(undefined);
6415
6937
  return;
6416
6938
  }
6417
6939
  const newDate = dateObj.toDate();
6418
6940
  if (format === 'iso-date-time') {
6419
6941
  const data = timeData;
6420
- const h = data?.hour ?? hour24;
6421
- const m = data?.minute ?? minute;
6942
+ // Use timeData values if provided, otherwise fall back to current state
6943
+ // But if timeData is explicitly provided with nulls, we need to check if all are null
6944
+ const h = data !== undefined ? data.hour : hour24;
6945
+ const m = data !== undefined ? data.minute : minute;
6422
6946
  // Always ignore seconds when showSeconds is false - set to 0
6423
- const s = showSeconds ? data?.second ?? second ?? 0 : 0;
6947
+ const s = showSeconds
6948
+ ? data !== undefined
6949
+ ? data.second ?? null
6950
+ : second ?? 0
6951
+ : 0;
6952
+ // If all time values are null, clear the value
6953
+ if (h === null && m === null && (showSeconds ? s === null : true)) {
6954
+ console.log('[DateTimePicker] All time values are null, clearing value');
6955
+ onChange?.(undefined);
6956
+ return;
6957
+ }
6958
+ console.log('[DateTimePicker] ISO format - setting time on date:', {
6959
+ h,
6960
+ m,
6961
+ s,
6962
+ showSeconds,
6963
+ });
6424
6964
  if (h !== null)
6425
6965
  newDate.setHours(h);
6426
6966
  if (m !== null)
6427
6967
  newDate.setMinutes(m);
6428
- newDate.setSeconds(s);
6968
+ newDate.setSeconds(s ?? 0);
6429
6969
  }
6430
6970
  else {
6431
6971
  const data = timeData;
6432
- const h = data?.hour ?? hour12;
6433
- const m = data?.minute ?? minute;
6434
- const mer = data?.meridiem ?? meridiem;
6972
+ console.log('[DateTimePicker] Processing 12-hour format:', {
6973
+ 'data !== undefined': data !== undefined,
6974
+ 'data?.hour': data?.hour,
6975
+ 'data?.minute': data?.minute,
6976
+ 'data?.meridiem': data?.meridiem,
6977
+ 'current hour12': hour12,
6978
+ 'current minute': minute,
6979
+ 'current meridiem': meridiem,
6980
+ });
6981
+ // Use timeData values if provided, otherwise fall back to current state
6982
+ const h = data !== undefined ? data.hour : hour12;
6983
+ const m = data !== undefined ? data.minute : minute;
6984
+ const mer = data !== undefined ? data.meridiem : meridiem;
6985
+ console.log('[DateTimePicker] Resolved time values:', { h, m, mer });
6986
+ // If all time values are null, clear the value
6987
+ if (h === null && m === null && mer === null) {
6988
+ console.log('[DateTimePicker] All time values are null, clearing value');
6989
+ onChange?.(undefined);
6990
+ return;
6991
+ }
6992
+ console.log('[DateTimePicker] 12-hour format - converting time:', {
6993
+ h,
6994
+ m,
6995
+ mer,
6996
+ });
6435
6997
  if (h !== null && mer !== null) {
6436
6998
  let hour24 = h;
6437
6999
  if (mer === 'am' && h === 12)
6438
7000
  hour24 = 0;
6439
7001
  else if (mer === 'pm' && h < 12)
6440
7002
  hour24 = h + 12;
7003
+ console.log('[DateTimePicker] Converted to 24-hour:', {
7004
+ h,
7005
+ mer,
7006
+ hour24,
7007
+ });
6441
7008
  newDate.setHours(hour24);
6442
7009
  }
6443
- if (m !== null)
7010
+ else {
7011
+ console.log('[DateTimePicker] Skipping hour update - h or mer is null:', {
7012
+ h,
7013
+ mer,
7014
+ });
7015
+ }
7016
+ if (m !== null) {
6444
7017
  newDate.setMinutes(m);
7018
+ }
7019
+ else {
7020
+ console.log('[DateTimePicker] Skipping minute update - m is null');
7021
+ }
6445
7022
  newDate.setSeconds(0);
6446
7023
  }
6447
- onChange?.(dayjs(newDate).tz(timezone).toISOString());
7024
+ const finalISO = dayjs(newDate).tz(timezone).toISOString();
7025
+ console.log('[DateTimePicker] Final ISO string to emit:', {
7026
+ newDate: newDate.toISOString(),
7027
+ timezone,
7028
+ finalISO,
7029
+ });
7030
+ onChange?.(finalISO);
6448
7031
  };
6449
7032
  const handleClear = () => {
6450
7033
  setSelectedDate('');
@@ -6460,14 +7043,92 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
6460
7043
  const normalizedStartTime = startTime
6461
7044
  ? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
6462
7045
  : undefined;
6463
- return (jsxs(Flex, { direction: "column", gap: 4, p: 4, border: "1px solid", borderColor: "gray.200", borderRadius: "md", children: [jsx(DatePicker$1, { selected: selectedDate ? dayjs(selectedDate).tz(timezone).toDate() : new Date(), onDateSelected: ({ date }) => handleDateChange(dayjs(date).tz(timezone).toISOString()), monthsToDisplay: 1, labels: labels, minDate: normalizedStartTime &&
6464
- dayjs(normalizedStartTime).tz(timezone).isValid()
6465
- ? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
6466
- : undefined }), jsxs(Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: showSeconds ? second : null, setSecond: showSeconds ? setSecond : () => { }, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone })) : (jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone })), jsx(Button$1, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsx(Icon, { as: FaTrash }) })] }), selectedDate && (jsxs(Flex, { gap: 2, children: [jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: dayjs(value).format(isISO
6467
- ? showSeconds
6468
- ? 'YYYY-MM-DD HH:mm:ss'
6469
- : 'YYYY-MM-DD HH:mm'
6470
- : 'YYYY-MM-DD hh:mm A ') }), jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: dayjs(value).tz(timezone).format('Z') }), jsx(Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
7046
+ // Determine minDate: prioritize explicit minDate prop, then fall back to startTime
7047
+ const effectiveMinDate = minDate
7048
+ ? minDate
7049
+ : normalizedStartTime && dayjs(normalizedStartTime).tz(timezone).isValid()
7050
+ ? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
7051
+ : undefined;
7052
+ // Log current state before render
7053
+ useEffect(() => {
7054
+ console.log('[DateTimePicker] Current state before render:', {
7055
+ isISO,
7056
+ hour12,
7057
+ minute,
7058
+ meridiem,
7059
+ hour24,
7060
+ second,
7061
+ selectedDate,
7062
+ normalizedStartTime,
7063
+ timezone,
7064
+ });
7065
+ }, [
7066
+ isISO,
7067
+ hour12,
7068
+ minute,
7069
+ meridiem,
7070
+ hour24,
7071
+ second,
7072
+ selectedDate,
7073
+ normalizedStartTime,
7074
+ timezone,
7075
+ ]);
7076
+ // Compute display text from current state
7077
+ const displayText = useMemo(() => {
7078
+ if (!selectedDate)
7079
+ return null;
7080
+ const dateObj = dayjs.tz(selectedDate, timezone);
7081
+ if (!dateObj.isValid())
7082
+ return null;
7083
+ if (isISO) {
7084
+ // For ISO format, use hour24, minute, second
7085
+ if (hour24 === null || minute === null)
7086
+ return null;
7087
+ const dateTimeObj = dateObj
7088
+ .hour(hour24)
7089
+ .minute(minute)
7090
+ .second(second ?? 0);
7091
+ return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
7092
+ }
7093
+ else {
7094
+ // For 12-hour format, use hour12, minute, meridiem
7095
+ if (hour12 === null || minute === null || meridiem === null)
7096
+ return null;
7097
+ // Convert to 24-hour format for dayjs
7098
+ let hour24Value = hour12;
7099
+ if (meridiem === 'am' && hour12 === 12)
7100
+ hour24Value = 0;
7101
+ else if (meridiem === 'pm' && hour12 < 12)
7102
+ hour24Value = hour12 + 12;
7103
+ const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
7104
+ return dateTimeObj.format('YYYY-MM-DD hh:mm A');
7105
+ }
7106
+ }, [
7107
+ selectedDate,
7108
+ isISO,
7109
+ hour12,
7110
+ minute,
7111
+ meridiem,
7112
+ hour24,
7113
+ second,
7114
+ showSeconds,
7115
+ timezone,
7116
+ ]);
7117
+ const timezoneOffset = useMemo(() => {
7118
+ if (!selectedDate)
7119
+ return null;
7120
+ const dateObj = dayjs.tz(selectedDate, timezone);
7121
+ return dateObj.isValid() ? dateObj.format('Z') : null;
7122
+ }, [selectedDate, timezone]);
7123
+ return (jsxs(Flex, { direction: "column", gap: 4, p: 4, border: "1px solid", borderColor: "gray.200", borderRadius: "md", children: [jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
7124
+ if (date) {
7125
+ handleDateChange(date);
7126
+ }
7127
+ else {
7128
+ setSelectedDate('');
7129
+ onChange?.(undefined);
7130
+ }
7131
+ }, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly: true }), jsxs(Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: showSeconds ? second : null, setSecond: showSeconds ? setSecond : () => { }, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled })) : (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 })), 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 })] }))] }));
6471
7132
  }
6472
7133
 
6473
7134
  dayjs.extend(utc);
@@ -6483,32 +7144,9 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
6483
7144
  const colLabel = formI18n.colLabel;
6484
7145
  const [open, setOpen] = useState(false);
6485
7146
  const selectedDate = watch(colLabel);
6486
- const displayDate = dayjs(selectedDate)
6487
- .tz(timezone)
6488
- .format(displayDateFormat);
6489
- useEffect(() => {
6490
- try {
6491
- if (selectedDate) {
6492
- // Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
6493
- // For example, parse as UTC:
6494
- const parsedDate = dayjs(selectedDate).tz(timezone);
6495
- if (!parsedDate.isValid())
6496
- return;
6497
- // Format according to dateFormat from schema
6498
- const formatted = parsedDate.format(dateFormat);
6499
- // Update the form value only if different to avoid loops
6500
- if (formatted !== selectedDate) {
6501
- setValue(colLabel, formatted, {
6502
- shouldValidate: true,
6503
- shouldDirty: true,
6504
- });
6505
- }
6506
- }
6507
- }
6508
- catch (e) {
6509
- console.error(e);
6510
- }
6511
- }, [selectedDate, dateFormat, colLabel, setValue]);
7147
+ const displayDate = selectedDate && dayjs(selectedDate).tz(timezone).isValid()
7148
+ ? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
7149
+ : '';
6512
7150
  const dateTimePickerLabelsConfig = {
6513
7151
  monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
6514
7152
  formI18n.translate.t(`common.month_1`, {
@@ -6581,12 +7219,22 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
6581
7219
  }),
6582
7220
  };
6583
7221
  const dateTimePickerContent = (jsx(DateTimePicker$1, { value: selectedDate, onChange: (date) => {
6584
- setValue(colLabel, dayjs(date).tz(timezone).format(dateFormat));
7222
+ if (!date || date === null || date === undefined) {
7223
+ setValue(colLabel, undefined);
7224
+ return;
7225
+ }
7226
+ const dateObj = dayjs(date).tz(timezone);
7227
+ if (dateObj.isValid()) {
7228
+ setValue(colLabel, dateObj.format(dateFormat));
7229
+ }
7230
+ else {
7231
+ setValue(colLabel, undefined);
7232
+ }
6585
7233
  }, timezone: timezone, labels: dateTimePickerLabelsConfig }));
6586
7234
  return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6587
- 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: () => {
7235
+ 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: () => {
6588
7236
  setOpen(true);
6589
- }, justifyContent: 'start', children: [jsx(MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
7237
+ }, justifyContent: 'start', children: [jsx(MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) })) : (jsx(Portal, { children: jsx(Popover.Positioner, { children: jsx(Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsx(Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
6590
7238
  };
6591
7239
 
6592
7240
  const SchemaRenderer = ({ schema, prefix, column, }) => {
@@ -8017,4 +8665,4 @@ function DataTableServer({ columns, enableRowSelection = true, enableMultiRowSel
8017
8665
  }, children: jsx(DataTableServerContext.Provider, { value: { url, query }, children: children }) }));
8018
8666
  }
8019
8667
 
8020
- export { CardHeader, DataDisplay, DataTable, DataTableServer, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, MediaLibraryBrowser, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, SelectAllRowsToggle, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
8668
+ export { CardHeader, DataDisplay, DataTable, DataTableServer, DatePickerInput, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, MediaLibraryBrowser, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, SelectAllRowsToggle, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };