@dumiflex/wp-kit 0.3.0 → 0.5.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/CHANGELOG.md +92 -51
- package/LICENSE +674 -674
- package/README.md +73 -76
- package/package.json +78 -77
- package/src/Avatar.vue +83 -83
- package/src/Badge.vue +97 -97
- package/src/Banner.vue +118 -118
- package/src/BookmarkButton.vue +27 -27
- package/src/BundleMeta.vue +51 -51
- package/src/Button.vue +103 -103
- package/src/Card.vue +68 -68
- package/src/Checkbox.vue +109 -107
- package/src/Chip.vue +163 -97
- package/src/ChoiceCards.vue +133 -133
- package/src/CloseButton.vue +69 -69
- package/src/CodeBlock.vue +146 -119
- package/src/ColorField.vue +188 -206
- package/src/ColorPicker.vue +201 -201
- package/src/Combobox.vue +331 -334
- package/src/ConfirmDialog.vue +165 -163
- package/src/ConfirmModal.vue +147 -147
- package/src/ConstraintMatrixPreview.vue +708 -708
- package/src/ContextMenu.vue +386 -379
- package/src/CopyButton.vue +156 -156
- package/src/DataTable.vue +293 -292
- package/src/DatePicker.vue +234 -234
- package/src/DescriptionList.vue +73 -73
- package/src/DiffView.vue +68 -68
- package/src/Disclosure.vue +166 -166
- package/src/DragHandle.vue +49 -49
- package/src/Drawer.vue +131 -131
- package/src/DropZone.vue +196 -196
- package/src/DropdownMenu.vue +72 -72
- package/src/EmptyState.vue +111 -111
- package/src/Field.vue +28 -28
- package/src/HsvPicker.vue +208 -208
- package/src/Icon.vue +39 -39
- package/src/IconPicker.vue +209 -209
- package/src/ImageGrid.vue +127 -125
- package/src/InlineEdit.vue +183 -183
- package/src/Input.vue +99 -99
- package/src/Kbd.vue +56 -56
- package/src/Lightbox.vue +176 -173
- package/src/ListFilter.vue +85 -85
- package/src/ListRow.vue +204 -204
- package/src/LoadingMark.vue +217 -161
- package/src/Logo.vue +60 -60
- package/src/Modal.vue +78 -76
- package/src/ModalShell.vue +109 -107
- package/src/Pagination.vue +106 -106
- package/src/Popover.vue +180 -171
- package/src/PostCard.vue +210 -211
- package/src/ProgressBar.vue +90 -90
- package/src/RadioGroup.vue +177 -177
- package/src/RangeSlider.vue +183 -183
- package/src/RelativeDate.vue +35 -35
- package/src/ResizeHandle.vue +143 -143
- package/src/SearchInput.vue +192 -199
- package/src/Select.vue +644 -641
- package/src/Separator.vue +54 -54
- package/src/Skeleton.vue +98 -82
- package/src/Slider.vue +198 -198
- package/src/SortableList.vue +260 -260
- package/src/SplitPane.vue +68 -68
- package/src/StarButton.vue +40 -40
- package/src/Stepper.vue +141 -141
- package/src/SupportLinks.vue +93 -93
- package/src/TagInput.vue +297 -300
- package/src/Textarea.vue +57 -57
- package/src/Timeline.vue +92 -92
- package/src/ToastCard.vue +119 -119
- package/src/ToastHost.vue +40 -40
- package/src/Toggle.vue +36 -36
- package/src/TokenizedValue.vue +310 -310
- package/src/Toolbar.vue +110 -110
- package/src/VirtualList.vue +87 -87
- package/src/VisuallyHidden.vue +29 -29
- package/src/WpButton.vue +75 -75
- package/src/WpCheck.vue +104 -102
- package/src/WpIconButton.vue +107 -107
- package/src/WpImageZoom.vue +544 -544
- package/src/WpNumber.vue +178 -183
- package/src/WpSegmented.vue +175 -175
- package/src/WpSelect.vue +93 -89
- package/src/WpSwitch.vue +163 -163
- package/src/WpTabs.vue +188 -188
- package/src/_modal-motion.css +33 -33
- package/src/_skeleton.css +50 -50
- package/src/axis-color.ts +42 -42
- package/src/button.css +169 -169
- package/src/calendar.ts +59 -59
- package/src/clipboard.ts +50 -50
- package/src/colour-presets.ts +12 -12
- package/src/diff.ts +51 -51
- package/src/drop-zone.ts +41 -41
- package/src/field.css +50 -0
- package/src/flip.ts +237 -237
- package/src/floating-layer.ts +48 -0
- package/src/floating.ts +99 -99
- package/src/highlight.ts +199 -0
- package/src/hsv.ts +100 -100
- package/src/icon-catalog.ts +52 -52
- package/src/image-zoom.ts +93 -93
- package/src/kind-colour.ts +28 -0
- package/src/loading-mark-motion.ts +249 -0
- package/src/logo-paths.ts +21 -21
- package/src/matrix-axis.ts +91 -91
- package/src/pagination.ts +27 -27
- package/src/platform-keys.ts +29 -29
- package/src/rich-tokenize.ts +357 -357
- package/src/select-types.ts +32 -32
- package/src/slider-math.ts +45 -45
- package/src/sortable.ts +22 -22
- package/src/theme/fonts/OFL-Fraunces.txt +93 -93
- package/src/theme/fonts/OFL-JetBrainsMono.txt +93 -93
- package/src/theme/fonts/OFL-Manrope.txt +93 -93
- package/src/theme/looks.css +428 -428
- package/src/theme/palettes.css +254 -254
- package/src/theme/tokens.css +2896 -2894
- package/src/tokens.ts +56 -56
- package/src/tooltip.css +43 -43
- package/src/tooltip.ts +146 -146
- package/src/useDialogFocus.ts +73 -73
- package/src/useGrowableField.ts +357 -357
- package/src/useModalKeyShield.ts +116 -116
- package/src/useToast.ts +48 -48
- package/src/wp-logo.svg +14 -14
- package/src/ChipToggle.vue +0 -92
- package/src/KindChip.vue +0 -53
- package/src/RatingBadge.vue +0 -42
package/src/DataTable.vue
CHANGED
|
@@ -1,292 +1,293 @@
|
|
|
1
|
-
<script setup lang="ts" generic="T extends object">
|
|
2
|
-
/**
|
|
3
|
-
* DataTable: rows and columns with a sticky header, sortable columns, row
|
|
4
|
-
* selection, a loading state and an empty state. Built on the `.wp-table`
|
|
5
|
-
* classes so it matches the list pages.
|
|
6
|
-
*
|
|
7
|
-
* <DataTable
|
|
8
|
-
* v-model:sort="sort" v-model:selected="picked"
|
|
9
|
-
* :columns="[{ key: 'name', label: 'Name', sortable: true }, { key: 'uses', label: 'Uses', align: 'right', sortable: true }]"
|
|
10
|
-
* :rows="rows" row-key="id" selectable aria-label="Tags"
|
|
11
|
-
* >
|
|
12
|
-
* <template #cell-name="{ row }"><Chip>{{ row.name }}</Chip></template>
|
|
13
|
-
* <template #empty><EmptyState … /></template>
|
|
14
|
-
* </DataTable>
|
|
15
|
-
*
|
|
16
|
-
* Sorting (APG sortable table): each sortable header holds a button, and only
|
|
17
|
-
* the sorted column carries `aria-sort`. The table sorts its rows itself
|
|
18
|
-
* (by `sortValue`, else the cell value); with `manual-sort` it only reports
|
|
19
|
-
* the sort and the parent passes rows in order.
|
|
20
|
-
*
|
|
21
|
-
* `row-clickable` makes each row open something (a trace, a detail panel):
|
|
22
|
-
* rows take focus, and a click or Enter emits `row-click`. `active-key`
|
|
23
|
-
* marks the row that is open.
|
|
24
|
-
*/
|
|
25
|
-
import { computed } from "vue";
|
|
26
|
-
import Checkbox from "./Checkbox.vue";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
width
|
|
35
|
-
|
|
36
|
-
mono
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
(e: "update:
|
|
89
|
-
(e: "
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
:
|
|
157
|
-
:aria-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<col v-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
:
|
|
170
|
-
:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
:
|
|
182
|
-
:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
<
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
:
|
|
221
|
-
:data-
|
|
222
|
-
:data-
|
|
223
|
-
:
|
|
224
|
-
|
|
225
|
-
@
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
:
|
|
239
|
-
:
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
</
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
.wp-data-table
|
|
253
|
-
.wp-data-table th[data-align="
|
|
254
|
-
.wp-data-table th[data-align="
|
|
255
|
-
.wp-data-table[data-
|
|
256
|
-
.wp-data-table[data-size="sm"] :deep(
|
|
257
|
-
.wp-data-
|
|
258
|
-
.wp-data-table__check { width: 40px;
|
|
259
|
-
.wp-data-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
border
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
.wp-data-table__sort:
|
|
276
|
-
.wp-data-table__sort
|
|
277
|
-
.wp-data-table__sort-
|
|
278
|
-
.wp-data-table__sort
|
|
279
|
-
.wp-data-
|
|
280
|
-
.wp-data-table tbody tr[data-
|
|
281
|
-
.wp-data-table tbody tr[data-clickable]
|
|
282
|
-
.wp-data-table tbody tr[data-
|
|
283
|
-
.wp-data-
|
|
284
|
-
.wp-data-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
1
|
+
<script setup lang="ts" generic="T extends object">
|
|
2
|
+
/**
|
|
3
|
+
* DataTable: rows and columns with a sticky header, sortable columns, row
|
|
4
|
+
* selection, a loading state and an empty state. Built on the `.wp-table`
|
|
5
|
+
* classes so it matches the list pages.
|
|
6
|
+
*
|
|
7
|
+
* <DataTable
|
|
8
|
+
* v-model:sort="sort" v-model:selected="picked"
|
|
9
|
+
* :columns="[{ key: 'name', label: 'Name', sortable: true }, { key: 'uses', label: 'Uses', align: 'right', sortable: true }]"
|
|
10
|
+
* :rows="rows" row-key="id" selectable aria-label="Tags"
|
|
11
|
+
* >
|
|
12
|
+
* <template #cell-name="{ row }"><Chip>{{ row.name }}</Chip></template>
|
|
13
|
+
* <template #empty><EmptyState … /></template>
|
|
14
|
+
* </DataTable>
|
|
15
|
+
*
|
|
16
|
+
* Sorting (APG sortable table): each sortable header holds a button, and only
|
|
17
|
+
* the sorted column carries `aria-sort`. The table sorts its rows itself
|
|
18
|
+
* (by `sortValue`, else the cell value); with `manual-sort` it only reports
|
|
19
|
+
* the sort and the parent passes rows in order.
|
|
20
|
+
*
|
|
21
|
+
* `row-clickable` makes each row open something (a trace, a detail panel):
|
|
22
|
+
* rows take focus, and a click or Enter emits `row-click`. `active-key`
|
|
23
|
+
* marks the row that is open.
|
|
24
|
+
*/
|
|
25
|
+
import { computed } from "vue";
|
|
26
|
+
import Checkbox from "./Checkbox.vue";
|
|
27
|
+
import Skeleton from "./Skeleton.vue";
|
|
28
|
+
|
|
29
|
+
export interface DataColumn<R> {
|
|
30
|
+
key: string;
|
|
31
|
+
label: string;
|
|
32
|
+
sortable?: boolean | undefined;
|
|
33
|
+
align?: "left" | "right" | "center" | undefined;
|
|
34
|
+
/** A CSS width for the column, e.g. `120px` or `30%`. */
|
|
35
|
+
width?: string | undefined;
|
|
36
|
+
/** Show the value in the mono font (counts, ids, seeds). */
|
|
37
|
+
mono?: boolean | undefined;
|
|
38
|
+
/** The value to sort by, when it isn't `row[key]`. */
|
|
39
|
+
sortValue?: (row: R) => string | number | null | undefined;
|
|
40
|
+
/** The direction a first click sorts in. */
|
|
41
|
+
firstDir?: "asc" | "desc" | undefined;
|
|
42
|
+
/** Hide the header text (an actions column); it still names the cells. */
|
|
43
|
+
hideLabel?: boolean | undefined;
|
|
44
|
+
/** A class on the column's header and cells. */
|
|
45
|
+
class?: string | undefined;
|
|
46
|
+
/** Extra attributes on the header cell (e.g. `data-ai-anchor`). */
|
|
47
|
+
headAttrs?: Record<string, string> | undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DataSort { key: string; dir: "asc" | "desc" }
|
|
51
|
+
|
|
52
|
+
const props = withDefaults(defineProps<{
|
|
53
|
+
columns: DataColumn<T>[];
|
|
54
|
+
rows: T[];
|
|
55
|
+
/** A field name, or a function, giving each row a stable key. */
|
|
56
|
+
rowKey: string | ((row: T) => string);
|
|
57
|
+
sort?: DataSort | null | undefined;
|
|
58
|
+
/** The parent sorts the rows; the table only reports clicks. */
|
|
59
|
+
manualSort?: boolean | undefined;
|
|
60
|
+
selectable?: boolean | undefined;
|
|
61
|
+
selected?: string[] | undefined;
|
|
62
|
+
loading?: boolean | undefined;
|
|
63
|
+
/** How many placeholder rows to draw while loading. */
|
|
64
|
+
loadingRows?: number | undefined;
|
|
65
|
+
stickyHead?: boolean | undefined;
|
|
66
|
+
/** Shown when there are no rows and no `#empty` slot. */
|
|
67
|
+
emptyText?: string | undefined;
|
|
68
|
+
ariaLabel?: string | undefined;
|
|
69
|
+
/** Extra attributes per row, e.g. `data-test`. */
|
|
70
|
+
rowAttrs?: ((row: T) => Record<string, string | undefined>) | undefined;
|
|
71
|
+
size?: "sm" | "md" | undefined;
|
|
72
|
+
/** Rows are focusable and emit `row-click` on click or Enter. */
|
|
73
|
+
rowClickable?: boolean | undefined;
|
|
74
|
+
/** The key of the row that is open, drawn highlighted. */
|
|
75
|
+
activeKey?: string | null | undefined;
|
|
76
|
+
}>(), {
|
|
77
|
+
sort: null,
|
|
78
|
+
selected: () => [],
|
|
79
|
+
loadingRows: 3,
|
|
80
|
+
stickyHead: true,
|
|
81
|
+
emptyText: "Nothing here yet.",
|
|
82
|
+
rowAttrs: undefined,
|
|
83
|
+
size: "md",
|
|
84
|
+
activeKey: null,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const emit = defineEmits<{
|
|
88
|
+
(e: "update:sort", s: DataSort | null): void;
|
|
89
|
+
(e: "update:selected", keys: string[]): void;
|
|
90
|
+
(e: "row-click", row: T): void;
|
|
91
|
+
}>();
|
|
92
|
+
|
|
93
|
+
const keyOf = (row: T): string =>
|
|
94
|
+
typeof props.rowKey === "function" ? props.rowKey(row) : String((row as Record<string, unknown>)[props.rowKey]);
|
|
95
|
+
|
|
96
|
+
const cell = (row: T, key: string): unknown => (row as Record<string, unknown>)[key];
|
|
97
|
+
|
|
98
|
+
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
|
|
99
|
+
|
|
100
|
+
const shown = computed<T[]>(() => {
|
|
101
|
+
const s = props.sort;
|
|
102
|
+
if (!s || props.manualSort) return props.rows;
|
|
103
|
+
const col = props.columns.find((c) => c.key === s.key);
|
|
104
|
+
if (!col) return props.rows;
|
|
105
|
+
const val = (r: T): unknown => (col.sortValue ? col.sortValue(r) : cell(r, col.key));
|
|
106
|
+
const sign = s.dir === "asc" ? 1 : -1;
|
|
107
|
+
return [...props.rows].sort((a, b) => {
|
|
108
|
+
const x = val(a);
|
|
109
|
+
const y = val(b);
|
|
110
|
+
// Blanks sort last whichever way the column goes.
|
|
111
|
+
const xb = x === null || x === undefined || x === "";
|
|
112
|
+
const yb = y === null || y === undefined || y === "";
|
|
113
|
+
if (xb || yb) return xb === yb ? 0 : xb ? 1 : -1;
|
|
114
|
+
if (typeof x === "number" && typeof y === "number") return (x - y) * sign;
|
|
115
|
+
return collator.compare(String(x), String(y)) * sign;
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
function ariaSort(c: DataColumn<T>): "ascending" | "descending" | undefined {
|
|
120
|
+
if (!c.sortable || props.sort?.key !== c.key) return undefined;
|
|
121
|
+
return props.sort.dir === "asc" ? "ascending" : "descending";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function sortBy(c: DataColumn<T>): void {
|
|
125
|
+
const now = props.sort;
|
|
126
|
+
const dir = now?.key === c.key ? (now.dir === "asc" ? "desc" : "asc") : (c.firstDir ?? "asc");
|
|
127
|
+
emit("update:sort", { key: c.key, dir });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const picked = computed(() => new Set(props.selected));
|
|
131
|
+
const allKeys = computed(() => shown.value.map(keyOf));
|
|
132
|
+
const allPicked = computed(() => allKeys.value.length > 0 && allKeys.value.every((k) => picked.value.has(k)));
|
|
133
|
+
const somePicked = computed(() => !allPicked.value && allKeys.value.some((k) => picked.value.has(k)));
|
|
134
|
+
|
|
135
|
+
function toggleAll(on: boolean): void {
|
|
136
|
+
const keys = new Set(props.selected);
|
|
137
|
+
for (const k of allKeys.value) {
|
|
138
|
+
if (on) keys.add(k);
|
|
139
|
+
else keys.delete(k);
|
|
140
|
+
}
|
|
141
|
+
emit("update:selected", [...keys]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function toggleRow(row: T, on: boolean): void {
|
|
145
|
+
const k = keyOf(row);
|
|
146
|
+
emit("update:selected", on ? [...props.selected.filter((x) => x !== k), k] : props.selected.filter((x) => x !== k));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const span = computed(() => props.columns.length + (props.selectable ? 1 : 0));
|
|
150
|
+
</script>
|
|
151
|
+
|
|
152
|
+
<template>
|
|
153
|
+
<div class="wp-table-wrap wp-data-table" :data-size="size">
|
|
154
|
+
<table
|
|
155
|
+
class="wp-table"
|
|
156
|
+
:class="{ 'wp-table--sticky-head': stickyHead }"
|
|
157
|
+
:aria-label="ariaLabel"
|
|
158
|
+
:aria-busy="loading || undefined"
|
|
159
|
+
>
|
|
160
|
+
<colgroup>
|
|
161
|
+
<col v-if="selectable" class="wp-data-table__check-col">
|
|
162
|
+
<col v-for="c in columns" :key="c.key" :style="c.width ? { width: c.width } : undefined">
|
|
163
|
+
</colgroup>
|
|
164
|
+
<thead>
|
|
165
|
+
<tr>
|
|
166
|
+
<th v-if="selectable" class="wp-data-table__check" scope="col">
|
|
167
|
+
<Checkbox
|
|
168
|
+
class="wp-checkbox--inline"
|
|
169
|
+
:model-value="allPicked"
|
|
170
|
+
:indeterminate="somePicked"
|
|
171
|
+
:disabled="loading || allKeys.length === 0"
|
|
172
|
+
aria-label="Select all rows"
|
|
173
|
+
@update:model-value="toggleAll"
|
|
174
|
+
/>
|
|
175
|
+
</th>
|
|
176
|
+
<th
|
|
177
|
+
v-for="c in columns"
|
|
178
|
+
:key="c.key"
|
|
179
|
+
scope="col"
|
|
180
|
+
v-bind="c.headAttrs ?? {}"
|
|
181
|
+
:class="c.class"
|
|
182
|
+
:aria-sort="ariaSort(c)"
|
|
183
|
+
:data-align="c.align"
|
|
184
|
+
>
|
|
185
|
+
<button
|
|
186
|
+
v-if="c.sortable"
|
|
187
|
+
type="button"
|
|
188
|
+
class="wp-data-table__sort"
|
|
189
|
+
:data-sorted="sort?.key === c.key ? sort.dir : undefined"
|
|
190
|
+
@click="sortBy(c)"
|
|
191
|
+
>
|
|
192
|
+
<slot :name="`head-${c.key}`" :column="c">{{ c.label }}</slot>
|
|
193
|
+
<i
|
|
194
|
+
class="pi wp-data-table__sort-icon"
|
|
195
|
+
:class="sort?.key === c.key ? (sort.dir === 'asc' ? 'pi-sort-up-fill' : 'pi-sort-down-fill') : 'pi-sort'"
|
|
196
|
+
aria-hidden="true"
|
|
197
|
+
/>
|
|
198
|
+
</button>
|
|
199
|
+
<span v-else-if="c.hideLabel" class="wp-data-table__sr">{{ c.label }}</span>
|
|
200
|
+
<slot v-else :name="`head-${c.key}`" :column="c">{{ c.label }}</slot>
|
|
201
|
+
</th>
|
|
202
|
+
</tr>
|
|
203
|
+
</thead>
|
|
204
|
+
<tbody>
|
|
205
|
+
<template v-if="loading && rows.length === 0">
|
|
206
|
+
<tr v-for="n in loadingRows" :key="`skel-${n}`" class="wp-data-table__loading" aria-hidden="true">
|
|
207
|
+
<td v-for="i in span" :key="i"><Skeleton :width="`${40 + ((n * 17 + i * 23) % 50)}%`" /></td>
|
|
208
|
+
</tr>
|
|
209
|
+
</template>
|
|
210
|
+
<tr v-else-if="shown.length === 0" class="wp-data-table__empty">
|
|
211
|
+
<td :colspan="span">
|
|
212
|
+
<slot name="empty"><span class="wp-dim">{{ emptyText }}</span></slot>
|
|
213
|
+
</td>
|
|
214
|
+
</tr>
|
|
215
|
+
<template v-else>
|
|
216
|
+
<tr
|
|
217
|
+
v-for="(row, index) in shown"
|
|
218
|
+
:key="keyOf(row)"
|
|
219
|
+
v-bind="rowAttrs ? rowAttrs(row) : {}"
|
|
220
|
+
:aria-selected="selectable ? picked.has(keyOf(row)) : undefined"
|
|
221
|
+
:data-selected="selectable && picked.has(keyOf(row)) ? 'true' : undefined"
|
|
222
|
+
:data-active="activeKey !== null && keyOf(row) === activeKey ? 'true' : undefined"
|
|
223
|
+
:data-clickable="rowClickable || undefined"
|
|
224
|
+
:tabindex="rowClickable ? 0 : undefined"
|
|
225
|
+
@click="rowClickable && emit('row-click', row)"
|
|
226
|
+
@keydown.enter="rowClickable && emit('row-click', row)"
|
|
227
|
+
>
|
|
228
|
+
<td v-if="selectable" class="wp-data-table__check" @click.stop>
|
|
229
|
+
<Checkbox
|
|
230
|
+
class="wp-checkbox--inline"
|
|
231
|
+
:model-value="picked.has(keyOf(row))"
|
|
232
|
+
aria-label="Select row"
|
|
233
|
+
@update:model-value="(on: boolean) => toggleRow(row, on)"
|
|
234
|
+
/>
|
|
235
|
+
</td>
|
|
236
|
+
<td
|
|
237
|
+
v-for="c in columns"
|
|
238
|
+
:key="c.key"
|
|
239
|
+
:data-align="c.align"
|
|
240
|
+
:class="[c.class, { 'wp-mono': c.mono }]"
|
|
241
|
+
>
|
|
242
|
+
<slot :name="`cell-${c.key}`" :row="row" :value="cell(row, c.key)" :index="index">{{ cell(row, c.key) ?? "" }}</slot>
|
|
243
|
+
</td>
|
|
244
|
+
</tr>
|
|
245
|
+
</template>
|
|
246
|
+
</tbody>
|
|
247
|
+
</table>
|
|
248
|
+
</div>
|
|
249
|
+
</template>
|
|
250
|
+
|
|
251
|
+
<style scoped>
|
|
252
|
+
.wp-data-table { overflow: auto; }
|
|
253
|
+
.wp-data-table th[data-align="right"], .wp-data-table td[data-align="right"] { text-align: right; }
|
|
254
|
+
.wp-data-table th[data-align="center"], .wp-data-table td[data-align="center"] { text-align: center; }
|
|
255
|
+
.wp-data-table th[data-align="right"] .wp-data-table__sort { flex-direction: row-reverse; }
|
|
256
|
+
.wp-data-table[data-size="sm"] :deep(thead th) { padding: 6px 10px; }
|
|
257
|
+
.wp-data-table[data-size="sm"] :deep(tbody td) { padding: 6px 10px; }
|
|
258
|
+
.wp-data-table__check-col { width: 40px; }
|
|
259
|
+
.wp-data-table__check { width: 40px; text-align: center; }
|
|
260
|
+
.wp-data-table__sort {
|
|
261
|
+
display: inline-flex;
|
|
262
|
+
align-items: center;
|
|
263
|
+
gap: 6px;
|
|
264
|
+
margin: -4px -6px;
|
|
265
|
+
padding: 4px 6px;
|
|
266
|
+
border: 0;
|
|
267
|
+
border-radius: var(--wp-radius-sm);
|
|
268
|
+
background: none;
|
|
269
|
+
color: inherit;
|
|
270
|
+
font: inherit;
|
|
271
|
+
letter-spacing: inherit;
|
|
272
|
+
text-transform: inherit;
|
|
273
|
+
cursor: pointer;
|
|
274
|
+
}
|
|
275
|
+
.wp-data-table__sort:hover { color: var(--wp-text); background: var(--wp-bg-3); }
|
|
276
|
+
.wp-data-table__sort:focus-visible { outline: none; box-shadow: var(--wp-focus-ring); }
|
|
277
|
+
.wp-data-table__sort[data-sorted] { color: var(--wp-text); }
|
|
278
|
+
.wp-data-table__sort-icon { font-size: 10px; opacity: 0.5; }
|
|
279
|
+
.wp-data-table__sort[data-sorted] .wp-data-table__sort-icon { opacity: 1; color: var(--wp-accent-text); }
|
|
280
|
+
.wp-data-table tbody tr[data-selected="true"] td { background: var(--wp-row-active, var(--wp-row-hover)); }
|
|
281
|
+
.wp-data-table tbody tr[data-clickable] { cursor: pointer; }
|
|
282
|
+
.wp-data-table tbody tr[data-clickable]:focus-visible { outline: 2px solid var(--wp-border-focus); outline-offset: -2px; }
|
|
283
|
+
.wp-data-table tbody tr[data-active="true"] td { background: color-mix(in oklab, var(--wp-accent-500) 16%, transparent); }
|
|
284
|
+
.wp-data-table__empty td { padding: 20px 12px; text-align: center; }
|
|
285
|
+
.wp-data-table__sr {
|
|
286
|
+
position: absolute;
|
|
287
|
+
width: 1px;
|
|
288
|
+
height: 1px;
|
|
289
|
+
overflow: hidden;
|
|
290
|
+
clip-path: inset(50%);
|
|
291
|
+
white-space: nowrap;
|
|
292
|
+
}
|
|
293
|
+
</style>
|