@digitalwalletcorp/vue-core 1.2.1 → 1.3.1

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 +79 -3
  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-BuTJ1wX8.js +26 -0
  7. package/lib/components-CKql5lWg.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 +29 -0
  15. package/lib/data-table.d.ts +29 -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 +86 -0
  23. package/lib/helpers/show-floating-component.d.ts +86 -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-BKV_FX6k.js +1837 -0
  102. package/lib/warning-popper.vue_vue_type_style_index_0_lang-CHmkjVCO.cjs +1 -0
  103. package/package.json +40 -14
  104. package/src/accordion-fieldset.vue +24 -19
  105. package/src/accordion-section.vue +33 -28
  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 +648 -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 +177 -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 +9 -6
  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 +4 -4
  142. package/src/screen-loading.vue +90 -0
  143. package/src/tab-content.vue +16 -15
  144. package/src/tab-group.vue +34 -36
  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-Cf4czDXz.js +0 -66
  152. package/lib/accordion-fieldset.vue_vue_type_style_index_0_lang-DoM5QjpJ.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-C63HJoCn.cjs +0 -1
  158. package/lib/components-q4Cf97bO.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_style_index_0_lang-D7ZgfZOL.cjs +0 -1
  166. package/lib/label-button.vue_vue_type_style_index_0_lang-DqRYddVt.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,218 @@
