@veritree/ui 0.1.8 → 0.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/index.js +38 -8
- package/package-lock.json +13 -0
- package/package.json +2 -2
- package/src/Dialog/VTDialog.vue +118 -0
- package/src/Dialog/VTDialogClose.vue +46 -0
- package/src/Dialog/VTDialogContent.vue +72 -0
- package/src/Dialog/VTDialogFooter.vue +30 -0
- package/src/Dialog/VTDialogHeader.vue +56 -0
- package/src/Dialog/VTDialogMain.vue +49 -0
- package/src/Dialog/VTDialogOverlay.vue +52 -0
- package/src/Drawer/VTDrawer.vue +113 -0
- package/src/Drawer/VTDrawerClose.vue +50 -0
- package/src/Drawer/VTDrawerContent.vue +97 -0
- package/src/Drawer/VTDrawerFooter.vue +30 -0
- package/src/Drawer/VTDrawerHeader.vue +56 -0
- package/src/Drawer/VTDrawerMain.vue +53 -0
- package/src/Drawer/VTDrawerOverlay.vue +50 -0
- package/src/Listbox/VTListbox.vue +1 -1
- package/src/Listbox/VTListboxButton.vue +4 -5
- package/src/Listbox/VTListboxOption.vue +22 -17
- package/src/Listbox/VTListboxOptions.vue +11 -12
- package/src/Modal/VTModal.vue +2 -2
- package/src/utils/components.js +18 -0
- package/src/utils/ids.js +0 -21
package/index.js
CHANGED
|
@@ -12,10 +12,10 @@ import VTInputUpload from './src/Input/VTInputUpload.vue';
|
|
|
12
12
|
|
|
13
13
|
import VTTextarea from './src/Textarea/VTTextarea.vue';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
import VTListbox from './src/Listbox/VTListbox.vue';
|
|
16
|
+
import VTListboxButton from './src/Listbox/VTListboxButton.vue';
|
|
17
|
+
import VTListboxOption from './src/Listbox/VTListboxOption.vue';
|
|
18
|
+
import VTListboxOptions from './src/Listbox/VTListboxOptions.vue';
|
|
19
19
|
|
|
20
20
|
import VTModal from './src/Modal/VTModal.vue';
|
|
21
21
|
|
|
@@ -30,6 +30,22 @@ import VTTabList from './src/Tabs/VTTabList.vue';
|
|
|
30
30
|
import VTTabPanel from './src/Tabs/VTTabPanel.vue';
|
|
31
31
|
import VTTabPanels from './src/Tabs/VTTabPanels.vue';
|
|
32
32
|
|
|
33
|
+
import VTDialog from './src/Dialog/VTDialog.vue';
|
|
34
|
+
import VTDialogClose from './src/Dialog/VTDialogClose.vue';
|
|
35
|
+
import VTDialogContent from './src/Dialog/VTDialogContent.vue';
|
|
36
|
+
import VTDialogFooter from './src/Dialog/VTDialogFooter.vue';
|
|
37
|
+
import VTDialogHeader from './src/Dialog/VTDialogHeader.vue';
|
|
38
|
+
import VTDialogMain from './src/Dialog/VTDialogMain.vue';
|
|
39
|
+
import VTDialogOverlay from './src/Dialog/VTDialogOverlay.vue';
|
|
40
|
+
|
|
41
|
+
import VTDrawer from './src/Drawer/VTDrawer.vue';
|
|
42
|
+
import VTDrawerClose from './src/Drawer/VTDrawerClose.vue';
|
|
43
|
+
import VTDrawerContent from './src/Drawer/VTDrawerContent.vue';
|
|
44
|
+
import VTDrawerFooter from './src/Drawer/VTDrawerFooter.vue';
|
|
45
|
+
import VTDrawerHeader from './src/Drawer/VTDrawerHeader.vue';
|
|
46
|
+
import VTDrawerMain from './src/Drawer/VTDrawerMain.vue';
|
|
47
|
+
import VTDrawerOverlay from './src/Drawer/VTDrawerOverlay.vue';
|
|
48
|
+
|
|
33
49
|
export {
|
|
34
50
|
VTAlert,
|
|
35
51
|
// VTSpinner,
|
|
@@ -40,10 +56,10 @@ export {
|
|
|
40
56
|
VTInputFile,
|
|
41
57
|
VTInputUpload,
|
|
42
58
|
VTTextarea,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
VTListbox,
|
|
60
|
+
VTListboxButton,
|
|
61
|
+
VTListboxOption,
|
|
62
|
+
VTListboxOptions,
|
|
47
63
|
VTModal,
|
|
48
64
|
VTAccordion,
|
|
49
65
|
VTAccordionButton,
|
|
@@ -54,5 +70,19 @@ export {
|
|
|
54
70
|
VTTabList,
|
|
55
71
|
VTTabPanel,
|
|
56
72
|
VTTabPanels,
|
|
73
|
+
VTDrawer,
|
|
74
|
+
VTDrawerClose,
|
|
75
|
+
VTDrawerContent,
|
|
76
|
+
VTDrawerFooter,
|
|
77
|
+
VTDrawerHeader,
|
|
78
|
+
VTDrawerMain,
|
|
79
|
+
VTDrawerOverlay,
|
|
80
|
+
VTDialog,
|
|
81
|
+
VTDialogClose,
|
|
82
|
+
VTDialogContent,
|
|
83
|
+
VTDialogFooter,
|
|
84
|
+
VTDialogHeader,
|
|
85
|
+
VTDialogMain,
|
|
86
|
+
VTDialogOverlay,
|
|
57
87
|
}
|
|
58
88
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@veritree/ui",
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"lockfileVersion": 1,
|
|
5
|
+
"requires": true,
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@veritree/icons": {
|
|
8
|
+
"version": "0.12.0",
|
|
9
|
+
"resolved": "https://registry.npmjs.org/@veritree/icons/-/icons-0.12.0.tgz",
|
|
10
|
+
"integrity": "sha512-vunUKzvS9neslSf3R3y6RYQrcfRpxmp8PnhWWe2peYiyElLIJcb7zAsfCZ+I0Fg5PQ6GZG6StqWy0WF7MJ7VOg=="
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veritree/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "veritree ui library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -11,6 +11,6 @@
|
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@veritree/icons": "^0.
|
|
14
|
+
"@veritree/icons": "^0.19.0"
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if="visible"
|
|
4
|
+
:id="id"
|
|
5
|
+
:class="{
|
|
6
|
+
Dialog: headless,
|
|
7
|
+
'fixed inset-0 z-50 grid grid-cols-1 grid-rows-1 p-4 md:p-8': !headless,
|
|
8
|
+
}"
|
|
9
|
+
aria-modal="true"
|
|
10
|
+
@click="hide"
|
|
11
|
+
>
|
|
12
|
+
<slot></slot>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script>
|
|
17
|
+
import { genId } from '~/utils/ids';
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
name: 'VTDialog',
|
|
21
|
+
|
|
22
|
+
provide() {
|
|
23
|
+
return {
|
|
24
|
+
api: () => {
|
|
25
|
+
const id = this.id;
|
|
26
|
+
const isDark = this.dark;
|
|
27
|
+
const isHeadless = this.headless;
|
|
28
|
+
|
|
29
|
+
const registerContent = (content) => {
|
|
30
|
+
if (!content) return;
|
|
31
|
+
this.content = content;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const registerOverlay = (overlay) => {
|
|
35
|
+
if (!overlay) return;
|
|
36
|
+
this.overlay = overlay;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const hide = () => this.hide();
|
|
40
|
+
|
|
41
|
+
const emit = () => this.emit();
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
isDark,
|
|
46
|
+
isHeadless,
|
|
47
|
+
hide,
|
|
48
|
+
emit,
|
|
49
|
+
registerContent,
|
|
50
|
+
registerOverlay,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
model: {
|
|
57
|
+
prop: 'visible',
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
props: {
|
|
61
|
+
visible: {
|
|
62
|
+
type: Boolean,
|
|
63
|
+
default: false,
|
|
64
|
+
},
|
|
65
|
+
headless: {
|
|
66
|
+
type: Boolean,
|
|
67
|
+
default: false,
|
|
68
|
+
},
|
|
69
|
+
dark: {
|
|
70
|
+
type: Boolean,
|
|
71
|
+
default: false,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
data() {
|
|
76
|
+
return {
|
|
77
|
+
id: `dialog-${genId()}`,
|
|
78
|
+
content: null,
|
|
79
|
+
overlay: null,
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
computed: {
|
|
84
|
+
hasContent() {
|
|
85
|
+
return this.content !== null;
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
hasOverlay() {
|
|
89
|
+
return this.overlay !== null;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
watch: {
|
|
94
|
+
visible(isVisible) {
|
|
95
|
+
if (!isVisible) this.hide();
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
mounted() {
|
|
100
|
+
if (this.hasContent) this.content.show();
|
|
101
|
+
if (this.hasOverlay) this.overlay.show();
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
methods: {
|
|
105
|
+
hide() {
|
|
106
|
+
if (this.hasOverlay) this.overlay.hide();
|
|
107
|
+
if (this.hasContent) this.content.hide();
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
emit() {
|
|
111
|
+
this.$nextTick(() => {
|
|
112
|
+
this.$emit('input', false);
|
|
113
|
+
this.$emit('hidden');
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
</script>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<VTButton
|
|
3
|
+
variant="icon"
|
|
4
|
+
:class="{
|
|
5
|
+
'Dialog-close': headless,
|
|
6
|
+
'absolute right-4 top-4': !headless,
|
|
7
|
+
}"
|
|
8
|
+
:theme="theme"
|
|
9
|
+
@click.prevent="hide"
|
|
10
|
+
><slot><IconClose class="h-5 w-5" /></slot
|
|
11
|
+
></VTButton>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
import { IconClose } from '@veritree/icons';
|
|
16
|
+
import VTButton from '~/components/Button/VTButton.vue';
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
name: 'VTDialogClose',
|
|
20
|
+
|
|
21
|
+
components: { IconClose, VTButton },
|
|
22
|
+
|
|
23
|
+
inject: ['api'],
|
|
24
|
+
|
|
25
|
+
computed: {
|
|
26
|
+
dark() {
|
|
27
|
+
return this.api().isDark;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
headless() {
|
|
31
|
+
return this.api().isHeadless;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// temporary till button theme is implemented
|
|
35
|
+
theme() {
|
|
36
|
+
return this.dark ? 'dark' : null;
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
methods: {
|
|
41
|
+
hide() {
|
|
42
|
+
this.api().hide();
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
</script>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<transition
|
|
3
|
+
enter-active-class="duration-300 ease-out"
|
|
4
|
+
enter-class="translate-y-[50px] opacity-0"
|
|
5
|
+
enter-to-class="translate-y-0 opacity-100"
|
|
6
|
+
leave-active-class="duration-300 ease-out"
|
|
7
|
+
leave-class="translate-y-0 opacity-100"
|
|
8
|
+
leave-to-class="translate-y-[50px] opacity-0"
|
|
9
|
+
@after-leave="hideDialog"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
v-show="visible"
|
|
13
|
+
:class="{
|
|
14
|
+
'Dialog-content': headless,
|
|
15
|
+
'relative m-auto max-h-full max-w-full overflow-auto rounded p-6 focus:outline-none sm:p-10':
|
|
16
|
+
!headless,
|
|
17
|
+
'bg-white': !dark,
|
|
18
|
+
'bg-fd-600': dark,
|
|
19
|
+
}"
|
|
20
|
+
tabindex="-1"
|
|
21
|
+
@keyup.esc="hide"
|
|
22
|
+
@click.stop
|
|
23
|
+
>
|
|
24
|
+
<slot></slot>
|
|
25
|
+
</div>
|
|
26
|
+
</transition>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script>
|
|
30
|
+
export default {
|
|
31
|
+
name: 'VTDialogContent',
|
|
32
|
+
|
|
33
|
+
inject: ['api'],
|
|
34
|
+
|
|
35
|
+
data() {
|
|
36
|
+
return {
|
|
37
|
+
visible: false,
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
computed: {
|
|
42
|
+
dark() {
|
|
43
|
+
return this.api().isDark;
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
headless() {
|
|
47
|
+
return this.api().isHeadless;
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
mounted() {
|
|
52
|
+
this.api().registerContent(this);
|
|
53
|
+
this.show();
|
|
54
|
+
|
|
55
|
+
this.$nextTick(() => this.$el.focus());
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
methods: {
|
|
59
|
+
show() {
|
|
60
|
+
this.visible = true;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
hide() {
|
|
64
|
+
this.visible = false;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
hideDialog() {
|
|
68
|
+
this.api().emit();
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
</script>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component :is="as" :class="{ 'Dialog-footer': headless }">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</component>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
export default {
|
|
9
|
+
name: 'VTDialogFooter',
|
|
10
|
+
|
|
11
|
+
inject: ['api'],
|
|
12
|
+
|
|
13
|
+
props: {
|
|
14
|
+
as: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: 'footer',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
computed: {
|
|
21
|
+
dark() {
|
|
22
|
+
return this.api().isDark;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
headless() {
|
|
26
|
+
return this.api().isHeadless;
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
</script>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="as"
|
|
4
|
+
:id="id"
|
|
5
|
+
:class="{
|
|
6
|
+
'Dialog-header': headless,
|
|
7
|
+
'mb-8 text-2xl font-semibold': !headless,
|
|
8
|
+
}"
|
|
9
|
+
>
|
|
10
|
+
<slot></slot>
|
|
11
|
+
</component>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
export default {
|
|
16
|
+
name: 'VTDialogHeader',
|
|
17
|
+
|
|
18
|
+
inject: ['api'],
|
|
19
|
+
|
|
20
|
+
props: {
|
|
21
|
+
as: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: 'header',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
computed: {
|
|
28
|
+
dark() {
|
|
29
|
+
return this.api().isDark;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
headless() {
|
|
33
|
+
return this.api().isHeadless;
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
id() {
|
|
37
|
+
return `${this.api().id}-header`;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
mounted() {
|
|
42
|
+
this.setDialogLabelledby();
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
methods: {
|
|
46
|
+
// In here because if there is no header, the dialog will not be labelled by
|
|
47
|
+
setDialogLabelledby() {
|
|
48
|
+
const dialog = document.getElementById(this.api().id);
|
|
49
|
+
|
|
50
|
+
if (dialog) {
|
|
51
|
+
dialog.setAttribute('aria-labelledby', this.id);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
</script>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component :is="as" :id="id" :class="{ 'Dialog-body': headless }">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</component>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
export default {
|
|
9
|
+
name: 'VTDialogMain',
|
|
10
|
+
|
|
11
|
+
inject: ['api'],
|
|
12
|
+
|
|
13
|
+
props: {
|
|
14
|
+
as: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: 'main',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
computed: {
|
|
21
|
+
dark() {
|
|
22
|
+
return this.api().isDark;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
headless() {
|
|
26
|
+
return this.api().isHeadless;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
id() {
|
|
30
|
+
return `${this.api().id}-desc`;
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
mounted() {
|
|
35
|
+
this.setDialogDescribedby();
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
methods: {
|
|
39
|
+
// In here because if there is no body, the dialog will not be described by
|
|
40
|
+
setDialogDescribedby() {
|
|
41
|
+
const dialog = document.getElementById(this.api().id);
|
|
42
|
+
|
|
43
|
+
if (dialog) {
|
|
44
|
+
dialog.setAttribute('aria-describedby', this.id);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FadeInOut>
|
|
3
|
+
<div
|
|
4
|
+
v-if="visible"
|
|
5
|
+
:class="{
|
|
6
|
+
'Dialog-overlay': headless,
|
|
7
|
+
'fixed inset-0 bg-fd-450/80': !headless,
|
|
8
|
+
}"
|
|
9
|
+
></div>
|
|
10
|
+
</FadeInOut>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
import FadeInOut from '~/components/Transitions/FadeInOut.vue';
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
name: 'VTDialogOverlay',
|
|
18
|
+
|
|
19
|
+
components: {
|
|
20
|
+
FadeInOut,
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
inject: ['api'],
|
|
24
|
+
|
|
25
|
+
data() {
|
|
26
|
+
return {
|
|
27
|
+
visible: false,
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
computed: {
|
|
32
|
+
dark() {
|
|
33
|
+
return this.api().isDark;
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
headless() {
|
|
37
|
+
return this.api().isHeadless;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
mounted() {
|
|
42
|
+
this.visible = true;
|
|
43
|
+
this.api().registerOverlay(this);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
methods: {
|
|
47
|
+
hide() {
|
|
48
|
+
this.visible = false;
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
</script>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if="visible"
|
|
4
|
+
:id="id"
|
|
5
|
+
:class="{ Drawer: headless, 'fixed inset-0 z-50 h-screen': !headless }"
|
|
6
|
+
aria-modal="true"
|
|
7
|
+
@click="hide"
|
|
8
|
+
>
|
|
9
|
+
<slot></slot>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
import { genId } from '~/utils/ids';
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
name: 'VTDrawer',
|
|
18
|
+
|
|
19
|
+
provide() {
|
|
20
|
+
return {
|
|
21
|
+
api: () => {
|
|
22
|
+
const id = this.id;
|
|
23
|
+
const isDark = this.dark;
|
|
24
|
+
const isHeadless = this.headless;
|
|
25
|
+
const isRight = this.right;
|
|
26
|
+
|
|
27
|
+
const registerContent = (content) => {
|
|
28
|
+
if (!content) return;
|
|
29
|
+
this.content = content;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const registerOverlay = (overlay) => {
|
|
33
|
+
if (!overlay) return;
|
|
34
|
+
this.overlay = overlay;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const hide = () => this.hide();
|
|
38
|
+
|
|
39
|
+
const emit = () => this.emit();
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
id,
|
|
43
|
+
isDark,
|
|
44
|
+
isHeadless,
|
|
45
|
+
isRight,
|
|
46
|
+
hide,
|
|
47
|
+
emit,
|
|
48
|
+
registerContent,
|
|
49
|
+
registerOverlay,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
model: {
|
|
56
|
+
prop: 'visible',
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
props: {
|
|
60
|
+
visible: {
|
|
61
|
+
type: Boolean,
|
|
62
|
+
default: false,
|
|
63
|
+
},
|
|
64
|
+
headless: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: false,
|
|
67
|
+
},
|
|
68
|
+
dark: {
|
|
69
|
+
type: Boolean,
|
|
70
|
+
default: false,
|
|
71
|
+
},
|
|
72
|
+
right: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
default: false,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
data() {
|
|
79
|
+
return {
|
|
80
|
+
id: `drawer-${genId()}`,
|
|
81
|
+
content: null,
|
|
82
|
+
overlay: null,
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
watch: {
|
|
87
|
+
visible(isVisible) {
|
|
88
|
+
if (!isVisible) this.hide();
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
mounted() {
|
|
93
|
+
if (!this.content) return;
|
|
94
|
+
this.content.show();
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
methods: {
|
|
98
|
+
hide() {
|
|
99
|
+
if (!this.content) return;
|
|
100
|
+
this.content.hide();
|
|
101
|
+
if (!this.overlay) return;
|
|
102
|
+
this.overlay.hide();
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
emit() {
|
|
106
|
+
this.$nextTick(() => {
|
|
107
|
+
this.$emit('input', false);
|
|
108
|
+
this.$emit('hidden');
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
</script>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<VTButton
|
|
3
|
+
variant="icon"
|
|
4
|
+
:class="{
|
|
5
|
+
'Drawer-close': headless,
|
|
6
|
+
'ml-auto mb-4 text-inherit sm:mb-8': !headless,
|
|
7
|
+
}"
|
|
8
|
+
:theme="theme"
|
|
9
|
+
@click.prevent="hide"
|
|
10
|
+
><slot><IconLeft :class="{ 'rotate-180': right }" /></slot
|
|
11
|
+
></VTButton>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
import { IconLeft } from '@veritree/icons';
|
|
16
|
+
import VTButton from '~/components/Button/VTButton.vue';
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
name: 'VTDrawerClose',
|
|
20
|
+
|
|
21
|
+
components: { IconLeft, VTButton },
|
|
22
|
+
|
|
23
|
+
inject: ['api'],
|
|
24
|
+
|
|
25
|
+
computed: {
|
|
26
|
+
dark() {
|
|
27
|
+
return this.api().isDark;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
headless() {
|
|
31
|
+
return this.api().isHeadless;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
right() {
|
|
35
|
+
return this.api().isRight;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// temporary till button theme is implemented
|
|
39
|
+
theme() {
|
|
40
|
+
return this.dark ? 'dark' : null;
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
methods: {
|
|
45
|
+
hide() {
|
|
46
|
+
this.api().hide();
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<transition
|
|
3
|
+
:enter-active-class="activeClass"
|
|
4
|
+
:enter-class="enterClass"
|
|
5
|
+
:enter-to-class="enterToClass"
|
|
6
|
+
:leave-active-class="activeClass"
|
|
7
|
+
:leave-class="leaveClass"
|
|
8
|
+
:leave-to-class="leaveToClass"
|
|
9
|
+
@after-leave="hideDrawer"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
v-show="visible"
|
|
13
|
+
:class="{
|
|
14
|
+
'Drawer-content': headless,
|
|
15
|
+
'absolute z-20 flex h-screen max-h-full max-w-full flex-col overflow-auto p-5 outline-0 sm:px-10 sm:py-6':
|
|
16
|
+
!headless,
|
|
17
|
+
'bg-white': !dark,
|
|
18
|
+
'bg-fd-600': dark,
|
|
19
|
+
'right-0': right,
|
|
20
|
+
}"
|
|
21
|
+
tabindex="-1"
|
|
22
|
+
@keyup.esc="hide"
|
|
23
|
+
@click.stop
|
|
24
|
+
>
|
|
25
|
+
<slot></slot>
|
|
26
|
+
</div>
|
|
27
|
+
</transition>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script>
|
|
31
|
+
export default {
|
|
32
|
+
name: 'VTDrawerContent',
|
|
33
|
+
|
|
34
|
+
inject: ['api'],
|
|
35
|
+
|
|
36
|
+
data() {
|
|
37
|
+
return {
|
|
38
|
+
visible: false,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
computed: {
|
|
43
|
+
dark() {
|
|
44
|
+
return this.api().isDark;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
headless() {
|
|
48
|
+
return this.api().isHeadless;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
right() {
|
|
52
|
+
return this.api().isRight;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
activeClass() {
|
|
56
|
+
return 'transform transition duration-300 ease-in-out';
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
enterClass() {
|
|
60
|
+
return `opacity-0 ${this.right ? '' : '-'}translate-x-full`;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
enterToClass() {
|
|
64
|
+
return `opacity-100 ${this.right ? '-' : ''}translate-x-0`;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
leaveClass() {
|
|
68
|
+
return `opacity-100 ${this.right ? '-' : ''}translate-x-0`;
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
leaveToClass() {
|
|
72
|
+
return `opacity-0 ${this.right ? '' : '-'}translate-x-full`;
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
mounted() {
|
|
77
|
+
this.api().registerContent(this);
|
|
78
|
+
this.show();
|
|
79
|
+
|
|
80
|
+
this.$nextTick(() => this.$el.focus());
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
methods: {
|
|
84
|
+
show() {
|
|
85
|
+
this.visible = true;
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
hide() {
|
|
89
|
+
this.visible = false;
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
hideDrawer() {
|
|
93
|
+
this.api().emit();
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
</script>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component :is="as" :class="{ 'Dialog-footer': headless }">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</component>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
export default {
|
|
9
|
+
name: 'VTDrawerFooter',
|
|
10
|
+
|
|
11
|
+
inject: ['api'],
|
|
12
|
+
|
|
13
|
+
props: {
|
|
14
|
+
as: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: 'footer',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
computed: {
|
|
21
|
+
dark() {
|
|
22
|
+
return this.api().isDark;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
headless() {
|
|
26
|
+
return this.api().isHeadless;
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
</script>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="as"
|
|
4
|
+
:id="id"
|
|
5
|
+
:class="{
|
|
6
|
+
'Drawer-header': headless,
|
|
7
|
+
'mb-8 text-2xl font-semibold': !headless,
|
|
8
|
+
}"
|
|
9
|
+
>
|
|
10
|
+
<slot></slot>
|
|
11
|
+
</component>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
export default {
|
|
16
|
+
name: 'VTDrawerHeader',
|
|
17
|
+
|
|
18
|
+
inject: ['api'],
|
|
19
|
+
|
|
20
|
+
props: {
|
|
21
|
+
as: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: 'header',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
computed: {
|
|
28
|
+
dark() {
|
|
29
|
+
return this.api().isDark;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
headless() {
|
|
33
|
+
return this.api().isHeadless;
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
id() {
|
|
37
|
+
return `${this.api().id}-header`;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
mounted() {
|
|
42
|
+
this.setDialogLabelledby();
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
methods: {
|
|
46
|
+
// In here because if there is no header, the dialog will not be labelled by
|
|
47
|
+
setDialogLabelledby() {
|
|
48
|
+
const dialog = document.getElementById(this.api().id);
|
|
49
|
+
|
|
50
|
+
if (dialog) {
|
|
51
|
+
dialog.setAttribute('aria-labelledby', this.id);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
</script>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="as"
|
|
4
|
+
:id="id"
|
|
5
|
+
:class="{ 'Drawer-body': headless, 'flex-1': !headless }"
|
|
6
|
+
>
|
|
7
|
+
<slot></slot>
|
|
8
|
+
</component>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script>
|
|
12
|
+
export default {
|
|
13
|
+
name: 'VTDrawerMain',
|
|
14
|
+
|
|
15
|
+
inject: ['api'],
|
|
16
|
+
|
|
17
|
+
props: {
|
|
18
|
+
as: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: 'main',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
computed: {
|
|
25
|
+
dark() {
|
|
26
|
+
return this.api().isDark;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
headless() {
|
|
30
|
+
return this.api().isHeadless;
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
id() {
|
|
34
|
+
return `${this.api().id}-desc`;
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
mounted() {
|
|
39
|
+
this.setDialogDescribedby();
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
methods: {
|
|
43
|
+
// In here because if there is no body, the dialog will not be described by
|
|
44
|
+
setDialogDescribedby() {
|
|
45
|
+
const dialog = document.getElementById(this.api().id);
|
|
46
|
+
|
|
47
|
+
if (dialog) {
|
|
48
|
+
dialog.setAttribute('aria-describedby', this.id);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
</script>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FadeInOut>
|
|
3
|
+
<div
|
|
4
|
+
v-if="visible"
|
|
5
|
+
:class="{
|
|
6
|
+
'Drawer-overlay': headless,
|
|
7
|
+
'fixed inset-0 z-10 bg-fd-450/80': !headless,
|
|
8
|
+
}"
|
|
9
|
+
></div>
|
|
10
|
+
</FadeInOut>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
import FadeInOut from '~/components/Transitions/FadeInOut.vue';
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
components: {
|
|
18
|
+
FadeInOut,
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
inject: ['api'],
|
|
22
|
+
|
|
23
|
+
data() {
|
|
24
|
+
return {
|
|
25
|
+
visible: false,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
computed: {
|
|
30
|
+
dark() {
|
|
31
|
+
return this.api().isDark;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
headless() {
|
|
35
|
+
return this.api().isHeadless;
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
mounted() {
|
|
40
|
+
this.visible = true;
|
|
41
|
+
this.api().registerOverlay(this);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
methods: {
|
|
45
|
+
hide() {
|
|
46
|
+
this.visible = false;
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
</script>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<button
|
|
3
3
|
:aria-expanded="expanded"
|
|
4
4
|
aria-haspopup="listbox"
|
|
5
|
-
class="Listbox-button
|
|
5
|
+
class="Listbox-button"
|
|
6
6
|
:data-theme="theme"
|
|
7
7
|
type="button"
|
|
8
8
|
@click.prevent="onClick"
|
|
@@ -39,13 +39,12 @@ export default {
|
|
|
39
39
|
|
|
40
40
|
data() {
|
|
41
41
|
return {
|
|
42
|
-
api: this.api(),
|
|
43
42
|
expanded: false,
|
|
44
43
|
};
|
|
45
44
|
},
|
|
46
45
|
|
|
47
46
|
mounted() {
|
|
48
|
-
this.api.registerListboxButton(this);
|
|
47
|
+
this.api().registerListboxButton(this);
|
|
49
48
|
},
|
|
50
49
|
|
|
51
50
|
methods: {
|
|
@@ -59,7 +58,7 @@ export default {
|
|
|
59
58
|
},
|
|
60
59
|
|
|
61
60
|
onClick() {
|
|
62
|
-
const listbox = this.api.getListbox();
|
|
61
|
+
const listbox = this.api().getListbox();
|
|
63
62
|
this.expanded ? listbox.hide() : listbox.show();
|
|
64
63
|
|
|
65
64
|
this.toggleExpanded();
|
|
@@ -67,7 +66,7 @@ export default {
|
|
|
67
66
|
|
|
68
67
|
onKeyDown(event) {
|
|
69
68
|
const key = event.key;
|
|
70
|
-
const listbox = this.api.getListbox();
|
|
69
|
+
const listbox = this.api().getListbox();
|
|
71
70
|
|
|
72
71
|
switch (key) {
|
|
73
72
|
case keys.down:
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<li
|
|
3
3
|
:id="id"
|
|
4
|
-
:aria-selected="selected"
|
|
5
4
|
class="Listbox-option"
|
|
6
5
|
role="option"
|
|
7
6
|
@mousedown.prevent="onMouseDown"
|
|
@@ -13,6 +12,7 @@
|
|
|
13
12
|
</template>
|
|
14
13
|
|
|
15
14
|
<script>
|
|
15
|
+
import { scrollElementIntoView } from '../utils/components';
|
|
16
16
|
import { genId } from '../utils/ids';
|
|
17
17
|
import { areObjsEqual, isObj } from '../utils/objects';
|
|
18
18
|
|
|
@@ -26,36 +26,41 @@ export default {
|
|
|
26
26
|
type: [String, Number, Object],
|
|
27
27
|
required: true,
|
|
28
28
|
},
|
|
29
|
+
selected: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false,
|
|
32
|
+
},
|
|
29
33
|
},
|
|
30
34
|
|
|
31
35
|
data() {
|
|
32
36
|
return {
|
|
33
|
-
api: this.api(),
|
|
34
37
|
id: `listbox-option-${genId()}`,
|
|
35
|
-
selected: false,
|
|
36
38
|
focused: false,
|
|
37
39
|
isMousemove: false,
|
|
40
|
+
parent: null,
|
|
38
41
|
};
|
|
39
42
|
},
|
|
40
43
|
|
|
41
44
|
watch: {
|
|
42
45
|
focused(newValue) {
|
|
43
|
-
if (newValue)
|
|
44
|
-
if (!this.isMousemove) {
|
|
45
|
-
this.$el.scrollIntoView({ block: 'nearest' });
|
|
46
|
-
}
|
|
46
|
+
if (!newValue) return;
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
if (!this.parent) this.parent = this.api().getListbox();
|
|
49
|
+
if (!this.isMousemove) scrollElementIntoView(this.$el, this.parent.$el);
|
|
50
|
+
this.parent.updateActiveDescendant(this.id);
|
|
51
51
|
},
|
|
52
52
|
},
|
|
53
53
|
|
|
54
54
|
mounted() {
|
|
55
|
-
this.api.registerOption(this);
|
|
55
|
+
this.api().registerOption(this);
|
|
56
|
+
|
|
57
|
+
if (this.selected) {
|
|
58
|
+
this.select();
|
|
59
|
+
this.focus();
|
|
60
|
+
}
|
|
56
61
|
|
|
57
62
|
// todo: make sure it works with other values than objects
|
|
58
|
-
const listboxOptionSelected = this.api.getListboxValue();
|
|
63
|
+
const listboxOptionSelected = this.api().getListboxValue();
|
|
59
64
|
|
|
60
65
|
if (!listboxOptionSelected) {
|
|
61
66
|
return;
|
|
@@ -77,7 +82,7 @@ export default {
|
|
|
77
82
|
},
|
|
78
83
|
|
|
79
84
|
beforeDestroy() {
|
|
80
|
-
this.api.unregisterOption(this.id);
|
|
85
|
+
this.api().unregisterOption(this.id);
|
|
81
86
|
},
|
|
82
87
|
|
|
83
88
|
methods: {
|
|
@@ -92,15 +97,15 @@ export default {
|
|
|
92
97
|
},
|
|
93
98
|
|
|
94
99
|
select() {
|
|
95
|
-
this.selected
|
|
100
|
+
this.$el.setAttribute('aria-selected', true);
|
|
96
101
|
},
|
|
97
102
|
|
|
98
103
|
unselect() {
|
|
99
|
-
this.selected
|
|
104
|
+
this.$el.setAttribute('aria-selected', false);
|
|
100
105
|
},
|
|
101
106
|
|
|
102
107
|
onMouseDown(event) {
|
|
103
|
-
if (event.buttons === 1) this.api.selectOption(this);
|
|
108
|
+
if (event.buttons === 1) this.api().selectOption(this);
|
|
104
109
|
},
|
|
105
110
|
|
|
106
111
|
// Mousemove instead of mouseover to support keyboard navigation.
|
|
@@ -108,7 +113,7 @@ export default {
|
|
|
108
113
|
// mouseover event gets triggered.
|
|
109
114
|
onMousemove() {
|
|
110
115
|
this.isMousemove = true;
|
|
111
|
-
this.api.unfocusOptions();
|
|
116
|
+
this.api().unfocusOptions();
|
|
112
117
|
this.focus();
|
|
113
118
|
},
|
|
114
119
|
|
|
@@ -23,7 +23,6 @@ export default {
|
|
|
23
23
|
|
|
24
24
|
data() {
|
|
25
25
|
return {
|
|
26
|
-
api: this.api(),
|
|
27
26
|
visible: false,
|
|
28
27
|
filter: '',
|
|
29
28
|
};
|
|
@@ -37,7 +36,7 @@ export default {
|
|
|
37
36
|
this.handleOptionFocus();
|
|
38
37
|
});
|
|
39
38
|
} else {
|
|
40
|
-
const listboxButton = this.api.getListboxButton();
|
|
39
|
+
const listboxButton = this.api().getListboxButton();
|
|
41
40
|
|
|
42
41
|
this.$nextTick(() => {
|
|
43
42
|
listboxButton.focus();
|
|
@@ -47,7 +46,7 @@ export default {
|
|
|
47
46
|
},
|
|
48
47
|
|
|
49
48
|
mounted() {
|
|
50
|
-
this.api.registerListbox(this);
|
|
49
|
+
this.api().registerListbox(this);
|
|
51
50
|
},
|
|
52
51
|
|
|
53
52
|
methods: {
|
|
@@ -60,8 +59,8 @@ export default {
|
|
|
60
59
|
},
|
|
61
60
|
|
|
62
61
|
handleOptionFocus() {
|
|
63
|
-
const selectedIndex = this.api.getFocusedIndex();
|
|
64
|
-
if (!selectedIndex) this.api.focusFirstOption();
|
|
62
|
+
const selectedIndex = this.api().getFocusedIndex();
|
|
63
|
+
if (!selectedIndex) this.api().focusFirstOption();
|
|
65
64
|
},
|
|
66
65
|
|
|
67
66
|
updateActiveDescendant(id) {
|
|
@@ -78,31 +77,31 @@ export default {
|
|
|
78
77
|
clearTimeout(timer);
|
|
79
78
|
|
|
80
79
|
timer = setTimeout(() => {
|
|
81
|
-
this.api.focusOptionByFilter(this.filter);
|
|
80
|
+
this.api().focusOptionByFilter(this.filter);
|
|
82
81
|
this.filter = '';
|
|
83
|
-
},
|
|
82
|
+
}, 100);
|
|
84
83
|
}
|
|
85
84
|
|
|
86
85
|
switch (key) {
|
|
87
86
|
case keys.up:
|
|
88
87
|
event.preventDefault();
|
|
89
|
-
this.api.focusPreviousOption();
|
|
88
|
+
this.api().focusPreviousOption();
|
|
90
89
|
break;
|
|
91
90
|
case keys.down:
|
|
92
91
|
event.preventDefault();
|
|
93
|
-
this.api.focusNextOption();
|
|
92
|
+
this.api().focusNextOption();
|
|
94
93
|
break;
|
|
95
94
|
case keys.home:
|
|
96
95
|
event.preventDefault();
|
|
97
|
-
this.api.focusFirstOption();
|
|
96
|
+
this.api().focusFirstOption();
|
|
98
97
|
break;
|
|
99
98
|
case keys.end:
|
|
100
99
|
event.preventDefault();
|
|
101
|
-
this.api.focusLastOption();
|
|
100
|
+
this.api().focusLastOption();
|
|
102
101
|
break;
|
|
103
102
|
case keys.enter:
|
|
104
103
|
event.preventDefault();
|
|
105
|
-
this.api.selectOption();
|
|
104
|
+
this.api().selectOption();
|
|
106
105
|
break;
|
|
107
106
|
case keys.escape:
|
|
108
107
|
this.hide();
|
package/src/Modal/VTModal.vue
CHANGED
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
>
|
|
12
12
|
<div
|
|
13
13
|
v-if="visible"
|
|
14
|
-
class="fixed inset-0 z-50 flex flex-col justify-center bg-fd-700/75
|
|
14
|
+
class="fixed inset-0 z-50 flex flex-col justify-center bg-fd-700/75"
|
|
15
15
|
tabindex="-1"
|
|
16
16
|
@keyup.esc="close"
|
|
17
17
|
@click="close"
|
|
18
18
|
>
|
|
19
19
|
<div
|
|
20
|
-
class="relative mx-auto flex max-w-lg flex-col justify-center rounded bg-white
|
|
20
|
+
class="relative mx-auto flex max-w-lg flex-col justify-center rounded bg-white"
|
|
21
21
|
@click.stop
|
|
22
22
|
>
|
|
23
23
|
<div class="absolute right-4 top-4">
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {HTMLElement} el
|
|
4
|
+
* @param {HTMLElement} parent
|
|
5
|
+
*/
|
|
6
|
+
export const scrollElementIntoView = (el, parent) => {
|
|
7
|
+
// this works better than scrollIntoView
|
|
8
|
+
if (parent.scrollHeight <= parent.clientHeight) return;
|
|
9
|
+
|
|
10
|
+
const scrollBottom = parent.clientHeight + parent.scrollTop;
|
|
11
|
+
const elBottom = el.offsetTop + el.offsetHeight;
|
|
12
|
+
|
|
13
|
+
if (elBottom > scrollBottom) {
|
|
14
|
+
parent.scrollTop = elBottom - parent.clientHeight;
|
|
15
|
+
} else if (el.offsetTop < parent.scrollTop) {
|
|
16
|
+
parent.scrollTop = el.offsetTop;
|
|
17
|
+
}
|
|
18
|
+
};
|
package/src/utils/ids.js
CHANGED
|
@@ -11,24 +11,3 @@ export const genId = () => {
|
|
|
11
11
|
if (!gen) gen = idMaker();
|
|
12
12
|
return gen.next().value;
|
|
13
13
|
};
|
|
14
|
-
|
|
15
|
-
// Add leading zeros
|
|
16
|
-
// export const pad = (num, size) => {
|
|
17
|
-
// let s = num + '';
|
|
18
|
-
|
|
19
|
-
// while (s.length < size) {
|
|
20
|
-
// s = '0' + s;
|
|
21
|
-
// }
|
|
22
|
-
|
|
23
|
-
// return s;
|
|
24
|
-
// };
|
|
25
|
-
|
|
26
|
-
export const addZerosToId = (id) => {
|
|
27
|
-
let formattedId = null;
|
|
28
|
-
|
|
29
|
-
if (id < 10) formattedId = '000' + id;
|
|
30
|
-
else if (id < 100) formattedId = '00' + id;
|
|
31
|
-
else if (id < 1000) formattedId = '0' + id;
|
|
32
|
-
|
|
33
|
-
return '#' + formattedId;
|
|
34
|
-
};
|