@vertebr/react 0.18.0 → 0.19.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/dist/components/admin/AdminContent.d.ts +5 -1
- package/dist/components/admin/AdminContent.d.ts.map +1 -1
- package/dist/components/admin/AdminContent.js +75 -50
- package/dist/components/admin/AuditBoard.d.ts +6 -2
- package/dist/components/admin/AuditBoard.d.ts.map +1 -1
- package/dist/components/admin/AuditBoard.js +145 -3
- package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
- package/dist/components/admin/VertebrAdmin.js +14 -3
- package/dist/components/config/EnumInput.d.ts.map +1 -1
- package/dist/components/config/EnumInput.js +9 -2
- package/dist/components/config/form/ConfigForm.d.ts.map +1 -1
- package/dist/components/config/form/ConfigForm.js +14 -2
- package/dist/components/emails/EmailsActivityPanel.d.ts.map +1 -1
- package/dist/components/emails/EmailsActivityPanel.js +80 -2
- package/dist/components/metrics/MetricsBoard.d.ts.map +1 -1
- package/dist/components/metrics/MetricsBoard.js +7 -1
- package/dist/components/resources/ColumnVisibilityMenu.d.ts +4 -0
- package/dist/components/resources/ColumnVisibilityMenu.d.ts.map +1 -1
- package/dist/components/resources/ColumnVisibilityMenu.js +14 -7
- package/dist/components/resources/ResourceTable.d.ts +7 -1
- package/dist/components/resources/ResourceTable.d.ts.map +1 -1
- package/dist/components/resources/ResourceTable.js +84 -64
- package/dist/components/resources/SortableHeader.d.ts +9 -0
- package/dist/components/resources/SortableHeader.d.ts.map +1 -0
- package/dist/components/resources/SortableHeader.js +9 -0
- package/dist/components/table/TableFilters.d.ts +20 -0
- package/dist/components/table/TableFilters.d.ts.map +1 -0
- package/dist/components/table/TableFilters.js +55 -0
- package/dist/components/table/TableSearch.d.ts +11 -0
- package/dist/components/table/TableSearch.d.ts.map +1 -0
- package/dist/components/table/TableSearch.js +23 -0
- package/dist/components/table/TableSkeleton.d.ts +16 -0
- package/dist/components/table/TableSkeleton.d.ts.map +1 -0
- package/dist/components/table/TableSkeleton.js +29 -0
- package/dist/components/table/TableWrap.d.ts +23 -0
- package/dist/components/table/TableWrap.d.ts.map +1 -0
- package/dist/components/table/TableWrap.js +35 -0
- package/dist/components/table/filterModel.d.ts +35 -0
- package/dist/components/table/filterModel.d.ts.map +1 -0
- package/dist/components/table/filterModel.js +150 -0
- package/dist/components/table/index.d.ts +13 -0
- package/dist/components/table/index.d.ts.map +1 -0
- package/dist/components/table/index.js +6 -0
- package/dist/components/table/useTableOverflow.d.ts +17 -0
- package/dist/components/table/useTableOverflow.d.ts.map +1 -0
- package/dist/components/table/useTableOverflow.js +54 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/sort.d.ts +10 -0
- package/dist/lib/sort.d.ts.map +1 -0
- package/dist/lib/sort.js +59 -0
- package/dist/styles/audit.css +11 -1
- package/dist/styles/columns.css +1 -84
- package/dist/styles/metrics.css +27 -4
- package/dist/styles/panel.css +143 -0
- package/dist/styles/select.css +16 -0
- package/dist/styles/sync.css +4 -1
- package/dist/styles/table.css +115 -2
- package/package.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
3
|
+
import { clearTableFilters, hasActiveFilters, setFilterValue, } from "./filterModel";
|
|
4
|
+
import { ColumnVisibilityMenu, } from "../resources/ColumnVisibilityMenu";
|
|
5
|
+
/**
|
|
6
|
+
* Host-declared filter chips (+ optional Columns chip).
|
|
7
|
+
* Selected values stay checked in each dropdown.
|
|
8
|
+
*/
|
|
9
|
+
export function TableFilters({ columns, selected, onChange, columnVisibility, }) {
|
|
10
|
+
const hasFieldFilters = columns.length > 0;
|
|
11
|
+
const hasColumns = Boolean(columnVisibility?.columns.length);
|
|
12
|
+
if (!hasFieldFilters && !hasColumns)
|
|
13
|
+
return null;
|
|
14
|
+
const active = hasActiveFilters(selected);
|
|
15
|
+
return (_jsxs("div", { className: "vertebr-table-filters", role: "group", "aria-label": "Filters", children: [columns.map((column) => (_jsx(TableFilterControl, { column: column, values: selected[column.id] ?? [], onToggle: (value) => onChange(setFilterValue(selected, column.id, value, column.multiselect)) }, column.id))), columnVisibility ? (_jsx(ColumnVisibilityMenu, { columns: columnVisibility.columns, visibility: columnVisibility.visibility, onChange: columnVisibility.onChange })) : null, active ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-table-filter-clear", onClick: () => onChange(clearTableFilters()), children: "Clear filters" })) : null] }));
|
|
16
|
+
}
|
|
17
|
+
function TableFilterControl({ column, values, onToggle, }) {
|
|
18
|
+
const listId = useId();
|
|
19
|
+
const rootRef = useRef(null);
|
|
20
|
+
const [open, setOpen] = useState(false);
|
|
21
|
+
const selected = new Set(values);
|
|
22
|
+
const count = values.length;
|
|
23
|
+
const filtering = count > 0;
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!open)
|
|
26
|
+
return;
|
|
27
|
+
const onPointer = (event) => {
|
|
28
|
+
if (!rootRef.current?.contains(event.target))
|
|
29
|
+
setOpen(false);
|
|
30
|
+
};
|
|
31
|
+
const onKey = (event) => {
|
|
32
|
+
if (event.key === "Escape")
|
|
33
|
+
setOpen(false);
|
|
34
|
+
};
|
|
35
|
+
window.addEventListener("mousedown", onPointer);
|
|
36
|
+
window.addEventListener("keydown", onKey);
|
|
37
|
+
return () => {
|
|
38
|
+
window.removeEventListener("mousedown", onPointer);
|
|
39
|
+
window.removeEventListener("keydown", onKey);
|
|
40
|
+
};
|
|
41
|
+
}, [open]);
|
|
42
|
+
return (_jsxs("div", { className: "vertebr-table-filter-control", ref: rootRef, "data-open": open ? "true" : undefined, "data-active": filtering ? "true" : undefined, "data-multiselect": column.multiselect ? "true" : "false", children: [_jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-table-filter-trigger", "aria-expanded": open, "aria-controls": listId, "aria-label": filtering
|
|
43
|
+
? `${column.label}, ${count} selected`
|
|
44
|
+
: column.label, onClick: () => setOpen((next) => !next), children: [column.label, filtering ? (_jsx("span", { className: "vertebr-table-filter-count", children: count })) : null] }), open ? (_jsxs("div", { className: "vertebr-table-filter-menu", id: listId, role: "listbox", "aria-multiselectable": column.multiselect, children: [_jsxs("div", { className: "vertebr-table-filter-menu-head", children: [_jsx("span", { children: column.label }), filtering ? (_jsx("button", { type: "button", className: "vertebr-link-btn", onClick: () => {
|
|
45
|
+
for (const value of values)
|
|
46
|
+
onToggle(value);
|
|
47
|
+
}, children: "Clear" })) : null] }), _jsx("ul", { className: "vertebr-table-filter-list", children: column.options.map((option) => {
|
|
48
|
+
const on = selected.has(option.value);
|
|
49
|
+
return (_jsx("li", { children: _jsxs("label", { className: "vertebr-table-filter-item", children: [_jsx("input", { type: column.multiselect ? "checkbox" : "radio", name: `filter-${column.id}`, checked: on, onChange: () => {
|
|
50
|
+
onToggle(option.value);
|
|
51
|
+
if (!column.multiselect)
|
|
52
|
+
setOpen(false);
|
|
53
|
+
} }), _jsx("span", { children: option.label })] }) }, option.value));
|
|
54
|
+
}) })] })) : null] }));
|
|
55
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type TableSearchProps = {
|
|
2
|
+
value: string;
|
|
3
|
+
onChange: (value: string) => void;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
"aria-label"?: string;
|
|
6
|
+
};
|
|
7
|
+
/** Default search field for Vertebr table toolbars / panel heads. */
|
|
8
|
+
export declare function TableSearch({ value, onChange, placeholder, "aria-label": ariaLabel, }: TableSearchProps): import("react").JSX.Element;
|
|
9
|
+
/** Case-insensitive substring match over a value or nested object/array. */
|
|
10
|
+
export declare function matchesTableQuery(value: unknown, query: string): boolean;
|
|
11
|
+
//# sourceMappingURL=TableSearch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableSearch.d.ts","sourceRoot":"","sources":["../../../src/components/table/TableSearch.tsx"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,WAAuB,EACvB,YAAY,EAAE,SAA0B,GACzC,EAAE,gBAAgB,+BAWlB;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAgBxE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/** Default search field for Vertebr table toolbars / panel heads. */
|
|
3
|
+
export function TableSearch({ value, onChange, placeholder = "Search…", "aria-label": ariaLabel = "Search table", }) {
|
|
4
|
+
return (_jsx("input", { className: "vertebr-table-search", type: "search", placeholder: placeholder, value: value, onChange: (event) => onChange(event.target.value), "aria-label": ariaLabel }));
|
|
5
|
+
}
|
|
6
|
+
/** Case-insensitive substring match over a value or nested object/array. */
|
|
7
|
+
export function matchesTableQuery(value, query) {
|
|
8
|
+
const q = query.trim().toLowerCase();
|
|
9
|
+
if (!q)
|
|
10
|
+
return true;
|
|
11
|
+
if (value == null)
|
|
12
|
+
return false;
|
|
13
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
14
|
+
return String(value).toLowerCase().includes(q);
|
|
15
|
+
}
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return value.some((item) => matchesTableQuery(item, q));
|
|
18
|
+
}
|
|
19
|
+
if (typeof value === "object") {
|
|
20
|
+
return Object.values(value).some((item) => matchesTableQuery(item, q));
|
|
21
|
+
}
|
|
22
|
+
return String(value).toLowerCase().includes(q);
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type TableSkeletonColumn = {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
};
|
|
5
|
+
export type TableSkeletonProps = {
|
|
6
|
+
columns: TableSkeletonColumn[];
|
|
7
|
+
/** Number of placeholder rows. @default 6 */
|
|
8
|
+
rows?: number;
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Lightweight table-shaped shimmer for initial load.
|
|
13
|
+
* Prefer a small fixed row count — enough to hold layout, not a fake dataset.
|
|
14
|
+
*/
|
|
15
|
+
export declare function TableSkeleton({ columns, rows, className, }: TableSkeletonProps): import("react").JSX.Element | null;
|
|
16
|
+
//# sourceMappingURL=TableSkeleton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableSkeleton.d.ts","sourceRoot":"","sources":["../../../src/components/table/TableSkeleton.tsx"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,IAAQ,EACR,SAAS,GACV,EAAE,kBAAkB,sCAwCpB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Lightweight table-shaped shimmer for initial load.
|
|
4
|
+
* Prefer a small fixed row count — enough to hold layout, not a fake dataset.
|
|
5
|
+
*/
|
|
6
|
+
export function TableSkeleton({ columns, rows = 6, className, }) {
|
|
7
|
+
if (columns.length === 0)
|
|
8
|
+
return null;
|
|
9
|
+
const wrapClass = className
|
|
10
|
+
? `vertebr-table-wrap vertebr-table-skeleton ${className}`
|
|
11
|
+
: "vertebr-table-wrap vertebr-table-skeleton";
|
|
12
|
+
return (_jsxs("div", { className: wrapClass, "aria-busy": "true", "aria-live": "polite", children: [_jsxs("table", { className: "vertebr-table", children: [_jsx("thead", { children: _jsx("tr", { children: columns.map((column, colIndex) => (_jsx("th", { "aria-label": column.label, children: _jsx("span", { className: "vertebr-table-skeleton-cell vertebr-table-skeleton-header", "data-width": headerWidth(colIndex) }) }, column.id))) }) }), _jsx("tbody", { children: Array.from({ length: rows }, (_, rowIndex) => (_jsx("tr", { children: columns.map((column, colIndex) => (_jsx("td", { children: _jsx("span", { className: "vertebr-table-skeleton-cell", "data-width": skeletonWidth(rowIndex, colIndex) }) }, column.id))) }, rowIndex))) })] }), _jsx("span", { className: "vertebr-sr-only", children: "Loading table\u2026" })] }));
|
|
13
|
+
}
|
|
14
|
+
function skeletonWidth(row, col) {
|
|
15
|
+
const pattern = (row + col) % 3;
|
|
16
|
+
if (pattern === 0)
|
|
17
|
+
return "sm";
|
|
18
|
+
if (pattern === 1)
|
|
19
|
+
return "md";
|
|
20
|
+
return "lg";
|
|
21
|
+
}
|
|
22
|
+
function headerWidth(col) {
|
|
23
|
+
const pattern = col % 3;
|
|
24
|
+
if (pattern === 0)
|
|
25
|
+
return "sm";
|
|
26
|
+
if (pattern === 1)
|
|
27
|
+
return "md";
|
|
28
|
+
return "lg";
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { TableOverflow } from "./useTableOverflow";
|
|
3
|
+
export type TableWrapProps = {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
/** From `useTableOverflow` — enables scroll edges + controls. */
|
|
7
|
+
overflow: Pick<TableOverflow, "wrapRef" | "overflow" | "canLeft" | "canRight">;
|
|
8
|
+
/**
|
|
9
|
+
* When this value changes (e.g. filter/search state), play a light
|
|
10
|
+
* row transition. Pass a stable serialized key.
|
|
11
|
+
*/
|
|
12
|
+
transitionKey?: string | number;
|
|
13
|
+
};
|
|
14
|
+
/** Default scroll container for Vertebr tables (`overflow-x` + `overflow-y`). */
|
|
15
|
+
export declare function TableWrap({ children, className, overflow, transitionKey, }: TableWrapProps): import("react").JSX.Element;
|
|
16
|
+
export type TableScrollControlsProps = {
|
|
17
|
+
canLeft: boolean;
|
|
18
|
+
canRight: boolean;
|
|
19
|
+
onScroll: (direction: "left" | "right") => void;
|
|
20
|
+
};
|
|
21
|
+
/** Left/right column scroll buttons — place in toolbars / panel heads. */
|
|
22
|
+
export declare function TableScrollControls({ canLeft, canRight, onScroll, }: TableScrollControlsProps): import("react").JSX.Element;
|
|
23
|
+
//# sourceMappingURL=TableWrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableWrap.d.ts","sourceRoot":"","sources":["../../../src/components/table/TableWrap.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,QAAQ,EAAE,IAAI,CACZ,aAAa,EACb,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAChD,CAAC;IACF;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACjC,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,aAAa,GACd,EAAE,cAAc,+BAkChB;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF,0EAA0E;AAC1E,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,QAAQ,EACR,QAAQ,GACT,EAAE,wBAAwB,+BA2B1B"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState, } from "react";
|
|
3
|
+
/** Default scroll container for Vertebr tables (`overflow-x` + `overflow-y`). */
|
|
4
|
+
export function TableWrap({ children, className, overflow, transitionKey, }) {
|
|
5
|
+
const [transitioning, setTransitioning] = useState(false);
|
|
6
|
+
const prevKey = useRef(transitionKey);
|
|
7
|
+
const skipFirst = useRef(true);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (skipFirst.current) {
|
|
10
|
+
skipFirst.current = false;
|
|
11
|
+
prevKey.current = transitionKey;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (prevKey.current === transitionKey)
|
|
15
|
+
return;
|
|
16
|
+
prevKey.current = transitionKey;
|
|
17
|
+
setTransitioning(true);
|
|
18
|
+
const handle = window.setTimeout(() => setTransitioning(false), 220);
|
|
19
|
+
return () => window.clearTimeout(handle);
|
|
20
|
+
}, [transitionKey]);
|
|
21
|
+
const classes = className
|
|
22
|
+
? `vertebr-table-wrap ${className}`
|
|
23
|
+
: "vertebr-table-wrap";
|
|
24
|
+
return (_jsx("div", { ref: overflow.wrapRef, className: classes, "data-overflow": overflow.overflow ? "true" : undefined, "data-overflow-left": overflow.canLeft ? "true" : undefined, "data-overflow-right": overflow.canRight ? "true" : undefined, "data-transition": transitioning ? "true" : undefined, children: children }));
|
|
25
|
+
}
|
|
26
|
+
/** Left/right column scroll buttons — place in toolbars / panel heads. */
|
|
27
|
+
export function TableScrollControls({ canLeft, canRight, onScroll, }) {
|
|
28
|
+
return (_jsxs("div", { className: "vertebr-table-scroll-controls", role: "group", "aria-label": "Scroll columns", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !canLeft, onClick: () => onScroll("left"), "aria-label": "Scroll columns left", children: _jsx(ChevronLeftIcon, {}) }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !canRight, onClick: () => onScroll("right"), "aria-label": "Scroll columns right", children: _jsx(ChevronRightIcon, {}) })] }));
|
|
29
|
+
}
|
|
30
|
+
function ChevronLeftIcon() {
|
|
31
|
+
return (_jsx("svg", { className: "vertebr-btn-glyph", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m15 18-6-6 6-6" }) }));
|
|
32
|
+
}
|
|
33
|
+
function ChevronRightIcon() {
|
|
34
|
+
return (_jsx("svg", { className: "vertebr-btn-glyph", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m9 18 6-6-6-6" }) }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FieldFilterConfig, FieldSchema } from "@vertebr/client";
|
|
2
|
+
export type TableFilterOption = {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
};
|
|
6
|
+
export type TableFilterColumn = {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
/** @default true */
|
|
10
|
+
multiselect: boolean;
|
|
11
|
+
options: TableFilterOption[];
|
|
12
|
+
};
|
|
13
|
+
/** Selected filter values keyed by column id. Empty / missing = no filter. */
|
|
14
|
+
export type TableFilterState = Record<string, string[]>;
|
|
15
|
+
export declare function filterValueKey(value: unknown): string | null;
|
|
16
|
+
export declare function filterValueLabel(value: string, optionLabels?: Record<string, string>): string;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a host-declared field filter into a table filter column.
|
|
19
|
+
* Returns null when the host did not opt in or options cannot be resolved.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveFieldFilter(id: string, label: string, field: FieldSchema): TableFilterColumn | null;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a host-declared audit column filter.
|
|
24
|
+
* Audit has no field.options — hosts must pass `filter.options`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveAuditFilter(id: string, label: string, filter: boolean | FieldFilterConfig | undefined): TableFilterColumn | null;
|
|
27
|
+
/** Row matches when every active column filter includes at least one of its values. */
|
|
28
|
+
export declare function matchesTableFilters<T>(row: T, selected: TableFilterState, getters: Record<string, (row: T) => unknown>): boolean;
|
|
29
|
+
export declare function setFilterValue(selected: TableFilterState, columnId: string, value: string, multiselect: boolean): TableFilterState;
|
|
30
|
+
export declare function removeFilterValue(selected: TableFilterState, columnId: string, value: string): TableFilterState;
|
|
31
|
+
export declare function clearTableFilters(): TableFilterState;
|
|
32
|
+
export declare function hasActiveFilters(selected: TableFilterState): boolean;
|
|
33
|
+
/** @deprecated Use setFilterValue */
|
|
34
|
+
export declare function toggleFilterValue(selected: TableFilterState, columnId: string, value: string): TableFilterState;
|
|
35
|
+
//# sourceMappingURL=filterModel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filterModel.d.ts","sourceRoot":"","sources":["../../../src/components/table/filterModel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEtE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAExD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAO5D;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,MAAM,CAKR;AAUD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,WAAW,GACjB,iBAAiB,GAAG,IAAI,CA6B1B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,GAAG,iBAAiB,GAAG,SAAS,GAC9C,iBAAiB,GAAG,IAAI,CAc1B;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,GAAG,EAAE,CAAC,EACN,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,GAC3C,OAAO,CAmBT;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,OAAO,GACnB,gBAAgB,CAmBlB;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,gBAAgB,CAOlB;AAED,wBAAgB,iBAAiB,IAAI,gBAAgB,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAEpE;AAED,qCAAqC;AACrC,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,gBAAgB,CAElB"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export function filterValueKey(value) {
|
|
2
|
+
if (value == null || value === "")
|
|
3
|
+
return null;
|
|
4
|
+
if (typeof value === "boolean")
|
|
5
|
+
return value ? "true" : "false";
|
|
6
|
+
if (typeof value === "number")
|
|
7
|
+
return String(value);
|
|
8
|
+
if (typeof value === "string")
|
|
9
|
+
return value;
|
|
10
|
+
if (Array.isArray(value))
|
|
11
|
+
return null;
|
|
12
|
+
return String(value);
|
|
13
|
+
}
|
|
14
|
+
export function filterValueLabel(value, optionLabels) {
|
|
15
|
+
if (optionLabels?.[value])
|
|
16
|
+
return optionLabels[value];
|
|
17
|
+
if (value === "true")
|
|
18
|
+
return "Yes";
|
|
19
|
+
if (value === "false")
|
|
20
|
+
return "No";
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function normalizeFilterConfig(filter) {
|
|
24
|
+
if (filter == null || filter === false)
|
|
25
|
+
return null;
|
|
26
|
+
if (filter === true)
|
|
27
|
+
return {};
|
|
28
|
+
return filter;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a host-declared field filter into a table filter column.
|
|
32
|
+
* Returns null when the host did not opt in or options cannot be resolved.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveFieldFilter(id, label, field) {
|
|
35
|
+
const config = normalizeFilterConfig(field.filter);
|
|
36
|
+
if (!config)
|
|
37
|
+
return null;
|
|
38
|
+
const optionLabels = {
|
|
39
|
+
...(field.optionLabels ?? {}),
|
|
40
|
+
...(config.optionLabels ?? {}),
|
|
41
|
+
};
|
|
42
|
+
let options = config.options?.length
|
|
43
|
+
? config.options
|
|
44
|
+
: field.options?.length
|
|
45
|
+
? field.options
|
|
46
|
+
: field.type === "boolean"
|
|
47
|
+
? ["true", "false"]
|
|
48
|
+
: [];
|
|
49
|
+
options = [...new Set(options.map(String).filter(Boolean))];
|
|
50
|
+
if (options.length === 0)
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
id,
|
|
54
|
+
label,
|
|
55
|
+
multiselect: config.multiselect !== false,
|
|
56
|
+
options: options.map((value) => ({
|
|
57
|
+
value,
|
|
58
|
+
label: filterValueLabel(value, optionLabels),
|
|
59
|
+
})),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a host-declared audit column filter.
|
|
64
|
+
* Audit has no field.options — hosts must pass `filter.options`.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveAuditFilter(id, label, filter) {
|
|
67
|
+
const config = normalizeFilterConfig(filter);
|
|
68
|
+
if (!config)
|
|
69
|
+
return null;
|
|
70
|
+
const options = [...new Set((config.options ?? []).map(String).filter(Boolean))];
|
|
71
|
+
if (options.length === 0)
|
|
72
|
+
return null;
|
|
73
|
+
return {
|
|
74
|
+
id,
|
|
75
|
+
label,
|
|
76
|
+
multiselect: config.multiselect !== false,
|
|
77
|
+
options: options.map((value) => ({
|
|
78
|
+
value,
|
|
79
|
+
label: filterValueLabel(value, config.optionLabels),
|
|
80
|
+
})),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Row matches when every active column filter includes at least one of its values. */
|
|
84
|
+
export function matchesTableFilters(row, selected, getters) {
|
|
85
|
+
for (const [columnId, values] of Object.entries(selected)) {
|
|
86
|
+
if (!values?.length)
|
|
87
|
+
continue;
|
|
88
|
+
const getValue = getters[columnId];
|
|
89
|
+
if (!getValue)
|
|
90
|
+
continue;
|
|
91
|
+
const raw = getValue(row);
|
|
92
|
+
const keys = new Set();
|
|
93
|
+
if (Array.isArray(raw)) {
|
|
94
|
+
for (const item of raw) {
|
|
95
|
+
const key = filterValueKey(item);
|
|
96
|
+
if (key)
|
|
97
|
+
keys.add(key);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const key = filterValueKey(raw);
|
|
102
|
+
if (key)
|
|
103
|
+
keys.add(key);
|
|
104
|
+
}
|
|
105
|
+
if (![...values].some((value) => keys.has(value)))
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
export function setFilterValue(selected, columnId, value, multiselect) {
|
|
111
|
+
if (!multiselect) {
|
|
112
|
+
const current = selected[columnId] ?? [];
|
|
113
|
+
if (current.length === 1 && current[0] === value) {
|
|
114
|
+
const next = { ...selected };
|
|
115
|
+
delete next[columnId];
|
|
116
|
+
return next;
|
|
117
|
+
}
|
|
118
|
+
return { ...selected, [columnId]: [value] };
|
|
119
|
+
}
|
|
120
|
+
const current = selected[columnId] ?? [];
|
|
121
|
+
const nextValues = current.includes(value)
|
|
122
|
+
? current.filter((item) => item !== value)
|
|
123
|
+
: [...current, value];
|
|
124
|
+
const next = { ...selected };
|
|
125
|
+
if (nextValues.length === 0)
|
|
126
|
+
delete next[columnId];
|
|
127
|
+
else
|
|
128
|
+
next[columnId] = nextValues;
|
|
129
|
+
return next;
|
|
130
|
+
}
|
|
131
|
+
export function removeFilterValue(selected, columnId, value) {
|
|
132
|
+
const current = selected[columnId] ?? [];
|
|
133
|
+
const nextValues = current.filter((item) => item !== value);
|
|
134
|
+
const next = { ...selected };
|
|
135
|
+
if (nextValues.length === 0)
|
|
136
|
+
delete next[columnId];
|
|
137
|
+
else
|
|
138
|
+
next[columnId] = nextValues;
|
|
139
|
+
return next;
|
|
140
|
+
}
|
|
141
|
+
export function clearTableFilters() {
|
|
142
|
+
return {};
|
|
143
|
+
}
|
|
144
|
+
export function hasActiveFilters(selected) {
|
|
145
|
+
return Object.values(selected).some((values) => values.length > 0);
|
|
146
|
+
}
|
|
147
|
+
/** @deprecated Use setFilterValue */
|
|
148
|
+
export function toggleFilterValue(selected, columnId, value) {
|
|
149
|
+
return setFilterValue(selected, columnId, value, true);
|
|
150
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { TableSkeleton } from "./TableSkeleton";
|
|
2
|
+
export type { TableSkeletonColumn, TableSkeletonProps } from "./TableSkeleton";
|
|
3
|
+
export { useTableOverflow } from "./useTableOverflow";
|
|
4
|
+
export type { TableOverflow, TableOverflowState } from "./useTableOverflow";
|
|
5
|
+
export { TableWrap, TableScrollControls } from "./TableWrap";
|
|
6
|
+
export type { TableWrapProps, TableScrollControlsProps } from "./TableWrap";
|
|
7
|
+
export { TableSearch, matchesTableQuery } from "./TableSearch";
|
|
8
|
+
export type { TableSearchProps } from "./TableSearch";
|
|
9
|
+
export { TableFilters } from "./TableFilters";
|
|
10
|
+
export type { TableFiltersProps, TableColumnVisibilityProps, } from "./TableFilters";
|
|
11
|
+
export { clearTableFilters, filterValueKey, filterValueLabel, hasActiveFilters, matchesTableFilters, removeFilterValue, resolveAuditFilter, resolveFieldFilter, setFilterValue, toggleFilterValue, } from "./filterModel";
|
|
12
|
+
export type { TableFilterColumn, TableFilterOption, TableFilterState, } from "./filterModel";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/table/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC7D,YAAY,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC/D,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EACV,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { TableSkeleton } from "./TableSkeleton";
|
|
2
|
+
export { useTableOverflow } from "./useTableOverflow";
|
|
3
|
+
export { TableWrap, TableScrollControls } from "./TableWrap";
|
|
4
|
+
export { TableSearch, matchesTableQuery } from "./TableSearch";
|
|
5
|
+
export { TableFilters } from "./TableFilters";
|
|
6
|
+
export { clearTableFilters, filterValueKey, filterValueLabel, hasActiveFilters, matchesTableFilters, removeFilterValue, resolveAuditFilter, resolveFieldFilter, setFilterValue, toggleFilterValue, } from "./filterModel";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
export type TableOverflowState = {
|
|
3
|
+
overflow: boolean;
|
|
4
|
+
canLeft: boolean;
|
|
5
|
+
canRight: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type TableOverflow = TableOverflowState & {
|
|
8
|
+
wrapRef: RefObject<HTMLDivElement | null>;
|
|
9
|
+
scrollBy: (direction: "left" | "right") => void;
|
|
10
|
+
update: () => void;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Default horizontal overflow tracking for `.vertebr-table-wrap`.
|
|
14
|
+
* Pass `observeKey` (e.g. row count) so layout is remeasured when content changes.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useTableOverflow(observeKey?: unknown): TableOverflow;
|
|
17
|
+
//# sourceMappingURL=useTableOverflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTableOverflow.d.ts","sourceRoot":"","sources":["../../../src/components/table/useTableOverflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,kBAAkB,GAAG;IAC/C,OAAO,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC1C,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;IAChD,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,CAAC,EAAE,OAAO,GAAG,aAAa,CAiDpE"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState, } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Default horizontal overflow tracking for `.vertebr-table-wrap`.
|
|
4
|
+
* Pass `observeKey` (e.g. row count) so layout is remeasured when content changes.
|
|
5
|
+
*/
|
|
6
|
+
export function useTableOverflow(observeKey) {
|
|
7
|
+
const wrapRef = useRef(null);
|
|
8
|
+
const [state, setState] = useState({
|
|
9
|
+
overflow: false,
|
|
10
|
+
canLeft: false,
|
|
11
|
+
canRight: false,
|
|
12
|
+
});
|
|
13
|
+
const update = useCallback(() => {
|
|
14
|
+
const el = wrapRef.current;
|
|
15
|
+
if (!el) {
|
|
16
|
+
setState({ overflow: false, canLeft: false, canRight: false });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const overflow = el.scrollWidth > el.clientWidth + 2;
|
|
20
|
+
setState({
|
|
21
|
+
overflow,
|
|
22
|
+
canLeft: overflow && el.scrollLeft > 2,
|
|
23
|
+
canRight: overflow && el.scrollLeft + el.clientWidth < el.scrollWidth - 2,
|
|
24
|
+
});
|
|
25
|
+
}, []);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const el = wrapRef.current;
|
|
28
|
+
if (!el)
|
|
29
|
+
return;
|
|
30
|
+
update();
|
|
31
|
+
const onScroll = () => update();
|
|
32
|
+
el.addEventListener("scroll", onScroll, { passive: true });
|
|
33
|
+
const observer = new ResizeObserver(update);
|
|
34
|
+
observer.observe(el);
|
|
35
|
+
const table = el.querySelector("table");
|
|
36
|
+
if (table)
|
|
37
|
+
observer.observe(table);
|
|
38
|
+
return () => {
|
|
39
|
+
el.removeEventListener("scroll", onScroll);
|
|
40
|
+
observer.disconnect();
|
|
41
|
+
};
|
|
42
|
+
}, [update, observeKey]);
|
|
43
|
+
const scrollBy = useCallback((direction) => {
|
|
44
|
+
const el = wrapRef.current;
|
|
45
|
+
if (!el)
|
|
46
|
+
return;
|
|
47
|
+
const amount = Math.max(220, Math.round(el.clientWidth * 0.55));
|
|
48
|
+
el.scrollBy({
|
|
49
|
+
left: direction === "left" ? -amount : amount,
|
|
50
|
+
behavior: "smooth",
|
|
51
|
+
});
|
|
52
|
+
}, []);
|
|
53
|
+
return { wrapRef, ...state, scrollBy, update };
|
|
54
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export type { MultiCheckOption } from "./components/emails/MultiCheckSelect";
|
|
|
11
11
|
export type { EmailsLibraryPanelProps } from "./components/emails/EmailsLibraryPanel";
|
|
12
12
|
export type { EmailsActivityPanelProps } from "./components/emails/EmailsActivityPanel";
|
|
13
13
|
export { ResourcePanel, ResourceTable } from "./components/resources/ResourceTable";
|
|
14
|
+
export { SortableHeader } from "./components/resources/SortableHeader";
|
|
15
|
+
export { compareFieldValues, nextTableSort, sortRows, type SortOrder, type TableSortState, } from "./lib/sort";
|
|
14
16
|
export { ThemeToggle } from "./components/theme/ThemeToggle";
|
|
15
17
|
export type { ThemeToggleProps } from "./components/theme/ThemeToggle";
|
|
16
18
|
export { ConfigForm } from "./components/config/ConfigForm";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,YAAY,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AACrF,YAAY,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,YAAY,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACtF,YAAY,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAE/E,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,EACf,WAAW,EACX,cAAc,EACd,GAAG,EACH,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ export { FontLibraryBoard } from "./components/fonts/FontLibraryBoard";
|
|
|
5
5
|
export { FontPickerField } from "./components/fonts/FontPickerField";
|
|
6
6
|
export { EmailsBoard } from "./components/emails/EmailsBoard";
|
|
7
7
|
export { ResourcePanel, ResourceTable } from "./components/resources/ResourceTable";
|
|
8
|
+
export { SortableHeader } from "./components/resources/SortableHeader";
|
|
9
|
+
export { compareFieldValues, nextTableSort, sortRows, } from "./lib/sort";
|
|
8
10
|
export { ThemeToggle } from "./components/theme/ThemeToggle";
|
|
9
11
|
export { ConfigForm } from "./components/config/ConfigForm";
|
|
10
12
|
export { formatPrice, formatPriceInput, parsePriceInput, isCentsField, isPriceField, } from "./lib/price";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FieldSchema, FieldType } from "@vertebr/client";
|
|
2
|
+
export type SortOrder = "asc" | "desc";
|
|
3
|
+
export type TableSortState = {
|
|
4
|
+
field: string;
|
|
5
|
+
order: SortOrder;
|
|
6
|
+
};
|
|
7
|
+
export declare function compareFieldValues(left: unknown, right: unknown, type: FieldType | undefined, order: SortOrder): number;
|
|
8
|
+
export declare function sortRows<T extends Record<string, unknown>>(rows: T[], sort: TableSortState | null | undefined, schema?: Record<string, FieldSchema>): T[];
|
|
9
|
+
export declare function nextTableSort(current: TableSortState | null | undefined, field: string, preferredFirst?: SortOrder): TableSortState;
|
|
10
|
+
//# sourceMappingURL=sort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sort.d.ts","sourceRoot":"","sources":["../../src/lib/sort.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC;AAEvC,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;CAClB,CAAC;AA8BF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,SAAS,GAAG,SAAS,EAC3B,KAAK,EAAE,SAAS,GACf,MAAM,CAiBR;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxD,IAAI,EAAE,CAAC,EAAE,EACT,IAAI,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EACvC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACnC,CAAC,EAAE,CAQL;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EAC1C,KAAK,EAAE,MAAM,EACb,cAAc,GAAE,SAAiB,GAChC,cAAc,CAKhB"}
|
package/dist/lib/sort.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function sortValue(value, type) {
|
|
2
|
+
if (value == null || value === "")
|
|
3
|
+
return null;
|
|
4
|
+
if (type === "number") {
|
|
5
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
6
|
+
return Number.isFinite(n) ? n : null;
|
|
7
|
+
}
|
|
8
|
+
if (type === "datetime") {
|
|
9
|
+
const ms = typeof value === "number"
|
|
10
|
+
? value
|
|
11
|
+
: Date.parse(typeof value === "string" ? value : String(value));
|
|
12
|
+
return Number.isFinite(ms) ? ms : null;
|
|
13
|
+
}
|
|
14
|
+
if (type === "boolean") {
|
|
15
|
+
return value === true || value === "true" || value === 1;
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
return value.map((item) => String(item ?? "")).join(", ");
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === "object") {
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
}
|
|
23
|
+
return String(value);
|
|
24
|
+
}
|
|
25
|
+
export function compareFieldValues(left, right, type, order) {
|
|
26
|
+
const dir = order === "desc" ? -1 : 1;
|
|
27
|
+
const a = sortValue(left, type);
|
|
28
|
+
const b = sortValue(right, type);
|
|
29
|
+
if (a == null && b == null)
|
|
30
|
+
return 0;
|
|
31
|
+
if (a == null)
|
|
32
|
+
return 1;
|
|
33
|
+
if (b == null)
|
|
34
|
+
return -1;
|
|
35
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
36
|
+
return (a - b) * dir;
|
|
37
|
+
}
|
|
38
|
+
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
39
|
+
return (Number(a) - Number(b)) * dir;
|
|
40
|
+
}
|
|
41
|
+
return String(a).localeCompare(String(b), undefined, {
|
|
42
|
+
numeric: true,
|
|
43
|
+
sensitivity: "base",
|
|
44
|
+
}) * dir;
|
|
45
|
+
}
|
|
46
|
+
export function sortRows(rows, sort, schema) {
|
|
47
|
+
if (!sort?.field)
|
|
48
|
+
return rows;
|
|
49
|
+
const type = schema?.[sort.field]?.type;
|
|
50
|
+
const field = sort.field;
|
|
51
|
+
const order = sort.order;
|
|
52
|
+
return [...rows].sort((left, right) => compareFieldValues(left[field], right[field], type, order));
|
|
53
|
+
}
|
|
54
|
+
export function nextTableSort(current, field, preferredFirst = "asc") {
|
|
55
|
+
if (current?.field === field) {
|
|
56
|
+
return { field, order: current.order === "asc" ? "desc" : "asc" };
|
|
57
|
+
}
|
|
58
|
+
return { field, order: preferredFirst };
|
|
59
|
+
}
|