@dreamtree-org/twreact-ui 1.0.71 → 1.0.72

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
@@ -3886,6 +3886,98 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
3886
3886
  var _excluded$k = ["data", "columns", "sortable", "filterable", "selectable", "pagination", "pageSize", "onSort", "onFilter", "onFetch", "onFilterChange", "onSelectionChange", "onRowClick", "hasDetails", "DetailsComponent", "className", "withAction", "onAction", "actions", "showSerial", "cellClass", "rowClass", "globalSearch", "limitOptions", "showLimitSelector", "onLimitChange", "showReloadButton", "renderReloadButton", "onReload", "stripedRows", "stripedColors", "responsiveBreakpoint", "serverSide", "totalRecords", "pageNumber", "onPageChange"];
3887
3887
  function ownKeys$9(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3888
3888
  function _objectSpread$9(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$9(Object(t), true).forEach(function (r) { _defineProperty$4(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$9(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
3889
+
3890
+ // Constants moved outside component
3891
+ var DEFAULT_ACTIONS = [{
3892
+ name: "edit",
3893
+ label: "Edit",
3894
+ onClick: function onClick() {
3895
+ console.log("Edit action clicked");
3896
+ },
3897
+ icon: /*#__PURE__*/React.createElement(PenSquare, {
3898
+ size: 16
3899
+ })
3900
+ }, {
3901
+ name: "delete",
3902
+ label: "Delete",
3903
+ onClick: function onClick() {
3904
+ console.log("Delete action clicked");
3905
+ },
3906
+ icon: /*#__PURE__*/React.createElement(Trash, {
3907
+ size: 16
3908
+ })
3909
+ }, {
3910
+ name: "view",
3911
+ label: "View",
3912
+ onClick: function onClick() {
3913
+ console.log("View action clicked");
3914
+ },
3915
+ icon: /*#__PURE__*/React.createElement(Eye, {
3916
+ size: 16
3917
+ })
3918
+ }];
3919
+ var RESIZE_DEBOUNCE_MS = 150;
3920
+ var MENU_PLACEMENT_THRESHOLD = 160;
3921
+
3922
+ // Helper functions
3923
+ var getRowKey = function getRowKey(row, globalIndex) {
3924
+ if ((row === null || row === void 0 ? void 0 : row.id) != null) return String(row.id);
3925
+ if ((row === null || row === void 0 ? void 0 : row._id) != null) return String(row._id);
3926
+ return String(globalIndex);
3927
+ };
3928
+
3929
+ // Calculate pagination numbers
3930
+ var getPaginationNumbers = function getPaginationNumbers(currentPage, totalPages) {
3931
+ var maxVisible = 5;
3932
+ if (totalPages <= maxVisible) {
3933
+ return Array.from({
3934
+ length: totalPages
3935
+ }, function (_, i) {
3936
+ return i + 1;
3937
+ });
3938
+ }
3939
+ if (currentPage <= 3) {
3940
+ return Array.from({
3941
+ length: maxVisible
3942
+ }, function (_, i) {
3943
+ return i + 1;
3944
+ });
3945
+ }
3946
+ if (currentPage >= totalPages - 2) {
3947
+ return Array.from({
3948
+ length: maxVisible
3949
+ }, function (_, i) {
3950
+ return totalPages - 4 + i;
3951
+ });
3952
+ }
3953
+ return Array.from({
3954
+ length: maxVisible
3955
+ }, function (_, i) {
3956
+ return currentPage - 2 + i;
3957
+ });
3958
+ };
3959
+
3960
+ // Empty and Loading state components
3961
+ var EmptyState = function EmptyState() {
3962
+ return /*#__PURE__*/React.createElement("div", {
3963
+ className: "flex flex-col items-center gap-2"
3964
+ }, /*#__PURE__*/React.createElement("div", {
3965
+ className: "text-gray-400"
3966
+ }, /*#__PURE__*/React.createElement(List, {
3967
+ className: "h-8 w-8"
3968
+ })), /*#__PURE__*/React.createElement("span", {
3969
+ className: "text-sm text-gray-500"
3970
+ }, "No results found."));
3971
+ };
3972
+ var LoadingState = function LoadingState() {
3973
+ return /*#__PURE__*/React.createElement("div", {
3974
+ className: "flex flex-col items-center gap-2"
3975
+ }, /*#__PURE__*/React.createElement("div", {
3976
+ className: "h-6 w-6 animate-spin rounded-full border-2 border-primary-600 border-t-transparent"
3977
+ }), /*#__PURE__*/React.createElement("span", {
3978
+ className: "text-sm text-gray-500"
3979
+ }, "Loading..."));
3980
+ };
3889
3981
  var Table = function Table(_ref) {
3890
3982
  var _ref$data = _ref.data,
3891
3983
  data = _ref$data === void 0 ? [] : _ref$data,
@@ -3941,9 +4033,9 @@ var Table = function Table(_ref) {
3941
4033
  serverSide = _ref$serverSide === void 0 ? false : _ref$serverSide,
3942
4034
  _ref$totalRecords = _ref.totalRecords,
3943
4035
  totalRecords = _ref$totalRecords === void 0 ? 0 : _ref$totalRecords,
3944
- pageNumber = _ref.pageNumber;
3945
- _ref.onPageChange;
3946
- var props = _objectWithoutProperties$1(_ref, _excluded$k);
4036
+ pageNumber = _ref.pageNumber,
4037
+ onPageChange = _ref.onPageChange,
4038
+ props = _objectWithoutProperties$1(_ref, _excluded$k);
3947
4039
  // State for responsive view
3948
4040
  var _useState = React.useState(false),
3949
4041
  _useState2 = _slicedToArray(_useState, 2),
@@ -3951,14 +4043,16 @@ var Table = function Table(_ref) {
3951
4043
  setIsMobileView = _useState2[1];
3952
4044
 
3953
4045
  // Keep original table data / loading
3954
- var _useState3 = React.useState(Array.isArray(data) ? data : []),
4046
+ var _useState3 = React.useState(function () {
4047
+ return Array.isArray(data) ? data : [];
4048
+ }),
3955
4049
  _useState4 = _slicedToArray(_useState3, 2),
3956
4050
  tableData = _useState4[0],
3957
4051
  setTableData = _useState4[1];
3958
4052
  var _useState5 = React.useState(false),
3959
4053
  _useState6 = _slicedToArray(_useState5, 2),
3960
4054
  loading = _useState6[0],
3961
- setLoading = _useState6[1];
4055
+ _setLoading = _useState6[1];
3962
4056
 
3963
4057
  // Column visibility state
3964
4058
  var _useState7 = React.useState(function () {
@@ -3970,10 +4064,26 @@ var Table = function Table(_ref) {
3970
4064
  columnsState = _useState8[0],
3971
4065
  setColumnsState = _useState8[1];
3972
4066
  React.useEffect(function () {
3973
- // Sync when prop changes
3974
- setColumnsState(Array.isArray(columns) ? columns.map(function (c) {
3975
- return _objectSpread$9({}, c);
3976
- }) : []);
4067
+ // Sync when prop changes - only update if columns actually changed
4068
+ if (Array.isArray(columns)) {
4069
+ setColumnsState(function (prev) {
4070
+ var prevKeys = new Set(prev.map(function (c) {
4071
+ return c.key;
4072
+ }));
4073
+ var newKeys = new Set(columns.map(function (c) {
4074
+ return c.key;
4075
+ }));
4076
+ var keysChanged = prevKeys.size !== newKeys.size || _toConsumableArray$1(prevKeys).some(function (k) {
4077
+ return !newKeys.has(k);
4078
+ });
4079
+ if (keysChanged) {
4080
+ return columns.map(function (c) {
4081
+ return _objectSpread$9({}, c);
4082
+ });
4083
+ }
4084
+ return prev;
4085
+ });
4086
+ }
3977
4087
  }, [columns]);
3978
4088
 
3979
4089
  // Popover state for column toggler
@@ -4033,39 +4143,43 @@ var Table = function Table(_ref) {
4033
4143
  _useState28 = _slicedToArray(_useState27, 2),
4034
4144
  openActionKey = _useState28[0],
4035
4145
  setOpenActionKey = _useState28[1];
4036
- var openRef = React.useRef(null);
4037
4146
  React.useEffect(function () {
4038
4147
  if (typeof pageNumber === "number" && pageNumber !== currentPage) {
4039
4148
  setCurrentPage(pageNumber);
4149
+ onPageChange === null || onPageChange === void 0 || onPageChange(pageNumber);
4040
4150
  }
4041
- }, [pageNumber]);
4151
+ }, [pageNumber, currentPage, onPageChange]);
4042
4152
  React.useEffect(function () {
4043
4153
  setLimit(pageSize);
4044
4154
  }, [pageSize]);
4045
4155
 
4046
- // Check if mobile view
4156
+ // Check if mobile view with debouncing
4047
4157
  React.useEffect(function () {
4158
+ var timeoutId;
4048
4159
  var checkMobile = function checkMobile() {
4049
4160
  setIsMobileView(window.innerWidth < responsiveBreakpoint);
4050
4161
  };
4162
+ var debouncedCheck = function debouncedCheck() {
4163
+ clearTimeout(timeoutId);
4164
+ timeoutId = setTimeout(checkMobile, RESIZE_DEBOUNCE_MS);
4165
+ };
4051
4166
 
4052
4167
  // Initial check
4053
4168
  checkMobile();
4054
4169
 
4055
- // Add event listener
4056
- window.addEventListener("resize", checkMobile);
4170
+ // Add event listener with debouncing
4171
+ window.addEventListener("resize", debouncedCheck);
4057
4172
  return function () {
4058
- window.removeEventListener("resize", checkMobile);
4173
+ clearTimeout(timeoutId);
4174
+ window.removeEventListener("resize", debouncedCheck);
4059
4175
  };
4060
4176
  }, [responsiveBreakpoint]);
4061
- var isValidList = function isValidList(d) {
4062
- return Array.isArray(d);
4063
- };
4064
4177
 
4065
4178
  // Close column menu when clicking outside
4066
4179
  React.useEffect(function () {
4180
+ if (!showColumnMenu) return;
4067
4181
  var onDocClick = function onDocClick(e) {
4068
- if (showColumnMenu && columnMenuRef.current && !columnMenuRef.current.contains(e.target) && columnToggleBtnRef.current && !columnToggleBtnRef.current.contains(e.target)) {
4182
+ if (columnMenuRef.current && !columnMenuRef.current.contains(e.target) && columnToggleBtnRef.current && !columnToggleBtnRef.current.contains(e.target)) {
4069
4183
  setShowColumnMenu(false);
4070
4184
  }
4071
4185
  };
@@ -4075,26 +4189,32 @@ var Table = function Table(_ref) {
4075
4189
  };
4076
4190
  }, [showColumnMenu]);
4077
4191
 
4078
- // Close per-row actionmenu when clicking outside
4079
- React.useEffect(function () {
4080
- var handler = function handler(e) {
4081
- if (openActionKey && openRef.current && !openRef.current.contains(e.target)) {
4082
- setOpenActionKey(null);
4083
- }
4084
- };
4085
- document.addEventListener("click", handler);
4086
- return function () {
4087
- return document.removeEventListener("click", handler);
4088
- };
4089
- }, [openActionKey]);
4090
-
4091
4192
  // Visible columns derived from columnsState
4092
4193
  var visibleColumns = React.useMemo(function () {
4093
4194
  return columnsState.filter(function (col) {
4094
4195
  return col.isVisible !== false;
4095
4196
  });
4096
4197
  }, [columnsState]);
4097
- var toggleColumnVisibility = function toggleColumnVisibility(key) {
4198
+
4199
+ // Memoize actions
4200
+ var actionsToUse = React.useMemo(function () {
4201
+ return actions !== null && actions !== void 0 ? actions : DEFAULT_ACTIONS;
4202
+ }, [actions]);
4203
+
4204
+ // Memoize visible count
4205
+ var visibleCount = React.useMemo(function () {
4206
+ return (showSerial ? 1 : 0) + visibleColumns.length + (selectable ? 1 : 0) + (hasDetails ? 1 : 0) + (withAction ? 1 : 0);
4207
+ }, [showSerial, visibleColumns.length, selectable, hasDetails, withAction]);
4208
+
4209
+ // Memoize key-to-row mapping for selection
4210
+ var keyToRowMap = React.useMemo(function () {
4211
+ var map = new Map();
4212
+ filteredData.forEach(function (row, i) {
4213
+ map.set(getRowKey(row, i), row);
4214
+ });
4215
+ return map;
4216
+ }, [filteredData]);
4217
+ var toggleColumnVisibility = React.useCallback(function (key) {
4098
4218
  setColumnsState(function (prev) {
4099
4219
  return prev.map(function (c) {
4100
4220
  return c.key === key ? _objectSpread$9(_objectSpread$9({}, c), {}, {
@@ -4103,57 +4223,22 @@ var Table = function Table(_ref) {
4103
4223
  });
4104
4224
  });
4105
4225
  setCurrentPage(1);
4106
- };
4226
+ }, []);
4107
4227
 
4108
4228
  // Handle actions
4109
- var handleOnAction = function handleOnAction(action, row) {
4229
+ var handleOnAction = React.useCallback(function (action, row) {
4110
4230
  var _action$onClick;
4111
4231
  (_action$onClick = action.onClick) === null || _action$onClick === void 0 || _action$onClick.call(action, {
4112
4232
  action: action,
4113
4233
  row: row
4114
4234
  });
4115
- onAction && onAction({
4235
+ onAction === null || onAction === void 0 || onAction({
4116
4236
  action: action,
4117
4237
  row: row
4118
4238
  });
4119
4239
  setOpenActionKey(null);
4120
4240
  setActionAnchor(null);
4121
- };
4122
- var defaultActions = [{
4123
- name: "edit",
4124
- label: "Edit",
4125
- onClick: function onClick() {
4126
- console.log("Edit action clicked");
4127
- },
4128
- icon: /*#__PURE__*/React.createElement(PenSquare, {
4129
- size: 16
4130
- })
4131
- }, {
4132
- name: "delete",
4133
- label: "Delete",
4134
- onClick: function onClick() {
4135
- console.log("Delete action clicked");
4136
- },
4137
- icon: /*#__PURE__*/React.createElement(Trash, {
4138
- size: 16
4139
- })
4140
- }, {
4141
- name: "view",
4142
- label: "View",
4143
- onClick: function onClick() {
4144
- console.log("View action clicked");
4145
- },
4146
- icon: /*#__PURE__*/React.createElement(Eye, {
4147
- size: 16
4148
- })
4149
- }];
4150
- var actionsToUse = actions !== undefined ? actions : defaultActions;
4151
- var getRowKey = function getRowKey(row, globalIndex) {
4152
- if (row == null) return String(globalIndex);
4153
- if (row.id !== undefined && row.id !== null) return String(row.id);
4154
- if (row._id !== undefined && row._id !== null) return String(row._id);
4155
- return String(globalIndex);
4156
- };
4241
+ }, [onAction]);
4157
4242
 
4158
4243
  // Sorting
4159
4244
  var sortedData = React.useMemo(function () {
@@ -4179,9 +4264,18 @@ var Table = function Table(_ref) {
4179
4264
  });
4180
4265
  });
4181
4266
  }, [sortedData, filters, filterable, serverSide]);
4182
- // Pagination indices
4183
- var startIndex = pagination ? (currentPage - 1) * limit : 0;
4184
- var endIndex = pagination ? startIndex + limit : filteredData.length;
4267
+
4268
+ // Pagination indices - memoized
4269
+ var _useMemo = React.useMemo(function () {
4270
+ var start = pagination ? (currentPage - 1) * limit : 0;
4271
+ var end = pagination ? start + limit : filteredData.length;
4272
+ return {
4273
+ startIndex: start,
4274
+ endIndex: end
4275
+ };
4276
+ }, [pagination, currentPage, limit, filteredData.length]),
4277
+ startIndex = _useMemo.startIndex,
4278
+ endIndex = _useMemo.endIndex;
4185
4279
 
4186
4280
  // Paginated view
4187
4281
  var paginatedData = React.useMemo(function () {
@@ -4195,7 +4289,7 @@ var Table = function Table(_ref) {
4195
4289
  }, [pagination, serverSide, totalRecords, filteredData.length, limit]);
4196
4290
 
4197
4291
  // Sorting handler
4198
- var handleSort = function handleSort(key) {
4292
+ var handleSort = React.useCallback(function (key) {
4199
4293
  if (!sortable) return;
4200
4294
  var direction = sortConfig.key === key && sortConfig.direction === "asc" ? "desc" : "asc";
4201
4295
  setSortConfig({
@@ -4203,10 +4297,10 @@ var Table = function Table(_ref) {
4203
4297
  direction: direction
4204
4298
  });
4205
4299
  onSort === null || onSort === void 0 || onSort(key, direction);
4206
- };
4300
+ }, [sortable, sortConfig, onSort]);
4207
4301
 
4208
- // Selection handlers
4209
- var handleSelectAll = function handleSelectAll() {
4302
+ // Selection handlers - optimized with memoized keyToRowMap
4303
+ var handleSelectAll = React.useCallback(function () {
4210
4304
  var newSelection = new Set(selectedRows);
4211
4305
  var pageRowKeys = paginatedData.map(function (r, i) {
4212
4306
  return getRowKey(r, startIndex + i);
@@ -4224,50 +4318,45 @@ var Table = function Table(_ref) {
4224
4318
  });
4225
4319
  }
4226
4320
  setSelectedRows(newSelection);
4227
- var keyToRow = new Map(filteredData.map(function (r, i) {
4228
- return [getRowKey(r, i), r];
4229
- }));
4230
4321
  var selectedData = Array.from(newSelection).map(function (k) {
4231
- return keyToRow.get(k);
4322
+ return keyToRowMap.get(k);
4232
4323
  }).filter(Boolean);
4233
4324
  onSelectionChange === null || onSelectionChange === void 0 || onSelectionChange(selectedData);
4234
- };
4235
- var handleSelectRow = function handleSelectRow(row, rowIndexInPage) {
4325
+ }, [selectedRows, paginatedData, startIndex, keyToRowMap, onSelectionChange]);
4326
+ var handleSelectRow = React.useCallback(function (row, rowIndexInPage) {
4236
4327
  var globalIndex = startIndex + rowIndexInPage;
4237
4328
  var key = getRowKey(row, globalIndex);
4238
4329
  var newSelection = new Set(selectedRows);
4239
4330
  if (newSelection.has(key)) newSelection["delete"](key);else newSelection.add(key);
4240
4331
  setSelectedRows(newSelection);
4241
- var keyToRow = new Map(filteredData.map(function (r, i) {
4242
- return [getRowKey(r, i), r];
4243
- }));
4244
4332
  var selectedData = Array.from(newSelection).map(function (k) {
4245
- return keyToRow.get(k);
4333
+ return keyToRowMap.get(k);
4246
4334
  }).filter(Boolean);
4247
4335
  onSelectionChange === null || onSelectionChange === void 0 || onSelectionChange(selectedData);
4248
- };
4249
- var toggleExpandRow = function toggleExpandRow(row, rowIndexInPage) {
4336
+ }, [startIndex, selectedRows, keyToRowMap, onSelectionChange]);
4337
+ var toggleExpandRow = React.useCallback(function (row, rowIndexInPage) {
4250
4338
  var globalIndex = startIndex + rowIndexInPage;
4251
4339
  var key = getRowKey(row, globalIndex);
4252
- var newExpanded = new Set(expandedRows);
4253
- if (newExpanded.has(key)) newExpanded["delete"](key);else newExpanded.add(key);
4254
- setExpandedRows(newExpanded);
4255
- };
4256
- var handleFilter = function handleFilter(key, value) {
4257
- var newFilters = _objectSpread$9(_objectSpread$9({}, filters), {}, _defineProperty$4({}, key, value));
4258
- setFilters(newFilters);
4259
- onFilter === null || onFilter === void 0 || onFilter(newFilters);
4260
- onFilterChange === null || onFilterChange === void 0 || onFilterChange(newFilters);
4340
+ setExpandedRows(function (prev) {
4341
+ var newExpanded = new Set(prev);
4342
+ if (newExpanded.has(key)) newExpanded["delete"](key);else newExpanded.add(key);
4343
+ return newExpanded;
4344
+ });
4345
+ }, [startIndex]);
4346
+ var handleFilter = React.useCallback(function (key, value) {
4347
+ setFilters(function (prev) {
4348
+ var newFilters = _objectSpread$9(_objectSpread$9({}, prev), {}, _defineProperty$4({}, key, value));
4349
+ onFilter === null || onFilter === void 0 || onFilter(newFilters);
4350
+ onFilterChange === null || onFilterChange === void 0 || onFilterChange(newFilters);
4351
+ return newFilters;
4352
+ });
4261
4353
  setCurrentPage(1);
4262
- };
4263
- var renderCell = function renderCell(column, row, globalIndex) {
4354
+ }, [onFilter, onFilterChange]);
4355
+ var renderCell = React.useCallback(function (column, row, globalIndex) {
4264
4356
  if (column.render) return column.render(row, globalIndex);
4265
4357
  return row === null || row === void 0 ? void 0 : row[column.key];
4266
- };
4267
-
4268
- // UI counts
4269
- var visibleCount = (showSerial ? 1 : 0) + visibleColumns.length + (selectable ? 1 : 0) + (hasDetails ? 1 : 0) + (withAction ? 1 : 0);
4270
- var toggleActions = function toggleActions(e, actionCellKey, row) {
4358
+ }, []);
4359
+ var toggleActions = React.useCallback(function (e, actionCellKey, row) {
4271
4360
  e.stopPropagation();
4272
4361
  if (actionAnchor && actionAnchor.key === actionCellKey) {
4273
4362
  setOpenActionKey(null);
@@ -4280,7 +4369,7 @@ var Table = function Table(_ref) {
4280
4369
  row: row
4281
4370
  });
4282
4371
  }
4283
- };
4372
+ }, [actionAnchor]);
4284
4373
  React.useEffect(function () {
4285
4374
  if (!actionAnchor) return;
4286
4375
  var onDocClick = function onDocClick(ev) {
@@ -4304,58 +4393,139 @@ var Table = function Table(_ref) {
4304
4393
  window.removeEventListener("resize", onScrollResize);
4305
4394
  };
4306
4395
  }, [actionAnchor]);
4396
+
4397
+ // Track previous fetch params to prevent unnecessary calls
4398
+ var prevFetchParamsRef = React.useRef(null);
4399
+ var isFetchingRef = React.useRef(false);
4400
+ var isMountedRef = React.useRef(true);
4401
+ var onFetchRef = React.useRef(onFetch);
4402
+
4403
+ // Keep ref in sync with onFetch
4307
4404
  React.useEffect(function () {
4308
- if (!onFetch) return;
4309
- onFetch({
4310
- setData: function setData(rows) {
4311
- return setTableData(Array.isArray(rows) ? rows : []);
4312
- },
4313
- setLoading: setLoading,
4405
+ onFetchRef.current = onFetch;
4406
+ }, [onFetch]);
4407
+
4408
+ // Track mount status
4409
+ React.useEffect(function () {
4410
+ isMountedRef.current = true;
4411
+ return function () {
4412
+ isMountedRef.current = false;
4413
+ isFetchingRef.current = false;
4414
+ };
4415
+ }, []);
4416
+
4417
+ // Serialize fetch params for comparison
4418
+ var fetchParams = React.useMemo(function () {
4419
+ return {
4314
4420
  filters: filters,
4315
4421
  page: currentPage,
4316
4422
  limit: limit,
4317
- sort: sortConfig
4318
- });
4319
- }, [onFetch, filters, currentPage, limit, sortConfig]);
4320
- React.useEffect(function () {
4321
- if (!serverSide) setTableData(data);
4322
- }, [data, serverSide]);
4423
+ sortKey: sortConfig.key,
4424
+ sortDirection: sortConfig.direction
4425
+ };
4426
+ }, [filters, currentPage, limit, sortConfig.key, sortConfig.direction]);
4427
+ var fetchParamsString = React.useMemo(function () {
4428
+ return JSON.stringify(fetchParams);
4429
+ }, [fetchParams]);
4430
+
4431
+ // Consolidated data sync effect
4323
4432
  React.useEffect(function () {
4324
- if (!onFetch && isValidList(data)) setTableData(data);
4325
- }, [data, onFetch]);
4433
+ var currentOnFetch = onFetchRef.current;
4434
+ if (currentOnFetch) {
4435
+ // Only fetch if params actually changed and not already fetching
4436
+ if (!isFetchingRef.current && prevFetchParamsRef.current !== fetchParamsString) {
4437
+ prevFetchParamsRef.current = fetchParamsString;
4438
+ isFetchingRef.current = true;
4439
+ currentOnFetch({
4440
+ setData: function setData(rows) {
4441
+ // Only update if component is still mounted
4442
+ if (isMountedRef.current && isFetchingRef.current) {
4443
+ setTableData(Array.isArray(rows) ? rows : []);
4444
+ isFetchingRef.current = false;
4445
+ }
4446
+ },
4447
+ setLoading: function setLoading(loading) {
4448
+ // Only update if component is still mounted
4449
+ if (isMountedRef.current) {
4450
+ _setLoading(loading);
4451
+ if (!loading && isFetchingRef.current) {
4452
+ isFetchingRef.current = false;
4453
+ }
4454
+ }
4455
+ },
4456
+ filters: fetchParams.filters,
4457
+ page: fetchParams.page,
4458
+ limit: fetchParams.limit,
4459
+ sort: {
4460
+ key: fetchParams.sortKey,
4461
+ direction: fetchParams.sortDirection
4462
+ }
4463
+ });
4464
+ }
4465
+ } else if (!serverSide && Array.isArray(data)) {
4466
+ setTableData(data);
4467
+ }
4468
+ }, [serverSide, data, fetchParamsString, fetchParams]);
4326
4469
 
4327
4470
  // Global search
4328
4471
  React.useEffect(function () {
4329
4472
  setSearchInput(filters.global || "");
4330
4473
  }, [filters.global]);
4331
- var applyGlobalSearch = function applyGlobalSearch() {
4474
+ var applyGlobalSearch = React.useCallback(function () {
4332
4475
  handleFilter("global", searchInput);
4333
- };
4334
- var onGlobalKeyDown = function onGlobalKeyDown(e) {
4476
+ }, [handleFilter, searchInput]);
4477
+ var onGlobalKeyDown = React.useCallback(function (e) {
4335
4478
  if (e.key === "Enter") {
4336
4479
  e.preventDefault();
4337
4480
  applyGlobalSearch();
4338
4481
  }
4339
- };
4340
- var handleLimitChange = function handleLimitChange(nextLimit) {
4482
+ }, [applyGlobalSearch]);
4483
+ var handleLimitChange = React.useCallback(function (nextLimit) {
4341
4484
  var parsed = Number(nextLimit);
4342
4485
  if (!Number.isFinite(parsed) || parsed <= 0) return;
4343
4486
  setLimit(parsed);
4344
4487
  setCurrentPage(1);
4345
4488
  onLimitChange === null || onLimitChange === void 0 || onLimitChange(parsed);
4346
- };
4489
+ }, [onLimitChange]);
4347
4490
 
4348
- // Render mobile card
4349
- var renderMobileCard = function renderMobileCard(row, rowIndexInPage) {
4491
+ // Memoize limit options
4492
+ var limitOptionsMemo = React.useMemo(function () {
4493
+ var opts = Array.isArray(limitOptions) && limitOptions.length > 0 ? limitOptions : [25, 50, 100];
4494
+ return opts.map(function (opt) {
4495
+ return {
4496
+ label: String(opt),
4497
+ value: opt
4498
+ };
4499
+ });
4500
+ }, [limitOptions]);
4501
+
4502
+ // Extract row metadata computation
4503
+ var getRowMetadata = React.useCallback(function (row, rowIndexInPage) {
4350
4504
  var globalIndex = startIndex + rowIndexInPage;
4351
4505
  var key = getRowKey(row, globalIndex);
4352
4506
  var actionCellKey = "actions-".concat(key);
4353
- var extraRowClass = typeof rowClass === "function" ? rowClass({
4507
+ var extraRowClass = typeof rowClass === "function" ? (rowClass({
4354
4508
  row: row,
4355
4509
  rowIndex: globalIndex
4356
- }) : "";
4357
- var safeExtraRowClass = typeof extraRowClass === "string" ? extraRowClass.trim() : "";
4510
+ }) || "").trim() : "";
4358
4511
  var stripeBg = stripedRows && !selectedRows.has(key) ? stripedColors[globalIndex % stripedColors.length] : undefined;
4512
+ return {
4513
+ globalIndex: globalIndex,
4514
+ key: key,
4515
+ actionCellKey: actionCellKey,
4516
+ extraRowClass: extraRowClass,
4517
+ stripeBg: stripeBg
4518
+ };
4519
+ }, [startIndex, rowClass, stripedRows, selectedRows, stripedColors]);
4520
+
4521
+ // Render mobile card
4522
+ var renderMobileCard = React.useCallback(function (row, rowIndexInPage) {
4523
+ var _getRowMetadata = getRowMetadata(row, rowIndexInPage),
4524
+ globalIndex = _getRowMetadata.globalIndex,
4525
+ key = _getRowMetadata.key,
4526
+ actionCellKey = _getRowMetadata.actionCellKey;
4527
+ _getRowMetadata.extraRowClass;
4528
+ var stripeBg = _getRowMetadata.stripeBg;
4359
4529
  return /*#__PURE__*/React.createElement("div", {
4360
4530
  key: key,
4361
4531
  style: stripeBg ? {
@@ -4422,16 +4592,15 @@ var Table = function Table(_ref) {
4422
4592
  }), document.body))), /*#__PURE__*/React.createElement("div", {
4423
4593
  className: "p-4"
4424
4594
  }, visibleColumns.map(function (column, colIndex) {
4425
- var extraCellClass = typeof cellClass === "function" ? cellClass({
4595
+ var extraCellClass = typeof cellClass === "function" ? (cellClass({
4426
4596
  row: row,
4427
4597
  rowIndex: globalIndex,
4428
4598
  column: column,
4429
4599
  columnIndex: colIndex
4430
- }) : "";
4431
- var safeExtraCellClass = typeof extraCellClass === "string" ? extraCellClass.trim() : "";
4600
+ }) || "").trim() : "";
4432
4601
  return /*#__PURE__*/React.createElement("div", {
4433
4602
  key: column.key,
4434
- className: cn$1("flex justify-between items-center flex-row py-2 border-b last:border-b-0", safeExtraCellClass)
4603
+ className: cn$1("flex justify-between items-center flex-row py-2 border-b last:border-b-0", extraCellClass)
4435
4604
  }, /*#__PURE__*/React.createElement("div", {
4436
4605
  className: "text-xs font-medium text-gray-500 uppercase tracking-wide mb-1 sm:mb-0 sm:w-1/3 sm:pr-2"
4437
4606
  }, column.label), /*#__PURE__*/React.createElement("div", {
@@ -4443,7 +4612,7 @@ var Table = function Table(_ref) {
4443
4612
  row: row,
4444
4613
  index: globalIndex
4445
4614
  })));
4446
- };
4615
+ }, [getRowMetadata, hasDetails, selectable, showSerial, withAction, expandedRows, selectedRows, onRowClick, visibleColumns, renderCell, cellClass, DetailsComponent, actionsToUse, actionAnchor, openActionKey, handleOnAction, actionMenuRef]);
4447
4616
 
4448
4617
  // Render mobile filters
4449
4618
  var renderMobileFilters = function renderMobileFilters() {
@@ -4511,23 +4680,11 @@ var Table = function Table(_ref) {
4511
4680
  htmlFor: "pagination-limit",
4512
4681
  className: "text-sm text-gray-600 whitespace-nowrap"
4513
4682
  }, "Show"), /*#__PURE__*/React.createElement(Select, {
4514
- options: Array.isArray(limitOptions) && limitOptions.length > 0 ? limitOptions.map(function (opt) {
4515
- return {
4516
- label: String(opt),
4517
- value: opt
4518
- };
4519
- }) : [25, 50, 100].map(function (opt) {
4520
- return {
4521
- label: String(opt),
4522
- value: opt
4523
- };
4524
- }),
4683
+ options: limitOptionsMemo,
4525
4684
  value: limit,
4526
4685
  allowClear: false,
4527
4686
  placeholder: "",
4528
- onChange: function onChange(e) {
4529
- return handleLimitChange(e);
4530
- },
4687
+ onChange: handleLimitChange,
4531
4688
  className: "w-20 md:w-30"
4532
4689
  }), /*#__PURE__*/React.createElement("p", {
4533
4690
  className: "text-sm text-gray-600 whitespace-nowrap"
@@ -4699,33 +4856,16 @@ var Table = function Table(_ref) {
4699
4856
  }, loading ? /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", {
4700
4857
  colSpan: visibleCount,
4701
4858
  className: "px-6 py-8 text-center"
4702
- }, /*#__PURE__*/React.createElement("div", {
4703
- className: "flex flex-col items-center gap-2"
4704
- }, /*#__PURE__*/React.createElement("div", {
4705
- className: "h-6 w-6 animate-spin rounded-full border-2 border-primary-600 border-t-transparent"
4706
- }), /*#__PURE__*/React.createElement("span", {
4707
- className: "text-sm text-gray-500"
4708
- }, "Loading...")))) : paginatedData.length === 0 ? /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", {
4859
+ }, /*#__PURE__*/React.createElement(LoadingState, null))) : paginatedData.length === 0 ? /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", {
4709
4860
  colSpan: visibleCount,
4710
4861
  className: "px-6 py-8 text-center"
4711
- }, /*#__PURE__*/React.createElement("div", {
4712
- className: "flex flex-col items-center gap-2"
4713
- }, /*#__PURE__*/React.createElement("div", {
4714
- className: "text-gray-400"
4715
- }, /*#__PURE__*/React.createElement(List, {
4716
- className: "h-8 w-8"
4717
- })), /*#__PURE__*/React.createElement("span", {
4718
- className: "text-sm text-gray-500"
4719
- }, "No results found.")))) : paginatedData.map(function (row, rowIndexInPage) {
4720
- var globalIndex = startIndex + rowIndexInPage;
4721
- var key = getRowKey(row, globalIndex);
4722
- var actionCellKey = "actions-".concat(key);
4723
- var extraRowClass = typeof rowClass === "function" ? rowClass({
4724
- row: row,
4725
- rowIndex: globalIndex
4726
- }) : "";
4727
- var safeExtraRowClass = typeof extraRowClass === "string" ? extraRowClass.trim() : "";
4728
- var stripeBg = stripedRows && !selectedRows.has(key) ? stripedColors[globalIndex % stripedColors.length] : undefined;
4862
+ }, /*#__PURE__*/React.createElement(EmptyState, null))) : paginatedData.map(function (row, rowIndexInPage) {
4863
+ var _getRowMetadata2 = getRowMetadata(row, rowIndexInPage),
4864
+ globalIndex = _getRowMetadata2.globalIndex,
4865
+ key = _getRowMetadata2.key,
4866
+ actionCellKey = _getRowMetadata2.actionCellKey,
4867
+ extraRowClass = _getRowMetadata2.extraRowClass,
4868
+ stripeBg = _getRowMetadata2.stripeBg;
4729
4869
  return /*#__PURE__*/React.createElement(React.Fragment, {
4730
4870
  key: key
4731
4871
  }, /*#__PURE__*/React.createElement("tr", {
@@ -4735,7 +4875,7 @@ var Table = function Table(_ref) {
4735
4875
  className: cn$1("hover:bg-gray-50", {
4736
4876
  "cursor-pointer": !!onRowClick,
4737
4877
  "bg-primary-50": selectedRows.has(key)
4738
- }, safeExtraRowClass),
4878
+ }, extraRowClass),
4739
4879
  onClick: function onClick() {
4740
4880
  return onRowClick === null || onRowClick === void 0 ? void 0 : onRowClick(row, globalIndex);
4741
4881
  }
@@ -4763,16 +4903,15 @@ var Table = function Table(_ref) {
4763
4903
  return handleSelectRow(row, rowIndexInPage);
4764
4904
  }
4765
4905
  })), visibleColumns.map(function (column, columnIndex) {
4766
- var extraCellClass = typeof cellClass === "function" ? cellClass({
4906
+ var extraCellClass = typeof cellClass === "function" ? (cellClass({
4767
4907
  row: row,
4768
4908
  rowIndex: globalIndex,
4769
4909
  column: column,
4770
4910
  columnIndex: columnIndex
4771
- }) : "";
4772
- var safeExtraCellClass = typeof extraCellClass === "string" ? extraCellClass.trim() : "";
4911
+ }) || "").trim() : "";
4773
4912
  return /*#__PURE__*/React.createElement("td", {
4774
4913
  key: column.key,
4775
- className: cn$1("px-6 py-4 whitespace-nowrap text-sm text-gray-900", safeExtraCellClass)
4914
+ className: cn$1("px-6 py-4 whitespace-nowrap text-sm text-gray-900", extraCellClass)
4776
4915
  }, renderCell(column, row, globalIndex));
4777
4916
  }), withAction && /*#__PURE__*/React.createElement("td", {
4778
4917
  className: "px-4 py-4 text-sm text-right",
@@ -4815,23 +4954,9 @@ var Table = function Table(_ref) {
4815
4954
  className: "p-4"
4816
4955
  }, loading ? /*#__PURE__*/React.createElement("div", {
4817
4956
  className: "py-8 text-center"
4818
- }, /*#__PURE__*/React.createElement("div", {
4819
- className: "flex flex-col items-center gap-2"
4820
- }, /*#__PURE__*/React.createElement("div", {
4821
- className: "h-6 w-6 animate-spin rounded-full border-2 border-primary-600 border-t-transparent"
4822
- }), /*#__PURE__*/React.createElement("span", {
4823
- className: "text-sm text-gray-500"
4824
- }, "Loading..."))) : paginatedData.length === 0 ? /*#__PURE__*/React.createElement("div", {
4957
+ }, /*#__PURE__*/React.createElement(LoadingState, null)) : paginatedData.length === 0 ? /*#__PURE__*/React.createElement("div", {
4825
4958
  className: "py-8 text-center"
4826
- }, /*#__PURE__*/React.createElement("div", {
4827
- className: "flex flex-col items-center gap-2"
4828
- }, /*#__PURE__*/React.createElement("div", {
4829
- className: "text-gray-400"
4830
- }, /*#__PURE__*/React.createElement(List, {
4831
- className: "h-8 w-8"
4832
- })), /*#__PURE__*/React.createElement("span", {
4833
- className: "text-sm text-gray-500"
4834
- }, "No results found."))) : paginatedData.map(function (row, rowIndexInPage) {
4959
+ }, /*#__PURE__*/React.createElement(EmptyState, null)) : paginatedData.map(function (row, rowIndexInPage) {
4835
4960
  return renderMobileCard(row, rowIndexInPage);
4836
4961
  })), pagination && /*#__PURE__*/React.createElement("div", {
4837
4962
  className: "p-4 border-t"
@@ -4851,19 +4976,7 @@ var Table = function Table(_ref) {
4851
4976
  }
4852
4977
  }, "Previous"), /*#__PURE__*/React.createElement("div", {
4853
4978
  className: "flex items-center gap-1"
4854
- }, Array.from({
4855
- length: Math.min(5, totalPages)
4856
- }, function (_, i) {
4857
- var pageNum;
4858
- if (totalPages <= 5) {
4859
- pageNum = i + 1;
4860
- } else if (currentPage <= 3) {
4861
- pageNum = i + 1;
4862
- } else if (currentPage >= totalPages - 2) {
4863
- pageNum = totalPages - 4 + i;
4864
- } else {
4865
- pageNum = currentPage - 2 + i;
4866
- }
4979
+ }, getPaginationNumbers(currentPage, totalPages).map(function (pageNum) {
4867
4980
  return /*#__PURE__*/React.createElement("button", {
4868
4981
  key: pageNum,
4869
4982
  className: cn$1("h-8 w-8 rounded-lg md:rounded-md text-sm", {
@@ -4884,6 +4997,12 @@ var Table = function Table(_ref) {
4884
4997
  }
4885
4998
  }, "Next")))));
4886
4999
  };
5000
+
5001
+ // ActionMenuPortal constants
5002
+ var MENU_WIDTH = 180;
5003
+ var MAX_MENU_HEIGHT = 320;
5004
+ var MENU_MARGIN = 8;
5005
+ var MIN_MENU_HEIGHT = 80;
4887
5006
  function ActionMenuPortal(_ref2) {
4888
5007
  var anchorElem = _ref2.anchorElem,
4889
5008
  anchorRow = _ref2.anchorRow,
@@ -4897,16 +5016,12 @@ function ActionMenuPortal(_ref2) {
4897
5016
  top: 0,
4898
5017
  transformOrigin: "top right",
4899
5018
  maxHeight: 300,
4900
- width: 180,
5019
+ width: MENU_WIDTH,
4901
5020
  opacity: 0
4902
5021
  }),
4903
5022
  _useState30 = _slicedToArray(_useState29, 2),
4904
5023
  style = _useState30[0],
4905
5024
  setStyle = _useState30[1];
4906
- var menuWidth = 180;
4907
- var maxMenuHeight = 320;
4908
- var margin = 8;
4909
- var minMenuHeight = 80;
4910
5025
 
4911
5026
  // compute position using actual menu height (if available) and layout measurements
4912
5027
  var computePosition = function computePosition() {
@@ -4918,17 +5033,17 @@ function ActionMenuPortal(_ref2) {
4918
5033
  var spaceAbove = rect.top;
4919
5034
 
4920
5035
  // allowed max height based on available space
4921
- var allowedMaxHeight = Math.min(maxMenuHeight, Math.max(minMenuHeight, Math.max(spaceBelow - margin, spaceAbove - margin)));
5036
+ var allowedMaxHeight = Math.min(MAX_MENU_HEIGHT, Math.max(MIN_MENU_HEIGHT, Math.max(spaceBelow - MENU_MARGIN, spaceAbove - MENU_MARGIN)));
4922
5037
 
4923
5038
  // choose placement by comparing actual available spaces
4924
5039
  var placement = "bottom";
4925
- if (spaceBelow < 160 && spaceAbove > spaceBelow) {
5040
+ if (spaceBelow < MENU_PLACEMENT_THRESHOLD && spaceAbove > spaceBelow) {
4926
5041
  placement = "top";
4927
5042
  // when placing on top we should cap allowedMaxHeight by spaceAbove
4928
- allowedMaxHeight = Math.min(maxMenuHeight, Math.max(minMenuHeight, spaceAbove - margin));
5043
+ allowedMaxHeight = Math.min(MAX_MENU_HEIGHT, Math.max(MIN_MENU_HEIGHT, spaceAbove - MENU_MARGIN));
4929
5044
  } else {
4930
5045
  // placing bottom: cap by spaceBelow
4931
- allowedMaxHeight = Math.min(maxMenuHeight, Math.max(minMenuHeight, spaceBelow - margin));
5046
+ allowedMaxHeight = Math.min(MAX_MENU_HEIGHT, Math.max(MIN_MENU_HEIGHT, spaceBelow - MENU_MARGIN));
4932
5047
  }
4933
5048
 
4934
5049
  // measure the menu's content height if we can, and compute the actual menu height we'll use.
@@ -4945,23 +5060,23 @@ function ActionMenuPortal(_ref2) {
4945
5060
  var top;
4946
5061
  if (placement === "top") {
4947
5062
  // place menu so its bottom sits just above the anchor (rect.top - margin)
4948
- top = rect.top + scrollY - measuredMenuHeight - margin;
5063
+ top = rect.top + scrollY - measuredMenuHeight - MENU_MARGIN;
4949
5064
  } else {
4950
5065
  // place menu just below the anchor (rect.bottom + margin)
4951
- top = rect.bottom + scrollY + margin;
5066
+ top = rect.bottom + scrollY + MENU_MARGIN;
4952
5067
  }
4953
5068
 
4954
5069
  // clamp top to viewport (so it never goes off-screen)
4955
- var minTop = margin + scrollY;
4956
- var maxTop = window.innerHeight + scrollY - measuredMenuHeight - margin;
5070
+ var minTop = MENU_MARGIN + scrollY;
5071
+ var maxTop = window.innerHeight + scrollY - measuredMenuHeight - MENU_MARGIN;
4957
5072
  if (top < minTop) top = minTop;
4958
5073
  if (top > maxTop) top = maxTop;
4959
5074
 
4960
5075
  // compute left and clamp horizontally
4961
- var left = rect.right + scrollX - menuWidth;
4962
- if (left < margin) left = margin;
4963
- if (left + menuWidth > window.innerWidth - margin) {
4964
- left = Math.max(margin, window.innerWidth - menuWidth - margin);
5076
+ var left = rect.right + scrollX - MENU_WIDTH;
5077
+ if (left < MENU_MARGIN) left = MENU_MARGIN;
5078
+ if (left + MENU_WIDTH > window.innerWidth - MENU_MARGIN) {
5079
+ left = Math.max(MENU_MARGIN, window.innerWidth - MENU_WIDTH - MENU_MARGIN);
4965
5080
  }
4966
5081
  var transformOrigin = placement === "bottom" ? "top right" : "bottom right";
4967
5082
 
@@ -4971,7 +5086,7 @@ function ActionMenuPortal(_ref2) {
4971
5086
  top: top,
4972
5087
  transformOrigin: transformOrigin,
4973
5088
  maxHeight: allowedMaxHeight,
4974
- width: menuWidth,
5089
+ width: MENU_WIDTH,
4975
5090
  opacity: 1
4976
5091
  });
4977
5092
  };