@absreim/react-bootstrap-data-grid 1.2.2 → 1.3.0

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/Grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FC } from "react";
2
- import { ColDef, FilterModel, RowDef, SelectModel, Size, TableSortModel } from "./types";
2
+ import { ColDef, EditModel, FilterModel, RowDef, SelectModel, Size, TableSortModel } from "./types";
3
3
  export interface GridPaginationState {
4
4
  pageSizeOptions: number[];
5
5
  pageSizeIndex: number;
@@ -16,6 +16,8 @@ export interface GridProps {
16
16
  sortModel?: TableSortModel;
17
17
  filterModel?: FilterModel;
18
18
  selectModel?: SelectModel;
19
+ editModel?: EditModel;
20
+ caption?: string;
19
21
  }
20
22
  declare const Grid: FC<GridProps>;
21
23
  export default Grid;
package/Grid.js CHANGED
@@ -4,7 +4,7 @@ import { useMemo, useState } from "react";
4
4
  import ColHeaderCell from "./ColHeaderCell";
5
5
  import useFilter from "./hooks/useFilter";
6
6
  import ToggleButton from "./ToggleButton";
7
- import FilterOptionsTable from "./FilterOptionsTable/FilterOptionsTable";
7
+ import FilterOptionsTable from "./filtering/FilterOptionsTable";
8
8
  import useFilterStateFromEditable from "./hooks/useFilterStateFromEditable";
9
9
  import useAugmentedRows from "./hooks/useAugmentedRows";
10
10
  import useSortedRows from "./hooks/useSortedRows";
@@ -13,8 +13,10 @@ import SelectAllHeaderCell from "./selection/SelectAllHeaderCell";
13
13
  import SelectionInput from "./selection/SelectionInput";
14
14
  import Pagination from "./pagination/Pagination";
15
15
  import classNames from "classnames";
16
+ import EditableRow from "./editing/EditableRow";
17
+ import inputStrsToRowDef from "./editing/inputStrsToRowDef";
16
18
  var Grid = function (_a) {
17
- var rows = _a.rows, cols = _a.cols, pagination = _a.pagination, sortModel = _a.sortModel, filterModel = _a.filterModel, selectModel = _a.selectModel;
19
+ var rows = _a.rows, cols = _a.cols, pagination = _a.pagination, sortModel = _a.sortModel, filterModel = _a.filterModel, selectModel = _a.selectModel, editModel = _a.editModel, caption = _a.caption;
18
20
  var editableFilterState = (filterModel === null || filterModel === void 0 ? void 0 : filterModel.tableFilterState) || null;
19
21
  var filterState = useFilterStateFromEditable(cols, editableFilterState);
20
22
  var augmentedRows = useAugmentedRows(rows);
@@ -30,7 +32,9 @@ var Grid = function (_a) {
30
32
  var upperIndex = lowerIndex + pageSize;
31
33
  return sortedRows.slice(lowerIndex, upperIndex);
32
34
  }, [sortedRows, pagination]);
33
- var displayRows = useDisplayRows(currentPageRows, cols);
35
+ var showSelectCol = selectModel && selectModel.mode !== "row";
36
+ var ariaColIndexOffset = showSelectCol ? 1 : 0;
37
+ var displayRows = useDisplayRows(currentPageRows, cols, ariaColIndexOffset);
34
38
  var _b = useState(false), filterOptionsVisible = _b[0], setFilterOptionsVisible = _b[1];
35
39
  var handleToggleFilterOptions = function () {
36
40
  setFilterOptionsVisible(!filterOptionsVisible);
@@ -94,7 +98,6 @@ var Grid = function (_a) {
94
98
  deselectCallback: getDeselectHandler(index),
95
99
  };
96
100
  };
97
- var showSelectCol = selectModel && selectModel.mode !== "row";
98
101
  var selectedSet = new Set();
99
102
  if (selectModel && selectModel.type === "multi") {
100
103
  selectModel.selected.forEach(function (value) { return selectedSet.add(value); });
@@ -106,7 +109,6 @@ var Grid = function (_a) {
106
109
  }
107
110
  var rowsAreSelectable = !!(selectModel && selectModel.mode !== "column");
108
111
  var getRowClickHandler = function (index) { return function (event) {
109
- event.preventDefault();
110
112
  if (!rowsAreSelectable) {
111
113
  return;
112
114
  }
@@ -122,12 +124,17 @@ var Grid = function (_a) {
122
124
  }
123
125
  return String(selectedSet.has(index));
124
126
  };
125
- // Once this component implements selection state, and if such interactivity is enabled, (conditionally) change the
126
- // aria role to "grid".
127
- // TODO: implement the above described features: conditionally changing aria role to grid
127
+ var getInputStrSubmitCallback = editModel &&
128
+ (function (origIndex) {
129
+ var indexSpecificCallback = editModel.getUpdateCallback(origIndex);
130
+ return function (inputStrs) {
131
+ var rowDef = inputStrsToRowDef(cols, inputStrs);
132
+ indexSpecificCallback(rowDef);
133
+ };
134
+ });
128
135
  return (_jsxs("div", { children: [filterState && filterModel && (_jsxs("div", { children: [_jsx(ToggleButton, { isActive: filterOptionsVisible, label: "".concat(filterOptionsVisible ? "Hide" : "Show ", " Filter Options"), onClick: handleToggleFilterOptions }), filterOptionsVisible && (_jsx(FilterOptionsTable, { filterState: filterState, setFilterState: filterModel.setTableFilterState }))] })), _jsxs("table", { className: classNames("table", {
129
136
  "table-hover": rowsAreSelectable,
130
- }), "aria-rowcount": filteredRows.length + 1, children: [_jsx("thead", { children: _jsxs("tr", { "aria-rowindex": 1, children: [showSelectCol && (_jsx(SelectAllHeaderCell, { selectType: selectModel.type, onClick: selectAllOnClick, selectionExists: selectionExists })), cols.map(function (_a, index) {
137
+ }), "aria-rowcount": filteredRows.length + 1, children: [caption !== undefined && _jsx("caption", { children: caption }), _jsx("thead", { children: _jsxs("tr", { "aria-rowindex": 1, children: [showSelectCol && (_jsx(SelectAllHeaderCell, { selectType: selectModel.type, onClick: selectAllOnClick, selectionExists: selectionExists })), cols.map(function (_a, index) {
131
138
  var _b;
132
139
  var name = _a.name, label = _a.label, sortable = _a.sortable;
133
140
  var colSortModel = sortModel && sortable
@@ -141,8 +148,12 @@ var Grid = function (_a) {
141
148
  }
142
149
  : undefined;
143
150
  return (_jsx(ColHeaderCell, { label: label, sortModel: colSortModel, ariaColIndex: index + 1 + (showSelectCol ? 1 : 0) }, name));
144
- })] }) }), _jsx("tbody", { children: displayRows.map(function (row, index) { return (_jsxs("tr", { onClick: getRowClickHandler(row.origIndex), className: classNames({
145
- "table-active": selectedSet.has(row.origIndex),
146
- }), "aria-rowindex": index + 2, "data-rowindex": row.origIndex, "aria-selected": getAriaSelectedValue(row.origIndex), children: [showSelectCol && (_jsx("td", { children: _jsx(SelectionInput, { selected: selectedSet.has(row.origIndex), selectionInputModel: getSelectInputModel(row.origIndex, selectModel), selectCallback: getSelectHandler(row.origIndex) }) })), row.contents.map(function (value, index) { return (_jsx("td", { "aria-colindex": index + 1 + (showSelectCol ? 1 : 0), children: value }, index)); })] }, row.origIndex)); }) })] }), pagination && (_jsx(Pagination, { componentSize: pagination.componentSize || "medium", pageSizeOptions: pagination.pageSizeOptions, pageSizeIndex: pagination.pageSizeIndex, handleSetPageSizeIndex: pagination.setPageSizeIndex, handleSetPageNum: pagination.setCurrentPage, prePagingNumRows: sortedRows.length, maxPageButtons: pagination.maxPageButtons, currentPage: pagination.currentPage }))] }));
151
+ }), editModel && (_jsx("th", { "aria-colindex": cols.length + 1 + (showSelectCol ? 1 : 0), children: "Edit Controls" }))] }) }), _jsx("tbody", { children: displayRows.map(function (row, index) {
152
+ return (_jsx(EditableRow, { onClick: getRowClickHandler(row.origIndex), className: classNames({
153
+ "table-active": selectedSet.has(row.origIndex),
154
+ }), "aria-rowindex": index + 2, dataRowIndex: row.origIndex, "aria-selected": getAriaSelectedValue(row.origIndex), ariaColIndexOffset: ariaColIndexOffset, cellData: row.contents, updateCallback: getInputStrSubmitCallback &&
155
+ getInputStrSubmitCallback(row.origIndex), deleteCallback: (editModel === null || editModel === void 0 ? void 0 : editModel.getDeleteCallback) &&
156
+ editModel.getDeleteCallback(row.origIndex), children: showSelectCol && (_jsx("td", { children: _jsx(SelectionInput, { selected: selectedSet.has(row.origIndex), selectionInputModel: getSelectInputModel(row.origIndex, selectModel), selectCallback: getSelectHandler(row.origIndex) }) })) }, row.origIndex));
157
+ }) })] }), pagination && (_jsx(Pagination, { componentSize: pagination.componentSize || "medium", pageSizeOptions: pagination.pageSizeOptions, pageSizeIndex: pagination.pageSizeIndex, handleSetPageSizeIndex: pagination.setPageSizeIndex, handleSetPageNum: pagination.setCurrentPage, prePagingNumRows: sortedRows.length, maxPageButtons: pagination.maxPageButtons, currentPage: pagination.currentPage }))] }));
147
158
  };
