@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.js CHANGED
@@ -34,6 +34,7 @@ var dayjs = require('dayjs');
34
34
  var utc = require('dayjs/plugin/utc');
35
35
  var timezone = require('dayjs/plugin/timezone');
36
36
  var ti = require('react-icons/ti');
37
+ var customParseFormat = require('dayjs/plugin/customParseFormat');
37
38
  var matchSorterUtils = require('@tanstack/match-sorter-utils');
38
39
 
39
40
  function _interopNamespaceDefault(e) {
@@ -3335,11 +3336,136 @@ const TableLoadingComponent = ({ render, }) => {
3335
3336
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: render(query.isLoading) });
3336
3337
  };
3337
3338
 
3338
- const TextCell = ({ label, containerProps = {}, textProps = {}, children, }) => {
3339
- if (label) {
3340
- return (jsxRuntime.jsx(react.Flex, { alignItems: "center", height: "100%", ...containerProps, children: jsxRuntime.jsx(Tooltip, { content: jsxRuntime.jsx(react.Text, { as: "span", overflow: "hidden", textOverflow: "ellipsis", children: label }), children: jsxRuntime.jsx(react.Text, { as: "span", overflow: "hidden", textOverflow: "ellipsis", wordBreak: "break-all", ...textProps, children: children }) }) }));
3339
+ // Helper function to highlight matching text
3340
+ const highlightText$1 = (text, searchTerm) => {
3341
+ if (!searchTerm || searchTerm.trim() === '') {
3342
+ return String(text);
3343
+ }
3344
+ const textStr = String(text);
3345
+ const searchLower = searchTerm.toLowerCase();
3346
+ const textLower = textStr.toLowerCase();
3347
+ const parts = [];
3348
+ let lastIndex = 0;
3349
+ let index = textLower.indexOf(searchLower, lastIndex);
3350
+ while (index !== -1) {
3351
+ // Add text before match
3352
+ if (index > lastIndex) {
3353
+ parts.push(textStr.substring(lastIndex, index));
3354
+ }
3355
+ // Add highlighted match
3356
+ parts.push(jsxRuntime.jsx(react.Text, { as: "mark", bg: {
3357
+ base: 'yellow.200',
3358
+ _dark: 'yellow.800',
3359
+ }, color: {
3360
+ base: 'gray.900',
3361
+ _dark: 'gray.100',
3362
+ }, px: 0.5, borderRadius: "sm", children: textStr.substring(index, index + searchTerm.length) }, index));
3363
+ lastIndex = index + searchTerm.length;
3364
+ index = textLower.indexOf(searchLower, lastIndex);
3365
+ }
3366
+ // Add remaining text
3367
+ if (lastIndex < textStr.length) {
3368
+ parts.push(textStr.substring(lastIndex));
3369
+ }
3370
+ return parts.length > 0 ? jsxRuntime.jsx(jsxRuntime.Fragment, { children: parts }) : textStr;
3371
+ };
3372
+ const TextWithCopy = ({ text, globalFilter, highlightedText, }) => {
3373
+ const textValue = String(text ?? '');
3374
+ const displayText = highlightedText !== undefined
3375
+ ? highlightedText
3376
+ : highlightText$1(textValue, globalFilter);
3377
+ return (jsxRuntime.jsxs(react.HStack, { gap: 2, alignItems: "center", children: [jsxRuntime.jsx(react.Text, { as: "span", children: displayText }), jsxRuntime.jsx(react.Clipboard.Root, { value: textValue, children: jsxRuntime.jsx(react.Clipboard.Trigger, { asChild: true, children: jsxRuntime.jsx(react.IconButton, { size: "xs", variant: "ghost", "aria-label": "Copy", children: jsxRuntime.jsx(react.Clipboard.Indicator, { copied: jsxRuntime.jsx(lu.LuCheck, {}), children: jsxRuntime.jsx(lu.LuCopy, {}) }) }) }) })] }));
3378
+ };
3379
+
3380
+ // Helper function to highlight matching text
3381
+ const highlightText = (text, searchTerm) => {
3382
+ if (!searchTerm || searchTerm.trim() === '') {
3383
+ return String(text);
3384
+ }
3385
+ const textStr = String(text);
3386
+ const searchLower = searchTerm.toLowerCase();
3387
+ const textLower = textStr.toLowerCase();
3388
+ const parts = [];
3389
+ let lastIndex = 0;
3390
+ let index = textLower.indexOf(searchLower, lastIndex);
3391
+ while (index !== -1) {
3392
+ // Add text before match
3393
+ if (index > lastIndex) {
3394
+ parts.push(textStr.substring(lastIndex, index));
3395
+ }
3396
+ // Add highlighted match
3397
+ parts.push(jsxRuntime.jsx(react.Text, { as: "mark", bg: {
3398
+ base: 'yellow.200',
3399
+ _dark: 'yellow.800',
3400
+ }, color: {
3401
+ base: 'gray.900',
3402
+ _dark: 'gray.100',
3403
+ }, px: 0.5, borderRadius: "sm", children: textStr.substring(index, index + searchTerm.length) }, index));
3404
+ lastIndex = index + searchTerm.length;
3405
+ index = textLower.indexOf(searchLower, lastIndex);
3406
+ }
3407
+ // Add remaining text
3408
+ if (lastIndex < textStr.length) {
3409
+ parts.push(textStr.substring(lastIndex));
3410
+ }
3411
+ return parts.length > 0 ? jsxRuntime.jsx(jsxRuntime.Fragment, { children: parts }) : textStr;
3412
+ };
3413
+ const RenderValue = ({ text, href, onClick, isCopyable, isBadge, badgeColor, colorPalette, globalFilter, }) => {
3414
+ const highlightedText = React.useMemo(() => highlightText(text ?? '', globalFilter), [text, globalFilter]);
3415
+ if (isBadge) {
3416
+ return (jsxRuntime.jsx(react.Badge, { colorPalette: colorPalette || badgeColor, children: highlightedText }));
3417
+ }
3418
+ // onClick takes precedence over href
3419
+ if (onClick) {
3420
+ return (jsxRuntime.jsx(react.Box, { as: "button", onClick: onClick, cursor: "pointer", textAlign: "left", _hover: {
3421
+ textDecoration: 'underline',
3422
+ color: {
3423
+ base: 'blue.500',
3424
+ _dark: 'blue.400',
3425
+ },
3426
+ }, transition: "all 0.2s", children: highlightedText }));
3341
3427
  }
3342
- return (jsxRuntime.jsx(react.Flex, { alignItems: "center", height: "100%", ...containerProps, children: jsxRuntime.jsx(react.Text, { as: "span", overflow: "hidden", textOverflow: "ellipsis", wordBreak: "break-all", ...textProps, children: children }) }));
3428
+ if (href) {
3429
+ return (jsxRuntime.jsxs(react.Link, { href: href, target: "_blank", rel: "noopener noreferrer", _hover: {
3430
+ textDecoration: 'underline',
3431
+ }, children: [highlightedText, " ", jsxRuntime.jsx(react.Icon, { as: lu.LuExternalLink })] }));
3432
+ }
3433
+ if (isCopyable) {
3434
+ return (jsxRuntime.jsx(TextWithCopy, { text: text, globalFilter: globalFilter, highlightedText: highlightedText }));
3435
+ }
3436
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: highlightedText });
3437
+ };
3438
+ const TextCell = ({ text, href, onClick, isCopyable, isBadge, badgeColor, colorPalette,
3439
+ // Legacy props
3440
+ label, containerProps = {}, textProps = {}, children, }) => {
3441
+ // Get globalFilter from context
3442
+ // If not in DataTable context, will use default empty string from context
3443
+ const { globalFilter } = useDataTableContext();
3444
+ // Legacy API: if children is provided, use old behavior
3445
+ if (children !== undefined) {
3446
+ const displayText = typeof children === 'string' || typeof children === 'number'
3447
+ ? String(children)
3448
+ : children;
3449
+ const highlightedDisplayText = typeof displayText === 'string' || typeof displayText === 'number'
3450
+ ? highlightText(displayText, globalFilter)
3451
+ : displayText;
3452
+ if (label) {
3453
+ return (jsxRuntime.jsx(react.Flex, { alignItems: 'center', height: '100%', ...containerProps, children: jsxRuntime.jsx(Tooltip, { content: jsxRuntime.jsx(react.Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', children: label }), children: jsxRuntime.jsx(react.Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', wordBreak: 'break-all', ...textProps, children: highlightedDisplayText }) }) }));
3454
+ }
3455
+ return (jsxRuntime.jsx(react.Flex, { alignItems: 'center', height: '100%', ...containerProps, children: jsxRuntime.jsx(react.Text, { as: "span", overflow: "hidden", textOverflow: 'ellipsis', wordBreak: 'break-all', ...textProps, children: highlightedDisplayText }) }));
3456
+ }
3457
+ // New API: use text prop
3458
+ const displayValue = text ?? children;
3459
+ if (Array.isArray(displayValue)) {
3460
+ return (jsxRuntime.jsx(react.Flex, { gap: 2, flexWrap: "wrap", children: displayValue.map((item, index) => {
3461
+ const highlightedItem = highlightText(item, globalFilter);
3462
+ return (jsxRuntime.jsx(react.Badge, { colorPalette: colorPalette || badgeColor, children: highlightedItem }, index));
3463
+ }) }));
3464
+ }
3465
+ if (!!displayValue === false) {
3466
+ return (jsxRuntime.jsx(react.Text, { textOverflow: "ellipsis", whiteSpace: "nowrap", overflow: "hidden", wordBreak: "break-all", display: "flex", alignItems: "center", height: "100%", children: "-" }));
3467
+ }
3468
+ return (jsxRuntime.jsx(react.Box, { textOverflow: "ellipsis", whiteSpace: "nowrap", wordBreak: "break-all", overflow: "auto", display: "flex", alignItems: "center", height: "100%", children: jsxRuntime.jsx(RenderValue, { text: displayValue, href: href, onClick: onClick, isCopyable: isCopyable, isBadge: isBadge, badgeColor: badgeColor, colorPalette: colorPalette, globalFilter: globalFilter }) }));
3343
3469
  };
