@hyperpackai/hyperui 0.1.0 → 0.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/README.md +69 -61
- package/dist/components/Accordion/index.d.ts +6 -0
- package/dist/components/Accordion/index.d.ts.map +1 -1
- package/dist/components/Accordion/index.js +65 -9
- package/dist/components/Autocomplete/index.d.ts +12 -2
- package/dist/components/Autocomplete/index.d.ts.map +1 -1
- package/dist/components/Autocomplete/index.js +148 -24
- package/dist/components/Backdrop/index.d.ts +2 -1
- package/dist/components/Backdrop/index.d.ts.map +1 -1
- package/dist/components/Backdrop/index.js +6 -3
- package/dist/components/Checkbox/index.d.ts +1 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -1
- package/dist/components/Checkbox/index.js +6 -2
- package/dist/components/DashboardLayout/index.d.ts +13 -0
- package/dist/components/DashboardLayout/index.d.ts.map +1 -1
- package/dist/components/DashboardLayout/index.js +50 -7
- package/dist/components/DataTable/index.d.ts +43 -0
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DataTable/index.js +126 -21
- package/dist/components/Dialog/index.d.ts +9 -3
- package/dist/components/Dialog/index.d.ts.map +1 -1
- package/dist/components/Dialog/index.js +46 -30
- package/dist/components/Drawer/index.d.ts +11 -3
- package/dist/components/Drawer/index.d.ts.map +1 -1
- package/dist/components/Drawer/index.js +66 -11
- package/dist/components/DropdownMenu/index.d.ts +5 -3
- package/dist/components/DropdownMenu/index.d.ts.map +1 -1
- package/dist/components/DropdownMenu/index.js +56 -13
- package/dist/components/FocusTrap/index.d.ts.map +1 -1
- package/dist/components/FocusTrap/index.js +34 -32
- package/dist/components/Input/index.d.ts +2 -0
- package/dist/components/Input/index.d.ts.map +1 -1
- package/dist/components/Input/index.js +18 -4
- package/dist/components/Menu/index.d.ts +6 -2
- package/dist/components/Menu/index.d.ts.map +1 -1
- package/dist/components/Menu/index.js +50 -15
- package/dist/components/Modal/index.d.ts +3 -1
- package/dist/components/Modal/index.d.ts.map +1 -1
- package/dist/components/Modal/index.js +27 -9
- package/dist/components/NestedNavbar/index.d.ts +33 -0
- package/dist/components/NestedNavbar/index.d.ts.map +1 -0
- package/dist/components/NestedNavbar/index.js +435 -0
- package/dist/components/NestedSidebar/index.d.ts +48 -0
- package/dist/components/NestedSidebar/index.d.ts.map +1 -0
- package/dist/components/NestedSidebar/index.js +368 -0
- package/dist/components/Popover/index.d.ts +11 -3
- package/dist/components/Popover/index.d.ts.map +1 -1
- package/dist/components/Popover/index.js +45 -9
- package/dist/components/Radio/index.d.ts +26 -1
- package/dist/components/Radio/index.d.ts.map +1 -1
- package/dist/components/Radio/index.js +61 -2
- package/dist/components/Select/index.d.ts +5 -0
- package/dist/components/Select/index.d.ts.map +1 -1
- package/dist/components/Select/index.js +22 -5
- package/dist/components/Sheet/index.d.ts +9 -3
- package/dist/components/Sheet/index.d.ts.map +1 -1
- package/dist/components/Sheet/index.js +48 -23
- package/dist/components/Sidebar/index.d.ts +20 -1
- package/dist/components/Sidebar/index.d.ts.map +1 -1
- package/dist/components/Sidebar/index.js +285 -8
- package/dist/components/SpeedDial/index.d.ts +10 -0
- package/dist/components/SpeedDial/index.d.ts.map +1 -1
- package/dist/components/SpeedDial/index.js +61 -11
- package/dist/components/Switch/index.d.ts +2 -0
- package/dist/components/Switch/index.d.ts.map +1 -1
- package/dist/components/Switch/index.js +6 -2
- package/dist/components/Tabs/index.d.ts +3 -0
- package/dist/components/Tabs/index.d.ts.map +1 -1
- package/dist/components/Tabs/index.js +47 -8
- package/dist/components/TextField/index.d.ts +2 -0
- package/dist/components/TextField/index.d.ts.map +1 -1
- package/dist/components/TextField/index.js +12 -4
- package/dist/components/Textarea/index.d.ts +5 -0
- package/dist/components/Textarea/index.d.ts.map +1 -1
- package/dist/components/Textarea/index.js +21 -4
- package/dist/components/Transition/index.d.ts +14 -0
- package/dist/components/Transition/index.d.ts.map +1 -0
- package/dist/components/Transition/index.js +49 -0
- package/dist/components/TransitionGroup/index.d.ts +16 -0
- package/dist/components/TransitionGroup/index.d.ts.map +1 -0
- package/dist/components/TransitionGroup/index.js +95 -0
- package/dist/components/data.d.ts +81 -16
- package/dist/components/data.d.ts.map +1 -1
- package/dist/components/data.js +163 -31
- package/dist/components/enterprise.d.ts +85 -26
- package/dist/components/enterprise.d.ts.map +1 -1
- package/dist/components/enterprise.js +211 -36
- package/dist/components/index.d.ts +21 -13
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +7 -2
- package/dist/portal.d.ts.map +1 -1
- package/dist/portal.js +3 -0
- package/dist/theme/index.d.ts +5 -6
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +30 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +11 -0
- package/package.json +8 -3
package/dist/components/data.js
CHANGED
|
@@ -6,6 +6,18 @@ import { injectCSS, cn, h } from "../theme/index.js";
|
|
|
6
6
|
import { Button } from "./Button/index.js";
|
|
7
7
|
import { Input } from "./Input/index.js";
|
|
8
8
|
import { Select } from "./Select/index.js";
|
|
9
|
+
function rootProps(props, className, extra) {
|
|
10
|
+
return {
|
|
11
|
+
...extra,
|
|
12
|
+
id: props.id,
|
|
13
|
+
class: cn(className, props.class),
|
|
14
|
+
style: mergeStyle(props.style, extra?.style),
|
|
15
|
+
"data-testid": props.testId
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function mergeStyle(base, extra) {
|
|
19
|
+
return [base, extra].filter(Boolean).join(";") || undefined;
|
|
20
|
+
}
|
|
9
21
|
const DATA_CSS = `
|
|
10
22
|
.hu-virtual-table {
|
|
11
23
|
border: 1px solid var(--hu-border); border-radius: var(--hu-radius-md);
|
|
@@ -14,6 +26,11 @@ const DATA_CSS = `
|
|
|
14
26
|
.hu-virtual-table__inner { position: relative; min-width: 100%; }
|
|
15
27
|
.hu-virtual-table table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
|
16
28
|
.hu-virtual-table thead { position: sticky; inset-block-start: 0; z-index: 1; background: var(--hu-bg-2); }
|
|
29
|
+
.hu-virtual-table--no-sticky thead { position: static; }
|
|
30
|
+
.hu-virtual-table__caption {
|
|
31
|
+
padding: var(--hu-space-2) var(--hu-space-3); color: var(--hu-text-2);
|
|
32
|
+
font-size: var(--hu-font-size-sm); text-align: start; caption-side: top;
|
|
33
|
+
}
|
|
17
34
|
.hu-virtual-table th {
|
|
18
35
|
padding: var(--hu-space-2) var(--hu-space-3); border-block-end: 1px solid var(--hu-border);
|
|
19
36
|
color: var(--hu-text-2); font-size: var(--hu-font-size-xs); font-weight: var(--hu-font-weight-semibold);
|
|
@@ -23,7 +40,14 @@ const DATA_CSS = `
|
|
|
23
40
|
padding: var(--hu-space-2) var(--hu-space-3); border-block-end: 1px solid var(--hu-border);
|
|
24
41
|
color: var(--hu-text); font-size: var(--hu-font-size-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
25
42
|
}
|
|
43
|
+
.hu-virtual-table--compact th,
|
|
44
|
+
.hu-virtual-table--compact td { padding: var(--hu-space-1) var(--hu-space-2); }
|
|
45
|
+
.hu-virtual-table--comfortable th,
|
|
46
|
+
.hu-virtual-table--comfortable td { padding: var(--hu-space-3) var(--hu-space-4); }
|
|
26
47
|
.hu-virtual-table tr:hover td { background: var(--hu-bg-2); }
|
|
48
|
+
.hu-virtual-table tr[aria-selected="true"] td { background: var(--hu-primary-bg); }
|
|
49
|
+
.hu-virtual-table tr[aria-disabled="true"] td { color: var(--hu-text-3); cursor: not-allowed; opacity: .72; }
|
|
50
|
+
.hu-virtual-table tr[aria-disabled="true"]:hover td { background: transparent; }
|
|
27
51
|
.hu-virtual-table__empty { padding: var(--hu-space-8); text-align: center; color: var(--hu-text-2); }
|
|
28
52
|
|
|
29
53
|
.hu-advanced-filters {
|
|
@@ -59,6 +83,12 @@ const DATA_CSS = `
|
|
|
59
83
|
border: 1px solid var(--hu-border); border-radius: var(--hu-radius-md);
|
|
60
84
|
background: var(--hu-bg); padding: var(--hu-space-4);
|
|
61
85
|
}
|
|
86
|
+
.hu-data-grid__header { display: flex; flex-direction: column; gap: var(--hu-space-1); }
|
|
87
|
+
.hu-data-grid__title {
|
|
88
|
+
margin: 0; color: var(--hu-text); font-size: var(--hu-font-size-lg);
|
|
89
|
+
font-weight: var(--hu-font-weight-semibold);
|
|
90
|
+
}
|
|
91
|
+
.hu-data-grid__description { margin: 0; color: var(--hu-text-2); font-size: var(--hu-font-size-sm); }
|
|
62
92
|
.hu-data-grid__toolbar {
|
|
63
93
|
display: flex; align-items: center; justify-content: space-between;
|
|
64
94
|
gap: var(--hu-space-3); flex-wrap: wrap;
|
|
@@ -143,12 +173,21 @@ const DATA_CSS = `
|
|
|
143
173
|
.hu-pivot-grid__meta { font-size: var(--hu-font-size-xs); color: var(--hu-text-2); }
|
|
144
174
|
.hu-pivot-grid__table { overflow: auto; border: 1px solid var(--hu-border); border-radius: var(--hu-radius); }
|
|
145
175
|
.hu-pivot-grid__table table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
|
176
|
+
.hu-pivot-grid__table--sticky thead { position: sticky; inset-block-start: 0; z-index: 1; }
|
|
177
|
+
.hu-pivot-grid__caption {
|
|
178
|
+
padding: var(--hu-space-2) var(--hu-space-3); color: var(--hu-text-2);
|
|
179
|
+
font-size: var(--hu-font-size-sm); text-align: start; caption-side: top;
|
|
180
|
+
}
|
|
146
181
|
.hu-pivot-grid__table th,
|
|
147
182
|
.hu-pivot-grid__table td {
|
|
148
183
|
padding: var(--hu-space-2) var(--hu-space-3); border-block-end: 1px solid var(--hu-border);
|
|
149
184
|
border-inline-end: 1px solid var(--hu-border); font-size: var(--hu-font-size-xs);
|
|
150
185
|
color: var(--hu-text); text-align: end; white-space: nowrap;
|
|
151
186
|
}
|
|
187
|
+
.hu-pivot-grid__table--compact th,
|
|
188
|
+
.hu-pivot-grid__table--compact td { padding: var(--hu-space-1) var(--hu-space-2); }
|
|
189
|
+
.hu-pivot-grid__table--comfortable th,
|
|
190
|
+
.hu-pivot-grid__table--comfortable td { padding: var(--hu-space-3) var(--hu-space-4); }
|
|
152
191
|
.hu-pivot-grid__table th { background: var(--hu-bg-2); color: var(--hu-text-2); font-weight: var(--hu-font-weight-semibold); text-align: start; }
|
|
153
192
|
.hu-pivot-grid__total { font-weight: var(--hu-font-weight-semibold); background: var(--hu-bg-2); }
|
|
154
193
|
@media (max-width: 720px) {
|
|
@@ -168,14 +207,18 @@ export function VirtualTable(props) {
|
|
|
168
207
|
const visibleRows = props.rows.slice(startIndex, startIndex + visibleCount);
|
|
169
208
|
const totalHeight = props.rows.length * rowHeight;
|
|
170
209
|
const offset = startIndex * rowHeight;
|
|
171
|
-
|
|
172
|
-
|
|
210
|
+
const selectedRows = props.selectedRows ? new Set(props.selectedRows) : undefined;
|
|
211
|
+
const stickyHeader = props.stickyHeader !== false;
|
|
212
|
+
return h("div", rootProps(props, cn("hu-virtual-table", props.density && props.density !== "default" && `hu-virtual-table--${props.density}`, !stickyHeader && "hu-virtual-table--no-sticky"), {
|
|
173
213
|
style: props.height !== undefined ? `height:${toCssUnit(props.height)}` : undefined,
|
|
174
214
|
role: "region",
|
|
175
|
-
"aria-label": "Virtualized table"
|
|
176
|
-
|
|
215
|
+
"aria-label": props["aria-label"] ?? "Virtualized table",
|
|
216
|
+
"aria-labelledby": props["aria-labelledby"]
|
|
217
|
+
}), h("div", { class: "hu-virtual-table__inner", style: `height:${totalHeight}px` }, h("table", { role: "table", "aria-rowcount": props.rows.length }, props.caption && h("caption", { class: "hu-virtual-table__caption" }, props.caption), h("thead", {}, h("tr", { role: "row" }, ...props.columns.map((column) => h("th", {
|
|
177
218
|
key: column.key,
|
|
219
|
+
id: getColumnId(column),
|
|
178
220
|
role: "columnheader",
|
|
221
|
+
"aria-label": column.headerLabel,
|
|
179
222
|
style: [
|
|
180
223
|
column.width !== undefined && `width:${toCssUnit(column.width)}`,
|
|
181
224
|
column.align && `text-align:${column.align}`
|
|
@@ -185,15 +228,21 @@ export function VirtualTable(props) {
|
|
|
185
228
|
: visibleRows.map((row, localIndex) => {
|
|
186
229
|
const index = startIndex + localIndex;
|
|
187
230
|
const key = props.rowKey?.(row, index) ?? String(row["id"] ?? index);
|
|
231
|
+
const disabled = props.isRowDisabled?.(row, index) ?? false;
|
|
188
232
|
return h("tr", {
|
|
189
233
|
key,
|
|
190
234
|
role: "row",
|
|
191
235
|
"aria-rowindex": index + 1,
|
|
236
|
+
"aria-label": props.getRowLabel?.(row, index),
|
|
237
|
+
"aria-selected": selectedRows?.has(key) ? "true" : undefined,
|
|
238
|
+
"aria-disabled": disabled ? "true" : undefined,
|
|
192
239
|
style: `height:${rowHeight}px`,
|
|
193
|
-
onClick: props.onRowClick ? () => props.onRowClick?.(row, index) : undefined
|
|
240
|
+
onClick: props.onRowClick && !disabled ? () => props.onRowClick?.(row, index) : undefined
|
|
194
241
|
}, ...props.columns.map((column) => h("td", {
|
|
195
242
|
key: column.key,
|
|
196
243
|
role: "cell",
|
|
244
|
+
headers: getColumnId(column),
|
|
245
|
+
"aria-label": column.cellLabel?.(row, index),
|
|
197
246
|
style: column.align ? `text-align:${column.align}` : undefined
|
|
198
247
|
}, column.render ? column.render(row, index) : row[column.key])));
|
|
199
248
|
})))));
|
|
@@ -214,7 +263,7 @@ export function AdvancedFilters(props) {
|
|
|
214
263
|
injectCSS("hu-data", DATA_CSS);
|
|
215
264
|
const fieldOptions = props.fields.map((field) => ({ value: field.key, label: field.label }));
|
|
216
265
|
const operatorOptions = FILTER_OPERATORS.map((operator) => ({ value: operator.value, label: operator.label }));
|
|
217
|
-
return h("section",
|
|
266
|
+
return h("section", rootProps(props, "hu-advanced-filters", { "aria-label": props.title ?? "Advanced filters" }), h("div", { class: "hu-advanced-filters__header" }, h("div", { class: "hu-advanced-filters__title" }, props.title ?? "Advanced filters"), Button({ type: "button", variant: "secondary", size: "sm", children: "Add filter", onClick: props.onAddRule })), h("div", { class: "hu-advanced-filters__rules" }, (props.rules?.length ?? 0) === 0
|
|
218
267
|
? h("div", { class: "hu-advanced-filters__empty" }, "No filters applied.")
|
|
219
268
|
: props.rules.map((rule) => h("div", { key: rule.id, class: "hu-advanced-filters__rule", role: "group", "aria-label": `Filter ${rule.id}` }, Select({
|
|
220
269
|
label: "Field",
|
|
@@ -236,7 +285,7 @@ export function SearchBuilder(props) {
|
|
|
236
285
|
injectCSS("hu-data", DATA_CSS);
|
|
237
286
|
const tokens = props.tokens ?? [];
|
|
238
287
|
const query = props.query ?? "";
|
|
239
|
-
return h("section",
|
|
288
|
+
return h("section", rootProps(props, "hu-search-builder", { "aria-label": "Search builder" }), h("div", { class: "hu-search-builder__bar", role: "search" }, h("div", { class: "hu-search-builder__query" }, Input({
|
|
240
289
|
value: query,
|
|
241
290
|
placeholder: props.placeholder ?? "Search…",
|
|
242
291
|
onInput: (e) => props.onQueryChange?.(e.target.value)
|
|
@@ -260,14 +309,19 @@ export function DataGrid(props) {
|
|
|
260
309
|
key: "__select",
|
|
261
310
|
header: "",
|
|
262
311
|
width: 44,
|
|
312
|
+
headerLabel: "Select rows",
|
|
263
313
|
render: (row, index) => {
|
|
264
314
|
const key = rowKey(row, index);
|
|
265
315
|
const checked = selected.has(key);
|
|
316
|
+
const disabled = props.isRowDisabled?.(row, index) ?? false;
|
|
266
317
|
return h("input", {
|
|
267
318
|
type: "checkbox",
|
|
268
319
|
checked,
|
|
320
|
+
disabled,
|
|
269
321
|
"aria-label": `Select row ${index + 1}`,
|
|
270
322
|
onChange: (e) => {
|
|
323
|
+
if (disabled)
|
|
324
|
+
return;
|
|
271
325
|
const next = new Set(selected);
|
|
272
326
|
if (e.target.checked)
|
|
273
327
|
next.add(key);
|
|
@@ -280,11 +334,14 @@ export function DataGrid(props) {
|
|
|
280
334
|
}
|
|
281
335
|
: undefined;
|
|
282
336
|
const tableColumns = selectionColumn ? [selectionColumn, ...visibleColumns] : visibleColumns;
|
|
283
|
-
return h("section",
|
|
337
|
+
return h("section", rootProps(props, "hu-data-grid", {
|
|
338
|
+
"aria-label": props["aria-label"] ?? props.title ?? "Data grid",
|
|
339
|
+
"aria-labelledby": props["aria-labelledby"]
|
|
340
|
+
}), (props.title || props.description) && h("div", { class: "hu-data-grid__header" }, props.title && h("h2", { class: "hu-data-grid__title" }, props.title), props.description && h("p", { class: "hu-data-grid__description" }, props.description)), h("div", { class: "hu-data-grid__toolbar" }, h("div", { class: "hu-data-grid__search" }, Input({
|
|
284
341
|
value: props.query ?? "",
|
|
285
|
-
placeholder: "Search rows...",
|
|
342
|
+
placeholder: props.searchPlaceholder ?? "Search rows...",
|
|
286
343
|
onInput: (e) => props.onQueryChange?.(e.target.value)
|
|
287
|
-
})), h("div", { class: "hu-data-grid__actions" }, props.toolbarActions, props.filters && Button({ type: "button", variant: "secondary", size: "sm", children: "Add filter", onClick: props.onFilterAdd }))), props.showColumnControls && h("div", { class: "hu-data-grid__columns", "aria-label": "Column visibility" }, ...props.columns.map((column) => h("label", { class: "hu-data-grid__column-toggle" }, h("input", {
|
|
344
|
+
})), h("div", { class: "hu-data-grid__actions" }, props.toolbarActions, props.filters && Button({ type: "button", variant: "secondary", size: "sm", children: "Add filter", onClick: props.onFilterAdd }))), props.showColumnControls && h("div", { class: "hu-data-grid__columns", "aria-label": props.columnControlsLabel ?? "Column visibility" }, ...props.columns.map((column) => h("label", { class: "hu-data-grid__column-toggle" }, h("input", {
|
|
288
345
|
type: "checkbox",
|
|
289
346
|
checked: column.visible !== false,
|
|
290
347
|
onChange: (e) => props.onColumnVisibilityChange?.(column.key, e.target.checked)
|
|
@@ -294,10 +351,18 @@ export function DataGrid(props) {
|
|
|
294
351
|
onAddRule: props.onFilterAdd,
|
|
295
352
|
onRemoveRule: props.onFilterRemove,
|
|
296
353
|
onRuleChange: props.onFilterChange
|
|
297
|
-
}), props.selectedRows && h("div", { class: "hu-data-grid__selection", "aria-live": "polite" }, `${props.selectedRows.length} selected`), VirtualTable({
|
|
354
|
+
}), props.selectedRows && h("div", { class: "hu-data-grid__selection", "aria-live": "polite" }, props.selectedRowsLabel?.(props.selectedRows.length) ?? `${props.selectedRows.length} selected`), VirtualTable({
|
|
298
355
|
columns: tableColumns,
|
|
299
356
|
rows: props.rows,
|
|
357
|
+
caption: props.caption,
|
|
358
|
+
"aria-label": props.tableLabel,
|
|
359
|
+
"aria-labelledby": props.tableLabelledBy,
|
|
360
|
+
density: props.density,
|
|
361
|
+
stickyHeader: props.stickyHeader,
|
|
300
362
|
rowKey,
|
|
363
|
+
selectedRows: props.selectedRows,
|
|
364
|
+
getRowLabel: props.getRowLabel,
|
|
365
|
+
isRowDisabled: props.isRowDisabled,
|
|
301
366
|
rowHeight: props.rowHeight,
|
|
302
367
|
height: props.height,
|
|
303
368
|
startIndex: props.visibleStartIndex,
|
|
@@ -324,8 +389,11 @@ export function DataExporter(props) {
|
|
|
324
389
|
const format = props.format ?? "csv";
|
|
325
390
|
const filename = props.filename ?? `export.${format}`;
|
|
326
391
|
const selectedColumns = getSelectedExportColumns(props.columns, props.selectedColumns);
|
|
327
|
-
return h("section",
|
|
328
|
-
label: "
|
|
392
|
+
return h("section", rootProps(props, "hu-data-transfer", {
|
|
393
|
+
"aria-label": props["aria-label"] ?? props.title ?? "Data exporter",
|
|
394
|
+
"aria-labelledby": props["aria-labelledby"]
|
|
395
|
+
}), h("div", { class: "hu-data-transfer__header" }, h("div", {}, h("div", { class: "hu-data-transfer__title" }, props.title ?? "Export data"), h("div", { class: "hu-data-transfer__meta" }, props.description ?? `${props.rows.length} rows available`)), h("div", { class: "hu-data-transfer__meta", "aria-live": "polite" }, props.selectedColumnsLabel?.(selectedColumns.length) ?? `${selectedColumns.length} columns selected`)), h("div", { class: "hu-data-transfer__controls" }, Select({
|
|
396
|
+
label: props.formatLabel ?? "Format",
|
|
329
397
|
value: format,
|
|
330
398
|
options: [
|
|
331
399
|
{ label: "CSV", value: "csv" },
|
|
@@ -333,12 +401,12 @@ export function DataExporter(props) {
|
|
|
333
401
|
],
|
|
334
402
|
onChange: (e) => props.onFormatChange?.(e.target.value)
|
|
335
403
|
}), Input({
|
|
336
|
-
label: "Filename",
|
|
404
|
+
label: props.filenameLabel ?? "Filename",
|
|
337
405
|
value: filename,
|
|
338
406
|
onInput: (e) => props.onFilenameChange?.(e.target.value)
|
|
339
407
|
}), Button({
|
|
340
408
|
type: "button",
|
|
341
|
-
children: "Export",
|
|
409
|
+
children: props.exportLabel ?? "Export",
|
|
342
410
|
disabled: selectedColumns.length === 0,
|
|
343
411
|
onClick: () => props.onExport?.({
|
|
344
412
|
format,
|
|
@@ -347,12 +415,18 @@ export function DataExporter(props) {
|
|
|
347
415
|
columns: selectedColumns,
|
|
348
416
|
content: serializeExport(props.rows, selectedColumns, format, props.includeHeaders !== false)
|
|
349
417
|
})
|
|
350
|
-
})), h("div", { class: "hu-data-transfer__columns", "aria-label": "Export columns" }, ...props.columns.map((column) => {
|
|
418
|
+
})), h("div", { class: "hu-data-transfer__columns", "aria-label": props.columnsLabel ?? "Export columns" }, ...props.columns.map((column) => {
|
|
351
419
|
const checked = selectedColumns.some((selected) => selected.key === column.key);
|
|
352
420
|
return h("label", { class: "hu-data-transfer__column" }, h("input", {
|
|
353
421
|
type: "checkbox",
|
|
354
422
|
checked,
|
|
355
|
-
|
|
423
|
+
disabled: column.disabled,
|
|
424
|
+
"aria-label": column.headerLabel ?? `Export ${column.header}`,
|
|
425
|
+
onChange: (e) => {
|
|
426
|
+
if (column.disabled)
|
|
427
|
+
return;
|
|
428
|
+
props.onColumnToggle?.(column.key, e.target.checked);
|
|
429
|
+
}
|
|
356
430
|
}), column.header);
|
|
357
431
|
})));
|
|
358
432
|
}
|
|
@@ -363,19 +437,23 @@ export function DataImporter(props) {
|
|
|
363
437
|
const previewRows = props.previewRows ?? [];
|
|
364
438
|
const columns = props.columns ?? inferImportColumns(previewRows);
|
|
365
439
|
const status = props.status ?? (props.fileName ? "ready" : "idle");
|
|
366
|
-
return h("section",
|
|
367
|
-
label: "
|
|
440
|
+
return h("section", rootProps(props, "hu-data-transfer", {
|
|
441
|
+
"aria-label": props["aria-label"] ?? props.title ?? "Data importer",
|
|
442
|
+
"aria-labelledby": props["aria-labelledby"]
|
|
443
|
+
}), h("div", { class: "hu-data-transfer__header" }, h("div", {}, h("div", { class: "hu-data-transfer__title" }, props.title ?? "Import data"), h("div", { class: "hu-data-transfer__meta" }, props.description ?? "Upload a file, validate mapped columns, then import.")), h("div", { class: "hu-data-transfer__meta", "aria-live": "polite" }, props.previewRowsLabel?.(previewRows.length) ?? `${previewRows.length} preview rows`)), h("div", { class: "hu-data-transfer__controls" }, Select({
|
|
444
|
+
label: props.formatLabel ?? "Format",
|
|
368
445
|
value: format,
|
|
369
446
|
options: [
|
|
370
447
|
{ label: "CSV", value: "csv" },
|
|
371
448
|
{ label: "JSON", value: "json" }
|
|
372
449
|
],
|
|
373
450
|
onChange: (e) => props.onFormatChange?.(e.target.value)
|
|
374
|
-
}), h("label", { class: "hu-data-transfer__dropzone" }, h("span", { class: "hu-data-transfer__meta" }, props.fileName ?? "Choose import file"), h("input", {
|
|
451
|
+
}), h("label", { class: "hu-data-transfer__dropzone" }, h("span", { class: "hu-data-transfer__meta" }, props.fileName ?? props.fileLabel ?? "Choose import file"), h("input", {
|
|
375
452
|
class: "hu-data-transfer__file",
|
|
376
453
|
type: "file",
|
|
377
454
|
accept,
|
|
378
455
|
multiple: props.multiple,
|
|
456
|
+
"aria-label": props.fileLabel ?? "Choose import file",
|
|
379
457
|
onChange: (e) => {
|
|
380
458
|
const file = e.target.files?.[0];
|
|
381
459
|
if (file)
|
|
@@ -383,10 +461,21 @@ export function DataImporter(props) {
|
|
|
383
461
|
}
|
|
384
462
|
})), Button({
|
|
385
463
|
type: "button",
|
|
386
|
-
children: "Import",
|
|
464
|
+
children: props.importLabel ?? "Import",
|
|
387
465
|
disabled: previewRows.length === 0 || status === "error" || status === "parsing",
|
|
388
466
|
onClick: () => props.onImport?.(previewRows)
|
|
389
|
-
})), props.fileName && h("div", { class: "hu-data-transfer__status", role: "status" }, `${props.fileName}${props.maxFileSize ? ` · max ${formatBytes(props.maxFileSize)}` : ""}`), props.error && h("div", { class: "hu-data-transfer__error", role: "alert" }, props.error), previewRows.length > 0 && h("div", { class: "hu-data-transfer__preview", "aria-label": "Import preview" }, h("table", {
|
|
467
|
+
})), props.fileName && h("div", { class: "hu-data-transfer__status", role: "status" }, props.statusLabel?.(status, props.fileName) ?? `${props.fileName}${props.maxFileSize ? ` · max ${formatBytes(props.maxFileSize)}` : ""}`), props.error && h("div", { class: "hu-data-transfer__error", role: "alert" }, props.error), previewRows.length > 0 && h("div", { class: "hu-data-transfer__preview", "aria-label": props.previewLabel ?? "Import preview" }, h("table", { role: "table", "aria-rowcount": previewRows.length }, props.previewCaption && h("caption", { class: "hu-virtual-table__caption" }, props.previewCaption), h("thead", {}, h("tr", {}, ...columns.map((column) => h("th", {
|
|
468
|
+
key: column.key,
|
|
469
|
+
id: getImportColumnId(column),
|
|
470
|
+
scope: "col",
|
|
471
|
+
role: "columnheader",
|
|
472
|
+
"aria-label": column.headerLabel
|
|
473
|
+
}, `${column.header}${column.required ? " *" : ""}`)))), h("tbody", {}, ...previewRows.slice(0, 5).map((row, index) => h("tr", { key: String(row["id"] ?? index), role: "row", "aria-rowindex": index + 1 }, ...columns.map((column) => h("td", {
|
|
474
|
+
key: column.key,
|
|
475
|
+
role: "cell",
|
|
476
|
+
headers: getImportColumnId(column),
|
|
477
|
+
"aria-label": column.cellLabel?.(row, index)
|
|
478
|
+
}, row[column.key] ?? ""))))))), props.onClear && Button({ type: "button", variant: "ghost", size: "sm", children: props.clearLabel ?? "Clear import", onClick: props.onClear }));
|
|
390
479
|
}
|
|
391
480
|
export function TreeGrid(props) {
|
|
392
481
|
injectCSS("hu-data", DATA_CSS);
|
|
@@ -394,14 +483,17 @@ export function TreeGrid(props) {
|
|
|
394
483
|
const flatRows = flattenTreeGridRows(props.rows, expanded);
|
|
395
484
|
const visibleColumns = props.columns.filter((column) => column.visible !== false);
|
|
396
485
|
const firstColumn = visibleColumns[0]?.key;
|
|
397
|
-
|
|
398
|
-
|
|
486
|
+
const stickyHeader = props.stickyHeader !== false;
|
|
487
|
+
return h("div", rootProps(props, cn("hu-virtual-table hu-tree-grid", props.density && props.density !== "default" && `hu-virtual-table--${props.density}`, !stickyHeader && "hu-virtual-table--no-sticky"), {
|
|
399
488
|
style: props.height !== undefined ? `height:${toCssUnit(props.height)}` : undefined,
|
|
400
489
|
role: "region",
|
|
401
|
-
"aria-label": "Tree grid"
|
|
402
|
-
|
|
490
|
+
"aria-label": props["aria-label"] ?? "Tree grid",
|
|
491
|
+
"aria-labelledby": props["aria-labelledby"]
|
|
492
|
+
}), h("table", { role: "treegrid", "aria-rowcount": flatRows.length }, props.caption && h("caption", { class: "hu-virtual-table__caption" }, props.caption), h("thead", {}, h("tr", { role: "row" }, ...visibleColumns.map((column) => h("th", {
|
|
403
493
|
key: column.key,
|
|
494
|
+
id: getColumnId(column),
|
|
404
495
|
role: "columnheader",
|
|
496
|
+
"aria-label": column.headerLabel,
|
|
405
497
|
style: [
|
|
406
498
|
column.width !== undefined && `width:${toCssUnit(column.width)}`,
|
|
407
499
|
column.align && `text-align:${column.align}`
|
|
@@ -411,18 +503,23 @@ export function TreeGrid(props) {
|
|
|
411
503
|
: flatRows.map(({ row, level }, index) => {
|
|
412
504
|
const hasChildren = (row.children?.length ?? 0) > 0;
|
|
413
505
|
const isExpanded = expanded.has(row.id);
|
|
506
|
+
const disabled = row.disabled || (props.isRowDisabled?.(row, index, level) ?? false);
|
|
414
507
|
return h("tr", {
|
|
415
508
|
key: row.id,
|
|
416
509
|
role: "row",
|
|
417
510
|
"aria-level": level,
|
|
418
511
|
"aria-expanded": hasChildren ? String(isExpanded) : undefined,
|
|
419
512
|
"aria-selected": props.selected === row.id ? "true" : undefined,
|
|
513
|
+
"aria-disabled": disabled ? "true" : undefined,
|
|
514
|
+
"aria-label": props.getRowLabel?.(row, index, level),
|
|
420
515
|
"aria-rowindex": index + 1,
|
|
421
516
|
style: props.rowHeight ? `height:${props.rowHeight}px` : undefined,
|
|
422
|
-
onClick: props.onRowClick ? () => props.onRowClick?.(row) : undefined
|
|
517
|
+
onClick: props.onRowClick && !disabled ? () => props.onRowClick?.(row) : undefined
|
|
423
518
|
}, ...visibleColumns.map((column) => h("td", {
|
|
424
519
|
key: column.key,
|
|
425
520
|
role: "gridcell",
|
|
521
|
+
headers: getColumnId(column),
|
|
522
|
+
"aria-label": column.cellLabel?.(row.data, index),
|
|
426
523
|
style: column.align ? `text-align:${column.align}` : undefined
|
|
427
524
|
}, column.key === firstColumn
|
|
428
525
|
? h("span", {
|
|
@@ -434,8 +531,10 @@ export function TreeGrid(props) {
|
|
|
434
531
|
class: "hu-tree-grid__toggle",
|
|
435
532
|
"aria-label": `${isExpanded ? "Collapse" : "Expand"} ${row.id}`,
|
|
436
533
|
"aria-expanded": String(isExpanded),
|
|
437
|
-
disabled
|
|
534
|
+
disabled,
|
|
438
535
|
onClick: (e) => {
|
|
536
|
+
if (disabled)
|
|
537
|
+
return;
|
|
439
538
|
e.stopPropagation?.();
|
|
440
539
|
props.onToggle?.(row.id, !isExpanded);
|
|
441
540
|
}
|
|
@@ -448,13 +547,46 @@ export function PivotGrid(props) {
|
|
|
448
547
|
injectCSS("hu-data", DATA_CSS);
|
|
449
548
|
const aggregation = props.aggregation ?? "sum";
|
|
450
549
|
const matrix = buildPivotMatrix(props.rows, props.rowFields, props.columnField, props.valueField, aggregation);
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
550
|
+
const groupHeaderId = getPivotFieldId(props.rowFields[0], "group");
|
|
551
|
+
const totalHeaderId = "hu-pivot-total";
|
|
552
|
+
return h("section", rootProps(props, "hu-pivot-grid", {
|
|
553
|
+
"aria-label": props["aria-label"] ?? props.title ?? "Pivot grid",
|
|
554
|
+
"aria-labelledby": props["aria-labelledby"]
|
|
555
|
+
}), h("div", { class: "hu-pivot-grid__header" }, h("div", {}, h("div", { class: "hu-pivot-grid__title" }, props.title ?? "Pivot grid"), h("div", { class: "hu-pivot-grid__meta" }, `${props.rows.length} rows · ${aggregation}`)), h("div", { class: "hu-pivot-grid__meta", "aria-live": "polite" }, `${matrix.rowKeys.length} groups · ${matrix.columnKeys.length} columns`)), h("div", {
|
|
556
|
+
class: cn("hu-pivot-grid__table", props.density && props.density !== "default" && `hu-pivot-grid__table--${props.density}`, props.stickyHeader && "hu-pivot-grid__table--sticky"),
|
|
557
|
+
role: "region",
|
|
558
|
+
"aria-label": props.resultsLabel ?? "Pivot results",
|
|
559
|
+
"aria-labelledby": props.resultsLabelledBy
|
|
560
|
+
}, h("table", { role: "table" }, props.caption && h("caption", { class: "hu-pivot-grid__caption" }, props.caption), h("thead", {}, h("tr", { role: "row" }, h("th", {
|
|
561
|
+
id: groupHeaderId,
|
|
562
|
+
role: "columnheader",
|
|
563
|
+
scope: "col",
|
|
564
|
+
"aria-label": props.rowFields.map((field) => field.headerLabel).filter(Boolean).join(" / ") || undefined
|
|
565
|
+
}, props.rowFields.map((field) => field.header).join(" / ") || "Group"), ...matrix.columnKeys.map((columnKey) => h("th", { key: columnKey, id: getPivotColumnId(columnKey), role: "columnheader", scope: "col" }, columnKey)), h("th", { id: totalHeaderId, role: "columnheader", scope: "col" }, "Total"))), h("tbody", {}, matrix.rowKeys.length === 0
|
|
566
|
+
? h("tr", {}, h("td", { colspan: matrix.columnKeys.length + 2, class: "hu-virtual-table__empty" }, props.emptyMessage ?? "No pivot data."))
|
|
567
|
+
: matrix.rowKeys.map((rowKey) => h("tr", { key: rowKey, role: "row" }, h("th", { id: getPivotRowId(rowKey), role: "rowheader", scope: "row", headers: groupHeaderId }, rowKey), ...matrix.columnKeys.map((columnKey) => h("td", { key: columnKey, role: "cell", headers: `${getPivotRowId(rowKey)} ${getPivotColumnId(columnKey)}` }, formatPivotValue(matrix.values.get(`${rowKey}\u0000${columnKey}`) ?? 0, props, rowKey, columnKey, aggregation))), h("td", { class: "hu-pivot-grid__total", role: "cell", headers: `${getPivotRowId(rowKey)} ${totalHeaderId}` }, formatPivotValue(matrix.rowTotals.get(rowKey) ?? 0, props, rowKey, "__total", aggregation))))), matrix.rowKeys.length > 0 && h("tfoot", {}, h("tr", { role: "row" }, h("th", { id: "hu-pivot-footer-total", role: "rowheader", scope: "row", headers: groupHeaderId }, "Total"), ...matrix.columnKeys.map((columnKey) => h("td", { key: columnKey, class: "hu-pivot-grid__total", role: "cell", headers: `hu-pivot-footer-total ${getPivotColumnId(columnKey)}` }, formatPivotValue(matrix.columnTotals.get(columnKey) ?? 0, props, "__total", columnKey, aggregation))), h("td", { class: "hu-pivot-grid__total", role: "cell", headers: `hu-pivot-footer-total ${totalHeaderId}` }, formatPivotValue(matrix.grandTotal, props, "__total", "__total", aggregation)))))));
|
|
454
568
|
}
|
|
455
569
|
function toCssUnit(value) {
|
|
456
570
|
return typeof value === "number" ? `${value}px` : value;
|
|
457
571
|
}
|
|
572
|
+
function getColumnId(column) {
|
|
573
|
+
return column.id ?? `hu-virtual-col-${column.key}`;
|
|
574
|
+
}
|
|
575
|
+
function getImportColumnId(column) {
|
|
576
|
+
return column.id ?? `hu-import-col-${column.key}`;
|
|
577
|
+
}
|
|
578
|
+
function getPivotFieldId(field, fallback) {
|
|
579
|
+
return field?.id ?? `hu-pivot-${fallback}`;
|
|
580
|
+
}
|
|
581
|
+
function getPivotColumnId(value) {
|
|
582
|
+
return `hu-pivot-col-${toDomId(value)}`;
|
|
583
|
+
}
|
|
584
|
+
function getPivotRowId(value) {
|
|
585
|
+
return `hu-pivot-row-${toDomId(value)}`;
|
|
586
|
+
}
|
|
587
|
+
function toDomId(value) {
|
|
588
|
+
return value.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "value";
|
|
589
|
+
}
|
|
458
590
|
function getSelectedExportColumns(columns, selectedColumns) {
|
|
459
591
|
if (selectedColumns) {
|
|
460
592
|
const selected = new Set(selectedColumns);
|