@digitalwalletcorp/vue-core 1.3.0 → 1.4.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 +36 -0
- package/lib/{components-2zQ2SZuY.js → components-B2jz-g6A.js} +1 -1
- package/lib/{components-HaiHcseF.cjs → components-COL2ZbhC.cjs} +1 -1
- package/lib/data-table.d.cts +101 -9
- package/lib/data-table.d.ts +101 -9
- package/lib/helpers/show-floating-component.d.cts +8 -4
- package/lib/helpers/show-floating-component.d.ts +8 -4
- package/lib/helpers/vue-core-options.d.cts +17 -0
- package/lib/helpers/vue-core-options.d.ts +17 -0
- package/lib/index.cjs +1 -1
- package/lib/index.d.cts +5 -2
- package/lib/index.d.ts +5 -2
- package/lib/index.js +79 -78
- package/lib/internal/data-table-controls.d.cts +2 -2
- package/lib/internal/data-table-controls.d.ts +2 -2
- package/lib/nuxt.cjs +1 -1
- package/lib/nuxt.js +1 -1
- package/lib/register.cjs +1 -1
- package/lib/register.js +1 -1
- package/lib/types/data-table.d.cts +1 -94
- package/lib/types/data-table.d.ts +1 -94
- package/lib/warning-popper.vue_vue_type_style_index_0_lang-4FIOsOY5.cjs +1 -0
- package/lib/warning-popper.vue_vue_type_style_index_0_lang-CgXoSjjp.js +1865 -0
- package/package.json +1 -1
- package/src/data-table.vue +210 -65
- package/src/helpers/show-floating-component.ts +22 -14
- package/src/helpers/vue-core-options.ts +20 -0
- package/src/index.ts +5 -2
- package/src/types/data-table.ts +1 -98
- package/lib/warning-popper.vue_vue_type_style_index_0_lang-CxVzqsbn.cjs +0 -1
- package/lib/warning-popper.vue_vue_type_style_index_0_lang-zt-jCsvz.js +0 -1833
package/package.json
CHANGED
package/src/data-table.vue
CHANGED
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
<template>
|
|
20
20
|
<section
|
|
21
21
|
class="dwui-data-table"
|
|
22
|
-
:class="{ 'dwui-selectable':
|
|
22
|
+
:class="{ 'dwui-selectable': settings.selectable }"
|
|
23
23
|
>
|
|
24
24
|
<DataTableControls
|
|
25
|
-
v-if="
|
|
25
|
+
v-if="settings.controls === 'both' || settings.controls === 'top'"
|
|
26
26
|
v-bind="controlProps"
|
|
27
|
-
:pagination="
|
|
27
|
+
:pagination="settings.pagination === 'both' || settings.pagination === 'top'"
|
|
28
28
|
@emit:change-page="emit('emit:changePage', $event)"
|
|
29
29
|
@emit:change-limit="emit('emit:changeLimit', $event)"
|
|
30
30
|
@emit:download-page="emit('emit:downloadPage')"
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
<div
|
|
34
34
|
ref="refContent"
|
|
35
35
|
class="dwui-data-table-content"
|
|
36
|
-
:class="[{ 'dwui-loading': props.loading },
|
|
37
|
-
:style="
|
|
36
|
+
:class="[{ 'dwui-loading': props.loading }, settings.contentClass]"
|
|
37
|
+
:style="settings.contentStyle"
|
|
38
38
|
tabindex="-1"
|
|
39
39
|
@keydown="onKeyDown"
|
|
40
40
|
>
|
|
41
41
|
<table
|
|
42
|
-
:class="
|
|
43
|
-
:style="
|
|
42
|
+
:class="settings.tableClass"
|
|
43
|
+
:style="settings.tableStyle"
|
|
44
44
|
>
|
|
45
45
|
<thead ref="refThead">
|
|
46
46
|
<tr>
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
<div class="dwui-data-table-header">
|
|
58
58
|
<span
|
|
59
59
|
class="dwui-data-table-sort"
|
|
60
|
-
:role="
|
|
61
|
-
:tabindex="
|
|
60
|
+
:role="settings.sortable ? 'button' : undefined"
|
|
61
|
+
:tabindex="settings.sortable ? 0 : undefined"
|
|
62
62
|
@click="onClickSort(header)"
|
|
63
63
|
@keydown.enter.prevent="onClickSort(header)"
|
|
64
64
|
@keydown.space.prevent="onClickSort(header)"
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
</span>
|
|
77
77
|
</div>
|
|
78
78
|
<div
|
|
79
|
-
v-if="
|
|
79
|
+
v-if="settings.resizable && index < innerHeaders.length - 1"
|
|
80
80
|
class="dwui-data-table-resizer"
|
|
81
81
|
@pointerdown="onResizeStart($event, index)"
|
|
82
82
|
@pointermove="onResizing"
|
|
@@ -89,13 +89,14 @@
|
|
|
89
89
|
<tbody ref="refTbody">
|
|
90
90
|
<template v-if="!props.loading">
|
|
91
91
|
<slot
|
|
92
|
+
:rows="rows"
|
|
92
93
|
:click-row="clickRow"
|
|
93
94
|
:dblclick-row="dblclickRow"
|
|
94
95
|
:row-number="rowNumber"
|
|
95
96
|
:selected-index="selectedIndex"
|
|
96
97
|
/>
|
|
97
98
|
<tr
|
|
98
|
-
v-if="props.dataList.length === 0 &&
|
|
99
|
+
v-if="props.dataList.length === 0 && settings.showNoData"
|
|
99
100
|
class="dwui-data-table-no-data"
|
|
100
101
|
>
|
|
101
102
|
<td :colspan="totalColSize">
|
|
@@ -115,9 +116,9 @@
|
|
|
115
116
|
</div>
|
|
116
117
|
</div>
|
|
117
118
|
<DataTableControls
|
|
118
|
-
v-if="
|
|
119
|
+
v-if="settings.controls === 'both' || settings.controls === 'bottom'"
|
|
119
120
|
v-bind="controlProps"
|
|
120
|
-
:pagination="
|
|
121
|
+
:pagination="settings.pagination === 'both' || settings.pagination === 'bottom'"
|
|
121
122
|
@emit:change-page="emit('emit:changePage', $event)"
|
|
122
123
|
@emit:change-limit="emit('emit:changeLimit', $event)"
|
|
123
124
|
@emit:download-page="emit('emit:downloadPage')"
|
|
@@ -127,32 +128,130 @@
|
|
|
127
128
|
</template>
|
|
128
129
|
|
|
129
130
|
<script setup lang="ts" generic="T">
|
|
130
|
-
import { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
|
|
131
|
-
import type { StyleValue } from 'vue';
|
|
131
|
+
import { computed, inject, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
|
|
132
|
+
import type { ClassValue, StyleValue } from 'vue';
|
|
132
133
|
import { SvgPointingTriangle } from '@digitalwalletcorp/vue-svg-icons';
|
|
133
134
|
import DataTableControls from '@/internal/data-table-controls.vue';
|
|
134
135
|
import { compareValues, getValueByPath, resolveLabel, stringifyValue } from '@/internal/data-table-value';
|
|
135
|
-
import
|
|
136
|
+
import { vueCoreOptionsKey } from '@/helpers/vue-core-options';
|
|
137
|
+
import type { DataTableOptions } from '@/helpers/vue-core-options';
|
|
138
|
+
import type { DataTableControlsPlacement, DataTableHeader, DataTableLabels, DataTableResolvedLabels, DataTableSortContext, DataTableSortMode } from '@/types/data-table';
|
|
136
139
|
|
|
137
|
-
|
|
140
|
+
/**
|
|
141
|
+
* DataTable の props
|
|
142
|
+
*/
|
|
143
|
+
export interface Props<T = unknown> {
|
|
144
|
+
/** 表示している行のデータ。スロットで描画する行と同じ順に並んでいること */
|
|
145
|
+
dataList: T[];
|
|
146
|
+
/** ヘッダ。id は一意であること */
|
|
147
|
+
headers: DataTableHeader<T>[];
|
|
148
|
+
/** 読み込み中。true の間は行を描画せず読み込み中の表示を出す */
|
|
149
|
+
loading?: boolean;
|
|
150
|
+
/** 総件数。未指定なら dataList の件数 */
|
|
151
|
+
totalCount?: number;
|
|
152
|
+
/** 現在ページ(1起点) */
|
|
153
|
+
current?: number;
|
|
154
|
+
/** 1ページあたりの表示件数 */
|
|
155
|
+
limit?: number;
|
|
156
|
+
/** 1ページあたりの表示件数の上限 */
|
|
157
|
+
maxLimit?: number;
|
|
158
|
+
/** ページネーションに並べるページ番号の最大数 */
|
|
159
|
+
maxPagination?: number;
|
|
160
|
+
/** 行をクリックで選択できるか */
|
|
161
|
+
selectable?: boolean;
|
|
162
|
+
/** コントロールを出す位置 */
|
|
163
|
+
controls?: DataTableControlsPlacement;
|
|
164
|
+
/** ページネーションを出す位置。controls が無い側には出ない */
|
|
165
|
+
pagination?: DataTableControlsPlacement;
|
|
166
|
+
/** ページダウンロードボタンを表示するか */
|
|
167
|
+
showDownloadPage?: boolean;
|
|
168
|
+
/** 全件ダウンロードボタンを表示するか */
|
|
169
|
+
showDownloadAll?: boolean;
|
|
170
|
+
/** 総件数を表示するか */
|
|
171
|
+
showTotal?: boolean;
|
|
172
|
+
/** 1ページあたりの表示件数の入力を表示するか */
|
|
173
|
+
showLimit?: boolean;
|
|
174
|
+
/** ページネーションにページ番号の選択を表示するか */
|
|
175
|
+
showPageSelect?: boolean;
|
|
176
|
+
/** dataList が空のときにデータが無い旨の行を表示するか */
|
|
177
|
+
showNoData?: boolean;
|
|
178
|
+
/** ヘッダの境界のドラッグで列幅を変えられるか */
|
|
179
|
+
resizable?: boolean;
|
|
180
|
+
/** ヘッダのクリックでソートできるか */
|
|
181
|
+
sortable?: boolean;
|
|
182
|
+
/** ソートの方式。未指定なら remote */
|
|
183
|
+
sortMode?: DataTableSortMode;
|
|
184
|
+
/** テーブルをスクロールする領域に付ける class */
|
|
185
|
+
contentClass?: ClassValue;
|
|
186
|
+
/** テーブルをスクロールする領域に付ける style */
|
|
187
|
+
contentStyle?: StyleValue;
|
|
188
|
+
/** table に付ける class */
|
|
189
|
+
tableClass?: ClassValue;
|
|
190
|
+
/** table に付ける style */
|
|
191
|
+
tableStyle?: StyleValue;
|
|
192
|
+
/** 表示する文言。未指定の項目は英語 */
|
|
193
|
+
labels?: DataTableLabels;
|
|
194
|
+
/** 総件数の整形。未指定なら en-US の桁区切り。SSR とクライアントで同じ文字列を返すこと */
|
|
195
|
+
formatTotalCount?: (count: number) => string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* DataTable が発行するイベント
|
|
200
|
+
*/
|
|
201
|
+
export interface Emits<T = unknown> {
|
|
202
|
+
/** 選択が変わった。クリックでの選択・解除、カーソルキーでの移動、読み込み完了時の解除で発行する。解除は undefined と -1 */
|
|
203
|
+
(e: 'emit:selectRow', value: T | undefined, index: number): void;
|
|
204
|
+
/** ソートの条件が変わった。sortMode に関わらず発行する */
|
|
205
|
+
(e: 'emit:changeSort', context: DataTableSortContext): void;
|
|
206
|
+
(e: 'emit:updateHeader', headers: DataTableHeader<T>[]): void;
|
|
207
|
+
(e: 'emit:dblclickRow', value: T, index: number): void;
|
|
208
|
+
(e: 'emit:changeLimit', limit: number): void;
|
|
209
|
+
(e: 'emit:changePage', page: number): void;
|
|
210
|
+
(e: 'emit:downloadPage'): void;
|
|
211
|
+
(e: 'emit:downloadAll'): void;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* DataTable のスロット
|
|
216
|
+
*/
|
|
217
|
+
export interface Slots<T = unknown> {
|
|
218
|
+
/** 行。tr を rows と同じ順に並べ、tr の click で clickRow(row, index) を呼ぶ */
|
|
219
|
+
default?(props: {
|
|
220
|
+
/** 表示する行。sortMode が page のときはソートした順、それ以外は dataList そのもの */
|
|
221
|
+
rows: T[];
|
|
222
|
+
/** 行を選択する。選択している行なら選択を解除する */
|
|
223
|
+
clickRow: (row: T, index: number) => void;
|
|
224
|
+
/** 行を選択してダブルクリックを通知する */
|
|
225
|
+
dblclickRow: (row: T, index: number) => void;
|
|
226
|
+
/** 行のインデックスから、ページをまたいだ1起点の行番号を返す */
|
|
227
|
+
rowNumber: (index: number) => number;
|
|
228
|
+
/** 選択している行のインデックス。選択していなければ -1 */
|
|
229
|
+
selectedIndex: number;
|
|
230
|
+
}): unknown;
|
|
231
|
+
/** 読み込み中の表示。未指定なら既定のスピナー */
|
|
232
|
+
loading?(): unknown;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 設定の props は未指定を undefined のまま受け、vueCoreOptionsKey の値とライブラリ既定で埋める(boolean が false にならないよう既定を明示する)
|
|
236
|
+
const props = withDefaults(defineProps<Props<T>>(), {
|
|
138
237
|
loading: false,
|
|
139
238
|
totalCount: undefined,
|
|
140
239
|
current: 1,
|
|
141
240
|
limit: 100,
|
|
142
|
-
maxLimit:
|
|
143
|
-
maxPagination:
|
|
144
|
-
selectable:
|
|
145
|
-
controls:
|
|
146
|
-
pagination:
|
|
147
|
-
showDownloadPage:
|
|
148
|
-
showDownloadAll:
|
|
149
|
-
showTotal:
|
|
150
|
-
showLimit:
|
|
151
|
-
showPageSelect:
|
|
152
|
-
showNoData:
|
|
153
|
-
resizable:
|
|
154
|
-
sortable:
|
|
155
|
-
sortMode:
|
|
241
|
+
maxLimit: undefined,
|
|
242
|
+
maxPagination: undefined,
|
|
243
|
+
selectable: undefined,
|
|
244
|
+
controls: undefined,
|
|
245
|
+
pagination: undefined,
|
|
246
|
+
showDownloadPage: undefined,
|
|
247
|
+
showDownloadAll: undefined,
|
|
248
|
+
showTotal: undefined,
|
|
249
|
+
showLimit: undefined,
|
|
250
|
+
showPageSelect: undefined,
|
|
251
|
+
showNoData: undefined,
|
|
252
|
+
resizable: undefined,
|
|
253
|
+
sortable: undefined,
|
|
254
|
+
sortMode: undefined,
|
|
156
255
|
formatTotalCount: undefined,
|
|
157
256
|
contentClass: undefined,
|
|
158
257
|
contentStyle: undefined,
|
|
@@ -161,9 +260,11 @@ const props = withDefaults(defineProps<DataTableProps<T>>(), {
|
|
|
161
260
|
labels: undefined
|
|
162
261
|
});
|
|
163
262
|
|
|
164
|
-
const emit = defineEmits<
|
|
263
|
+
const emit = defineEmits<Emits<T>>();
|
|
264
|
+
|
|
265
|
+
defineSlots<Slots<T>>();
|
|
165
266
|
|
|
166
|
-
|
|
267
|
+
const appOptions = inject(vueCoreOptionsKey, {});
|
|
167
268
|
|
|
168
269
|
const refContent = ref<HTMLDivElement>();
|
|
169
270
|
const refThead = ref<HTMLTableSectionElement>();
|
|
@@ -185,6 +286,45 @@ const innerHeaders = computed((): DataTableHeader<T>[] => {
|
|
|
185
286
|
return props.headers.map(header => widths.value[header.id] == null ? header : { ...header, width: widths.value[header.id] });
|
|
186
287
|
});
|
|
187
288
|
|
|
289
|
+
/** 設定のライブラリ既定。labels は項目ごとに合わせるため別に持つ */
|
|
290
|
+
const DEFAULT_SETTINGS: Omit<DataTableOptions, 'labels'> = {
|
|
291
|
+
maxLimit: 9999,
|
|
292
|
+
maxPagination: 7,
|
|
293
|
+
selectable: true,
|
|
294
|
+
controls: 'both',
|
|
295
|
+
pagination: 'both',
|
|
296
|
+
showDownloadPage: true,
|
|
297
|
+
showDownloadAll: true,
|
|
298
|
+
showTotal: true,
|
|
299
|
+
showLimit: true,
|
|
300
|
+
showPageSelect: false,
|
|
301
|
+
showNoData: true,
|
|
302
|
+
resizable: true,
|
|
303
|
+
sortable: true,
|
|
304
|
+
sortMode: 'remote',
|
|
305
|
+
contentClass: undefined,
|
|
306
|
+
contentStyle: undefined,
|
|
307
|
+
tableClass: undefined,
|
|
308
|
+
tableStyle: undefined,
|
|
309
|
+
formatTotalCount: undefined
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
type OptionalSettingKey = 'contentClass' | 'contentStyle' | 'tableClass' | 'tableStyle' | 'formatTotalCount';
|
|
313
|
+
type DataTableSettings = Required<Omit<DataTableOptions, 'labels' | OptionalSettingKey>> & Pick<DataTableOptions, OptionalSettingKey>;
|
|
314
|
+
|
|
315
|
+
/** 設定。props、vueCoreOptionsKey の dataTable、ライブラリ既定の順に、指定のあるものを使う */
|
|
316
|
+
const settings = computed((): DataTableSettings => {
|
|
317
|
+
const resolved = { ...DEFAULT_SETTINGS } as DataTableSettings;
|
|
318
|
+
for (const source of [appOptions.dataTable ?? {}, props]) {
|
|
319
|
+
for (const key of Object.keys(DEFAULT_SETTINGS) as (keyof typeof DEFAULT_SETTINGS)[]) {
|
|
320
|
+
if (source[key] !== undefined) {
|
|
321
|
+
Object.assign(resolved, { [key]: source[key] });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return resolved;
|
|
326
|
+
});
|
|
327
|
+
|
|
188
328
|
const labels = computed((): DataTableResolvedLabels => {
|
|
189
329
|
const merged: Required<DataTableLabels> = {
|
|
190
330
|
totalCount: 'Total',
|
|
@@ -200,6 +340,7 @@ const labels = computed((): DataTableResolvedLabels => {
|
|
|
200
340
|
nextPages: 'Next pages',
|
|
201
341
|
nextPage: 'Next page',
|
|
202
342
|
lastPage: 'Last page',
|
|
343
|
+
...appOptions.dataTable?.labels,
|
|
203
344
|
...props.labels
|
|
204
345
|
};
|
|
205
346
|
const resolved = {} as DataTableResolvedLabels;
|
|
@@ -211,22 +352,22 @@ const labels = computed((): DataTableResolvedLabels => {
|
|
|
211
352
|
});
|
|
212
353
|
|
|
213
354
|
const limit = computed((): number => {
|
|
214
|
-
return Math.min(props.limit,
|
|
355
|
+
return Math.min(props.limit, settings.value.maxLimit);
|
|
215
356
|
});
|
|
216
357
|
|
|
217
358
|
const controlProps = computed(() => {
|
|
218
359
|
return {
|
|
219
|
-
showTotal:
|
|
220
|
-
showLimit:
|
|
221
|
-
showPageSelect:
|
|
222
|
-
showDownloadPage:
|
|
223
|
-
showDownloadAll:
|
|
360
|
+
showTotal: settings.value.showTotal,
|
|
361
|
+
showLimit: settings.value.showLimit,
|
|
362
|
+
showPageSelect: settings.value.showPageSelect,
|
|
363
|
+
showDownloadPage: settings.value.showDownloadPage,
|
|
364
|
+
showDownloadAll: settings.value.showDownloadAll,
|
|
224
365
|
current: props.current,
|
|
225
366
|
limit: limit.value,
|
|
226
|
-
maxLimit:
|
|
227
|
-
maxPagination:
|
|
367
|
+
maxLimit: settings.value.maxLimit,
|
|
368
|
+
maxPagination: settings.value.maxPagination,
|
|
228
369
|
totalCount: props.totalCount ?? props.dataList.length,
|
|
229
|
-
formatTotalCount:
|
|
370
|
+
formatTotalCount: settings.value.formatTotalCount,
|
|
230
371
|
labels: labels.value
|
|
231
372
|
};
|
|
232
373
|
});
|
|
@@ -234,13 +375,26 @@ const controlProps = computed(() => {
|
|
|
234
375
|
/** 選択している行。行のオブジェクトが差し替わっても内容が同じなら選択を保てるよう、行そのものを持つ */
|
|
235
376
|
const selectedRow = shallowRef<T | undefined>(undefined);
|
|
236
377
|
|
|
237
|
-
/**
|
|
378
|
+
/** 表示する行。ページ内ソート中は dataList を並べ替えたコピー、それ以外は dataList そのもの */
|
|
379
|
+
const rows = computed((): T[] => {
|
|
380
|
+
const header = settings.value.sortMode === 'page' ? props.headers.find(header => header.id === sortKey.value) : undefined;
|
|
381
|
+
if (!header) {
|
|
382
|
+
return props.dataList;
|
|
383
|
+
}
|
|
384
|
+
const compare = header.compare ?? ((a: T, b: T): number => {
|
|
385
|
+
const key = header.sortKey ?? header.id;
|
|
386
|
+
return compareValues(getValueByPath(a, key), getValueByPath(b, key), header.type ?? 'string');
|
|
387
|
+
});
|
|
388
|
+
return [...props.dataList].sort((a, b) => descending.value ? compare(b, a) : compare(a, b));
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
/** 選択している行の、表示順でのインデックス。同じ参照が無ければ内容で探す */
|
|
238
392
|
const selectedIndex = computed((): number => {
|
|
239
393
|
const selected = selectedRow.value;
|
|
240
394
|
if (selected == null) {
|
|
241
395
|
return -1;
|
|
242
396
|
}
|
|
243
|
-
const index =
|
|
397
|
+
const index = rows.value.indexOf(selected);
|
|
244
398
|
if (index !== -1) {
|
|
245
399
|
return index;
|
|
246
400
|
}
|
|
@@ -248,7 +402,7 @@ const selectedIndex = computed((): number => {
|
|
|
248
402
|
if (json === '') {
|
|
249
403
|
return -1;
|
|
250
404
|
}
|
|
251
|
-
return
|
|
405
|
+
return rows.value.findIndex(row => stringifyValue(row) === json);
|
|
252
406
|
});
|
|
253
407
|
|
|
254
408
|
/** データが無い行の colspan。colspan を持つヘッダはその数で数える */
|
|
@@ -279,7 +433,7 @@ const ariaSort = (id: string): 'ascending' | 'descending' | undefined => {
|
|
|
279
433
|
};
|
|
280
434
|
|
|
281
435
|
const caretDirection = (id: string): 'up' | 'down' | undefined => {
|
|
282
|
-
if (!
|
|
436
|
+
if (!settings.value.sortable || sortKey.value !== id) {
|
|
283
437
|
return undefined;
|
|
284
438
|
}
|
|
285
439
|
return descending.value ? 'down' : 'up';
|
|
@@ -289,8 +443,8 @@ const rowNumber = (index: number): number => {
|
|
|
289
443
|
return (props.current - 1) * limit.value + index + 1;
|
|
290
444
|
};
|
|
291
445
|
|
|
292
|
-
/**
|
|
293
|
-
const
|
|
446
|
+
/** スロットが描画した行の要素。データが無い行は含めない */
|
|
447
|
+
const rowElements = (): HTMLTableRowElement[] => {
|
|
294
448
|
return Array.from(refTbody.value?.children ?? [])
|
|
295
449
|
.filter((el): el is HTMLTableRowElement => el.tagName === 'TR' && !el.classList.contains('dwui-data-table-no-data'));
|
|
296
450
|
};
|
|
@@ -308,7 +462,7 @@ const emitHeaders = (): void => {
|
|
|
308
462
|
* 行はスロットで描画されるため、描画後の DOM に対してここで反映する
|
|
309
463
|
*/
|
|
310
464
|
const syncRows = (): void => {
|
|
311
|
-
|
|
465
|
+
rowElements().forEach((tr, index) => tr.classList.toggle('dwui-selected', index === selectedIndex.value));
|
|
312
466
|
};
|
|
313
467
|
|
|
314
468
|
/**
|
|
@@ -316,7 +470,7 @@ const syncRows = (): void => {
|
|
|
316
470
|
*/
|
|
317
471
|
const scrollToSelectedRow = (): void => {
|
|
318
472
|
const pane = refContent.value;
|
|
319
|
-
const tr =
|
|
473
|
+
const tr = rowElements()[selectedIndex.value];
|
|
320
474
|
if (!pane || !tr) {
|
|
321
475
|
return;
|
|
322
476
|
}
|
|
@@ -342,7 +496,7 @@ const select = (row: T | undefined, index: number): void => {
|
|
|
342
496
|
* 行を選択する。選択している行なら選択を解除する
|
|
343
497
|
*/
|
|
344
498
|
const clickRow = (row: T, index: number): void => {
|
|
345
|
-
if (!
|
|
499
|
+
if (!settings.value.selectable) {
|
|
346
500
|
return;
|
|
347
501
|
}
|
|
348
502
|
if (index === selectedIndex.value) {
|
|
@@ -373,15 +527,15 @@ const onKeyDown = (event: KeyboardEvent): void => {
|
|
|
373
527
|
return;
|
|
374
528
|
}
|
|
375
529
|
const index = selectedIndex.value + (event.key === 'ArrowUp' ? -1 : 1);
|
|
376
|
-
if (index < 0 ||
|
|
530
|
+
if (index < 0 || rows.value.length <= index) {
|
|
377
531
|
return;
|
|
378
532
|
}
|
|
379
533
|
event.preventDefault();
|
|
380
|
-
select(
|
|
534
|
+
select(rows.value[index], index);
|
|
381
535
|
};
|
|
382
536
|
|
|
383
537
|
const onClickSort = (header: DataTableHeader<T>): void => {
|
|
384
|
-
if (!
|
|
538
|
+
if (!settings.value.sortable || props.dataList.length === 0) {
|
|
385
539
|
return;
|
|
386
540
|
}
|
|
387
541
|
descending.value = sortKey.value === header.id ? !descending.value : false;
|
|
@@ -390,22 +544,13 @@ const onClickSort = (header: DataTableHeader<T>): void => {
|
|
|
390
544
|
sortKey: sortKey.value,
|
|
391
545
|
descending: descending.value
|
|
392
546
|
});
|
|
393
|
-
if (props.sortMode !== 'page') {
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
const compare = header.compare ?? ((a: T, b: T): number => {
|
|
397
|
-
const key = header.sortKey ?? header.id;
|
|
398
|
-
return compareValues(getValueByPath(a, key), getValueByPath(b, key), header.type ?? 'string');
|
|
399
|
-
});
|
|
400
|
-
const sorted = [...props.dataList].sort((a, b) => descending.value ? compare(b, a) : compare(a, b));
|
|
401
|
-
emit('emit:sortData', sorted);
|
|
402
547
|
};
|
|
403
548
|
|
|
404
549
|
/**
|
|
405
550
|
* 列幅の変更を始める。すべての列の幅を今の表示幅で固定してから変更する
|
|
406
551
|
*/
|
|
407
552
|
const onResizeStart = (event: PointerEvent, index: number): void => {
|
|
408
|
-
if (!
|
|
553
|
+
if (!settings.value.resizable) {
|
|
409
554
|
return;
|
|
410
555
|
}
|
|
411
556
|
event.preventDefault();
|
|
@@ -473,7 +618,7 @@ watch(() => props.loading, (newValue, oldValue) => {
|
|
|
473
618
|
// 読み込みが終わったら、前のデータに対する選択は引き継がない。
|
|
474
619
|
// ページ内ソートも同様だが、remote のソート条件は再取得後も有効なので保つ
|
|
475
620
|
if (oldValue && !newValue) {
|
|
476
|
-
if (
|
|
621
|
+
if (settings.value.sortMode === 'page') {
|
|
477
622
|
sortKey.value = '';
|
|
478
623
|
descending.value = false;
|
|
479
624
|
}
|
|
@@ -49,6 +49,11 @@ export interface ShowFloatingComponentOptions {
|
|
|
49
49
|
onClose?: () => void;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export interface ShowFloatingMessageOptions extends ShowFloatingComponentOptions {
|
|
53
|
+
/** trueを指定した場合、文字列をHTMLとして表示する。オブジェクトには影響しない */
|
|
54
|
+
html?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
52
57
|
const openedHandles = new Set<FloatingComponentHandle>();
|
|
53
58
|
|
|
54
59
|
/**
|
|
@@ -88,8 +93,11 @@ const toMessageText = (message: string | object): string => {
|
|
|
88
93
|
return typeof message === 'string' ? message : JSON.stringify(message, null, 2);
|
|
89
94
|
};
|
|
90
95
|
|
|
91
|
-
const renderMessage = (
|
|
92
|
-
|
|
96
|
+
const renderMessage = (message: string | object, html: boolean | undefined) => (): VNode => {
|
|
97
|
+
if (html && typeof message === 'string') {
|
|
98
|
+
return h('div', { class: 'dwui-floating-component-message', innerHTML: message });
|
|
99
|
+
}
|
|
100
|
+
return h('div', { class: 'dwui-floating-component-message' }, toMessageText(message));
|
|
93
101
|
};
|
|
94
102
|
|
|
95
103
|
/**
|
|
@@ -108,19 +116,19 @@ export function showFloatingComponent(component: Component, props?: Record<strin
|
|
|
108
116
|
|
|
109
117
|
/**
|
|
110
118
|
* FloatingComponent を body の末尾に描画し、中身に文字列を表示する。
|
|
111
|
-
*
|
|
112
|
-
* 見出しのコピーボタンは、copyValue
|
|
119
|
+
* 文字列は改行や空白をそのまま表示し、html を指定すれば HTML として表示する。オブジェクトは整形した JSON で表示する。
|
|
120
|
+
* 見出しのコピーボタンは、copyValue を指定しなければ渡した文字列をコピーする。
|
|
113
121
|
* ブラウザでのみ動作する
|
|
114
122
|
*
|
|
115
123
|
* @param {string | object} message 表示する文字列またはオブジェクト
|
|
116
|
-
* @param {
|
|
124
|
+
* @param {ShowFloatingMessageOptions} [options]
|
|
117
125
|
* @returns {FloatingComponentHandle}
|
|
118
126
|
*/
|
|
119
|
-
export function showFloatingMessage(message: string | object, options?:
|
|
120
|
-
const
|
|
121
|
-
return mountFloatingComponent(renderMessage(
|
|
122
|
-
copyValue:
|
|
123
|
-
...
|
|
127
|
+
export function showFloatingMessage(message: string | object, options?: ShowFloatingMessageOptions): FloatingComponentHandle {
|
|
128
|
+
const { html, ...windowOptions } = options ?? {};
|
|
129
|
+
return mountFloatingComponent(renderMessage(message, html), {
|
|
130
|
+
copyValue: toMessageText(message),
|
|
131
|
+
...windowOptions
|
|
124
132
|
});
|
|
125
133
|
}
|
|
126
134
|
|
|
@@ -159,10 +167,10 @@ export function useFloatingComponent(): {
|
|
|
159
167
|
return owner.own((onClosed) => mountFloatingComponent(renderComponent(component, props), withOwner(options), onClosed));
|
|
160
168
|
},
|
|
161
169
|
showFloatingMessage: (message, options) => {
|
|
162
|
-
const
|
|
163
|
-
return owner.own((onClosed) => mountFloatingComponent(renderMessage(
|
|
164
|
-
copyValue:
|
|
165
|
-
...
|
|
170
|
+
const { html, ...windowOptions } = options ?? {};
|
|
171
|
+
return owner.own((onClosed) => mountFloatingComponent(renderMessage(message, html), withOwner({
|
|
172
|
+
copyValue: toMessageText(message),
|
|
173
|
+
...windowOptions
|
|
166
174
|
}), onClosed));
|
|
167
175
|
}
|
|
168
176
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { InjectionKey } from 'vue';
|
|
2
|
+
import type { Props } from '@/data-table.vue';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DataTable のアプリ既定の設定。vueCoreOptionsKey の dataTable で渡す。
|
|
6
|
+
* props のうちデータと表示の状態を除いた項目を持ち、DataTable の props に同じ名前の指定があればそちらが優先される
|
|
7
|
+
*/
|
|
8
|
+
export type DataTableOptions = Partial<Omit<Props, 'dataList' | 'headers' | 'loading' | 'totalCount' | 'current' | 'limit'>>;
|
|
9
|
+
|
|
10
|
+
/** アプリ全体の設定 */
|
|
11
|
+
export type VueCoreOptions = {
|
|
12
|
+
/** DataTable のアプリ既定の設定 */
|
|
13
|
+
dataTable?: DataTableOptions;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* アプリ全体の設定を provide/inject するためのキー
|
|
18
|
+
* provide は任意。画面の上位または app.provide で使う
|
|
19
|
+
*/
|
|
20
|
+
export const vueCoreOptionsKey: InjectionKey<VueCoreOptions> = Symbol('dwui-vue-core-options');
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from '@/components';
|
|
|
2
2
|
export type { Props as AccordionFieldsetProps, Slots as AccordionFieldsetSlots } from '@/accordion-fieldset.vue';
|
|
3
3
|
export type { Props as AccordionSectionProps, Slots as AccordionSectionSlots } from '@/accordion-section.vue';
|
|
4
4
|
export type { Props as CopyButtonProps, Emits as CopyButtonEmits, Slots as CopyButtonSlots } from '@/copy-button.vue';
|
|
5
|
+
export type { Props as DataTableProps, Emits as DataTableEmits, Slots as DataTableSlots } from '@/data-table.vue';
|
|
5
6
|
export type { Props as DarkModeSelectorProps, Emits as DarkModeSelectorEmits, Slots as DarkModeSelectorSlots } from '@/dark-mode-selector.vue';
|
|
6
7
|
export type { Props as HelpButtonProps, Emits as HelpButtonEmits, Slots as HelpButtonSlots } from '@/help-button.vue';
|
|
7
8
|
export type { Props as HelpPopperProps, Emits as HelpPopperEmits, Slots as HelpPopperSlots } from '@/help-popper.vue';
|
|
@@ -18,9 +19,9 @@ export type { Props as TabContentProps, Slots as TabContentSlots } from '@/tab-c
|
|
|
18
19
|
export type { Props as TabGroupProps, Slots as TabGroupSlots } from '@/tab-group.vue';
|
|
19
20
|
export type { Props as WarningPopperProps, Emits as WarningPopperEmits, Slots as WarningPopperSlots } from '@/warning-popper.vue';
|
|
20
21
|
export { headersToCsvColumns, rowToCsvColumns } from '@/helpers/data-table-csv';
|
|
21
|
-
export type { DataTableControlsPlacement, DataTableCsvColumn,
|
|
22
|
+
export type { DataTableControlsPlacement, DataTableCsvColumn, DataTableHeader, DataTableLabels, DataTableResolvedLabels, DataTableSortContext, DataTableSortMode, DataTableValueType } from '@/types/data-table';
|
|
22
23
|
export { closeAllFloatingComponents, showFloatingComponent, showFloatingMessage, useFloatingComponent } from '@/helpers/show-floating-component';
|
|
23
|
-
export type { FloatingComponentContentProps, FloatingComponentHandle, ShowFloatingComponentOptions } from '@/helpers/show-floating-component';
|
|
24
|
+
export type { FloatingComponentContentProps, FloatingComponentHandle, ShowFloatingComponentOptions, ShowFloatingMessageOptions } from '@/helpers/show-floating-component';
|
|
24
25
|
export { showConfirmationDialog, showErrorDialog, showInformationDialog, showModalDialog, showWarningDialog } from '@/helpers/show-modal-dialog';
|
|
25
26
|
export type { ShowModalDialogOptions } from '@/helpers/show-modal-dialog';
|
|
26
27
|
export type { ModalDialogButton, ModalDialogImage, ModalDialogResult, ModalDialogType } from '@/types/modal-dialog';
|
|
@@ -34,4 +35,6 @@ export type { SnackbarDirection, SnackbarPlacement } from '@/internal/snackbar-i
|
|
|
34
35
|
export type { LabelPreset } from '@/types/label-preset';
|
|
35
36
|
export type { MovableWindowContentProps, MovableWindowDragMode, MovableWindowHandle } from '@/types/movable-window';
|
|
36
37
|
export type { TabContentStatus } from '@/internal/tab-group-context';
|
|
38
|
+
export { vueCoreOptionsKey } from '@/helpers/vue-core-options';
|
|
39
|
+
export type { DataTableOptions, VueCoreOptions } from '@/helpers/vue-core-options';
|
|
37
40
|
export type { PopoverPlacement } from '@/internal/anchored-position';
|