3344
3470
 
3345
3471
  const Tag = React__namespace.forwardRef(function Tag(props, ref) {
@@ -5315,15 +5441,15 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5315
5441
  // Check if we're currently searching (user typed but debounce hasn't fired yet)
5316
5442
  const isSearching = searchText !== debouncedSearchText;
5317
5443
  // Transform data for combobox collection
5444
+ // label is used for filtering/searching (must be a string)
5445
+ // raw item is stored for custom rendering
5318
5446
  const comboboxItems = React.useMemo(() => {
5319
5447
  return dataList.map((item) => ({
5320
- label: !!renderDisplay === true
5321
- ? String(renderDisplay(item))
5322
- : String(item[display_column] ?? ''),
5448
+ label: String(item[display_column] ?? ''), // Always use display_column for filtering
5323
5449
  value: String(item[column_ref]),
5324
5450
  raw: item,
5325
5451
  }));
5326
- }, [dataList, display_column, column_ref, renderDisplay]);
5452
+ }, [dataList, display_column, column_ref]);
5327
5453
  // Use filter hook for combobox
5328
5454
  const { contains } = react.useFilter({ sensitivity: 'base' });
5329
5455
  // Create collection for combobox
@@ -5382,13 +5508,17 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5382
5508
  ? idPickerLabels?.emptySearchResult ??
5383
5509
  formI18n.t('empty_search_result')
5384
5510
  : idPickerLabels?.initialResults ??
5385
- formI18n.t('initial_results') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: formI18n.t('loading_failed') })) : isFetching || isLoading || isPending || isSearching ? (
5511
+ formI18n.t('initial_results') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplay === true
5512
+ ? renderDisplay(item.raw)
5513
+ : item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: formI18n.t('loading_failed') })) : isFetching || isLoading || isPending || isSearching ? (
5386
5514
  // Show skeleton items to prevent UI shift
5387
5515
  jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
5388
5516
  ? idPickerLabels?.emptySearchResult ??
5389
5517
  formI18n.t('empty_search_result')
5390
5518
  : idPickerLabels?.initialResults ??
5391
- formI18n.t('initial_results') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
5519
+ formI18n.t('initial_results') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: !!renderDisplay === true
5520
+ ? renderDisplay(item.raw)
5521
+ : item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
5392
5522
  };
5393
5523
 
5394
5524
  const NumberInputRoot = React__namespace.forwardRef(function NumberInput(props, ref) {
@@ -5767,7 +5897,7 @@ meridiemLabel: _meridiemLabel = {
5767
5897
  pm: 'pm',
5768
5898
  },
5769
5899
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
5770
- onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedDate, }) {
5900
+ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedDate, portalled = true, }) {
5771
5901
  // Generate time options (every 15 minutes)
5772
5902
  const timeOptions = React.useMemo(() => {
5773
5903
  const options = [];
@@ -5790,12 +5920,10 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5790
5920
  for (let h = 1; h <= 12; h++) {
5791
5921
  for (let m = 0; m < 60; m += 15) {
5792
5922
  const hour24 = mer === 'am' ? (h === 12 ? 0 : h) : h === 12 ? 12 : h + 12;
5793
- const timeStr = dayjs()
5794
- .tz(timezone)
5795
- .hour(hour24)
5796
- .minute(m)
5797
- .format('HH:mmZ');
5798
- const displayTime = dayjs(`1970-01-01T${timeStr}`, 'HH:mmZ').format('hh:mm a');
5923
+ // Format time directly without using dayjs with dummy dates
5924
+ const formattedHour = h.toString().padStart(2, '0');
5925
+ const formattedMinute = m.toString().padStart(2, '0');
5926
+ const displayTime = `${formattedHour}:${formattedMinute} ${mer}`;
5799
5927
  // Filter out times that would result in negative duration (only when dates are the same)
5800
5928
  if (startDateTime && selectedDate && shouldFilterByDate) {
5801
5929
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
@@ -5808,8 +5936,8 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5808
5936
  continue; // Skip this option as it would result in negative duration
5809
5937
  }
5810
5938
  }
5811
- // Calculate and append duration if startTime is provided
5812
- let label = displayTime;
5939
+ // Calculate duration if startTime is provided
5940
+ let durationText;
5813
5941
  if (startDateTime && selectedDate) {
5814
5942
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
5815
5943
  const optionDateTime = selectedDateObj
@@ -5822,21 +5950,30 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5822
5950
  const diffMs = optionDateTime.diff(startDateTime);
5823
5951
  const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
5824
5952
  const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
5825
- if (diffHours > 0 || diffMinutes > 0) {
5826
- const diffText = diffHours > 0
5827
- ? `${diffHours}h ${diffMinutes}m`
5828
- : `${diffMinutes}m`;
5829
- label = `${displayTime} (+${diffText})`;
5953
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
5954
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
5955
+ let diffText = '';
5956
+ if (diffHours > 0) {
5957
+ diffText = `${diffHours}h ${diffMinutes}m`;
5958
+ }
5959
+ else if (diffMinutes > 0) {
5960
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
5961
+ }
5962
+ else {
5963
+ diffText = `${diffSeconds}s`;
5964
+ }
5965
+ durationText = `+${diffText}`;
5830
5966
  }
5831
5967
  }
5832
5968
  }
5833
5969
  options.push({
5834
- label,
5970
+ label: displayTime,
5835
5971
  value: `${h}:${m.toString().padStart(2, '0')}:${mer}`,
5836
5972
  hour: h,
5837
5973
  minute: m,
5838
5974
  meridiem: mer,
5839
5975
  searchText: displayTime, // Use base time without duration for searching
5976
+ durationText,
5840
5977
  });
5841
5978
  }
5842
5979
  }
@@ -5850,22 +5987,6 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5850
5987
  itemToValue: (item) => item.value,
5851
5988
  filter: contains,
5852
5989
  });
5853
- // Track input mode vs display mode
5854
- const [isInputMode, setIsInputMode] = React.useState(false);
5855
- const [inputValue, setInputValue] = React.useState('');
5856
- const inputRef = React.useRef(null);
5857
- // Switch to display mode when value is selected
5858
- React.useEffect(() => {
5859
- if (hour !== null && minute !== null && meridiem !== null) {
5860
- setIsInputMode(false);
5861
- }
5862
- }, [hour, minute, meridiem]);
5863
- // Focus input when switching to input mode
5864
- React.useEffect(() => {
5865
- if (isInputMode && inputRef.current) {
5866
- inputRef.current.focus();
5867
- }
5868
- }, [isInputMode]);
5869
5990
  // Get current value string for combobox
5870
5991
  const currentValue = React.useMemo(() => {
5871
5992
  if (hour === null || minute === null || meridiem === null) {
@@ -5873,14 +5994,14 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5873
5994
  }
5874
5995
  return `${hour}:${minute.toString().padStart(2, '0')}:${meridiem}`;
5875
5996
  }, [hour, minute, meridiem]);