148
159
  export default Grid;
@@ -0,0 +1,11 @@
1
+ import { FC } from "react";
2
+ export interface EditControlsCellProps {
3
+ ariaColIndex: number;
4
+ beginEditingCallback: () => void;
5
+ cancelEditingCallback: () => void;
6
+ isEditing: boolean;
7
+ saveCallback: () => void;
8
+ deleteCallback?: () => void;
9
+ }
10
+ declare const EditControlsCell: FC<EditControlsCellProps>;
11
+ export default EditControlsCell;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ var EditControlsCell = function (_a) {
3
+ var ariaColIndex = _a.ariaColIndex, beginEditingCallback = _a.beginEditingCallback, cancelEditingCallback = _a.cancelEditingCallback, isEditing = _a.isEditing, saveCallback = _a.saveCallback, deleteCallback = _a.deleteCallback;
4
+ return (_jsx("td", { "aria-colindex": ariaColIndex, children: _jsx("div", { className: "hstack gap-2", children: isEditing ? (_jsxs(_Fragment, { children: [_jsx("button", { className: "btn btn-secondary", onClick: cancelEditingCallback, children: "Cancel" }), _jsx("button", { className: "btn btn-primary", onClick: saveCallback, children: "Save" })] })) : (_jsxs(_Fragment, { children: [deleteCallback && (_jsx("button", { className: "btn btn-secondary", onClick: deleteCallback, children: "Delete" })), _jsx("button", { className: "btn btn-primary", onClick: beginEditingCallback, children: "Edit" })] })) }) }));
5
+ };
6
+ export default EditControlsCell;
@@ -0,0 +1,13 @@
1
+ import { FC, ReactNode } from "react";
2
+ import { CellData } from "../types";
3
+ import React from "react";
4
+ export type EditableRowProps = Pick<React.ComponentProps<"tr">, "onClick" | "className" | "aria-rowindex" | "aria-selected"> & {
5
+ ariaColIndexOffset: number;
6
+ dataRowIndex: number;
7
+ children: ReactNode;
8
+ cellData: CellData[];
9
+ updateCallback?: (values: string[]) => void;
10
+ deleteCallback?: () => void;
11
+ };
12
+ declare const EditableRow: FC<EditableRowProps>;
13
+ export default EditableRow;
@@ -0,0 +1,66 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useRef, useState } from "react";
4
+ import { dateToDatetimeInputStr, dateToInputStr } from "../util/datetime";
5
+ import EditControlsCell from "./EditControlsCell";
6
+ var initValueToFormValue = function (value, type) {
7
+ switch (type) {
8
+ case "date":
9
+ return dateToInputStr(value);
10
+ case "datetime":
11
+ return dateToDatetimeInputStr(value);
12
+ case "number":
13
+ return String(value);
14
+ default:
15
+ return value;
16
+ }
17
+ };
18
+ var colDataTypeToInputType = function (colDataType) {
19
+ switch (colDataType) {
20
+ case "date":
21
+ return "date";
22
+ case "datetime":
23
+ return "datetime-local";
24
+ case "number":
25
+ return "number";
26
+ default:
27
+ return "text";
28
+ }
29
+ };
30
+ var EditableRow = function (_a) {
31
+ var ariaColIndexOffset = _a.ariaColIndexOffset, cellData = _a.cellData, children = _a.children, updateCallback = _a.updateCallback, deleteCallback = _a.deleteCallback, onClick = _a.onClick, className = _a.className, ariaRowIndex = _a["aria-rowindex"], ariaSelected = _a["aria-selected"], dataRowIndex = _a.dataRowIndex;
32
+ var trRef = useRef(null);
33
+ var _b = useState(false), isEditing = _b[0], setIsEditing = _b[1];
34
+ var handleSave = function () {
35
+ var failedValidationFound = false;
36
+ var formValues = [];
37
+ for (var _i = 0, _a = Array.from(trRef.current.children); _i < _a.length; _i++) {
38
+ var rowChild = _a[_i];
39
+ if (!(rowChild instanceof HTMLTableCellElement)) {
40
+ console.warn("Found non-td element inside EditableRow");
41
+ continue;
42
+ }
43
+ for (var _b = 0, _c = Array.from(rowChild.children); _b < _c.length; _b++) {
44
+ var cellChild = _c[_b];
45
+ if (!(cellChild instanceof HTMLInputElement) ||
46
+ cellChild.type === "checkbox" ||
47
+ cellChild.type === "radio") {
48
+ continue;
49
+ }
50
+ formValues.push(cellChild.value);
51
+ failedValidationFound =
52
+ !cellChild.reportValidity() || failedValidationFound;
53
+ break; // Each cell should only have one input element
54
+ }
55
+ }
56
+ if (!failedValidationFound) {
57
+ updateCallback(formValues);
58
+ setIsEditing(false);
59
+ }
60
+ };
61
+ return (_jsxs("tr", { ref: trRef, onClick: onClick, className: className, "aria-rowindex": ariaRowIndex, "aria-selected": ariaSelected, "data-rowindex": dataRowIndex, children: [children, cellData.map(function (_a, index) {
62
+ var type = _a.type, value = _a.value, formattedValue = _a.formattedValue, label = _a.label;
63
+ return (_jsx("td", { "aria-colindex": index + ariaColIndexOffset + 1, children: isEditing && !!updateCallback ? (_jsx("input", { "aria-label": label, name: "editable-cell-input-".concat(dataRowIndex, "-").concat(index), className: "form-control", type: colDataTypeToInputType(type), defaultValue: initValueToFormValue(value, type), required: type !== "string" })) : (formattedValue) }, index));
64
+ }), updateCallback && (_jsx(EditControlsCell, { ariaColIndex: ariaColIndexOffset + cellData.length + 1, beginEditingCallback: function () { return setIsEditing(true); }, cancelEditingCallback: function () { return setIsEditing(false); }, isEditing: isEditing, saveCallback: handleSave, deleteCallback: deleteCallback }))] }));
65
+ };
66
+ export default EditableRow;
@@ -0,0 +1,3 @@
1
+ import { ColDef, RowDef } from "../types";
2
+ declare const inputStrsToRowDef: (cols: ColDef[], inputStrs: string[]) => RowDef;
3
+ export default inputStrsToRowDef;
@@ -0,0 +1,18 @@
1
+ var inputStrsToRowDef = function (cols, inputStrs) {
2
+ var newRow = {};
3
+ inputStrs.forEach(function (value, index) {
4
+ var col = cols[index];
5
+ switch (col.type) {
6
+ case "string":
7
+ newRow[col.name] = value;
8
+ return;
9
+ case "number":
10
+ newRow[col.name] = Number(value);
11
+ return;
12
+ default:
13
+ newRow[col.name] = new Date(value);
14
+ }
15
+ });
16
+ return newRow;
17
+ };
18
+ export default inputStrsToRowDef;
@@ -1,17 +1,13 @@
1
1
  import { useState } from "react";
