@digitalwalletcorp/vue-core 1.2.0 → 1.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.
Files changed (188) hide show
  1. package/README.md +95 -8
  2. package/lib/accordion-fieldset.d.cts +14 -4
  3. package/lib/accordion-fieldset.d.ts +14 -4
  4. package/lib/accordion-section.d.cts +20 -10
  5. package/lib/accordion-section.d.ts +20 -10
  6. package/lib/components-2zQ2SZuY.js +26 -0
  7. package/lib/components-HaiHcseF.cjs +1 -0
  8. package/lib/components.d.cts +10 -0
  9. package/lib/components.d.ts +10 -0
  10. package/lib/copy-button.d.cts +34 -0
  11. package/lib/copy-button.d.ts +34 -0
  12. package/lib/dark-mode-selector.d.cts +32 -0
  13. package/lib/dark-mode-selector.d.ts +32 -0
  14. package/lib/data-table.d.cts +30 -0
  15. package/lib/data-table.d.ts +30 -0
  16. package/lib/help-button.d.cts +39 -0
  17. package/lib/help-button.d.ts +39 -0
  18. package/lib/help-popper.d.cts +39 -0
  19. package/lib/help-popper.d.ts +39 -0
  20. package/lib/helpers/data-table-csv.d.cts +17 -0
  21. package/lib/helpers/data-table-csv.d.ts +17 -0
  22. package/lib/helpers/show-floating-component.d.cts +82 -0
  23. package/lib/helpers/show-floating-component.d.ts +82 -0
  24. package/lib/helpers/show-modal-dialog.d.cts +73 -0
  25. package/lib/helpers/show-modal-dialog.d.ts +73 -0
  26. package/lib/helpers/show-overlay-box.d.cts +53 -0
  27. package/lib/helpers/show-overlay-box.d.ts +53 -0
  28. package/lib/helpers/show-screen-loading.d.cts +20 -0
  29. package/lib/helpers/show-screen-loading.d.ts +20 -0
  30. package/lib/helpers/show-snackbar.d.cts +47 -0
  31. package/lib/helpers/show-snackbar.d.ts +47 -0
  32. package/lib/hourglass-loading.d.cts +1 -1
  33. package/lib/hourglass-loading.d.ts +1 -1
  34. package/lib/index.cjs +1 -1
  35. package/lib/index.d.cts +34 -0
  36. package/lib/index.d.ts +34 -0
  37. package/lib/index.js +445 -18
  38. package/lib/internal/anchored-popover.d.cts +35 -0
  39. package/lib/internal/anchored-popover.d.ts +35 -0
  40. package/lib/internal/anchored-position.d.cts +50 -0
  41. package/lib/internal/anchored-position.d.ts +50 -0
  42. package/lib/internal/data-table-controls.d.cts +42 -0
  43. package/lib/internal/data-table-controls.d.ts +42 -0
  44. package/lib/internal/data-table-value.d.cts +32 -0
  45. package/lib/internal/data-table-value.d.ts +32 -0
  46. package/lib/internal/floating-component.d.cts +52 -0
  47. package/lib/internal/floating-component.d.ts +52 -0
  48. package/lib/internal/icon-popper.d.cts +32 -0
  49. package/lib/internal/icon-popper.d.ts +32 -0
  50. package/lib/internal/mount-window.d.cts +39 -0
  51. package/lib/internal/mount-window.d.ts +39 -0
  52. package/lib/internal/movable-window.d.cts +55 -0
  53. package/lib/internal/movable-window.d.ts +55 -0
  54. package/lib/internal/opened-overlay-boxes.d.cts +2 -0
  55. package/lib/internal/opened-overlay-boxes.d.ts +2 -0
  56. package/lib/internal/pagination.d.cts +22 -0
  57. package/lib/internal/pagination.d.ts +22 -0
  58. package/lib/internal/snackbar-container.d.cts +20 -0
  59. package/lib/internal/snackbar-container.d.ts +20 -0
  60. package/lib/internal/snackbar-item.d.cts +17 -0
  61. package/lib/internal/snackbar-item.d.ts +17 -0
  62. package/lib/internal/tab-group-context.d.cts +4 -2
  63. package/lib/internal/tab-group-context.d.ts +4 -2
  64. package/lib/internal/use-movable-window.d.cts +44 -0
  65. package/lib/internal/use-movable-window.d.ts +44 -0
  66. package/lib/internal/window-order.d.cts +6 -0
  67. package/lib/internal/window-order.d.ts +6 -0
  68. package/lib/intersection-lazy-component.d.cts +32 -0
  69. package/lib/intersection-lazy-component.d.ts +32 -0
  70. package/lib/label-button.d.cts +2 -2
  71. package/lib/label-button.d.ts +2 -2
  72. package/lib/label-icon.d.cts +2 -2
  73. package/lib/label-icon.d.ts +2 -2
  74. package/lib/modal-dialog.d.cts +67 -0
  75. package/lib/modal-dialog.d.ts +67 -0
  76. package/lib/nuxt.cjs +1 -1
  77. package/lib/nuxt.js +1 -1
  78. package/lib/overlay-box.d.cts +52 -0
  79. package/lib/overlay-box.d.ts +52 -0
  80. package/lib/page-section.d.cts +2 -2
  81. package/lib/page-section.d.ts +2 -2
  82. package/lib/page-title.d.cts +2 -2
  83. package/lib/page-title.d.ts +2 -2
  84. package/lib/register.cjs +1 -1
  85. package/lib/register.js +1 -1
  86. package/lib/screen-loading.d.cts +7 -0
  87. package/lib/screen-loading.d.ts +7 -0
  88. package/lib/style.css +1 -1
  89. package/lib/tab-content.d.cts +5 -5
  90. package/lib/tab-content.d.ts +5 -5
  91. package/lib/tab-group.d.cts +7 -10
  92. package/lib/tab-group.d.ts +7 -10
  93. package/lib/types/data-table.d.cts +185 -0
  94. package/lib/types/data-table.d.ts +185 -0
  95. package/lib/types/modal-dialog.d.cts +13 -0
  96. package/lib/types/modal-dialog.d.ts +13 -0
  97. package/lib/types/movable-window.d.cts +16 -0
  98. package/lib/types/movable-window.d.ts +16 -0
  99. package/lib/warning-popper.d.cts +39 -0
  100. package/lib/warning-popper.d.ts +39 -0
  101. package/lib/warning-popper.vue_vue_type_style_index_0_lang-CxVzqsbn.cjs +1 -0
  102. package/lib/warning-popper.vue_vue_type_style_index_0_lang-zt-jCsvz.js +1833 -0
  103. package/package.json +43 -16
  104. package/src/accordion-fieldset.vue +29 -19
  105. package/src/accordion-section.vue +41 -26
  106. package/src/components.ts +10 -0
  107. package/src/copy-button.vue +89 -0
  108. package/src/dark-mode-selector.vue +212 -0
  109. package/src/data-table.vue +643 -0
  110. package/src/help-button.vue +128 -0
  111. package/src/help-popper.vue +70 -0
  112. package/src/helpers/data-table-csv.ts +55 -0
  113. package/src/helpers/show-floating-component.ts +169 -0
  114. package/src/helpers/show-modal-dialog.ts +121 -0
  115. package/src/helpers/show-overlay-box.ts +106 -0
  116. package/src/helpers/show-screen-loading.ts +37 -0
  117. package/src/helpers/show-snackbar.ts +153 -0
  118. package/src/hourglass-loading.vue +1 -1
  119. package/src/index.ts +34 -0
  120. package/src/internal/anchored-popover.vue +192 -0
  121. package/src/internal/anchored-position.ts +135 -0
  122. package/src/internal/data-table-controls.vue +203 -0
  123. package/src/internal/data-table-value.ts +99 -0
  124. package/src/internal/floating-component.vue +202 -0
  125. package/src/internal/icon-popper.vue +185 -0
  126. package/src/internal/mount-window.ts +110 -0
  127. package/src/internal/movable-window.vue +315 -0
  128. package/src/internal/opened-overlay-boxes.ts +2 -0
  129. package/src/internal/pagination.vue +325 -0
  130. package/src/internal/snackbar-container.vue +258 -0
  131. package/src/internal/snackbar-item.ts +20 -0
  132. package/src/internal/tab-group-context.ts +4 -2
  133. package/src/internal/use-movable-window.ts +208 -0
  134. package/src/internal/window-order.ts +11 -0
  135. package/src/intersection-lazy-component.vue +84 -0
  136. package/src/label-button.vue +37 -3
  137. package/src/label-icon.vue +2 -2
  138. package/src/modal-dialog.vue +287 -0
  139. package/src/overlay-box.vue +218 -0
  140. package/src/page-section.vue +7 -7
  141. package/src/page-title.vue +5 -5
  142. package/src/screen-loading.vue +90 -0
  143. package/src/tab-content.vue +17 -16
  144. package/src/tab-group.vue +55 -46
  145. package/src/types/data-table.ts +196 -0
  146. package/src/types/modal-dialog.ts +16 -0
  147. package/src/types/movable-window.ts +18 -0
  148. package/src/warning-popper.vue +70 -0
  149. package/lib/accordion-fieldset.cjs +0 -1
  150. package/lib/accordion-fieldset.js +0 -4
  151. package/lib/accordion-fieldset.vue_vue_type_style_index_0_lang-n-IZxUiu.js +0 -66
  152. package/lib/accordion-fieldset.vue_vue_type_style_index_0_lang-t9NqmoZW.cjs +0 -1
  153. package/lib/accordion-section.cjs +0 -1
  154. package/lib/accordion-section.js +0 -4
  155. package/lib/accordion-section.vue_vue_type_style_index_0_lang-CFDdUEnn.cjs +0 -1
  156. package/lib/accordion-section.vue_vue_type_style_index_0_lang-Gvjjx02P.js +0 -78
  157. package/lib/components-CNOeuZPu.cjs +0 -1
  158. package/lib/components-DBK8NUBh.js +0 -24
  159. package/lib/hourglass-loading.cjs +0 -1
  160. package/lib/hourglass-loading.js +0 -4
  161. package/lib/hourglass-loading.vue_vue_type_style_index_0_lang-C72UVA9W.cjs +0 -1
  162. package/lib/hourglass-loading.vue_vue_type_style_index_0_lang-DkUGIxQ2.js +0 -22
  163. package/lib/label-button.cjs +0 -1
  164. package/lib/label-button.js +0 -4
  165. package/lib/label-button.vue_vue_type_script_setup_true_lang-BwVOxaJP.cjs +0 -1
  166. package/lib/label-button.vue_vue_type_script_setup_true_lang-DorC4pKz.js +0 -44
  167. package/lib/label-icon.cjs +0 -1
  168. package/lib/label-icon.js +0 -4
  169. package/lib/label-icon.vue_vue_type_style_index_0_lang-D9Cimu20.cjs +0 -1
  170. package/lib/label-icon.vue_vue_type_style_index_0_lang-me52HE2V.js +0 -38
  171. package/lib/page-section.cjs +0 -1
  172. package/lib/page-section.js +0 -4
  173. package/lib/page-section.vue_vue_type_style_index_0_lang-DEAkTLxd.cjs +0 -1
  174. package/lib/page-section.vue_vue_type_style_index_0_lang-DpVQe_rV.js +0 -54
  175. package/lib/page-title.cjs +0 -1
  176. package/lib/page-title.js +0 -4
  177. package/lib/page-title.vue_vue_type_style_index_0_lang-BqIsIks5.cjs +0 -1
  178. package/lib/page-title.vue_vue_type_style_index_0_lang-ChZot85K.js +0 -36
  179. package/lib/tab-content.cjs +0 -1
  180. package/lib/tab-content.js +0 -4
  181. package/lib/tab-content.vue_vue_type_style_index_0_lang-BeCi7Xkp.cjs +0 -1
  182. package/lib/tab-content.vue_vue_type_style_index_0_lang-CcQLHmIF.js +0 -44
  183. package/lib/tab-group.cjs +0 -1
  184. package/lib/tab-group.js +0 -4
  185. package/lib/tab-group.vue_vue_type_style_index_0_lang-C84mDEOC.js +0 -103
  186. package/lib/tab-group.vue_vue_type_style_index_0_lang-CMaufRJV.cjs +0 -1
  187. package/lib/use-shrinkable-CS2bmsJJ.js +0 -17
  188. package/lib/use-shrinkable-Cu9JQZll.cjs +0 -1