5876
- // INPUT MODE: Show raw input text (no duration)
5877
- const inputModeText = React.useMemo(() => {
5878
- return inputValue;
5879
- }, [inputValue]);
5880
- // DISPLAY MODE: Show selected value with duration
5881
- const displayModeText = React.useMemo(() => {
5882
- if (hour === null || minute === null || meridiem === null) {
5883
- return '';
5997
+ // Calculate duration difference
5998
+ const durationDiff = React.useMemo(() => {
5999
+ if (!startTime ||
6000
+ !selectedDate ||
6001
+ hour === null ||
6002
+ minute === null ||
6003
+ meridiem === null) {
6004
+ return null;
5884
6005
  }
5885
6006
  const hour24 = meridiem === 'am'
5886
6007
  ? hour === 12
@@ -5889,45 +6010,42 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5889
6010
  : hour === 12
5890
6011
  ? 12
5891
6012
  : hour + 12;
5892
- const timeStr = dayjs()
5893
- .tz(timezone)
6013
+ const startDateObj = dayjs(startTime).tz(timezone);
6014
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
6015
+ const currentDateTime = selectedDateObj
5894
6016
  .hour(hour24)
5895
6017
  .minute(minute)
5896
- .format('HH:mmZ');
5897
- const timeDisplay = dayjs(`1970-01-01T${timeStr}`, 'HH:mmZ').format('hh:mm a');
5898
- // Add duration if startTime is provided
5899
- if (startTime && selectedDate) {
5900
- const startDateObj = dayjs(startTime).tz(timezone);
5901
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
5902
- const currentDateTime = selectedDateObj
5903
- .hour(hour24)
5904
- .minute(minute)
5905
- .second(0)
5906
- .millisecond(0);
5907
- if (startDateObj.isValid() && currentDateTime.isValid()) {
5908
- const diffMs = currentDateTime.diff(startDateObj);
5909
- if (diffMs >= 0) {
5910
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
5911
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
5912
- if (diffHours > 0 || diffMinutes > 0) {
5913
- const diffText = diffHours > 0
5914
- ? `${diffHours}h ${diffMinutes}m`
5915
- : `${diffMinutes}m`;
5916
- return `${timeDisplay} (+${diffText})`;
5917
- }
5918
- }
6018
+ .second(0)
6019
+ .millisecond(0);
6020
+ if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6021
+ return null;
6022
+ }
6023
+ const diffMs = currentDateTime.diff(startDateObj);
6024
+ if (diffMs < 0) {
6025
+ return null;
6026
+ }
6027
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6028
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6029
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6030
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6031
+ let diffText = '';
6032
+ if (diffHours > 0) {
6033
+ diffText = `${diffHours}h ${diffMinutes}m`;
6034
+ }
6035
+ else if (diffMinutes > 0) {
6036
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
6037
+ }
6038
+ else {
6039
+ diffText = `${diffSeconds}s`;
5919
6040
  }
6041
+ return `+${diffText}`;
5920
6042
  }
5921
- return timeDisplay;
5922
- }, [hour, minute, meridiem, timezone, startTime, selectedDate]);
5923
- // Choose text based on mode
5924
- const displayText = isInputMode ? inputModeText : displayModeText;
6043
+ return null;
6044
+ }, [hour, minute, meridiem, startTime, selectedDate, timezone]);
5925
6045
  const handleClear = () => {
5926
6046
  setHour(null);
5927
6047
  setMinute(null);
5928
6048
  setMeridiem(null);
5929
- setIsInputMode(false);
5930
- setInputValue('');
5931
6049
  filter(''); // Reset filter to show all options
5932
6050
  onChange({ hour: null, minute: null, meridiem: null });
5933
6051
  };
@@ -5942,8 +6060,6 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5942
6060
  setHour(selectedOption.hour);
5943
6061
  setMinute(selectedOption.minute);
5944
6062
  setMeridiem(selectedOption.meridiem);
5945
- setIsInputMode(false); // Switch to display mode
5946
- setInputValue('');
5947
6063
  filter(''); // Reset filter after selection
5948
6064
  onChange({
5949
6065
  hour: selectedOption.hour,
@@ -5952,41 +6068,16 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5952
6068
  });
5953
6069
  }
5954
6070
  };
5955
- // Handle Enter key to select first filtered option
5956
- const handleKeyDown = (e) => {
5957
- if (e.key === 'Enter' && collection.items.length > 0) {
5958
- e.preventDefault();
5959
- const firstOption = collection.items[0];
5960
- if (firstOption) {
5961
- const selectedOption = timeOptions.find((opt) => opt.value === firstOption.value);
5962
- if (selectedOption) {
5963
- setHour(selectedOption.hour);
5964
- setMinute(selectedOption.minute);
5965
- setMeridiem(selectedOption.meridiem);
5966
- setIsInputMode(false); // Switch to display mode
5967
- setInputValue('');
5968
- filter('');
5969
- onChange({
5970
- hour: selectedOption.hour,
5971
- minute: selectedOption.minute,
5972
- meridiem: selectedOption.meridiem,
5973
- });
5974
- }
5975
- }
5976
- }
5977
- };
5978
- const handleInputValueChange = (details) => {
5979
- const inputValue = details.inputValue.trim();
5980
- setInputValue(inputValue);
5981
- setIsInputMode(true); // Switch to input mode
6071
+ // Parse input value and update state
6072
+ const parseAndCommitInput = (value) => {
6073
+ const trimmedValue = value.trim();
5982
6074
  // Filter the collection based on input
5983
- filter(inputValue);
5984
- if (!inputValue) {
5985
- setIsInputMode(false);
6075
+ filter(trimmedValue);
6076
+ if (!trimmedValue) {
5986
6077
  return;
5987
6078
  }
5988
6079
  // Try to parse custom input using explicit regex patterns
5989
- const normalized = inputValue.toLowerCase().replace(/\s+/g, '');
6080
+ const normalized = trimmedValue.toLowerCase().replace(/\s+/g, '');
5990
6081
  // Pattern 1: 12-hour format with meridiem (e.g., "930pm", "1230am", "9:30pm", "12:30am")
5991
6082
  // Matches: 1-2 digits hour, optional colon, 2 digits minute, am/pm
5992
6083
  const pattern12HourWithMeridiem = /^(\d{1,2}):?(\d{2})(am|pm)$/;
@@ -5997,29 +6088,43 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
5997
6088
  const parsedMeridiem = match12Hour[3];
5998
6089
  // Validate hour (1-12)
5999
6090
  if (parsedHour < 1 || parsedHour > 12) {
6091
+ // Parse failed, select first result
6092
+ selectFirstResult();
6000
6093
  return;
6001
6094
  }
6002
6095
  // Validate minute (0-59)
6003
- const validMinute = parsedMinute > 59 ? 0 : parsedMinute;
6096
+ if (parsedMinute < 0 || parsedMinute > 59) {
6097
+ // Parse failed, select first result
6098
+ selectFirstResult();
6099
+ return;
6100
+ }
6004
6101
  setHour(parsedHour);
6005
- setMinute(validMinute);
6102
+ setMinute(parsedMinute);
6006
6103
  setMeridiem(parsedMeridiem);
6007
6104
  onChange({
6008
6105
  hour: parsedHour,
6009
- minute: validMinute,
6106
+ minute: parsedMinute,
6010
6107
  meridiem: parsedMeridiem,
6011
6108
  });
6012
6109
  return;
6013
6110
  }
6014
6111
  // Pattern 2: 24-hour format (e.g., "2130", "09:30", "21:30")
6015
6112
  // Matches: 1-2 digits hour, optional colon, 2 digits minute
6016
- const pattern24Hour = /^(\d{2}):?(\d{2})$/;
6113
+ const pattern24Hour = /^(\d{1,2}):?(\d{2})$/;
6017
6114
  const match24Hour = normalized.match(pattern24Hour);
6018
6115
  if (match24Hour) {
6019
6116
  let parsedHour = parseInt(match24Hour[1], 10);
6020
6117
  const parsedMinute = parseInt(match24Hour[2], 10);
6021
6118
  // Validate hour (0-23)
6022
- if (parsedHour > 23) {
6119
+ if (parsedHour < 0 || parsedHour > 23) {
6120
+ // Parse failed, select first result
6121
+ selectFirstResult();
6122
+ return;
6123
+ }
6124
+ // Validate minute (0-59)
6125
+ if (parsedMinute < 0 || parsedMinute > 59) {
6126
+ // Parse failed, select first result
6127
+ selectFirstResult();
6023
6128
  return;
6024
6129
  }
6025
6130
  // Convert 24-hour to 12-hour format
@@ -6039,23 +6144,62 @@ onChange = (_newValue) => { }, timezone = 'Asia/Hong_Kong', startTime, selectedD
6039
6144
  else {
6040
6145
  parsedMeridiem = 'am';
6041
6146
  }
6042
- // Validate minute (0-59)
6043
- const validMinute = parsedMinute > 59 ? 0 : parsedMinute;
6044
6147
  setHour(parsedHour);
6045
- setMinute(validMinute);
6148
+ setMinute(parsedMinute);
6046
6149
  setMeridiem(parsedMeridiem);
6047
6150
  onChange({
6048
6151
  hour: parsedHour,
6049
- minute: validMinute,
6152
+ minute: parsedMinute,
6050
6153
  meridiem: parsedMeridiem,
6051
6154
  });
6052
6155
  return;
6053
6156
  }
6157
+ // Parse failed, select first result
6158
+ selectFirstResult();
6054
6159
  };
6055
- return (jsxRuntime.jsxs(react.Grid, { justifyContent: 'center', alignItems: 'center', templateColumns: '200px auto', gap: "2", width: "auto", minWidth: "250px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, width: "100%", children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [isInputMode ? (jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { ref: inputRef, placeholder: "Select time", value: displayText, onKeyDown: handleKeyDown }) })) : (jsxRuntime.jsxs(react.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: [jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(bs.BsClock, {}) }), jsxRuntime.jsx(react.Text, { fontSize: "sm", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: displayText || 'Select time' }), jsxRuntime.jsx(react.Combobox.Trigger, { onClick: (e) => {
6056
- e.stopPropagation();
6057
- setIsInputMode(true);
6058
- } })] })), isInputMode && (jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [jsxRuntime.jsx(react.Combobox.ClearTrigger, {}), jsxRuntime.jsx(react.Combobox.Trigger, {})] }))] }), jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: "No time found" }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [item.label, jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdCancel, {}) }) })] }));
6160
+ // Select first result from filtered collection
6161
+ const selectFirstResult = () => {
6162
+ if (collection.items.length > 0) {
6163
+ const firstItem = collection.items[0];
6164
+ setHour(firstItem.hour);
6165
+ setMinute(firstItem.minute);
6166
+ setMeridiem(firstItem.meridiem);
6167
+ filter(''); // Reset filter after selection
6168
+ onChange({
6169
+ hour: firstItem.hour,
6170
+ minute: firstItem.minute,
6171
+ meridiem: firstItem.meridiem,
6172
+ });
6173
+ }
6174
+ };
6175
+ const handleInputValueChange = (details) => {
6176
+ // Filter the collection based on input, but don't parse yet
6177
+ filter(details.inputValue);
6178
+ };
6179
+ const handleFocus = (e) => {
6180
+ // Select all text when focusing
6181
+ e.target.select();
6182
+ };
6183
+ const handleBlur = (e) => {
6184
+ // Parse and commit the input value when losing focus
6185
+ const inputValue = e.target.value;
6186
+ if (inputValue) {
6187
+ parseAndCommitInput(inputValue);
6188
+ }
6189
+ };
6190
+ const handleKeyDown = (e) => {
6191
+ // Commit input on Enter key
6192
+ if (e.key === 'Enter') {
6193
+ e.preventDefault();
6194
+ const inputValue = e.currentTarget.value;
6195
+ if (inputValue) {
6196
+ parseAndCommitInput(inputValue);
6197
+ }
6198
+ // Blur the input
6199
+ e.currentTarget?.blur();
6200
+ }
6201
+ };
6202
+ return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: "hh:mm a", onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: "No time found" }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsxRuntime.jsx(react.Text, { flex: 1, children: item.label }), item.durationText && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", colorPalette: "blue", children: jsxRuntime.jsx(react.Tag.Label, { children: item.durationText }) }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: durationDiff }) })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdCancel, {}) }) })] }) }));
6059
6203
  }