2
2
  var useControlledHover = function () {
3
3
  var _a = useState(false), isHovering = _a[0], setIsHovering = _a[1];
4
- var handleMouseOver = function () {
5
- return setIsHovering(true);
6
- };
7
- var handleMouseOut = function () {
8
- return setIsHovering(false);
9
- };
4
+ var handleMouseOver = function () { return setIsHovering(true); };
5
+ var handleMouseOut = function () { return setIsHovering(false); };
10
6
  return {
11
7
  isHovering: isHovering,
12
8
  setIsHovering: setIsHovering,
13
9
  handleMouseOver: handleMouseOver,
14
- handleMouseOut: handleMouseOut
10
+ handleMouseOut: handleMouseOut,
15
11
  };
16
12
  };
17
13
  export default useControlledHover;
@@ -1,3 +1,3 @@
1
1
  import { AugRowDef, FormattedRow, ColDef } from "../types";
2
- declare const useDisplayRows: (currentPageRows: AugRowDef[], cols: ColDef[]) => FormattedRow[];
2
+ declare const useDisplayRows: (currentPageRows: AugRowDef[], cols: ColDef[], ariaColIndexOffset: number) => FormattedRow[];
3
3
  export default useDisplayRows;
@@ -1,5 +1,20 @@
1
1
  import { useMemo } from "react";
