@absreim/react-bootstrap-data-grid 1.4.0 → 1.4.2

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.js CHANGED
@@ -17,6 +17,7 @@ import EditableRow from "./editing/EditableRow";
17
17
  import inputStrsToRowDef from "./editing/inputStrsToRowDef";
18
18
  import { unwrapAdditionalComponentsStyleModel, unwrapTableStyleModel, } from "./styling/styleModelUnwrappers";
19
19
  import useCurrentPageRows from "./pipeline/useCurrentPageRows";
20
+ import isSubset from "./util/isSubset";
20
21
  var Grid = function (_a) {
21
22
  var _b;
22
23
  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, styleModel = _a.styleModel;
@@ -56,8 +57,8 @@ var Grid = function (_a) {
56
57
  return;
57
58
  }
58
59
  if (!selectionExists && selectModel.type === "multi") {
59
- var allFilteredRowIndices = filteredRows.map(function (def) { return def.meta.origIndex; });
60
- selectModel.setSelected(allFilteredRowIndices);
60
+ var allRowIndices = rows.map(function (_, index) { return index; });
61
+ selectModel.setSelected(allRowIndices);
61
62
  }
62
63
  // Button should be disabled in the case of selectionExists &&
63
64
  // selectModel.type === "single", so function execution should never get
@@ -102,7 +103,35 @@ var Grid = function (_a) {
102
103
  selectedSet.add(selectModel.selected);
103
104
  }
104
105
  var rowsAreSelectable = !!(selectModel && selectModel.mode !== "column");
105
- var getRowClickHandler = function (index) { return function (event) {
106
+ var selectionInfo = useMemo(function () {
107
+ if (!selectModel) {
108
+ return null;
109
+ }
110
+ if (selectModel.type === "single") {
111
+ return {
112
+ selectType: "single",
113
+ existingSelection: selectionExists
114
+ };
115
+ }
116
+ var getMultiExistingSelection = function (selectionExists, rows) {
117
+ var rowIndices = rows.map(function (_, index) { return index; });
118
+ var isFullSelection = isSubset(rowIndices, selectModel.selected);
119
+ // Note that isFullSelection is true if there are no rows at all. In that case, the existing selection value
120
+ // should be "none", not "full".
121
+ if (!selectionExists) {
122
+ return "none";
123
+ }
124
+ if (isFullSelection) {
125
+ return "full";
126
+ }
127
+ return "partial";
128
+ };
129
+ return {
130
+ selectType: "multi",
131
+ existingSelection: getMultiExistingSelection(selectionExists, rows)
132
+ };
133
+ }, [selectModel, selectionExists, rows]);
134
+ var getRowClickHandler = function (index) { return function () {
106
135
  if (!rowsAreSelectable) {
107
136
  return;
108
137
  }
@@ -132,7 +161,7 @@ var Grid = function (_a) {
132
161
  }, [styleModel === null || styleModel === void 0 ? void 0 : styleModel.additionalComponentsStyleModel]);
133
162
  return (_jsxs("div", { "data-testid": "rbdg-top-level-div", className: classNames(unwrappedAdditionalStyleModel.topLevelDiv), children: [filterState && filterModel && (_jsxs("div", { "data-testid": "rbdg-filter-inputs-div", className: classNames(unwrappedAdditionalStyleModel.filterInputsDiv), children: [_jsx(ToggleButton, { isActive: filterOptionsVisible, label: "".concat(filterOptionsVisible ? "Hide" : "Show ", " Filter Options"), onClick: handleToggleFilterOptions, classes: (_b = styleModel === null || styleModel === void 0 ? void 0 : styleModel.additionalComponentsStyleModel) === null || _b === void 0 ? void 0 : _b.filterUiToggleButton }), filterOptionsVisible && (_jsx(FilterOptionsTable, { caption: filterModel.filterTableCaption, filterState: filterState, setFilterState: filterModel.setTableFilterState, styleModel: styleModel === null || styleModel === void 0 ? void 0 : styleModel.filterInputTableStyleModel }))] })), _jsxs("div", { "data-testid": "rbdg-table-and-pagination-div", className: classNames(unwrappedAdditionalStyleModel.tableAndPaginationDiv), children: [_jsxs("table", { className: classNames("table", {
134
163
  "table-hover": rowsAreSelectable,
135
- }, unwrappedTableModel.table), "aria-rowcount": filteredRows.length + 1, children: [caption !== undefined && (_jsx("caption", { className: classNames(unwrappedTableModel.caption), children: caption })), _jsx("thead", { className: classNames(unwrappedTableModel.thead), children: _jsxs("tr", { "aria-rowindex": 1, className: classNames(unwrappedTableModel.theadTr), children: [showSelectCol && (_jsx(SelectAllHeaderCell, { selectType: selectModel.type, onClick: selectAllOnClick, selectionExists: selectionExists, additionalClasses: unwrappedTableModel.rowSelectColTh })), cols.map(function (_a, index) {
164
+ }, unwrappedTableModel.table), "aria-rowcount": filteredRows.length + 1, children: [caption !== undefined && (_jsx("caption", { className: classNames(unwrappedTableModel.caption), children: caption })), _jsx("thead", { className: classNames(unwrappedTableModel.thead), children: _jsxs("tr", { "aria-rowindex": 1, className: classNames(unwrappedTableModel.theadTr), children: [showSelectCol && (_jsx(SelectAllHeaderCell, { selectionInfo: selectionInfo, onClick: selectAllOnClick, totalRows: rows.length, additionalClasses: unwrappedTableModel.rowSelectColTh })), cols.map(function (_a, index) {
136
165
  var _b;
137
166
  var name = _a.name, label = _a.label, sortable = _a.sortable;
138
167
  var colSortModel = sortModel && sortable
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import classNames from "classnames";
3
3
  var stopPropagationWrapper = function (fn) { return function (event) {
4
4
  event.stopPropagation();
@@ -6,14 +6,14 @@ var stopPropagationWrapper = function (fn) { return function (event) {
6
6
  }; };
7
7
  var EditControlsCell = function (_a) {
8
8
  var ariaColIndex = _a.ariaColIndex, beginEditingCallback = _a.beginEditingCallback, cancelEditingCallback = _a.cancelEditingCallback, isEditing = _a.isEditing, saveCallback = _a.saveCallback, deleteCallback = _a.deleteCallback, editControlsCellClasses = _a.editControlsCellClasses, saveButtonClasses = _a.saveButtonClasses, deleteButtonClasses = _a.deleteButtonClasses, startButtonClasses = _a.startButtonClasses, cancelButtonClasses = _a.cancelButtonClasses;
9
- return (_jsx("td", { "aria-colindex": ariaColIndex, className: classNames(editControlsCellClasses), children: _jsx("div", { className: "hstack gap-2", children: isEditing ? (_jsxs(_Fragment, { children: [_jsx("button", { className: classNames("btn", cancelButtonClasses.length === 0
9
+ return (_jsx("td", { "aria-colindex": ariaColIndex, className: classNames(editControlsCellClasses), children: _jsx("div", { className: "hstack gap-2", children: isEditing ? (_jsxs(_Fragment, { children: [_jsx("button", { "aria-label": "Cancel", className: classNames("btn", cancelButtonClasses.length === 0
10
10
  ? ["btn-secondary"]
11
- : cancelButtonClasses), onClick: stopPropagationWrapper(cancelEditingCallback), children: "Cancel" }), _jsx("button", { className: classNames("btn", saveButtonClasses.length === 0
11
+ : cancelButtonClasses), onClick: stopPropagationWrapper(cancelEditingCallback), title: "Cancel", children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", fill: "currentColor", viewBox: "0 0 16 16", children: _jsx("path", { d: "M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293z" }) }) }), _jsx("button", { "aria-label": "Save", className: classNames("btn", saveButtonClasses.length === 0
12
12
  ? ["btn-primary"]
13
- : saveButtonClasses), onClick: stopPropagationWrapper(saveCallback), children: "Save" })] })) : (_jsxs(_Fragment, { children: [deleteCallback && (_jsx("button", { className: classNames("btn", deleteButtonClasses.length === 0
14
- ? ["btn-primary"]
15
- : deleteButtonClasses), onClick: stopPropagationWrapper(deleteCallback), children: "Delete" })), _jsx("button", { className: classNames("btn", startButtonClasses.length === 0
13
+ : saveButtonClasses), onClick: stopPropagationWrapper(saveCallback), title: "Save", children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", fill: "currentColor", viewBox: "0 0 16 16", children: [_jsx("path", { d: "M12 2h-2v3h2z" }), _jsx("path", { d: "M1.5 0A1.5 1.5 0 0 0 0 1.5v13A1.5 1.5 0 0 0 1.5 16h13a1.5 1.5 0 0 0 1.5-1.5V2.914a1.5 1.5 0 0 0-.44-1.06L14.147.439A1.5 1.5 0 0 0 13.086 0zM4 6a1 1 0 0 1-1-1V1h10v4a1 1 0 0 1-1 1zM3 9h10a1 1 0 0 1 1 1v5H2v-5a1 1 0 0 1 1-1" })] }) })] })) : (_jsxs(_Fragment, { children: [deleteCallback && (_jsx("button", { "aria-label": "Delete", className: classNames("btn", deleteButtonClasses.length === 0
14
+ ? ["btn-secondary"]
15
+ : deleteButtonClasses), onClick: stopPropagationWrapper(deleteCallback), title: "Delete", children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", fill: "currentColor", viewBox: "0 0 16 16", children: _jsx("path", { d: "M2.5 1a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1H3v9a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V4h.5a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H10a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1zm3 4a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5M8 5a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7A.5.5 0 0 1 8 5m3 .5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 1 0" }) }) })), _jsx("button", { "aria-label": "Edit", className: classNames("btn", startButtonClasses.length === 0
16
16
  ? ["btn-primary"]
17
- : startButtonClasses), onClick: stopPropagationWrapper(beginEditingCallback), children: "Edit" })] })) }) }));
17
+ : startButtonClasses), onClick: stopPropagationWrapper(beginEditingCallback), title: "Edit", children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", fill: "currentColor", viewBox: "0 0 16 16", children: [_jsx("path", { d: "M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z" }), _jsx("path", { fillRule: "evenodd", d: "M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5z" })] }) })] })) }) }));
18
18
  };
19
19
  export default EditControlsCell;
package/index.d.ts CHANGED
@@ -6,4 +6,5 @@ export * from "./filtering/types";
6
6
  export * from "./sorting/types";
7
7
  export * from "./pagination/types";
8
8
  export * from "./Grid";
9
+ export * from "./util/datetime";
9
10
  export { default } from "./Grid";
package/index.js CHANGED
@@ -6,4 +6,5 @@ export * from "./filtering/types";
6
6
  export * from "./sorting/types";
7
7
  export * from "./pagination/types";
8
8
  export * from "./Grid";
9
+ export * from "./util/datetime";
9
10
  export { default } from "./Grid";
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.4.0",
4
+ "version": "1.4.2",
5
5
  "license": "MIT",
6
6
  "author": "Brook Li",
7
7
  "homepage": "https://react-bootstrap-data-grid.vercel.app/",
@@ -28,6 +28,6 @@
28
28
  "repository": {
29
29
  "type": "git",
30
30
  "url": "git+https://github.com/absreim/react-bootstrap-data-grid.git",
31
- "directory": "src/grid"
31
+ "directory": "grid"
32
32
  }
33
33
  }
@@ -1,9 +1,9 @@
1
1
  import { FC } from "react";
2
- import { SelectType } from "./types";
2
+ import { SelectionInfo } from "./types";
3
3
  interface SelectAllHeaderCellProps {
4
4
  onClick: () => void;
5
- selectType: SelectType;
6
- selectionExists: boolean;
5
+ selectionInfo: SelectionInfo;
6
+ totalRows: number;
7
7
  additionalClasses?: string[];
8
8
  }
9
9
  declare const SelectAllHeaderCell: FC<SelectAllHeaderCellProps>;
@@ -1,42 +1,55 @@
1
+ "use client";
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import deselectAll from "./deselectAll";
3
- import selectAll from "./selectAll";
4
- import arrowPlaceholder from "../sorting/arrowPlaceholder";
3
+ import { useEffect, useRef } from "react";
5
4
  import classNames from "classnames";
6
- // It seems like React does not support setting indeterminate states on
7
- // checkboxes in a controlled manner, which caused me to not want to refactor
8
- // this feature to use a checkbox input instead of SVG icons. I am not sure how
9
- // much of an issue using an uncontrolled input would be, but because at time of
10
- // this writing I had already implemented a solution with SVG, on balance I felt
11
- // like it was not worth going out of my way to change things up to use an
12
- // uncontrolled checkbox input.
13
- var getSelectIcon = function (selectMode, existingSelection) {
14
- if (existingSelection) {
15
- return deselectAll;
5
+ var getCheckboxState = function (selectionInfo, noRows) {
6
+ var disabledState = {
7
+ indeterminate: false,
8
+ checked: false,
9
+ disabled: true,
10
+ description: "Select all (disabled)",
11
+ };
12
+ if (noRows) {
13
+ return disabledState;
16
14
  }
17
- if (selectMode === "multi" && !existingSelection) {
18
- return selectAll;
15
+ var existingSelection = selectionInfo.existingSelection;
16
+ if (existingSelection === "full") {
17
+ return {
18
+ indeterminate: false,
19
+ checked: true,
20
+ disabled: false,
21
+ description: "Deselect all rows",
22
+ };
19
23
  }
20
- // Single select mode and none selected means that the button is disabled
21
- return arrowPlaceholder;
22
- };
23
- var getCellAriaDescription = function (selectMode, existingSelection) {
24
- if (existingSelection) {
25
- return "Deselect all rows";
24
+ if (existingSelection === true || existingSelection === "partial") {
25
+ return {
26
+ indeterminate: true,
27
+ checked: true,
28
+ disabled: false,
29
+ description: "Deselect all rows",
30
+ };
26
31
  }
27
- if (selectMode === "multi" && !existingSelection) {
28
- return "Select all rows";
32
+ if (existingSelection === "none") {
33
+ return {
34
+ indeterminate: false,
35
+ checked: false,
36
+ disabled: false,
37
+ description: "Select all rows",
38
+ };
29
39
  }
30
- // Single select mode and none selected means that the button is disabled
31
- return "Selection input header cell";
40
+ // single select mode and none selected
41
+ return disabledState;
32
42
  };
33
43
  var SelectAllHeaderCell = function (_a) {
34
- var onClick = _a.onClick, selectType = _a.selectType, selectionExists = _a.selectionExists, additionalClasses = _a.additionalClasses;
35
- var disabled = selectType === "single" && !selectionExists;
36
- var description = getCellAriaDescription(selectType, selectionExists);
37
- return (_jsx("th", { "aria-colindex": 1, title: description, "aria-description": description, className: classNames("rbdg-select-header-cell", {
44
+ var onClick = _a.onClick, selectionInfo = _a.selectionInfo, totalRows = _a.totalRows, additionalClasses = _a.additionalClasses;
45
+ var noRows = totalRows === 0;
46
+ var _b = getCheckboxState(selectionInfo, noRows), indeterminate = _b.indeterminate, checked = _b.checked, disabled = _b.disabled, description = _b.description;
47
+ var ref = useRef(null);
48
+ useEffect(function () {
49
+ ref.current.indeterminate = indeterminate;
50
+ }, [indeterminate]);
51
+ return (_jsx("th", { "aria-colindex": 1, title: description, "aria-description": description, className: classNames({
38
52
  "btn-primary": !additionalClasses || additionalClasses.length === 0,
39
- "rbdg-clickable-grid-header-cell": !disabled,
40
- }, additionalClasses || []), onClick: onClick, children: getSelectIcon(selectType, selectionExists) }));
53
+ }, additionalClasses || []), children: _jsx("input", { type: "checkbox", checked: checked, ref: ref, disabled: disabled, "aria-label": description, onChange: onClick }) }));
41
54
  };
42
55
  export default SelectAllHeaderCell;
@@ -14,3 +14,13 @@ export interface SingleSelectModel {
14
14
  groupName: string;
15
15
  }
16
16
  export type SelectModel = SingleSelectModel | MultiSelectModel;
17
+ export type MultiExistingSelection = "full" | "partial" | "none";
18
+ export interface SingleSelectionInfo {
19
+ selectType: "single";
20
+ existingSelection: boolean;
21
+ }
22
+ export interface MultiSelectionInfo {
23
+ selectType: "multi";
24
+ existingSelection: 'full' | 'partial' | 'none';
25
+ }
26
+ export type SelectionInfo = SingleSelectionInfo | MultiSelectionInfo;
package/style.css CHANGED
@@ -1 +1 @@
1
- .rbdg-clickable-grid-header-cell{cursor:pointer}.rbdg-select-header-cell .rdbg-svg-icon{background-color:var(--bs-btn-bg)}.rbdg-select-header-cell .rdbg-svg-icon-foreground{color:var(--bs-btn-color)}.rbdg-select-header-cell:hover .rdbg-svg-icon{background-color:var(--bs-btn-hover-bg)}.rbdg-select-header-cell:hover .rdbg-svg-icon-foreground{color:var(--bs-btn-hover-color)}.rbdg-grid{display:table}.rbdg-grid-head{display:table-header-group}.rbdg-grid-body{display:table-row-group}.rbdg-grid-row{display:table-row}.rbdg-grid-cell{display:table-cell}
1
+ .rbdg-clickable-grid-header-cell{cursor:pointer}
package/style.scss CHANGED
@@ -1,41 +1,3 @@
1
1
  .rbdg-clickable-grid-header-cell {
2
2
  cursor: pointer;
3
- }
4
-
5
- .rbdg-select-header-cell {
6
- .rdbg-svg-icon {
7
- background-color: var(--bs-btn-bg);
8
- }
9
- .rdbg-svg-icon-foreground {
10
- color: var(--bs-btn-color);
11
- }
12
-
13
- &:hover {
14
- .rdbg-svg-icon {
15
- background-color: var(--bs-btn-hover-bg);
16
- }
17
- .rdbg-svg-icon-foreground {
18
- color: var(--bs-btn-hover-color);
19
- }
20
- }
21
- }
22
-
23
- .rbdg-grid {
24
- display: table;
25
- }
26
-
27
- .rbdg-grid-head {
28
- display: table-header-group;
29
- }
30
-
31
- .rbdg-grid-body {
32
- display: table-row-group;
33
- }
34
-
35
- .rbdg-grid-row {
36
- display: table-row;
37
- }
38
-
39
- .rbdg-grid-cell {
40
- display: table-cell;
41
- }
3
+ }
@@ -0,0 +1,2 @@
1
+ declare const isSubset: (subset: number[], superSet: number[]) => boolean;
2
+ export default isSubset;
@@ -0,0 +1,11 @@
1
+ var isSubset = function (subset, superSet) {
2
+ var supersetSet = new Set(superSet);
3
+ for (var _i = 0, subset_1 = subset; _i < subset_1.length; _i++) {
4
+ var num = subset_1[_i];
5
+ if (!supersetSet.has(num)) {
6
+ return false;
7
+ }
8
+ }
9
+ return true;
10
+ };
11
+ export default isSubset;
@@ -1,2 +0,0 @@
1
- declare const deselectAll: import("react/jsx-runtime").JSX.Element;
2
- export default deselectAll;
@@ -1,3 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- var deselectAll = (_jsxs("svg", { className: "rdbg-svg-icon", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", width: "16", height: "16", fill: "currentColor", children: [_jsx("desc", { children: "Minus sign inside a square" }), _jsx("rect", { className: "rdbg-svg-icon-foreground", x: "3.5", y: "7.5", width: "9", height: "1" })] }));
3
- export default deselectAll;
@@ -1,2 +0,0 @@
1
- declare const selectAll: import("react/jsx-runtime").JSX.Element;
2
- export default selectAll;
@@ -1,3 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- var selectAll = (_jsxs("svg", { className: "rdbg-svg-icon", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", width: "16", height: "16", fill: "currentColor", children: [_jsx("desc", { children: "Plus sign inside a square" }), _jsx("rect", { className: "rdbg-svg-icon-foreground", x: "3.5", y: "7.5", width: "9", height: "1" }), _jsx("rect", { className: "rdbg-svg-icon-foreground", x: "3.5", y: "7.5", width: "9", height: "1", transform: "translate(0 16) rotate(-90)" })] }));
3
- export default selectAll;