6060
6204
 
6061
6205
  dayjs.extend(timezone);
@@ -6133,11 +6277,138 @@ const TimePicker = ({ column, schema, prefix }) => {
6133
6277
  } }) }) }) }) }))] }) }));
6134
6278
  };
6135
6279
 
6280
+ dayjs.extend(utc);
6281
+ dayjs.extend(timezone);
6282
+ dayjs.extend(customParseFormat);
6283
+ function DatePickerInput({ value, onChange, placeholder = 'Select a date', dateFormat = 'YYYY-MM-DD', displayFormat = 'YYYY-MM-DD', labels = {
6284
+ monthNamesShort: [
6285
+ 'Jan',
6286
+ 'Feb',
6287
+ 'Mar',
6288
+ 'Apr',
6289
+ 'May',
6290
+ 'Jun',
6291
+ 'Jul',
6292
+ 'Aug',
6293
+ 'Sep',
6294
+ 'Oct',
6295
+ 'Nov',
6296
+ 'Dec',
6297
+ ],
6298
+ weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
6299
+ backButtonLabel: 'Back',
6300
+ forwardButtonLabel: 'Next',
6301
+ }, timezone = 'Asia/Hong_Kong', minDate, maxDate, firstDayOfWeek, showOutsideDays, monthsToDisplay = 1, insideDialog = false, readOnly = false, }) {
6302
+ const [open, setOpen] = React.useState(false);
6303
+ const [inputValue, setInputValue] = React.useState('');
6304
+ // Update input value when prop value changes
6305
+ React.useEffect(() => {
6306
+ if (value) {
6307
+ const formatted = typeof value === 'string'
6308
+ ? dayjs(value).tz(timezone).isValid()
6309
+ ? dayjs(value).tz(timezone).format(displayFormat)
6310
+ : ''
6311
+ : dayjs(value).tz(timezone).format(displayFormat);
6312
+ setInputValue(formatted);
6313
+ }
6314
+ else {
6315
+ setInputValue('');
6316
+ }
6317
+ }, [value, displayFormat, timezone]);
6318
+ // Convert value to Date object for DatePicker
6319
+ const selectedDate = value
6320
+ ? typeof value === 'string'
6321
+ ? dayjs(value).tz(timezone).isValid()
6322
+ ? dayjs(value).tz(timezone).toDate()
6323
+ : new Date()
6324
+ : value
6325
+ : new Date();
6326
+ // Shared function to parse and validate input value
6327
+ const parseAndValidateInput = (inputVal) => {
6328
+ // If empty, clear the value
6329
+ if (!inputVal.trim()) {
6330
+ onChange?.(undefined);
6331
+ setInputValue('');
6332
+ return;
6333
+ }
6334
+ // Try parsing with displayFormat first
6335
+ let parsedDate = dayjs(inputVal, displayFormat, true);
6336
+ // If that fails, try common date formats
6337
+ if (!parsedDate.isValid()) {
6338
+ parsedDate = dayjs(inputVal);
6339
+ }
6340
+ // If still invalid, try parsing with dateFormat
6341
+ if (!parsedDate.isValid()) {
6342
+ parsedDate = dayjs(inputVal, dateFormat, true);
6343
+ }
6344
+ // If valid, check constraints and update
6345
+ if (parsedDate.isValid()) {
6346
+ const dateObj = parsedDate.tz(timezone).toDate();
6347
+ // Check min/max constraints
6348
+ if (minDate && dateObj < minDate) {
6349
+ // Invalid: before minDate, reset to prop value
6350
+ resetToPropValue();
6351
+ return;
6352
+ }
6353
+ if (maxDate && dateObj > maxDate) {
6354
+ // Invalid: after maxDate, reset to prop value
6355
+ resetToPropValue();
6356
+ return;
6357
+ }
6358
+ // Valid date - format and update
6359
+ const formattedDate = parsedDate.tz(timezone).format(dateFormat);
6360
+ const formattedDisplay = parsedDate.tz(timezone).format(displayFormat);
6361
+ onChange?.(formattedDate);
6362
+ setInputValue(formattedDisplay);
6363
+ }
6364
+ else {
6365
+ // Invalid date - reset to prop value
6366
+ resetToPropValue();
6367
+ }
6368
+ };
6369
+ // Helper function to reset input to prop value
6370
+ const resetToPropValue = () => {
6371
+ if (value) {
6372
+ const formatted = typeof value === 'string'
6373
+ ? dayjs(value).tz(timezone).isValid()
6374
+ ? dayjs(value).tz(timezone).format(displayFormat)
6375
+ : ''
6376
+ : dayjs(value).tz(timezone).format(displayFormat);
6377
+ setInputValue(formatted);
6378
+ }
6379
+ else {
6380
+ setInputValue('');
6381
+ }
6382
+ };
6383
+ const handleInputChange = (e) => {
6384
+ // Only update the input value, don't parse yet
6385
+ setInputValue(e.target.value);
6386
+ };
6387
+ const handleInputBlur = () => {
6388
+ // Parse and validate when input loses focus
6389
+ parseAndValidateInput(inputValue);
6390
+ };
6391
+ const handleKeyDown = (e) => {
6392
+ // Parse and validate when Enter is pressed
6393
+ if (e.key === 'Enter') {
6394
+ e.preventDefault();
6395
+ parseAndValidateInput(inputValue);
6396
+ }
6397
+ };
6398
+ const handleDateSelected = ({ date }) => {
6399
+ const formattedDate = dayjs(date).tz(timezone).format(dateFormat);
6400
+ onChange?.(formattedDate);
6401
+ setOpen(false);
6402
+ };
6403
+ const datePickerContent = (jsxRuntime.jsx(DatePicker$1, { selected: selectedDate, onDateSelected: handleDateSelected, labels: labels, minDate: minDate, maxDate: maxDate, firstDayOfWeek: firstDayOfWeek, showOutsideDays: showOutsideDays, monthsToDisplay: monthsToDisplay }));
6404
+ return (jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(InputGroup, { endElement: jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsx(react.IconButton, { variant: "ghost", size: "2xs", "aria-label": "Open calendar", onClick: () => setOpen(true), children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdDateRange, {}) }) }) }), children: jsxRuntime.jsx(react.Input, { value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, readOnly: readOnly }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: datePickerContent }) }) }) }))] }));
6405
+ }
6406
+
6136
6407
  dayjs.extend(utc);
6137
6408
  dayjs.extend(timezone);
6138
6409
  function IsoTimePicker({ hour, setHour, minute, setMinute, second, setSecond,
6139
6410
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
6140
- onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', }) {
6411
+ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Kong', portalled = true, }) {
6141
6412
  // Generate time options (every 15 minutes, seconds always 0)
6142
6413
  const timeOptions = React.useMemo(() => {
6143
6414
  const options = [];
@@ -6172,8 +6443,8 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6172
6443
  continue; // Skip this option as it would result in negative duration
6173
6444
  }
6174
6445
  }
6175
- // Calculate and append duration if startTime is provided
6176
- let label = timeDisplay;
6446
+ // Calculate duration if startTime is provided
6447
+ let durationText;
6177
6448
  if (startDateTime && selectedDate) {
6178
6449
  const selectedDateObj = dayjs(selectedDate).tz(timezone);
6179
6450
  const optionDateTime = selectedDateObj
@@ -6198,17 +6469,18 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6198
6469
  else {
6199
6470
  diffText = `${diffSeconds}s`;
6200
6471
  }
6201
- label = `${timeDisplay} (+${diffText})`;
6472
+ durationText = `+${diffText}`;
6202
6473
  }
6203
6474
  }
6204
6475
  }
