@vinorcola/dynamic-table 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/lib/ColumnDefinition.d.ts +4 -4
- package/lib/Controller.d.ts +9 -0
- package/lib/Controller.js +29 -0
- package/lib/DefaultTheme.d.ts +30 -29
- package/lib/DefaultTheme.js +3 -23
- package/lib/Icon.d.ts +12 -12
- package/lib/UniquePopupProvider.d.ts +1 -1
- package/lib/UniquePopupProvider.js +11 -7
- package/lib/ValueList.d.ts +7 -0
- package/lib/ValueList.js +10 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +4 -3
- package/lib/useColumns.d.ts +4 -4
- package/lib/useColumns.js +1 -0
- package/lib/useFilterState.js +18 -14
- package/lib/useItems.d.ts +5 -5
- package/lib/useItems.js +35 -13
- package/lib/useMaskableColumns.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { BaseItem, Dictionary, ItemKey, Primitive } from "./index.js";
|
|
3
|
-
export type ValueDecorator<Item extends BaseItem, Value extends Primitive> = (value: Value, defaultDisplay: ReactNode, item: Item) => ReactNode;
|
|
4
|
-
export type ValueResolver<Item extends BaseItem, Value extends Primitive> = (item: Item) => Value | null;
|
|
3
|
+
export type ValueDecorator<Item extends BaseItem, Value extends Primitive | Primitive[]> = (value: Value, defaultDisplay: ReactNode, item: Item) => ReactNode;
|
|
4
|
+
export type ValueResolver<Item extends BaseItem, Value extends Primitive | Primitive[]> = (item: Item) => Value | null;
|
|
5
5
|
interface BaseColumneDefinition<Item extends BaseItem, Value extends Primitive> {
|
|
6
6
|
readonly title: string;
|
|
7
7
|
readonly dictionary?: Dictionary<Value> | Promise<Dictionary<Value>>;
|
|
8
|
-
readonly decorateValue?: ValueDecorator<Item, Value>;
|
|
8
|
+
readonly decorateValue?: ValueDecorator<Item, Value | Value[]>;
|
|
9
9
|
readonly decorateNoValue?: () => ReactNode;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
@@ -19,7 +19,7 @@ interface AccessorColumnDefinition<Item extends BaseItem, Value extends Primitiv
|
|
|
19
19
|
*/
|
|
20
20
|
interface ResolvedColumnDefinition<Item extends BaseItem, Value extends Primitive> extends BaseColumneDefinition<Item, Value> {
|
|
21
21
|
readonly id: string;
|
|
22
|
-
readonly resolveValue: ValueResolver<Item, Value>;
|
|
22
|
+
readonly resolveValue: ValueResolver<Item, Value | Value[]>;
|
|
23
23
|
}
|
|
24
24
|
export type ColumnDefinition<Item extends BaseItem, Value extends Primitive> = AccessorColumnDefinition<Item, Value> | ResolvedColumnDefinition<Item, Value>;
|
|
25
25
|
export declare function isAccessorColumnDefinition<Item extends BaseItem, Value extends Primitive>(definition: ColumnDefinition<Item, Value>): definition is AccessorColumnDefinition<Item, Value>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BaseItem } from "./index.js";
|
|
2
|
+
import type { InternalColumns } from "./useColumns.js";
|
|
3
|
+
interface Props<Item extends BaseItem> {
|
|
4
|
+
columns: InternalColumns<Item>;
|
|
5
|
+
clearFilterState: () => void;
|
|
6
|
+
clearSortState: () => void;
|
|
7
|
+
}
|
|
8
|
+
export default function Controller<Item extends BaseItem>(props: Props<Item>): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import cx from "@vinorcola/utils/classNames";
|
|
3
|
+
import { CloseIcon, ColumnsIcon, FilterIcon, SortIcon } from "./Icon.js";
|
|
4
|
+
import { usePopup } from "./UniquePopupProvider.js";
|
|
5
|
+
import DynamicTable from "./index.js";
|
|
6
|
+
import { isSearchable, isSelectable } from "./useFilterState.js";
|
|
7
|
+
import { isMaskable } from "./useMaskableColumns.js";
|
|
8
|
+
import { isSortable } from "./useSortState.js";
|
|
9
|
+
export default function Controller(props) {
|
|
10
|
+
const columnListPopup = usePopup();
|
|
11
|
+
const hasHiddenColumns = props.columns.some((column) => isMaskable(column) && !column.displayed);
|
|
12
|
+
const hasFilteredColumns = props.columns.some((column) => (isSearchable(column) && column.searchText !== null) ||
|
|
13
|
+
(isSelectable(column) && column.hiddenValues.length > 0));
|
|
14
|
+
const hasHiddenFilteredColumns = hasHiddenColumns &&
|
|
15
|
+
hasFilteredColumns &&
|
|
16
|
+
props.columns.some((column) => isMaskable(column) &&
|
|
17
|
+
!column.displayed &&
|
|
18
|
+
((isSearchable(column) && column.searchText !== null) ||
|
|
19
|
+
(isSelectable(column) && column.hiddenValues.length > 0)));
|
|
20
|
+
const hasSortedColumns = props.columns.some((column) => isSortable(column) && column.sorted !== null);
|
|
21
|
+
const hasHiddenSortedColumns = hasHiddenColumns &&
|
|
22
|
+
hasSortedColumns &&
|
|
23
|
+
props.columns.some((column) => isMaskable(column) && !column.displayed && isSortable(column) && column.sorted !== null);
|
|
24
|
+
return (_jsx(DynamicTable.ControllerContainer, { filterButton: _jsx(DynamicTable.Button, { className: cx(hasHiddenFilteredColumns
|
|
25
|
+
? "[--bg-color:var(--color-orange-400)]"
|
|
26
|
+
: hasFilteredColumns && "[--bg-color:var(--color-lime-400)]"), disabled: !hasFilteredColumns, hoverChildren: _jsx(CloseIcon, {}), onClick: props.clearFilterState, children: _jsx(FilterIcon, {}) }), sortButton: _jsx(DynamicTable.Button, { className: cx(hasHiddenSortedColumns
|
|
27
|
+
? "[--bg-color:var(--color-orange-400)]"
|
|
28
|
+
: hasSortedColumns && "[--bg-color:var(--color-sky-400)]"), disabled: !hasSortedColumns, hoverChildren: _jsx(CloseIcon, {}), onClick: props.clearSortState, children: _jsx(SortIcon, {}) }), columnButton: _jsx(DynamicTable.Button, { className: cx(hasHiddenColumns ? "[--bg-color:var(--color-orange-400)]" : "[--bg-default-color:transparent]"), onClick: columnListPopup.show, children: _jsx(ColumnsIcon, {}) }), columnPopup: columnListPopup.display ? (_jsx(DynamicTable.ColumnsPopup, { columns: props.columns, onDismiss: columnListPopup.dismiss })) : undefined }));
|
|
29
|
+
}
|
package/lib/DefaultTheme.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
2
|
import type { BaseItem, Dictionary, Primitive } from "./index.js";
|
|
3
3
|
import type { InternalColumn, InternalColumns } from "./useColumns.js";
|
|
4
4
|
import { type FilterType } from "./useFilterState.js";
|
|
@@ -6,89 +6,90 @@ import { type InternalSortableColumn } from "./useSortState.js";
|
|
|
6
6
|
export interface Props {
|
|
7
7
|
children?: ReactNode;
|
|
8
8
|
}
|
|
9
|
-
export declare function TableContainer(props: Props): import("react
|
|
10
|
-
export interface
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
export declare function TableContainer(props: Props): import("react").JSX.Element;
|
|
10
|
+
export interface ControllerButtonsGroupProps {
|
|
11
|
+
filterButton: ReactElement;
|
|
12
|
+
sortButton: ReactElement;
|
|
13
|
+
columnButton: ReactElement;
|
|
14
|
+
columnPopup?: ReactElement;
|
|
14
15
|
}
|
|
15
|
-
export declare function
|
|
16
|
+
export declare function ControllerContainer(props: ControllerButtonsGroupProps): import("react").JSX.Element;
|
|
16
17
|
export interface ColumnsPopupProps<Item extends BaseItem> {
|
|
17
18
|
columns: InternalColumns<Item>;
|
|
18
19
|
onDismiss: () => void;
|
|
19
20
|
}
|
|
20
|
-
export declare function ColumnsPopup<Item extends BaseItem>(props: ColumnsPopupProps<Item>): import("react
|
|
21
|
-
export declare function Table(props: Props): import("react
|
|
22
|
-
export declare function HeaderContainer(props: Props): import("react
|
|
23
|
-
export declare function HeaderLine(props: Props): import("react
|
|
21
|
+
export declare function ColumnsPopup<Item extends BaseItem>(props: ColumnsPopupProps<Item>): import("react").JSX.Element;
|
|
22
|
+
export declare function Table(props: Props): import("react").JSX.Element;
|
|
23
|
+
export declare function HeaderContainer(props: Props): import("react").JSX.Element;
|
|
24
|
+
export declare function HeaderLine(props: Props): import("react").JSX.Element;
|
|
24
25
|
export interface SelectionHeaderProps extends Props {
|
|
25
26
|
totalSelected: number;
|
|
26
27
|
unvisibleSelected: number;
|
|
27
28
|
}
|
|
28
|
-
export declare function SelectionHeader(props: SelectionHeaderProps): import("react
|
|
29
|
+
export declare function SelectionHeader(props: SelectionHeaderProps): import("react").JSX.Element;
|
|
29
30
|
export interface HeaderProps<Item extends BaseItem, Value extends Primitive> extends Props {
|
|
30
31
|
column: InternalColumn<Item, Value>;
|
|
31
32
|
}
|
|
32
|
-
export declare function Header<Item extends BaseItem, Value extends Primitive>(props: HeaderProps<Item, Value>): import("react
|
|
33
|
+
export declare function Header<Item extends BaseItem, Value extends Primitive>(props: HeaderProps<Item, Value>): import("react").JSX.Element;
|
|
33
34
|
export interface HeaderActionsProps {
|
|
34
35
|
filterType?: FilterType;
|
|
35
36
|
onFilterPopupToggle?: () => void;
|
|
36
37
|
onDisplayToggle?: () => void;
|
|
37
38
|
}
|
|
38
|
-
export declare function HeaderActions(props: HeaderActionsProps): import("react
|
|
39
|
+
export declare function HeaderActions(props: HeaderActionsProps): import("react").JSX.Element;
|
|
39
40
|
export interface HeaderStatusProps {
|
|
40
41
|
filterType?: FilterType;
|
|
41
42
|
sortState?: NonNullable<InternalSortableColumn<BaseItem, Primitive>["sorted"]>;
|
|
42
43
|
}
|
|
43
|
-
export declare function HeaderStatus(props: HeaderStatusProps): import("react
|
|
44
|
+
export declare function HeaderStatus(props: HeaderStatusProps): import("react").JSX.Element | null;
|
|
44
45
|
export interface SearchFilterPopupProps {
|
|
45
46
|
searchText: string;
|
|
46
47
|
onSearchTextChange: (searchText: string) => void;
|
|
47
48
|
onDismiss: () => void;
|
|
48
49
|
}
|
|
49
|
-
export declare function SearchFilterPopup(props: SearchFilterPopupProps): import("react
|
|
50
|
+
export declare function SearchFilterPopup(props: SearchFilterPopupProps): import("react").JSX.Element;
|
|
50
51
|
export interface SelectionFilterPopupProps<Value extends Primitive> {
|
|
51
52
|
dictionary: Dictionary<Value> | null;
|
|
52
53
|
hiddenValues: Value[];
|
|
53
54
|
onHiddenValuesChange: (hiddenValues: Value[]) => void;
|
|
54
55
|
onDismiss: () => void;
|
|
55
56
|
}
|
|
56
|
-
export declare function SelectionFilterPopup<Value extends Primitive>(props: SelectionFilterPopupProps<Value>): import("react
|
|
57
|
-
export declare function BodyContainer(props: Props): import("react
|
|
58
|
-
export declare function Line(props: Props): import("react
|
|
57
|
+
export declare function SelectionFilterPopup<Value extends Primitive>(props: SelectionFilterPopupProps<Value>): import("react").JSX.Element;
|
|
58
|
+
export declare function BodyContainer(props: Props): import("react").JSX.Element;
|
|
59
|
+
export declare function Line(props: Props): import("react").JSX.Element;
|
|
59
60
|
interface SelectionCellProps extends Props {
|
|
60
61
|
selected: boolean;
|
|
61
62
|
onToggle: () => void;
|
|
62
63
|
}
|
|
63
|
-
export declare function SelectionCell(props: SelectionCellProps): import("react
|
|
64
|
-
export declare function Cell(props: Props): import("react
|
|
64
|
+
export declare function SelectionCell(props: SelectionCellProps): import("react").JSX.Element;
|
|
65
|
+
export declare function Cell(props: Props): import("react").JSX.Element;
|
|
65
66
|
export interface ClickableProps extends Props {
|
|
66
67
|
target: string;
|
|
67
68
|
}
|
|
68
|
-
export declare function ClickableCell(props: ClickableProps): import("react
|
|
69
|
+
export declare function ClickableCell(props: ClickableProps): import("react").JSX.Element;
|
|
69
70
|
export interface FooterContainerProps {
|
|
70
71
|
totalColums: number;
|
|
71
72
|
pageSelector: ReactNode;
|
|
72
73
|
itemsPerPageSelector: ReactNode;
|
|
73
74
|
}
|
|
74
|
-
export declare function FooterContainer(props: FooterContainerProps): import("react
|
|
75
|
+
export declare function FooterContainer(props: FooterContainerProps): import("react").JSX.Element;
|
|
75
76
|
export interface ItemsPerPageSelectorProps extends Props {
|
|
76
77
|
itemsPerPage: number;
|
|
77
78
|
onItemsPerPageChange: (itemsPerPage: number) => void;
|
|
78
79
|
}
|
|
79
|
-
export declare function ItemsPerPageSelector(props: ItemsPerPageSelectorProps): import("react
|
|
80
|
+
export declare function ItemsPerPageSelector(props: ItemsPerPageSelectorProps): import("react").JSX.Element;
|
|
80
81
|
export interface PageSelectorProps extends Props {
|
|
81
82
|
totalPages: number;
|
|
82
83
|
currentPage: number;
|
|
83
84
|
onCurrentPageChange: (page: number) => void;
|
|
84
85
|
}
|
|
85
|
-
export declare function PageSelector(props: PageSelectorProps): import("react
|
|
86
|
+
export declare function PageSelector(props: PageSelectorProps): import("react").JSX.Element | null;
|
|
86
87
|
export interface PageButtonProps extends Props {
|
|
87
88
|
enabled: boolean;
|
|
88
89
|
active?: boolean;
|
|
89
90
|
onClick: () => void;
|
|
90
91
|
}
|
|
91
|
-
export declare function PageButton(props: PageButtonProps): import("react
|
|
92
|
+
export declare function PageButton(props: PageButtonProps): import("react").JSX.Element;
|
|
92
93
|
export interface ButtonProps extends Props {
|
|
93
94
|
className?: string;
|
|
94
95
|
disabled?: boolean;
|
|
@@ -111,11 +112,11 @@ export interface ButtonProps extends Props {
|
|
|
111
112
|
* background color (when button is non-hovered) by defining a custom `--bg-default-color`, for example:
|
|
112
113
|
* `<DynamicTable.Button className="[--bg-default-color:transparent]">...</DynamicTable.Button>`
|
|
113
114
|
*/
|
|
114
|
-
export declare function Button(props: ButtonProps): import("react
|
|
115
|
+
export declare function Button(props: ButtonProps): import("react").JSX.Element;
|
|
115
116
|
export interface PopupProps extends Props {
|
|
116
117
|
className?: string;
|
|
117
118
|
onDismiss: () => void;
|
|
118
119
|
}
|
|
119
|
-
export declare function Popup(props: PopupProps): import("react
|
|
120
|
-
export declare function Loader(): import("react
|
|
120
|
+
export declare function Popup(props: PopupProps): import("react").JSX.Element;
|
|
121
|
+
export declare function Loader(): import("react").JSX.Element;
|
|
121
122
|
export {};
|
package/lib/DefaultTheme.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import cx from "@vinorcola/utils/classNames";
|
|
3
3
|
import { useEffect, useMemo } from "react";
|
|
4
|
-
import { AscSortIcon, CheckedIcon,
|
|
4
|
+
import { AscSortIcon, CheckedIcon, DescSortIcon, FilterIcon, HideIcon, NextIcon, PreviousIcon, SearchIcon, UncheckedIcon, } from "./Icon.js";
|
|
5
5
|
import { usePopup } from "./UniquePopupProvider.js";
|
|
6
6
|
import DynamicTable from "./index.js";
|
|
7
7
|
import { isSearchable, isSelectable } from "./useFilterState.js";
|
|
@@ -10,28 +10,8 @@ import { isSortable } from "./useSortState.js";
|
|
|
10
10
|
export function TableContainer(props) {
|
|
11
11
|
return _jsx("div", { children: props.children });
|
|
12
12
|
}
|
|
13
|
-
export function
|
|
14
|
-
|
|
15
|
-
const hasHiddenColumns = props.columns.some((column) => isMaskable(column) && !column.displayed);
|
|
16
|
-
const hasFilteredColumns = props.columns.some((column) => (isSearchable(column) && column.searchText !== null) ||
|
|
17
|
-
(isSelectable(column) && column.hiddenValues.length > 0));
|
|
18
|
-
const hasHiddenFilteredColumns = hasHiddenColumns &&
|
|
19
|
-
hasFilteredColumns &&
|
|
20
|
-
props.columns.some((column) => isMaskable(column) &&
|
|
21
|
-
!column.displayed &&
|
|
22
|
-
((isSearchable(column) && column.searchText !== null) ||
|
|
23
|
-
(isSelectable(column) && column.hiddenValues.length > 0)));
|
|
24
|
-
const hasSortedColumns = props.columns.some((column) => isSortable(column) && column.sorted !== null);
|
|
25
|
-
const hasHiddenSortedColumns = hasHiddenColumns &&
|
|
26
|
-
hasSortedColumns &&
|
|
27
|
-
props.columns.some((column) => isMaskable(column) && !column.displayed && isSortable(column) && column.sorted !== null);
|
|
28
|
-
return (_jsxs("div", { className: "relative", children: [_jsxs("ul", { className: "flex justify-end", children: [_jsx("li", { children: _jsx(DynamicTable.Button, { className: cx(hasHiddenFilteredColumns
|
|
29
|
-
? "[--bg-color:var(--color-orange-400)]"
|
|
30
|
-
: hasFilteredColumns && "[--bg-color:var(--color-lime-400)]"), disabled: !hasFilteredColumns, hoverChildren: _jsx(CloseIcon, {}), onClick: props.clearFilterState, children: _jsx(FilterIcon, {}) }) }), _jsx("li", { children: _jsx(DynamicTable.Button, { className: cx(hasHiddenSortedColumns
|
|
31
|
-
? "[--bg-color:var(--color-orange-400)]"
|
|
32
|
-
: hasSortedColumns && "[--bg-color:var(--color-sky-400)]"), disabled: !hasSortedColumns, hoverChildren: _jsx(CloseIcon, {}), onClick: props.clearSortState, children: _jsx(SortIcon, {}) }) }), _jsx("li", { children: _jsx(DynamicTable.Button, { className: cx(hasHiddenColumns
|
|
33
|
-
? "[--bg-color:var(--color-orange-400)]"
|
|
34
|
-
: "[--bg-default-color:transparent]"), onClick: columnListPopup.show, children: _jsx(ColumnsIcon, {}) }) })] }), columnListPopup.display && (_jsx(DynamicTable.ColumnsPopup, { columns: props.columns, onDismiss: columnListPopup.dismiss }))] }));
|
|
13
|
+
export function ControllerContainer(props) {
|
|
14
|
+
return (_jsxs("div", { className: "relative", children: [_jsxs("ul", { className: "flex justify-end", children: [_jsx("li", { children: props.filterButton }), _jsx("li", { children: props.sortButton }), _jsx("li", { children: props.columnButton })] }), props.columnPopup] }));
|
|
35
15
|
}
|
|
36
16
|
export function ColumnsPopup(props) {
|
|
37
17
|
return (_jsx(DynamicTable.Popup, { className: "right-2", onDismiss: props.onDismiss, children: _jsx("main", { children: _jsx("ul", { className: "min-w-40", children: props.columns.map((column) => {
|
package/lib/Icon.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export declare const DEFAULT_CLASS = "inline grow-0 shrink-0 size-[1em]";
|
|
2
|
-
export declare function CheckedIcon(): import("react
|
|
3
|
-
export declare function UncheckedIcon(): import("react
|
|
4
|
-
export declare function CloseIcon(): import("react
|
|
5
|
-
export declare function ColumnsIcon(): import("react
|
|
6
|
-
export declare function FilterIcon(): import("react
|
|
7
|
-
export declare function HideIcon(): import("react
|
|
8
|
-
export declare function NextIcon(): import("react
|
|
9
|
-
export declare function PreviousIcon(): import("react
|
|
10
|
-
export declare function SearchIcon(): import("react
|
|
11
|
-
export declare function SortIcon(): import("react
|
|
12
|
-
export declare function AscSortIcon(): import("react
|
|
13
|
-
export declare function DescSortIcon(): import("react
|
|
2
|
+
export declare function CheckedIcon(): import("react").JSX.Element;
|
|
3
|
+
export declare function UncheckedIcon(): import("react").JSX.Element;
|
|
4
|
+
export declare function CloseIcon(): import("react").JSX.Element;
|
|
5
|
+
export declare function ColumnsIcon(): import("react").JSX.Element;
|
|
6
|
+
export declare function FilterIcon(): import("react").JSX.Element;
|
|
7
|
+
export declare function HideIcon(): import("react").JSX.Element;
|
|
8
|
+
export declare function NextIcon(): import("react").JSX.Element;
|
|
9
|
+
export declare function PreviousIcon(): import("react").JSX.Element;
|
|
10
|
+
export declare function SearchIcon(): import("react").JSX.Element;
|
|
11
|
+
export declare function SortIcon(): import("react").JSX.Element;
|
|
12
|
+
export declare function AscSortIcon(): import("react").JSX.Element;
|
|
13
|
+
export declare function DescSortIcon(): import("react").JSX.Element;
|
|
@@ -18,5 +18,5 @@ export declare function usePopup(): PopupState;
|
|
|
18
18
|
interface Props {
|
|
19
19
|
children?: ReactNode;
|
|
20
20
|
}
|
|
21
|
-
export default function UniquePopupProvider(props: Props): import("react
|
|
21
|
+
export default function UniquePopupProvider(props: Props): import("react").JSX.Element;
|
|
22
22
|
export {};
|
|
@@ -11,21 +11,25 @@ const PopupContext = createContext(createRef());
|
|
|
11
11
|
*/
|
|
12
12
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
13
13
|
export function usePopup() {
|
|
14
|
-
const
|
|
14
|
+
const dismissRef = useContext(PopupContext);
|
|
15
15
|
const [display, setDisplay] = useState(false);
|
|
16
16
|
return {
|
|
17
17
|
display,
|
|
18
18
|
show: useCallback(() => {
|
|
19
|
-
if (
|
|
20
|
-
|
|
19
|
+
if (dismissRef.current !== null) {
|
|
20
|
+
dismissRef.current();
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
dismissRef.current = () => {
|
|
23
23
|
setDisplay(false);
|
|
24
|
-
|
|
24
|
+
dismissRef.current = null;
|
|
25
25
|
};
|
|
26
26
|
setDisplay(true);
|
|
27
|
-
}, [
|
|
28
|
-
dismiss:
|
|
27
|
+
}, [dismissRef]),
|
|
28
|
+
dismiss: useCallback(() => {
|
|
29
|
+
if (dismissRef.current !== null) {
|
|
30
|
+
dismissRef.current();
|
|
31
|
+
}
|
|
32
|
+
}, [dismissRef]),
|
|
29
33
|
};
|
|
30
34
|
}
|
|
31
35
|
export default function UniquePopupProvider(props) {
|
package/lib/ValueList.js
ADDED
package/lib/index.d.ts
CHANGED
|
@@ -26,10 +26,10 @@ interface BaseProps<Item extends BaseItem> {
|
|
|
26
26
|
initialPaginationState?: PaginationState;
|
|
27
27
|
}
|
|
28
28
|
type Props<Item extends BaseItem> = BaseProps<Item> & SelectionOptions<Item>;
|
|
29
|
-
declare function DynamicTable<Item extends BaseItem>(props: Props<Item>): import("react
|
|
29
|
+
declare function DynamicTable<Item extends BaseItem>(props: Props<Item>): import("react").JSX.Element;
|
|
30
30
|
declare namespace DynamicTable {
|
|
31
31
|
var TableContainer: typeof import("./DefaultTheme.js").TableContainer;
|
|
32
|
-
var
|
|
32
|
+
var ControllerContainer: typeof import("./DefaultTheme.js").ControllerContainer;
|
|
33
33
|
var ColumnsPopup: typeof import("./DefaultTheme.js").ColumnsPopup;
|
|
34
34
|
var Table: typeof import("./DefaultTheme.js").Table;
|
|
35
35
|
var HeaderContainer: typeof import("./DefaultTheme.js").HeaderContainer;
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import Controller from "./Controller.js";
|
|
3
|
+
import { BodyContainer, Button, Cell, ClickableCell, ColumnsPopup, ControllerContainer, FooterContainer, Header, HeaderActions, HeaderContainer, HeaderLine, HeaderStatus, ItemsPerPageSelector, Line, Loader, PageButton, PageSelector, Popup, SearchFilterPopup, SelectionCell, SelectionFilterPopup, SelectionHeader, Table, TableContainer, } from "./DefaultTheme.js";
|
|
3
4
|
import UniquePopupProvider from "./UniquePopupProvider.js";
|
|
4
5
|
import useColumns from "./useColumns.js";
|
|
5
6
|
import useFilterState from "./useFilterState.js";
|
|
@@ -17,13 +18,13 @@ export default function DynamicTable(props) {
|
|
|
17
18
|
const { allColumns, columns: displayedColumns, items: displayedItems, } = useMaskableColumns(sortedColumns, sortedItems, props.initialColumnsMaskState);
|
|
18
19
|
const { items: paginatedItems, itemsPerPage, onItemsPerPageChange, totalPages, currentPage, onCurrentPageChange, } = usePagination(displayedItems, props.initialPaginationState);
|
|
19
20
|
const { isInSelectionMode, totalSelectedQuantity, unvisibleSelectedQuantity, isItemSelected, onItemSelectionToggle, } = useSelection(props, paginatedItems);
|
|
20
|
-
return (_jsx(UniquePopupProvider, { children: _jsxs(DynamicTable.TableContainer, { children: [_jsx(
|
|
21
|
+
return (_jsx(UniquePopupProvider, { children: _jsxs(DynamicTable.TableContainer, { children: [_jsx(Controller, { columns: allColumns, clearFilterState: clearFilterState, clearSortState: clearSortState }), _jsxs(DynamicTable.Table, { children: [_jsx(DynamicTable.HeaderContainer, { children: _jsxs(DynamicTable.HeaderLine, { children: [isInSelectionMode && (_jsx(DynamicTable.SelectionHeader, { totalSelected: totalSelectedQuantity, unvisibleSelected: unvisibleSelectedQuantity })), displayedColumns.map((column) => (_jsx(DynamicTable.Header, { column: column, children: column.title }, column.id)))] }) }), _jsx(DynamicTable.BodyContainer, { children: paginatedItems.map((item) => (_jsxs(DynamicTable.Line, { children: [isInSelectionMode &&
|
|
21
22
|
(item.isSelectable ? (_jsx(DynamicTable.SelectionCell, { selected: isItemSelected(item), onToggle: onItemSelectionToggle(item) })) : (_jsx(DynamicTable.Cell, {}))), item.target !== null
|
|
22
23
|
? item.values.map((value) => (_jsx(DynamicTable.ClickableCell, { target: item.target, children: value.loading ? _jsx(DynamicTable.Loader, {}) : value.display }, value.column)))
|
|
23
24
|
: item.values.map((value) => (_jsx(DynamicTable.Cell, { children: value.loading ? _jsx(DynamicTable.Loader, {}) : value.display }, value.column)))] }, item.key))) }), _jsx(DynamicTable.FooterContainer, { totalColums: displayedColumns.length, pageSelector: _jsx(DynamicTable.PageSelector, { totalPages: totalPages, currentPage: currentPage, onCurrentPageChange: onCurrentPageChange }), itemsPerPageSelector: _jsx(DynamicTable.ItemsPerPageSelector, { itemsPerPage: itemsPerPage, onItemsPerPageChange: onItemsPerPageChange }) })] })] }) }));
|
|
24
25
|
}
|
|
25
26
|
DynamicTable.TableContainer = TableContainer;
|
|
26
|
-
DynamicTable.
|
|
27
|
+
DynamicTable.ControllerContainer = ControllerContainer;
|
|
27
28
|
DynamicTable.ColumnsPopup = ColumnsPopup;
|
|
28
29
|
DynamicTable.Table = Table;
|
|
29
30
|
DynamicTable.HeaderContainer = HeaderContainer;
|
package/lib/useColumns.d.ts
CHANGED
|
@@ -9,8 +9,8 @@ export interface LoadingInternalColumn<Item extends BaseItem, Value extends Prim
|
|
|
9
9
|
readonly title: string;
|
|
10
10
|
readonly loadingDictionary: true;
|
|
11
11
|
readonly dictionary: Promise<Dictionary<Value>>;
|
|
12
|
-
readonly resolveValue: ValueResolver<Item, Value>;
|
|
13
|
-
readonly decorateValue?: ValueDecorator<Item, Value>;
|
|
12
|
+
readonly resolveValue: ValueResolver<Item, Value | Value[]>;
|
|
13
|
+
readonly decorateValue?: ValueDecorator<Item, Value | Value[]>;
|
|
14
14
|
readonly decorateNoValue?: () => ReactNode;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
@@ -21,8 +21,8 @@ export interface LoadedInternalColumn<Item extends BaseItem, Value extends Primi
|
|
|
21
21
|
readonly title: string;
|
|
22
22
|
readonly loadingDictionary: false;
|
|
23
23
|
readonly dictionary?: Dictionary<Value>;
|
|
24
|
-
readonly resolveValue: ValueResolver<Item, Value>;
|
|
25
|
-
readonly decorateValue?: ValueDecorator<Item, Value>;
|
|
24
|
+
readonly resolveValue: ValueResolver<Item, Value | Value[]>;
|
|
25
|
+
readonly decorateValue?: ValueDecorator<Item, Value | Value[]>;
|
|
26
26
|
readonly decorateNoValue?: () => ReactNode;
|
|
27
27
|
}
|
|
28
28
|
/**
|
package/lib/useColumns.js
CHANGED
|
@@ -14,6 +14,7 @@ export default function useColumns(definitions) {
|
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
definitionVersion.current++;
|
|
16
16
|
const currentVersion = definitionVersion.current;
|
|
17
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
17
18
|
setColumns(resolveInitialColumnState(definitions));
|
|
18
19
|
definitions.forEach((definition, index) => {
|
|
19
20
|
if (definition.dictionary instanceof Promise) {
|
package/lib/useFilterState.js
CHANGED
|
@@ -47,25 +47,26 @@ export default function useFilterState(columns, items, initialFilterState = {})
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
}), [columns, filterState]),
|
|
50
|
-
items: useMemo(() => items.filter((items) => items.values.every((value) =>
|
|
51
|
-
if (value.loading) {
|
|
52
|
-
// Keep loading values.
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
const columnFilterState = filterState[value.column] ?? null;
|
|
56
|
-
if (columnFilterState === null) {
|
|
57
|
-
// There is no filter on this column.
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
return isSearchedState(columnFilterState)
|
|
61
|
-
? matchSearch(value, columnFilterState)
|
|
62
|
-
: matchSelection(value, columnFilterState);
|
|
63
|
-
})), [items, filterState]),
|
|
50
|
+
items: useMemo(() => items.filter((items) => items.values.every((value) => isValueMatchingColumnFilter(filterState, value))), [items, filterState]),
|
|
64
51
|
clearFilterState: useCallback(() => {
|
|
65
52
|
setFilterState({});
|
|
66
53
|
}, []),
|
|
67
54
|
};
|
|
68
55
|
}
|
|
56
|
+
function isValueMatchingColumnFilter(filterState, value) {
|
|
57
|
+
if (value.loading) {
|
|
58
|
+
// Keep loading values.
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
const columnFilterState = filterState[value.column] ?? null;
|
|
62
|
+
if (columnFilterState === null) {
|
|
63
|
+
// There is no filter on this column.
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return isSearchedState(columnFilterState)
|
|
67
|
+
? matchSearch(value, columnFilterState)
|
|
68
|
+
: matchSelection(value, columnFilterState);
|
|
69
|
+
}
|
|
69
70
|
function isSearchedState(state) {
|
|
70
71
|
return state.searchText !== undefined;
|
|
71
72
|
}
|
|
@@ -81,5 +82,8 @@ function matchSelection(value, filterState) {
|
|
|
81
82
|
// Filter out empty values.
|
|
82
83
|
return false;
|
|
83
84
|
}
|
|
85
|
+
if (Array.isArray(value.raw)) {
|
|
86
|
+
return value.raw.some((rawValue) => !filterState.hiddenValues.includes(rawValue));
|
|
87
|
+
}
|
|
84
88
|
return !filterState.hiddenValues.includes(value.raw);
|
|
85
89
|
}
|
package/lib/useItems.d.ts
CHANGED
|
@@ -5,18 +5,18 @@ import type { SortableValue } from "./useSortState.js";
|
|
|
5
5
|
/**
|
|
6
6
|
* A value that is loading (waiting for an async column dictionary to be available).
|
|
7
7
|
*/
|
|
8
|
-
export interface LoadingInternalValue {
|
|
8
|
+
export interface LoadingInternalValue<Value extends Primitive | Primitive[] = Primitive | Primitive[]> {
|
|
9
9
|
readonly column: string;
|
|
10
10
|
readonly loading: true;
|
|
11
|
-
readonly raw:
|
|
11
|
+
readonly raw: Value | null;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* A loaded value, ready for filter, sort and display.
|
|
15
15
|
*/
|
|
16
|
-
export interface LoadedInternalValue {
|
|
16
|
+
export interface LoadedInternalValue<Value extends Primitive | Primitive[] = Primitive | Primitive[]> {
|
|
17
17
|
readonly column: string;
|
|
18
18
|
readonly loading: false;
|
|
19
|
-
readonly raw:
|
|
19
|
+
readonly raw: Value | null;
|
|
20
20
|
readonly search: string | null;
|
|
21
21
|
readonly sort: SortableValue;
|
|
22
22
|
readonly display: ReactNode;
|
|
@@ -24,7 +24,7 @@ export interface LoadedInternalValue {
|
|
|
24
24
|
/**
|
|
25
25
|
* A value, either in loading state ou in loaded state.
|
|
26
26
|
*/
|
|
27
|
-
export type InternalValue = LoadingInternalValue | LoadedInternalValue
|
|
27
|
+
export type InternalValue<Value extends Primitive | Primitive[] = Primitive | Primitive[]> = LoadingInternalValue<Value> | LoadedInternalValue<Value>;
|
|
28
28
|
/**
|
|
29
29
|
* An internal item.
|
|
30
30
|
*/
|
package/lib/useItems.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { displayInteger } from "@vinorcola/utils/number";
|
|
3
3
|
import { extractSearchableText } from "@vinorcola/utils/text";
|
|
4
|
-
import { useMemo } from "react";
|
|
4
|
+
import { Fragment, useMemo } from "react";
|
|
5
|
+
import ValueList from "./ValueList.js";
|
|
5
6
|
export default function useItems(items, itemTarget, columns, canSelectItem) {
|
|
6
7
|
return useMemo(() => items.map((item) => ({
|
|
7
8
|
key: item.id,
|
|
@@ -12,35 +13,46 @@ export default function useItems(items, itemTarget, columns, canSelectItem) {
|
|
|
12
13
|
})), [items, itemTarget, columns, canSelectItem]);
|
|
13
14
|
}
|
|
14
15
|
function resolveInternalValue(item, column) {
|
|
15
|
-
const
|
|
16
|
+
const value = column.resolveValue(item);
|
|
16
17
|
if (column.loadingDictionary) {
|
|
17
18
|
return {
|
|
18
19
|
column: column.id,
|
|
19
20
|
loading: true,
|
|
20
|
-
raw,
|
|
21
|
+
raw: value,
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
|
-
let display
|
|
24
|
-
if (
|
|
24
|
+
let display;
|
|
25
|
+
if (value === null || value === undefined || value === "") {
|
|
25
26
|
if (column.decorateNoValue !== undefined) {
|
|
26
27
|
display = column.decorateNoValue();
|
|
27
28
|
}
|
|
29
|
+
else {
|
|
30
|
+
display = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (column.decorateValue !== undefined) {
|
|
34
|
+
display = column.decorateValue(value, display, item);
|
|
28
35
|
}
|
|
29
36
|
else {
|
|
30
|
-
|
|
31
|
-
display = column.decorateValue(raw, display, item);
|
|
32
|
-
}
|
|
37
|
+
display = resolveDisplayableValue(value, column.dictionary);
|
|
33
38
|
}
|
|
34
39
|
return {
|
|
35
40
|
column: column.id,
|
|
36
41
|
loading: false,
|
|
37
|
-
raw,
|
|
38
|
-
search: resolveSearchableValue(
|
|
39
|
-
sort: resolveSortableValue(
|
|
42
|
+
raw: value,
|
|
43
|
+
search: resolveSearchableValue(value, column.dictionary),
|
|
44
|
+
sort: resolveSortableValue(value, column.dictionary),
|
|
40
45
|
display,
|
|
41
46
|
};
|
|
42
47
|
}
|
|
48
|
+
const ARBITRARY_SEARCH_SEPARATOR = "#|!$#"; // To avoid searching text across several values, we separate each value by
|
|
49
|
+
// // this arbitrary (unlikely searched by users) text.
|
|
43
50
|
function resolveSearchableValue(value, dictionary) {
|
|
51
|
+
return Array.isArray(value)
|
|
52
|
+
? value.map((value) => resolveSearchableSingleValue(value, dictionary)).join(ARBITRARY_SEARCH_SEPARATOR)
|
|
53
|
+
: resolveSearchableSingleValue(value, dictionary);
|
|
54
|
+
}
|
|
55
|
+
function resolveSearchableSingleValue(value, dictionary) {
|
|
44
56
|
if (dictionary !== undefined) {
|
|
45
57
|
// Columns with dictionary are selectable, not searchable.
|
|
46
58
|
return null;
|
|
@@ -60,6 +72,13 @@ function resolveSearchableValue(value, dictionary) {
|
|
|
60
72
|
return extractSearchableText(value);
|
|
61
73
|
}
|
|
62
74
|
function resolveSortableValue(value, dictionary) {
|
|
75
|
+
return Array.isArray(value)
|
|
76
|
+
? value.length === 0
|
|
77
|
+
? null
|
|
78
|
+
: resolveSortableSingleValue(value[0], dictionary) // Sort using first value (until we find a better way!)
|
|
79
|
+
: resolveSortableSingleValue(value, dictionary);
|
|
80
|
+
}
|
|
81
|
+
function resolveSortableSingleValue(value, dictionary) {
|
|
63
82
|
if (value === null) {
|
|
64
83
|
return null;
|
|
65
84
|
}
|
|
@@ -78,6 +97,9 @@ function resolveSortableValue(value, dictionary) {
|
|
|
78
97
|
return value.toLocaleLowerCase();
|
|
79
98
|
}
|
|
80
99
|
function resolveDisplayableValue(value, dictionary) {
|
|
100
|
+
return Array.isArray(value) ? (_jsx(ValueList, { items: value.map((value, index) => resolveDisplayableSingleValue(value, dictionary, index)) })) : (resolveDisplayableSingleValue(value, dictionary));
|
|
101
|
+
}
|
|
102
|
+
function resolveDisplayableSingleValue(value, dictionary, nodeKey) {
|
|
81
103
|
if (value === null) {
|
|
82
104
|
return null;
|
|
83
105
|
}
|
|
@@ -86,7 +108,7 @@ function resolveDisplayableValue(value, dictionary) {
|
|
|
86
108
|
if (dictionaryEntry === undefined) {
|
|
87
109
|
return dictionary.unknownMessage;
|
|
88
110
|
}
|
|
89
|
-
return dictionaryEntry.prepend !== undefined ? (_jsxs(
|
|
111
|
+
return dictionaryEntry.prepend !== undefined ? (_jsxs(Fragment, { children: [dictionaryEntry.prepend, dictionaryEntry.title] }, nodeKey)) : (dictionaryEntry.title);
|
|
90
112
|
}
|
|
91
113
|
if (typeof value === "boolean") {
|
|
92
114
|
return value ? "true" : "false";
|
|
@@ -26,7 +26,7 @@ export default function useMaskableColumns<Item extends BaseItem>(columns: Inter
|
|
|
26
26
|
allColumns: InternalMaskableColumn<Item, Primitive>[];
|
|
27
27
|
columns: InternalMaskableColumn<Item, Primitive>[];
|
|
28
28
|
items: {
|
|
29
|
-
values: import("./useItems.js").InternalValue[];
|
|
29
|
+
values: import("./useItems.js").InternalValue<Primitive | Primitive[]>[];
|
|
30
30
|
key: import("react").Key;
|
|
31
31
|
item: Item;
|
|
32
32
|
isSelectable: boolean;
|