trotl-table 1.0.97 → 1.0.98

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/README.md CHANGED
@@ -127,6 +127,8 @@ export default function Demo() {
127
127
  - `extraSearchTerm`: string to filter + highlight rows
128
128
  - `rowHeight`: row height in px
129
129
  - `groupHeaderHeight`: group header height in px
130
+ - `mobileColumnCount`: number of data columns shown at viewport widths of 768px or less (1 or 2; default `2`). The full row details are shown in a modal when a mobile row is clicked.
131
+ - `mobileRowClick`: optional callback `(row) => {}` invoked instead of opening the built-in details modal when a mobile row is clicked. Clicks on row controls and action buttons keep their existing behavior.
130
132
  - `showKey`: show row key (true/false)
131
133
  - `enableDragRow`: enable drag‑and‑drop (true/false)
132
134
  - `selectedRowsCallback`: callback with selected row IDs
@@ -174,4 +176,3 @@ function Toolbar({ tableId }) {
174
176
  ## 🏆 License
175
177
 
176
178
  MIT — free to use, modify, and share.
177
-
package/dist/Table.cjs.js CHANGED
@@ -9687,6 +9687,8 @@ function TableInner({
9687
9687
  tableId = "default",
9688
9688
  columns = [],
9689
9689
  data = [],
9690
+ mobileColumnCount = 2,
9691
+ mobileRowClick = null,
9690
9692
  highlightRow = null,
9691
9693
  isGrouped = false,
9692
9694
  groupKey = "groupId",
@@ -9754,6 +9756,8 @@ function TableInner({
9754
9756
  share: "Share",
9755
9757
  duplicate: "Duplicate",
9756
9758
  download: "Download",
9759
+ details: "Details",
9760
+ close: "Close",
9757
9761
  confirmDelete: "Confirm Delete",
9758
9762
  yesDelete: "Yes, Delete",
9759
9763
  areYouSureYouWantToDelete: "Are you sure you want to delete",
@@ -9777,6 +9781,7 @@ function TableInner({
9777
9781
  const duplicateCallbackRef = React.useRef(duplicateCallback);
9778
9782
  const downloadCallbackRef = React.useRef(downloadCallback);
9779
9783
  const undeleteCallbackRef = React.useRef(undeleteCallback);
9784
+ const mobileRowClickRef = React.useRef(mobileRowClick);
9780
9785
  const onCellChangeRef = React.useRef(onCellChange);
9781
9786
  const cellCallbackRef = React.useRef(cellCallback);
9782
9787
 
@@ -9788,9 +9793,16 @@ function TableInner({
9788
9793
  editCallbackRef.current = editCallback;
9789
9794
  deleteCallbackRef.current = deleteCallback;
9790
9795
  undeleteCallbackRef.current = undeleteCallback;
9796
+ mobileRowClickRef.current = mobileRowClick;
9791
9797
  onCellChangeRef.current = onCellChange;
9792
9798
  cellCallbackRef.current = cellCallback;
9793
9799
  });
9800
+ const [isMobile, setIsMobile] = React.useState(() => typeof window !== "undefined" && window.innerWidth <= 768);
9801
+ React.useEffect(() => {
9802
+ const updateMobileState = () => setIsMobile(window.innerWidth <= 768);
9803
+ window.addEventListener("resize", updateMobileState);
9804
+ return () => window.removeEventListener("resize", updateMobileState);
9805
+ }, []);
9794
9806
 
9795
9807
  // Context menu right click state
9796
9808
  const [contextMenu, setContextMenu] = React.useState({
@@ -9876,6 +9888,22 @@ function TableInner({
9876
9888
  flex: "1 1 0%"
9877
9889
  };
9878
9890
  }, [columnWidths, getColumnKey]);
9891
+ const allColumns = React.useMemo(() => {
9892
+ const composedColumns = [...columns];
9893
+ if (Array.isArray(customColumns)) {
9894
+ customColumns.forEach(col => {
9895
+ if (typeof col.place === "number") {
9896
+ composedColumns.splice(col.place, 0, {
9897
+ ...col,
9898
+ isCustom: true
9899
+ });
9900
+ }
9901
+ });
9902
+ }
9903
+ return composedColumns;
9904
+ }, [columns, customColumns]);
9905
+ const normalizedMobileColumnCount = Math.min(2, Math.max(1, Math.floor(Number(mobileColumnCount) || 2)));
9906
+ const visibleColumns = isMobile ? allColumns.slice(0, normalizedMobileColumnCount) : allColumns;
9879
9907
  const getNestedValue = React.useCallback((row, accessor) => {
9880
9908
  if (!row || accessor == null) return undefined;
9881
9909
  if (Array.isArray(accessor)) {
@@ -10254,6 +10282,21 @@ function TableInner({
10254
10282
  }).filter(Boolean);
10255
10283
  return [...new Set(parts)].join(", ");
10256
10284
  }, []);
10285
+ const formatMobileDetailValue = React.useCallback((value, col) => {
10286
+ if (value == null || value === "") return "...";
10287
+ if (typeof value === "boolean") return value ? "✓" : "✗";
10288
+ if (Array.isArray(value)) return formatArrayValue(value, col) || "...";
10289
+ if (value instanceof Date) {
10290
+ return formatDateValue(value, getColumnDateFormat(col)) || "...";
10291
+ }
10292
+ if (getColumnType(col) === "date" || col?.dateFormat) {
10293
+ return formatDateValue(value, getColumnDateFormat(col)) || String(value);
10294
+ }
10295
+ if (typeof value === "object") {
10296
+ return String(value.label ?? value.name ?? value.value ?? value);
10297
+ }
10298
+ return String(col?.translate ? translate(String(value)) : value);
10299
+ }, [formatArrayValue, translate]);
10257
10300
 
10258
10301
  // console.log(extraSearchTerm)
10259
10302
 
@@ -10568,6 +10611,7 @@ function TableInner({
10568
10611
  // DELETE
10569
10612
  const [showConfirm, setShowConfirm] = React.useState(false);
10570
10613
  const [pendingDelete, setPendingDelete] = React.useState(null);
10614
+ const [mobileDetailRow, setMobileDetailRow] = React.useState(null);
10571
10615
 
10572
10616
  // perform deletion for a given row (shared by immediate & modal-confirm flows)
10573
10617
  const doDelete = React.useCallback(async row => {
@@ -11163,19 +11207,6 @@ function TableInner({
11163
11207
  const visualIndex = tableDataFlat.slice(0, index).filter(i => i.type === "row").length + 1;
11164
11208
  const highlightRowStyle = getHighlightRowStyle(row);
11165
11209
 
11166
- // Compose columns + custom columns
11167
- const allColumns = [...columns];
11168
- // Insert custom columns at specified places
11169
- if (Array.isArray(customColumns)) {
11170
- customColumns.forEach(col => {
11171
- if (typeof col.place === "number") {
11172
- allColumns.splice(col.place, 0, {
11173
- ...col,
11174
- isCustom: true
11175
- });
11176
- }
11177
- });
11178
- }
11179
11210
  // handler called when user double-clicks the non-action portion of the row
11180
11211
  const handleRowDoubleClick = e => {
11181
11212
  if (!doubleClickEnable) return;
@@ -11185,13 +11216,24 @@ function TableInner({
11185
11216
  }
11186
11217
  editCallbackRef.current(row);
11187
11218
  };
11219
+ const handleMobileRowClick = event => {
11220
+ if (!isMobile || event.target.closest(".checkbox-cell, .action-cell, button, input, select, textarea, a, [contenteditable='true']")) {
11221
+ return;
11222
+ }
11223
+ if (typeof mobileRowClickRef.current === "function") {
11224
+ mobileRowClickRef.current(row);
11225
+ } else {
11226
+ setMobileDetailRow(row);
11227
+ }
11228
+ };
11188
11229
  const content = /*#__PURE__*/React.createElement("div", {
11189
11230
  key: key,
11190
11231
  style: highlightRowStyle ? {
11191
11232
  ...style,
11192
11233
  ...highlightRowStyle
11193
11234
  } : style,
11194
- className: "table-row",
11235
+ className: isMobile ? "table-row mobile-clickable" : "table-row",
11236
+ onClick: isMobile ? handleMobileRowClick : undefined,
11195
11237
  onContextMenu: e => {
11196
11238
  if (!Array.isArray(enableMouseRightClick) || enableMouseRightClick.length === 0) return;
11197
11239
  e.preventDefault();
@@ -11225,14 +11267,20 @@ function TableInner({
11225
11267
  minWidth: keyWidth,
11226
11268
  maxWidth: keyWidth
11227
11269
  } : undefined
11228
- }, visualIndex), allColumns.map((col, i) => {
11270
+ }, visualIndex), visibleColumns.map((col, i) => {
11229
11271
  const resolvedAccessor = getAccessorKey(row, col.accessor);
11230
11272
  const cellValue = getAccessorValue(row, col.accessor);
11231
11273
  const cellStyle = getCellStyle(col, i);
11232
11274
  return /*#__PURE__*/React.createElement("div", {
11233
11275
  key: i,
11234
11276
  className: "table-cell",
11235
- style: cellStyle,
11277
+ style: isMobile ? {
11278
+ ...cellStyle,
11279
+ flex: "1 1 0%",
11280
+ width: undefined,
11281
+ minWidth: 0,
11282
+ maxWidth: "100%"
11283
+ } : cellStyle,
11236
11284
  onClick: () => triggerCellCallback(row, col)
11237
11285
  }, col.isCustom ? typeof col.render === "function" ? col.render(row, i) : col.render : renderCell(cellValue, resolvedAccessor, row, col));
11238
11286
  })), showActions && /*#__PURE__*/React.createElement("div", {
@@ -11317,7 +11365,7 @@ function TableInner({
11317
11365
  }, content);
11318
11366
  }
11319
11367
  return content;
11320
- }, [tableDataFlat, columns, selectedRows, toggleRowSelection, groupRowsById, renderCell, getCellStyle, showActions, showDelete, showEdit, showKey, showView, translate, enableDragRow, moveRow, enableMultiSelect, rowHeight, tableId, customColumns, doubleClickEnable, keyWidth, actionColumnStyle, showDuplicate, showDownload, showIcons, showShare, getHighlightRowStyle, triggerCellCallback, getAccessorKey, getAccessorValue]);
11368
+ }, [tableDataFlat, visibleColumns, selectedRows, toggleRowSelection, groupRowsById, renderCell, getCellStyle, showActions, showDelete, showEdit, showKey, showView, translate, enableDragRow, moveRow, enableMultiSelect, rowHeight, tableId, customColumns, isMobile, doubleClickEnable, keyWidth, actionColumnStyle, showDuplicate, showDownload, showIcons, showShare, getHighlightRowStyle, triggerCellCallback, getAccessorKey, getAccessorValue]);
11321
11369
  const rightClickActions = Array.isArray(enableMouseRightClick) ? enableMouseRightClick : [];
11322
11370
  const [hoveredActionIndex, setHoveredActionIndex] = React.useState(null);
11323
11371
  const rowHeightGetter = ({
@@ -11347,19 +11395,6 @@ function TableInner({
11347
11395
  externalDrop(node);
11348
11396
  }
11349
11397
  }, [useExternalDndContext, enableExternalRowDrop, externalDrop]);
11350
-
11351
- // Compose columns + custom columns for header
11352
- const allColumns = [...columns];
11353
- if (Array.isArray(customColumns)) {
11354
- customColumns.forEach(col => {
11355
- if (typeof col.place === "number") {
11356
- allColumns.splice(col.place, 0, {
11357
- ...col,
11358
- isCustom: true
11359
- });
11360
- }
11361
- });
11362
- }
11363
11398
  const tableMarkup = /*#__PURE__*/React.createElement("div", {
11364
11399
  className: "table-container",
11365
11400
  ref: setTableRef,
@@ -11400,14 +11435,20 @@ function TableInner({
11400
11435
  minWidth: keyWidth,
11401
11436
  maxWidth: keyWidth
11402
11437
  } : undefined
11403
- }, "#"), allColumns.map((col, i) => {
11438
+ }, "#"), visibleColumns.map((col, i) => {
11404
11439
  const cellStyle = getCellStyle(col, i);
11405
11440
  const translatedHeader = typeof col.header === "string" ? translate(col.header) : col.header;
11406
11441
  const headerTitle = typeof translatedHeader === "string" ? translatedHeader : undefined;
11407
11442
  return /*#__PURE__*/React.createElement("div", {
11408
11443
  key: i,
11409
11444
  className: "table-cell header-cell",
11410
- style: cellStyle,
11445
+ style: isMobile ? {
11446
+ ...cellStyle,
11447
+ flex: "1 1 0%",
11448
+ width: undefined,
11449
+ minWidth: 0,
11450
+ maxWidth: "100%"
11451
+ } : cellStyle,
11411
11452
  title: headerTitle,
11412
11453
  onClick: col.isCustom ? undefined : () => setSort(col.accessor)
11413
11454
  }, col.isCustom ? translatedHeader ?? "" : translatedHeader, !col.isCustom && isSameAccessor(sorting?.column, col.accessor) && (sorting.direction === "asc" ? " ↑" : " ↓"), /*#__PURE__*/React.createElement("div", {
@@ -11500,7 +11541,22 @@ function TableInner({
11500
11541
  }, typeof contextMenuModal.component === "function" ? /*#__PURE__*/React.createElement(contextMenuModal.component, {
11501
11542
  row: contextMenu.row
11502
11543
  }) : contextMenuModal.component) : null;
11503
- return /*#__PURE__*/React.createElement(React.Fragment, null, tableMarkup, contextMenuMarkup, contextMenuModalMarkup);
11544
+ const mobileDetailModalMarkup = mobileDetailRow ? /*#__PURE__*/React.createElement(Modal.default, {
11545
+ isOpen: true,
11546
+ title: String(mobileDetailRow.name ?? mobileDetailRow.title ?? translate("details")),
11547
+ onCancel: () => setMobileDetailRow(null),
11548
+ cancelLabel: translate("close"),
11549
+ showCancel: true
11550
+ }, /*#__PURE__*/React.createElement("dl", {
11551
+ className: "mobile-row-details"
11552
+ }, allColumns.map((col, index) => {
11553
+ const header = typeof col.header === "string" ? translate(col.header) : col.header;
11554
+ const value = col.isCustom ? typeof col.render === "function" ? col.render(mobileDetailRow, index) : col.render ?? "..." : formatMobileDetailValue(getAccessorValue(mobileDetailRow, col.accessor), col);
11555
+ return /*#__PURE__*/React.createElement(React.Fragment, {
11556
+ key: index
11557
+ }, /*#__PURE__*/React.createElement("dt", null, header), /*#__PURE__*/React.createElement("dd", null, value));
11558
+ }))) : null;
11559
+ return /*#__PURE__*/React.createElement(React.Fragment, null, tableMarkup, contextMenuMarkup, contextMenuModalMarkup, mobileDetailModalMarkup);
11504
11560
  }
11505
11561
  function Table(props) {
11506
11562
  const {