@tint-ui/data-table 0.3.5
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/LICENSE +21 -0
- package/README.md +9 -0
- package/adapters/boolean.d.ts +10 -0
- package/adapters/boolean.js +38 -0
- package/adapters/index.d.ts +6 -0
- package/adapters/index.js +18 -0
- package/adapters/number-format.d.ts +1 -0
- package/adapters/number-format.js +42 -0
- package/adapters/number.d.ts +12 -0
- package/adapters/number.js +51 -0
- package/adapters/text.d.ts +4 -0
- package/adapters/text.js +9 -0
- package/cell-adapter-type.d.ts +43 -0
- package/cell-adapter-type.js +306 -0
- package/cjs/adapters/boolean.js +43 -0
- package/cjs/adapters/index.js +21 -0
- package/cjs/adapters/number-format.js +46 -0
- package/cjs/adapters/number.js +56 -0
- package/cjs/adapters/text.js +13 -0
- package/cjs/cell-adapter-type.js +312 -0
- package/cjs/classes.js +27 -0
- package/cjs/context.js +14 -0
- package/cjs/data-table-content.js +105 -0
- package/cjs/data-table-pagination.js +38 -0
- package/cjs/data-table-text-filter.js +83 -0
- package/cjs/data-table-toolbar.js +103 -0
- package/cjs/data-table-views-options.js +137 -0
- package/cjs/data-table.js +63 -0
- package/cjs/filter-adapter-type.js +162 -0
- package/cjs/filter-adapters/index.js +10 -0
- package/cjs/filter-adapters/option.js +152 -0
- package/cjs/filter-adapters/use-option-filter.js +195 -0
- package/cjs/filter-classes.js +26 -0
- package/cjs/filter-fn.js +84 -0
- package/cjs/index.js +99 -0
- package/cjs/package.json +3 -0
- package/cjs/pagination-arrow.js +93 -0
- package/cjs/pagination-classes.js +20 -0
- package/cjs/pagination-number.js +66 -0
- package/cjs/pagination-size-options.js +48 -0
- package/cjs/row-button-menu.js +49 -0
- package/cjs/row-popover-menu.js +52 -0
- package/cjs/toolbar-classes.js +24 -0
- package/cjs/types.js +3 -0
- package/cjs/use-data-table.js +768 -0
- package/cjs/use-lexicon.js +155 -0
- package/cjs/use-row-menu.js +60 -0
- package/cjs/use-visibility-column.js +105 -0
- package/cjs/use-visibility-filter.js +149 -0
- package/cjs/utils.js +136 -0
- package/classes.d.ts +34 -0
- package/classes.js +23 -0
- package/context.d.ts +5 -0
- package/context.js +9 -0
- package/data-table-content.d.ts +9 -0
- package/data-table-content.js +98 -0
- package/data-table-pagination.d.ts +5 -0
- package/data-table-pagination.js +31 -0
- package/data-table-text-filter.d.ts +7 -0
- package/data-table-text-filter.js +76 -0
- package/data-table-toolbar.d.ts +5 -0
- package/data-table-toolbar.js +95 -0
- package/data-table-views-options.d.ts +2 -0
- package/data-table-views-options.js +133 -0
- package/data-table.d.ts +18 -0
- package/data-table.js +56 -0
- package/filter-adapter-type.d.ts +9 -0
- package/filter-adapter-type.js +155 -0
- package/filter-adapters/index.d.ts +5 -0
- package/filter-adapters/index.js +7 -0
- package/filter-adapters/option.d.ts +3 -0
- package/filter-adapters/option.js +147 -0
- package/filter-adapters/use-option-filter.d.ts +27 -0
- package/filter-adapters/use-option-filter.js +192 -0
- package/filter-classes.d.ts +32 -0
- package/filter-classes.js +22 -0
- package/filter-fn.d.ts +7 -0
- package/filter-fn.js +76 -0
- package/index.d.ts +16 -0
- package/index.js +16 -0
- package/package.json +97 -0
- package/pagination-arrow.d.ts +5 -0
- package/pagination-arrow.js +86 -0
- package/pagination-classes.d.ts +20 -0
- package/pagination-classes.js +16 -0
- package/pagination-number.d.ts +5 -0
- package/pagination-number.js +59 -0
- package/pagination-size-options.d.ts +3 -0
- package/pagination-size-options.js +44 -0
- package/row-button-menu.d.ts +5 -0
- package/row-button-menu.js +45 -0
- package/row-popover-menu.d.ts +5 -0
- package/row-popover-menu.js +48 -0
- package/styles-filter.css +1 -0
- package/styles-filter.module.css +64 -0
- package/styles-filter.module.scss +65 -0
- package/styles-pagination.css +1 -0
- package/styles-pagination.module.css +28 -0
- package/styles-pagination.module.scss +31 -0
- package/styles-toolbar.css +1 -0
- package/styles-toolbar.module.css +70 -0
- package/styles-toolbar.module.scss +67 -0
- package/styles.css +1 -0
- package/styles.json +8 -0
- package/styles.module.css +39 -0
- package/styles.module.scss +38 -0
- package/toolbar-classes.d.ts +26 -0
- package/toolbar-classes.js +20 -0
- package/types.d.ts +226 -0
- package/types.js +2 -0
- package/use-data-table.d.ts +3 -0
- package/use-data-table.js +768 -0
- package/use-lexicon.d.ts +12 -0
- package/use-lexicon.js +151 -0
- package/use-row-menu.d.ts +7 -0
- package/use-row-menu.js +58 -0
- package/use-visibility-column.d.ts +7 -0
- package/use-visibility-column.js +101 -0
- package/use-visibility-filter.d.ts +7 -0
- package/use-visibility-filter.js +145 -0
- package/utils.d.ts +14 -0
- package/utils.js +128 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { invariant, warningOnce } from "@tint-ui/tools/proof";
|
|
5
|
+
import { errorMessage } from "@tint-ui/tools/error-message";
|
|
6
|
+
import { adapters as coreAdapters } from "./filter-adapters/index.js";
|
|
7
|
+
import { useDataTableClasses } from "./classes.js";
|
|
8
|
+
const adapters = new Map();
|
|
9
|
+
const defaultAdapters = new Map();
|
|
10
|
+
Object.entries(coreAdapters).forEach(([name, {
|
|
11
|
+
adapter
|
|
12
|
+
}]) => {
|
|
13
|
+
defaultAdapters.set(name, {
|
|
14
|
+
adapter
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
const getAdapt = type => {
|
|
18
|
+
const adapter = adapters.get(type) || defaultAdapters.get(type);
|
|
19
|
+
if (!adapter) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return adapter;
|
|
23
|
+
};
|
|
24
|
+
const privateTypes = [];
|
|
25
|
+
const checkAdapt = type => {
|
|
26
|
+
if (!type) {
|
|
27
|
+
throw new Error(`Adapter type is empty`);
|
|
28
|
+
}
|
|
29
|
+
if (privateTypes.includes(type)) {
|
|
30
|
+
throw new Error(`The ${type} type is private`);
|
|
31
|
+
}
|
|
32
|
+
if (adapters.has(type)) {
|
|
33
|
+
throw new Error(`The ${type} form adapter already exists`);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const hasFilterAdapter = type => adapters.has(type);
|
|
37
|
+
const addFilterAdapter = function (name, adapter) {
|
|
38
|
+
checkAdapt(name);
|
|
39
|
+
adapters.set(name, {
|
|
40
|
+
adapter
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
const addFilterAdapterAsync = function (name, handler) {
|
|
44
|
+
checkAdapt(name);
|
|
45
|
+
adapters.set(name, {
|
|
46
|
+
adapter: createAsyncAdapterCallback(name, handler)
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
const getAsyncAdapter = function (type, result) {
|
|
50
|
+
const adapter = result?.default;
|
|
51
|
+
invariant(typeof adapter === "function", `Async ${type} adapter failure: default is not function`);
|
|
52
|
+
return adapter;
|
|
53
|
+
};
|
|
54
|
+
const createAsyncAdapterCallback = function (type, handler) {
|
|
55
|
+
const adapt = {
|
|
56
|
+
waiters: new Set(),
|
|
57
|
+
handler,
|
|
58
|
+
error: null
|
|
59
|
+
};
|
|
60
|
+
const adapter = filter => {
|
|
61
|
+
return /*#__PURE__*/React.createElement(DataTableFilterAdapterAsync, {
|
|
62
|
+
adapt: adapt,
|
|
63
|
+
type: type,
|
|
64
|
+
filter: filter
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
adapt.adapter = adapter;
|
|
68
|
+
return adapter;
|
|
69
|
+
};
|
|
70
|
+
const createAsyncPromise = async (type, adapt) => {
|
|
71
|
+
try {
|
|
72
|
+
const result = await adapt.handler();
|
|
73
|
+
const adapter = getAsyncAdapter(type, result);
|
|
74
|
+
adapt.error = null;
|
|
75
|
+
adapt.adapter = adapter;
|
|
76
|
+
adapters.set(type, {
|
|
77
|
+
adapter
|
|
78
|
+
});
|
|
79
|
+
} catch (err) {
|
|
80
|
+
adapt.error = errorMessage(err).message;
|
|
81
|
+
}
|
|
82
|
+
let event;
|
|
83
|
+
if (adapt.error != null) {
|
|
84
|
+
event = {
|
|
85
|
+
error: adapt.error
|
|
86
|
+
};
|
|
87
|
+
} else if (adapt.adapter) {
|
|
88
|
+
event = {
|
|
89
|
+
adapter: adapt.adapter
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const handlers = Array.from(adapt.waiters.values());
|
|
95
|
+
adapt.waiters.clear();
|
|
96
|
+
handlers.forEach(handler => {
|
|
97
|
+
handler(event);
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
const DataTableFilterAdapterAsync = props => {
|
|
101
|
+
const classes = useDataTableClasses();
|
|
102
|
+
const {
|
|
103
|
+
adapt,
|
|
104
|
+
filter,
|
|
105
|
+
type
|
|
106
|
+
} = props;
|
|
107
|
+
const [state, setState] = React.useState(() => {
|
|
108
|
+
return adapt.adapter ? {
|
|
109
|
+
adapter: adapt.adapter
|
|
110
|
+
} : adapt.error != null ? {
|
|
111
|
+
error: adapt.error
|
|
112
|
+
} : null;
|
|
113
|
+
});
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
const {
|
|
116
|
+
adapter,
|
|
117
|
+
error,
|
|
118
|
+
promise
|
|
119
|
+
} = adapt;
|
|
120
|
+
if (error != null || adapter != null) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (promise == null) {
|
|
124
|
+
adapt.promise = createAsyncPromise(type, adapt);
|
|
125
|
+
}
|
|
126
|
+
adapt.waiters.add(setState);
|
|
127
|
+
return () => {
|
|
128
|
+
adapt.waiters.delete(setState);
|
|
129
|
+
};
|
|
130
|
+
}, [type, adapt]);
|
|
131
|
+
if (state == null) {
|
|
132
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
133
|
+
className: classes.loader
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if ("error" in state) {
|
|
137
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
138
|
+
className: classes.error
|
|
139
|
+
}, state.error);
|
|
140
|
+
}
|
|
141
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, state.adapter(filter));
|
|
142
|
+
};
|
|
143
|
+
DataTableFilterAdapterAsync.displayName = "DataTableFilterAdapterAsync";
|
|
144
|
+
const renderDataTableFilter = function (filter) {
|
|
145
|
+
const {
|
|
146
|
+
type = "option"
|
|
147
|
+
} = filter;
|
|
148
|
+
const adapt = getAdapt(type);
|
|
149
|
+
if (!adapt) {
|
|
150
|
+
warningOnce(`table-filter:${type}`, false, `The ${type} filter type not found`);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return adapt.adapter(filter);
|
|
154
|
+
};
|
|
155
|
+
export { hasFilterAdapter, addFilterAdapter, addFilterAdapterAsync, renderDataTableFilter };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { SvgThemeIcon } from "@tint-ui/svg-icon";
|
|
6
|
+
import clsx from "clsx";
|
|
7
|
+
import { isEmptyString } from "@tint-ui/tools/is-empty";
|
|
8
|
+
import { Badge } from "@tint-ui/badge";
|
|
9
|
+
import { Button } from "@tint-ui/button";
|
|
10
|
+
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from "@tint-ui/command";
|
|
11
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@tint-ui/popover";
|
|
12
|
+
import { useOptionFilter } from "./use-option-filter.js";
|
|
13
|
+
import { useDataTableFilterClasses } from "../filter-classes.js";
|
|
14
|
+
const createOptionGroups = (options, groupBy) => {
|
|
15
|
+
const root = [];
|
|
16
|
+
const optionGroup = [];
|
|
17
|
+
const cache = new Map();
|
|
18
|
+
for (const option of options) {
|
|
19
|
+
const {
|
|
20
|
+
config
|
|
21
|
+
} = option;
|
|
22
|
+
if (config && groupBy in config && config[groupBy]) {
|
|
23
|
+
const heading = String(config[groupBy]).trim();
|
|
24
|
+
let options = cache.get(heading);
|
|
25
|
+
if (!options) {
|
|
26
|
+
options = [];
|
|
27
|
+
cache.set(heading, options);
|
|
28
|
+
optionGroup.push({
|
|
29
|
+
heading,
|
|
30
|
+
options
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
options.push(option);
|
|
34
|
+
} else {
|
|
35
|
+
root.push(option);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (root.length) {
|
|
39
|
+
optionGroup.push({
|
|
40
|
+
options: root
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return optionGroup;
|
|
44
|
+
};
|
|
45
|
+
const FilterOptionType = ({
|
|
46
|
+
filter
|
|
47
|
+
}) => {
|
|
48
|
+
const classes = useDataTableFilterClasses();
|
|
49
|
+
const ctx = useOptionFilter(filter);
|
|
50
|
+
const column = ctx.column;
|
|
51
|
+
if (!column) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const {
|
|
55
|
+
value,
|
|
56
|
+
options,
|
|
57
|
+
getSelectedOptions,
|
|
58
|
+
manual,
|
|
59
|
+
inputProps,
|
|
60
|
+
lexicon,
|
|
61
|
+
icon,
|
|
62
|
+
groupBy = null,
|
|
63
|
+
disableSearch = false
|
|
64
|
+
} = ctx;
|
|
65
|
+
const selectedOptions = getSelectedOptions();
|
|
66
|
+
const optionGroups = React.useMemo(() => groupBy ? createOptionGroups(options, groupBy) : [{
|
|
67
|
+
options
|
|
68
|
+
}], [options, groupBy]);
|
|
69
|
+
return /*#__PURE__*/React.createElement(Popover, null, /*#__PURE__*/React.createElement(PopoverTrigger, {
|
|
70
|
+
asChild: true
|
|
71
|
+
}, /*#__PURE__*/React.createElement(Button, {
|
|
72
|
+
variant: "outline",
|
|
73
|
+
size: ctx.size,
|
|
74
|
+
className: classes.button,
|
|
75
|
+
iconLeft: /*#__PURE__*/React.createElement(SvgThemeIcon, {
|
|
76
|
+
icon: icon || "data-table-add-filter"
|
|
77
|
+
})
|
|
78
|
+
}, /*#__PURE__*/React.createElement("span", null, filter.label), selectedOptions.length > 0 && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Badge, {
|
|
79
|
+
variant: "secondary",
|
|
80
|
+
className: clsx(classes.badge, classes.mobile)
|
|
81
|
+
}, selectedOptions.length), /*#__PURE__*/React.createElement("div", {
|
|
82
|
+
className: clsx(classes.badges, classes.desktop)
|
|
83
|
+
}, selectedOptions.length > 2 ? /*#__PURE__*/React.createElement(Badge, {
|
|
84
|
+
variant: "secondary",
|
|
85
|
+
className: classes.badge
|
|
86
|
+
}, lexicon.filterSelected(selectedOptions.length)) : selectedOptions.map(option => /*#__PURE__*/React.createElement(Badge, {
|
|
87
|
+
variant: "secondary",
|
|
88
|
+
key: option.value,
|
|
89
|
+
className: classes.badge
|
|
90
|
+
}, option.label)))))), /*#__PURE__*/React.createElement(PopoverContent, {
|
|
91
|
+
className: classes.popover,
|
|
92
|
+
align: "start"
|
|
93
|
+
}, /*#__PURE__*/React.createElement(Command, null, (manual || options.length > 5 && !disableSearch) && /*#__PURE__*/React.createElement(CommandInput, _extends({
|
|
94
|
+
placeholder: lexicon.filterSearch(filter.label)
|
|
95
|
+
}, manual ? inputProps : null)), /*#__PURE__*/React.createElement(CommandList, null, /*#__PURE__*/React.createElement(CommandEmpty, null, lexicon.filterNotFound), optionGroups.map(({
|
|
96
|
+
heading,
|
|
97
|
+
options
|
|
98
|
+
}, index) => /*#__PURE__*/React.createElement(CommandGroup, {
|
|
99
|
+
key: index,
|
|
100
|
+
heading: heading
|
|
101
|
+
}, options.map(option => {
|
|
102
|
+
const optionValue = option.value;
|
|
103
|
+
const isSelected = value.includes(optionValue);
|
|
104
|
+
const {
|
|
105
|
+
icon
|
|
106
|
+
} = option.config || {};
|
|
107
|
+
return /*#__PURE__*/React.createElement(CommandItem, {
|
|
108
|
+
key: option.value,
|
|
109
|
+
onSelect: () => {
|
|
110
|
+
const selectedValues = new Set(value);
|
|
111
|
+
if (isSelected) {
|
|
112
|
+
selectedValues.delete(optionValue);
|
|
113
|
+
} else {
|
|
114
|
+
if (!filter.multiple) {
|
|
115
|
+
selectedValues.clear();
|
|
116
|
+
}
|
|
117
|
+
selectedValues.add(optionValue);
|
|
118
|
+
}
|
|
119
|
+
column.setFilterValue(Array.from(selectedValues));
|
|
120
|
+
}
|
|
121
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
122
|
+
className: clsx(classes.checkbox, isSelected && classes.selected)
|
|
123
|
+
}, /*#__PURE__*/React.createElement(SvgThemeIcon, {
|
|
124
|
+
icon: "check",
|
|
125
|
+
"aria-hidden": "true"
|
|
126
|
+
})), !isEmptyString(icon) && /*#__PURE__*/React.createElement(SvgThemeIcon, {
|
|
127
|
+
icon: icon,
|
|
128
|
+
className: classes.icon,
|
|
129
|
+
"aria-hidden": "true"
|
|
130
|
+
}), /*#__PURE__*/React.createElement("span", {
|
|
131
|
+
className: classes.label
|
|
132
|
+
}, option.label));
|
|
133
|
+
}))), value.length > 0 && /*#__PURE__*/React.createElement(CommandGroup, {
|
|
134
|
+
forceMount: true
|
|
135
|
+
}, /*#__PURE__*/React.createElement(CommandSeparator, null), /*#__PURE__*/React.createElement(CommandItem, {
|
|
136
|
+
onSelect: () => {
|
|
137
|
+
column.setFilterValue(undefined);
|
|
138
|
+
}
|
|
139
|
+
}, lexicon.filterClear))))));
|
|
140
|
+
};
|
|
141
|
+
FilterOptionType.displayName = "FilterOptionType";
|
|
142
|
+
const optionFilterAdapter = filter => {
|
|
143
|
+
return /*#__PURE__*/React.createElement(FilterOptionType, {
|
|
144
|
+
filter: filter
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
export { optionFilterAdapter };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Column } from "@tanstack/react-table";
|
|
2
|
+
import type { InputSelectOption } from "@tint-ui/tools";
|
|
3
|
+
import type { DataTableDisplayFilter, DataTableFilterOptionConfig } from "../types";
|
|
4
|
+
declare const useOptionFilter: <TData>(filter: DataTableDisplayFilter<keyof TData, DataTableFilterOptionConfig>) => {
|
|
5
|
+
name: keyof TData extends infer T ? T extends keyof TData ? T extends string ? T : string : never : never;
|
|
6
|
+
column: Column<TData, unknown> | undefined;
|
|
7
|
+
inputProps: {
|
|
8
|
+
readonly value: string;
|
|
9
|
+
onValueChange(inputText: string): void;
|
|
10
|
+
onFocus(): void;
|
|
11
|
+
};
|
|
12
|
+
getSelectedOptions: () => InputSelectOption<object>[];
|
|
13
|
+
lexicon: import("../types").LexiconType;
|
|
14
|
+
size: import("../types").DataTableToolbarSize;
|
|
15
|
+
manual: boolean;
|
|
16
|
+
value: string[];
|
|
17
|
+
options: InputSelectOption<object>[];
|
|
18
|
+
inputText: string;
|
|
19
|
+
error: string | null;
|
|
20
|
+
loading: boolean;
|
|
21
|
+
type?: "string" | "number" | "boolean" | undefined;
|
|
22
|
+
icon?: string | undefined;
|
|
23
|
+
disableSearch?: boolean | undefined;
|
|
24
|
+
autoSelect?: boolean | undefined;
|
|
25
|
+
groupBy?: string | undefined;
|
|
26
|
+
};
|
|
27
|
+
export { useOptionFilter };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useDataTableContext } from "../context.js";
|
|
5
|
+
import { errorMessage } from "@tint-ui/tools/error-message";
|
|
6
|
+
import { makeOption } from "@tint-ui/tools/make-option";
|
|
7
|
+
const getOptions = (result, dump) => {
|
|
8
|
+
const options = [];
|
|
9
|
+
if (result.length) {
|
|
10
|
+
result.forEach(option => {
|
|
11
|
+
const item = makeOption(option, dump);
|
|
12
|
+
if (item) {
|
|
13
|
+
options.push(item);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return options;
|
|
18
|
+
};
|
|
19
|
+
const useOptionFilter = function (filter) {
|
|
20
|
+
const {
|
|
21
|
+
name,
|
|
22
|
+
config: {
|
|
23
|
+
options: filterOptions,
|
|
24
|
+
initialOptions,
|
|
25
|
+
...rest
|
|
26
|
+
} = {}
|
|
27
|
+
} = filter;
|
|
28
|
+
const ctx = useDataTableContext();
|
|
29
|
+
const column = ctx.table.getColumn(name);
|
|
30
|
+
const refFn = React.useRef(null);
|
|
31
|
+
refFn.current = typeof filterOptions === "function" ? filterOptions : null;
|
|
32
|
+
const refDump = React.useRef({});
|
|
33
|
+
const filterValue = column ? column.getFilterValue() : null;
|
|
34
|
+
const value = filterValue == null ? [] : Array.isArray(filterValue) ? filterValue : [filterValue];
|
|
35
|
+
const refValue = React.useRef(value);
|
|
36
|
+
refValue.current = value;
|
|
37
|
+
const [state, setState] = React.useState(() => ({
|
|
38
|
+
options: Array.isArray(filterOptions) ? getOptions(filterOptions, refDump.current) : Array.isArray(initialOptions) ? getOptions(initialOptions, refDump.current) : [],
|
|
39
|
+
inputText: "",
|
|
40
|
+
text: "",
|
|
41
|
+
loading: false,
|
|
42
|
+
error: null
|
|
43
|
+
}));
|
|
44
|
+
const refState = React.useRef(state);
|
|
45
|
+
refState.current = state;
|
|
46
|
+
const {
|
|
47
|
+
onMount,
|
|
48
|
+
inputProps,
|
|
49
|
+
getSelectedOptions
|
|
50
|
+
} = React.useMemo(() => {
|
|
51
|
+
let localState = state;
|
|
52
|
+
let lazyId = null;
|
|
53
|
+
let mount = false;
|
|
54
|
+
let abortController = null;
|
|
55
|
+
const update = newState => {
|
|
56
|
+
if (!mount) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
localState = {
|
|
60
|
+
...localState,
|
|
61
|
+
...newState
|
|
62
|
+
};
|
|
63
|
+
setState(localState);
|
|
64
|
+
};
|
|
65
|
+
const lazyClear = () => {
|
|
66
|
+
if (lazyId != null) {
|
|
67
|
+
window.clearTimeout(lazyId);
|
|
68
|
+
lazyId = null;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const abort = () => {
|
|
72
|
+
if (abortController != null) {
|
|
73
|
+
abortController.abort();
|
|
74
|
+
abortController = null;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const query = (force = false, mode = "search") => {
|
|
78
|
+
if (!mount) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const callback = refFn.current;
|
|
82
|
+
const text = localState.inputText.trim();
|
|
83
|
+
if (callback == null) {
|
|
84
|
+
return update({
|
|
85
|
+
loading: false,
|
|
86
|
+
error: null,
|
|
87
|
+
text
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (!force && localState.text === text) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
abort();
|
|
94
|
+
update({
|
|
95
|
+
loading: true,
|
|
96
|
+
error: null,
|
|
97
|
+
text
|
|
98
|
+
});
|
|
99
|
+
abortController = new AbortController();
|
|
100
|
+
const signal = abortController.signal;
|
|
101
|
+
(async (...args) => callback(...args))(localState.text, refValue.current, mode, abortController).then(result => {
|
|
102
|
+
const options = [];
|
|
103
|
+
const resultDump = {};
|
|
104
|
+
result.forEach(option => {
|
|
105
|
+
const item = makeOption(option, resultDump);
|
|
106
|
+
if (item) {
|
|
107
|
+
options.push(item);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
Object.assign(refDump.current, resultDump);
|
|
111
|
+
update({
|
|
112
|
+
options,
|
|
113
|
+
loading: false
|
|
114
|
+
});
|
|
115
|
+
}).catch(error => {
|
|
116
|
+
if (signal.aborted) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
update({
|
|
120
|
+
loading: false,
|
|
121
|
+
error: errorMessage(error).message
|
|
122
|
+
});
|
|
123
|
+
}).finally(() => {
|
|
124
|
+
abortController = null;
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
const lazyLoad = () => {
|
|
128
|
+
if (refFn.current) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
lazyClear();
|
|
132
|
+
lazyId = window.setTimeout(() => {
|
|
133
|
+
lazyId = null;
|
|
134
|
+
query();
|
|
135
|
+
}, 250);
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
onMount() {
|
|
139
|
+
mount = true;
|
|
140
|
+
if (refValue.current.length) {
|
|
141
|
+
query(true, "initial");
|
|
142
|
+
}
|
|
143
|
+
return () => {
|
|
144
|
+
mount = false;
|
|
145
|
+
lazyClear();
|
|
146
|
+
abort();
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
getSelectedOptions() {
|
|
150
|
+
const options = [];
|
|
151
|
+
refValue.current.forEach(name => {
|
|
152
|
+
const option = refDump.current[name];
|
|
153
|
+
if (option) {
|
|
154
|
+
options.push(option);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
return options;
|
|
158
|
+
},
|
|
159
|
+
inputProps: {
|
|
160
|
+
get value() {
|
|
161
|
+
return localState.inputText;
|
|
162
|
+
},
|
|
163
|
+
onValueChange(inputText) {
|
|
164
|
+
update({
|
|
165
|
+
inputText
|
|
166
|
+
});
|
|
167
|
+
lazyLoad();
|
|
168
|
+
},
|
|
169
|
+
onFocus() {
|
|
170
|
+
query(true);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}, []);
|
|
175
|
+
React.useEffect(onMount, [onMount]);
|
|
176
|
+
return {
|
|
177
|
+
...rest,
|
|
178
|
+
name,
|
|
179
|
+
column,
|
|
180
|
+
inputProps,
|
|
181
|
+
getSelectedOptions,
|
|
182
|
+
lexicon: ctx.lexicon,
|
|
183
|
+
size: ctx.toolbar.size,
|
|
184
|
+
manual: refFn.current != null,
|
|
185
|
+
value: refValue.current,
|
|
186
|
+
options: state.options,
|
|
187
|
+
inputText: state.inputText,
|
|
188
|
+
error: state.error,
|
|
189
|
+
loading: state.loading
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
export { useOptionFilter };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
declare const filterClasses: {
|
|
2
|
+
button: string;
|
|
3
|
+
popover: string;
|
|
4
|
+
badges: string;
|
|
5
|
+
badge: string;
|
|
6
|
+
mobile: string;
|
|
7
|
+
desktop: string;
|
|
8
|
+
checkbox: string;
|
|
9
|
+
icon: string;
|
|
10
|
+
label: string;
|
|
11
|
+
selected: string;
|
|
12
|
+
text: string;
|
|
13
|
+
textMobile: string;
|
|
14
|
+
};
|
|
15
|
+
declare const useDataTableFilterClasses: () => {
|
|
16
|
+
button: string;
|
|
17
|
+
popover: string;
|
|
18
|
+
badges: string;
|
|
19
|
+
badge: string;
|
|
20
|
+
mobile: string;
|
|
21
|
+
desktop: string;
|
|
22
|
+
checkbox: string;
|
|
23
|
+
icon: string;
|
|
24
|
+
label: string;
|
|
25
|
+
selected: string;
|
|
26
|
+
text: string;
|
|
27
|
+
textMobile: string;
|
|
28
|
+
};
|
|
29
|
+
type DataTableFilterClassesType = keyof typeof filterClasses;
|
|
30
|
+
type DataTableFilterClasses = Record<DataTableFilterClassesType, string>;
|
|
31
|
+
export { filterClasses, useDataTableFilterClasses };
|
|
32
|
+
export type { DataTableFilterClassesType, DataTableFilterClasses };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { classGroup } from "@tint-ui/tools/class-group";
|
|
2
|
+
import { useClasses } from "@tint-ui/theme";
|
|
3
|
+
const {
|
|
4
|
+
base,
|
|
5
|
+
b
|
|
6
|
+
} = classGroup("data-table-filter");
|
|
7
|
+
const filterClasses = {
|
|
8
|
+
button: base,
|
|
9
|
+
popover: b("popover"),
|
|
10
|
+
badges: b("badges"),
|
|
11
|
+
badge: b("badge"),
|
|
12
|
+
mobile: b("mobile"),
|
|
13
|
+
desktop: b("desktop"),
|
|
14
|
+
checkbox: b("checkbox"),
|
|
15
|
+
icon: b("icon"),
|
|
16
|
+
label: b("label"),
|
|
17
|
+
selected: b("selected"),
|
|
18
|
+
text: b("text"),
|
|
19
|
+
textMobile: b("text", "mobile")
|
|
20
|
+
};
|
|
21
|
+
const useDataTableFilterClasses = () => useClasses("data-table-filter", filterClasses);
|
|
22
|
+
export { filterClasses, useDataTableFilterClasses };
|
package/filter-fn.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FilterFn } from "@tanstack/react-table";
|
|
2
|
+
declare const filterBoolean: FilterFn<any>;
|
|
3
|
+
declare const filterNumber: FilterFn<any>;
|
|
4
|
+
declare const filterNumberMultiple: FilterFn<any>;
|
|
5
|
+
declare const filterString: FilterFn<any>;
|
|
6
|
+
declare const filterStringMultiple: FilterFn<any>;
|
|
7
|
+
export { filterBoolean, filterNumber, filterNumberMultiple, filterString, filterStringMultiple };
|
package/filter-fn.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const isEmpty = (value, filterValue) => value == null || Array.isArray(filterValue) && filterValue.length === 0;
|
|
2
|
+
const filterBoolean = (row, columnId, filterValue) => {
|
|
3
|
+
const value = row.getValue(columnId);
|
|
4
|
+
if (Array.isArray(filterValue)) {
|
|
5
|
+
filterValue = filterValue[0];
|
|
6
|
+
}
|
|
7
|
+
if (value == null) {
|
|
8
|
+
return filterValue === "";
|
|
9
|
+
}
|
|
10
|
+
if (typeof filterValue === "boolean") {
|
|
11
|
+
return value === filterValue;
|
|
12
|
+
}
|
|
13
|
+
if (filterValue === "1") {
|
|
14
|
+
return value === true;
|
|
15
|
+
}
|
|
16
|
+
if (filterValue === "0") {
|
|
17
|
+
return value === false;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
};
|
|
21
|
+
const isNumberValue = (value, filterValue) => {
|
|
22
|
+
return value === (typeof filterValue === "number" ? filterValue : parseInt(String(filterValue)));
|
|
23
|
+
};
|
|
24
|
+
const filterNumber = (row, columnId, filterValue) => {
|
|
25
|
+
const value = row.getValue(columnId);
|
|
26
|
+
if (isEmpty(value, filterValue)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (typeof value !== "number") {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return isNumberValue(value, Array.isArray(filterValue) ? filterValue[0] : filterValue);
|
|
33
|
+
};
|
|
34
|
+
const filterNumberMultiple = (row, columnId, filterValue) => {
|
|
35
|
+
const value = row.getValue(columnId);
|
|
36
|
+
if (isEmpty(value, filterValue)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (typeof value !== "number") {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(filterValue)) {
|
|
43
|
+
for (const val of filterValue) {
|
|
44
|
+
if (isNumberValue(value, val)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else if (isNumberValue(value, filterValue)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
const filterString = (row, columnId, filterValue) => {
|
|
54
|
+
const value = row.getValue(columnId);
|
|
55
|
+
if (isEmpty(value, filterValue)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return value === String(Array.isArray(filterValue) ? filterValue[0] : filterValue);
|
|
59
|
+
};
|
|
60
|
+
const filterStringMultiple = (row, columnId, filterValue) => {
|
|
61
|
+
const value = row.getValue(columnId);
|
|
62
|
+
if (isEmpty(value, filterValue)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(filterValue)) {
|
|
66
|
+
for (const val of filterValue) {
|
|
67
|
+
if (value === String(val)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} else if (value === String(filterValue)) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
76
|
+
export { filterBoolean, filterNumber, filterNumberMultiple, filterString, filterStringMultiple };
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./classes";
|
|
2
|
+
export * from "./filter-classes";
|
|
3
|
+
export * from "./toolbar-classes";
|
|
4
|
+
export * from "./pagination-classes";
|
|
5
|
+
export * from "./context";
|
|
6
|
+
export * from "./cell-adapter-type";
|
|
7
|
+
export * from "./filter-adapter-type";
|
|
8
|
+
export * from "./data-table-content";
|
|
9
|
+
export * from "./data-table-pagination";
|
|
10
|
+
export * from "./data-table-toolbar";
|
|
11
|
+
export * from "./data-table-views-options";
|
|
12
|
+
export * from "./data-table";
|
|
13
|
+
export * from "./pagination-arrow";
|
|
14
|
+
export * from "./pagination-number";
|
|
15
|
+
export * from "./pagination-size-options";
|
|
16
|
+
export * from "./types";
|