@absreim/react-bootstrap-data-grid 0.1.4 → 1.1.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.
@@ -3,6 +3,7 @@ import { ColSortModel } from "./types";
3
3
  interface ColHeaderCellProps {
4
4
  label: string;
5
5
  sortModel?: ColSortModel;
6
+ ariaColIndex: number;
6
7
  }
7
8
  declare const ColHeaderCell: FC<ColHeaderCellProps>;
8
9
  export default ColHeaderCell;
package/ColHeaderCell.jsx CHANGED
@@ -10,10 +10,25 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
10
10
  };
11
11
  import { useState } from "react";
12
12
  import classNames from "classnames";
13
- var getUpArrow = function (addlClasses) { return (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className={classNames(__spreadArray(["bi", "bi-arrow-up"], (addlClasses || []), true))} viewBox="0 0 16 16">
13
+ var getUpArrow = function (grayed) { return (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className={classNames(__spreadArray([
14
+ "bi",
15
+ "bi-arrow-up"
16
+ ], (grayed ? ["text-body-secondary"] : []), true))} viewBox="0 0 16 16">
17
+ {!grayed && (<>
18
+ <title>(sorted ascending)</title>
19
+ <desc>
20
+ Up arrow indicating that the column is being sorted in an ascending
21
+ manner
22
+ </desc>
23
+ </>)}
14
24
  <path fillRule="evenodd" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5"/>
15
25
  </svg>); };
16
26
  var downArrow = (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-arrow-down" viewBox="0 0 16 16">
27
+ <title>(sorted descending)</title>
28
+ <desc>
29
+ Down arrow indicating that the column is being sorted in an descending
30
+ manner
31
+ </desc>
17
32
  <path fillRule="evenodd" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1"/>
18
33
  </svg>);
19
34
  // Temporary solution to prevent column widths from changing when hovering over
@@ -22,7 +37,7 @@ var downArrow = (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
22
37
  // values.
23
38
  var placeholder = (<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-arrow-down" viewBox="0 0 16 16"></svg>);
24
39
  var ColHeaderCell = function (_a) {
25
- var label = _a.label, sortModel = _a.sortModel;
40
+ var label = _a.label, sortModel = _a.sortModel, ariaColIndex = _a.ariaColIndex;
26
41
  var _b = useState(false), isHovering = _b[0], setIsHovering = _b[1];
27
42
  var handleMouseOver = function () {
28
43
  return setIsHovering(true);
@@ -55,19 +70,21 @@ var ColHeaderCell = function (_a) {
55
70
  switch (sortModel.sortOrder) {
56
71
  case null: {
57
72
  if (isHovering) {
58
- return getUpArrow(["text-body-secondary"]);
73
+ return getUpArrow(true);
59
74
  }
60
75
  return placeholder;
61
76
  }
62
77
  case "asc": {
63
- return getUpArrow();
78
+ return getUpArrow(false);
64
79
  }
65
80
  case "desc": {
66
81
  return downArrow;
67
82
  }
68
83
  }
69
84
  };
70
- return (<th onClick={sortModel && handleClick} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} aria-description="Column header that can be clicked to change the sorting mode" style={{ cursor: "pointer " }}>
85
+ return (<th onClick={sortModel && handleClick} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} aria-description={sortModel
86
+ ? "Column header"
87
+ : "Column header that can be clicked to change the sorting mode"} style={{ cursor: sortModel ? "pointer" : "default" }} aria-colindex={ariaColIndex}>
71
88
  {label}
72
89
  {getSortSymbol()}
73
90
  </th>);
@@ -0,0 +1,10 @@
1
+ import { FC } from "react";
2
+ import { DateFormFilterState } from "./types";
3
+ interface DateFilterRowProps {
4
+ includeTime: boolean;
5
+ columnLabel: string;
6
+ filterState: DateFormFilterState;
7
+ setFilterState: (filterState: DateFormFilterState) => void;
8
+ }
9
+ declare const DateFilterRow: FC<DateFilterRowProps>;
10
+ export default DateFilterRow;
@@ -0,0 +1,63 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { useMemo } from "react";
13
+ import { dateFilterSchemeNames, dateFilterSchemes, } from "../types";
14
+ import { nanoid } from "nanoid/non-secure";
15
+ var DateFilterRow = function (_a) {
16
+ var includeTime = _a.includeTime, columnLabel = _a.columnLabel, filterState = _a.filterState, setFilterState = _a.setFilterState;
17
+ var handleOpChange = function (_a) {
18
+ var target = _a.target;
19
+ setFilterState(__assign(__assign({}, filterState), { scheme: target.value }));
20
+ };
21
+ var handleEnabledChange = function (_a) {
22
+ var target = _a.target;
23
+ setFilterState(__assign(__assign({}, filterState), { enabled: target.checked }));
24
+ };
25
+ var handleStartValueChange = function (_a) {
26
+ var target = _a.target;
27
+ setFilterState(__assign(__assign({}, filterState), { startDate: target.value }));
28
+ };
29
+ var handleEndValueChange = function (_a) {
30
+ var target = _a.target;
31
+ setFilterState(__assign(__assign({}, filterState), { endDate: target.value }));
32
+ };
33
+ var enabled = filterState.enabled, scheme = filterState.scheme, startDate = filterState.startDate, endDate = filterState.endDate;
34
+ var inputType = includeTime ? "datetime-local" : "date";
35
+ var inputId = useMemo(function () { return nanoid(); }, []);
36
+ var startDateInputId = "$startDate-".concat(inputId);
37
+ var endDateInputId = "$endDate-".concat(inputId);
38
+ return (<tr>
39
+ <td>
40
+ <input type="checkbox" checked={enabled} name="enabled" onChange={handleEnabledChange}/>
41
+ </td>
42
+ <td>{columnLabel}</td>
43
+ <td>{filterState.type === "date" ? "Date" : "Datetime"}</td>
44
+ <td>
45
+ <select disabled={!enabled} className="form-select" value={scheme} onChange={handleOpChange}>
46
+ {dateFilterSchemes.map(function (scheme) { return (<option key={scheme} value={scheme}>
47
+ {dateFilterSchemeNames[scheme]}
48
+ </option>); })}
49
+ </select>
50
+ </td>
51
+ <td>
52
+ {scheme !== "endAt" && (<>
53
+ {scheme === "between" && (<label htmlFor={startDateInputId}>Start Date</label>)}
54
+ <input id={startDateInputId} className="form-control" type={inputType} required={enabled} disabled={!enabled} value={startDate} onChange={handleStartValueChange} aria-label="Start Date"/>
55
+ </>)}
56
+ {scheme !== "startFrom" && (<>
57
+ {scheme === "between" && (<label htmlFor={endDateInputId}>End Date</label>)}
58
+ <input className="form-control" type={inputType} required={enabled} disabled={!enabled} value={endDate} onChange={handleEndValueChange} aria-label="End Date"/>
59
+ </>)}
60
+ </td>
61
+ </tr>);
62
+ };
63
+ export default DateFilterRow;
@@ -0,0 +1,8 @@
1
+ import { FC } from "react";
2
+ import { EditableTableFilterState, TableFilterState } from "../types";
3
+ interface FilterOptionsTableProps {
4
+ filterState: TableFilterState;
5
+ setFilterState: (filterState: EditableTableFilterState) => void;
6
+ }
7
+ declare const FilterOptionsTable: FC<FilterOptionsTableProps>;
8
+ export default FilterOptionsTable;
@@ -0,0 +1,118 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { useState } from "react";
13
+ import StringFilterRow from "./StringFilterRow";
14
+ import { datetimeInputStrToUtc } from "../util/datetime";
15
+ import NumberFilterRow from "./NumberFilterRow";
16
+ import useFormStateFromTableFilterState from "./useFormStateFromTableFilterState";
17
+ import DateFilterRow from "./DateFilterRow";
18
+ var convertFilterFormStateToEditableState = function (filterFormState) {
19
+ return Object.keys(filterFormState).reduce(function (editableState, colName) {
20
+ var rowFilterFormState = filterFormState[colName];
21
+ switch (rowFilterFormState.type) {
22
+ case "string": {
23
+ editableState[colName] = __assign({}, rowFilterFormState);
24
+ break;
25
+ }
26
+ case "number": {
27
+ editableState[colName] = {
28
+ type: rowFilterFormState.type,
29
+ enabled: rowFilterFormState.enabled,
30
+ scheme: rowFilterFormState.scheme,
31
+ numValue: rowFilterFormState.inputValue === ""
32
+ ? null
33
+ : Number(rowFilterFormState.inputValue),
34
+ };
35
+ break;
36
+ }
37
+ default: {
38
+ // date or datetime
39
+ var partialFilterState = {
40
+ type: rowFilterFormState.type,
41
+ enabled: rowFilterFormState.enabled,
42
+ };
43
+ var strModifierFn_1 = rowFilterFormState.type === "date"
44
+ ? function (str) { return str; }
45
+ : datetimeInputStrToUtc;
46
+ var inputStrToDate = function (str) {
47
+ return str === "" ? null : new Date(strModifierFn_1(str));
48
+ };
49
+ switch (rowFilterFormState.scheme) {
50
+ case "startFrom": {
51
+ editableState[colName] = __assign(__assign({}, partialFilterState), { scheme: rowFilterFormState.scheme, startDate: inputStrToDate(rowFilterFormState.startDate) });
52
+ break;
53
+ }
54
+ case "endAt": {
55
+ editableState[colName] = __assign(__assign({}, partialFilterState), { scheme: rowFilterFormState.scheme, endDate: inputStrToDate(rowFilterFormState.endDate) });
56
+ break;
57
+ }
58
+ default: {
59
+ editableState[colName] = __assign(__assign({}, partialFilterState), { scheme: rowFilterFormState.scheme, startDate: inputStrToDate(rowFilterFormState.startDate), endDate: inputStrToDate(rowFilterFormState.endDate) });
60
+ }
61
+ }
62
+ }
63
+ }
64
+ return editableState;
65
+ }, {});
66
+ };
67
+ var FilterOptionsTable = function (_a) {
68
+ var filterState = _a.filterState, setFilterState = _a.setFilterState;
69
+ var formFilterState = useFormStateFromTableFilterState(filterState);
70
+ var _b = useState(formFilterState), formState = _b[0], setFormState = _b[1];
71
+ var getRows = function () {
72
+ return Object.keys(formState).map(function (colName) {
73
+ function getColStateSetter(colName) {
74
+ return function (rowState) {
75
+ var _a;
76
+ return setFormState(__assign(__assign({}, formState), (_a = {}, _a[colName] = rowState, _a)));
77
+ };
78
+ }
79
+ var colLabel = filterState[colName].label;
80
+ var colFilterState = formState[colName];
81
+ switch (colFilterState.type) {
82
+ case "string": {
83
+ return (<StringFilterRow key={colName} columnLabel={colLabel} filterState={colFilterState} setFilterState={getColStateSetter(colName)}/>);
84
+ }
85
+ case "number": {
86
+ return (<NumberFilterRow key={colName} columnLabel={colLabel} filterState={colFilterState} setFilterState={getColStateSetter(colName)}/>);
87
+ }
88
+ default: {
89
+ // date or datetime
90
+ return (<DateFilterRow key={colName} includeTime={colFilterState.type === "datetime"} columnLabel={colLabel} filterState={colFilterState} setFilterState={getColStateSetter(colName)}/>);
91
+ }
92
+ }
93
+ });
94
+ };
95
+ var onSubmit = function (event) {
96
+ event.preventDefault();
97
+ var editableTableFilterState = convertFilterFormStateToEditableState(formState);
98
+ setFilterState(editableTableFilterState);
99
+ };
100
+ return (<form onSubmit={onSubmit}>
101
+ <table className="table">
102
+ <thead>
103
+ <tr>
104
+ <th>Enabled</th>
105
+ <th>Column</th>
106
+ <th>Type</th>
107
+ <th>Operator</th>
108
+ <th>Value</th>
109
+ </tr>
110
+ </thead>
111
+ <tbody>{getRows()}</tbody>
112
+ </table>
113
+ <button className="btn btn-secondary" type="submit">
114
+ Submit
115
+ </button>
116
+ </form>);
117
+ };
118
+ export default FilterOptionsTable;
@@ -0,0 +1,9 @@
1
+ import { FC } from "react";
2
+ import { NumberFormFilterState } from "./types";
3
+ interface NumberFilterRowProps {
4
+ columnLabel: string;
5
+ filterState: NumberFormFilterState;
6
+ setFilterState: (filterState: NumberFormFilterState) => void;
7
+ }
8
+ declare const NumberFilterRow: FC<NumberFilterRowProps>;
9
+ export default NumberFilterRow;
@@ -0,0 +1,47 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { numberFilterSchemeNames, numberFilterSchemes, } from "../types";
13
+ var NumberFilterRow = function (_a) {
14
+ var columnLabel = _a.columnLabel, filterState = _a.filterState, setFilterState = _a.setFilterState;
15
+ var handleOpChange = function (_a) {
16
+ var target = _a.target;
17
+ setFilterState(__assign(__assign({}, filterState), { scheme: target.value }));
18
+ };
19
+ var handleEnabledChange = function (_a) {
20
+ var target = _a.target;
21
+ setFilterState(__assign(__assign({}, filterState), { enabled: target.checked }));
22
+ };
23
+ var handleNumInputValueChange = function (_a) {
24
+ var target = _a.target;
25
+ setFilterState(__assign(__assign({}, filterState), { inputValue: target.value }));
26
+ };
27
+ var enabled = filterState.enabled, scheme = filterState.scheme, inputValue = filterState.inputValue;
28
+ // TODO: Input labelling for accessibility
29
+ return (<tr>
30
+ <td>
31
+ <input type="checkbox" checked={enabled} name="enabled" onChange={handleEnabledChange}/>
32
+ </td>
33
+ <td>{columnLabel}</td>
34
+ <td>Number</td>
35
+ <td>
36
+ <select disabled={!enabled} className="form-select" value={scheme} onChange={handleOpChange}>
37
+ {numberFilterSchemes.map(function (scheme) { return (<option key={scheme} value={scheme}>
38
+ {numberFilterSchemeNames[scheme]}
39
+ </option>); })}
40
+ </select>
41
+ </td>
42
+ <td>
43
+ <input className="form-control" type="number" required={enabled} disabled={!enabled} value={inputValue} onChange={handleNumInputValueChange}/>
44
+ </td>
45
+ </tr>);
46
+ };
47
+ export default NumberFilterRow;
@@ -0,0 +1,9 @@
1
+ import { FC } from "react";
2
+ import { StringFilterState } from "../types";
3
+ interface StringFilterRowProps {
4
+ columnLabel: string;
5
+ filterState: StringFilterState;
6
+ setFilterState: (filterState: StringFilterState) => void;
7
+ }
8
+ declare const StringFilterRow: FC<StringFilterRowProps>;
9
+ export default StringFilterRow;
@@ -0,0 +1,47 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { stringFilterSchemeNames, stringFilterSchemes, } from "../types";
13
+ var StringFilterRow = function (_a) {
14
+ var columnLabel = _a.columnLabel, filterState = _a.filterState, setFilterState = _a.setFilterState;
15
+ var handleOpChange = function (_a) {
16
+ var target = _a.target;
17
+ setFilterState(__assign(__assign({}, filterState), { scheme: target.value }));
18
+ };
19
+ var handleEnabledChange = function (_a) {
20
+ var target = _a.target;
21
+ setFilterState(__assign(__assign({}, filterState), { enabled: target.checked }));
22
+ };
23
+ var handleSearchStringChange = function (_a) {
24
+ var target = _a.target;
25
+ setFilterState(__assign(__assign({}, filterState), { searchString: target.value }));
26
+ };
27
+ var enabled = filterState.enabled, scheme = filterState.scheme, searchString = filterState.searchString;
28
+ // TODO: Input labelling for accessibility
29
+ return (<tr>
30
+ <td>
31
+ <input type="checkbox" checked={enabled} name="enabled" onChange={handleEnabledChange}/>
32
+ </td>
33
+ <td>{columnLabel}</td>
34
+ <td>String</td>
35
+ <td>
36
+ <select disabled={!enabled} className="form-select" value={scheme} onChange={handleOpChange}>
37
+ {stringFilterSchemes.map(function (scheme) { return (<option key={scheme} value={scheme}>
38
+ {stringFilterSchemeNames[scheme]}
39
+ </option>); })}
40
+ </select>
41
+ </td>
42
+ <td>
43
+ <input className="form-control" required={enabled} disabled={!enabled} value={searchString} onChange={handleSearchStringChange}/>
44
+ </td>
45
+ </tr>);
46
+ };
47
+ export default StringFilterRow;
@@ -0,0 +1,13 @@
1
+ import { AbstractDateFilterState, AbstractFilterState, DateFilterScheme, NumberFilterScheme, StringFilterState } from "../types";
2
+ export interface NumberFormFilterState extends AbstractFilterState {
3
+ type: "number";
4
+ scheme: NumberFilterScheme;
5
+ inputValue: string;
6
+ }
7
+ export interface DateFormFilterState extends AbstractDateFilterState {
8
+ scheme: DateFilterScheme;
9
+ startDate: string;
10
+ endDate: string;
11
+ }
12
+ export type FilterFormRowState = StringFilterState | NumberFormFilterState | DateFormFilterState;
13
+ export type FilterFormState = Record<string, FilterFormRowState>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { TableFilterState } from "../types";
2
+ import { FilterFormState } from "./types";
3
+ declare const useFormStateFromTableFilterState: (tableFilterState: TableFilterState) => FilterFormState;
4
+ export default useFormStateFromTableFilterState;
@@ -0,0 +1,72 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { useMemo } from "react";
13
+ import { dateToDatetimeInputStr, dateToInputStr } from "../util/datetime";
14
+ var useFormStateFromTableFilterState = function (tableFilterState) {
15
+ return useMemo(function () {
16
+ return Object.keys(tableFilterState).reduce(function (filterFormState, colName) {
17
+ var editableState = tableFilterState[colName].editableState;
18
+ switch (editableState.type) {
19
+ case "string": {
20
+ filterFormState[colName] = __assign({}, editableState);
21
+ break;
22
+ }
23
+ case "number": {
24
+ filterFormState[colName] = {
25
+ type: "number",
26
+ scheme: editableState.scheme,
27
+ enabled: editableState.enabled,
28
+ inputValue: editableState.numValue === null
29
+ ? ""
30
+ : String(editableState.numValue),
31
+ };
32
+ break;
33
+ }
34
+ default: {
35
+ // date or datetime
36
+ var partialFormState = {
37
+ type: editableState.type,
38
+ enabled: editableState.enabled,
39
+ };
40
+ var dateToStrConverter = editableState.type === "date"
41
+ ? dateToInputStr
42
+ : dateToDatetimeInputStr;
43
+ switch (editableState.scheme) {
44
+ case "startFrom": {
45
+ filterFormState[colName] = __assign(__assign({}, partialFormState), { scheme: "startFrom", startDate: editableState.startDate === null
46
+ ? ""
47
+ : dateToStrConverter(editableState.startDate), endDate: "" });
48
+ break;
49
+ }
50
+ case "endAt": {
51
+ filterFormState[colName] = __assign(__assign({}, partialFormState), { scheme: "endAt", startDate: "", endDate: editableState.endDate === null
52
+ ? ""
53
+ : dateToStrConverter(editableState.endDate) });
54
+ break;
55
+ }
56
+ default: {
57
+ // between
58
+ filterFormState[colName] = __assign(__assign({}, partialFormState), { scheme: "between", startDate: editableState.startDate === null
59
+ ? ""
60
+ : dateToStrConverter(editableState.startDate), endDate: editableState.endDate === null
61
+ ? ""
62
+ : dateToStrConverter(editableState.endDate) });
63
+ break;
64
+ }
65
+ }
66
+ }
67
+ }
68
+ return filterFormState;
69
+ }, {});
70
+ }, [tableFilterState]);
71
+ };
72
+ export default useFormStateFromTableFilterState;
package/Grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FC } from "react";
2
- import { ColDef, RowDef, Size, TableSortModel } from "./types";
2
+ import { ColDef, FilterModel, RowDef, Size, TableSortModel } from "./types";
3
3
  export interface GridPaginationState {
4
4
  pageSizeOptions: number[];
5
5
  pageSizeIndex: number;
@@ -14,6 +14,7 @@ export interface GridProps {
14
14
  cols: ColDef[];
15
15
  pagination?: GridPaginationState;
16
16
  sortModel?: TableSortModel;
17
+ filterModel?: FilterModel;
17
18
  }
18
19
  declare const Grid: FC<GridProps>;
19
20
  export default Grid;
package/Grid.jsx CHANGED
@@ -1,8 +1,12 @@
1
1
  "use client";
2
- import { useMemo } from "react";
2
+ import { useMemo, useState } from "react";
3
3
  import Pagination from "./Pagination";
4
4
  import classNames from "classnames";
5
5
  import ColHeaderCell from "./ColHeaderCell";
6
+ import useFilter from "./hooks/useFilter";
7
+ import ToggleButton from "./ToggleButton";
8
+ import FilterOptionsTable from "./FilterOptionsTable/FilterOptionsTable";
9
+ import useFilterStateFromEditable from "./hooks/useFilterStateFromEditable";
6
10
  var getTypeComparator = function (typeStr) {
7
11
  if (typeStr === "date" || typeStr === "datetime") {
8
12
  return function (a, b) { return a.valueOf() - b.valueOf(); };
@@ -24,10 +28,13 @@ var getRowComparator = function (comparator, fieldName) {
24
28
  return function (rowA, rowB) { return comparator(rowA[fieldName], rowB[fieldName]); };
25
29
  };
26
30
  var Grid = function (_a) {
27
- var rows = _a.rows, cols = _a.cols, pagination = _a.pagination, sortModel = _a.sortModel;
31
+ var rows = _a.rows, cols = _a.cols, pagination = _a.pagination, sortModel = _a.sortModel, filterModel = _a.filterModel;
32
+ var editableFilterState = (filterModel === null || filterModel === void 0 ? void 0 : filterModel.tableFilterState) || null;
33
+ var filterState = useFilterStateFromEditable(cols, editableFilterState);
34
+ var filteredRows = useFilter(rows, editableFilterState);
28
35
  var sortedRows = useMemo(function () {
29
36
  if (!sortModel || !sortModel.sortColDef) {
30
- return rows;
37
+ return filteredRows;
31
38
  }
32
39
  var sortFieldName = sortModel.sortColDef.name;
33
40
  var sortOrder = sortModel.sortColDef.order;
@@ -47,8 +54,8 @@ var Grid = function (_a) {
47
54
  };
48
55
  rowComparator = getRowComparator(descComparator, sortFieldName);
49
56
  }
50
- return rows.slice().sort(rowComparator);
51
- }, [rows, cols, sortModel]);
57
+ return filteredRows.slice().sort(rowComparator);
58
+ }, [filteredRows, cols, sortModel]);
52
59
  var currentPageRows = useMemo(function () {
53
60
  if (pagination === undefined) {
54
61
  return sortedRows;
@@ -79,7 +86,8 @@ var Grid = function (_a) {
79
86
  var displayRow = [];
80
87
  Object.keys(row).forEach(function (name) {
81
88
  if (!nameToIndex.has(name)) {
82
- throw new Error("Row data contains a property named \"".concat(name, "\", but it was not found among the column definitions."));
89
+ console.error("Warning: row data contains a property named \"".concat(name, "\", but it was not found among the column definitions."));
90
+ return;
83
91
  }
84
92
  var index = nameToIndex.get(name);
85
93
  var formatter = cols[index].formatter;
@@ -106,6 +114,7 @@ var Grid = function (_a) {
106
114
  return displayRow;
107
115
  });
108
116
  }, [currentPageRows, cols]);
117
+ var _b = useState(false), filterOptionsVisible = _b[0], setFilterOptionsVisible = _b[1];
109
118
  var handleSetPageNum = function (pageNum) {
110
119
  if (pagination === undefined) {
111
120
  return;
@@ -118,15 +127,24 @@ var Grid = function (_a) {
118
127
  }
119
128
  pagination.setPageSizeIndex(Number(event.target.value));
120
129
  };
130
+ var handleToggleFilterOptions = function () {
131
+ setFilterOptionsVisible(!filterOptionsVisible);
132
+ };
121
133
  // Once this component implements selection state, and if such interactivity is enabled, (conditionally) change the
122
134
  // aria role to "grid".
123
135
  // Array index is okay for the key for rows until some type of feature involving changing the index of rows, such as
124
136
  // sorting or pagination, is implemented.
137
+ // TODO: implement the above described features: conditionally changing aria role to grid and a key field other than
138
+ // index
125
139
  return (<div>
140
+ {filterState && filterModel && (<div>
141
+ <ToggleButton isActive={filterOptionsVisible} label={"".concat(filterOptionsVisible ? "Hide" : "Show ", " Filter Options")} onClick={handleToggleFilterOptions}/>
142
+ {filterOptionsVisible && (<FilterOptionsTable filterState={filterState} setFilterState={filterModel.setTableFilterState}/>)}
143
+ </div>)}
126
144
  <table className="table">
127
145
  <thead>
128
- <tr>
129
- {cols.map(function (_a) {
146
+ <tr aria-rowindex={1}>
147
+ {cols.map(function (_a, index) {
130
148
  var _b;
131
149
  var name = _a.name, label = _a.label, sortable = _a.sortable;
132
150
  var colSortModel = sortModel && sortable
@@ -139,13 +157,15 @@ var Grid = function (_a) {
139
157
  },
140
158
  }
141
159
  : undefined;
142
- return (<ColHeaderCell key={name} label={label} sortModel={colSortModel}/>);
160
+ return (<ColHeaderCell key={name} label={label} sortModel={colSortModel} ariaColIndex={index + 1}/>);
143
161
  })}
144
162
  </tr>
145
163
  </thead>
146
164
  <tbody>
147
- {displayRows.map(function (row, index) { return (<tr key={index}>
148
- {row.map(function (value, index) { return (<td key={index}>{value}</td>); })}
165
+ {displayRows.map(function (row, index) { return (<tr key={index} aria-rowindex={index + 2}>
166
+ {row.map(function (value, index) { return (<td key={index} aria-colindex={index + 1}>
167
+ {value}
168
+ </td>); })}
149
169
  </tr>); })}
150
170
  </tbody>
151
171
  </table>
@@ -0,0 +1,8 @@
1
+ import { FC } from "react";
2
+ export interface ToggleButtonProps {
3
+ isActive: boolean;
4
+ label: string;
5
+ onClick: () => void;
6
+ }
7
+ declare const ToggleButton: FC<ToggleButtonProps>;
8
+ export default ToggleButton;
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import classNames from "classnames";
3
+ var ToggleButton = function (_a) {
4
+ var isActive = _a.isActive, label = _a.label, onClick = _a.onClick;
5
+ var baseClasses = ["btn", "btn-primary"];
6
+ var variableClasses = {
7
+ active: isActive,
8
+ };
9
+ return (<button type="button" className={classNames(baseClasses, variableClasses)} aria-pressed={isActive} onClick={onClick}>
10
+ {label}
11
+ </button>);
12
+ };
13
+ export default ToggleButton;
@@ -0,0 +1,3 @@
1
+ import { EditableTableFilterState, TableFilterState } from "../types";
2
+ declare const useEditableFromFilterState: (filterState: TableFilterState | null) => EditableTableFilterState | null;
3
+ export default useEditableFromFilterState;
@@ -0,0 +1,14 @@
1
+ import { useMemo } from "react";
2
+ var useEditableFromFilterState = function (filterState) {
3
+ return useMemo(function () {
4
+ if (filterState === null) {
5
+ return null;
6
+ }
7
+ var editableState = {};
8
+ Object.keys(filterState).forEach(function (columnName) {
9
+ editableState[columnName] = filterState[columnName].editableState;
10
+ });
11
+ return editableState;
12
+ }, [filterState]);
13
+ };
14
+ export default useEditableFromFilterState;
@@ -0,0 +1,3 @@
1
+ import { EditableTableFilterState, RowDef } from "../types";
2
+ declare const useFilter: (rows: RowDef[], filterState: EditableTableFilterState | null) => RowDef[];
3
+ export default useFilter;
@@ -0,0 +1,82 @@
1
+ import { useMemo } from "react";
2
+ var useFilter = function (rows, filterState) {
3
+ return useMemo(function () {
4
+ if (filterState === null) {
5
+ return rows;
6
+ }
7
+ return rows.filter(function (row) {
8
+ function checkIfPassesStringFilter(value, state) {
9
+ switch (state.scheme) {
10
+ case "contains": {
11
+ return value.includes(state.searchString);
12
+ }
13
+ case "startsWith": {
14
+ return value.startsWith(state.searchString);
15
+ }
16
+ default: {
17
+ return value.endsWith(state.searchString);
18
+ }
19
+ }
20
+ }
21
+ function checkIfPassesNumberFilter(value, state) {
22
+ var numValue = Number(state.numValue); // Note that a blank string becomes 0
23
+ switch (state.scheme) {
24
+ case "equals":
25
+ return value === numValue;
26
+ case "greaterThan":
27
+ return value > numValue;
28
+ case "lessThan":
29
+ return value < numValue;
30
+ case "greaterOrEqual":
31
+ return value >= numValue;
32
+ default:
33
+ return value <= numValue;
34
+ }
35
+ }
36
+ function checkIfPassesDateFilter(value, state) {
37
+ switch (state.scheme) {
38
+ case "startFrom":
39
+ return state.startDate === null || value >= state.startDate;
40
+ case "endAt":
41
+ return state.endDate === null || value <= state.endDate;
42
+ case "between":
43
+ return (state.startDate === null ||
44
+ state.endDate === null ||
45
+ (value >= state.startDate && value <= state.endDate));
46
+ }
47
+ }
48
+ var columnNames = Object.keys(row);
49
+ for (var _i = 0, columnNames_1 = columnNames; _i < columnNames_1.length; _i++) {
50
+ var columnName = columnNames_1[_i];
51
+ if (!(columnName in filterState)) {
52
+ continue;
53
+ }
54
+ if (!filterState[columnName].enabled) {
55
+ continue;
56
+ }
57
+ var columnFilterState = filterState[columnName];
58
+ switch (columnFilterState.type) {
59
+ case "string": {
60
+ if (!checkIfPassesStringFilter(row[columnName], columnFilterState)) {
61
+ return false;
62
+ }
63
+ break;
64
+ }
65
+ case "number": {
66
+ if (!checkIfPassesNumberFilter(row[columnName], columnFilterState)) {
67
+ return false;
68
+ }
69
+ break;
70
+ }
71
+ default: {
72
+ if (!checkIfPassesDateFilter(row[columnName], columnFilterState)) {
73
+ return false;
74
+ }
75
+ }
76
+ }
77
+ }
78
+ return true;
79
+ });
80
+ }, [rows, filterState]);
81
+ };
82
+ export default useFilter;
@@ -0,0 +1,3 @@
1
+ import { ColDef, EditableTableFilterState, TableFilterState } from "../types";
2
+ declare const useFilterStateFromEditable: (colDefs: ColDef[], editableFilterState: EditableTableFilterState | null) => TableFilterState | null;
3
+ export default useFilterStateFromEditable;
@@ -0,0 +1,18 @@
1
+ import { useMemo } from "react";
2
+ var useFilterStateFromEditable = function (colDefs, editableFilterState) {
3
+ return useMemo(function () {
4
+ if (editableFilterState === null) {
5
+ return null;
6
+ }
7
+ var filterState = {};
8
+ colDefs.forEach(function (_a) {
9
+ var name = _a.name, label = _a.label;
10
+ filterState[name] = {
11
+ editableState: editableFilterState[name],
12
+ label: label,
13
+ };
14
+ });
15
+ return filterState;
16
+ }, [colDefs, editableFilterState]);
17
+ };
18
+ export default useFilterStateFromEditable;
@@ -0,0 +1,2 @@
1
+ import { ColDataTypeStrings } from "../types";
2
+ export declare const colTypeDescs: Record<ColDataTypeStrings, string>;
@@ -0,0 +1,6 @@
1
+ export var colTypeDescs = {
2
+ string: "String",
3
+ number: "Number",
4
+ date: "Date",
5
+ datetime: "DateTime",
6
+ };
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": "0.1.4",
4
+ "version": "1.1.0",
5
5
  "license": "MIT",
6
6
  "author": "Brook Li",
7
7
  "homepage": "https://react-bootstrap-data-grid.vercel.app/",
@@ -14,11 +14,12 @@
14
14
  "grid"
15
15
  ],
16
16
  "dependencies": {
17
- "classnames": "^2.5.1"
17
+ "classnames": "^2.5.1",
18
+ "dayjs": "^1.11.13"
18
19
  },
19
20
  "peerDependencies": {
20
- "react": "^18",
21
- "react-dom": "^18"
21
+ "react": "^19",
22
+ "react-dom": "^19"
22
23
  },
23
24
  "main": "./index.js",
24
25
  "module": "./index.js",
package/types.d.ts CHANGED
@@ -23,3 +23,54 @@ export interface TableSortModel {
23
23
  sortColDef: SortColDef | null;
24
24
  setSortColDef: (sortColDef: SortColDef | null) => void;
25
25
  }
26
+ export type TableFilterState = Record<string, ColFilterState>;
27
+ export type EditableTableFilterState = Record<string, FilterState>;
28
+ export interface ColFilterState {
29
+ editableState: FilterState;
30
+ label: string;
31
+ }
32
+ export interface AbstractFilterState {
33
+ enabled: boolean;
34
+ }
35
+ export declare const stringFilterSchemes: readonly ["contains", "startsWith", "endsWith"];
36
+ export type StringFilterScheme = (typeof stringFilterSchemes)[number];
37
+ export declare const stringFilterSchemeNames: Record<StringFilterScheme, string>;
38
+ export interface StringFilterState extends AbstractFilterState {
39
+ type: "string";
40
+ scheme: StringFilterScheme;
41
+ searchString: string;
42
+ }
43
+ export declare const numberFilterSchemes: readonly ["equals", "greaterThan", "lessThan", "greaterOrEqual", "lessOrEqual"];
44
+ export type NumberFilterScheme = (typeof numberFilterSchemes)[number];
45
+ export declare const numberFilterSchemeNames: Record<NumberFilterScheme, string>;
46
+ export interface NumberFilterState extends AbstractFilterState {
47
+ type: "number";
48
+ scheme: NumberFilterScheme;
49
+ numValue: number | null;
50
+ }
51
+ export declare const dateFilterSchemes: readonly ["startFrom", "endAt", "between"];
52
+ export type DateFilterScheme = (typeof dateFilterSchemes)[number];
53
+ export declare const dateFilterSchemeNames: Record<DateFilterScheme, string>;
54
+ export interface AbstractDateFilterState extends AbstractFilterState {
55
+ type: "date" | "datetime";
56
+ scheme: DateFilterScheme;
57
+ }
58
+ export interface StartDateFilterState extends AbstractDateFilterState {
59
+ scheme: "startFrom";
60
+ startDate: Date | null;
61
+ }
62
+ export interface EndDateFilterState extends AbstractDateFilterState {
63
+ scheme: "endAt";
64
+ endDate: Date | null;
65
+ }
66
+ export interface BetweenDatesFilterState extends AbstractDateFilterState {
67
+ scheme: "between";
68
+ startDate: Date | null;
69
+ endDate: Date | null;
70
+ }
71
+ export type DateFilterState = StartDateFilterState | EndDateFilterState | BetweenDatesFilterState;
72
+ export type FilterState = StringFilterState | NumberFilterState | DateFilterState;
73
+ export interface FilterModel {
74
+ tableFilterState: EditableTableFilterState;
75
+ setTableFilterState: (state: EditableTableFilterState) => void;
76
+ }
package/types.js CHANGED
@@ -1 +1,30 @@
1
- export {};
1
+ export var stringFilterSchemes = [
2
+ "contains",
3
+ "startsWith",
4
+ "endsWith",
5
+ ];
6
+ export var stringFilterSchemeNames = {
7
+ contains: "Contains",
8
+ startsWith: "Starts With",
9
+ endsWith: "Ends With",
10
+ };
11
+ export var numberFilterSchemes = [
12
+ "equals",
13
+ "greaterThan",
14
+ "lessThan",
15
+ "greaterOrEqual",
16
+ "lessOrEqual",
17
+ ];
18
+ export var numberFilterSchemeNames = {
19
+ equals: "=",
20
+ greaterThan: ">",
21
+ lessThan: "<",
22
+ greaterOrEqual: ">=",
23
+ lessOrEqual: "<=",
24
+ };
25
+ export var dateFilterSchemes = ["startFrom", "endAt", "between"];
26
+ export var dateFilterSchemeNames = {
27
+ startFrom: "Start Form",
28
+ endAt: "End At",
29
+ between: "Between",
30
+ };
@@ -0,0 +1,3 @@
1
+ export declare const dateToInputStr: (date: Date) => string;
2
+ export declare const dateToDatetimeInputStr: (date: Date) => string;
3
+ export declare const datetimeInputStrToUtc: (datetimeStr: string) => string;
@@ -0,0 +1,9 @@
1
+ import dayjs from "dayjs";
2
+ export var dateToInputStr = function (date) { return dayjs(date).format("YYYY-MM-DD"); };
3
+ export var dateToDatetimeInputStr = function (date) {
4
+ return dayjs(date).format("YYYY-MM-DDTHH:mm");
5
+ };
6
+ // All dates and datetimes that the grid displays are in UTC. The datetime
7
+ // string in the value attribute of an input element of type datetime-local
8
+ // is based on the local timezone of the client.
9
+ export var datetimeInputStrToUtc = function (datetimeStr) { return datetimeStr + "Z"; };