@digitalwalletcorp/vue-core 1.2.1 → 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.
- package/README.md +79 -3
- package/lib/accordion-fieldset.d.cts +14 -4
- package/lib/accordion-fieldset.d.ts +14 -4
- package/lib/accordion-section.d.cts +20 -10
- package/lib/accordion-section.d.ts +20 -10
- package/lib/components-2zQ2SZuY.js +26 -0
- package/lib/components-HaiHcseF.cjs +1 -0
- package/lib/components.d.cts +10 -0
- package/lib/components.d.ts +10 -0
- package/lib/copy-button.d.cts +34 -0
- package/lib/copy-button.d.ts +34 -0
- package/lib/dark-mode-selector.d.cts +32 -0
- package/lib/dark-mode-selector.d.ts +32 -0
- package/lib/data-table.d.cts +30 -0
- package/lib/data-table.d.ts +30 -0
- package/lib/help-button.d.cts +39 -0
- package/lib/help-button.d.ts +39 -0
- package/lib/help-popper.d.cts +39 -0
- package/lib/help-popper.d.ts +39 -0
- package/lib/helpers/data-table-csv.d.cts +17 -0
- package/lib/helpers/data-table-csv.d.ts +17 -0
- package/lib/helpers/show-floating-component.d.cts +82 -0
- package/lib/helpers/show-floating-component.d.ts +82 -0
- package/lib/helpers/show-modal-dialog.d.cts +73 -0
- package/lib/helpers/show-modal-dialog.d.ts +73 -0
- package/lib/helpers/show-overlay-box.d.cts +53 -0
- package/lib/helpers/show-overlay-box.d.ts +53 -0
- package/lib/helpers/show-screen-loading.d.cts +20 -0
- package/lib/helpers/show-screen-loading.d.ts +20 -0
- package/lib/helpers/show-snackbar.d.cts +47 -0
- package/lib/helpers/show-snackbar.d.ts +47 -0
- package/lib/hourglass-loading.d.cts +1 -1
- package/lib/hourglass-loading.d.ts +1 -1
- package/lib/index.cjs +1 -1
- package/lib/index.d.cts +34 -0
- package/lib/index.d.ts +34 -0
- package/lib/index.js +445 -18
- package/lib/internal/anchored-popover.d.cts +35 -0
- package/lib/internal/anchored-popover.d.ts +35 -0
- package/lib/internal/anchored-position.d.cts +50 -0
- package/lib/internal/anchored-position.d.ts +50 -0
- package/lib/internal/data-table-controls.d.cts +42 -0
- package/lib/internal/data-table-controls.d.ts +42 -0
- package/lib/internal/data-table-value.d.cts +32 -0
- package/lib/internal/data-table-value.d.ts +32 -0
- package/lib/internal/floating-component.d.cts +52 -0
- package/lib/internal/floating-component.d.ts +52 -0
- package/lib/internal/icon-popper.d.cts +32 -0
- package/lib/internal/icon-popper.d.ts +32 -0
- package/lib/internal/mount-window.d.cts +39 -0
- package/lib/internal/mount-window.d.ts +39 -0
- package/lib/internal/movable-window.d.cts +55 -0
- package/lib/internal/movable-window.d.ts +55 -0
- package/lib/internal/opened-overlay-boxes.d.cts +2 -0
- package/lib/internal/opened-overlay-boxes.d.ts +2 -0
- package/lib/internal/pagination.d.cts +22 -0
- package/lib/internal/pagination.d.ts +22 -0
- package/lib/internal/snackbar-container.d.cts +20 -0
- package/lib/internal/snackbar-container.d.ts +20 -0
- package/lib/internal/snackbar-item.d.cts +17 -0
- package/lib/internal/snackbar-item.d.ts +17 -0
- package/lib/internal/tab-group-context.d.cts +4 -2
- package/lib/internal/tab-group-context.d.ts +4 -2
- package/lib/internal/use-movable-window.d.cts +44 -0
- package/lib/internal/use-movable-window.d.ts +44 -0
- package/lib/internal/window-order.d.cts +6 -0
- package/lib/internal/window-order.d.ts +6 -0
- package/lib/intersection-lazy-component.d.cts +32 -0
- package/lib/intersection-lazy-component.d.ts +32 -0
- package/lib/label-button.d.cts +2 -2
- package/lib/label-button.d.ts +2 -2
- package/lib/label-icon.d.cts +2 -2
- package/lib/label-icon.d.ts +2 -2
- package/lib/modal-dialog.d.cts +67 -0
- package/lib/modal-dialog.d.ts +67 -0
- package/lib/nuxt.cjs +1 -1
- package/lib/nuxt.js +1 -1
- package/lib/overlay-box.d.cts +52 -0
- package/lib/overlay-box.d.ts +52 -0
- package/lib/page-section.d.cts +2 -2
- package/lib/page-section.d.ts +2 -2
- package/lib/page-title.d.cts +2 -2
- package/lib/page-title.d.ts +2 -2
- package/lib/register.cjs +1 -1
- package/lib/register.js +1 -1
- package/lib/screen-loading.d.cts +7 -0
- package/lib/screen-loading.d.ts +7 -0
- package/lib/style.css +1 -1
- package/lib/tab-content.d.cts +5 -5
- package/lib/tab-content.d.ts +5 -5
- package/lib/tab-group.d.cts +7 -10
- package/lib/tab-group.d.ts +7 -10
- package/lib/types/data-table.d.cts +185 -0
- package/lib/types/data-table.d.ts +185 -0
- package/lib/types/modal-dialog.d.cts +13 -0
- package/lib/types/modal-dialog.d.ts +13 -0
- package/lib/types/movable-window.d.cts +16 -0
- package/lib/types/movable-window.d.ts +16 -0
- package/lib/warning-popper.d.cts +39 -0
- package/lib/warning-popper.d.ts +39 -0
- package/lib/warning-popper.vue_vue_type_style_index_0_lang-CxVzqsbn.cjs +1 -0
- package/lib/warning-popper.vue_vue_type_style_index_0_lang-zt-jCsvz.js +1833 -0
- package/package.json +40 -14
- package/src/accordion-fieldset.vue +24 -19
- package/src/accordion-section.vue +33 -28
- package/src/components.ts +10 -0
- package/src/copy-button.vue +89 -0
- package/src/dark-mode-selector.vue +212 -0
- package/src/data-table.vue +643 -0
- package/src/help-button.vue +128 -0
- package/src/help-popper.vue +70 -0
- package/src/helpers/data-table-csv.ts +55 -0
- package/src/helpers/show-floating-component.ts +169 -0
- package/src/helpers/show-modal-dialog.ts +121 -0
- package/src/helpers/show-overlay-box.ts +106 -0
- package/src/helpers/show-screen-loading.ts +37 -0
- package/src/helpers/show-snackbar.ts +153 -0
- package/src/hourglass-loading.vue +1 -1
- package/src/index.ts +34 -0
- package/src/internal/anchored-popover.vue +192 -0
- package/src/internal/anchored-position.ts +135 -0
- package/src/internal/data-table-controls.vue +203 -0
- package/src/internal/data-table-value.ts +99 -0
- package/src/internal/floating-component.vue +202 -0
- package/src/internal/icon-popper.vue +185 -0
- package/src/internal/mount-window.ts +110 -0
- package/src/internal/movable-window.vue +315 -0
- package/src/internal/opened-overlay-boxes.ts +2 -0
- package/src/internal/pagination.vue +325 -0
- package/src/internal/snackbar-container.vue +258 -0
- package/src/internal/snackbar-item.ts +20 -0
- package/src/internal/tab-group-context.ts +4 -2
- package/src/internal/use-movable-window.ts +208 -0
- package/src/internal/window-order.ts +11 -0
- package/src/intersection-lazy-component.vue +84 -0
- package/src/label-button.vue +9 -6
- package/src/label-icon.vue +2 -2
- package/src/modal-dialog.vue +287 -0
- package/src/overlay-box.vue +218 -0
- package/src/page-section.vue +7 -7
- package/src/page-title.vue +4 -4
- package/src/screen-loading.vue +90 -0
- package/src/tab-content.vue +16 -15
- package/src/tab-group.vue +34 -36
- package/src/types/data-table.ts +196 -0
- package/src/types/modal-dialog.ts +16 -0
- package/src/types/movable-window.ts +18 -0
- package/src/warning-popper.vue +70 -0
- package/lib/accordion-fieldset.cjs +0 -1
- package/lib/accordion-fieldset.js +0 -4
- package/lib/accordion-fieldset.vue_vue_type_style_index_0_lang-Cf4czDXz.js +0 -66
- package/lib/accordion-fieldset.vue_vue_type_style_index_0_lang-DoM5QjpJ.cjs +0 -1
- package/lib/accordion-section.cjs +0 -1
- package/lib/accordion-section.js +0 -4
- package/lib/accordion-section.vue_vue_type_style_index_0_lang-CFDdUEnn.cjs +0 -1
- package/lib/accordion-section.vue_vue_type_style_index_0_lang-Gvjjx02P.js +0 -78
- package/lib/components-C63HJoCn.cjs +0 -1
- package/lib/components-q4Cf97bO.js +0 -24
- package/lib/hourglass-loading.cjs +0 -1
- package/lib/hourglass-loading.js +0 -4
- package/lib/hourglass-loading.vue_vue_type_style_index_0_lang-C72UVA9W.cjs +0 -1
- package/lib/hourglass-loading.vue_vue_type_style_index_0_lang-DkUGIxQ2.js +0 -22
- package/lib/label-button.cjs +0 -1
- package/lib/label-button.js +0 -4
- package/lib/label-button.vue_vue_type_style_index_0_lang-D7ZgfZOL.cjs +0 -1
- package/lib/label-button.vue_vue_type_style_index_0_lang-DqRYddVt.js +0 -44
- package/lib/label-icon.cjs +0 -1
- package/lib/label-icon.js +0 -4
- package/lib/label-icon.vue_vue_type_style_index_0_lang-D9Cimu20.cjs +0 -1
- package/lib/label-icon.vue_vue_type_style_index_0_lang-me52HE2V.js +0 -38
- package/lib/page-section.cjs +0 -1
- package/lib/page-section.js +0 -4
- package/lib/page-section.vue_vue_type_style_index_0_lang-DEAkTLxd.cjs +0 -1
- package/lib/page-section.vue_vue_type_style_index_0_lang-DpVQe_rV.js +0 -54
- package/lib/page-title.cjs +0 -1
- package/lib/page-title.js +0 -4
- package/lib/page-title.vue_vue_type_style_index_0_lang-BqIsIks5.cjs +0 -1
- package/lib/page-title.vue_vue_type_style_index_0_lang-ChZot85K.js +0 -36
- package/lib/tab-content.cjs +0 -1
- package/lib/tab-content.js +0 -4
- package/lib/tab-content.vue_vue_type_style_index_0_lang-BeCi7Xkp.cjs +0 -1
- package/lib/tab-content.vue_vue_type_style_index_0_lang-CcQLHmIF.js +0 -44
- package/lib/tab-group.cjs +0 -1
- package/lib/tab-group.js +0 -4
- package/lib/tab-group.vue_vue_type_style_index_0_lang-C84mDEOC.js +0 -103
- package/lib/tab-group.vue_vue_type_style_index_0_lang-CMaufRJV.cjs +0 -1
- package/lib/use-shrinkable-CS2bmsJJ.js +0 -17
- 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>
|
package/src/page-section.vue
CHANGED
|
@@ -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:
|
|
89
|
-
padding-top:
|
|
90
|
-
font-size: var(--dwui-font-size-page-section,
|
|
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,
|
|
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:
|
|
101
|
+
margin: 0.67em 0 0.67em 1.33em;
|
|
102
102
|
}
|
|
103
103
|
</style>
|
package/src/page-title.vue
CHANGED
|
@@ -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:
|
|
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,
|
|
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>
|
package/src/tab-content.vue
CHANGED
|
@@ -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
|
-
|
|
9
|
-
:aria-
|
|
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
|
-
:
|
|
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
|
-
//
|
|
42
|
-
interface Props {
|
|
43
|
-
/** タブを識別するID。TabGroupの
|
|
44
|
-
|
|
42
|
+
// tabId・label・header*・status・#iconは、TabGroupが見出しを描画するために読む
|
|
43
|
+
export interface Props {
|
|
44
|
+
/** タブを識別するID。TabGroupのselectedTabIdに渡す値 */
|
|
45
|
+
tabId: string;
|
|
45
46
|
/** タブの見出しの文字 */
|
|
46
|
-
|
|
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?.
|
|
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[
|
|
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:
|
|
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
|
|
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.
|
|
19
|
+
:key="tab.tabId"
|
|
19
20
|
>
|
|
20
21
|
<input
|
|
21
|
-
:id="`${groupName}-${tab.
|
|
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.
|
|
27
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
/**
|
|
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-
|
|
83
|
+
* 親がいつ代入しても選び直せるよう、v-model:selected-tab-idで同期する前提とする
|
|
85
84
|
*/
|
|
86
|
-
const
|
|
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
|
-
|
|
98
|
-
|
|
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のキーはテンプレートの書き方(
|
|
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
|
-
|
|
124
|
-
|
|
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
|
|
152
|
-
if (
|
|
153
|
-
return
|
|
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?.
|
|
155
|
+
return first?.tabId;
|
|
157
156
|
});
|
|
158
157
|
// タブの中身はactivatedを契機にAPIを呼ぶため、親の画面のonMountedで検索条件が整う前に通知しないよう、マウント後に通知を始める
|
|
159
158
|
const mounted = ref(false);
|
|
160
|
-
provide(tabGroupContextKey, {
|
|
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 = (
|
|
167
|
-
|
|
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:
|
|
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:
|
|
183
|
-
padding-bottom:
|
|
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
|
|
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) {
|