1
+ <!--
2
+ オーバーレイボックス
3
+ 画面全体に暗幕を掛け、その上に見出しと閉じるボタンの付いたウィンドウを表示する。
4
+ 閉じるボタン・Esc・暗幕のクリック(closeOnBackdrop)で閉じる。ウィンドウはドラッグで移動でき、右下のつまみでリサイズできる。
5
+ 中に置いた要素が body 直下に描画するツールチップやドロップダウンを隠さないよう、<dialog> の最前面表示ではなく z-index で前面に出す。
6
+ FloatingComponent とは開いた順に重なり、後から開いたものが前面に出る。
7
+ -->
8
+ <template>
9
+ <Transition
10
+ name="dwui-overlay-box-fade"
11
+ @after-leave="onAfterLeaveOverlayBox"
12
+ >
13
+ <div
14
+ v-if="open"
15
+ class="dwui-overlay-box"
16
+ :style="{ '--dwui-order-window': order }"
17
+ >
18
+ <div
19
+ class="dwui-overlay-box-backdrop"
20
+ @click="onClickBackdrop"
21
+ />
22
+ <MovableWindow
23
+ ref="refWindow"
24
+ class="dwui-overlay-box-container"
25
+ name="dwui-overlay-box"
26
+ tabindex="-1"
27
+ :drag-mode="props.dragMode"
28
+ :loading="props.loading"
29
+ :close-label="props.closeLabel"
30
+ clamp-bottom
31
+ @keydown="onKeydownWindow"
32
+ @emit:close="onCloseWindow"
33
+ >
34
+ <template #title>
35
+ <slot name="title">
36
+ {{ props.title }}
37
+ </slot>
38
+ </template>
39
+ <slot />
40
+ </MovableWindow>
41
+ </div>
42
+ </Transition>
43
+ </template>
44
+
45
+ <script setup lang="ts">
46
+ import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
47
+ import MovableWindow from '@/internal/movable-window.vue';
48
+ import { openedOverlayBoxes } from '@/internal/opened-overlay-boxes';
49
+ import { nextWindowOrder } from '@/internal/window-order';
50
+ import type { MovableWindowDragMode } from '@/types/movable-window';
51
+
52
+ export interface Props {
53
+ /** 見出し。title スロットで差し替えられる */
54
+ title?: string;
55
+ /** ドラッグで移動するときに掴む範囲。body はウィンドウ全体、header は見出しだけ。未指定なら移動できない */
56
+ dragMode?: MovableWindowDragMode;
57
+ /** 暗幕のクリックで閉じる */
58
+ closeOnBackdrop?: boolean;
59
+ /** ウィンドウに暗幕とスピナーを重ね、中身を操作できなくする */
60
+ loading?: boolean;
61
+ /** 閉じるボタンの読み上げ名 */
62
+ closeLabel?: string;
63
+ }
64
+ const props = withDefaults(defineProps<Props>(), {
65
+ title: undefined,
66
+ dragMode: undefined,
67
+ closeOnBackdrop: false,
68
+ loading: false,
69
+ closeLabel: 'Close'
70
+ });
71
+
72
+ export interface Emits {
73
+ /** 閉じるアニメーションが終わり、ボックスが取り除かれた */
74
+ (e: 'emit:close'): void;
75
+ }
76
+ const emit = defineEmits<Emits>();
77
+
78
+ export interface Slots {
79
+ /** 見出し */
80
+ title?(): unknown;
81
+ /** ウィンドウに表示する中身 */
82
+ default?(): unknown;
83
+ }
84
+ defineSlots<Slots>();
85
+
86
+ /** 表示するか。閉じる操作で false になる */
87
+ const open = defineModel<boolean>('open', { default: false });
88
+
89
+ const refWindow = ref<InstanceType<typeof MovableWindow>>();
90
+ const order = ref(0);
91
+
92
+ const boxKey = Symbol('dwui-overlay-box');
93
+ let previousFocus: HTMLElement | null = null;
94
+
95
+ const onClickBackdrop = (): void => {
96
+ if (props.closeOnBackdrop) {
97
+ open.value = false;
98
+ }
99
+ };
100
+
101
+ const onCloseWindow = (): void => {
102
+ open.value = false;
103
+ };
104
+
105
+ /**
106
+ * ウィンドウにフォーカスがある間、Esc で閉じる。
107
+ * 中に置いた要素が dispatchEvent で発行した Escape、IME の変換を取り消す Esc、
108
+ * 中に置いた <dialog> に向けた Esc では閉じない
109
+ */
110
+ const onKeydownWindow = (event: KeyboardEvent): void => {
111
+ if (event.key !== 'Escape' || !event.isTrusted || event.isComposing) {
112
+ return;
113
+ }
114
+ if (event.target instanceof Element && event.target.closest('dialog')) {
115
+ return;
116
+ }
117
+ // テンプレートで box の中に置いた box の Esc は外側にもバブルするため、最後に開いた box だけが閉じる
118
+ if (openedOverlayBoxes.at(-1) === boxKey) {
119
+ open.value = false;
120
+ }
121
+ };
122
+
123
+ const onAfterLeaveOverlayBox = (): void => {
124
+ emit('emit:close');
125
+ };
126
+
127
+ const removeFromOpenedBoxes = (): void => {
128
+ const index = openedOverlayBoxes.indexOf(boxKey);
129
+ if (0 <= index) {
130
+ openedOverlayBoxes.splice(index, 1);
131
+ }
132
+ };
133
+
134
+ const activate = (): void => {
135
+ order.value = nextWindowOrder();
136
+ const movable = refWindow.value;
137
+ const rect = movable?.element?.getBoundingClientRect();
138
+ if (movable) {
139
+ movable.position.x = (window.innerWidth - (rect?.width || 0)) / 2;
140
+ // 画面より高いときに見出しが上に隠れて掴めなくならないよう、上端は画面内に収める
141
+ movable.position.y = Math.max(0, (window.innerHeight - (rect?.height || 0)) / 2);
142
+ }
143
+ previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
144
+ movable?.element?.focus();
145
+ openedOverlayBoxes.push(boxKey);
146
+ };
147
+
148
+ const deactivate = (): void => {
149
+ refWindow.value?.teardown();
150
+ removeFromOpenedBoxes();
151
+ if (previousFocus?.isConnected) {
152
+ previousFocus.focus();
153
+ }
154
+ previousFocus = null;
155
+ };
156
+
157
+ onMounted(() => {
158
+ if (open.value) {
159
+ activate();
160
+ }
161
+ });
162
+
163
+ onBeforeUnmount(() => {
164
+ removeFromOpenedBoxes();
165
+ });
166
+
167
+ watch(open, (newValue) => {
168
+ if (newValue) {
169
+ activate();
170
+ } else {
171
+ deactivate();
172
+ }
173
+ }, { flush: 'post' });
174
+ </script>
175
+
176
+ <style>
177
+ :where(html:has(.dwui-overlay-box), html:has(.dwui-overlay-box) > body) {
178
+ overflow: hidden;
179
+ }
180
+
181
+ :where(.dwui-overlay-box) {
182
+ position: fixed;
183
+ inset: 0;
184
+ z-index: calc(var(--dwui-z-index-overlay-box, 5000) + var(--dwui-order-window, 0));
185
+ }
186
+
187
+ :where(.dwui-overlay-box-backdrop) {
188
+ position: absolute;
189
+ inset: 0;
190
+ background: var(--dwui-background-overlay-box-backdrop, light-dark(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.5)));
191
+ }
192
+
193
+ :where(.dwui-overlay-box-container) {
194
+ --dwui-border-color-movable-window: var(--dwui-border-color-overlay-box, light-dark(#8f8f8f, #6b6f76));
195
+ --dwui-radius-movable-window: var(--dwui-radius-overlay-box, 4px);
196
+ --dwui-background-movable-window: var(--dwui-background-overlay-box, Canvas);
197
+ --dwui-color-text-movable-window: var(--dwui-color-text-overlay-box, CanvasText);
198
+ --dwui-box-shadow-movable-window: var(--dwui-box-shadow-overlay-box, 0 2px 8px rgba(0, 0, 0, 0.33));
199
+ --dwui-padding-movable-window-header: var(--dwui-padding-overlay-box-header, 0.17em 0.33em);
200
+ --dwui-background-movable-window-header: var(--dwui-background-overlay-box-header, transparent);
201
+ --dwui-color-text-movable-window-header: var(--dwui-color-text-overlay-box-header, inherit);
202
+ --dwui-background-movable-window-close-button-hover: var(--dwui-background-overlay-box-close-button-hover, light-dark(rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.12)));
203
+ --dwui-padding-movable-window-body: var(--dwui-padding-overlay-box-body, 0.4em);
204
+ --dwui-color-text-movable-window-resizer: var(--dwui-color-text-overlay-box-resizer, GrayText);
205
+ --dwui-background-movable-window-loading: var(--dwui-background-overlay-box-loading, light-dark(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.4)));
206
+ --dwui-size-movable-window-spinner: var(--dwui-size-overlay-box-spinner, 48px);
207
+ --dwui-border-color-movable-window-spinner: var(--dwui-border-color-overlay-box-spinner, light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.2)));
208
+ --dwui-border-color-movable-window-spinner-accent: var(--dwui-border-color-overlay-box-spinner-accent, light-dark(rgba(47, 90, 74, 0.8), rgba(140, 200, 170, 0.8)));
209
+ }
210
+
211
+ :where(.dwui-overlay-box-fade-enter-active, .dwui-overlay-box-fade-leave-active) {
212
+ transition: opacity 0.25s ease;
213
+ }
214
+
215
+ :where(.dwui-overlay-box-fade-enter-from, .dwui-overlay-box-fade-leave-to) {
216
+ opacity: 0;
217
+ }
218
+ </style>
@@ -36,7 +36,7 @@ import LabelIcon from '@/label-icon.vue';
36
36
  /** 見出しのアイコンとして選べるもの */