6205
6476
  options.push({
6206
- label,
6477
+ label: timeDisplay,
6207
6478
  value: `${h}:${m}:0`,
6208
6479
  hour: h,
6209
6480
  minute: m,
6210
6481
  second: 0,
6211
6482
  searchText: timeDisplay, // Use base time without duration for searching
6483
+ durationText,
6212
6484
  });
6213
6485
  }
6214
6486
  }
@@ -6228,46 +6500,46 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6228
6500
  }
6229
6501
  return `${hour}:${minute}:${second}`;
6230
6502
  }, [hour, minute, second]);
6231
- // Get display text for combobox
6232
- const displayText = React.useMemo(() => {
6233
- if (hour === null || minute === null || second === null) {
6234
- return '';
6503
+ // Calculate duration difference
6504
+ const durationDiff = React.useMemo(() => {
6505
+ if (!startTime ||
6506
+ !selectedDate ||
6507
+ hour === null ||
6508
+ minute === null ||
6509
+ second === null) {
6510
+ return null;
6235
6511
  }
6236
- const timeDisplay = `${hour.toString().padStart(2, '0')}:${minute
6237
- .toString()
6238
- .padStart(2, '0')}:${second.toString().padStart(2, '0')}`;
6239
- // Show duration difference if startTime is provided
6240
- if (startTime && selectedDate) {
6241
- const startDateObj = dayjs(startTime).tz(timezone);
6242
- const selectedDateObj = dayjs(selectedDate).tz(timezone);
6243
- const currentDateTime = selectedDateObj
6244
- .hour(hour)
6245
- .minute(minute)
6246
- .second(second ?? 0)
6247
- .millisecond(0);
6248
- if (startDateObj.isValid() && currentDateTime.isValid()) {
6249
- const diffMs = currentDateTime.diff(startDateObj);
6250
- if (diffMs >= 0) {
6251
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6252
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6253
- const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6254
- if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6255
- let diffText = '';
6256
- if (diffHours > 0) {
6257
- diffText = `${diffHours}h ${diffMinutes}m`;
6258
- }
6259
- else if (diffMinutes > 0) {
6260
- diffText = `${diffMinutes}m ${diffSeconds}s`;
6261
- }
6262
- else {
6263
- diffText = `${diffSeconds}s`;
6264
- }
6265
- return `${timeDisplay} (+${diffText})`;
6266
- }
6267
- }
6512
+ const startDateObj = dayjs(startTime).tz(timezone);
6513
+ const selectedDateObj = dayjs(selectedDate).tz(timezone);
6514
+ const currentDateTime = selectedDateObj
6515
+ .hour(hour)
6516
+ .minute(minute)
6517
+ .second(second ?? 0)
6518
+ .millisecond(0);
6519
+ if (!startDateObj.isValid() || !currentDateTime.isValid()) {
6520
+ return null;
6521
+ }
6522
+ const diffMs = currentDateTime.diff(startDateObj);
6523
+ if (diffMs < 0) {
6524
+ return null;
6525
+ }
6526
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
6527
+ const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
6528
+ const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000);
6529
+ if (diffHours > 0 || diffMinutes > 0 || diffSeconds > 0) {
6530
+ let diffText = '';
6531
+ if (diffHours > 0) {
6532
+ diffText = `${diffHours}h ${diffMinutes}m`;
6533
+ }
6534
+ else if (diffMinutes > 0) {
6535
+ diffText = `${diffMinutes}m ${diffSeconds}s`;
6268
6536
  }
6537
+ else {
6538
+ diffText = `${diffSeconds}s`;
6539
+ }
6540
+ return `+${diffText}`;
6269
6541
  }
6270
- return timeDisplay;
6542
+ return null;
6271
6543
  }, [hour, minute, second, startTime, selectedDate, timezone]);
6272
6544
  const handleClear = () => {
6273
6545
  setHour(null);
@@ -6295,16 +6567,17 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6295
6567
  });
6296
6568
  }
6297
6569
  };
6298
- const handleInputValueChange = (details) => {
6299
- const inputValue = details.inputValue.trim();
6570
+ // Parse input value and update state
6571
+ const parseAndCommitInput = (value) => {
6572
+ const trimmedValue = value.trim();
6300
6573
  // Filter the collection based on input
6301
- filter(inputValue);
6302
- if (!inputValue) {
6574
+ filter(trimmedValue);
6575
+ if (!trimmedValue) {
6303
6576
  return;
6304
6577
  }
6305
6578
  // Parse HH:mm:ss or HH:mm format
6306
6579
  const timePattern = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
6307
- const match = inputValue.match(timePattern);
6580
+ const match = trimmedValue.match(timePattern);
6308
6581
  if (match) {
6309
6582
  const parsedHour = parseInt(match[1], 10);
6310
6583
  const parsedMinute = parseInt(match[2], 10);
@@ -6324,11 +6597,12 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6324
6597
  minute: parsedMinute,
6325
6598
  second: parsedSecond,
6326
6599
  });
6600
+ return;
6327
6601
  }
6328
6602
  }
6329
6603
  else {
6330
6604
  // Try to parse formats like "123045" (HHmmss) or "1230" (HHmm)
6331
- const numbersOnly = inputValue.replace(/[^0-9]/g, '');
6605
+ const numbersOnly = trimmedValue.replace(/[^0-9]/g, '');
6332
6606
  if (numbersOnly.length >= 4) {
6333
6607
  const parsedHour = parseInt(numbersOnly.slice(0, 2), 10);
6334
6608
  const parsedMinute = parseInt(numbersOnly.slice(2, 4), 10);
@@ -6348,11 +6622,56 @@ onChange = (_newValue) => { }, startTime, selectedDate, timezone = 'Asia/Hong_Ko
6348
6622
  minute: parsedMinute,
6349
6623
  second: parsedSecond,
6350
6624
  });
6625
+ return;
6351
6626
  }
6352
6627
  }
6353
6628
  }
6629
+ // Parse failed, select first result
6630
+ selectFirstResult();
6631
+ };
6632
+ // Select first result from filtered collection
6633
+ const selectFirstResult = () => {
6634
+ if (collection.items.length > 0) {
6635
+ const firstItem = collection.items[0];
6636
+ setHour(firstItem.hour);
6637
+ setMinute(firstItem.minute);
6638
+ setSecond(firstItem.second);
6639
+ filter(''); // Reset filter after selection
6640
+ onChange({
6641
+ hour: firstItem.hour,
6642
+ minute: firstItem.minute,
6643
+ second: firstItem.second,
6644
+ });
6645
+ }
6646
+ };
6647
+ const handleInputValueChange = (details) => {
6648
+ // Filter the collection based on input, but don't parse yet
6649
+ filter(details.inputValue);
6650
+ };
6651
+ const handleFocus = (e) => {
6652
+ // Select all text when focusing
6653
+ e.target.select();
6654
+ };
6655
+ const handleBlur = (e) => {
6656
+ // Parse and commit the input value when losing focus
6657
+ const inputValue = e.target.value;
6658
+ if (inputValue) {
6659
+ parseAndCommitInput(inputValue);
6660
+ }
6661
+ };
6662
+ const handleKeyDown = (e) => {
6663
+ // Commit input on Enter key
6664
+ if (e.key === 'Enter') {
6665
+ e.preventDefault();
6666
+ const inputValue = e.currentTarget.value;
6667
+ if (inputValue) {
6668
+ parseAndCommitInput(inputValue);
6669
+ }
6670
+ // Blur the input
6671
+ e.currentTarget?.blur();
6672
+ }
6354
6673
  };