2
- var useDisplayRows = function (currentPageRows, cols) {
2
+ var getFormattedValue = function (value, formatter, typeString) {
3
+ if (formatter) {
4
+ return formatter(value);
5
+ }
6
+ if (typeString === "date") {
7
+ return value.toDateString();
8
+ }
9
+ if (typeString === "datetime") {
10
+ return value.toLocaleString();
11
+ }
12
+ if (typeString === "number") {
13
+ return value.toLocaleString();
14
+ }
15
+ return value;
16
+ };
17
+ var useDisplayRows = function (currentPageRows, cols, ariaColIndexOffset) {
3
18
  return useMemo(function () {
4
19
  var nameToIndex = new Map();
5
20
  cols.forEach(function (_a, index) {
@@ -24,29 +39,21 @@ var useDisplayRows = function (currentPageRows, cols) {
24
39
  return;
25
40
  }
26
41
  var index = nameToIndex.get(name);
27
- var formatter = cols[index].formatter;
28
- var typeString = cols[index].type;
42
+ var col = cols[index];
43
+ var formatter = col.formatter;
44
+ var typeString = col.type;
29
45
  var value = row.data[name];
30
- if (formatter) {
31
- displayRow[index] = formatter(value);
32
- return;
33
- }
34
- if (typeString === "date") {
35
- displayRow[index] = value.toDateString();
36
- return;
37
- }
38
- if (typeString === "datetime") {
39
- displayRow[index] = value.toLocaleString();
40
- return;
41
- }
42
- if (typeString === "number") {
43
- displayRow[index] = value.toLocaleString();
44
- return;
45
- }
46
- displayRow[index] = value;
46
+ displayRow[index] = {
47
+ fieldName: cols[index].name,
48
+ value: value,
49
+ type: typeString,
50
+ ariaColIndex: index + 1 + ariaColIndexOffset,
51
+ formattedValue: getFormattedValue(value, formatter, typeString),
52
+ label: cols[index].label,
53
+ };
47
54
  });
48
55
  return { contents: displayRow, origIndex: row.meta.origIndex };
49
56
  });
50
- }, [currentPageRows, cols]);
57
+ }, [currentPageRows, cols, ariaColIndexOffset]);
51
58
  };
