@openg2p/registry-widgets 1.1.6-dev.2 → 1.1.6-dev.4

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.esm.js CHANGED
@@ -1686,12 +1686,13 @@ function useWidgetTheme() {
1686
1686
  const WidgetContext = createContext({
1687
1687
  dataSourceRequestHandler: undefined,
1688
1688
  schemaData: undefined,
1689
+ hostContext: undefined,
1689
1690
  t: undefined,
1690
1691
  });
1691
1692
  const useWidgetContext = () => {
1692
1693
  return useContext(WidgetContext);
1693
1694
  };
1694
- const WidgetProvider = ({ store, dataSourceRequestHandler, schemaData, t, theme, children, }) => {
1695
+ const WidgetProvider = ({ store, dataSourceRequestHandler, schemaData, hostContext, t, theme, children, }) => {
1695
1696
  const widgetStore = useMemo(() => store || createWidgetStore(), [store]);
1696
1697
  const eventBus = useMemo(() => new WidgetEventBus(), []);
1697
1698
  const resolvedTheme = useMemo(() => resolveTheme(theme), [theme]);
@@ -1699,8 +1700,9 @@ const WidgetProvider = ({ store, dataSourceRequestHandler, schemaData, t, theme,
1699
1700
  const contextValue = useMemo(() => ({
1700
1701
  dataSourceRequestHandler,
1701
1702
  schemaData,
1703
+ hostContext,
1702
1704
  t,
1703
- }), [dataSourceRequestHandler, schemaData, t]);
1705
+ }), [dataSourceRequestHandler, schemaData, hostContext, t]);
1704
1706
  useEffect(() => {
1705
1707
  if (!dataSourceRequestHandler) {
1706
1708
  console.warn('[WidgetProvider] dataSourceRequestHandler is not provided. ' +
@@ -1848,7 +1850,7 @@ const useBaseWidget = (options) => {
1848
1850
  // This prevents data disappearance when switching to Edit mode and components
1849
1851
  // incorrectly clear values before options load or if handler is temporarily missing.
1850
1852
  if (newValue === '' || newValue === null || newValue === undefined) {
1851
- const allowEmptyClear = config.widget === 'register-lookup';
1853
+ const allowEmptyClear = config.widget === 'register-lookup' || config.widget === 'parent-lookup';
1852
1854
  if (!allowEmptyClear) {
1853
1855
  if (loadingRef.current) {
1854
1856
  console.warn(`[useBaseWidget] Ignoring empty value for ${widgetId} because data source is loading`);
@@ -3049,11 +3051,15 @@ const useCrViewData = (mode, currentSchemaData, storeValues) => useMemo(() => {
3049
3051
  return null;
3050
3052
  const dataSource = { ...storeValues, ...currentSchemaData };
3051
3053
  const recordPath = Object.keys(dataSource)[0];
3054
+ const records = dataSource[recordPath]?.records;
3055
+ const auditPath = Array.isArray(records) && records.length > 0
3056
+ ? `${recordPath}.records.${records.length - 1}`
3057
+ : recordPath;
3052
3058
  return {
3053
- createdBy: getValueByPath(dataSource, `${recordPath}.created_by`),
3054
- createdDate: getValueByPath(dataSource, `${recordPath}.created_at`),
3055
- approvedBy: getValueByPath(dataSource, `${recordPath}.last_approved_by`),
3056
- approvedDate: getValueByPath(dataSource, `${recordPath}.last_approved_at`),
3059
+ createdBy: getValueByPath(dataSource, `${auditPath}.created_by`),
3060
+ createdDate: getValueByPath(dataSource, `${auditPath}.created_at`),
3061
+ approvedBy: getValueByPath(dataSource, `${auditPath}.last_approved_by`),
3062
+ approvedDate: getValueByPath(dataSource, `${auditPath}.last_approved_at`),
3057
3063
  };
3058
3064
  }, [mode, currentSchemaData, storeValues]);
3059
3065
 
@@ -4271,6 +4277,7 @@ const SectionsContainer = ({ sections, dataSourceRequestHandler: propDataSourceR
4271
4277
  }
4272
4278
  return results;
4273
4279
  },
4280
+ hasUnsavedChanges: () => Object.values(sectionDirtyMapRef.current).some(Boolean),
4274
4281
  };
4275
4282
  }, [store, dispatch, safeSections]);
4276
4283
  useEffect(() => {
@@ -4382,6 +4389,8 @@ const WIDGET_TYPES = [
4382
4389
  'display',
4383
4390
  'profile',
4384
4391
  'geo-hierarchy',
4392
+ 'register-lookup',
4393
+ 'parent-lookup',
4385
4394
  ];
4386
4395
  const ORIENTATIONS = ['horizontal', 'vertical'];
4387
4396
  const CONDITION_OPERATORS = [
@@ -7005,7 +7014,18 @@ const SelectWidget = ({ config }) => {
7005
7014
  : (value != null && value !== '' ? tSchema(t, String(value)) : '-');
7006
7015
  return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] SelectDisplayWidget 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 }) })] }));
7007
7016
  }
7008
- return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: tSchema(t, widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("select", { value: value || '', onChange: (e) => onChange(e.target.value === '' ? undefined : e.target.value), onBlur: onBlur, disabled: !isEnabled || loading || widgetConfig['widget-readonly'], 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 === ''))
7017
+ return (jsxRuntimeExports.jsx("div", { className: "mb-[10px]", children: jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: tSchema(t, widgetConfig['widget-label']), required: isRequired }), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntimeExports.jsxs("select", {
7018
+ // value={value || ''}
7019
+ // onChange={(e) => onChange(e.target.value === '' ? undefined : e.target.value)}
7020
+ value: value === undefined || value === null ? '' : String(value), onChange: (e) => {
7021
+ const rawValue = e.target.value;
7022
+ if (rawValue === '') {
7023
+ onChange(undefined);
7024
+ return;
7025
+ }
7026
+ const selectedOption = dataSourceOptions.find((option) => String(option.value) === rawValue);
7027
+ onChange(selectedOption ? selectedOption.value : rawValue);
7028
+ }, onBlur: onBlur, disabled: !isEnabled || loading || widgetConfig['widget-readonly'], 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 === ''))
7009
7029
  ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
7010
7030
  : 'border-gray-300'} ${!isEnabled || loading || widgetConfig['widget-readonly'] ? 'bg-gray-100 cursor-not-allowed' : 'bg-white'}`, style: { borderRadius: '10px' }, title: tSchema(t, widgetConfig['widget-data-tooltip']), children: [jsxRuntimeExports.jsx("option", { value: "", children: t?.('common.select') }), dataSourceOptions.map((option) => (jsxRuntimeExports.jsx("option", { value: option.value, children: tSchema(t, option.label) }, option.value)))] }), loading && (jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 mt-1", children: t?.('common.loadingOptions') })), touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] }) }));
7011
7031
  };
@@ -7392,18 +7412,46 @@ const TableWidget = ({ config }) => {
7392
7412
  const [originalRows, setOriginalRows] = useState(null);
7393
7413
  const isSectionEditMode = !isReadonly && operations.edit;
7394
7414
  const isAnyRowEditing = editingState !== null || isAdding;
7415
+ const resolveEditingRowData = useCallback(() => {
7416
+ if (!editingState)
7417
+ return null;
7418
+ const row = { ...editingState.currentValue };
7419
+ columns.forEach((col) => {
7420
+ const key = col['column-key'];
7421
+ const cellWidgetId = `${widgetConfig['widget-id']}-row-${editingState.rowIndex}-col-${key}`;
7422
+ if (storeValues[cellWidgetId] !== undefined) {
7423
+ row[key] = storeValues[cellWidgetId];
7424
+ }
7425
+ });
7426
+ return row;
7427
+ }, [editingState, columns, widgetConfig, storeValues]);
7428
+ const resolveNewRowData = useCallback(() => {
7429
+ if (!isAdding || !newRowData)
7430
+ return null;
7431
+ const row = { ...newRowData };
7432
+ columns.forEach((col) => {
7433
+ const key = col['column-key'];
7434
+ const cellWidgetId = `${widgetConfig['widget-id']}-row-${rows.length}-col-${key}`;
7435
+ if (storeValues[cellWidgetId] !== undefined) {
7436
+ row[key] = storeValues[cellWidgetId];
7437
+ }
7438
+ });
7439
+ return row;
7440
+ }, [isAdding, newRowData, columns, widgetConfig, rows.length, storeValues]);
7395
7441
  const canSaveEditingRow = useMemo(() => {
7396
- if (!editingState) {
7442
+ const rowData = resolveEditingRowData();
7443
+ if (!rowData) {
7397
7444
  return false;
7398
7445
  }
7399
- return isTableRowDataValid(editingState.currentValue, columns, isReadonly, resolveSchemaLabel);
7400
- }, [editingState, columns, isReadonly, resolveSchemaLabel]);
7446
+ return isTableRowDataValid(rowData, columns, isReadonly, resolveSchemaLabel);
7447
+ }, [resolveEditingRowData, columns, isReadonly, resolveSchemaLabel]);
7401
7448
  const canSaveNewRow = useMemo(() => {
7402
- if (!isAdding || !newRowData) {
7449
+ const rowData = resolveNewRowData();
7450
+ if (!rowData) {
7403
7451
  return false;
7404
7452
  }
7405
- return isTableRowDataValid(newRowData, columns, isReadonly, resolveSchemaLabel);
7406
- }, [isAdding, newRowData, columns, isReadonly, resolveSchemaLabel]);
7453
+ return isTableRowDataValid(rowData, columns, isReadonly, resolveSchemaLabel);
7454
+ }, [resolveNewRowData, columns, isReadonly, resolveSchemaLabel]);
7407
7455
  const showConfirmation = useCallback((message, onConfirm, onCancel) => {
7408
7456
  setConfirmationState({
7409
7457
  show: true,
@@ -7462,29 +7510,41 @@ const TableWidget = ({ config }) => {
7462
7510
  }, [isAnyRowEditing, rows, showConfirmation, cancelEdit, t]);
7463
7511
  const updateCellValue = useCallback((columnKey, newValue, rowIndex) => {
7464
7512
  if (editingState && rowIndex !== undefined) {
7465
- setEditingState({
7466
- ...editingState,
7467
- currentValue: {
7468
- ...editingState.currentValue,
7469
- [columnKey]: newValue,
7470
- },
7513
+ setEditingState((prev) => {
7514
+ if (!prev || prev.rowIndex !== rowIndex)
7515
+ return prev;
7516
+ return {
7517
+ ...prev,
7518
+ currentValue: {
7519
+ ...prev.currentValue,
7520
+ [columnKey]: newValue,
7521
+ },
7522
+ };
7471
7523
  });
7472
7524
  const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${columnKey}`;
7473
7525
  dispatch(setValue({ widgetId: cellWidgetId, value: newValue }));
7474
7526
  }
7475
- else if (isAdding && newRowData) {
7476
- setNewRowData({
7477
- ...newRowData,
7478
- [columnKey]: newValue,
7527
+ else if (isAdding) {
7528
+ setNewRowData((prev) => {
7529
+ if (!prev)
7530
+ return prev;
7531
+ return {
7532
+ ...prev,
7533
+ [columnKey]: newValue,
7534
+ };
7479
7535
  });
7536
+ if (rowIndex !== undefined) {
7537
+ const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${columnKey}`;
7538
+ dispatch(setValue({ widgetId: cellWidgetId, value: newValue }));
7539
+ }
7480
7540
  }
7481
- }, [editingState, isAdding, newRowData, widgetConfig, dispatch]);
7541
+ }, [editingState, isAdding, widgetConfig, dispatch]);
7482
7542
  const saveEdit = useCallback(async () => {
7483
- if (!editingState)
7543
+ const rowData = resolveEditingRowData();
7544
+ if (!editingState || !rowData)
7484
7545
  return;
7485
7546
  if (!canSaveEditingRow)
7486
7547
  return;
7487
- const rowData = editingState.currentValue;
7488
7548
  const rowIndex = editingState.rowIndex;
7489
7549
  setLoadingRowIndex(rowIndex);
7490
7550
  try {
@@ -7543,7 +7603,7 @@ const TableWidget = ({ config }) => {
7543
7603
  finally {
7544
7604
  setLoadingRowIndex(null);
7545
7605
  }
7546
- }, [editingState, canSaveEditingRow, rows, onChange, dataSourceRequestHandler, apiConfig, t, isSectionEditMode, originalRows, columns, widgetConfig, dispatch]);
7606
+ }, [editingState, canSaveEditingRow, resolveEditingRowData, rows, onChange, dataSourceRequestHandler, apiConfig, t, isSectionEditMode, originalRows, columns, widgetConfig, dispatch]);
7547
7607
  const startAdd = useCallback(() => {
7548
7608
  if (isAnyRowEditing) {
7549
7609
  cancelEdit();
@@ -7556,23 +7616,26 @@ const TableWidget = ({ config }) => {
7556
7616
  setNewRowData(emptyRow);
7557
7617
  }, [isAnyRowEditing, columns, cancelEdit]);
7558
7618
  const saveAdd = useCallback(async () => {
7559
- if (!isAdding || !newRowData)
7619
+ const rowData = resolveNewRowData();
7620
+ if (!isAdding || !rowData)
7560
7621
  return;
7561
7622
  if (!canSaveNewRow)
7562
7623
  return;
7563
- setLoadingRowIndex(-1); // Use -1 to indicate new row
7624
+ setLoadingRowIndex(-1);
7564
7625
  try {
7565
- let savedRow = { ...newRowData };
7566
- // TODO: Update to use dataSourceRequestHandler pattern
7626
+ let savedRow = { ...rowData };
7567
7627
  if (dataSourceRequestHandler && apiConfig.add) {
7568
7628
  console.warn('[TableWidget] API add operations require migration to dataSourceRequestHandler pattern');
7569
- const response = newRowData;
7570
- if (response && typeof response === 'object') {
7571
- savedRow = { ...savedRow, ...response };
7629
+ if (rowData && typeof rowData === 'object') {
7630
+ savedRow = { ...savedRow, ...rowData };
7572
7631
  }
7573
7632
  }
7574
7633
  savedRow = { ...savedRow, edit_action: 'ADD' };
7575
7634
  onChange([...rows, savedRow]);
7635
+ columns.forEach((col) => {
7636
+ const cellWidgetId = `${widgetConfig['widget-id']}-row-${rows.length}-col-${col['column-key']}`;
7637
+ dispatch(resetWidget(cellWidgetId));
7638
+ });
7576
7639
  setIsAdding(false);
7577
7640
  setNewRowData(null);
7578
7641
  }
@@ -7583,7 +7646,7 @@ const TableWidget = ({ config }) => {
7583
7646
  finally {
7584
7647
  setLoadingRowIndex(null);
7585
7648
  }
7586
- }, [isAdding, newRowData, canSaveNewRow, rows, onChange, dataSourceRequestHandler, apiConfig, t, isSectionEditMode]);
7649
+ }, [isAdding, resolveNewRowData, canSaveNewRow, rows, onChange, dataSourceRequestHandler, apiConfig, t, columns, widgetConfig, dispatch]);
7587
7650
  const deleteRow = useCallback(async (rowIndex) => {
7588
7651
  if (isAnyRowEditing) {
7589
7652
  showConfirmation(t?.('table.unsavedChanges') || 'You have unsaved changes. Do you want to discard them?', () => {
@@ -7634,6 +7697,11 @@ const TableWidget = ({ config }) => {
7634
7697
  return editingState.currentValue[columnKey];
7635
7698
  }
7636
7699
  if (isAdding && rowIndex === rows.length) {
7700
+ const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${columnKey}`;
7701
+ const storeValue = storeValues[cellWidgetId];
7702
+ if (storeValue !== undefined) {
7703
+ return storeValue;
7704
+ }
7637
7705
  return newRowData?.[columnKey];
7638
7706
  }
7639
7707
  return rows[rowIndex]?.[columnKey];
@@ -7648,6 +7716,9 @@ const TableWidget = ({ config }) => {
7648
7716
  if (widgetType === 'select') {
7649
7717
  return null; // Will be handled by SelectDisplayValue component
7650
7718
  }
7719
+ if (widgetType === 'parent-lookup') {
7720
+ return null;
7721
+ }
7651
7722
  if (column['widget-data-format']) {
7652
7723
  return formatValue(cellValue, column['widget-data-format'], column.widget);
7653
7724
  }
@@ -7664,7 +7735,14 @@ const TableWidget = ({ config }) => {
7664
7735
  setOriginalRows(null);
7665
7736
  }
7666
7737
  }, [isSectionEditMode, rows, originalRows]);
7738
+ const editSessionKey = editingState
7739
+ ? `edit-${editingState.rowIndex}`
7740
+ : isAdding
7741
+ ? `add-${rows.length}`
7742
+ : null;
7667
7743
  useEffect(() => {
7744
+ if (!editSessionKey)
7745
+ return;
7668
7746
  if (editingState) {
7669
7747
  columns.forEach((col) => {
7670
7748
  const columnKey = col['column-key'];
@@ -7673,9 +7751,8 @@ const TableWidget = ({ config }) => {
7673
7751
  const defaultValue = cellValue !== undefined ? cellValue : (col['widget-data-default'] ?? '');
7674
7752
  dispatch(setValue({ widgetId: cellWidgetId, value: defaultValue }));
7675
7753
  });
7754
+ return;
7676
7755
  }
7677
- }, [editingState, columns, widgetConfig, dispatch]);
7678
- useEffect(() => {
7679
7756
  if (isAdding && newRowData) {
7680
7757
  columns.forEach((col) => {
7681
7758
  const columnKey = col['column-key'];
@@ -7685,7 +7762,9 @@ const TableWidget = ({ config }) => {
7685
7762
  dispatch(setValue({ widgetId: cellWidgetId, value: defaultValue }));
7686
7763
  });
7687
7764
  }
7688
- }, [isAdding, newRowData, columns, widgetConfig, rows.length, dispatch]);
7765
+ // Seed only when add/edit session starts — not on every cell change.
7766
+ // eslint-disable-next-line react-hooks/exhaustive-deps
7767
+ }, [editSessionKey]);
7689
7768
  const getRowValuesForEdit = useCallback((rowIndex) => {
7690
7769
  if (editingState && editingState.rowIndex === rowIndex) {
7691
7770
  return editingState.currentValue ?? {};
@@ -7765,6 +7844,20 @@ const TableWidget = ({ config }) => {
7765
7844
  };
7766
7845
  return (jsxRuntimeExports.jsx("div", { className: "text-sm", style: getCellStyle(), children: jsxRuntimeExports.jsx(SelectDisplayValue$1, { config: cellConfig, value: cellValue }) }));
7767
7846
  }
7847
+ if (widgetType === 'parent-lookup' && displayValue === null) {
7848
+ const cellWidgetId = `${widgetConfig['widget-id']}-row-${rowIndex}-col-${columnKey}`;
7849
+ const cellConfig = {
7850
+ ...column,
7851
+ 'widget-id': cellWidgetId,
7852
+ 'widget-label': '',
7853
+ 'widget-readonly': true,
7854
+ 'widget-data-path': undefined,
7855
+ 'widget-data-default': cellValue !== undefined ? cellValue : '',
7856
+ };
7857
+ return (jsxRuntimeExports.jsx("div", { className: "text-sm table-cell-widget", style: getCellStyle(), children: jsxRuntimeExports.jsx(WidgetRenderer, { config: cellConfig, schemaData: {
7858
+ [cellWidgetId]: cellValue !== undefined ? cellValue : '',
7859
+ } }) }));
7860
+ }
7768
7861
  return (jsxRuntimeExports.jsx("div", { className: "text-sm", style: getCellStyle(), children: displayValue }));
7769
7862
  }
7770
7863
  }, [isRowEditing, getCellValue, getDisplayValue, renderTableCell]);
@@ -7860,7 +7953,7 @@ const TableWidget = ({ config }) => {
7860
7953
  border: '1px solid var(--owt-btn-primary-border, #F07B1A)',
7861
7954
  backgroundColor: 'var(--owt-color-primary, #F5BB1A)',
7862
7955
  color: 'var(--owt-color-bg, #FFFFFF)',
7863
- }, children: t?.('table.addRecord') || 'Add New Record' }) })), jsxRuntimeExports.jsx("div", { className: "overflow-x-auto border", style: { borderRadius: 'var(--owt-widget-table-border-radius, 15px)', borderColor: 'var(--owt-widget-table-border-color, #C4C4C4)' }, children: jsxRuntimeExports.jsxs("table", { className: "min-w-full", style: { borderCollapse: 'separate', borderSpacing: 0 }, children: [jsxRuntimeExports.jsx("thead", { style: { backgroundColor: 'var(--owt-widget-table-header-bg, #F6F6F6)' }, children: jsxRuntimeExports.jsxs("tr", { style: { borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)' }, children: [columns.map((col) => (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: tSchema(t, col['widget-label']) }, col['column-key']))), ((operations.edit || operations.remove) && !isReadonly) || isAnyRowEditing || isSectionEditMode ? (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: t?.('common.actions') || 'Actions' })) : null] }) }), jsxRuntimeExports.jsxs("tbody", { style: { backgroundColor: 'var(--owt-widget-table-body-bg, #FFFFFF)' }, children: [rows.length === 0 && !isAdding && (jsxRuntimeExports.jsx("tr", { children: jsxRuntimeExports.jsxs("td", { colSpan: columns.length + (((operations.edit || operations.remove) && !isReadonly) || isSectionEditMode ? 1 : 0), className: "px-4 py-6 text-center text-sm", style: { color: 'var(--owt-widget-table-empty-color, #727474)' }, children: [t?.('table.noData') || 'No records available.', operations.add && !isReadonly && ` ${t?.('table.clickToAdd') || 'Click "Add New Record" to add one.'}`] }) })), rows.map((row, rowIndex) => {
7956
+ }, children: t?.('table.addRecord') || 'Add New Record' }) })), jsxRuntimeExports.jsx("div", { className: "overflow-x-auto border", style: { borderRadius: 'var(--owt-widget-table-border-radius, 15px)', borderColor: 'var(--owt-widget-table-border-color, #C4C4C4)' }, children: jsxRuntimeExports.jsxs("table", { className: "min-w-full", style: { borderCollapse: 'separate', borderSpacing: 0 }, children: [jsxRuntimeExports.jsx("thead", { style: { backgroundColor: 'var(--owt-widget-table-header-bg, #F6F6F6)' }, children: jsxRuntimeExports.jsxs("tr", { style: { borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)' }, children: [columns.map((col) => (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: tSchema(t, col['column-label'] || col['widget-label'] || col['column-key']) }, col['column-key']))), ((operations.edit || operations.remove) && !isReadonly) || isAnyRowEditing || isSectionEditMode ? (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: t?.('common.actions') || 'Actions' })) : null] }) }), jsxRuntimeExports.jsxs("tbody", { style: { backgroundColor: 'var(--owt-widget-table-body-bg, #FFFFFF)' }, children: [rows.length === 0 && !isAdding && (jsxRuntimeExports.jsx("tr", { children: jsxRuntimeExports.jsxs("td", { colSpan: columns.length + (((operations.edit || operations.remove) && !isReadonly) || isSectionEditMode ? 1 : 0), className: "px-4 py-6 text-center text-sm", style: { color: 'var(--owt-widget-table-empty-color, #727474)' }, children: [t?.('table.noData') || 'No records available.', operations.add && !isReadonly && ` ${t?.('table.clickToAdd') || 'Click "Add New Record" to add one.'}`] }) })), rows.map((row, rowIndex) => {
7864
7957
  const isEditing = isRowEditing(rowIndex);
7865
7958
  const isLoading = loadingRowIndex === rowIndex;
7866
7959
  return (jsxRuntimeExports.jsxs("tr", { className: `${isLoading ? 'opacity-50' : ''}${isEditing ? ' table-row-editing' : ''}`, style: {
@@ -7982,7 +8075,7 @@ const DialogTableField = memo(function DialogTableField({ col, cellWidgetId, dia
7982
8075
  widget: widgetType,
7983
8076
  'widget-type': col['widget-type'] || 'input',
7984
8077
  'widget-id': cellWidgetId,
7985
- 'widget-label': col['widget-label'],
8078
+ 'widget-label': col['widget-label'] || col['column-label'] || col['column-key'] || '',
7986
8079
  'widget-readonly': isReadonly || col['widget-readonly'] === true,
7987
8080
  'widget-data-path': undefined,
7988
8081
  'widget-data-default': col['widget-data-default'],
@@ -8227,6 +8320,8 @@ const DialogTableWidget = ({ config }) => {
8227
8320
  return '-';
8228
8321
  if (widgetType === 'select')
8229
8322
  return null;
8323
+ if (widgetType === 'parent-lookup')
8324
+ return null;
8230
8325
  if (column['widget-data-format'])
8231
8326
  return formatValue(cellValue, column['widget-data-format'], column.widget);
8232
8327
  return String(cellValue);
@@ -8280,7 +8375,7 @@ const DialogTableWidget = ({ config }) => {
8280
8375
  }, children: t?.('table.addRecord') || 'Add New Record' }) })), jsxRuntimeExports.jsx("div", { className: "overflow-x-auto border", style: {
8281
8376
  borderRadius: 'var(--owt-widget-table-border-radius, 15px)',
8282
8377
  borderColor: 'var(--owt-widget-table-border-color, #C4C4C4)',
8283
- }, children: jsxRuntimeExports.jsxs("table", { className: "min-w-full", style: { borderCollapse: 'separate', borderSpacing: 0 }, children: [jsxRuntimeExports.jsx("thead", { style: { backgroundColor: 'var(--owt-widget-table-header-bg, #F6F6F6)' }, children: jsxRuntimeExports.jsxs("tr", { style: { borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)' }, children: [visibleColumns.map((col) => (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: tSchema(t, col['widget-label']) }, col['column-key']))), ((operations.edit || operations.remove) && !isReadonly) && (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: t?.('common.actions') || 'Actions' }))] }) }), jsxRuntimeExports.jsxs("tbody", { style: { backgroundColor: 'var(--owt-widget-table-body-bg, #FFFFFF)' }, children: [rows.length === 0 && (jsxRuntimeExports.jsx("tr", { children: jsxRuntimeExports.jsxs("td", { colSpan: visibleColumns.length + (((operations.edit || operations.remove) && !isReadonly) ? 1 : 0), className: "px-4 py-6 text-center text-sm", style: { color: 'var(--owt-widget-table-empty-color, #727474)' }, children: [t?.('table.noData') || 'No records available.', operations.add && !isReadonly && ` ${t?.('table.clickToAdd') || 'Click "Add New Record" to add one.'}`] }) })), rows.map((row, rowIndex) => {
8378
+ }, children: jsxRuntimeExports.jsxs("table", { className: "min-w-full", style: { borderCollapse: 'separate', borderSpacing: 0 }, children: [jsxRuntimeExports.jsx("thead", { style: { backgroundColor: 'var(--owt-widget-table-header-bg, #F6F6F6)' }, children: jsxRuntimeExports.jsxs("tr", { style: { borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)' }, children: [visibleColumns.map((col) => (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: tSchema(t, col['column-label'] || col['widget-label'] || col['column-key']) }, col['column-key']))), ((operations.edit || operations.remove) && !isReadonly) && (jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wider", style: { color: 'var(--owt-widget-table-header-color, #727474)' }, children: t?.('common.actions') || 'Actions' }))] }) }), jsxRuntimeExports.jsxs("tbody", { style: { backgroundColor: 'var(--owt-widget-table-body-bg, #FFFFFF)' }, children: [rows.length === 0 && (jsxRuntimeExports.jsx("tr", { children: jsxRuntimeExports.jsxs("td", { colSpan: visibleColumns.length + (((operations.edit || operations.remove) && !isReadonly) ? 1 : 0), className: "px-4 py-6 text-center text-sm", style: { color: 'var(--owt-widget-table-empty-color, #727474)' }, children: [t?.('table.noData') || 'No records available.', operations.add && !isReadonly && ` ${t?.('table.clickToAdd') || 'Click "Add New Record" to add one.'}`] }) })), rows.map((row, rowIndex) => {
8284
8379
  const cellStyle = getRowCellStyle(row?.edit_action);
8285
8380
  return (jsxRuntimeExports.jsxs("tr", { style: {
8286
8381
  borderBottom: '1px solid var(--owt-widget-table-row-divider, #E4E4E4)',
@@ -8301,6 +8396,18 @@ const DialogTableWidget = ({ config }) => {
8301
8396
  };
8302
8397
  return (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: jsxRuntimeExports.jsx("div", { className: "text-sm", style: cellStyle, children: jsxRuntimeExports.jsx(SelectDisplayValue, { config: displayConfig, value: row?.[key] }) }) }, key));
8303
8398
  }
8399
+ if (widgetType === 'parent-lookup' && displayValue === null) {
8400
+ const cellWidgetId = `${widgetConfig['widget-id']}-view-row-${rowIndex}-col-${key}`;
8401
+ const displayConfig = {
8402
+ ...col,
8403
+ 'widget-id': cellWidgetId,
8404
+ 'widget-label': '',
8405
+ 'widget-readonly': true,
8406
+ 'widget-data-path': undefined,
8407
+ 'widget-data-default': row?.[key] ?? '',
8408
+ };
8409
+ return (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: jsxRuntimeExports.jsx("div", { className: "text-sm table-cell-widget", style: cellStyle, children: jsxRuntimeExports.jsx(WidgetRenderer, { config: displayConfig, schemaData: { [cellWidgetId]: row?.[key] ?? '' } }) }) }, key));
8410
+ }
8304
8411
  return (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", children: jsxRuntimeExports.jsx("div", { className: "text-sm", style: cellStyle, children: displayValue }) }, key));
8305
8412
  }), ((operations.edit || operations.remove) && !isReadonly) && (jsxRuntimeExports.jsx("td", { className: "px-4 py-3 whitespace-nowrap", style: { minWidth: '120px' }, children: jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [operations.edit && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => openEditDialog(rowIndex), disabled: !isEnabled, className: "px-3 py-1 text-xs disabled:opacity-50 disabled:cursor-not-allowed", style: {
8306
8413
  borderRadius: 'var(--owt-btn-border-radius, 10px)',
@@ -9875,7 +9982,7 @@ const normalizeDisplayFields = (row) => {
9875
9982
  value: f.value !== null && f.value !== undefined ? String(f.value) : '-',
9876
9983
  }));
9877
9984
  };
9878
- const parsePagination = (pagination, rowCount, size, fallbackPage = 1) => {
9985
+ const parsePagination$1 = (pagination, rowCount, size, fallbackPage = 1) => {
9879
9986
  const totalItems = typeof pagination.number_of_items === 'number' ? pagination.number_of_items : rowCount;
9880
9987
  const totalPages = typeof pagination.number_of_pages === 'number'
9881
9988
  ? Math.max(1, pagination.number_of_pages)
@@ -10026,7 +10133,7 @@ const RegisterLookupWidget = ({ config }) => {
10026
10133
  const match = rows.find((row) => String(row.internal_record_id ?? '').trim() === target);
10027
10134
  if (match)
10028
10135
  return match;
10029
- totalPages = parsePagination(pagination, rows.length, hydratePageSize).totalPages;
10136
+ totalPages = parsePagination$1(pagination, rows.length, hydratePageSize).totalPages;
10030
10137
  if (page >= totalPages)
10031
10138
  break;
10032
10139
  page += 1;
@@ -10036,7 +10143,7 @@ const RegisterLookupWidget = ({ config }) => {
10036
10143
  const runSearch = useCallback(async (text, page = 1) => {
10037
10144
  try {
10038
10145
  const { rows, pagination } = await fetchRecords(text, page, pageSize);
10039
- const parsed = parsePagination(pagination, rows.length, pageSize, page);
10146
+ const parsed = parsePagination$1(pagination, rows.length, pageSize, page);
10040
10147
  setSearchResults(rows);
10041
10148
  setTotalCount(parsed.totalItems);
10042
10149
  setTotalPages(parsed.totalPages);
@@ -10169,6 +10276,361 @@ const RegisterLookupWidget = ({ config }) => {
10169
10276
  : t?.('common.searchHint', { defaultValue: 'Type and press Enter or click search' }) })) : (jsxRuntimeExports.jsx(ResultsTable, { rows: searchResults, selectedRowKey: pendingRow?.internal_record_id ?? null, onRowClick: setPendingRow, onRowDoubleClick: applySelection })) }), jsxRuntimeExports.jsxs("div", { className: `flex-shrink-0 flex flex-wrap items-center gap-3 px-5 py-3 border-t border-gray-200 ${totalCount !== null ? 'justify-between' : 'justify-end'}`, children: [totalCount !== null && (jsxRuntimeExports.jsx(PaginationFooter, { embedded: true, currentPage: currentPage, totalPages: totalPages, totalCount: totalCount, pageSize: pageSize, onPageChange: (page) => runSearch(searchText, page), onPrev: () => currentPage > 1 && runSearch(searchText, currentPage - 1), onNext: () => currentPage < totalPages && runSearch(searchText, currentPage + 1) })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => pendingRow && applySelection(pendingRow), disabled: !pendingRow, className: "px-4 h-9 text-sm font-medium rounded-[10px] text-white disabled:opacity-40 disabled:cursor-not-allowed flex-shrink-0", style: { backgroundColor: 'var(--owt-color-info, #2563eb)' }, children: selectRecordLabel })] })] })] }))] }));
10170
10277
  };
10171
10278
 
10279
+ const parentLookupPageCache = new Map();
10280
+ const parentLookupPageInflight = new Map();
10281
+ const parentLookupRecordCache = new Map();
10282
+ const parsePagination = (pagination, rowCount, size, fallbackPage = 1) => {
10283
+ const totalItems = typeof pagination.number_of_items === 'number' ? pagination.number_of_items : rowCount;
10284
+ const totalPages = typeof pagination.number_of_pages === 'number'
10285
+ ? Math.max(1, pagination.number_of_pages)
10286
+ : totalItems > 0
10287
+ ? Math.max(1, Math.ceil(totalItems / size))
10288
+ : 1;
10289
+ return { totalItems, totalPages, currentPage: pagination.current_page ?? fallbackPage };
10290
+ };
10291
+ const parentLabel = (row) => {
10292
+ if (!row)
10293
+ return '';
10294
+ if (row.record_name != null && String(row.record_name).trim() !== '') {
10295
+ return String(row.record_name);
10296
+ }
10297
+ return String(row.internal_record_id ?? '');
10298
+ };
10299
+ const indexParentRecords = (rows) => {
10300
+ for (const row of rows) {
10301
+ const id = String(row.internal_record_id ?? '').trim();
10302
+ if (id) {
10303
+ parentLookupRecordCache.set(id, row);
10304
+ }
10305
+ }
10306
+ };
10307
+ const buildParentLookupCacheKey = (service, endpoint, method, params) => `${service}|${endpoint}|${method}|${JSON.stringify(params)}`;
10308
+ const fetchParentLookupPage = async (handler, service, endpoint, method, params, headers) => {
10309
+ const resolvedMethod = method || 'POST';
10310
+ const cacheKey = buildParentLookupCacheKey(service, endpoint, resolvedMethod, params);
10311
+ const cached = parentLookupPageCache.get(cacheKey);
10312
+ if (cached) {
10313
+ return cached;
10314
+ }
10315
+ const inflight = parentLookupPageInflight.get(cacheKey);
10316
+ if (inflight) {
10317
+ return inflight;
10318
+ }
10319
+ const fetchPromise = (async () => {
10320
+ const result = await handler(service, endpoint, resolvedMethod, params, { headers });
10321
+ const parsed = {
10322
+ rows: (result?.records ?? []),
10323
+ pagination: (result?.pagination ?? {}),
10324
+ };
10325
+ indexParentRecords(parsed.rows);
10326
+ parentLookupPageCache.set(cacheKey, parsed);
10327
+ return parsed;
10328
+ })();
10329
+ parentLookupPageInflight.set(cacheKey, fetchPromise);
10330
+ try {
10331
+ return await fetchPromise;
10332
+ }
10333
+ finally {
10334
+ parentLookupPageInflight.delete(cacheKey);
10335
+ }
10336
+ };
10337
+ const ParentLookupWidget = ({ config, value: valueProp, error: errorProp, touched: touchedProp, isEnabled: isEnabledProp, isRequired: isRequiredProp, onChange: onChangeProp, onBlur: onBlurProp, }) => {
10338
+ const hook = useBaseWidget({ config });
10339
+ // WidgetRenderer spreads its hook result as props (includes table onValueChange).
10340
+ // Prefer those so parent selection updates the table row / form state.
10341
+ const useInjected = typeof onChangeProp === 'function';
10342
+ const value = useInjected ? valueProp : hook.value;
10343
+ const error = useInjected ? (errorProp ?? hook.error) : hook.error;
10344
+ const touched = useInjected ? !!touchedProp : hook.touched;
10345
+ const isEnabled = useInjected ? (isEnabledProp ?? hook.isEnabled) : hook.isEnabled;
10346
+ const isRequired = useInjected ? !!isRequiredProp : hook.isRequired;
10347
+ const onChange = useInjected ? onChangeProp : hook.onChange;
10348
+ const onBlur = useInjected ? (onBlurProp ?? hook.onBlur) : hook.onBlur;
10349
+ const widgetConfig = config;
10350
+ const { t, dataSourceRequestHandler, hostContext } = useWidgetContext();
10351
+ const dataSource = widgetConfig['widget-data-source'];
10352
+ const lookupConfig = widgetConfig['widget-lookup-config'];
10353
+ const pageSize = lookupConfig?.page_size ?? 10;
10354
+ const isCompact = !widgetConfig['widget-label'];
10355
+ const requestParams = useMemo(() => {
10356
+ const merged = { ...(hostContext || {}) };
10357
+ const schemaParams = dataSource?.params || {};
10358
+ for (const [key, paramValue] of Object.entries(schemaParams)) {
10359
+ if (paramValue !== null && paramValue !== undefined && paramValue !== '') {
10360
+ merged[key] = paramValue;
10361
+ }
10362
+ }
10363
+ return merged;
10364
+ }, [hostContext, dataSource?.params]);
10365
+ const [isOpen, setIsOpen] = useState(false);
10366
+ const [searchText, setSearchText] = useState('');
10367
+ const [searchResults, setSearchResults] = useState([]);
10368
+ const [currentPage, setCurrentPage] = useState(1);
10369
+ const [totalPages, setTotalPages] = useState(1);
10370
+ const [totalCount, setTotalCount] = useState(null);
10371
+ const [pendingRow, setPendingRow] = useState(null);
10372
+ const [appliedRecord, setAppliedRecord] = useState(null);
10373
+ const [isHydrating, setIsHydrating] = useState(false);
10374
+ const [modalPos, setModalPos] = useState({ x: 80, y: 80 });
10375
+ const [modalSize, setModalSize] = useState({ w: 860, h: 520 });
10376
+ const isDragging = useRef(false);
10377
+ const dragOrigin = useRef({ mouseX: 0, mouseY: 0, posX: 0, posY: 0 });
10378
+ const searchInputRef = useRef(null);
10379
+ const hydratedValueRef = useRef(null);
10380
+ const fetchRecords = useCallback(async (text, page, size) => {
10381
+ if (!dataSource?.service || !dataSource?.endpoint || !dataSourceRequestHandler) {
10382
+ return { rows: [], pagination: {} };
10383
+ }
10384
+ return fetchParentLookupPage(dataSourceRequestHandler, dataSource.service, dataSource.endpoint, dataSource.method, {
10385
+ ...requestParams,
10386
+ search_text: text,
10387
+ current_page: page,
10388
+ page_size: size,
10389
+ }, dataSource.headers);
10390
+ }, [dataSource, dataSourceRequestHandler, requestParams]);
10391
+ const findRecordByValue = useCallback(async (recordValue) => {
10392
+ const target = String(recordValue).trim();
10393
+ if (!target)
10394
+ return null;
10395
+ const cachedRecord = parentLookupRecordCache.get(target);
10396
+ if (cachedRecord) {
10397
+ return cachedRecord;
10398
+ }
10399
+ const hydratePageSize = lookupConfig?.hydrate_page_size ?? 50;
10400
+ let page = 1;
10401
+ let pages = 1;
10402
+ while (page <= pages) {
10403
+ const { rows, pagination } = await fetchRecords('', page, hydratePageSize);
10404
+ const match = rows.find((row) => String(row.internal_record_id ?? '').trim() === target);
10405
+ if (match)
10406
+ return match;
10407
+ pages = parsePagination(pagination, rows.length, hydratePageSize).totalPages;
10408
+ if (page >= pages)
10409
+ break;
10410
+ page += 1;
10411
+ }
10412
+ return null;
10413
+ }, [fetchRecords, lookupConfig?.hydrate_page_size]);
10414
+ const runSearch = useCallback(async (text, page = 1) => {
10415
+ try {
10416
+ const { rows, pagination } = await fetchRecords(text, page, pageSize);
10417
+ const parsed = parsePagination(pagination, rows.length, pageSize, page);
10418
+ setSearchResults(rows);
10419
+ setTotalCount(parsed.totalItems);
10420
+ setTotalPages(parsed.totalPages);
10421
+ setCurrentPage(parsed.currentPage);
10422
+ }
10423
+ catch {
10424
+ setSearchResults([]);
10425
+ setTotalCount(null);
10426
+ setTotalPages(1);
10427
+ setCurrentPage(1);
10428
+ }
10429
+ }, [fetchRecords, pageSize]);
10430
+ useEffect(() => {
10431
+ const onMove = (e) => {
10432
+ if (!isDragging.current)
10433
+ return;
10434
+ setModalPos({
10435
+ x: dragOrigin.current.posX + (e.clientX - dragOrigin.current.mouseX),
10436
+ y: dragOrigin.current.posY + (e.clientY - dragOrigin.current.mouseY),
10437
+ });
10438
+ };
10439
+ const onUp = () => {
10440
+ isDragging.current = false;
10441
+ };
10442
+ document.addEventListener('mousemove', onMove);
10443
+ document.addEventListener('mouseup', onUp);
10444
+ return () => {
10445
+ document.removeEventListener('mousemove', onMove);
10446
+ document.removeEventListener('mouseup', onUp);
10447
+ };
10448
+ }, []);
10449
+ const hasValue = value !== null && value !== undefined && value !== '';
10450
+ const applySelection = (row) => {
10451
+ indexParentRecords([row]);
10452
+ hydratedValueRef.current = row.internal_record_id;
10453
+ onChange(row.internal_record_id);
10454
+ setAppliedRecord(row);
10455
+ setIsOpen(false);
10456
+ };
10457
+ const clearSelection = () => {
10458
+ hydratedValueRef.current = null;
10459
+ onChange(null);
10460
+ setAppliedRecord(null);
10461
+ setPendingRow(null);
10462
+ };
10463
+ const openLookup = () => {
10464
+ const w = Math.min(Math.round(window.innerWidth * 0.82), 940);
10465
+ const h = Math.min(Math.round(window.innerHeight * 0.72), 560);
10466
+ setModalPos({
10467
+ x: Math.round((window.innerWidth - w) / 2),
10468
+ y: Math.round((window.innerHeight - h) / 2),
10469
+ });
10470
+ setModalSize({ w, h });
10471
+ setIsOpen(true);
10472
+ setSearchText('');
10473
+ setSearchResults([]);
10474
+ setTotalCount(null);
10475
+ setCurrentPage(1);
10476
+ setTotalPages(1);
10477
+ setPendingRow(appliedRecord);
10478
+ setTimeout(() => searchInputRef.current?.focus(), 50);
10479
+ runSearch('', 1);
10480
+ };
10481
+ useEffect(() => {
10482
+ if (!hasValue) {
10483
+ hydratedValueRef.current = null;
10484
+ setAppliedRecord(null);
10485
+ setIsHydrating(false);
10486
+ return;
10487
+ }
10488
+ if (!dataSource?.service || !dataSource?.endpoint || !dataSourceRequestHandler)
10489
+ return;
10490
+ if (hydratedValueRef.current === value)
10491
+ return;
10492
+ let cancelled = false;
10493
+ setIsHydrating(true);
10494
+ setAppliedRecord(null);
10495
+ (async () => {
10496
+ try {
10497
+ const match = await findRecordByValue(value);
10498
+ if (cancelled)
10499
+ return;
10500
+ hydratedValueRef.current = value;
10501
+ setAppliedRecord(match ?? { internal_record_id: String(value) });
10502
+ }
10503
+ catch {
10504
+ if (!cancelled) {
10505
+ hydratedValueRef.current = value;
10506
+ setAppliedRecord({ internal_record_id: String(value) });
10507
+ }
10508
+ }
10509
+ finally {
10510
+ if (!cancelled)
10511
+ setIsHydrating(false);
10512
+ }
10513
+ })();
10514
+ return () => {
10515
+ cancelled = true;
10516
+ setIsHydrating(false);
10517
+ };
10518
+ }, [hasValue, value, dataSource, dataSourceRequestHandler, findRecordByValue]);
10519
+ const isReadonly = !!widgetConfig['widget-readonly'];
10520
+ const rawLabel = widgetConfig['widget-label'];
10521
+ const label = tSchema(t, rawLabel || 'Parent');
10522
+ const hasError = (touched && error.length > 0) || (!!widgetConfig['widget-required'] && !hasValue);
10523
+ const placeholder = tSchema(t, String(lookupConfig?.action_label ?? t?.('common.select') ?? 'Select'));
10524
+ const searchPlaceholder = tSchema(t, String(lookupConfig?.search_placeholder ?? 'Search parent...'));
10525
+ const selectRecordLabel = tSchema(t, String(lookupConfig?.select_record_label ?? 'Select Parent'));
10526
+ const displayName = isHydrating
10527
+ ? t?.('common.loading', { defaultValue: 'Loading...' })
10528
+ : parentLabel(appliedRecord) || (hasValue ? String(value) : '');
10529
+ const selectTrigger = isCompact ? (jsxRuntimeExports.jsxs("select", { value: hasValue ? '__selected__' : '', disabled: !isEnabled || isReadonly, onMouseDown: (e) => {
10530
+ if (!isEnabled || isReadonly)
10531
+ return;
10532
+ e.preventDefault();
10533
+ openLookup();
10534
+ }, onKeyDown: (e) => {
10535
+ if (!isEnabled || isReadonly)
10536
+ return;
10537
+ if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
10538
+ e.preventDefault();
10539
+ openLookup();
10540
+ }
10541
+ }, onChange: () => undefined, onBlur: onBlur, className: `w-full h-[28px] px-2 text-sm border focus:outline-none table-cell-input ${!isEnabled || isReadonly ? 'cursor-not-allowed' : 'cursor-pointer'}`, style: {
10542
+ borderRadius: '10px',
10543
+ borderColor: hasError
10544
+ ? 'var(--owt-color-error, #B91C1C)'
10545
+ : 'var(--owt-widget-input-border, #C4C4C4)',
10546
+ backgroundColor: !isEnabled || isReadonly
10547
+ ? 'var(--owt-color-bg-alt, #F6F6F6)'
10548
+ : 'var(--owt-color-bg, #FFFFFF)',
10549
+ }, title: hasValue ? displayName : placeholder, children: [jsxRuntimeExports.jsx("option", { value: "", children: placeholder }), hasValue && jsxRuntimeExports.jsx("option", { value: "__selected__", children: displayName })] })) : (jsxRuntimeExports.jsxs("select", { value: hasValue ? '__selected__' : '', disabled: !isEnabled || isReadonly, onMouseDown: (e) => {
10550
+ if (!isEnabled || isReadonly)
10551
+ return;
10552
+ e.preventDefault();
10553
+ openLookup();
10554
+ }, onKeyDown: (e) => {
10555
+ if (!isEnabled || isReadonly)
10556
+ return;
10557
+ if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
10558
+ e.preventDefault();
10559
+ openLookup();
10560
+ }
10561
+ }, onChange: () => undefined, onBlur: onBlur, 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 ${hasError
10562
+ ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
10563
+ : 'border-gray-300'} ${!isEnabled || isReadonly
10564
+ ? 'bg-gray-100 cursor-not-allowed'
10565
+ : 'bg-white cursor-pointer'}`, style: { borderRadius: '10px' }, title: hasValue ? displayName : placeholder, children: [jsxRuntimeExports.jsx("option", { value: "", children: placeholder }), hasValue && jsxRuntimeExports.jsx("option", { value: "__selected__", children: displayName })] }));
10566
+ if (isReadonly && !isCompact) {
10567
+ return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] flex flex-col sm:flex-row sm:items-start", children: [rawLabel && (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 text-base text-gray-900 font-medium", children: hasValue ? displayName : '-' })] }));
10568
+ }
10569
+ if (isReadonly && isCompact) {
10570
+ return (jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-900 truncate block", children: hasValue ? displayName : '-' }));
10571
+ }
10572
+ return (jsxRuntimeExports.jsxs("div", { className: isCompact ? 'table-cell-field w-full' : 'mb-[10px]', children: [isCompact ? (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [selectTrigger, jsxRuntimeExports.jsx("p", { className: "table-cell-field-error", "aria-hidden": "true", children: '\u00a0' })] })) : (jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start", children: [rawLabel && (jsxRuntimeExports.jsx(WidgetFieldLabel, { className: "text-base font-medium text-gray-700 md:min-w-[120px] sm:pr-4 sm:pt-1 mb-1 sm:mb-0", label: rawLabel, required: isRequired })), jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [selectTrigger, touched && error.length > 0 && (jsxRuntimeExports.jsx("p", { className: "text-red-500 text-sm mt-1", children: error[0] }))] })] })), isOpen && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[100]", style: { backgroundColor: 'rgba(0,0,0,0.5)' }, onClick: () => {
10573
+ setIsOpen(false);
10574
+ onBlur();
10575
+ } }), jsxRuntimeExports.jsxs("div", { className: "flex flex-col overflow-hidden", style: {
10576
+ position: 'fixed',
10577
+ top: modalPos.y,
10578
+ left: modalPos.x,
10579
+ width: modalSize.w,
10580
+ height: modalSize.h,
10581
+ zIndex: 101,
10582
+ resize: 'both',
10583
+ minWidth: 340,
10584
+ minHeight: 260,
10585
+ maxWidth: '96vw',
10586
+ maxHeight: '92vh',
10587
+ backgroundColor: 'var(--owt-color-bg, #FFFFFF)',
10588
+ borderRadius: 'var(--owt-widget-card-border-radius, 20px)',
10589
+ boxShadow: '0 24px 64px rgba(0,0,0,0.28)',
10590
+ }, onClick: (e) => e.stopPropagation(), children: [jsxRuntimeExports.jsxs("div", { onMouseDown: (e) => {
10591
+ e.preventDefault();
10592
+ isDragging.current = true;
10593
+ dragOrigin.current = {
10594
+ mouseX: e.clientX,
10595
+ mouseY: e.clientY,
10596
+ posX: modalPos.x,
10597
+ posY: modalPos.y,
10598
+ };
10599
+ }, className: "flex items-center justify-between px-5 py-4 flex-shrink-0 select-none border-b border-gray-200 cursor-grab", children: [jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: t?.('common.selectTitle', { label, defaultValue: `Select ${label}` }) }), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => {
10600
+ setIsOpen(false);
10601
+ onBlur();
10602
+ }, onMouseDown: (e) => e.stopPropagation(), className: "p-0 border-0 bg-transparent cursor-pointer", "aria-label": t?.('common.close', { defaultValue: 'Close' }), children: jsxRuntimeExports.jsx("img", { src: img$f, alt: "", className: "w-5 h-5 opacity-60" }) })] }), jsxRuntimeExports.jsx("div", { className: "px-5 py-3 flex-shrink-0 border-b border-gray-200", children: jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-3 h-[30px] border border-gray-300 rounded-[10px] bg-white", children: [jsxRuntimeExports.jsx("input", { ref: searchInputRef, type: "text", value: searchText, onChange: (e) => setSearchText(e.target.value), onKeyDown: (e) => {
10603
+ if (e.key === 'Enter') {
10604
+ e.preventDefault();
10605
+ runSearch(searchText, 1);
10606
+ }
10607
+ }, placeholder: searchPlaceholder, className: "flex-1 outline-none text-sm text-gray-900 bg-transparent" }), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => runSearch(searchText, 1), "aria-label": t?.('common.search', { defaultValue: 'Search' }), className: "flex-shrink-0 p-0 border-0 bg-transparent cursor-pointer", children: jsxRuntimeExports.jsx("img", { src: img$g, alt: "", className: "w-4 h-4 opacity-40" }) })] }) }), jsxRuntimeExports.jsx("div", { className: "overflow-auto flex-1", children: searchResults.length === 0 ? (jsxRuntimeExports.jsx("p", { className: "text-center text-sm text-gray-500 py-10", children: searchText
10608
+ ? t?.('common.noResults', { defaultValue: 'No results found' })
10609
+ : t?.('common.searchHint', {
10610
+ defaultValue: 'Type and press Enter or click search',
10611
+ }) })) : (jsxRuntimeExports.jsx("div", { className: "overflow-auto h-full", children: jsxRuntimeExports.jsxs("table", { className: "w-full text-sm border-collapse", children: [jsxRuntimeExports.jsx("thead", { className: "sticky top-0 z-[1] bg-gray-50", children: jsxRuntimeExports.jsxs("tr", { children: [jsxRuntimeExports.jsx("th", { className: "text-left px-4 py-2 text-sm font-medium text-gray-600 whitespace-nowrap border-b border-gray-200 bg-gray-50", children: tSchema(t, 'record_name') }), jsxRuntimeExports.jsx("th", { className: "text-left px-4 py-2 text-sm font-medium text-gray-600 whitespace-nowrap border-b border-gray-200 bg-gray-50", children: tSchema(t, 'internal_record_id') })] }) }), jsxRuntimeExports.jsx("tbody", { children: searchResults.map((row, idx) => {
10612
+ const rowKey = row.internal_record_id ?? idx;
10613
+ const isSelected = pendingRow?.internal_record_id != null &&
10614
+ rowKey === pendingRow.internal_record_id;
10615
+ return (jsxRuntimeExports.jsxs("tr", { onClick: () => setPendingRow(row), onDoubleClick: () => applySelection(row), className: `cursor-pointer border-b border-gray-100 transition-colors ${isSelected ? 'bg-blue-100' : 'hover:bg-blue-50'}`, children: [jsxRuntimeExports.jsx("td", { className: "px-4 py-2 text-sm text-gray-900 whitespace-nowrap", children: parentLabel(row) || '-' }), jsxRuntimeExports.jsx("td", { className: "px-4 py-2 text-sm text-gray-900 whitespace-nowrap", children: row.internal_record_id != null
10616
+ ? String(row.internal_record_id)
10617
+ : '-' })] }, rowKey));
10618
+ }) })] }) })) }), jsxRuntimeExports.jsxs("div", { className: `flex-shrink-0 flex flex-wrap items-center gap-3 px-5 py-3 border-t border-gray-200 ${totalCount !== null ? 'justify-between' : 'justify-end'}`, children: [totalCount !== null && (jsxRuntimeExports.jsxs("span", { className: "text-sm text-gray-600", children: [totalCount === 1
10619
+ ? t?.('common.record', {
10620
+ count: totalCount,
10621
+ defaultValue: `${totalCount} record`,
10622
+ })
10623
+ : t?.('common.records', {
10624
+ count: totalCount,
10625
+ defaultValue: `${totalCount} records`,
10626
+ }), totalCount > 0 &&
10627
+ ` · ${(currentPage - 1) * pageSize + 1}-${Math.min(currentPage * pageSize, totalCount)}`] })), jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [totalCount !== null && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: () => currentPage > 1 && runSearch(searchText, currentPage - 1), disabled: currentPage <= 1, className: "px-3 h-8 text-sm font-medium rounded-[10px] bg-gray-100 text-gray-700 disabled:opacity-40 disabled:cursor-not-allowed", children: t?.('common.previous', { defaultValue: 'Prev' }) }), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => currentPage < totalPages && runSearch(searchText, currentPage + 1), disabled: currentPage >= totalPages, className: "px-3 h-8 text-sm font-medium rounded-[10px] bg-gray-100 text-gray-700 disabled:opacity-40 disabled:cursor-not-allowed", children: t?.('common.next', { defaultValue: 'Next' }) })] })), hasValue && (jsxRuntimeExports.jsx("button", { type: "button", onClick: () => {
10628
+ clearSelection();
10629
+ setIsOpen(false);
10630
+ onBlur();
10631
+ }, className: "px-4 h-9 text-sm font-medium rounded-[10px] bg-gray-100 text-gray-700 flex-shrink-0", children: t?.('common.remove', { defaultValue: 'Clear' }) })), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => pendingRow && applySelection(pendingRow), disabled: !pendingRow, className: "px-4 h-9 text-sm font-medium rounded-[10px] text-white disabled:opacity-40 disabled:cursor-not-allowed flex-shrink-0", style: { backgroundColor: 'var(--owt-color-info, #2563eb)' }, children: selectRecordLabel })] })] })] })] }))] }));
10632
+ };
10633
+
10172
10634
  const MultiSelectWidget = ({ config }) => {
10173
10635
  const { value, error, touched, isEnabled, isRequired, onChange, onBlur, dataSourceOptions, loading, config: widgetConfig, } = useBaseWidget({ config });
10174
10636
  const { t } = useWidgetContext();
@@ -11373,6 +11835,7 @@ const registerDefaultWidgets = () => {
11373
11835
  widgetRegistry.register({ widget: 'scores-display', component: ScoresDisplayWidget });
11374
11836
  widgetRegistry.register({ widget: 'id-authentication', component: IdAuthenticationWidget });
11375
11837
  widgetRegistry.register({ widget: 'register-lookup', component: RegisterLookupWidget });
11838
+ widgetRegistry.register({ widget: 'parent-lookup', component: ParentLookupWidget });
11376
11839
  widgetRegistry.register({ widget: 'multi-select', component: MultiSelectWidget });
11377
11840
  widgetRegistry.register({ widget: 'geo-hierarchy', component: GeoHierarchyWidget });
11378
11841
  widgetRegistry.register({ widget: 'docs', component: DocsWidget });
@@ -11482,5 +11945,5 @@ const translateUISchema = (schema, translate) => {
11482
11945
  };
11483
11946
  };
11484
11947
 
11485
- export { BooleanWidget, CheckboxWidget, DateInputWidget, DateTimeInputWidget, DialogTableWidget, DisplayWidget, DocsWidget, FileInputWidget, GeoHierarchyWidget, HeaderSectionWidget, IdAuthenticationWidget, JSONEditorPanel, MultiSelectWidget, NumberInputWidget, PanelRenderer, PhoneInputWidget, ProfileWidget, PropertyEditor, RadioWidget, RegisterLookupWidget, ScoresDisplayWidget, SectionBuilder, SectionRenderer, SectionTree, SectionsContainer, SelectWidget, TableWidget, TextAreaWidget, TextInputWidget, VisualBuilderPanel, WidgetEventBus, WidgetProvider, WidgetRenderer, applyCaseControl, applyDecimalPrecision, applyMask, createWidgetStore, createZodSchema, defaultTheme, evaluateCondition, evaluateWidgetConditions, filterByCharacterType, formatCurrency, formatDate, formatNumber, formatPhone, formatValue, getApiDataSource, getCachedApiDataSource, getFormattedNumberLength, getSchemaDataSource, getStaticDataSource, getValueByPath, getWidgetValue, hasVisibilityRules, isAllowedKey, isGeoHierarchyDataSource, normalizeNumericDefault, normalizeOptionRules, parseDataPath, parseNumber, registerDefaultWidgets, removeMask, resetAll, resetWidget, resolveTheme, resolveWidgetIdValue, setDataSource, setError, setLoading, setTouched, setValue, setValueByPath, setValues, setWidgetValue, shouldEnableWidget, shouldRequireWidget, shouldShowWidget, transformDataSourceOptions, translatePanelConfig, translateUISchema, translateWidgetConfig, useBaseWidget, useWidgetCascade, useWidgetContext, useWidgetEventBus, useWidgetTheme, validateNumericValue, validateWidget, widgetRegistry };
11948
+ export { BooleanWidget, CheckboxWidget, DateInputWidget, DateTimeInputWidget, DialogTableWidget, DisplayWidget, DocsWidget, FileInputWidget, GeoHierarchyWidget, HeaderSectionWidget, IdAuthenticationWidget, JSONEditorPanel, MultiSelectWidget, NumberInputWidget, PanelRenderer, ParentLookupWidget, PhoneInputWidget, ProfileWidget, PropertyEditor, RadioWidget, RegisterLookupWidget, ScoresDisplayWidget, SectionBuilder, SectionRenderer, SectionTree, SectionsContainer, SelectWidget, TableWidget, TextAreaWidget, TextInputWidget, VisualBuilderPanel, WidgetEventBus, WidgetProvider, WidgetRenderer, applyCaseControl, applyDecimalPrecision, applyMask, createWidgetStore, createZodSchema, defaultTheme, evaluateCondition, evaluateWidgetConditions, filterByCharacterType, formatCurrency, formatDate, formatNumber, formatPhone, formatValue, getApiDataSource, getCachedApiDataSource, getFormattedNumberLength, getSchemaDataSource, getStaticDataSource, getValueByPath, getWidgetValue, hasVisibilityRules, isAllowedKey, isGeoHierarchyDataSource, normalizeNumericDefault, normalizeOptionRules, parseDataPath, parseNumber, registerDefaultWidgets, removeMask, resetAll, resetWidget, resolveTheme, resolveWidgetIdValue, setDataSource, setError, setLoading, setTouched, setValue, setValueByPath, setValues, setWidgetValue, shouldEnableWidget, shouldRequireWidget, shouldShowWidget, transformDataSourceOptions, translatePanelConfig, translateUISchema, translateWidgetConfig, useBaseWidget, useWidgetCascade, useWidgetContext, useWidgetEventBus, useWidgetTheme, validateNumericValue, validateWidget, widgetRegistry };
11486
11949
  //# sourceMappingURL=index.esm.js.map