@openg2p/registry-widgets 1.1.0-dev.14 → 1.1.0-dev.15

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.
@@ -371,6 +371,18 @@ export declare const panelSchema: {
371
371
  maxDate: {
372
372
  type: "string";
373
373
  };
374
+ minDateField: {
375
+ type: "string";
376
+ };
377
+ maxDateField: {
378
+ type: "string";
379
+ };
380
+ minDateMessage: {
381
+ type: "string";
382
+ };
383
+ maxDateMessage: {
384
+ type: "string";
385
+ };
374
386
  showCalendar: {
375
387
  type: "boolean";
376
388
  };
@@ -835,6 +847,18 @@ export declare const panelSchema: {
835
847
  maxDate: {
836
848
  type: "string";
837
849
  };
850
+ minDateField: {
851
+ type: "string";
852
+ };
853
+ maxDateField: {
854
+ type: "string";
855
+ };
856
+ minDateMessage: {
857
+ type: "string";
858
+ };
859
+ maxDateMessage: {
860
+ type: "string";
861
+ };
838
862
  showCalendar: {
839
863
  type: "boolean";
840
864
  };
@@ -1362,6 +1386,18 @@ export declare const sectionSchema: {
1362
1386
  maxDate: {
1363
1387
  type: "string";
1364
1388
  };
1389
+ minDateField: {
1390
+ type: "string";
1391
+ };
1392
+ maxDateField: {
1393
+ type: "string";
1394
+ };
1395
+ minDateMessage: {
1396
+ type: "string";
1397
+ };
1398
+ maxDateMessage: {
1399
+ type: "string";
1400
+ };
1365
1401
  showCalendar: {
1366
1402
  type: "boolean";
1367
1403
  };
@@ -1826,6 +1862,18 @@ export declare const sectionSchema: {
1826
1862
  maxDate: {
1827
1863
  type: "string";
1828
1864
  };
1865
+ minDateField: {
1866
+ type: "string";
1867
+ };
1868
+ maxDateField: {
1869
+ type: "string";
1870
+ };
1871
+ minDateMessage: {
1872
+ type: "string";
1873
+ };
1874
+ maxDateMessage: {
1875
+ type: "string";
1876
+ };
1829
1877
  showCalendar: {
1830
1878
  type: "boolean";
1831
1879
  };
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/components/SectionBuilder/schemas.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/components/SectionBuilder/schemas.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -175,6 +175,10 @@ interface WidgetOptions {
175
175
  condition?: WidgetCondition;
176
176
  minDate?: string;
177
177
  maxDate?: string;
178
+ minDateField?: string;
179
+ maxDateField?: string;
180
+ minDateMessage?: string;
181
+ maxDateMessage?: string;
178
182
  showCalendar?: boolean;
179
183
  [key: string]: any;
180
184
  }
@@ -978,6 +982,7 @@ declare const BooleanWidget: ({ config }: BooleanWidgetProps) => react_jsx_runti
978
982
  * - Configurable date format (DD/MM/YYYY, MM/DD/YYYY, YYYY-MM-DD, etc.)
979
983
  * - Default value (none / today)
980
984
  * - Date constraints (minDate, maxDate, any/past only/future only)
985
+ * - Relative bounds via minDateField / maxDateField (e.g. end date >= start date)
981
986
  * - Required vs optional
982
987
  * - Input method (date picker / manual / hybrid)
983
988
  * - Placeholder text
@@ -1000,6 +1005,10 @@ declare const BooleanWidget: ({ config }: BooleanWidgetProps) => react_jsx_runti
1000
1005
  * "widget-data-options": {
1001
1006
  * "minDate": "1900-01-01",
1002
1007
  * "maxDate": "today"
1008
+ * "minDateField": "register.start_date",
1009
+ * "minDateMessage": "Date of birth must be on or after 1900-01-01"
1010
+ * "maxDateField": "register.end_date",
1011
+ * "maxDateMessage": "Date of birth must be on or before today"
1003
1012
  * },
1004
1013
  * "widget-data-validation": {},
1005
1014
  * "widget-required": true,
package/dist/index.esm.js CHANGED
@@ -807,6 +807,40 @@ const parseDateFromFormat = (dateString, format) => {
807
807
  }
808
808
  return parseDate(dateString);
809
809
  };
810
+ /**
811
+ * Resolve a stored date value (ISO or parseable string) to YYYY-MM-DD for comparisons.
812
+ */
813
+ const resolveDateBoundFromFieldValue = (fieldValue) => {
814
+ if (fieldValue == null || fieldValue === '') {
815
+ return undefined;
816
+ }
817
+ const iso = formatDateToISO(fieldValue);
818
+ return iso || undefined;
819
+ };
820
+ /**
821
+ * Pick the stricter (later) minimum when combining static and field-based bounds.
822
+ */
823
+ const mergeMinDateBounds = (boundA, boundB) => {
824
+ if (!boundA) {
825
+ return boundB;
826
+ }
827
+ if (!boundB) {
828
+ return boundA;
829
+ }
830
+ return boundA > boundB ? boundA : boundB;
831
+ };
832
+ /**
833
+ * Pick the stricter (earlier) maximum when combining static and field-based bounds.
834
+ */
835
+ const mergeMaxDateBounds = (boundA, boundB) => {
836
+ if (!boundA) {
837
+ return boundB;
838
+ }
839
+ if (!boundB) {
840
+ return boundA;
841
+ }
842
+ return boundA < boundB ? boundA : boundB;
843
+ };
810
844
  /**
811
845
  * Get min date based on constraint type
812
846
  */
@@ -849,10 +883,7 @@ const getMaxDate = (constraint, maxDate) => {
849
883
  }
850
884
  return undefined;
851
885
  };
852
- /**
853
- * Validate date constraints
854
- */
855
- const validateDateConstraints = (date, minDate, maxDate, constraint) => {
886
+ const validateDateConstraints = (date, minDate, maxDate, constraint, messages) => {
856
887
  if (!date)
857
888
  return null;
858
889
  const dateObj = date instanceof Date ? date : parseDate(date);
@@ -871,15 +902,12 @@ const validateDateConstraints = (date, minDate, maxDate, constraint) => {
871
902
  return 'Date must be in the future';
872
903
  }
873
904
  }
874
- // Check minDate
875
- const effectiveMinDate = getMinDate(constraint, minDate);
876
- if (effectiveMinDate && dateISO < effectiveMinDate) {
877
- return `Date must be on or after ${effectiveMinDate}`;
905
+ // minDate / maxDate are effective bounds (static + field-based), resolved by the caller
906
+ if (minDate && dateISO < minDate) {
907
+ return messages?.minDateMessage ?? `Date must be on or after ${minDate}`;
878
908
  }
879
- // Check maxDate
880
- const effectiveMaxDate = getMaxDate(constraint, maxDate);
881
- if (effectiveMaxDate && dateISO > effectiveMaxDate) {
882
- return `Date must be on or before ${effectiveMaxDate}`;
909
+ if (maxDate && dateISO > maxDate) {
910
+ return messages?.maxDateMessage ?? `Date must be on or before ${maxDate}`;
883
911
  }
884
912
  return null;
885
913
  };
@@ -7647,42 +7675,76 @@ const BooleanWidget = ({ config }) => {
7647
7675
  };
7648
7676
 
7649
7677
  const DateInputWidget = ({ config }) => {
7650
- const { value, formattedValue, error, touched, isEnabled, onChange, onBlur, config: widgetConfig, } = useBaseWidget({ config });
7651
- const { translate, translateConfig } = useWidgetTranslation();
7678
+ const { value, error, touched, isEnabled, onChange, onBlur, setError, config: widgetConfig, } = useBaseWidget({ config });
7679
+ const formValues = useSelector((state) => state.widget.values);
7680
+ const { translateConfig } = useWidgetTranslation();
7652
7681
  const formatConfig = widgetConfig['widget-data-format'];
7653
7682
  const optionsConfig = widgetConfig['widget-data-options'];
7654
7683
  const dateFormat = formatConfig?.dateFormat || 'YYYY-MM-DD';
7655
- const inputMethod = formatConfig?.inputMethod || 'picker'; // Default to picker for better UX
7684
+ const inputMethod = formatConfig?.inputMethod || 'picker';
7656
7685
  const dateConstraint = formatConfig?.dateConstraint || 'any';
7657
7686
  const minDate = optionsConfig?.minDate;
7658
7687
  const maxDate = optionsConfig?.maxDate;
7688
+ const minDateField = optionsConfig?.minDateField;
7689
+ const maxDateField = optionsConfig?.maxDateField;
7690
+ const minDateMessage = optionsConfig?.minDateMessage
7691
+ ? translateConfig(optionsConfig.minDateMessage)
7692
+ : undefined;
7693
+ const maxDateMessage = optionsConfig?.maxDateMessage
7694
+ ? translateConfig(optionsConfig.maxDateMessage)
7695
+ : undefined;
7659
7696
  const defaultToToday = widgetConfig['widget-data-default'] === 'today';
7660
- // Track manual input value (for manual/hybrid modes)
7661
7697
  const [manualInputValue, setManualInputValue] = useState('');
7662
7698
  const [isFocused, setIsFocused] = useState(false);
7663
- // Initialize default value to today if configured
7699
+ const fieldMinDate = useMemo(() => {
7700
+ if (!minDateField) {
7701
+ return undefined;
7702
+ }
7703
+ return resolveDateBoundFromFieldValue(getValueByPath(formValues, minDateField));
7704
+ }, [formValues, minDateField]);
7705
+ const fieldMaxDate = useMemo(() => {
7706
+ if (!maxDateField) {
7707
+ return undefined;
7708
+ }
7709
+ return resolveDateBoundFromFieldValue(getValueByPath(formValues, maxDateField));
7710
+ }, [formValues, maxDateField]);
7711
+ const effectiveMinDate = useMemo(() => {
7712
+ const staticMin = getMinDate(dateConstraint, minDate);
7713
+ return mergeMinDateBounds(staticMin, fieldMinDate);
7714
+ }, [dateConstraint, minDate, fieldMinDate]);
7715
+ const effectiveMaxDate = useMemo(() => {
7716
+ const staticMax = getMaxDate(dateConstraint, maxDate);
7717
+ return mergeMaxDateBounds(staticMax, fieldMaxDate);
7718
+ }, [dateConstraint, maxDate, fieldMaxDate]);
7719
+ const constraintMessages = useMemo(() => ({ minDateMessage, maxDateMessage }), [minDateMessage, maxDateMessage]);
7720
+ const runDateConstraintValidation = useCallback((dateValue) => {
7721
+ if (!dateValue) {
7722
+ return null;
7723
+ }
7724
+ return validateDateConstraints(dateValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages);
7725
+ }, [effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages]);
7664
7726
  useEffect(() => {
7665
7727
  if (defaultToToday && (value === null || value === undefined || value === '')) {
7666
7728
  const todayISO = formatDateToISO(new Date());
7667
7729
  onChange(todayISO);
7668
7730
  }
7669
7731
  }, [defaultToToday, value, onChange]);
7670
- // Get effective min/max dates
7671
- const effectiveMinDate = useMemo(() => {
7672
- return getMinDate(dateConstraint, minDate);
7673
- }, [dateConstraint, minDate]);
7674
- const effectiveMaxDate = useMemo(() => {
7675
- return getMaxDate(dateConstraint, maxDate);
7676
- }, [dateConstraint, maxDate]);
7677
- // Convert ISO value to display format
7732
+ // Re-validate when a relative bound field changes (e.g. start date set after end date)
7733
+ useEffect(() => {
7734
+ if (!value) {
7735
+ return;
7736
+ }
7737
+ const constraintError = runDateConstraintValidation(value);
7738
+ if (constraintError) {
7739
+ setError([constraintError]);
7740
+ }
7741
+ }, [fieldMinDate, fieldMaxDate, value, runDateConstraintValidation, setError]);
7678
7742
  const getDisplayValue = useCallback(() => {
7679
- // For picker mode, always use YYYY-MM-DD
7680
7743
  if (inputMethod === 'picker') {
7681
7744
  if (!value)
7682
7745
  return '';
7683
7746
  return formatDateToISO(value);
7684
7747
  }
7685
- // For manual/hybrid modes, use custom format
7686
7748
  if (isFocused && manualInputValue) {
7687
7749
  return manualInputValue;
7688
7750
  }
@@ -7693,7 +7755,6 @@ const DateInputWidget = ({ config }) => {
7693
7755
  }
7694
7756
  return formatDateToString(value, dateFormat);
7695
7757
  }, [value, inputMethod, dateFormat, isFocused, manualInputValue]);
7696
- // Initialize manual input value
7697
7758
  useEffect(() => {
7698
7759
  if (!isFocused && value) {
7699
7760
  if (dateFormat === 'YYYY-MM-DD') {
@@ -7704,66 +7765,81 @@ const DateInputWidget = ({ config }) => {
7704
7765
  }
7705
7766
  }
7706
7767
  }, [value, dateFormat, isFocused]);
7707
- // Handle input change
7768
+ const applyConstraintError = useCallback((dateValue) => {
7769
+ const constraintError = runDateConstraintValidation(dateValue);
7770
+ setError(constraintError ? [constraintError] : []);
7771
+ }, [runDateConstraintValidation, setError]);
7708
7772
  const handleChange = useCallback((e) => {
7709
7773
  const inputValue = e.target.value;
7710
7774
  if (inputMethod === 'picker') {
7711
- // Picker mode: input is always YYYY-MM-DD
7712
7775
  if (inputValue) {
7713
7776
  const date = parseDate(inputValue);
7714
7777
  if (date) {
7715
- onChange(formatDateToISO(date));
7778
+ const iso = formatDateToISO(date);
7779
+ onChange(iso);
7780
+ applyConstraintError(iso);
7716
7781
  }
7717
7782
  else {
7718
7783
  onChange('');
7784
+ setError([]);
7719
7785
  }
7720
7786
  }
7721
7787
  else {
7722
7788
  onChange('');
7789
+ setError([]);
7723
7790
  }
7724
7791
  }
7725
7792
  else {
7726
- // Manual/hybrid mode: parse custom format
7727
7793
  setManualInputValue(inputValue);
7728
7794
  if (inputValue) {
7729
7795
  const date = parseDateFromFormat(inputValue, dateFormat);
7730
7796
  if (date) {
7731
- // Validate constraints
7732
- const constraintError = validateDateConstraints(date, minDate, maxDate, dateConstraint);
7733
- if (!constraintError) {
7734
- onChange(formatDateToISO(date));
7735
- }
7736
- else {
7737
- // Still update the value but validation will catch it
7738
- onChange(formatDateToISO(date));
7739
- }
7797
+ const iso = formatDateToISO(date);
7798
+ onChange(iso);
7799
+ applyConstraintError(iso);
7740
7800
  }
7741
7801
  }
7742
7802
  else {
7743
7803
  onChange('');
7804
+ setError([]);
7744
7805
  }
7745
7806
  }
7746
- }, [inputMethod, dateFormat, onChange, minDate, maxDate, dateConstraint]);
7747
- // Handle blur - validate and format
7807
+ }, [inputMethod, dateFormat, onChange, applyConstraintError, setError]);
7748
7808
  const handleBlur = useCallback(() => {
7749
7809
  setIsFocused(false);
7750
7810
  if (inputMethod !== 'picker' && manualInputValue) {
7751
7811
  const date = parseDateFromFormat(manualInputValue, dateFormat);
7752
7812
  if (date) {
7753
- // Format the value according to the format
7754
7813
  const formatted = formatDateToString(date, dateFormat);
7755
7814
  setManualInputValue(formatted);
7756
- onChange(formatDateToISO(date));
7815
+ const iso = formatDateToISO(date);
7816
+ onChange(iso);
7817
+ applyConstraintError(iso);
7757
7818
  }
7758
7819
  else {
7759
- // Invalid date, clear it
7760
7820
  setManualInputValue('');
7761
7821
  onChange('');
7822
+ setError([]);
7762
7823
  }
7763
7824
  }
7764
7825
  onBlur();
7765
- }, [inputMethod, manualInputValue, dateFormat, onChange, onBlur]);
7766
- // Handle focus
7826
+ if (value) {
7827
+ const constraintError = runDateConstraintValidation(value);
7828
+ if (constraintError) {
7829
+ setError([constraintError]);
7830
+ }
7831
+ }
7832
+ }, [
7833
+ inputMethod,
7834
+ manualInputValue,
7835
+ dateFormat,
7836
+ onChange,
7837
+ onBlur,
7838
+ applyConstraintError,
7839
+ value,
7840
+ runDateConstraintValidation,
7841
+ setError,
7842
+ ]);
7767
7843
  const handleFocus = useCallback(() => {
7768
7844
  setIsFocused(true);
7769
7845
  if (value) {
@@ -7775,15 +7851,15 @@ const DateInputWidget = ({ config }) => {
7775
7851
  }
7776
7852
  }
7777
7853
  }, [value, dateFormat]);
7778
- // Determine placeholder
7779
7854
  const placeholder = useMemo(() => {
7780
- const hasValue = getDisplayValue() && getDisplayValue().trim().length > 0;
7855
+ const display = getDisplayValue();
7856
+ const hasValue = display && display.trim().length > 0;
7781
7857
  const placeholderText = translateConfig(widgetConfig['widget-data-placeholder']);
7782
- return hasValue ? undefined : (placeholderText || dateFormat);
7858
+ return hasValue ? undefined : placeholderText || dateFormat;
7783
7859
  }, [getDisplayValue, widgetConfig, translateConfig, dateFormat]);
7784
- // Determine input type
7785
7860
  const inputType = inputMethod === 'picker' ? 'date' : 'text';
7786
- // For readonly mode, render as display text
7861
+ const showRequiredError = widgetConfig['widget-required'] && (!value || value === '');
7862
+ const showValidationError = touched && error.length > 0;
7787
7863
  if (widgetConfig['widget-readonly']) {
7788
7864
  const label = translateConfig(widgetConfig['widget-label']);
7789
7865
  let displayValue = '';
@@ -7800,9 +7876,9 @@ const DateInputWidget = ({ config }) => {
7800
7876
  }
7801
7877
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DateDisplayWidget flex flex-col sm:flex-row sm:items-start", children: [label && (jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] })), jsxRuntimeExports.jsx("div", { className: "flex-1", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
7802
7878
  }
7803
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && (jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" }))] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDate : undefined, max: inputMethod === 'picker' ? effectiveMaxDate : undefined, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${(touched && error.length > 0) || (widgetConfig['widget-required'] && (!value || value === ''))
7879
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("label", { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: translateConfig(widgetConfig['widget-label']), children: [translateConfig(widgetConfig['widget-label']), widgetConfig['widget-required'] && jsxRuntimeExports.jsx("span", { className: "text-red-500 ml-1", children: "*" })] }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsx("input", { type: inputType, value: getDisplayValue(), onChange: handleChange, onBlur: handleBlur, onFocus: handleFocus, disabled: !isEnabled || widgetConfig['widget-readonly'], placeholder: placeholder, min: inputMethod === 'picker' ? effectiveMinDate : undefined, max: inputMethod === 'picker' ? effectiveMaxDate : undefined, className: `w-full sm:w-[180px] max-w-full h-[30px] px-3 border shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${showValidationError || showRequiredError
7804
7880
  ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
7805
- : 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
7881
+ : 'border-gray-300'} ${!isEnabled || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: translateConfig(widgetConfig['widget-data-tooltip']) }), showValidationError && jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] })] })] }) }));
7806
7882
  };
7807
7883
 
7808
7884
  /**
@@ -8746,16 +8822,70 @@ const TableCellNumber = ({ config, value, onValueChange }) => {
8746
8822
  backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8747
8823
  } }));
8748
8824
  };
8749
- const TableCellDate = ({ config, value, onValueChange }) => {
8825
+ const TableCellDate = ({ config, value, rowValues, onValueChange }) => {
8826
+ const { translateConfig } = useWidgetTranslation();
8750
8827
  const isReadonly = config['widget-readonly'] || false;
8751
8828
  const placeholder = config['widget-data-placeholder'] || '';
8829
+ const optionsConfig = config['widget-data-options'];
8830
+ const formatConfig = config['widget-data-format'];
8831
+ const dateConstraint = formatConfig?.dateConstraint || 'any';
8832
+ const minDate = optionsConfig?.minDate;
8833
+ const maxDate = optionsConfig?.maxDate;
8834
+ const minDateField = optionsConfig?.minDateField;
8835
+ const maxDateField = optionsConfig?.maxDateField;
8836
+ const minDateMessage = optionsConfig?.minDateMessage
8837
+ ? translateConfig(optionsConfig.minDateMessage)
8838
+ : undefined;
8839
+ const maxDateMessage = optionsConfig?.maxDateMessage
8840
+ ? translateConfig(optionsConfig.maxDateMessage)
8841
+ : undefined;
8842
+ const [constraintError, setConstraintError] = useState(null);
8843
+ const resolveSiblingDate = (fieldRef) => {
8844
+ if (!fieldRef || !rowValues) {
8845
+ return undefined;
8846
+ }
8847
+ const raw = getValueByPath(rowValues, fieldRef) ?? rowValues[fieldRef];
8848
+ return resolveDateBoundFromFieldValue(raw);
8849
+ };
8850
+ const fieldMinDate = useMemo(() => resolveSiblingDate(minDateField), [minDateField, rowValues]);
8851
+ const fieldMaxDate = useMemo(() => resolveSiblingDate(maxDateField), [maxDateField, rowValues]);
8852
+ const effectiveMinDate = useMemo(() => {
8853
+ const staticMin = getMinDate(dateConstraint, minDate);
8854
+ return mergeMinDateBounds(staticMin, fieldMinDate);
8855
+ }, [dateConstraint, minDate, fieldMinDate]);
8856
+ const effectiveMaxDate = useMemo(() => {
8857
+ const staticMax = getMaxDate(dateConstraint, maxDate);
8858
+ return mergeMaxDateBounds(staticMax, fieldMaxDate);
8859
+ }, [dateConstraint, maxDate, fieldMaxDate]);
8860
+ const constraintMessages = useMemo(() => ({ minDateMessage, maxDateMessage }), [minDateMessage, maxDateMessage]);
8752
8861
  // input type="date" requires YYYY-MM-DD format
8753
8862
  const displayValue = value && typeof value === 'string' ? value.split('T')[0] : '';
8754
- return (jsxRuntimeExports.jsx("input", { type: "date", value: displayValue, onChange: (e) => onValueChange(e.target.value), disabled: isReadonly, placeholder: placeholder, className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''} table-cell-input`, style: {
8755
- borderRadius: '10px',
8756
- borderColor: 'var(--owt-widget-input-border, #C4C4C4)',
8757
- backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8758
- } }));
8863
+ useEffect(() => {
8864
+ if (!displayValue) {
8865
+ setConstraintError(null);
8866
+ return;
8867
+ }
8868
+ const error = validateDateConstraints(displayValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages);
8869
+ setConstraintError(error);
8870
+ }, [displayValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages]);
8871
+ const handleChange = (e) => {
8872
+ const nextValue = e.target.value;
8873
+ onValueChange(nextValue);
8874
+ if (!nextValue) {
8875
+ setConstraintError(null);
8876
+ return;
8877
+ }
8878
+ const error = validateDateConstraints(nextValue, effectiveMinDate, effectiveMaxDate, dateConstraint, constraintMessages);
8879
+ setConstraintError(error);
8880
+ };
8881
+ const hasError = Boolean(constraintError);
8882
+ return (jsxRuntimeExports.jsxs("div", { className: "w-full", children: [jsxRuntimeExports.jsx("input", { type: "date", value: displayValue, onChange: handleChange, disabled: isReadonly, placeholder: placeholder, min: effectiveMinDate, max: effectiveMaxDate, title: constraintError || translateConfig(config['widget-data-tooltip']), className: `w-full h-[28px] px-2 text-sm border focus:outline-none ${isReadonly ? 'cursor-not-allowed' : ''} ${hasError ? 'border-red-500' : ''} table-cell-input`, style: {
8883
+ borderRadius: '10px',
8884
+ borderColor: hasError
8885
+ ? 'var(--owt-color-error, #B91C1C)'
8886
+ : 'var(--owt-widget-input-border, #C4C4C4)',
8887
+ backgroundColor: isReadonly ? 'var(--owt-color-bg-alt, #F6F6F6)' : 'var(--owt-color-bg, #FFFFFF)',
8888
+ } }), hasError && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-xs mt-0.5 leading-tight", children: constraintError }))] }));
8759
8889
  };
8760
8890
  const TableWidget = ({ config }) => {
8761
8891
  const { value, error, touched, isEnabled, onChange, config: widgetConfig, } = useBaseWidget({ config });
@@ -9114,11 +9244,21 @@ const TableWidget = ({ config }) => {
9114
9244
  });
9115
9245
  }
9116
9246
  }, [isAdding, newRowData, columns, widgetConfig, rows.length, dispatch]);
9247
+ const getRowValuesForEdit = useCallback((rowIndex) => {
9248
+ if (editingState && editingState.rowIndex === rowIndex) {
9249
+ return editingState.currentValue ?? {};
9250
+ }
9251
+ if (isAdding && rowIndex === rows.length && newRowData) {
9252
+ return newRowData;
9253
+ }
9254
+ return rows[rowIndex] ?? {};
9255
+ }, [editingState, isAdding, rows, newRowData]);
9117
9256
  // Lightweight cell renderer for table cells (no labels, compact)
9118
9257
  const renderTableCell = useCallback((rowIndex, column, cellValue, isReadonly) => {
9119
9258
  const columnKey = column['column-key'];
9120
9259
  const widgetType = column.widget || 'text';
9121
9260
  const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${columnKey}`;
9261
+ const rowValues = getRowValuesForEdit(rowIndex);
9122
9262
  // Use lightweight cell config (no label, minimal styling)
9123
9263
  const cellConfig = {
9124
9264
  ...column,
@@ -9141,7 +9281,7 @@ const TableWidget = ({ config }) => {
9141
9281
  return jsxRuntimeExports.jsx(TableCellNumber, { config: cellConfig, value: cellValue, onValueChange: (newValue) => updateCellValue(columnKey, newValue, rowIndex) });
9142
9282
  }
9143
9283
  else if (widgetType === 'date') {
9144
- return jsxRuntimeExports.jsx(TableCellDate, { config: cellConfig, value: cellValue, onValueChange: (newValue) => updateCellValue(columnKey, newValue, rowIndex) });
9284
+ return jsxRuntimeExports.jsx(TableCellDate, { config: cellConfig, value: cellValue, rowValues: rowValues, onValueChange: (newValue) => updateCellValue(columnKey, newValue, rowIndex) });
9145
9285
  }
9146
9286
  // For other widget types, use WidgetRenderer but with compact styling
9147
9287
  return (jsxRuntimeExports.jsx("div", { className: "table-cell-widget", style: { margin: 0, padding: 0 }, children: jsxRuntimeExports.jsx(WidgetRenderer, { config: cellConfig, schemaData: {
@@ -9149,7 +9289,7 @@ const TableWidget = ({ config }) => {
9149
9289
  }, onValueChange: (widgetId, newValue) => {
9150
9290
  updateCellValue(columnKey, newValue, rowIndex);
9151
9291
  } }) }));
9152
- }, [widgetConfig, updateCellValue]);
9292
+ }, [widgetConfig, updateCellValue, getRowValuesForEdit]);
9153
9293
  // Render cell content (widget in edit mode, formatted value in view mode)
9154
9294
  const renderCell = useCallback((rowIndex, column, row) => {
9155
9295
  const columnKey = column['column-key'];