37
37
  type PageSectionIcon = 'diamond' | 'circle' | 'square' | 'pushpin' | 'round-pushpin';
38
38
 
39
- interface Props {
39
+ export interface Props {
40
40
  /** 見出しの文字。未指定なら見出しを表示しない */
41
41
  title?: string;
42
42
  /** 見出しのアイコン。これ以外のアイコンは#iconスロットで渡す デフォルト 'diamond' */
@@ -54,7 +54,7 @@ const props = withDefaults(defineProps<Props>(), {
54
54
  icon: 'diamond'
55
55
  });
56
56
 
57
- interface Slots {
57
+ export interface Slots {
58
58
  /** 見出しの文字の前に表示するアイコン。iconより優先する */
59
59
  icon?(): unknown;
60
60
  default?(): unknown;
@@ -85,19 +85,19 @@ const presetIcon = computed((): { component: Component; props: Record<string, un
85
85
  <style>
86
86
  :where(.dwui-page-section) {
87
87
  display: block;
88
- margin: 4px 0 8px;
89
- padding-top: 2px;
90
- font-size: var(--dwui-font-size-page-section, 12px);
88
+ margin: 0.33em 0 0.67em;
89
+ padding-top: 0.17em;
90
+ font-size: var(--dwui-font-size-page-section, inherit);
91
91
  font-weight: 500;
92
92
  }
93
93
 
94
94
  :where(.dwui-page-section-title) {
95
95
  display: flex;
96
96
  align-items: center;
97
- font-size: var(--dwui-font-size-page-section-title, 14px);
97
+ font-size: var(--dwui-font-size-page-section-title, 1.17em);
98
98
  }
99
99
 
100
100
  :where(.dwui-page-section-content) {
101
- margin: 8px 0 8px 16px;
101
+ margin: 0.67em 0 0.67em 1.33em;
102
102
  }
103
103
  </style>
@@ -25,13 +25,13 @@
25
25
  <script setup lang="ts">
26
26
  import LabelIcon from '@/label-icon.vue';
27
27
 
28
- interface Props {
28
+ export interface Props {
29
29
  /** タイトルの文字 */
30
30
  title: string;
31
31
  }
32
32
  const props = defineProps<Props>();
33
33
 
34
- interface Slots {
34
+ export interface Slots {
35
35
  /** タイトルの文字の前に表示するアイコン */
36
36
  icon?(): unknown;
37
37
  /** タイトルバーの右側に表示する内容 */
@@ -46,11 +46,11 @@ const slots = defineSlots<Slots>();
46
46
  align-items: center;
47
47
  justify-content: space-between;
48
48
  width: var(--dwui-width-page-title, auto);
49
- padding: 4px;
49
+ padding: 0.33em;
50
50
  border-radius: 4px;
51
51
  background: var(--dwui-background-page-title, linear-gradient(light-dark(#323232, #3c3f45) 0%, light-dark(rgb(32, 32, 31), #2b2d31) 25%, light-dark(rgb(32, 32, 31), #2b2d31) 75%, light-dark(#323232, #3c3f45) 100%));
52
52
  color: var(--dwui-color-text-page-title, rgb(220, 220, 220));
53
- font-size: var(--dwui-font-size-page-title, 14px);
53
+ font-size: var(--dwui-font-size-page-title, 1.17em);
54
54
  font-weight: var(--dwui-font-weight-page-title, normal);
55
55
  }
56
56
  </style>
@@ -0,0 +1,90 @@
1
+ <!--
2
+ 画面全体を覆うローディング。
3
+ loading が true の間は <dialog> をモーダルで開き、暗幕とスピナーを表示して画面の操作を受け付けなくする。
4
+ Esc キーでは閉じない。
5
+ -->
6
+ <template>
7
+ <dialog
8
+ ref="refDialog"
9
+ class="dwui-screen-loading"
10
+ closedby="none"
11
+ @cancel.prevent
12
+ >
13
+ <div class="dwui-screen-loading-spinner" />
14
+ </dialog>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
19
+
20
+ export interface Props {
21
+ /** 読み込み中か */
22
+ loading: boolean;
23
+ }
24
+ const props = defineProps<Props>();
25
+
26
+ const refDialog = ref<HTMLDialogElement | null>(null);
27
+
28
+ const open = () => {
29
+ const dialog = refDialog.value;
30
+ if (dialog && !dialog.open) {
31
+ dialog.showModal();
32
+ }
33
+ };
34
+
35
+ const close = () => {
36
+ const dialog = refDialog.value;
37
+ if (dialog?.open) {
38
+ dialog.close();
39
+ }
40
+ };
41
+
42
+ onMounted(() => {
43
+ if (props.loading) {
44
+ open();
45
+ }
46
+ });
47
+
48
+ onBeforeUnmount(() => {
49
+ close();
50
+ });
51
+
52
+ watch(() => props.loading, (newValue) => {
53
+ if (newValue) {
54
+ open();
55
+ } else {
56
+ close();
57
+ }
58
+ }, { flush: 'post' });
59
+ </script>
60
+
61
+ <style>
62
+ :where(.dwui-screen-loading) {
63
+ padding: 0;
64
+ border: none;
65
+ outline: none;
66
+ background-color: transparent;
67
+ background-image: none;
68
+ overflow: visible;
69
+ }
70
+
71
+ :where(.dwui-screen-loading::backdrop) {
72
+ background: var(--dwui-background-screen-loading, light-dark(rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0.5)));
73
+ }
74
+
75
+ :where(.dwui-screen-loading-spinner) {
76
+ box-sizing: border-box;
77
+ width: var(--dwui-size-screen-loading-spinner, 100px);
78
+ height: var(--dwui-size-screen-loading-spinner, 100px);
79
+ border: 8px solid var(--dwui-border-color-screen-loading-spinner, light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.2)));
80
+ border-left-color: var(--dwui-border-color-screen-loading-spinner-accent, light-dark(rgba(47, 90, 74, 0.8), rgba(140, 200, 170, 0.8)));
81
+ border-radius: 50%;
82
+ animation: dwui-screen-loading-spin 1.1s linear infinite;
83
+ }
84
+
85
+ @keyframes dwui-screen-loading-spin {
86
+ to {
87
+ transform: rotate(360deg);
88
+ }
89
+ }
90
+ </style>
@@ -4,12 +4,13 @@ TabGroupの中に置くタブ1つ分の中身
4
4
  <template>
5
5
  <div
6
6
  v-if="!props.disabled"
7
+ :id="context ? `${context.groupName}-${props.tabId}-panel` : undefined"
7
8
  class="dwui-tab-content"
8
- :aria-label="props.labelId"
9
- :aria-checked="selected ? 'true' : 'false'"
10
- :aria-description="props.description"
9
+ role="tabpanel"
10
+ :aria-labelledby="context ? `${context.groupName}-${props.tabId}` : undefined"
11
11
  :aria-busy="loading ? 'true' : undefined"
12
- :data-test-tab-content="props.labelId"
12
+ :hidden="!selected"
13
+ :data-test-tab-content="props.tabId"
13
14
  >
14
15
  <slot
15
16
  :activated="activated"
@@ -38,12 +39,12 @@ import type { ClassValue, Slot, StyleValue } from 'vue';
38
39
  import { tabGroupContextKey } from '@/internal/tab-group-context';
39
40
  import type { TabContentStatus } from '@/internal/tab-group-context';
40
41
 
41
- // labelId・description・header*・status・#iconは、TabGroupが見出しを描画するために読む
42
- interface Props {
43
- /** タブを識別するID。TabGroupのselectedTabLabelIdに渡す値 */
44
- labelId: string;
42
+ // tabId・label・header*・status・#iconは、TabGroupが見出しを描画するために読む
43
+ export interface Props {
44
+ /** タブを識別するID。TabGroupのselectedTabIdに渡す値 */
45
+ tabId: string;
45
46
  /** タブの見出しの文字 */
46
- description: string;
47
+ label: string;
47
48
  /** trueの場合、タブを選べなくし、中身も描画しない */
48
49
  disabled?: boolean;
49
50
  /** タブの見出しの状態。見出しの色が変わる */
@@ -55,7 +56,7 @@ interface Props {
55
56
  }
56
57
  const props = defineProps<Props>();
57
58
 
58
- interface Slots {
59
+ export interface Slots {
59
60
  /** タブの見出しの文字の前に表示するアイコン */
60
61
  icon?(): unknown;
61
62
  /**
@@ -80,7 +81,7 @@ const loading = ref(false);
80
81
  const loadingSlot = (): Slot | undefined => context?.loadingSlot();
81
82
 
82
83
  // 表示の切り替えは初期描画から効かせるため、マウントを待たない
83
- const selected = computed((): boolean => context?.activeLabelId.value === props.labelId);
84
+ const selected = computed((): boolean => context?.activeTabId.value === props.tabId);
84
85
 
85
86
  const activated = computed((): boolean => !!context?.mounted.value && selected.value);
86
87
 
@@ -95,7 +96,7 @@ const setLoading = (value: boolean) => {
95
96
  </script>
96
97
 
97
98
  <style>
98
- :where(.dwui-tab-content[aria-checked='false']) {
99
+ :where(.dwui-tab-content[hidden]) {
99
100
  display: none;
100
101
  }
101
102
 
@@ -103,7 +104,7 @@ const setLoading = (value: boolean) => {
103
104
  display: block;
104
105
  /* 読み込み中の覆いを中身の範囲に重ねる基準にする */
105
106
  position: relative;
106
- padding: 4px 8px;
107
+ padding: 0.33em 0.67em;
107
108
  overflow: hidden;
108
109
  /* 全幅を横断すると速度が出てカクつくため、既定は固定の移動量とease-outとフェードでスライドインする */
109
110
  animation: dwui-tab-slide-in var(--dwui-duration-tab-slide, 0.35s) var(--dwui-easing-tab-slide, cubic-bezier(0.22, 1, 0.36, 1));
@@ -122,8 +123,8 @@ const setLoading = (value: boolean) => {
122
123
  :where(.dwui-tab-loading-spinner) {
123
124
  width: var(--dwui-size-loading-spinner, 40px);
124
125
  height: var(--dwui-size-loading-spinner, 40px);
125
- border: 4px solid var(--dwui-border-color-loading-spinner-track, rgba(255, 255, 255, 0.4));
126
- border-top-color: var(--dwui-border-color-loading-spinner, #ffffff);
126
+ border: 4px solid var(--dwui-border-color-loading-spinner, rgba(255, 255, 255, 0.4));
127
+ border-top-color: var(--dwui-border-color-loading-spinner-accent, #ffffff);
127
128
  border-radius: 50%;
128
129
  animation: dwui-tab-loading-spin 0.8s linear infinite;
129
130
  }
package/src/tab-group.vue CHANGED
@@ -10,28 +10,32 @@ TabContentを並べてタブで切り替える
10
10
  >
11
11
  <div
12
12
  class="dwui-tab-headers"
13
+ role="tablist"
13
14
  :class="props.headerClass"
14
15
  :style="props.headerStyle"
15
16
  >
16
17
  <template
17
18
  v-for="tab in tabs"
18
- :key="tab.labelId"
19
+ :key="tab.tabId"
19
20
  >
20
21
  <input
21
- :id="`${groupName}-${tab.labelId}`"
22
+ :id="`${groupName}-${tab.tabId}`"
22
23
  type="radio"
23
24
  class="dwui-tab-radio"
25
+ role="tab"
24
26
  :name="groupName"
25
27
  :disabled="tab.disabled"
26
- :checked="tab.labelId === activeLabelId"
27
- @change="onChangeTab(tab.labelId)"
28
+ :checked="tab.tabId === activeTabId"
29
+ :aria-selected="tab.tabId === activeTabId ? 'true' : 'false'"
30
+ :aria-controls="`${groupName}-${tab.tabId}-panel`"
31
+ @change="onChangeTab(tab.tabId)"
28
32
  >
29
33
  <label
30
- :for="`${groupName}-${tab.labelId}`"
34
+ :for="`${groupName}-${tab.tabId}`"
31
35
  class="dwui-tab-item"
32
36
  :class="[tab.status != null ? `dwui-tab-${tab.status}` : undefined, tab.headerClass]"
33
37
  :style="tab.headerStyle"
34
- :data-test-tab-content-control="tab.labelId"
38
+ :data-test-tab-content-control="tab.tabId"
35
39
  >
36
40
  <LabelIcon>
37
41
  <template
@@ -40,7 +44,7 @@ TabContentを並べてタブで切り替える
40
44
  >
41
45
  <component :is="tab.icon" />
42
46
  </template>
43
- {{ tab.description }}
47
+ {{ tab.label }}
44
48
  </LabelIcon>
45
49
  </label>
46
50
  </template>
@@ -60,10 +64,10 @@ import TabContent from '@/tab-content.vue';
60
64
  import { tabGroupContextKey } from '@/internal/tab-group-context';
61
65
  import type { TabContentStatus } from '@/internal/tab-group-context';
62
66
 
63
- interface Props {
67
+ export interface Props {
64
68
  /** ラジオボタンのname。未指定なら自動で採番する */
65
69
  tabGroupName?: string;
66
- /** selectedTabLabelIdが未指定のとき、trueならdisabledでない最初のタブを選ぶ。falseなら先頭のタブ(disabledなら中身は空になる) */
70
+ /** selectedTabIdが未指定のとき、trueならdisabledでない最初のタブを選ぶ。falseなら先頭のタブ(disabledなら中身は空になる) */
67
71
  selectFirstEnabled?: boolean;
68
72
  /** タブの見出しを並べる部分に付けるclass */
69
73
  headerClass?: ClassValue;
@@ -74,18 +78,13 @@ interface Props {
74
78
  }
75
79
  const props = defineProps<Props>();
76
80
 
77
- interface Emits {
78
- (e: 'emit:changeSelectedTab', labelId: string): void;
79
- }
80
- const emit = defineEmits<Emits>();
81
-
82
81
  /**
83
82
  * 選択するタブのlabelId。未指定なら先頭のタブ
84
- * 親がいつ代入しても選び直せるよう、v-model:selected-tab-label-idで同期する前提とする
83
+ * 親がいつ代入しても選び直せるよう、v-model:selected-tab-idで同期する前提とする
85
84
  */
86
- const selectedLabelId = defineModel<string>('selectedTabLabelId');
85
+ const selectedTabId = defineModel<string>('selectedTabId');
87
86
 
88
- interface Slots {
87
+ export interface Slots {
89
88
  default?(): VNode[];
90
89
  /** 読み込み中のTabContentに重ねて表示する内容。未指定なら既定の読み込み中表示 */
91
90
  loading?(): VNode[];
@@ -94,8 +93,8 @@ const slots = defineSlots<Slots>();
94
93
 
95
94
  /** タブの見出しの描画に使う、TabContentの指定 */
96
95
  interface TabHeader {
97
- labelId: string;
98
- description: string;
96
+ tabId: string;
97
+ label: string;
99
98
  disabled: boolean;
100
99
  status?: TabContentStatus;
101
100
  headerClass?: ClassValue;
@@ -107,7 +106,7 @@ const groupName = props.tabGroupName ?? useId();
107
106
 
108
107
  /**
109
108
  * TabContentのVNodeから見出しの指定を取り出す
110
- * propsのキーはテンプレートの書き方(label-id / labelId)のまま入っているため、両方を見る
109
+ * propsのキーはテンプレートの書き方(tab-id / tabId)のまま入っているため、両方を見る
111
110
  *
112
111
  * @param {VNode} vnode
113
112
  * @returns {TabHeader | undefined}
@@ -120,8 +119,8 @@ const toTabHeader = (vnode: VNode): TabHeader | undefined => {
120
119
  const vnodeProps = vnode.props ?? {};
121
120
  const disabled = vnodeProps.disabled;
122
121
  return {
123
- labelId: String(vnodeProps['label-id'] ?? vnodeProps.labelId),
124
- description: vnodeProps.description,
122
+ tabId: String(vnodeProps['tab-id'] ?? vnodeProps.tabId),
123
+ label: vnodeProps.label,
125
124
  // 属性だけを書いた場合(<TabContent disabled>)は空文字になる
126
125
  disabled: disabled === '' || !!disabled,
127
126
  status: vnodeProps.status,
@@ -148,30 +147,29 @@ const tabs = computed((): TabHeader[] => {
148
147
  });
149
148
 
150
149
  // 選ばれるまでは先頭のタブを選択中とする。スロットは描画中に読む必要があるため、setupの時点では先頭を確定しない
151
- const activeLabelId = computed((): string | undefined => {
152
- if (selectedLabelId.value != null) {
153
- return selectedLabelId.value;
150
+ const activeTabId = computed((): string | undefined => {
151
+ if (selectedTabId.value != null) {
152
+ return selectedTabId.value;
154
153
  }
155
154
  const first = props.selectFirstEnabled ? tabs.value.find(tab => !tab.disabled) : tabs.value[0];
156
- return first?.labelId;
155
+ return first?.tabId;
157
156
  });
158
157
  // タブの中身はactivatedを契機にAPIを呼ぶため、親の画面のonMountedで検索条件が整う前に通知しないよう、マウント後に通知を始める
159
158
  const mounted = ref(false);
160
- provide(tabGroupContextKey, { activeLabelId, mounted, loadingSlot: () => slots.loading });
159
+ provide(tabGroupContextKey, { groupName, activeTabId, mounted, loadingSlot: () => slots.loading });
161
160
 
162
161
  onMounted(() => {
163
162
  mounted.value = true;
164
163
  });
165
164
 
166
- const onChangeTab = (labelId: string) => {
167
- selectedLabelId.value = labelId;
168
- emit('emit:changeSelectedTab', labelId);
165
+ const onChangeTab = (tabId: string) => {
166
+ selectedTabId.value = tabId;
169
167
  };
170
168
  </script>
171
169
 
172
170
  <style>
173
171
  :where(.dwui-tab-group) {
174
- margin-bottom: 8px;
172
+ margin-bottom: 0.67em;
175
173
  }
176
174
 
177
175
  :where(.dwui-tab-headers) {
@@ -179,8 +177,8 @@ const onChangeTab = (labelId: string) => {
179
177
  flex-direction: row;
180
178
  flex-wrap: wrap;
181
179
  position: relative;
182
- margin-top: 5px;
183
- padding-bottom: 4px;
180
+ margin-top: 0.4em;
181
+ padding-bottom: 0.33em;
184
182
  }
185
183
 
186
184
  :where(.dwui-tab-radio) {
@@ -195,7 +193,7 @@ const onChangeTab = (labelId: string) => {
195
193
  :where(.dwui-tab-item) {
196
194
  display: block;
197
195
  min-width: 100px;
198
- padding: 0 4px;
196
+ padding: 0 0.33em;
199
197
  border: 1px solid var(--dwui-border-color-tab, light-dark(#565656, #8a8a8a));
200
198
  border-bottom: 1px solid var(--dwui-border-color-tab-header-accent, #5ab4bd);
201
199
  background: var(--dwui-background-tab-header, linear-gradient(light-dark(#1313c7, #2a3a8f), light-dark(navy, #141c4d)));
@@ -244,8 +242,8 @@ const onChangeTab = (labelId: string) => {
244
242
  }
245
243
 
246
244
  :where(.dwui-tab-radio:focus-visible + .dwui-tab-item) {
247
- outline: var(--dwui-width-focus-ring, 2px) solid var(--dwui-outline-color-focus-ring, Highlight);
248
- outline-offset: var(--dwui-offset-focus-ring, 2px);
245
+ outline: var(--dwui-outline-width-focus-ring, 2px) solid var(--dwui-outline-color-focus-ring, Highlight);
246
+ outline-offset: var(--dwui-outline-offset-focus-ring, 2px);
249
247
  }
250
248
 
251
249
  :where(.dwui-tab-items) {