@@ -0,0 +1,325 @@
1
+ <!--
2
+ ページネーション
3
+ 現在ページを中心に maxPagination 個のページ番号と、先頭・前・前ブロック・次ブロック・次・末尾のボタン、ページ番号の選択を表示する。
4
+ -->
5
+ <template>
6
+ <div class="dwui-pagination">
7
+ <ul class="dwui-pagination-list">
8
+ <li>
9
+ <button
10
+ type="button"
11
+ :disabled="current <= 1"
12
+ :aria-label="props.labels.firstPage"
13
+ :title="props.labels.firstPage"
14
+ data-test-pagination-btn-head
15
+ @click="onClickPage(1)"
16
+ >
17
+ <SvgChevronDouble
18
+ direction="left"
19
+ size="1em"
20
+ :stroke-width="8"
21
+ />
22
+ </button>
23
+ </li>
24
+ <li>
25
+ <button
26
+ type="button"
27
+ :disabled="current <= 1"
28
+ :aria-label="props.labels.previousPage"
29
+ :title="props.labels.previousPage"
30
+ data-test-pagination-btn-previous
31
+ @click="onClickPage(current - 1)"
32
+ >
33
+ <SvgChevron
34
+ direction="left"
35
+ size="1em"
36
+ :stroke-width="8"
37
+ />
38
+ </button>
39
+ </li>
40
+ <li>
41
+ <button
42
+ type="button"
43
+ :disabled="pages.length === 0 || pages[0] === 1"
44
+ :aria-label="props.labels.previousPages"
45
+ :title="props.labels.previousPages"
46
+ data-test-pagination-btn-previous-block
47
+ @click="onClickBlock(-1)"
48
+ >
49
+ <SvgBlackArrow
50
+ direction="left"
51
+ size="1em"
52
+ />
53
+ </button>
54
+ </li>
55
+ <li
56
+ v-for="page in pages"
57
+ :key="page"
58
+ >
59
+ <button
60
+ type="button"
61
+ class="dwui-pagination-page"
62
+ :class="{ 'dwui-current': page === current }"
63
+ :aria-current="page === current ? 'page' : undefined"
64
+ data-test-pagination-anchor
65
+ @click="onClickPage(page)"
66
+ >
67
+ {{ page }}
68
+ </button>
69
+ </li>
70
+ <li>
71
+ <button
72
+ type="button"
73
+ :disabled="pages.length === 0 || pages[pages.length - 1] === last"
74
+ :aria-label="props.labels.nextPages"
75
+ :title="props.labels.nextPages"
76
+ data-test-pagination-btn-next-block
77
+ @click="onClickBlock(1)"
78
+ >
79
+ <SvgBlackArrow
80
+ direction="right"
81
+ size="1em"
82
+ />
83
+ </button>
84
+ </li>
85
+ <li>
86
+ <button
87
+ type="button"
88
+ :disabled="last <= current"
89
+ :aria-label="props.labels.nextPage"
90
+ :title="props.labels.nextPage"
91
+ data-test-pagination-btn-next
92
+ @click="onClickPage(current + 1)"
93
+ >
94
+ <SvgChevron
95
+ direction="right"
96
+ size="1em"
97
+ :stroke-width="8"
98
+ />
99
+ </button>
100
+ </li>
101
+ <li>
102
+ <button
103
+ type="button"
104
+ :disabled="last <= current"
105
+ :aria-label="props.labels.lastPage"
106
+ :title="props.labels.lastPage"
107
+ data-test-pagination-btn-last
108
+ @click="onClickPage(last)"
109
+ >
110
+ <SvgChevronDouble
111
+ direction="right"
112
+ size="1em"
113
+ :stroke-width="8"
114
+ />
115
+ </button>
116
+ </li>
117
+ </ul>
118
+ <label
119
+ v-if="props.showPageSelect"
120
+ class="dwui-pagination-select"
121
+ >
122
+ <span>{{ props.labels.pageNumber }}</span>
123
+ <select
124
+ :value="current"
125
+ :disabled="last <= 1"
126
+ data-test-page-number
127
+ @pointerenter="onPointerEnterSelect"
128
+ @pointerleave="onPointerLeaveSelect"
129
+ @focus="onFocusSelect"
130
+ @blur="onBlurSelect"
131
+ @change="onChangePage"
132
+ >
133
+ <option
134
+ v-for="page in pageOptions"
135
+ :key="page"
136
+ :value="page"
137
+ >
138
+ {{ page }} ({{ (page - 1) * props.limit + 1 }} - {{ Math.min(page * props.limit, props.totalCount) }})
139
+ </option>
140
+ </select>
141
+ </label>
142
+ </div>
143
+ </template>
144
+
145
+ <script setup lang="ts">
146
+ import { computed, ref } from 'vue';
147
+ import type { DataTableResolvedLabels } from '@/types/data-table';
148
+ import { SvgBlackArrow, SvgChevron, SvgChevronDouble } from '@digitalwalletcorp/vue-svg-icons';
149
+
150
+ interface Props {
151
+ /** 現在ページ(1起点) */
152
+ current: number;
153
+ /** 総件数 */
154
+ totalCount: number;
155
+ /** 1ページあたりの件数 */
156
+ limit: number;
157
+ /** 並べるページ番号の最大数 */
158
+ maxPagination: number;
159
+ /** ページ番号の選択を表示するか */
160
+ showPageSelect: boolean;
161
+ /** 表示する文言 */
162
+ labels: DataTableResolvedLabels;
163
+ }
164
+ const props = defineProps<Props>();
165
+
166
+ interface Emits {
167
+ (e: 'emit:changePage', page: number): void;
168
+ }
169
+ const emit = defineEmits<Emits>();
170
+
171
+ const last = computed((): number => {
172
+ return Math.ceil(props.totalCount / props.limit);
173
+ });
174
+
175
+ /** 現在ページを 1 〜 last に収めた値 */
176
+ const current = computed((): number => {
177
+ return Math.min(Math.max(props.current, 1), last.value);
178
+ });
179
+
180
+ /** ページ番号の選択に全ページを並べているか。ページ数が多いと option の生成が重いため、操作するときだけ並べる */
181
+ const expanded = ref(false);
182
+
183
+ /** ページ番号の選択に並べるページ。閉じている間は現在ページだけにして表示値を保つ */
184
+ const pageOptions = computed((): number[] => {
185
+ if (last.value < 1) {
186
+ return [];
187
+ }
188
+ if (!expanded.value) {
189
+ return [current.value];
190
+ }
191
+ return Array.from({ length: last.value }, (_, i) => i + 1);
192
+ });
193
+
194
+ /** 現在ページを中心に並べるページ番号。端に寄ったときは反対側へ伸ばす */
195
+ const pages = computed((): number[] => {
196
+ if (last.value < 1) {
197
+ return [];
198
+ }
199
+ const start = Math.min(
200
+ Math.max(current.value - Math.floor((props.maxPagination - 1) / 2), 1),
201
+ Math.max(last.value - props.maxPagination + 1, 1)
202
+ );
203
+ const end = Math.min(start + props.maxPagination - 1, last.value);
204
+ return Array.from({ length: end - start + 1 }, (_, i) => start + i);
205
+ });
206
+
207
+ const onClickPage = (page: number): void => {
208
+ if (page !== props.current) {
209
+ emit('emit:changePage', page);
210
+ }
211
+ };
212
+
213
+ /**
214
+ * 表示しているページ番号の隣のブロックへ移動する。移動先はそのブロックの中央のページ
215
+ *
216
+ * @param {-1 | 1} direction -1 なら前のブロック、1 なら次のブロック
217
+ */
218
+ const onClickBlock = (direction: -1 | 1): void => {
219
+ let start: number;
220
+ if (direction < 0) {
221
+ start = Math.max(pages.value[0] - props.maxPagination, 1);
222
+ } else {
223
+ const end = Math.min(pages.value[pages.value.length - 1] + props.maxPagination, last.value);
224
+ start = end - props.maxPagination + 1;
225
+ }
226
+ onClickPage(start + Math.round((props.maxPagination - 1) / 2));
227
+ };
228
+
229
+ const onChangePage = (event: Event): void => {
230
+ onClickPage(Number((event.target as HTMLSelectElement).value));
231
+ };
232
+
233
+ // クリックで開く前に option が揃っているように、ポインタが乗った時点で並べる
234
+ const onPointerEnterSelect = (): void => {
235
+ expanded.value = true;
236
+ };
237
+
238
+ // 開いたリストは select の外にあるため、フォーカスがある間(開いている可能性がある間)は畳まない
239
+ const onPointerLeaveSelect = (event: PointerEvent): void => {
240
+ if (document.activeElement !== event.currentTarget) {
241
+ expanded.value = false;
242
+ }
243
+ };
244
+
245
+ const onFocusSelect = (): void => {
246
+ expanded.value = true;
247
+ };
248
+
249
+ const onBlurSelect = (): void => {
250
+ expanded.value = false;
251
+ };
252
+ </script>
253
+
254
+ <style>
255
+ :where(.dwui-pagination) {
256
+ display: flex;
257
+ align-items: center;
258
+ gap: 0.67em;
259
+ white-space: nowrap;
260
+ }
261
+
262
+ :where(.dwui-pagination-list) {
263
+ display: flex;
264
+ /* ボタンをコントロール行の高さまで伸ばす */
265
+ align-self: stretch;
266
+ align-items: stretch;
267
+ gap: 0.25em;
268
+ margin: 0;
269
+ padding: 0;
270
+ list-style: none;
271
+ }
272
+
273
+ :where(.dwui-pagination-list li) {
274
+ display: flex;
275
+ }
276
+
277
+ :where(.dwui-pagination-list button) {
278
+ display: inline-flex;
279
+ align-items: center;
280
+ justify-content: center;
281
+ box-sizing: border-box;
282
+ /* 高さは行にそろえ、幅は高さと同じにして正方形にする */
283
+ aspect-ratio: 1;
284
+ padding: 0;
285
+ border: 1px solid var(--dwui-border-color-pagination-button, light-dark(#d3d3d3, #5a5a5a));
286
+ border-radius: 3px;
287
+ background: var(--dwui-background-pagination-button, light-dark(#fafafa, #2a2a2a));
288
+ color: var(--dwui-color-text-pagination-button, inherit);
289
+ font: inherit;
290
+ line-height: 1;
291
+ cursor: pointer;
292
+ transition: background-color 0.3s ease;
293
+ }
294
+
295
+ :where(.dwui-pagination-list button:disabled) {
296
+ opacity: 0.4;
297
+ cursor: not-allowed;
298
+ }
299
+
300
+ :where(.dwui-pagination-list button:not(:disabled, .dwui-current):hover) {
301
+ background: var(--dwui-background-pagination-button-hover, light-dark(#46e2f7, #1f7a86));
302
+ }
303
+
304
+ :where(.dwui-pagination-list .dwui-current) {
305
+ background: var(--dwui-background-pagination-button-current, linear-gradient(rgba(78, 221, 231, 0.7), rgba(34, 162, 172, 0.7)));
306
+ color: var(--dwui-color-text-pagination-button-current, inherit);
307
+ cursor: default;
308
+ }
309
+
310
+ :where(.dwui-pagination-list button:focus-visible) {
311
+ outline: var(--dwui-outline-width-focus-ring, 2px) solid var(--dwui-outline-color-focus-ring, Highlight);
312
+ outline-offset: var(--dwui-outline-offset-focus-ring, 2px);
313
+ }
314
+
315
+ :where(.dwui-pagination-select) {
316
+ display: inline-flex;
317
+ align-self: stretch;
318
+ align-items: center;
319
+ gap: 0.33em;
320
+ }
321
+
322
+ :where(.dwui-pagination-select select) {
323
+ align-self: stretch;
324
+ }
325
+ </style>
@@ -0,0 +1,258 @@
1
+ <!--
2
+ snackbar の表示領域
3
+ 画面の上端または下端に固定し、snackbar を placement の側に寄せて並べる。
4
+ direction が overlap なら同じ位置に重ねて新しいものを前面に出し、column なら縦に積み、row なら横に並べて端で折り返す。
5
+ clearable の snackbar があるときは、先頭(overlap では重ねた snackbar の横)に一括消去ボタンを置く。
6
+ showSnackbar から body 直下に描画して使う。
7
+ -->
8
+ <template>
9
+ <TransitionGroup
10
+ tag="div"
11
+ name="dwui-snackbar-fade"
12
+ class="dwui-snackbar-container"
13
+ :class="containerClasses"
14
+ role="status"
15
+ @after-leave="emit('emit:afterLeave')"
16
+ >
17
+ <button
18
+ v-if="clearLabel != null"
19
+ key="clear"
20
+ type="button"
21
+ class="dwui-snackbar-clear-button"
22
+ :aria-label="clearLabel"
23
+ :title="clearLabel"
24
+ @click="emit('emit:clear')"
25
+ >
26
+ <SvgWastebasket />
27
+ </button>
28
+ <button
29
+ v-for="item of props.items"
30
+ :key="item.id"
31
+ type="button"
32
+ class="dwui-snackbar"
33
+ :class="item.class"
34
+ :style="item.style"
35
+ :title="item.message"
36
+ @click="emit('emit:close', item.id)"
37
+ >
38
+ <span
39
+ v-if="item.title || item.label"
40
+ class="dwui-snackbar-header"
41
+ >
42
+ <span
43
+ v-if="item.title"
44
+ class="dwui-snackbar-title"
45
+ >
46
+ {{ item.title }}
47
+ </span>
48
+ <span
49
+ v-if="item.label"
50
+ class="dwui-snackbar-label"
51
+ >
52
+ {{ item.label }}
53
+ </span>
54
+ </span>
55
+ <span class="dwui-snackbar-message">
56
+ {{ item.message }}
57
+ </span>
58
+ <span
59
+ v-if="item.timestamp"
60
+ class="dwui-snackbar-timestamp"
61
+ >
62
+ {{ item.timestamp }}
63
+ </span>
64
+ </button>
65
+ </TransitionGroup>
66
+ </template>
67
+
68
+ <script setup lang="ts">
69
+ import { computed } from 'vue';
70
+ import { SvgWastebasket } from '@digitalwalletcorp/vue-svg-icons';
71
+ import type { SnackbarDirection, SnackbarItem, SnackbarPlacement } from '@/internal/snackbar-item';
72
+
73
+ interface Props {
74
+ /** 表示する位置 */
75
+ placement: SnackbarPlacement;
76
+ /** 同じ表示領域に並べる向き */
77
+ direction: SnackbarDirection;
78
+ /** 表示する snackbar。古いものから順に並べる */
79
+ items: SnackbarItem[];
80
+ }
81
+ const props = defineProps<Props>();
82
+
83
+ interface Emits {
84
+ /** snackbar がクリックされた */
85
+ (e: 'emit:close', id: number): void;
86
+ /** 一括消去ボタンがクリックされた */
87
+ (e: 'emit:clear'): void;
88
+ /** 要素が閉じるアニメーションを終えて取り除かれた */
89
+ (e: 'emit:afterLeave'): void;
90
+ }
91
+ const emit = defineEmits<Emits>();
92
+
93
+ const containerClasses = computed(() => {
94
+ const [vertical, horizontal = 'center'] = props.placement.split('-');
95
+ return [
96
+ `dwui-snackbar-container-${vertical}`,
97
+ `dwui-snackbar-container-${horizontal}`,
98
+ `dwui-snackbar-container-${props.direction}`
99
+ ];
100
+ });
101
+
102
+ /** 一括消去ボタンの読み上げ名。clearable の snackbar のうち最も新しいものの値を使い、無ければボタンを出さない */
103
+ const clearLabel = computed(() => props.items.filter((item) => item.clearable).at(-1)?.clearLabel);
104
+ </script>
105
+
106
+ <style>
107
+ :where(.dwui-snackbar-container) {
108
+ position: fixed;
109
+ right: 0;
110
+ left: 0;
111
+ z-index: var(--dwui-z-index-snackbar, 7000);
112
+ display: flex;
113
+ gap: var(--dwui-gap-snackbar-container, 0.33em);
114
+ box-sizing: border-box;
115
+ padding: var(--dwui-padding-snackbar-container, 0.33em);
116
+ /* 領域は画面の幅いっぱいに広がるため、snackbar の無いところでは背面のページを操作できるようにする */
117
+ pointer-events: none;
118
+ }
119
+
120
+ :where(.dwui-snackbar-container > *) {
121
+ pointer-events: auto;
122
+ }
123
+
124
+ :where(.dwui-snackbar-container-top) {
125
+ top: 0;
126
+ }
127
+
128
+ :where(.dwui-snackbar-container-bottom) {
129
+ bottom: 0;
130
+ }
131
+
132
+ :where(.dwui-snackbar-container-overlap) {
133
+ display: grid;
134
+ grid-template-columns: auto auto;
135
+ column-gap: 0;
136
+ align-items: center;
137
+ justify-content: center;
138
+ }
139
+
140
+ :where(.dwui-snackbar-container-overlap.dwui-snackbar-container-start) {
141
+ justify-content: start;
142
+ }
143
+
144
+ :where(.dwui-snackbar-container-overlap.dwui-snackbar-container-end) {
145
+ justify-content: end;
146
+ }
147
+
148
+ /* 全件を同じセルに置く。DOM で後ろにある新しい snackbar が前面に描かれる */
149
+ :where(.dwui-snackbar-container-overlap > .dwui-snackbar) {
150
+ grid-area: 1 / 2;
151
+ }
152
+
153
+ :where(.dwui-snackbar-container-overlap > .dwui-snackbar-clear-button) {
154
+ grid-area: 1 / 1;
155
+ margin-right: var(--dwui-gap-snackbar-container, 0.33em);
156
+ }
157
+
158
+ :where(.dwui-snackbar-container-column) {
159
+ flex-direction: column;
160
+ align-items: center;
161
+ }
162
+
163
+ :where(.dwui-snackbar-container-column.dwui-snackbar-container-start) {
164
+ align-items: flex-start;
165
+ }
166
+
167
+ :where(.dwui-snackbar-container-column.dwui-snackbar-container-end) {
168
+ align-items: flex-end;
169
+ }
170
+
171
+ :where(.dwui-snackbar-container-row) {
172
+ flex-wrap: wrap;
173
+ align-items: center;
174
+ justify-content: center;
175
+ }
176
+
177
+ :where(.dwui-snackbar-container-row.dwui-snackbar-container-start) {
178
+ justify-content: flex-start;
179
+ }
180
+
181
+ :where(.dwui-snackbar-container-row.dwui-snackbar-container-end) {
182
+ justify-content: flex-end;
183
+ }
184
+
185
+ :where(.dwui-snackbar) {
186
+ display: block;
187
+ box-sizing: border-box;
188
+ width: var(--dwui-width-snackbar, 300px);
189
+ max-width: 100%;
190
+ padding: var(--dwui-padding-snackbar, 0.5em 0.67em);
191
+ border: none;
192
+ border-radius: var(--dwui-radius-snackbar, 0);
193
+ background: var(--dwui-background-snackbar, #323232);
194
+ color: var(--dwui-color-text-snackbar, #ffffff);
195
+ box-shadow: var(--dwui-box-shadow-snackbar, 4px 4px 3px 0 rgba(0, 0, 0, 0.25));
196
+ font: inherit;
197
+ text-align: left;
198
+ cursor: pointer;
199
+ }
200
+
201
+ :where(.dwui-snackbar:focus-visible) {
202
+ outline: var(--dwui-outline-width-focus-ring, 2px) solid var(--dwui-outline-color-focus-ring, Highlight);
203
+ outline-offset: var(--dwui-outline-offset-focus-ring, 2px);
204
+ }
205
+
206
+ :where(.dwui-snackbar-header) {
207
+ display: flex;
208
+ gap: 0.67em;
209
+ color: var(--dwui-color-text-snackbar-header, #d3d3d3);
210
+ font-size: 0.8em;
211
+ }
212
+
213
+ :where(.dwui-snackbar-label) {
214
+ margin-left: auto;
215
+ }
216
+
217
+ :where(.dwui-snackbar-message) {
218
+ display: block;
219
+ padding-left: 0.33em;
220
+ overflow: hidden;
221
+ text-overflow: ellipsis;
222
+ white-space: nowrap;
223
+ }
224
+
225
+ :where(.dwui-snackbar-timestamp) {
226
+ display: block;
227
+ color: var(--dwui-color-text-snackbar-timestamp, #d3d3d3);
228
+ font-size: 0.8em;
229
+ text-align: right;
230
+ }
231
+
232
+ :where(.dwui-snackbar-clear-button) {
233
+ display: inline-flex;
234
+ align-items: center;
235
+ justify-content: center;
236
+ padding: 0.17em;
237
+ border: none;
238
+ border-radius: 4px;
239
+ background-color: transparent;
240
+ background-image: none;
241
+ color: var(--dwui-color-text-snackbar-clear-button, inherit);
242
+ font: inherit;
243
+ cursor: pointer;
244
+ }
245
+
246
+ :where(.dwui-snackbar-clear-button:focus-visible) {
247
+ outline: var(--dwui-outline-width-focus-ring, 2px) solid var(--dwui-outline-color-focus-ring, Highlight);
248
+ outline-offset: var(--dwui-outline-offset-focus-ring, 2px);
249
+ }
250
+
251
+ :where(.dwui-snackbar-fade-enter-active, .dwui-snackbar-fade-leave-active) {
252
+ transition: opacity 0.5s ease;
253
+ }
254
+
255
+ :where(.dwui-snackbar-fade-enter-from, .dwui-snackbar-fade-leave-to) {
256
+ opacity: 0;
257
+ }
258
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { ClassValue, StyleValue } from 'vue';
2
+
3
+ /** snackbar を表示する画面上の位置 */
4
+ export type SnackbarPlacement = 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end';
5
+
6
+ /** 同じ表示領域に並べる向き。overlap は同じ位置に重ねて新しいものを前面に出し、column は縦に積み、row は横に並べて端で折り返す */
7
+ export type SnackbarDirection = 'overlap' | 'column' | 'row';
8
+
9
+ /** 表示領域に並べる snackbar 1件分の内容 */
10
+ export interface SnackbarItem {
11
+ id: number;
12
+ message: string;
13
+ title?: string;
14
+ label?: string;
15
+ timestamp?: string;
16
+ clearable: boolean;
17
+ clearLabel: string;
18
+ class?: ClassValue;
19
+ style?: StyleValue;
20
+ }
@@ -8,8 +8,10 @@ export type TabContentStatus = 'warn' | 'error';
8
8
  * TabGroupとTabContentの間だけで使い、利用側には公開しない
9
9
  */
10
10
  export interface TabGroupContext {
11
- /** 選択中のタブのlabelId */
12
- activeLabelId: ComputedRef<string | undefined>;
11
+ /** 見出しと中身の id の接頭辞。aria-controls / aria-labelledby で結び付けるのに使う */
12
+ groupName: string;
13
+ /** 選択中のタブのtabId */
14
+ activeTabId: ComputedRef<string | undefined>;
13
15
  /** TabGroupがマウント済みか */
14
16
  mounted: Ref<boolean>;
15
17
  /** TabGroupの#loadingスロット。未指定ならTabContentは既定の読み込み中表示を出す */