trotl-table 1.0.97 → 1.0.99
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 +3 -1
- package/dist/Table.cjs.js +130 -34
- package/dist/Table.cjs.js.map +1 -1
- package/dist/Table.esm.js +130 -34
- package/dist/Table.esm.js.map +1 -1
- package/dist/WorkOrderAnalyticsByClient.cjs.js +417 -0
- package/dist/WorkOrderAnalyticsByClient.cjs.js.map +1 -0
- package/dist/WorkOrderAnalyticsByClient.esm.js +412 -0
- package/dist/WorkOrderAnalyticsByClient.esm.js.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,6 +127,9 @@ 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.
|
|
132
|
+
- On mobile, when `enableMouseRightClick` actions are provided, double-tapping a row opens the same context menu as a right click (a single tap then opens details after a short ~280ms delay).
|
|
130
133
|
- `showKey`: show row key (true/false)
|
|
131
134
|
- `enableDragRow`: enable drag‑and‑drop (true/false)
|
|
132
135
|
- `selectedRowsCallback`: callback with selected row IDs
|
|
@@ -174,4 +177,3 @@ function Toolbar({ tableId }) {
|
|
|
174
177
|
## 🏆 License
|
|
175
178
|
|
|
176
179
|
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,11 @@ 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);
|
|
9785
|
+
const lastTapRef = React.useRef({
|
|
9786
|
+
timer: null,
|
|
9787
|
+
rowId: null
|
|
9788
|
+
});
|
|
9780
9789
|
const onCellChangeRef = React.useRef(onCellChange);
|
|
9781
9790
|
const cellCallbackRef = React.useRef(cellCallback);
|
|
9782
9791
|
|
|
@@ -9788,9 +9797,16 @@ function TableInner({
|
|
|
9788
9797
|
editCallbackRef.current = editCallback;
|
|
9789
9798
|
deleteCallbackRef.current = deleteCallback;
|
|
9790
9799
|
undeleteCallbackRef.current = undeleteCallback;
|
|
9800
|
+
mobileRowClickRef.current = mobileRowClick;
|
|
9791
9801
|
onCellChangeRef.current = onCellChange;
|
|
9792
9802
|
cellCallbackRef.current = cellCallback;
|
|
9793
9803
|
});
|
|
9804
|
+
const [isMobile, setIsMobile] = React.useState(() => typeof window !== "undefined" && window.innerWidth <= 768);
|
|
9805
|
+
React.useEffect(() => {
|
|
9806
|
+
const updateMobileState = () => setIsMobile(window.innerWidth <= 768);
|
|
9807
|
+
window.addEventListener("resize", updateMobileState);
|
|
9808
|
+
return () => window.removeEventListener("resize", updateMobileState);
|
|
9809
|
+
}, []);
|
|
9794
9810
|
|
|
9795
9811
|
// Context menu right click state
|
|
9796
9812
|
const [contextMenu, setContextMenu] = React.useState({
|
|
@@ -9876,6 +9892,22 @@ function TableInner({
|
|
|
9876
9892
|
flex: "1 1 0%"
|
|
9877
9893
|
};
|
|
9878
9894
|
}, [columnWidths, getColumnKey]);
|
|
9895
|
+
const allColumns = React.useMemo(() => {
|
|
9896
|
+
const composedColumns = [...columns];
|
|
9897
|
+
if (Array.isArray(customColumns)) {
|
|
9898
|
+
customColumns.forEach(col => {
|
|
9899
|
+
if (typeof col.place === "number") {
|
|
9900
|
+
composedColumns.splice(col.place, 0, {
|
|
9901
|
+
...col,
|
|
9902
|
+
isCustom: true
|
|
9903
|
+
});
|
|
9904
|
+
}
|
|
9905
|
+
});
|
|
9906
|
+
}
|
|
9907
|
+
return composedColumns;
|
|
9908
|
+
}, [columns, customColumns]);
|
|
9909
|
+
const normalizedMobileColumnCount = Math.min(2, Math.max(1, Math.floor(Number(mobileColumnCount) || 2)));
|
|
9910
|
+
const visibleColumns = isMobile ? allColumns.slice(0, normalizedMobileColumnCount) : allColumns;
|
|
9879
9911
|
const getNestedValue = React.useCallback((row, accessor) => {
|
|
9880
9912
|
if (!row || accessor == null) return undefined;
|
|
9881
9913
|
if (Array.isArray(accessor)) {
|
|
@@ -10254,6 +10286,21 @@ function TableInner({
|
|
|
10254
10286
|
}).filter(Boolean);
|
|
10255
10287
|
return [...new Set(parts)].join(", ");
|
|
10256
10288
|
}, []);
|
|
10289
|
+
const formatMobileDetailValue = React.useCallback((value, col) => {
|
|
10290
|
+
if (value == null || value === "") return "...";
|
|
10291
|
+
if (typeof value === "boolean") return value ? "✓" : "✗";
|
|
10292
|
+
if (Array.isArray(value)) return formatArrayValue(value, col) || "...";
|
|
10293
|
+
if (value instanceof Date) {
|
|
10294
|
+
return formatDateValue(value, getColumnDateFormat(col)) || "...";
|
|
10295
|
+
}
|
|
10296
|
+
if (getColumnType(col) === "date" || col?.dateFormat) {
|
|
10297
|
+
return formatDateValue(value, getColumnDateFormat(col)) || String(value);
|
|
10298
|
+
}
|
|
10299
|
+
if (typeof value === "object") {
|
|
10300
|
+
return String(value.label ?? value.name ?? value.value ?? value);
|
|
10301
|
+
}
|
|
10302
|
+
return String(col?.translate ? translate(String(value)) : value);
|
|
10303
|
+
}, [formatArrayValue, translate]);
|
|
10257
10304
|
|
|
10258
10305
|
// console.log(extraSearchTerm)
|
|
10259
10306
|
|
|
@@ -10568,6 +10615,7 @@ function TableInner({
|
|
|
10568
10615
|
// DELETE
|
|
10569
10616
|
const [showConfirm, setShowConfirm] = React.useState(false);
|
|
10570
10617
|
const [pendingDelete, setPendingDelete] = React.useState(null);
|
|
10618
|
+
const [mobileDetailRow, setMobileDetailRow] = React.useState(null);
|
|
10571
10619
|
|
|
10572
10620
|
// perform deletion for a given row (shared by immediate & modal-confirm flows)
|
|
10573
10621
|
const doDelete = React.useCallback(async row => {
|
|
@@ -10935,7 +10983,7 @@ function TableInner({
|
|
|
10935
10983
|
const showUndelete = buttons.includes("undelete");
|
|
10936
10984
|
const showDuplicate = buttons.includes("duplicate") || buttons.includes("duplicates");
|
|
10937
10985
|
const showDownload = buttons.includes("download");
|
|
10938
|
-
const showActions = showView || showEdit || showDelete || showShare || showUndelete || showDuplicate || showDownload;
|
|
10986
|
+
const showActions = !isMobile && (showView || showEdit || showDelete || showShare || showUndelete || showDuplicate || showDownload);
|
|
10939
10987
|
const visibleActionCount = [showView, showEdit, showDuplicate, showDelete, showShare, showUndelete, showDownload].filter(Boolean).length;
|
|
10940
10988
|
const actionColumnStyle = React.useMemo(() => {
|
|
10941
10989
|
if (!showActions) return undefined;
|
|
@@ -11163,19 +11211,6 @@ function TableInner({
|
|
|
11163
11211
|
const visualIndex = tableDataFlat.slice(0, index).filter(i => i.type === "row").length + 1;
|
|
11164
11212
|
const highlightRowStyle = getHighlightRowStyle(row);
|
|
11165
11213
|
|
|
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
11214
|
// handler called when user double-clicks the non-action portion of the row
|
|
11180
11215
|
const handleRowDoubleClick = e => {
|
|
11181
11216
|
if (!doubleClickEnable) return;
|
|
@@ -11185,13 +11220,60 @@ function TableInner({
|
|
|
11185
11220
|
}
|
|
11186
11221
|
editCallbackRef.current(row);
|
|
11187
11222
|
};
|
|
11223
|
+
const handleMobileRowClick = event => {
|
|
11224
|
+
if (!isMobile || event.target.closest(".checkbox-cell, .action-cell, button, input, select, textarea, a, [contenteditable='true']")) {
|
|
11225
|
+
return;
|
|
11226
|
+
}
|
|
11227
|
+
const openSingleTap = () => {
|
|
11228
|
+
if (typeof mobileRowClickRef.current === "function") {
|
|
11229
|
+
mobileRowClickRef.current(row);
|
|
11230
|
+
} else {
|
|
11231
|
+
setMobileDetailRow(row);
|
|
11232
|
+
}
|
|
11233
|
+
};
|
|
11234
|
+
const hasContextActions = Array.isArray(enableMouseRightClick) && enableMouseRightClick.length > 0;
|
|
11235
|
+
if (!hasContextActions) {
|
|
11236
|
+
openSingleTap();
|
|
11237
|
+
return;
|
|
11238
|
+
}
|
|
11239
|
+
|
|
11240
|
+
// Double tap opens the context menu; a single tap waits briefly so
|
|
11241
|
+
// the first tap of a double tap does not open the details modal.
|
|
11242
|
+
const tap = lastTapRef.current;
|
|
11243
|
+
if (tap.timer && tap.rowId === row.id) {
|
|
11244
|
+
window.clearTimeout(tap.timer);
|
|
11245
|
+
lastTapRef.current = {
|
|
11246
|
+
timer: null,
|
|
11247
|
+
rowId: null
|
|
11248
|
+
};
|
|
11249
|
+
setContextMenu({
|
|
11250
|
+
visible: true,
|
|
11251
|
+
x: Math.max(0, Math.min(event.clientX, window.innerWidth - 180)),
|
|
11252
|
+
y: Math.max(0, Math.min(event.clientY, window.innerHeight - 160)),
|
|
11253
|
+
row
|
|
11254
|
+
});
|
|
11255
|
+
return;
|
|
11256
|
+
}
|
|
11257
|
+
window.clearTimeout(tap.timer);
|
|
11258
|
+
lastTapRef.current = {
|
|
11259
|
+
rowId: row.id,
|
|
11260
|
+
timer: window.setTimeout(() => {
|
|
11261
|
+
lastTapRef.current = {
|
|
11262
|
+
timer: null,
|
|
11263
|
+
rowId: null
|
|
11264
|
+
};
|
|
11265
|
+
openSingleTap();
|
|
11266
|
+
}, 280)
|
|
11267
|
+
};
|
|
11268
|
+
};
|
|
11188
11269
|
const content = /*#__PURE__*/React.createElement("div", {
|
|
11189
11270
|
key: key,
|
|
11190
11271
|
style: highlightRowStyle ? {
|
|
11191
11272
|
...style,
|
|
11192
11273
|
...highlightRowStyle
|
|
11193
11274
|
} : style,
|
|
11194
|
-
className: "table-row",
|
|
11275
|
+
className: isMobile ? "table-row mobile-clickable" : "table-row",
|
|
11276
|
+
onClick: isMobile ? handleMobileRowClick : undefined,
|
|
11195
11277
|
onContextMenu: e => {
|
|
11196
11278
|
if (!Array.isArray(enableMouseRightClick) || enableMouseRightClick.length === 0) return;
|
|
11197
11279
|
e.preventDefault();
|
|
@@ -11225,14 +11307,20 @@ function TableInner({
|
|
|
11225
11307
|
minWidth: keyWidth,
|
|
11226
11308
|
maxWidth: keyWidth
|
|
11227
11309
|
} : undefined
|
|
11228
|
-
}, visualIndex),
|
|
11310
|
+
}, visualIndex), visibleColumns.map((col, i) => {
|
|
11229
11311
|
const resolvedAccessor = getAccessorKey(row, col.accessor);
|
|
11230
11312
|
const cellValue = getAccessorValue(row, col.accessor);
|
|
11231
11313
|
const cellStyle = getCellStyle(col, i);
|
|
11232
11314
|
return /*#__PURE__*/React.createElement("div", {
|
|
11233
11315
|
key: i,
|
|
11234
11316
|
className: "table-cell",
|
|
11235
|
-
style:
|
|
11317
|
+
style: isMobile ? {
|
|
11318
|
+
...cellStyle,
|
|
11319
|
+
flex: "1 1 0%",
|
|
11320
|
+
width: undefined,
|
|
11321
|
+
minWidth: 0,
|
|
11322
|
+
maxWidth: "100%"
|
|
11323
|
+
} : cellStyle,
|
|
11236
11324
|
onClick: () => triggerCellCallback(row, col)
|
|
11237
11325
|
}, col.isCustom ? typeof col.render === "function" ? col.render(row, i) : col.render : renderCell(cellValue, resolvedAccessor, row, col));
|
|
11238
11326
|
})), showActions && /*#__PURE__*/React.createElement("div", {
|
|
@@ -11317,7 +11405,7 @@ function TableInner({
|
|
|
11317
11405
|
}, content);
|
|
11318
11406
|
}
|
|
11319
11407
|
return content;
|
|
11320
|
-
}, [tableDataFlat,
|
|
11408
|
+
}, [tableDataFlat, visibleColumns, selectedRows, toggleRowSelection, groupRowsById, renderCell, getCellStyle, showActions, showDelete, showEdit, showKey, showView, translate, enableDragRow, moveRow, enableMultiSelect, rowHeight, tableId, customColumns, isMobile, enableMouseRightClick, doubleClickEnable, keyWidth, actionColumnStyle, showDuplicate, showDownload, showIcons, showShare, getHighlightRowStyle, triggerCellCallback, getAccessorKey, getAccessorValue]);
|
|
11321
11409
|
const rightClickActions = Array.isArray(enableMouseRightClick) ? enableMouseRightClick : [];
|
|
11322
11410
|
const [hoveredActionIndex, setHoveredActionIndex] = React.useState(null);
|
|
11323
11411
|
const rowHeightGetter = ({
|
|
@@ -11347,19 +11435,6 @@ function TableInner({
|
|
|
11347
11435
|
externalDrop(node);
|
|
11348
11436
|
}
|
|
11349
11437
|
}, [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
11438
|
const tableMarkup = /*#__PURE__*/React.createElement("div", {
|
|
11364
11439
|
className: "table-container",
|
|
11365
11440
|
ref: setTableRef,
|
|
@@ -11400,14 +11475,20 @@ function TableInner({
|
|
|
11400
11475
|
minWidth: keyWidth,
|
|
11401
11476
|
maxWidth: keyWidth
|
|
11402
11477
|
} : undefined
|
|
11403
|
-
}, "#"),
|
|
11478
|
+
}, "#"), visibleColumns.map((col, i) => {
|
|
11404
11479
|
const cellStyle = getCellStyle(col, i);
|
|
11405
11480
|
const translatedHeader = typeof col.header === "string" ? translate(col.header) : col.header;
|
|
11406
11481
|
const headerTitle = typeof translatedHeader === "string" ? translatedHeader : undefined;
|
|
11407
11482
|
return /*#__PURE__*/React.createElement("div", {
|
|
11408
11483
|
key: i,
|
|
11409
11484
|
className: "table-cell header-cell",
|
|
11410
|
-
style:
|
|
11485
|
+
style: isMobile ? {
|
|
11486
|
+
...cellStyle,
|
|
11487
|
+
flex: "1 1 0%",
|
|
11488
|
+
width: undefined,
|
|
11489
|
+
minWidth: 0,
|
|
11490
|
+
maxWidth: "100%"
|
|
11491
|
+
} : cellStyle,
|
|
11411
11492
|
title: headerTitle,
|
|
11412
11493
|
onClick: col.isCustom ? undefined : () => setSort(col.accessor)
|
|
11413
11494
|
}, col.isCustom ? translatedHeader ?? "" : translatedHeader, !col.isCustom && isSameAccessor(sorting?.column, col.accessor) && (sorting.direction === "asc" ? " ↑" : " ↓"), /*#__PURE__*/React.createElement("div", {
|
|
@@ -11500,7 +11581,22 @@ function TableInner({
|
|
|
11500
11581
|
}, typeof contextMenuModal.component === "function" ? /*#__PURE__*/React.createElement(contextMenuModal.component, {
|
|
11501
11582
|
row: contextMenu.row
|
|
11502
11583
|
}) : contextMenuModal.component) : null;
|
|
11503
|
-
|
|
11584
|
+
const mobileDetailModalMarkup = mobileDetailRow ? /*#__PURE__*/React.createElement(Modal.default, {
|
|
11585
|
+
isOpen: true,
|
|
11586
|
+
title: String(mobileDetailRow.name ?? mobileDetailRow.title ?? translate("details")),
|
|
11587
|
+
onCancel: () => setMobileDetailRow(null),
|
|
11588
|
+
cancelLabel: translate("close"),
|
|
11589
|
+
showCancel: true
|
|
11590
|
+
}, /*#__PURE__*/React.createElement("dl", {
|
|
11591
|
+
className: "mobile-row-details"
|
|
11592
|
+
}, allColumns.map((col, index) => {
|
|
11593
|
+
const header = typeof col.header === "string" ? translate(col.header) : col.header;
|
|
11594
|
+
const value = col.isCustom ? typeof col.render === "function" ? col.render(mobileDetailRow, index) : col.render ?? "..." : formatMobileDetailValue(getAccessorValue(mobileDetailRow, col.accessor), col);
|
|
11595
|
+
return /*#__PURE__*/React.createElement(React.Fragment, {
|
|
11596
|
+
key: index
|
|
11597
|
+
}, /*#__PURE__*/React.createElement("dt", null, header), /*#__PURE__*/React.createElement("dd", null, value));
|
|
11598
|
+
}))) : null;
|
|
11599
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, tableMarkup, contextMenuMarkup, contextMenuModalMarkup, mobileDetailModalMarkup);
|
|
11504
11600
|
}
|
|
11505
11601
|
function Table(props) {
|
|
11506
11602
|
const {
|