6355
- return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Grid, { justifyContent: 'center', alignItems: 'center', templateColumns: '1fr auto', gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, width: "100%", children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: "HH:mm:ss", value: displayText }) }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [jsxRuntime.jsx(react.Combobox.ClearTrigger, {}), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }), jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: "No time found" }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [item.label, jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdCancel, {}) }) })] }) }));
6674
+ return (jsxRuntime.jsx(react.Flex, { direction: "column", gap: 3, children: jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", width: "auto", minWidth: "300px", children: [jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue ? [currentValue] : [], onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, allowCustomValue: true, selectionBehavior: "replace", openOnClick: true, flex: 1, children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.InputGroup, { startElement: jsxRuntime.jsx(bs.BsClock, {}), children: jsxRuntime.jsx(react.Combobox.Input, { placeholder: "HH:mm:ss", onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), jsxRuntime.jsx(react.Combobox.IndicatorGroup, { children: jsxRuntime.jsx(react.Combobox.Trigger, {}) })] }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [jsxRuntime.jsx(react.Combobox.Empty, { children: "No time found" }), collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: 2, width: "100%", children: [jsxRuntime.jsx(react.Text, { flex: 1, children: item.label }), item.durationText && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: item.durationText }) }))] }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value)))] }) }) })] }), durationDiff && (jsxRuntime.jsx(react.Tag.Root, { size: "sm", children: jsxRuntime.jsx(react.Tag.Label, { children: durationDiff }) })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "ghost", children: jsxRuntime.jsx(react.Icon, { children: jsxRuntime.jsx(md.MdCancel, {}) }) })] }) }));
6356
6675
  }
6357
6676
 
6358
6677
  dayjs.extend(utc);
@@ -6375,30 +6694,193 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
6375
6694
  weekdayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
6376
6695
  backButtonLabel: 'Back',
6377
6696
  forwardButtonLabel: 'Next',
6378
- }, timezone = 'Asia/Hong_Kong', startTime, }) {
6379
- const [selectedDate, setSelectedDate] = React.useState(value || '');
6697
+ }, timezone = 'Asia/Hong_Kong', startTime, minDate, maxDate, portalled = false, }) {
6698
+ console.log('[DateTimePicker] Component initialized with props:', {
6699
+ value,
6700
+ format,
6701
+ showSeconds,
6702
+ timezone,
6703
+ startTime,
6704
+ minDate,
6705
+ maxDate,
6706
+ });
6707
+ // Initialize selectedDate from value prop, converting ISO to YYYY-MM-DD format
6708
+ const getDateString = React.useCallback((val) => {
6709
+ if (!val)
6710
+ return '';
6711
+ const dateObj = dayjs(val).tz(timezone);
6712
+ return dateObj.isValid() ? dateObj.format('YYYY-MM-DD') : '';
6713
+ }, [timezone]);
6714
+ const [selectedDate, setSelectedDate] = React.useState(getDateString(value));
6715
+ // Helper to get time values from value prop with timezone
6716
+ const getTimeFromValue = React.useCallback((val) => {
6717
+ console.log('[DateTimePicker] getTimeFromValue called:', {
6718
+ val,
6719
+ timezone,
6720
+ showSeconds,
6721
+ });
6722
+ if (!val) {
6723
+ console.log('[DateTimePicker] No value provided, returning nulls');
6724
+ return {
6725
+ hour12: null,
6726
+ minute: null,
6727
+ meridiem: null,
6728
+ hour24: null,
6729
+ second: null,
6730
+ };
6731
+ }
6732
+ const dateObj = dayjs(val).tz(timezone);
6733
+ console.log('[DateTimePicker] Parsed date object:', {
6734
+ original: val,
6735
+ timezone,
6736
+ isValid: dateObj.isValid(),
6737
+ formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
6738
+ hour24: dateObj.hour(),
6739
+ minute: dateObj.minute(),
6740
+ second: dateObj.second(),
6741
+ });
6742
+ if (!dateObj.isValid()) {
6743
+ console.log('[DateTimePicker] Invalid date object, returning nulls');
6744
+ return {
6745
+ hour12: null,
6746
+ minute: null,
6747
+ meridiem: null,
6748
+ hour24: null,
6749
+ second: null,
6750
+ };
6751
+ }
6752
+ const hour24Value = dateObj.hour();
6753
+ const hour12Value = hour24Value % 12 || 12;
6754
+ const minuteValue = dateObj.minute();
6755
+ const meridiemValue = hour24Value >= 12 ? 'pm' : 'am';
6756
+ const secondValue = showSeconds ? dateObj.second() : null;
6757
+ const result = {
6758
+ hour12: hour12Value,
6759
+ minute: minuteValue,
6760
+ meridiem: meridiemValue,
6761
+ hour24: hour24Value,
6762
+ second: secondValue,
6763
+ };
6764
+ console.log('[DateTimePicker] Extracted time values:', result);
6765
+ return result;
6766
+ }, [timezone, showSeconds]);
6767
+ const initialTime = getTimeFromValue(value);
6768
+ console.log('[DateTimePicker] Initial time from value:', {
6769
+ value,
6770
+ initialTime,
6771
+ });
6380
6772
  // Time state for 12-hour format
6381
- const [hour12, setHour12] = React.useState(value ? dayjs(value).hour() % 12 || 12 : null);
6382
- const [minute, setMinute] = React.useState(value ? dayjs(value).minute() : null);
6383
- const [meridiem, setMeridiem] = React.useState(value ? (dayjs(value).hour() >= 12 ? 'pm' : 'am') : null);
6773
+ const [hour12, setHour12] = React.useState(initialTime.hour12);
6774
+ const [minute, setMinute] = React.useState(initialTime.minute);
6775
+ const [meridiem, setMeridiem] = React.useState(initialTime.meridiem);
6384
6776
  // Time state for 24-hour format
6385
- const [hour24, setHour24] = React.useState(value ? dayjs(value).hour() : null);
6386
- const [second, setSecond] = React.useState(showSeconds && value ? dayjs(value).second() : null);
6777
+ const [hour24, setHour24] = React.useState(initialTime.hour24);
6778
+ const [second, setSecond] = React.useState(initialTime.second);
6779
+ // Sync selectedDate and time states when value prop changes
6780
+ React.useEffect(() => {
6781
+ console.log('[DateTimePicker] useEffect triggered - value changed:', {
6782
+ value,
6783
+ timezone,
6784
+ format,
6785
+ });
6786
+ // If value is null, undefined, or invalid, clear all fields
6787
+ if (!value || value === null || value === undefined) {
6788
+ console.log('[DateTimePicker] Value is null/undefined, clearing all fields');
6789
+ setSelectedDate('');
6790
+ setHour12(null);
6791
+ setMinute(null);
6792
+ setMeridiem(null);
6793
+ setHour24(null);
6794
+ setSecond(null);
6795
+ return;
6796
+ }
6797
+ // Check if value is valid
6798
+ const dateObj = dayjs(value).tz(timezone);
6799
+ if (!dateObj.isValid()) {
6800
+ console.log('[DateTimePicker] Invalid value, clearing all fields');
6801
+ setSelectedDate('');
6802
+ setHour12(null);
6803
+ setMinute(null);
6804
+ setMeridiem(null);
6805
+ setHour24(null);
6806
+ setSecond(null);
6807
+ return;
6808
+ }
6809
+ const dateString = getDateString(value);
6810
+ console.log('[DateTimePicker] Setting selectedDate:', dateString);
6811
+ setSelectedDate(dateString);
6812
+ const timeData = getTimeFromValue(value);
6813
+ console.log('[DateTimePicker] Updating time states:', {
6814
+ timeData,
6815
+ });
6816
+ setHour12(timeData.hour12);
6817
+ setMinute(timeData.minute);
6818
+ setMeridiem(timeData.meridiem);
6819
+ setHour24(timeData.hour24);
6820
+ setSecond(timeData.second);
6821
+ }, [value, getTimeFromValue, getDateString, timezone]);
6387
6822
  const handleDateChange = (date) => {
6823
+ console.log('[DateTimePicker] handleDateChange called:', {
6824
+ date,
6825
+ timezone,
6826
+ showSeconds,
6827
+ currentTimeStates: { hour12, minute, meridiem, hour24, second },
6828
+ });
6829
+ // If date is empty or invalid, clear all fields
6830
+ if (!date || date === '') {
6831
+ console.log('[DateTimePicker] Empty date, clearing all fields');
6832
+ setSelectedDate('');
6833
+ setHour12(null);
6834
+ setMinute(null);
6835
+ setMeridiem(null);
6836
+ setHour24(null);
6837
+ setSecond(null);
6838
+ onChange?.(undefined);
6839
+ return;
6840
+ }
6388
6841
  setSelectedDate(date);
6842
+ // Parse the date string (YYYY-MM-DD) in the specified timezone
6843
+ const dateObj = dayjs.tz(date, timezone);
6844
+ console.log('[DateTimePicker] Parsed date object:', {
6845
+ date,
6846
+ timezone,
6847
+ isValid: dateObj.isValid(),
6848
+ isoString: dateObj.toISOString(),
6849
+ formatted: dateObj.format('YYYY-MM-DD HH:mm:ss Z'),
6850
+ });
6851
+ if (!dateObj.isValid()) {
6852
+ console.warn('[DateTimePicker] Invalid date object in handleDateChange, clearing fields');
6853
+ setSelectedDate('');
6854
+ setHour12(null);
6855
+ setMinute(null);
6856
+ setMeridiem(null);
6857
+ setHour24(null);
6858
+ setSecond(null);
6859
+ onChange?.(undefined);
6860
+ return;
6861
+ }
6389
6862
  // When showSeconds is false, ignore seconds from the date
6390
- const dateObj = dayjs(date).tz(timezone);
6391
- if (!showSeconds && dateObj.isValid()) {
6863
+ if (!showSeconds) {
6392
6864
  const dateWithoutSeconds = dateObj.second(0).millisecond(0).toISOString();
6865
+ console.log('[DateTimePicker] Updating date without seconds:', dateWithoutSeconds);
6393
6866
  updateDateTime(dateWithoutSeconds);
6394
6867
  }
6395
6868
  else {
6396
- updateDateTime(dateObj.toISOString());
6869
+ const dateWithSeconds = dateObj.toISOString();
6870
+ console.log('[DateTimePicker] Updating date with seconds:', dateWithSeconds);
6871
+ updateDateTime(dateWithSeconds);
6397
6872
  }
6398
6873
  };
6399
6874
  const handleTimeChange = (timeData) => {
6875
+ console.log('[DateTimePicker] handleTimeChange called:', {
6876
+ timeData,
6877
+ format,
6878
+ selectedDate,
6879
+ timezone,
6880
+ });
6400
6881
  if (format === 'iso-date-time') {
6401
6882
  const data = timeData;
6883
+ console.log('[DateTimePicker] ISO format - setting 24-hour time:', data);
6402
6884
  setHour24(data.hour);
6403
6885
  setMinute(data.minute);
6404
6886
  if (showSeconds) {
@@ -6411,60 +6893,161 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
6411
6893
  }
6412
6894
  else {
6413
6895
  const data = timeData;
6896
+ console.log('[DateTimePicker] 12-hour format - setting time:', data);
6414
6897
  setHour12(data.hour);
6415
6898
  setMinute(data.minute);
6416
6899
  setMeridiem(data.meridiem);
6417
6900
  }
6418
- // Use selectedDate if valid, otherwise use today's date as fallback
6419
- const dateToUse = selectedDate && dayjs(selectedDate).isValid()
6420
- ? selectedDate
6421
- : dayjs().tz(timezone).toISOString();
6422
- const dateObj = dayjs(dateToUse).tz(timezone);
6901
+ // Use selectedDate if valid, otherwise clear all fields
6902
+ if (!selectedDate || !dayjs(selectedDate).isValid()) {
6903
+ console.log('[DateTimePicker] No valid selectedDate, clearing all fields');
6904
+ setSelectedDate('');
6905
+ setHour12(null);
6906
+ setMinute(null);
6907
+ setMeridiem(null);
6908
+ setHour24(null);
6909
+ setSecond(null);
6910
+ onChange?.(undefined);
6911
+ return;
6912
+ }
6913
+ const dateObj = dayjs(selectedDate).tz(timezone);
6423
6914
  if (dateObj.isValid()) {
6424
6915
  updateDateTime(dateObj.toISOString(), timeData);
6425
6916
  }
6917
+ else {
6918
+ console.warn('[DateTimePicker] Invalid date object in handleTimeChange, clearing fields');
6919
+ setSelectedDate('');
6920
+ setHour12(null);
6921
+ setMinute(null);
6922
+ setMeridiem(null);
6923
+ setHour24(null);
6924
+ setSecond(null);
6925
+ onChange?.(undefined);
6926
+ }
6426
6927
  };
6427
6928
  const updateDateTime = (date, timeData) => {
6428
- if (!date) {
6929
+ console.log('[DateTimePicker] updateDateTime called:', {
6930
+ date,
6931
+ timeData,
6932
+ format,
6933
+ currentStates: { hour12, minute, meridiem, hour24, second },
6934
+ });
6935
+ if (!date || date === null || date === undefined) {
6936
+ console.log('[DateTimePicker] No date provided, clearing all fields and calling onChange(undefined)');
6937
+ setSelectedDate('');
6938
+ setHour12(null);
6939
+ setMinute(null);
6940
+ setMeridiem(null);
6941
+ setHour24(null);
6942
+ setSecond(null);
6429
6943
  onChange?.(undefined);
6430
6944
  return;
6431
6945
  }
6432
6946
  // use dayjs to convert the date to the timezone
6433
6947
  const dateObj = dayjs(date).tz(timezone);
6434
6948
  if (!dateObj.isValid()) {
6949
+ console.warn('[DateTimePicker] Invalid date object in updateDateTime, clearing fields:', date);
6950
+ setSelectedDate('');
6951
+ setHour12(null);
6952
+ setMinute(null);
6953
+ setMeridiem(null);
6954
+ setHour24(null);
6955
+ setSecond(null);
6956
+ onChange?.(undefined);
6435
6957
  return;
6436
6958
  }
6437
6959
  const newDate = dateObj.toDate();
6438
6960
  if (format === 'iso-date-time') {
6439
6961
  const data = timeData;
6440
- const h = data?.hour ?? hour24;
6441
- const m = data?.minute ?? minute;
6962
+ // Use timeData values if provided, otherwise fall back to current state
6963
+ // But if timeData is explicitly provided with nulls, we need to check if all are null
6964
+ const h = data !== undefined ? data.hour : hour24;
6965
+ const m = data !== undefined ? data.minute : minute;
6442
6966
  // Always ignore seconds when showSeconds is false - set to 0
6443
- const s = showSeconds ? data?.second ?? second ?? 0 : 0;
6967
+ const s = showSeconds
6968
+ ? data !== undefined
6969
+ ? data.second ?? null
6970
+ : second ?? 0
6971
+ : 0;
6972
+ // If all time values are null, clear the value
6973
+ if (h === null && m === null && (showSeconds ? s === null : true)) {
6974
+ console.log('[DateTimePicker] All time values are null, clearing value');
6975
+ onChange?.(undefined);
6976
+ return;
6977
+ }
6978
+ console.log('[DateTimePicker] ISO format - setting time on date:', {
6979
+ h,
6980
+ m,
6981
+ s,
6982
+ showSeconds,
6983
+ });
6444
6984
  if (h !== null)
6445
6985
  newDate.setHours(h);
6446
6986
  if (m !== null)
6447
6987
  newDate.setMinutes(m);
6448
- newDate.setSeconds(s);
6988
+ newDate.setSeconds(s ?? 0);
6449
6989
  }
6450
6990
  else {
6451
6991
  const data = timeData;
6452
- const h = data?.hour ?? hour12;
6453
- const m = data?.minute ?? minute;
6454
- const mer = data?.meridiem ?? meridiem;
6992
+ console.log('[DateTimePicker] Processing 12-hour format:', {
6993
+ 'data !== undefined': data !== undefined,
6994
+ 'data?.hour': data?.hour,
6995
+ 'data?.minute': data?.minute,
6996
+ 'data?.meridiem': data?.meridiem,
6997
+ 'current hour12': hour12,
6998
+ 'current minute': minute,
6999
+ 'current meridiem': meridiem,
7000
+ });
7001
+ // Use timeData values if provided, otherwise fall back to current state
7002
+ const h = data !== undefined ? data.hour : hour12;
7003
+ const m = data !== undefined ? data.minute : minute;
7004
+ const mer = data !== undefined ? data.meridiem : meridiem;
7005
+ console.log('[DateTimePicker] Resolved time values:', { h, m, mer });
7006
+ // If all time values are null, clear the value
7007
+ if (h === null && m === null && mer === null) {
7008
+ console.log('[DateTimePicker] All time values are null, clearing value');
7009
+ onChange?.(undefined);
7010
+ return;
7011
+ }
7012
+ console.log('[DateTimePicker] 12-hour format - converting time:', {
7013
+ h,
7014
+ m,
7015
+ mer,
7016
+ });
6455
7017
  if (h !== null && mer !== null) {
6456
7018
  let hour24 = h;
6457
7019
  if (mer === 'am' && h === 12)
6458
7020
  hour24 = 0;
6459
7021
  else if (mer === 'pm' && h < 12)
6460
7022
  hour24 = h + 12;
7023
+ console.log('[DateTimePicker] Converted to 24-hour:', {
7024
+ h,
7025
+ mer,
7026
+ hour24,
7027
+ });
6461
7028
  newDate.setHours(hour24);
6462
7029
  }
6463
- if (m !== null)
7030
+ else {
7031
+ console.log('[DateTimePicker] Skipping hour update - h or mer is null:', {
7032
+ h,
7033
+ mer,
7034
+ });
7035
+ }
7036
+ if (m !== null) {
6464
7037
  newDate.setMinutes(m);
7038
+ }
7039
+ else {
7040
+ console.log('[DateTimePicker] Skipping minute update - m is null');
7041
+ }
6465
7042
  newDate.setSeconds(0);
6466
7043
  }
6467
- onChange?.(dayjs(newDate).tz(timezone).toISOString());
7044
+ const finalISO = dayjs(newDate).tz(timezone).toISOString();
7045
+ console.log('[DateTimePicker] Final ISO string to emit:', {
7046
+ newDate: newDate.toISOString(),
7047
+ timezone,
7048
+ finalISO,
7049
+ });
7050
+ onChange?.(finalISO);
6468
7051
  };
6469
7052
  const handleClear = () => {
6470
7053
  setSelectedDate('');
@@ -6480,14 +7063,92 @@ function DateTimePicker$1({ value, onChange, format = 'date-time', showSeconds =
6480
7063
  const normalizedStartTime = startTime
6481
7064
  ? dayjs(startTime).tz(timezone).millisecond(0).toISOString()
6482
7065
  : undefined;
6483
- return (jsxRuntime.jsxs(react.Flex, { direction: "column", gap: 4, p: 4, border: "1px solid", borderColor: "gray.200", borderRadius: "md", children: [jsxRuntime.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 &&
6484
- dayjs(normalizedStartTime).tz(timezone).isValid()
6485
- ? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
6486
- : undefined }), jsxRuntime.jsxs(react.Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (jsxRuntime.jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: showSeconds ? second : null, setSecond: showSeconds ? setSecond : () => { }, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone })) : (jsxRuntime.jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsxRuntime.jsx(react.Icon, { as: fa6.FaTrash }) })] }), selectedDate && (jsxRuntime.jsxs(react.Flex, { gap: 2, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: dayjs(value).format(isISO
6487
- ? showSeconds
6488
- ? 'YYYY-MM-DD HH:mm:ss'
6489
- : 'YYYY-MM-DD HH:mm'
6490
- : 'YYYY-MM-DD hh:mm A ') }), jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: dayjs(value).tz(timezone).format('Z') }), jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
7066
+ // Determine minDate: prioritize explicit minDate prop, then fall back to startTime
7067
+ const effectiveMinDate = minDate
7068
+ ? minDate
7069
+ : normalizedStartTime && dayjs(normalizedStartTime).tz(timezone).isValid()
7070
+ ? dayjs(normalizedStartTime).tz(timezone).startOf('day').toDate()
7071
+ : undefined;
7072
+ // Log current state before render
7073
+ React.useEffect(() => {
7074
+ console.log('[DateTimePicker] Current state before render:', {
7075
+ isISO,
7076
+ hour12,
7077
+ minute,
7078
+ meridiem,
7079
+ hour24,
7080
+ second,
7081
+ selectedDate,
7082
+ normalizedStartTime,
7083
+ timezone,
7084
+ });
7085
+ }, [
7086
+ isISO,
7087
+ hour12,
7088
+ minute,
7089
+ meridiem,
7090
+ hour24,
7091
+ second,
7092
+ selectedDate,
7093
+ normalizedStartTime,
7094
+ timezone,
7095
+ ]);
7096
+ // Compute display text from current state
7097
+ const displayText = React.useMemo(() => {
7098
+ if (!selectedDate)
7099
+ return null;
7100
+ const dateObj = dayjs.tz(selectedDate, timezone);
7101
+ if (!dateObj.isValid())
7102
+ return null;
7103
+ if (isISO) {
7104
+ // For ISO format, use hour24, minute, second
7105
+ if (hour24 === null || minute === null)
7106
+ return null;
7107
+ const dateTimeObj = dateObj
7108
+ .hour(hour24)
7109
+ .minute(minute)
7110
+ .second(second ?? 0);
7111
+ return dateTimeObj.format(showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm');
7112
+ }
7113
+ else {
7114
+ // For 12-hour format, use hour12, minute, meridiem
7115
+ if (hour12 === null || minute === null || meridiem === null)
7116
+ return null;
7117
+ // Convert to 24-hour format for dayjs
7118
+ let hour24Value = hour12;
7119
+ if (meridiem === 'am' && hour12 === 12)
7120
+ hour24Value = 0;
7121
+ else if (meridiem === 'pm' && hour12 < 12)
7122
+ hour24Value = hour12 + 12;
7123
+ const dateTimeObj = dateObj.hour(hour24Value).minute(minute).second(0);
7124
+ return dateTimeObj.format('YYYY-MM-DD hh:mm A');
7125
+ }
7126
+ }, [
7127
+ selectedDate,
7128
+ isISO,
7129
+ hour12,
7130
+ minute,
7131
+ meridiem,
7132
+ hour24,
7133
+ second,
7134
+ showSeconds,
7135
+ timezone,
7136
+ ]);
7137
+ const timezoneOffset = React.useMemo(() => {
7138
+ if (!selectedDate)
7139
+ return null;
7140
+ const dateObj = dayjs.tz(selectedDate, timezone);
7141
+ return dateObj.isValid() ? dateObj.format('Z') : null;
7142
+ }, [selectedDate, timezone]);
7143
+ return (jsxRuntime.jsxs(react.Flex, { direction: "column", gap: 4, p: 4, border: "1px solid", borderColor: "gray.200", borderRadius: "md", children: [jsxRuntime.jsx(DatePickerInput, { value: selectedDate || undefined, onChange: (date) => {
7144
+ if (date) {
7145
+ handleDateChange(date);
7146
+ }
7147
+ else {
7148
+ setSelectedDate('');
7149
+ onChange?.(undefined);
7150
+ }
7151
+ }, placeholder: "Select a date", dateFormat: "YYYY-MM-DD", displayFormat: "YYYY-MM-DD", labels: labels, timezone: timezone, minDate: effectiveMinDate, maxDate: maxDate, monthsToDisplay: 1, readOnly: true }), jsxRuntime.jsxs(react.Grid, { templateColumns: "1fr auto", alignItems: "center", gap: 4, children: [isISO ? (jsxRuntime.jsx(IsoTimePicker, { hour: hour24, setHour: setHour24, minute: minute, setMinute: setMinute, second: showSeconds ? second : null, setSecond: showSeconds ? setSecond : () => { }, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled })) : (jsxRuntime.jsx(TimePicker$1, { hour: hour12, setHour: setHour12, minute: minute, setMinute: setMinute, meridiem: meridiem, setMeridiem: setMeridiem, onChange: handleTimeChange, startTime: normalizedStartTime, selectedDate: selectedDate, timezone: timezone, portalled: portalled })), jsxRuntime.jsx(react.Button, { onClick: handleClear, size: "sm", variant: "outline", colorScheme: "red", children: jsxRuntime.jsx(react.Icon, { as: fa6.FaTrash }) })] }), displayText && (jsxRuntime.jsxs(react.Flex, { gap: 2, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: displayText }), timezoneOffset && (jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezoneOffset })), jsxRuntime.jsx(react.Text, { fontSize: "sm", color: { base: 'gray.600', _dark: 'gray.600' }, children: timezone })] }))] }));
6491
7152
  }
