@heyramzi/ui 2.1.0 → 2.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/COMPONENTS.md +1 -1
- package/package.json +7 -8
- package/scripts/sync-shadcn.mjs +3 -4
- package/src/agents/agent-avatar.svelte +604 -575
- package/src/agents/agent-cast.svelte +179 -183
- package/src/agents/demo.svelte +125 -113
- package/src/agents/helpers.ts +23 -17
- package/src/agents/index.ts +4 -4
- package/src/agents/page-state.ts +6 -6
- package/src/agents/types.ts +6 -6
- package/src/ai/ai-button/AiButton.svelte +4 -12
- package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
- package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
- package/src/ai/response/Response.css +2 -3
- package/src/animations/avatar-group-hover.css +2 -16
- package/src/animations/card-tilt.css +3 -6
- package/src/animations/digit-pop-in.css +2 -15
- package/src/animations/dropdown.css +4 -17
- package/src/animations/input-clear-dissolve.css +5 -18
- package/src/animations/input-shake.css +9 -41
- package/src/animations/learn-more-hover.css +17 -9
- package/src/animations/like-button.css +2 -4
- package/src/animations/page-side-by-side.css +2 -10
- package/src/animations/plus-menu-morph.css +2 -5
- package/src/animations/shimmer-text.css +5 -18
- package/src/animations/spinning-counter.css +2 -6
- package/src/animations/success-check.css +2 -15
- package/src/animations/thinking-states.css +2 -7
- package/src/brand/brand.test.ts +21 -5
- package/src/brand/index.ts +4 -10
- package/src/canvas/card/card-frame.svelte +43 -39
- package/src/canvas/card/resize-handles.svelte +89 -80
- package/src/canvas/chrome/chrome-zoom.svelte +101 -100
- package/src/canvas/chrome/lock-badge.svelte +8 -8
- package/src/canvas/chrome/side-handles.svelte +24 -19
- package/src/canvas/chrome/size-chip.svelte +30 -30
- package/src/canvas/chrome/snap-guides.svelte +20 -20
- package/src/canvas/components/icons/ToolIcon.svelte +8 -8
- package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
- package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
- package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
- package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
- package/src/canvas/edges/bend-handle.svelte +46 -46
- package/src/canvas/edges/edge-gradient.svelte +10 -10
- package/src/canvas/edges/gradient-edge.svelte +45 -29
- package/src/canvas/editor/lock.test.ts +6 -1
- package/src/canvas/editor/style.ts +7 -1
- package/src/canvas/headless/canvas-cleanup.test.ts +1 -1
- package/src/canvas/headless/svg-figure.test.ts +5 -1
- package/src/canvas/lod/lod-switch.svelte +111 -106
- package/src/canvas/ops/ops.ts +1 -1
- package/src/canvas/pen/PenField.svelte +87 -81
- package/src/canvas/pen/PenOverlay.svelte +127 -127
- package/src/canvas/pen/ShapeNode.svelte +31 -32
- package/src/canvas/pen/TextNode.svelte +54 -53
- package/src/canvas/pen/host-field.test.svelte +7 -7
- package/src/canvas/pen/shapes.ts +7 -1
- package/src/canvas/screen/GlyphIcon.svelte +22 -17
- package/src/canvas/screen/PartView.svelte +138 -72
- package/src/canvas/screen/ScreenBody.svelte +81 -60
- package/src/canvas/screen/ScreenCard.svelte +20 -20
- package/src/canvas/ui/CanvasToolbar.svelte +173 -85
- package/src/canvas/ui/FrameLayer.svelte +59 -59
- package/src/canvas/ui/PenLayer.svelte +39 -30
- package/src/canvas/ui/StylePanel.svelte +119 -102
- package/src/canvas/ui/canvas-grid.svelte +137 -129
- package/src/charts/animated-value.svelte +1 -1
- package/src/charts/area-chart.css +2 -5
- package/src/charts/area-chart.svelte +1 -1
- package/src/charts/bar-chart.css +2 -3
- package/src/charts/bar-chart.svelte +1 -1
- package/src/charts/charts.css +5 -17
- package/src/charts/crosshair.svelte +1 -1
- package/src/charts/dashboard/dashboard-empty.svelte +25 -25
- package/src/charts/dashboard/dashboard-section.svelte +24 -24
- package/src/charts/dashboard/data-panel.svelte +50 -50
- package/src/charts/dashboard/data-sort-head.svelte +56 -56
- package/src/charts/dashboard/data-table.svelte +138 -138
- package/src/charts/dashboard/panel-grid.svelte +30 -30
- package/src/charts/dashboard/period-filter.svelte +25 -25
- package/src/charts/dashboard/rating-chip.svelte +19 -19
- package/src/charts/dashboard/stat-band.svelte +32 -32
- package/src/charts/dashboard/stat-tile.svelte +106 -99
- package/src/charts/donut-chart.svelte +1 -1
- package/src/charts/edge-fade.svelte +1 -1
- package/src/charts/figure.svelte +1 -1
- package/src/charts/gauge-chart.css +4 -7
- package/src/charts/gauge-chart.svelte +1 -1
- package/src/charts/hover-focus.svelte +1 -1
- package/src/charts/legend.css +1 -3
- package/src/charts/legend.svelte +1 -1
- package/src/charts/line-chart.svelte +1 -1
- package/src/charts/number-chart.css +6 -9
- package/src/charts/number-chart.svelte +1 -1
- package/src/charts/reference-line.svelte +19 -19
- package/src/charts/reveal.svelte +1 -1
- package/src/charts/ring-chart.css +2 -4
- package/src/charts/ring-chart.svelte +1 -1
- package/src/charts/sparkline.svelte +5 -2
- package/src/charts/stat-card.svelte +1 -1
- package/src/charts/time-series-chart.css +2 -5
- package/src/charts/trend-pill.css +2 -4
- package/src/charts/trend-pill.svelte +1 -1
- package/src/components/copy/copy-button.svelte +93 -98
- package/src/components/copy/copy-button.test.ts +10 -2
- package/src/components/data-table/infinite-scroll.svelte +42 -42
- package/src/components/data-table/pagination.svelte +34 -22
- package/src/components/data-table/row-arrow.svelte +19 -16
- package/src/components/data-table/row-context-menu.svelte +52 -52
- package/src/components/data-table/sortable-head.svelte +25 -25
- package/src/components/data-table/table-skeleton.svelte +41 -36
- package/src/components/docs/doc-page.svelte +18 -9
- package/src/components/docs/on-this-page.svelte +16 -3
- package/src/components/docs/scroll-spy.svelte.ts +2 -3
- package/src/components/field-row/field-row.svelte +10 -8
- package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
- package/src/components/icon/icon.svelte +14 -14
- package/src/components/language-flag/language-flag.svelte +3 -1
- package/src/components/list-page/detail-empty.svelte +34 -34
- package/src/components/list-page/detail-header.svelte +55 -47
- package/src/components/list-page/detail-panel.svelte +51 -59
- package/src/components/list-page/detail-row.svelte +19 -19
- package/src/components/list-page/detail-stack.svelte +21 -21
- package/src/components/list-page/empty-state.svelte +30 -30
- package/src/components/list-page/filter-item.svelte +20 -20
- package/src/components/list-page/filter-popover.svelte +60 -55
- package/src/components/list-page/grouped-list.svelte +4 -2
- package/src/components/list-page/index.ts +5 -1
- package/src/components/list-page/list-page-shell.svelte +2 -5
- package/src/components/list-page/master-detail.svelte +87 -88
- package/src/components/list-page/notice-bar.svelte +38 -38
- package/src/components/list-page/prev-next.svelte +63 -63
- package/src/components/list-page/rail-body.svelte +18 -18
- package/src/components/list-page/rail-empty.svelte +23 -30
- package/src/components/list-page/rail-head.svelte +24 -24
- package/src/components/list-page/rail-row.svelte +77 -73
- package/src/components/list-page/section-host.ts +2 -3
- package/src/components/locale-switch/locale-switch.svelte +41 -41
- package/src/components/mark/mark.svelte +34 -38
- package/src/components/open-in/index.ts +1 -5
- package/src/components/open-in/open-in.svelte +40 -40
- package/src/components/picker/mark-picker.svelte +49 -49
- package/src/components/picker/picker.svelte +5 -3
- package/src/components/section-header/section-header.test.ts +7 -1
- package/src/components/section-header/section-label.svelte +20 -20
- package/src/components/segmented/animated-tabs.svelte +31 -2
- package/src/components/segmented/collapse.test.ts +2 -1
- package/src/components/segmented/index.ts +2 -0
- package/src/components/segmented/segment.svelte +52 -49
- package/src/components/segmented/segmented-filter.svelte +79 -80
- package/src/components/segmented/segmented-nav.svelte +34 -40
- package/src/components/segmented/segmented-overflow.svelte +53 -50
- package/src/components/segmented/segmented-track.svelte +62 -46
- package/src/components/segmented/segmented.ts +2 -3
- package/src/components/star-rating-input/star-rating-input.svelte +103 -103
- package/src/components/svg/svg.svelte +4 -4
- package/src/components/table-view/index.ts +1 -1
- package/src/components/table-view/table-view.svelte +352 -13
- package/src/components/table-view/table-view.test.ts +108 -1
- package/src/components/table-view/types.ts +22 -11
- package/src/components/tags-input/index.ts +13 -0
- package/src/components/tags-input/tags-input.svelte +305 -0
- package/src/components/tags-input/tags.test.ts +64 -0
- package/src/components/tags-input/tags.ts +142 -0
- package/src/components/theme/index.ts +2 -4
- package/src/components/theme/theme-provider.svelte +1 -3
- package/src/components/toolbar/toolbar-search.svelte +25 -25
- package/src/components/toolbar/toolbar.svelte +11 -7
- package/src/components/toolbar/toolbar.test.ts +10 -1
- package/src/design/index.css +21 -78
- package/src/design/polish.css +26 -35
- package/src/editor/editor-toolbar.svelte +104 -99
- package/src/editor/editor.svelte +296 -266
- package/src/editor/image-toolbar.svelte +20 -22
- package/src/editor/index.ts +1 -4
- package/src/editor/inline-editor.svelte +280 -238
- package/src/editor/markdown.ts +15 -15
- package/src/editor/renderer.svelte +71 -71
- package/src/editor/slash-menu.svelte +24 -27
- package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
- package/src/effects/image-comparison/ImageComparison.svelte +11 -1
- package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
- package/src/effects/magic-card/MagicCard.css +14 -7
- package/src/effects/marquee/Marquee.svelte +3 -5
- package/src/effects/reveal/observe.ts +0 -3
- package/src/effects/spotlight/Spotlight.svelte +1 -1
- package/src/effects/theme-toggle/ThemeToggle.css +25 -22
- package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
- package/src/gallery.test.ts +14 -4
- package/src/icons/trades.ts +1 -1
- package/src/inbox/README.md +1 -3
- package/src/inbox/inbox.test.ts +181 -138
- package/src/inbox/index.ts +20 -20
- package/src/inbox/logic.ts +114 -111
- package/src/inbox/thread-inbox.svelte +227 -151
- package/src/inbox/types.ts +130 -108
- package/src/lib/theme-transition.ts +2 -4
- package/src/lib/tone.ts +2 -4
- package/src/lib/utils.ts +2 -2
- package/src/modules.test.ts +79 -69
- package/src/onboarding/define-tours.test.ts +55 -40
- package/src/onboarding/define-tours.ts +23 -23
- package/src/onboarding/demo.svelte +24 -5
- package/src/onboarding/index.ts +1 -8
- package/src/onboarding/onboarding-provider.svelte +19 -7
- package/src/onboarding/onboarding.svelte.ts +16 -16
- package/src/onboarding/pick.test.ts +61 -52
- package/src/onboarding/pick.ts +16 -13
- package/src/onboarding/stores.test.ts +80 -80
- package/src/onboarding/stores.ts +41 -39
- package/src/onboarding/tour-card.svelte +30 -5
- package/src/onboarding/tour-runner.svelte +12 -3
- package/src/onboarding/types.ts +20 -12
- package/src/onboarding/wait.ts +22 -22
- package/src/panel/side-panel.svelte +94 -93
- package/src/record/demo-data.ts +291 -225
- package/src/record/field-section.svelte +38 -34
- package/src/record/fields.ts +148 -96
- package/src/record/format.ts +63 -56
- package/src/record/index.ts +49 -35
- package/src/record/info-list.svelte +145 -114
- package/src/record/kinds.ts +31 -31
- package/src/record/logic.ts +216 -214
- package/src/record/next-up.svelte +45 -34
- package/src/record/project-list.svelte +56 -50
- package/src/record/record-demo.svelte +130 -110
- package/src/record/record-header.svelte +37 -28
- package/src/record/record-shell.svelte +54 -54
- package/src/record/record-stats.svelte +25 -25
- package/src/record/record-timeline.svelte +134 -114
- package/src/record/record.test.ts +430 -309
- package/src/record/steps-rail.svelte +225 -216
- package/src/record/types.ts +234 -215
- package/src/record/where-we-are.svelte +62 -58
- package/src/shadcn/button/button.svelte +1 -1
- package/src/shadcn/card/card-title.svelte +1 -5
- package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
- package/src/shadcn/sheet/sheet-content.svelte +2 -4
- package/src/shadcn/table/table-cell.svelte +2 -3
- package/src/shadcn/utilities.css +3 -8
- package/src/slides/Controls.svelte +150 -124
- package/src/slides/Deck.svelte +243 -238
- package/src/slides/Fragment.svelte +64 -57
- package/src/slides/Overview.svelte +106 -101
- package/src/slides/Presenter.svelte +300 -266
- package/src/slides/Slide.svelte +185 -170
- package/src/slides/SlideList.svelte +16 -12
- package/src/slides/ambient.d.ts +1 -1
- package/src/slides/bridges.ts +42 -23
- package/src/slides/deck-controller.svelte.ts +47 -35
- package/src/slides/demo-slides.svelte +330 -306
- package/src/slides/demo.svelte +164 -160
- package/src/slides/export/capture.ts +20 -16
- package/src/slides/export/ffmpeg.ts +43 -38
- package/src/slides/export/index.ts +5 -5
- package/src/slides/export/launch.ts +3 -1
- package/src/slides/export/pdf.ts +30 -27
- package/src/slides/export/types.ts +10 -8
- package/src/slides/index.ts +11 -11
- package/src/slides/labels.ts +22 -19
- package/src/slides/layout.ts +9 -2
- package/src/slides/model.ts +4 -4
- package/src/slides/navigation.ts +43 -31
- package/src/slides/theme.css +5 -7
- package/src/slides/types.ts +9 -10
- package/src/status/define-statuses.ts +17 -24
- package/src/status/index.ts +5 -5
- package/src/status/status-badge.svelte +3 -1
- package/src/status/status-dot.svelte +1 -3
- package/src/status/status-icon.svelte +1 -1
- package/src/status/status-types.ts +21 -21
- package/src/tasks/board-order.ts +2 -4
- package/src/tasks/demo.svelte +189 -31
- package/src/tasks/detail/detail-demo.svelte +73 -13
- package/src/tasks/detail/detail-types.ts +45 -45
- package/src/tasks/detail/task-detail.svelte +40 -10
- package/src/tasks/detail/task-property.svelte +8 -2
- package/src/tasks/detail/task-thread.svelte +53 -12
- package/src/tasks/gantt/gantt-bar.svelte +17 -6
- package/src/tasks/gantt/gantt-grid.svelte +40 -9
- package/src/tasks/gantt/gantt-group-row.svelte +8 -2
- package/src/tasks/gantt/gantt-header.svelte +9 -3
- package/src/tasks/gantt/gantt-task-row.svelte +26 -6
- package/src/tasks/gantt/gantt.css +2 -1
- package/src/tasks/gantt/gantt.test.ts +3 -1
- package/src/tasks/gantt/logic.test.ts +110 -20
- package/src/tasks/gantt/logic.ts +42 -6
- package/src/tasks/inbox/inbox-card.svelte +13 -5
- package/src/tasks/inbox/inbox-draft.svelte +1 -1
- package/src/tasks/inbox/inbox-logic.ts +6 -40
- package/src/tasks/inbox/inbox-statuses.ts +23 -23
- package/src/tasks/inbox/inbox-trace.svelte +9 -3
- package/src/tasks/inbox/inbox-types.ts +54 -58
- package/src/tasks/inbox/index.ts +22 -21
- package/src/tasks/index.ts +49 -52
- package/src/tasks/kanban-board.svelte +16 -12
- package/src/tasks/kanban-types.ts +2 -3
- package/src/tasks/presets/sales.ts +6 -6
- package/src/tasks/task-gantt.svelte +28 -8
- package/src/tasks/task-list.svelte +41 -17
- package/src/tasks/time/duration.ts +40 -40
- package/src/tasks/time/time-chip.svelte +9 -2
- package/src/tasks/time/time-dot.svelte +1 -1
- package/src/tasks/time/time-ring.svelte +11 -3
- package/src/tasks/time/time-tracker.svelte +57 -15
- package/src/tasks/time/time-types.ts +57 -57
- package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
- package/src/thread/index.ts +7 -7
- package/src/thread/logic.ts +36 -33
- package/src/thread/thread.svelte +82 -16
- package/src/thread/thread.test.ts +58 -47
- package/src/thread/types.ts +68 -68
- package/src/tokens/craft.css +17 -53
- package/src/tokens/index.css +3 -13
- package/src/tokens/palette.css +13 -11
- package/src/tokens/primitives.css +10 -23
- package/src/tasks/inbox/inbox.svelte +0 -203
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { TableColumn } from "./types.js";
|
|
4
4
|
|
|
5
5
|
export type TableCell<Row> = { row: Row; column: TableColumn<Row>; value: unknown; text: string };
|
|
6
|
+
export type TableGroup<Row> = { key: string; rows: Row[] };
|
|
6
7
|
export type { TableColumn };
|
|
7
8
|
</script>
|
|
8
9
|
|
|
@@ -10,8 +11,13 @@
|
|
|
10
11
|
// =========================
|
|
11
12
|
// IMPORTS
|
|
12
13
|
// =========================
|
|
14
|
+
import { CaretDown, CaretUp, CaretUpDown } from "phosphor-svelte";
|
|
13
15
|
import { cn } from "../../lib/utils.js";
|
|
14
16
|
import * as Table from "../../shadcn/table/index.js";
|
|
17
|
+
import * as ContextMenu from "../../shadcn/context-menu/index.js";
|
|
18
|
+
import { Checkbox } from "../../shadcn/checkbox/index.js";
|
|
19
|
+
import { TableSkeleton } from "../data-table/index.js";
|
|
20
|
+
import { toggleSort, type SortState } from "../data-table/data-table.js";
|
|
15
21
|
|
|
16
22
|
// =========================
|
|
17
23
|
// TYPES
|
|
@@ -30,13 +36,111 @@
|
|
|
30
36
|
rowClass?: (row: Row) => string | undefined;
|
|
31
37
|
/** Below this width the table scrolls sideways inside its frame. */
|
|
32
38
|
minWidth?: string;
|
|
39
|
+
/** No outer border or radius, for a table inside a card. */
|
|
40
|
+
bare?: boolean;
|
|
33
41
|
class?: string;
|
|
42
|
+
/** The sorted column and its direction. Bind it; columns with `sortable` set it. */
|
|
43
|
+
sort?: SortState<Row>;
|
|
44
|
+
/** Where a row goes. Draws a real link over the row, so middle-click and cmd-click work. */
|
|
45
|
+
rowHref?: (row: Row) => string | undefined;
|
|
46
|
+
/** The link's accessible name. Defaults to the row's first column text. */
|
|
47
|
+
rowLabel?: (row: Row) => string;
|
|
48
|
+
/** Adds a checkbox column with a select-all in the head. */
|
|
49
|
+
selectable?: boolean;
|
|
50
|
+
/** The `rowKey` of every checked row. Bind it. */
|
|
51
|
+
selected?: Set<string>;
|
|
52
|
+
selectAllLabel?: string;
|
|
53
|
+
selectRowLabel?: (row: Row) => string;
|
|
54
|
+
/** The items of a right-click menu for one row: `ContextMenu.Item`s. */
|
|
55
|
+
rowMenu?: Snippet<[Row]>;
|
|
56
|
+
/** Rows that return the same key sit under one header row. Order `rows` so a group is contiguous. */
|
|
57
|
+
group?: (row: Row) => string;
|
|
58
|
+
/** Draws a group's header. Prints the key when left out. */
|
|
59
|
+
groupHeader?: Snippet<[TableGroup<Row>]>;
|
|
60
|
+
/** Makes each group header a button that folds its rows. */
|
|
61
|
+
collapsible?: boolean;
|
|
62
|
+
/** The keys of the folded groups. Bind it. */
|
|
63
|
+
collapsed?: Set<string>;
|
|
64
|
+
/** Draws a column head its own way, an icon or a colour. Print `column.label` for the plain case. */
|
|
65
|
+
head?: Snippet<[TableColumn<Row>]>;
|
|
66
|
+
/** The row opens in place. Click or Enter on the row, unless the target is a link, button or input inside it. */
|
|
67
|
+
onRowClick?: (row: Row) => void;
|
|
68
|
+
/** Whether a row shows its `subRow`. */
|
|
69
|
+
expanded?: (row: Row) => boolean;
|
|
70
|
+
/** A full-width row under the row, for detail. */
|
|
71
|
+
subRow?: Snippet<[Row]>;
|
|
72
|
+
/** Drawn across every column when there are no rows. */
|
|
73
|
+
empty?: Snippet;
|
|
74
|
+
/** Drawn across every column under the last row: a load-more sentinel or a spinner. */
|
|
75
|
+
trailing?: Snippet;
|
|
76
|
+
/** Draws placeholder rows in the same frame instead of `rows`. */
|
|
77
|
+
loading?: boolean;
|
|
78
|
+
skeletonRows?: number;
|
|
34
79
|
}
|
|
35
80
|
|
|
36
81
|
// =========================
|
|
37
82
|
// PROPS
|
|
38
83
|
// =========================
|
|
39
|
-
let {
|
|
84
|
+
let {
|
|
85
|
+
columns,
|
|
86
|
+
rows,
|
|
87
|
+
rowKey,
|
|
88
|
+
label,
|
|
89
|
+
footer,
|
|
90
|
+
cell,
|
|
91
|
+
rowClass,
|
|
92
|
+
minWidth = "0",
|
|
93
|
+
bare = false,
|
|
94
|
+
class: className,
|
|
95
|
+
sort = $bindable(null),
|
|
96
|
+
rowHref,
|
|
97
|
+
rowLabel,
|
|
98
|
+
selectable = false,
|
|
99
|
+
selected = $bindable(new Set<string>()),
|
|
100
|
+
selectAllLabel = "Select all rows",
|
|
101
|
+
selectRowLabel,
|
|
102
|
+
rowMenu,
|
|
103
|
+
group,
|
|
104
|
+
groupHeader,
|
|
105
|
+
collapsible = false,
|
|
106
|
+
collapsed = $bindable(new Set<string>()),
|
|
107
|
+
head,
|
|
108
|
+
onRowClick,
|
|
109
|
+
expanded,
|
|
110
|
+
subRow,
|
|
111
|
+
empty,
|
|
112
|
+
trailing,
|
|
113
|
+
loading = false,
|
|
114
|
+
skeletonRows = 5,
|
|
115
|
+
}: Props = $props();
|
|
116
|
+
|
|
117
|
+
// =========================
|
|
118
|
+
// DERIVED
|
|
119
|
+
// =========================
|
|
120
|
+
let span = $derived(columns.length + (selectable ? 1 : 0));
|
|
121
|
+
let selectedCount = $derived(rows.filter((row) => selected.has(rowKey(row))).length);
|
|
122
|
+
let allSelected = $derived(rows.length > 0 && selectedCount === rows.length);
|
|
123
|
+
let someSelected = $derived(selectedCount > 0 && !allSelected);
|
|
124
|
+
let skeletonColumns = $derived([
|
|
125
|
+
...(selectable ? [{ width: "w-4" }] : []),
|
|
126
|
+
...columns.map((column) => ({
|
|
127
|
+
align: column.numeric ? ("right" as const) : ("left" as const),
|
|
128
|
+
})),
|
|
129
|
+
]);
|
|
130
|
+
let entries = $derived.by<{ group: TableGroup<Row> }[]>(() => {
|
|
131
|
+
if (!group) return [];
|
|
132
|
+
const out: { group: TableGroup<Row> }[] = [];
|
|
133
|
+
let current: TableGroup<Row> | undefined;
|
|
134
|
+
for (const row of rows) {
|
|
135
|
+
const groupKey = group(row);
|
|
136
|
+
if (!current || current.key !== groupKey) {
|
|
137
|
+
current = { key: groupKey, rows: [] };
|
|
138
|
+
out.push({ group: current });
|
|
139
|
+
}
|
|
140
|
+
current.rows.push(row);
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
});
|
|
40
144
|
|
|
41
145
|
// =========================
|
|
42
146
|
// FUNCTIONS
|
|
@@ -48,6 +152,61 @@
|
|
|
48
152
|
if (column.format) return column.format(value, row);
|
|
49
153
|
return value === null || value === undefined ? "" : String(value);
|
|
50
154
|
}
|
|
155
|
+
|
|
156
|
+
function ariaSort(column: TableColumn<Row>): "ascending" | "descending" | "none" | undefined {
|
|
157
|
+
if (!column.sortable) return undefined;
|
|
158
|
+
if (sort?.key !== column.key) return "none";
|
|
159
|
+
return sort.direction === "asc" ? "ascending" : "descending";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function toggleRow(row: Row, on: boolean) {
|
|
163
|
+
const next = new Set(selected);
|
|
164
|
+
if (on) next.add(rowKey(row));
|
|
165
|
+
else next.delete(rowKey(row));
|
|
166
|
+
selected = next;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function toggleAll(on: boolean) {
|
|
170
|
+
const next = new Set(selected);
|
|
171
|
+
for (const row of rows) {
|
|
172
|
+
if (on) next.add(rowKey(row));
|
|
173
|
+
else next.delete(rowKey(row));
|
|
174
|
+
}
|
|
175
|
+
selected = next;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const HIDE = {
|
|
179
|
+
sm: "hidden sm:table-cell",
|
|
180
|
+
md: "hidden md:table-cell",
|
|
181
|
+
lg: "hidden lg:table-cell",
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
function toggleGroup(groupKey: string) {
|
|
185
|
+
const next = new Set(collapsed);
|
|
186
|
+
if (next.has(groupKey)) next.delete(groupKey);
|
|
187
|
+
else next.add(groupKey);
|
|
188
|
+
collapsed = next;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// WHY the target check: a row can hold its own link, button or checkbox, and a click on one of those belongs to it.
|
|
192
|
+
const OWN_CLICK =
|
|
193
|
+
"a, button, input, select, textarea, label, [role=checkbox], [role=button], [role=menuitem]";
|
|
194
|
+
|
|
195
|
+
function rowClick(event: MouseEvent, row: Row) {
|
|
196
|
+
if ((event.target as Element).closest(OWN_CLICK)) return;
|
|
197
|
+
onRowClick?.(row);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function rowKeydown(event: KeyboardEvent, row: Row) {
|
|
201
|
+
if (event.target !== event.currentTarget) return;
|
|
202
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
onRowClick?.(row);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function linkName(row: Row): string {
|
|
208
|
+
return rowLabel?.(row) ?? (columns[0] ? textOf(columns[0], row) : label);
|
|
209
|
+
}
|
|
51
210
|
</script>
|
|
52
211
|
|
|
53
212
|
<!-- =========================== -->
|
|
@@ -60,10 +219,19 @@
|
|
|
60
219
|
"align-top",
|
|
61
220
|
column.numeric ? "text-end tabular-nums" : "whitespace-normal",
|
|
62
221
|
column.muted && "text-muted-foreground",
|
|
222
|
+
column.interactive && "relative z-10",
|
|
223
|
+
column.hideBelow && HIDE[column.hideBelow],
|
|
224
|
+
column.sticky && "bg-background sticky left-0 z-10",
|
|
225
|
+
column.class,
|
|
63
226
|
)}
|
|
64
227
|
>
|
|
65
228
|
{#if cell}
|
|
66
|
-
{@render cell({
|
|
229
|
+
{@render cell({
|
|
230
|
+
row,
|
|
231
|
+
column,
|
|
232
|
+
value: (row as Record<string, unknown>)[column.key],
|
|
233
|
+
text: textOf(column, row),
|
|
234
|
+
})}
|
|
67
235
|
{:else}
|
|
68
236
|
{textOf(column, row)}
|
|
69
237
|
{/if}
|
|
@@ -71,31 +239,202 @@
|
|
|
71
239
|
{/each}
|
|
72
240
|
{/snippet}
|
|
73
241
|
|
|
74
|
-
|
|
75
|
-
|
|
242
|
+
{#snippet rowOrMenu(row: Row)}
|
|
243
|
+
{#if rowMenu}
|
|
244
|
+
<ContextMenu.Root>
|
|
245
|
+
<ContextMenu.Trigger>
|
|
246
|
+
{#snippet child({ props })}
|
|
247
|
+
{@render dataRow(row, props)}
|
|
248
|
+
{/snippet}
|
|
249
|
+
</ContextMenu.Trigger>
|
|
250
|
+
<ContextMenu.Content class="w-48">
|
|
251
|
+
{@render rowMenu(row)}
|
|
252
|
+
</ContextMenu.Content>
|
|
253
|
+
</ContextMenu.Root>
|
|
254
|
+
{:else}
|
|
255
|
+
{@render dataRow(row, {})}
|
|
256
|
+
{/if}
|
|
257
|
+
{/snippet}
|
|
258
|
+
|
|
259
|
+
{#snippet dataRow(row: Row, triggerProps: Record<string, unknown>)}
|
|
260
|
+
{@const href = rowHref?.(row)}
|
|
261
|
+
{@const isSelected = selectable && selected.has(rowKey(row))}
|
|
262
|
+
<Table.Row
|
|
263
|
+
{...triggerProps}
|
|
264
|
+
data-state={isSelected ? "selected" : undefined}
|
|
265
|
+
tabindex={onRowClick ? 0 : undefined}
|
|
266
|
+
onclick={onRowClick ? (event: MouseEvent) => rowClick(event, row) : undefined}
|
|
267
|
+
onkeydown={onRowClick ? (event: KeyboardEvent) => rowKeydown(event, row) : undefined}
|
|
268
|
+
class={cn(
|
|
269
|
+
href && "relative t-press active:bg-muted/50",
|
|
270
|
+
onRowClick &&
|
|
271
|
+
"t-press active:bg-muted/50 focus-visible:ring-ring cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-inset",
|
|
272
|
+
rowClass?.(row),
|
|
273
|
+
)}
|
|
274
|
+
>
|
|
275
|
+
{#if selectable}
|
|
276
|
+
<Table.Cell class="relative z-10 w-10 align-top">
|
|
277
|
+
<Checkbox
|
|
278
|
+
checked={isSelected}
|
|
279
|
+
onCheckedChange={(on) => toggleRow(row, on === true)}
|
|
280
|
+
aria-label={selectRowLabel?.(row) ?? `Select ${linkName(row)}`}
|
|
281
|
+
/>
|
|
282
|
+
</Table.Cell>
|
|
283
|
+
{/if}
|
|
284
|
+
{@render body(row)}
|
|
285
|
+
{#if href}
|
|
286
|
+
<!-- WHY an empty anchor and not a wrapped cell: cells hold buttons and links of their own, and an anchor can't nest one. -->
|
|
287
|
+
<td class="p-0">
|
|
288
|
+
<a
|
|
289
|
+
{href}
|
|
290
|
+
aria-label={linkName(row)}
|
|
291
|
+
class="absolute inset-0 z-0 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
|
|
292
|
+
></a>
|
|
293
|
+
</td>
|
|
294
|
+
{/if}
|
|
295
|
+
</Table.Row>
|
|
296
|
+
{#if subRow && expanded?.(row)}
|
|
297
|
+
<Table.Row class="bg-muted/20 hover:bg-muted/20 hover:translate-y-0 hover:shadow-none">
|
|
298
|
+
<Table.Cell colspan={span + (rowHref ? 1 : 0)} class="whitespace-normal">
|
|
299
|
+
{@render subRow(row)}
|
|
300
|
+
</Table.Cell>
|
|
301
|
+
</Table.Row>
|
|
302
|
+
{/if}
|
|
303
|
+
{/snippet}
|
|
304
|
+
|
|
305
|
+
<div class={cn(!bare && "overflow-hidden rounded-xl border", className)}>
|
|
306
|
+
<Table.Root
|
|
307
|
+
aria-label={label}
|
|
308
|
+
aria-busy={loading || undefined}
|
|
309
|
+
style={`min-width: ${minWidth}`}
|
|
310
|
+
class="min-w-0"
|
|
311
|
+
>
|
|
76
312
|
<Table.Header>
|
|
77
313
|
<Table.Row>
|
|
314
|
+
{#if selectable}
|
|
315
|
+
<Table.Head class="w-10">
|
|
316
|
+
<Checkbox
|
|
317
|
+
checked={allSelected}
|
|
318
|
+
indeterminate={someSelected}
|
|
319
|
+
onCheckedChange={(on) => toggleAll(on === true)}
|
|
320
|
+
aria-label={selectAllLabel}
|
|
321
|
+
disabled={loading || rows.length === 0}
|
|
322
|
+
/>
|
|
323
|
+
</Table.Head>
|
|
324
|
+
{/if}
|
|
78
325
|
{#each columns as column (column.key)}
|
|
79
326
|
<Table.Head
|
|
80
|
-
class={cn(
|
|
327
|
+
class={cn(
|
|
328
|
+
"whitespace-normal",
|
|
329
|
+
column.numeric && "text-end",
|
|
330
|
+
column.sortable && "h-10 p-0",
|
|
331
|
+
column.hideBelow && HIDE[column.hideBelow],
|
|
332
|
+
column.sticky && "bg-background sticky left-0 z-20",
|
|
333
|
+
column.headClass ?? column.class,
|
|
334
|
+
)}
|
|
81
335
|
style={column.width ? `width: ${column.width}` : undefined}
|
|
336
|
+
aria-sort={ariaSort(column)}
|
|
82
337
|
>
|
|
83
|
-
{column.
|
|
338
|
+
{#if column.sortable}
|
|
339
|
+
{@const dir = sort?.key === column.key ? sort.direction : null}
|
|
340
|
+
<button
|
|
341
|
+
type="button"
|
|
342
|
+
class={cn(
|
|
343
|
+
"t-press hover:bg-muted/30 focus-visible:ring-ring flex h-10 w-full items-center gap-1 px-2 text-xs font-medium outline-none focus-visible:ring-2 focus-visible:ring-inset",
|
|
344
|
+
column.numeric ? "justify-end" : "justify-start",
|
|
345
|
+
)}
|
|
346
|
+
onclick={() => (sort = toggleSort(sort, column.key as keyof Row & string))}
|
|
347
|
+
>
|
|
348
|
+
{#if head}{@render head(column)}{:else}{column.label}{/if}
|
|
349
|
+
{#if dir === "asc"}
|
|
350
|
+
<CaretUp class="text-foreground size-3.5" weight="bold" aria-hidden="true" />
|
|
351
|
+
{:else if dir === "desc"}
|
|
352
|
+
<CaretDown class="text-foreground size-3.5" weight="bold" aria-hidden="true" />
|
|
353
|
+
{:else}
|
|
354
|
+
<CaretUpDown class="text-muted-foreground/75 size-3.5" aria-hidden="true" />
|
|
355
|
+
{/if}
|
|
356
|
+
</button>
|
|
357
|
+
{:else if head}
|
|
358
|
+
{@render head(column)}
|
|
359
|
+
{:else}
|
|
360
|
+
{column.label}
|
|
361
|
+
{/if}
|
|
84
362
|
</Table.Head>
|
|
85
363
|
{/each}
|
|
364
|
+
{#if rowHref}
|
|
365
|
+
<th class="w-0 p-0"><span class="sr-only">Open</span></th>
|
|
366
|
+
{/if}
|
|
86
367
|
</Table.Row>
|
|
87
368
|
</Table.Header>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
369
|
+
{#if loading}
|
|
370
|
+
<TableSkeleton rows={skeletonRows} columns={skeletonColumns} />
|
|
371
|
+
{:else}
|
|
372
|
+
{#if group}
|
|
373
|
+
{#each entries as entry (entry.group.key)}
|
|
374
|
+
{@const folded = collapsible && collapsed.has(entry.group.key)}
|
|
375
|
+
<Table.Body>
|
|
376
|
+
<Table.Row class="bg-muted/40 hover:bg-muted/40 hover:translate-y-0 hover:shadow-none">
|
|
377
|
+
<Table.Head
|
|
378
|
+
colspan={span + (rowHref ? 1 : 0)}
|
|
379
|
+
scope="colgroup"
|
|
380
|
+
class="text-foreground h-10 p-0"
|
|
381
|
+
>
|
|
382
|
+
{#if collapsible}
|
|
383
|
+
<button
|
|
384
|
+
type="button"
|
|
385
|
+
aria-expanded={!folded}
|
|
386
|
+
class="t-press focus-visible:ring-ring flex h-10 w-full items-center gap-2 px-2 text-start outline-none focus-visible:ring-2 focus-visible:ring-inset"
|
|
387
|
+
onclick={() => toggleGroup(entry.group.key)}
|
|
388
|
+
>
|
|
389
|
+
<CaretDown
|
|
390
|
+
class={cn(
|
|
391
|
+
"size-3.5 shrink-0 transition-transform duration-[var(--duration-quick)] ease-[var(--ease-smooth-out)] motion-reduce:transition-none",
|
|
392
|
+
folded && "-rotate-90",
|
|
393
|
+
)}
|
|
394
|
+
aria-hidden="true"
|
|
395
|
+
/>
|
|
396
|
+
{#if groupHeader}{@render groupHeader(entry.group)}{:else}{entry.group.key}{/if}
|
|
397
|
+
</button>
|
|
398
|
+
{:else}
|
|
399
|
+
<span class="flex h-10 items-center px-2">
|
|
400
|
+
{#if groupHeader}{@render groupHeader(entry.group)}{:else}{entry.group.key}{/if}
|
|
401
|
+
</span>
|
|
402
|
+
{/if}
|
|
403
|
+
</Table.Head>
|
|
404
|
+
</Table.Row>
|
|
405
|
+
{#if !folded}
|
|
406
|
+
{#each entry.group.rows as row (rowKey(row))}
|
|
407
|
+
{@render rowOrMenu(row)}
|
|
408
|
+
{/each}
|
|
409
|
+
{/if}
|
|
410
|
+
</Table.Body>
|
|
411
|
+
{/each}
|
|
412
|
+
{:else}
|
|
413
|
+
<Table.Body>
|
|
414
|
+
{#each rows as row (rowKey(row))}
|
|
415
|
+
{@render rowOrMenu(row)}
|
|
416
|
+
{/each}
|
|
417
|
+
{#if rows.length === 0 && empty}
|
|
418
|
+
<Table.Row class="hover:bg-transparent hover:translate-y-0 hover:shadow-none">
|
|
419
|
+
<Table.Cell colspan={span + (rowHref ? 1 : 0)} class="whitespace-normal"
|
|
420
|
+
>{@render empty()}</Table.Cell
|
|
421
|
+
>
|
|
422
|
+
</Table.Row>
|
|
423
|
+
{/if}
|
|
424
|
+
</Table.Body>
|
|
425
|
+
{/if}
|
|
426
|
+
{#if trailing}
|
|
427
|
+
<tbody>
|
|
428
|
+
<tr><td colspan={span + (rowHref ? 1 : 0)} class="p-0">{@render trailing()}</td></tr>
|
|
429
|
+
</tbody>
|
|
430
|
+
{/if}
|
|
431
|
+
{/if}
|
|
95
432
|
{#if footer}
|
|
96
433
|
<Table.Footer>
|
|
97
434
|
<Table.Row>
|
|
435
|
+
{#if selectable}<Table.Cell></Table.Cell>{/if}
|
|
98
436
|
{@render body(footer)}
|
|
437
|
+
{#if rowHref}<td></td>{/if}
|
|
99
438
|
</Table.Row>
|
|
100
439
|
</Table.Footer>
|
|
101
440
|
{/if}
|
|
@@ -25,7 +25,13 @@ function render(extra: Record<string, unknown> = {}) {
|
|
|
25
25
|
},
|
|
26
26
|
});
|
|
27
27
|
flushSync();
|
|
28
|
-
return {
|
|
28
|
+
return {
|
|
29
|
+
target,
|
|
30
|
+
done: () => {
|
|
31
|
+
void unmount(app);
|
|
32
|
+
target.remove();
|
|
33
|
+
},
|
|
34
|
+
};
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
describe("TableView", () => {
|
|
@@ -51,3 +57,104 @@ describe("TableView", () => {
|
|
|
51
57
|
done();
|
|
52
58
|
});
|
|
53
59
|
});
|
|
60
|
+
|
|
61
|
+
describe("TableView extensions", () => {
|
|
62
|
+
const sortable = [
|
|
63
|
+
{ key: "name", label: "Name", sortable: true },
|
|
64
|
+
{ key: "total", label: "Total", numeric: true, sortable: true },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
it("sorts from a head button and sets aria-sort", () => {
|
|
68
|
+
const { target, done } = render({ columns: sortable, sort: { key: "name", direction: "asc" } });
|
|
69
|
+
const heads = target.querySelectorAll("thead th");
|
|
70
|
+
expect(heads[0].getAttribute("aria-sort")).toBe("ascending");
|
|
71
|
+
expect(heads[1].getAttribute("aria-sort")).toBe("none");
|
|
72
|
+
(heads[1].querySelector("button") as HTMLButtonElement).click();
|
|
73
|
+
flushSync();
|
|
74
|
+
expect(target.querySelectorAll("thead th")[1].getAttribute("aria-sort")).toBe("ascending");
|
|
75
|
+
done();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("draws a real link per row", () => {
|
|
79
|
+
const { target, done } = render({ rowHref: (row: Row) => `/lines/${row.id}` });
|
|
80
|
+
const links = [...target.querySelectorAll("tbody a")].map((a) => a.getAttribute("href"));
|
|
81
|
+
expect(links).toEqual(["/lines/a", "/lines/b"]);
|
|
82
|
+
expect(target.querySelector("tbody a")?.getAttribute("aria-label")).toBe("Audit");
|
|
83
|
+
done();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("selects rows and shows select-all as indeterminate", () => {
|
|
87
|
+
const selected = new Set<string>(["a"]);
|
|
88
|
+
const { target, done } = render({ selectable: true, selected });
|
|
89
|
+
const boxes = target.querySelectorAll<HTMLElement>("[role=checkbox]");
|
|
90
|
+
expect(boxes.length).toBe(3);
|
|
91
|
+
expect(boxes[0].getAttribute("aria-checked")).toBe("mixed");
|
|
92
|
+
expect(target.querySelectorAll("tbody tr")[0].getAttribute("data-state")).toBe("selected");
|
|
93
|
+
done();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("puts a header row over each group, spanning every column", () => {
|
|
97
|
+
const { target, done } = render({ group: (row: Row) => (row.id === "a" ? "One" : "Two") });
|
|
98
|
+
const heads = target.querySelectorAll("tbody th[colspan]");
|
|
99
|
+
expect([...heads].map((h) => h.textContent?.trim())).toEqual(["One", "Two"]);
|
|
100
|
+
expect(heads[0].getAttribute("colspan")).toBe("2");
|
|
101
|
+
done();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("swaps rows for skeletons inside the same frame while loading", () => {
|
|
105
|
+
const { target, done } = render({ loading: true, skeletonRows: 3 });
|
|
106
|
+
expect(target.querySelector("table")?.getAttribute("aria-busy")).toBe("true");
|
|
107
|
+
expect(target.querySelectorAll("tbody tr").length).toBe(3);
|
|
108
|
+
expect(target.querySelector("tbody td")?.textContent?.trim()).toBe("");
|
|
109
|
+
done();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("drops the frame when bare", () => {
|
|
113
|
+
const { target, done } = render({ bare: true });
|
|
114
|
+
expect((target.firstElementChild as HTMLElement).className).not.toContain("border");
|
|
115
|
+
done();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("TableView row actions", () => {
|
|
120
|
+
it("opens a row on click and Enter, but leaves a nested button alone", () => {
|
|
121
|
+
const calls: string[] = [];
|
|
122
|
+
const cell = createRawSnippet<[{ text: string }]>((cellProps) => ({
|
|
123
|
+
render: () => `<span><button>${cellProps().text}</button></span>`,
|
|
124
|
+
}));
|
|
125
|
+
const { target, done } = render({ onRowClick: (row: Row) => calls.push(row.id), cell });
|
|
126
|
+
const tr = target.querySelector("tbody tr") as HTMLElement;
|
|
127
|
+
tr.click();
|
|
128
|
+
tr.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
|
|
129
|
+
(tr.querySelector("button") as HTMLButtonElement).click();
|
|
130
|
+
expect(calls).toEqual(["a", "a"]);
|
|
131
|
+
done();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("folds a group, hides a column below a breakpoint and shows a sub-row", () => {
|
|
135
|
+
const subRow = createRawSnippet(() => ({ render: () => `<i>detail</i>` }));
|
|
136
|
+
const { target, done } = render({
|
|
137
|
+
group: () => "All",
|
|
138
|
+
collapsible: true,
|
|
139
|
+
subRow,
|
|
140
|
+
expanded: (row: Row) => row.id === "a",
|
|
141
|
+
columns: [
|
|
142
|
+
{ key: "name", label: "Name" },
|
|
143
|
+
{ key: "total", label: "Total", hideBelow: "md" },
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
expect(target.querySelector("tbody i")).not.toBeNull();
|
|
147
|
+
expect(target.querySelector("thead th:last-child")?.className).toContain("md:table-cell");
|
|
148
|
+
(target.querySelector("tbody th button") as HTMLButtonElement).click();
|
|
149
|
+
flushSync();
|
|
150
|
+
expect(target.querySelectorAll("tbody tr").length).toBe(1);
|
|
151
|
+
done();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("draws the empty snippet across the columns", () => {
|
|
155
|
+
const empty = createRawSnippet(() => ({ render: () => `<p>Nothing</p>` }));
|
|
156
|
+
const { target, done } = render({ rows: [], empty });
|
|
157
|
+
expect(target.querySelector("tbody td")?.getAttribute("colspan")).toBe("2");
|
|
158
|
+
done();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
/** One column of a `TableView`. */
|
|
2
2
|
export interface TableColumn<Row> {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
/** The key of the value in the row. */
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
/** A number or an amount: right-aligned, tabular figures, never wraps. */
|
|
7
|
+
numeric?: boolean;
|
|
8
|
+
/** Turns the raw value into the text shown. A string value that has no `format` prints as it is. A money column passes its own currency formatter. */
|
|
9
|
+
format?: (value: unknown, row: Row) => string;
|
|
10
|
+
/** A column width such as `"30%"` or `"8rem"`. Leave it out and the table shares the rest. */
|
|
11
|
+
width?: string;
|
|
12
|
+
/** Draws the column in the muted colour. */
|
|
13
|
+
muted?: boolean;
|
|
14
|
+
/** Click or press Enter on the head to sort. The table sorts nothing itself: bind `sort` and order `rows` with `sortRows`. */
|
|
15
|
+
sortable?: boolean;
|
|
16
|
+
/** Lifts the column's cells above the row link, so a button or a link inside it takes its own click. */
|
|
17
|
+
interactive?: boolean;
|
|
18
|
+
/** Hidden below this breakpoint, head and cells both. */
|
|
19
|
+
hideBelow?: "sm" | "md" | "lg";
|
|
20
|
+
/** Pins the column to the left edge while the table scrolls sideways. */
|
|
21
|
+
sticky?: boolean;
|
|
22
|
+
/** Extra classes on the column's cells (and its head, unless `headClass` is set). */
|
|
23
|
+
class?: string;
|
|
24
|
+
headClass?: string;
|
|
14
25
|
}
|