52
59
  export default useDisplayRows;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@absreim/react-bootstrap-data-grid",
3
3
  "description": "Data grid UI component for use with web apps using React and Bootstrap",
4
- "version": "1.2.2",
4
+ "version": "1.3.0",
5
5
  "license": "MIT",
6
6
  "author": "Brook Li",
7
7
  "homepage": "https://react-bootstrap-data-grid.vercel.app/",
@@ -15,7 +15,7 @@ var SelectionInput = function (_a) {
15
15
  selectCallback();
16
16
  }
17
17
  };
18
- return (_jsx("input", { "aria-description": "input to select the current row", onClick: function (event) {
18
+ return (_jsx("input", { "aria-label": "input to select the current row", onClick: function (event) {
19
19
  event.stopPropagation();
20
20
  }, type: type, checked: selected, onChange: onChange, name: selectionInputModel.name }));
21
21
  };
package/style.scss CHANGED
@@ -4,18 +4,18 @@
4
4
 
5
5
  .rbdg-select-header-cell {
6
6
  .rdbg-svg-icon {
7
- background-color: var(--bs-btn-bg)
7
+ background-color: var(--bs-btn-bg);
8
8
  }
9
9
  .rdbg-svg-icon-foreground {
10
- color: var(--bs-btn-color)
10
+ color: var(--bs-btn-color);
11
11
  }
12
12
 
13
13
  &:hover {
14
14
  .rdbg-svg-icon {
15
- background-color: var(--bs-btn-hover-bg)
15
+ background-color: var(--bs-btn-hover-bg);
16
16
  }
17
17
  .rdbg-svg-icon-foreground {
18
- color: var(--bs-btn-hover-color)
18
+ color: var(--bs-btn-hover-color);
19
19
  }
20
20
  }
21
21
  }
package/types.d.ts CHANGED
@@ -16,7 +16,7 @@ export interface AugRowDef {
16
16
  meta: RowMetadata;
17
17
  }
18
18
  export interface FormattedRow {
19
- contents: string[];
19
+ contents: CellData[];
20
20
  origIndex: number;
21
21
  }
22
22
  export type JustifyContentSetting = "start" | "end" | "center" | "between" | "around" | "evenly";
@@ -103,3 +103,16 @@ export interface SingleSelectModel {
103
103
  export type SelectModel = SingleSelectModel | MultiSelectModel;
104
104
  export type MultiSelectModelInitialState = Omit<MultiSelectModel, "setSelected">;
105
105
  export type SingleSelectModelInitialState = Omit<SingleSelectModel, "setSelected">;
106
+ export interface CellData {
107
+ fieldName: string;
108
+ value: ColDataType;
109
+ type: ColDataTypeStrings;
110
+ ariaColIndex: number;
111
+ formattedValue: string;
112
+ label: string;
113
+ }
114
+ export type UpdateCallbackGenerator = (origIndex: number) => (rowDef: RowDef) => void;
115
+ export interface EditModel {
116
+ getUpdateCallback: UpdateCallbackGenerator;
117
+ getDeleteCallback?: (origIndex: number) => () => void;
118
+ }
File without changes
File without changes