6492
7153
 
6493
7154
  dayjs.extend(utc);
@@ -6503,32 +7164,9 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
6503
7164
  const colLabel = formI18n.colLabel;
6504
7165
  const [open, setOpen] = React.useState(false);
6505
7166
  const selectedDate = watch(colLabel);
6506
- const displayDate = dayjs(selectedDate)
6507
- .tz(timezone)
6508
- .format(displayDateFormat);
6509
- React.useEffect(() => {
6510
- try {
6511
- if (selectedDate) {
6512
- // Parse the selectedDate as UTC or in a specific timezone to avoid +8 hour shift
6513
- // For example, parse as UTC:
6514
- const parsedDate = dayjs(selectedDate).tz(timezone);
6515
- if (!parsedDate.isValid())
6516
- return;
6517
- // Format according to dateFormat from schema
6518
- const formatted = parsedDate.format(dateFormat);
6519
- // Update the form value only if different to avoid loops
6520
- if (formatted !== selectedDate) {
6521
- setValue(colLabel, formatted, {
6522
- shouldValidate: true,
6523
- shouldDirty: true,
6524
- });
6525
- }
6526
- }
6527
- }
6528
- catch (e) {
6529
- console.error(e);
6530
- }
6531
- }, [selectedDate, dateFormat, colLabel, setValue]);
7167
+ const displayDate = selectedDate && dayjs(selectedDate).tz(timezone).isValid()
7168
+ ? dayjs(selectedDate).tz(timezone).format(displayDateFormat)
7169
+ : '';
6532
7170
  const dateTimePickerLabelsConfig = {
6533
7171
  monthNamesShort: dateTimePickerLabels?.monthNamesShort ?? [
6534
7172
  formI18n.translate.t(`common.month_1`, {
@@ -6601,12 +7239,22 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
6601
7239
  }),
6602
7240
  };
6603
7241
  const dateTimePickerContent = (jsxRuntime.jsx(DateTimePicker$1, { value: selectedDate, onChange: (date) => {
6604
- setValue(colLabel, dayjs(date).tz(timezone).format(dateFormat));
7242
+ if (!date || date === null || date === undefined) {
7243
+ setValue(colLabel, undefined);
7244
+ return;
7245
+ }
7246
+ const dateObj = dayjs(date).tz(timezone);
7247
+ if (dateObj.isValid()) {
7248
+ setValue(colLabel, dateObj.format(dateFormat));
7249
+ }
7250
+ else {
7251
+ setValue(colLabel, undefined);
7252
+ }
6605
7253
  }, timezone: timezone, labels: dateTimePickerLabelsConfig }));
6606
7254
  return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
6607
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
7255
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(react.Popover.Root, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, autoFocus: false, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
6608
7256
  setOpen(true);
6609
- }, justifyContent: 'start', children: [jsxRuntime.jsx(md.MdDateRange, {}), selectedDate !== undefined ? `${displayDate}` : ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
7257
+ }, justifyContent: 'start', children: [jsxRuntime.jsx(md.MdDateRange, {}), displayDate || ''] }) }), insideDialog ? (jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsx(react.Popover.Content, { width: "fit-content", minW: "450px", minH: "25rem", children: jsxRuntime.jsx(react.Popover.Body, { children: dateTimePickerContent }) }) }) }))] }) }));
6610
7258
  };
6611
7259
 
6612
7260
  const SchemaRenderer = ({ schema, prefix, column, }) => {
@@ -8041,6 +8689,7 @@ exports.CardHeader = CardHeader;
8041
8689
  exports.DataDisplay = DataDisplay;
8042
8690
  exports.DataTable = DataTable;
8043
8691
  exports.DataTableServer = DataTableServer;
8692
+ exports.DatePickerInput = DatePickerInput;
8044
8693
  exports.DefaultCardTitle = DefaultCardTitle;
8045
8694
  exports.DefaultForm = DefaultForm;
8046
8695
  exports.DefaultTable = DefaultTable;