@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,153 @@
|
|
|
1
|
+
import { h, render, shallowReactive } from 'vue';
|
|
2
|
+
import type { ClassValue, StyleValue } from 'vue';
|
|
3
|
+
import SnackbarContainer from '@/internal/snackbar-container.vue';
|
|
4
|
+
import type { SnackbarDirection, SnackbarItem, SnackbarPlacement } from '@/internal/snackbar-item';
|
|
5
|
+
|
|
6
|
+
/** snackbar が閉じた理由。click はクリック、duration は表示時間の経過、clear は一括消去ボタンと closeAllSnackbars()、handle は SnackbarHandle の close() */
|
|
7
|
+
export type SnackbarCloseReason = 'click' | 'duration' | 'clear' | 'handle';
|
|
8
|
+
|
|
9
|
+
export interface ShowSnackbarOptions {
|
|
10
|
+
/** 見出し */
|
|
11
|
+
title?: string;
|
|
12
|
+
/** 見出しの右側に出す短い文字 */
|
|
13
|
+
label?: string;
|
|
14
|
+
/** 右下に出す時刻。整形済みの文字列を渡す */
|
|
15
|
+
timestamp?: string;
|
|
16
|
+
/** 自動で閉じるまでの時間(ミリ秒)。未指定ならクリックするまで閉じない */
|
|
17
|
+
duration?: number;
|
|
18
|
+
/** 表示する位置 */
|
|
19
|
+
placement?: SnackbarPlacement;
|
|
20
|
+
/** 同じ表示領域に並べる向き。overlap は同じ位置に重ねて新しいものを前面に出し、column は縦に積み、row は横に並べて端で折り返す */
|
|
21
|
+
direction?: SnackbarDirection;
|
|
22
|
+
/** 同じ表示領域に一括消去ボタンを出す */
|
|
23
|
+
clearable?: boolean;
|
|
24
|
+
/** 一括消去ボタンの読み上げ名 */
|
|
25
|
+
clearLabel?: string;
|
|
26
|
+
/** snackbar に付けるクラス */
|
|
27
|
+
class?: ClassValue;
|
|
28
|
+
/** snackbar に付けるスタイル */
|
|
29
|
+
style?: StyleValue;
|
|
30
|
+
/** snackbar が閉じたときに呼ぶ関数 */
|
|
31
|
+
onClose?: (reason: SnackbarCloseReason) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** showSnackbar が返す、表示中の snackbar を閉じるためのハンドル */
|
|
35
|
+
export interface SnackbarHandle {
|
|
36
|
+
/** snackbar を閉じる */
|
|
37
|
+
close: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface SnackbarEntry {
|
|
41
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
42
|
+
onClose?: (reason: SnackbarCloseReason) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** まとめて閉じるときに1件ずつずらす間隔(ms) */
|
|
46
|
+
const CLEAR_INTERVAL = 50;
|
|
47
|
+
|
|
48
|
+
const containers = new Map<string, SnackbarItem[]>();
|
|
49
|
+
const entries = new Map<number, SnackbarEntry>();
|
|
50
|
+
let lastId = 0;
|
|
51
|
+
|
|
52
|
+
const removeItem = (items: SnackbarItem[], id: number, reason: SnackbarCloseReason): void => {
|
|
53
|
+
const index = items.findIndex((item) => item.id === id);
|
|
54
|
+
if (index < 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
items.splice(index, 1);
|
|
58
|
+
const entry = entries.get(id);
|
|
59
|
+
entries.delete(id);
|
|
60
|
+
clearTimeout(entry?.timer);
|
|
61
|
+
entry?.onClose?.(reason);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const clearItems = (items: SnackbarItem[]): void => {
|
|
65
|
+
const ids = items.map((item) => item.id);
|
|
66
|
+
for (let i = 0; i < ids.length; i++) {
|
|
67
|
+
setTimeout(() => removeItem(items, ids[i], 'clear'), CLEAR_INTERVAL * i);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* placement と direction の組み合わせごとの表示領域に並べる snackbar の一覧を返す。
|
|
73
|
+
* 表示領域が無ければ body の末尾に作り、空になったら取り外す
|
|
74
|
+
*
|
|
75
|
+
* @param {SnackbarPlacement} placement
|
|
76
|
+
* @param {SnackbarDirection} direction
|
|
77
|
+
* @returns {SnackbarItem[]}
|
|
78
|
+
*/
|
|
79
|
+
const openContainer = (placement: SnackbarPlacement, direction: SnackbarDirection): SnackbarItem[] => {
|
|
80
|
+
const key = `${placement} ${direction}`;
|
|
81
|
+
const opened = containers.get(key);
|
|
82
|
+
if (opened) {
|
|
83
|
+
return opened;
|
|
84
|
+
}
|
|
85
|
+
const container = document.createElement('div');
|
|
86
|
+
document.body.appendChild(container);
|
|
87
|
+
const items = shallowReactive<SnackbarItem[]>([]);
|
|
88
|
+
containers.set(key, items);
|
|
89
|
+
|
|
90
|
+
render(h(SnackbarContainer, {
|
|
91
|
+
placement,
|
|
92
|
+
direction,
|
|
93
|
+
items,
|
|
94
|
+
'onEmit:close': (id: number) => removeItem(items, id, 'click'),
|
|
95
|
+
'onEmit:clear': () => clearItems(items),
|
|
96
|
+
'onEmit:afterLeave': () => {
|
|
97
|
+
if (items.length === 0 && containers.get(key) === items) {
|
|
98
|
+
containers.delete(key);
|
|
99
|
+
render(null, container);
|
|
100
|
+
container.remove();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}), container);
|
|
104
|
+
return items;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* snackbar を表示する。
|
|
109
|
+
* placement と direction の組み合わせが同じ snackbar は同じ表示領域に並び、新しいものが末尾(overlap では前面)に加わる。
|
|
110
|
+
* クリックすると閉じる。ブラウザでのみ動作する
|
|
111
|
+
*
|
|
112
|
+
* @param {string} message 表示する文字列
|
|
113
|
+
* @param {ShowSnackbarOptions} [options]
|
|
114
|
+
* @returns {SnackbarHandle}
|
|
115
|
+
*/
|
|
116
|
+
export function showSnackbar(message: string, options?: ShowSnackbarOptions): SnackbarHandle {
|
|
117
|
+
const {
|
|
118
|
+
placement = 'bottom',
|
|
119
|
+
direction = 'overlap',
|
|
120
|
+
duration,
|
|
121
|
+
clearable = false,
|
|
122
|
+
clearLabel = 'Clear all',
|
|
123
|
+
onClose,
|
|
124
|
+
...content
|
|
125
|
+
} = options ?? {};
|
|
126
|
+
// 読み上げられるよう、表示領域が DOM に入ってから中身を加える
|
|
127
|
+
const items = openContainer(placement, direction);
|
|
128
|
+
lastId += 1;
|
|
129
|
+
const id = lastId;
|
|
130
|
+
items.push({
|
|
131
|
+
...content,
|
|
132
|
+
id,
|
|
133
|
+
message,
|
|
134
|
+
clearable,
|
|
135
|
+
clearLabel
|
|
136
|
+
});
|
|
137
|
+
entries.set(id, {
|
|
138
|
+
timer: duration ? setTimeout(() => removeItem(items, id, 'duration'), duration) : undefined,
|
|
139
|
+
onClose
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
close: () => removeItem(items, id, 'handle')
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 表示中の snackbar をすべて閉じる。表示領域ごとに、古い順に少しずつずらして閉じる
|
|
148
|
+
*/
|
|
149
|
+
export function closeAllSnackbars(): void {
|
|
150
|
+
for (const items of containers.values()) {
|
|
151
|
+
clearItems(items);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -16,7 +16,7 @@ CSSの :empty に依存しないため「取得成功したが値が空」のケ
|
|
|
16
16
|
<script setup lang="ts">
|
|
17
17
|
import { SvgHourglass } from '@digitalwalletcorp/vue-svg-icons';
|
|
18
18
|
|
|
19
|
-
interface Props {
|
|
19
|
+
export interface Props {
|
|
20
20
|
/** ローディング表示するか */
|
|
21
21
|
loading: boolean;
|
|
22
22
|
/** 砂が落ち切るまでの秒数。未指定ならSvgHourglassの既定値に任せる */
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
1
|
export * from '@/components';
|
|
2
|
+
export type { Props as AccordionFieldsetProps, Slots as AccordionFieldsetSlots } from '@/accordion-fieldset.vue';
|
|
3
|
+
export type { Props as AccordionSectionProps, Slots as AccordionSectionSlots } from '@/accordion-section.vue';
|
|
4
|
+
export type { Props as CopyButtonProps, Emits as CopyButtonEmits, Slots as CopyButtonSlots } from '@/copy-button.vue';
|
|
5
|
+
export type { Props as DarkModeSelectorProps, Emits as DarkModeSelectorEmits, Slots as DarkModeSelectorSlots } from '@/dark-mode-selector.vue';
|
|
6
|
+
export type { Props as HelpButtonProps, Emits as HelpButtonEmits, Slots as HelpButtonSlots } from '@/help-button.vue';
|
|
7
|
+
export type { Props as HelpPopperProps, Emits as HelpPopperEmits, Slots as HelpPopperSlots } from '@/help-popper.vue';
|
|
8
|
+
export type { Props as HourglassLoadingProps } from '@/hourglass-loading.vue';
|
|
9
|
+
export type { Props as IntersectionLazyComponentProps, Emits as IntersectionLazyComponentEmits, Slots as IntersectionLazyComponentSlots } from '@/intersection-lazy-component.vue';
|
|
10
|
+
export type { Props as LabelButtonProps, Slots as LabelButtonSlots } from '@/label-button.vue';
|
|
11
|
+
export type { Props as LabelIconProps, Slots as LabelIconSlots } from '@/label-icon.vue';
|
|
12
|
+
export type { Props as ModalDialogProps, Emits as ModalDialogEmits, Slots as ModalDialogSlots } from '@/modal-dialog.vue';
|
|
13
|
+
export type { Props as OverlayBoxProps, Emits as OverlayBoxEmits, Slots as OverlayBoxSlots } from '@/overlay-box.vue';
|
|
14
|
+
export type { Props as PageSectionProps, Slots as PageSectionSlots } from '@/page-section.vue';
|
|
15
|
+
export type { Props as PageTitleProps, Slots as PageTitleSlots } from '@/page-title.vue';
|
|
16
|
+
export type { Props as ScreenLoadingProps } from '@/screen-loading.vue';
|
|
17
|
+
export type { Props as TabContentProps, Slots as TabContentSlots } from '@/tab-content.vue';
|
|
18
|
+
export type { Props as TabGroupProps, Slots as TabGroupSlots } from '@/tab-group.vue';
|
|
19
|
+
export type { Props as WarningPopperProps, Emits as WarningPopperEmits, Slots as WarningPopperSlots } from '@/warning-popper.vue';
|
|
20
|
+
export { headersToCsvColumns, rowToCsvColumns } from '@/helpers/data-table-csv';
|
|
21
|
+
export type { DataTableControlsPlacement, DataTableCsvColumn, DataTableEmits, DataTableHeader, DataTableLabels, DataTableProps, DataTableResolvedLabels, DataTableSlots, DataTableSortContext, DataTableSortMode, DataTableValueType } from '@/types/data-table';
|
|
22
|
+
export { closeAllFloatingComponents, showFloatingComponent, showFloatingMessage, useFloatingComponent } from '@/helpers/show-floating-component';
|
|
23
|
+
export type { FloatingComponentContentProps, FloatingComponentHandle, ShowFloatingComponentOptions } from '@/helpers/show-floating-component';
|
|
24
|
+
export { showConfirmationDialog, showErrorDialog, showInformationDialog, showModalDialog, showWarningDialog } from '@/helpers/show-modal-dialog';
|
|
25
|
+
export type { ShowModalDialogOptions } from '@/helpers/show-modal-dialog';
|
|
26
|
+
export type { ModalDialogButton, ModalDialogImage, ModalDialogResult, ModalDialogType } from '@/types/modal-dialog';
|
|
27
|
+
export { closeAllOverlayBoxes, showOverlayBox, useOverlayBox } from '@/helpers/show-overlay-box';
|
|
28
|
+
export type { OverlayBoxContentProps, OverlayBoxHandle, ShowOverlayBoxOptions } from '@/helpers/show-overlay-box';
|
|
29
|
+
export { showScreenLoading } from '@/helpers/show-screen-loading';
|
|
30
|
+
export type { ScreenLoadingHandle, ShowScreenLoadingOptions } from '@/helpers/show-screen-loading';
|
|
31
|
+
export { closeAllSnackbars, showSnackbar } from '@/helpers/show-snackbar';
|
|
32
|
+
export type { ShowSnackbarOptions, SnackbarCloseReason, SnackbarHandle } from '@/helpers/show-snackbar';
|
|
33
|
+
export type { SnackbarDirection, SnackbarPlacement } from '@/internal/snackbar-item';
|
|
2
34
|
export type { LabelPreset } from '@/types/label-preset';
|
|
35
|
+
export type { MovableWindowContentProps, MovableWindowDragMode, MovableWindowHandle } from '@/types/movable-window';
|
|
3
36
|
export type { TabContentStatus } from '@/internal/tab-group-context';
|
|
37
|
+
export type { PopoverPlacement } from '@/internal/anchored-position';
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
基準要素の近くに出すパネル
|
|
3
|
+
popover 属性で最前面に描画するので、親の overflow や z-index の影響を受けない。
|
|
4
|
+
表示位置は開いた時点で寸法を測って決めるため、位置が決まるまでパネルは画面に表示しない。
|
|
5
|
+
開いている間はスクロール・リサイズのたびに基準要素の位置に合わせて置き直す。
|
|
6
|
+
-->
|
|
7
|
+
<template>
|
|
8
|
+
<div
|
|
9
|
+
ref="refPanel"
|
|
10
|
+
class="dwui-anchored-popover"
|
|
11
|
+
:class="{ 'dwui-arrow': props.arrow }"
|
|
12
|
+
:popover="props.mode"
|
|
13
|
+
:data-side="side"
|
|
14
|
+
@toggle="onTogglePopover"
|
|
15
|
+
>
|
|
16
|
+
<div class="dwui-anchored-popover-content">
|
|
17
|
+
<slot />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { onBeforeUnmount, ref } from 'vue';
|
|
24
|
+
import { computeAnchoredPosition } from '@/internal/anchored-position';
|
|
25
|
+
import type { PopoverPlacement, Side } from '@/internal/anchored-position';
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
/** 基準要素 */
|
|
29
|
+
anchor?: HTMLElement;
|
|
30
|
+
/** 基準要素に対するパネルの表示位置。収まらないときは反対側に出す */
|
|
31
|
+
placement: PopoverPlacement;
|
|
32
|
+
/** 基準要素を指す矢印を付ける */
|
|
33
|
+
arrow: boolean;
|
|
34
|
+
/** popover 属性の値。auto は外側のクリックや Esc で閉じ、manual は利用側が閉じる */
|
|
35
|
+
mode: 'auto' | 'manual';
|
|
36
|
+
}
|
|
37
|
+
const props = defineProps<Props>();
|
|
38
|
+
|
|
39
|
+
interface Emits {
|
|
40
|
+
(e: 'emit:open'): void;
|
|
41
|
+
(e: 'emit:close'): void;
|
|
42
|
+
}
|
|
43
|
+
const emit = defineEmits<Emits>();
|
|
44
|
+
|
|
45
|
+
const refPanel = ref<HTMLElement>();
|
|
46
|
+
const side = ref<Side>();
|
|
47
|
+
|
|
48
|
+
/** 基準要素とパネルの間隔(px) */
|
|
49
|
+
const gap = 4;
|
|
50
|
+
/** 矢印がパネルからはみ出す高さ(px)。CSS の矢印の寸法と合わせる */
|
|
51
|
+
const arrowHeight = 7;
|
|
52
|
+
/** 矢印の中心をパネルの端から離す距離(px)。角丸に矢印がかからないようにする */
|
|
53
|
+
const arrowMargin = 12;
|
|
54
|
+
/** ビューポートの端との最小の余白(px) */
|
|
55
|
+
const margin = 8;
|
|
56
|
+
|
|
57
|
+
const hide = (): void => {
|
|
58
|
+
refPanel.value?.hidePopover();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const show = (): void => {
|
|
62
|
+
refPanel.value?.showPopover();
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const place = (): void => {
|
|
66
|
+
const panel = refPanel.value;
|
|
67
|
+
if (!props.anchor || !panel) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const viewport = {
|
|
71
|
+
width: document.documentElement.clientWidth,
|
|
72
|
+
height: document.documentElement.clientHeight
|
|
73
|
+
};
|
|
74
|
+
const position = computeAnchoredPosition(props.anchor.getBoundingClientRect(), panel.getBoundingClientRect(), viewport, {
|
|
75
|
+
placement: props.placement,
|
|
76
|
+
offset: props.arrow ? gap + arrowHeight : gap,
|
|
77
|
+
margin,
|
|
78
|
+
arrowMargin
|
|
79
|
+
});
|
|
80
|
+
side.value = position.side;
|
|
81
|
+
panel.style.top = `${position.top}px`;
|
|
82
|
+
panel.style.left = `${position.left}px`;
|
|
83
|
+
panel.style.setProperty('--dwui-offset-arrow', `${position.arrow}px`);
|
|
84
|
+
panel.style.visibility = 'visible';
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// パネルの中のスクロールでは基準要素が動かないので置き直さない
|
|
88
|
+
const onScroll = (event: Event): void => {
|
|
89
|
+
if (!refPanel.value?.contains(event.target as Node)) {
|
|
90
|
+
place();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const onTogglePopover = (event: ToggleEvent): void => {
|
|
95
|
+
if (event.newState === 'open') {
|
|
96
|
+
place();
|
|
97
|
+
window.addEventListener('scroll', onScroll, true);
|
|
98
|
+
window.addEventListener('resize', place);
|
|
99
|
+
emit('emit:open');
|
|
100
|
+
} else {
|
|
101
|
+
if (refPanel.value) {
|
|
102
|
+
refPanel.value.style.visibility = '';
|
|
103
|
+
}
|
|
104
|
+
window.removeEventListener('scroll', onScroll, true);
|
|
105
|
+
window.removeEventListener('resize', place);
|
|
106
|
+
emit('emit:close');
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
onBeforeUnmount(() => {
|
|
111
|
+
window.removeEventListener('scroll', onScroll, true);
|
|
112
|
+
window.removeEventListener('resize', place);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
defineExpose({
|
|
116
|
+
/** パネルの要素。invoker の設定や、要素がパネルの中かの判定に使う */
|
|
117
|
+
element: refPanel,
|
|
118
|
+
show,
|
|
119
|
+
hide
|
|
120
|
+
});
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<style>
|
|
124
|
+
/*
|
|
125
|
+
* 色や余白は --dwui-*-anchored-popover で受け取る。利用側のコンポーネントがこの変数に自分の公開変数を割り当てる
|
|
126
|
+
* 矢印の分だけ overflow を visible にし、中身のスクロールは内側の要素で行う
|
|
127
|
+
*/
|
|
128
|
+
:where(.dwui-anchored-popover) {
|
|
129
|
+
inset: auto;
|
|
130
|
+
display: flex;
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
box-sizing: border-box;
|
|
133
|
+
max-width: var(--dwui-max-width-anchored-popover, calc(100vw - 16px));
|
|
134
|
+
max-height: var(--dwui-max-height-anchored-popover, calc(100vh - 16px));
|
|
135
|
+
margin: 0;
|
|
136
|
+
padding: 0;
|
|
137
|
+
border: 1px solid var(--dwui-border-color-anchored-popover, light-dark(#8f8f8f, #6b6f76));
|
|
138
|
+
border-radius: var(--dwui-radius-anchored-popover, 4px);
|
|
139
|
+
background: var(--dwui-background-anchored-popover, Canvas);
|
|
140
|
+
color: var(--dwui-color-text-anchored-popover, CanvasText);
|
|
141
|
+
box-shadow: var(--dwui-box-shadow-anchored-popover, 0 2px 8px rgba(0, 0, 0, 0.25));
|
|
142
|
+
font: inherit;
|
|
143
|
+
visibility: hidden;
|
|
144
|
+
overflow: visible;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
:where(.dwui-anchored-popover-content) {
|
|
148
|
+
min-height: 0;
|
|
149
|
+
padding: var(--dwui-padding-anchored-popover, 0.67em 1em);
|
|
150
|
+
overflow: auto;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* 矢印は 45 度回転した正方形の外側の 2 辺だけを見せる。--dwui-offset-arrow は開くたびに JS が設定する */
|
|
154
|
+
:where(.dwui-anchored-popover.dwui-arrow)::before {
|
|
155
|
+
content: '';
|
|
156
|
+
position: absolute;
|
|
157
|
+
width: 10px;
|
|
158
|
+
height: 10px;
|
|
159
|
+
box-sizing: border-box;
|
|
160
|
+
border: 1px solid var(--dwui-border-color-anchored-popover, light-dark(#8f8f8f, #6b6f76));
|
|
161
|
+
background: var(--dwui-background-anchored-popover-arrow, var(--dwui-background-anchored-popover, Canvas));
|
|
162
|
+
transform: rotate(45deg);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
:where(.dwui-anchored-popover.dwui-arrow[data-side='bottom'])::before {
|
|
166
|
+
top: -6px;
|
|
167
|
+
left: calc(var(--dwui-offset-arrow) - 6px);
|
|
168
|
+
border-right-color: transparent;
|
|
169
|
+
border-bottom-color: transparent;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
:where(.dwui-anchored-popover.dwui-arrow[data-side='top'])::before {
|
|
173
|
+
bottom: -6px;
|
|
174
|
+
left: calc(var(--dwui-offset-arrow) - 6px);
|
|
175
|
+
border-top-color: transparent;
|
|
176
|
+
border-left-color: transparent;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
:where(.dwui-anchored-popover.dwui-arrow[data-side='right'])::before {
|
|
180
|
+
top: calc(var(--dwui-offset-arrow) - 6px);
|
|
181
|
+
left: -6px;
|
|
182
|
+
border-top-color: transparent;
|
|
183
|
+
border-right-color: transparent;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
:where(.dwui-anchored-popover.dwui-arrow[data-side='left'])::before {
|
|
187
|
+
top: calc(var(--dwui-offset-arrow) - 6px);
|
|
188
|
+
right: -6px;
|
|
189
|
+
border-bottom-color: transparent;
|
|
190
|
+
border-left-color: transparent;
|
|
191
|
+
}
|
|
192
|
+
</style>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 基準要素(アンカー)に対するパネルの配置。向きと揃えをハイフンでつなぐ
|
|
3
|
+
* 向きだけを指定したときは中央揃え
|
|
4
|
+
*/
|
|
5
|
+
export type PopoverPlacement =
|
|
6
|
+
| 'top' | 'top-start' | 'top-end'
|
|
7
|
+
| 'bottom' | 'bottom-start' | 'bottom-end'
|
|
8
|
+
| 'left' | 'left-start' | 'left-end'
|
|
9
|
+
| 'right' | 'right-start' | 'right-end';
|
|
10
|
+
|
|
11
|
+
export type Side = 'top' | 'bottom' | 'left' | 'right';
|
|
12
|
+
type Align = 'start' | 'center' | 'end';
|
|
13
|
+
|
|
14
|
+
/** ビューポート座標の矩形 */
|
|
15
|
+
export interface AnchorRect {
|
|
16
|
+
top: number;
|
|
17
|
+
left: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Size {
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AnchoredPositionOptions {
|
|
28
|
+
/** 配置 */
|
|
29
|
+
placement: PopoverPlacement;
|
|
30
|
+
/** アンカーとパネルの間隔 */
|
|
31
|
+
offset: number;
|
|
32
|
+
/** ビューポートの端との最小の余白 */
|
|
33
|
+
margin: number;
|
|
34
|
+
/** 矢印の中心をパネルの端からどれだけ内側に収めるか */
|
|
35
|
+
arrowMargin: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AnchoredPosition {
|
|
39
|
+
/** パネルの上端のビューポート座標 */
|
|
40
|
+
top: number;
|
|
41
|
+
/** パネルの左端のビューポート座標 */
|
|
42
|
+
left: number;
|
|
43
|
+
/** 実際に配置した向き。収まらずに反転した場合は指定と異なる */
|
|
44
|
+
side: Side;
|
|
45
|
+
/** 矢印の中心の位置。上下に出したときはパネルの左端から、左右に出したときは上端からの距離 */
|
|
46
|
+
arrow: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const opposite: Record<Side, Side> = {
|
|
50
|
+
top: 'bottom',
|
|
51
|
+
bottom: 'top',
|
|
52
|
+
left: 'right',
|
|
53
|
+
right: 'left'
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const clamp = (value: number, min: number, max: number): number => Math.min(Math.max(value, min), max);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* アンカーに対するパネルの配置座標を計算する
|
|
60
|
+
* 指定した向きに収まらないときは、反対側の方が広ければ反対側に出す。
|
|
61
|
+
* 揃えの方向にはみ出す分はビューポートの内側にずらし、それでも収まらない分は端に寄せる。
|
|
62
|
+
* 矢印はずらした後のパネル上でアンカーの中心を指す位置に置く。
|
|
63
|
+
*
|
|
64
|
+
* @param {AnchorRect} anchor 基準要素の矩形
|
|
65
|
+
* @param {Size} panel パネルの寸法
|
|
66
|
+
* @param {Size} viewport ビューポートの寸法
|
|
67
|
+
* @param {AnchoredPositionOptions} options 配置・間隔・余白
|
|
68
|
+
* @returns {AnchoredPosition}
|
|
69
|
+
*/
|
|
70
|
+
export const computeAnchoredPosition = (
|
|
71
|
+
anchor: AnchorRect,
|
|
72
|
+
panel: Size,
|
|
73
|
+
viewport: Size,
|
|
74
|
+
options: AnchoredPositionOptions
|
|
75
|
+
): AnchoredPosition => {
|
|
76
|
+
const { offset, margin, arrowMargin } = options;
|
|
77
|
+
const [requested, align = 'center'] = options.placement.split('-') as [Side, Align | undefined];
|
|
78
|
+
const vertical = requested === 'top' || requested === 'bottom';
|
|
79
|
+
const anchorBottom = anchor.top + anchor.height;
|
|
80
|
+
const anchorRight = anchor.left + anchor.width;
|
|
81
|
+
|
|
82
|
+
// 向きの軸: 指定した向きに収まらず、反対側の方が広ければ反転する
|
|
83
|
+
const room: Record<Side, number> = {
|
|
84
|
+
top: anchor.top - margin,
|
|
85
|
+
bottom: viewport.height - anchorBottom - margin,
|
|
86
|
+
left: anchor.left - margin,
|
|
87
|
+
right: viewport.width - anchorRight - margin
|
|
88
|
+
};
|
|
89
|
+
const needed = (vertical ? panel.height : panel.width) + offset;
|
|
90
|
+
const fits = needed <= room[requested];
|
|
91
|
+
const side = fits || room[opposite[requested]] <= room[requested] ? requested : opposite[requested];
|
|
92
|
+
|
|
93
|
+
let mainStart: number;
|
|
94
|
+
switch (side) {
|
|
95
|
+
case 'top':
|
|
96
|
+
mainStart = anchor.top - offset - panel.height;
|
|
97
|
+
break;
|
|
98
|
+
case 'bottom':
|
|
99
|
+
mainStart = anchorBottom + offset;
|
|
100
|
+
break;
|
|
101
|
+
case 'left':
|
|
102
|
+
mainStart = anchor.left - offset - panel.width;
|
|
103
|
+
break;
|
|
104
|
+
default:
|
|
105
|
+
mainStart = anchorRight + offset;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 揃えの軸: start は辺をそろえ、center は中央をそろえ、end は反対の辺をそろえる
|
|
110
|
+
const alignStart = vertical ? anchor.left : anchor.top;
|
|
111
|
+
const anchorAlignSize = vertical ? anchor.width : anchor.height;
|
|
112
|
+
const panelAlignSize = vertical ? panel.width : panel.height;
|
|
113
|
+
let aligned: number;
|
|
114
|
+
switch (align) {
|
|
115
|
+
case 'start':
|
|
116
|
+
aligned = alignStart;
|
|
117
|
+
break;
|
|
118
|
+
case 'end':
|
|
119
|
+
aligned = alignStart + anchorAlignSize - panelAlignSize;
|
|
120
|
+
break;
|
|
121
|
+
default:
|
|
122
|
+
aligned = alignStart + (anchorAlignSize - panelAlignSize) / 2;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ビューポートに収まらない分は端に寄せる。パネルがビューポートより大きいときは margin 側を優先する
|
|
127
|
+
const top = clamp(vertical ? mainStart : aligned, margin, Math.max(margin, viewport.height - margin - panel.height));
|
|
128
|
+
const left = clamp(vertical ? aligned : mainStart, margin, Math.max(margin, viewport.width - margin - panel.width));
|
|
129
|
+
|
|
130
|
+
const anchorCenter = alignStart + anchorAlignSize / 2;
|
|
131
|
+
const panelStart = vertical ? left : top;
|
|
132
|
+
const arrow = clamp(anchorCenter - panelStart, arrowMargin, Math.max(arrowMargin, panelAlignSize - arrowMargin));
|
|
133
|
+
|
|
134
|
+
return { top, left, side, arrow };
|
|
135
|
+
};
|