@veltra/desktop 1.1.9 → 1.1.10
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/dist/components/action/action-group.vue.d.ts +1 -1
- package/dist/components/action/action.vue.d.ts +1 -1
- package/dist/components/button/style2.css +75 -11
- package/dist/components/card/style2.css +4 -6
- package/dist/components/cascade/cascade.vue.d.ts +2 -2
- package/dist/components/checkbox/checkbox-button.vue.d.ts +1 -1
- package/dist/components/collapse/collapse-item.js +8 -23
- package/dist/components/collapse/collapse-item.js.map +1 -1
- package/dist/components/collapse/collapse-item.vue.d.ts +5 -9
- package/dist/components/collapse/collapse.js +22 -44
- package/dist/components/collapse/collapse.js.map +1 -1
- package/dist/components/collapse/collapse.vue.d.ts +3 -4
- package/dist/components/collapse/di.js.map +1 -1
- package/dist/components/collapse/style2.css +36 -38
- package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
- package/dist/components/date-picker/date-picker.vue.d.ts +1 -1
- package/dist/components/date-range-picker/date-range-picker.vue.d.ts +1 -1
- package/dist/components/drawer/drawer.vue.d.ts +2 -2
- package/dist/components/file-viewer/file-viewer.vue.d.ts +3 -3
- package/dist/components/multi-select/multi-select.vue.d.ts +1 -1
- package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +1 -1
- package/dist/components/number-range-input/number-range-input.vue.d.ts +1 -1
- package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
- package/dist/components/progress-nodes/progress-nodes.vue.d.ts +2 -2
- package/dist/components/tabs/style.js +1 -0
- package/dist/components/tabs/tabs-horizontal.vue.d.ts +2 -2
- package/dist/components/tabs/tabs-vertical.vue.d.ts +2 -2
- package/dist/components/tabs/tabs.vue.d.ts +2 -2
- package/dist/components/tip/tip.vue.d.ts +1 -1
- package/dist/components/tree/tree.vue.d.ts +1 -1
- package/dist/components/tree-select/tree-select.vue.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/types/collapse.d.ts +5 -28
- package/dist/types/index.d.ts +2 -2
- package/package.json +11 -11
- package/src/components/button/style.scss +26 -7
- package/src/components/card/style.scss +4 -18
- package/src/components/collapse/__test__/collapse.test.ts +178 -0
- package/src/components/collapse/collapse-item.vue +3 -12
- package/src/components/collapse/collapse.vue +42 -52
- package/src/components/collapse/di.ts +3 -2
- package/src/components/collapse/style.scss +32 -80
- package/src/components/tabs/style.ts +1 -0
- package/src/types/collapse.ts +4 -32
|
@@ -8,15 +8,14 @@ $root-name: button;
|
|
|
8
8
|
@include m.b($root-name) {
|
|
9
9
|
color: fn.component-var(button, default-color, fn.use-var(text-color, main));
|
|
10
10
|
box-sizing: border-box;
|
|
11
|
-
|
|
11
|
+
border-radius: 9999px;
|
|
12
12
|
line-height: 1;
|
|
13
13
|
outline: none;
|
|
14
14
|
text-decoration: none;
|
|
15
15
|
white-space: nowrap;
|
|
16
16
|
vertical-align: middle;
|
|
17
|
-
border-radius: fn.use-var(radius, default);
|
|
18
17
|
cursor: pointer;
|
|
19
|
-
padding: 0
|
|
18
|
+
padding: 0 1em;
|
|
20
19
|
-webkit-appearance: none;
|
|
21
20
|
border: 1px solid fn.component-var(button, default-border, transparent);
|
|
22
21
|
background-color: fn.component-var(button, default-bg, fn.use-var(color, default));
|
|
@@ -50,6 +49,7 @@ $root-name: button;
|
|
|
50
49
|
@include m.is(plain) {
|
|
51
50
|
background-color: fn.use-var(bg-color, top);
|
|
52
51
|
border-color: fn.use-var(color, default, 'dark-1');
|
|
52
|
+
box-shadow: none;
|
|
53
53
|
|
|
54
54
|
&:hover {
|
|
55
55
|
border-color: fn.use-var(color, default, 'dark-1');
|
|
@@ -90,6 +90,7 @@ $root-name: button;
|
|
|
90
90
|
|
|
91
91
|
@include m.m(color-#{$type}) {
|
|
92
92
|
background-color: fn.use-var(color, $type);
|
|
93
|
+
border-color: fn.component-var(button, #{$type}-border, fn.use-var(color, $type));
|
|
93
94
|
color: fn.component-var(button, #{$type}-text-color, fn.use-var(text-color, white));
|
|
94
95
|
|
|
95
96
|
&:hover {
|
|
@@ -98,6 +99,11 @@ $root-name: button;
|
|
|
98
99
|
#{$type}-hover-bg,
|
|
99
100
|
fn.use-var(color, $type, dark-1)
|
|
100
101
|
);
|
|
102
|
+
border-color: fn.component-var(
|
|
103
|
+
button,
|
|
104
|
+
#{$type}-hover-border,
|
|
105
|
+
fn.use-var(color, $type, dark-1)
|
|
106
|
+
);
|
|
101
107
|
color: fn.component-var(
|
|
102
108
|
button,
|
|
103
109
|
#{$type}-hover-color,
|
|
@@ -144,8 +150,20 @@ $root-name: button;
|
|
|
144
150
|
#{$type}-plain-bg,
|
|
145
151
|
fn.use-var(color, $type, light-9)
|
|
146
152
|
);
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
border-color: fn.component-var(
|
|
154
|
+
button,
|
|
155
|
+
#{$type}-plain-border,
|
|
156
|
+
fn.use-var(color, $type, light-5)
|
|
157
|
+
);
|
|
158
|
+
box-shadow: none;
|
|
159
|
+
|
|
160
|
+
@include m.is-not(disabled, loading) {
|
|
161
|
+
&:hover {
|
|
162
|
+
color: fn.use-var(color, $type);
|
|
163
|
+
border-color: fn.use-var(color, $type);
|
|
164
|
+
background-color: fn.use-var(color, $type, light-9);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
149
167
|
|
|
150
168
|
&:active {
|
|
151
169
|
box-shadow: none;
|
|
@@ -153,6 +171,7 @@ $root-name: button;
|
|
|
153
171
|
|
|
154
172
|
&.is-disabled {
|
|
155
173
|
color: fn.use-var(color, $type, light-5);
|
|
174
|
+
border-color: fn.use-var(color, $type, light-8);
|
|
156
175
|
}
|
|
157
176
|
}
|
|
158
177
|
}
|
|
@@ -179,10 +198,10 @@ $root-name: button;
|
|
|
179
198
|
border-radius: 0;
|
|
180
199
|
|
|
181
200
|
&:first-child {
|
|
182
|
-
border-radius:
|
|
201
|
+
border-radius: 9999px 0 0 9999px;
|
|
183
202
|
}
|
|
184
203
|
&:last-child {
|
|
185
|
-
border-radius: 0
|
|
204
|
+
border-radius: 0 9999px 9999px 0;
|
|
186
205
|
}
|
|
187
206
|
}
|
|
188
207
|
}
|
|
@@ -14,15 +14,12 @@ $root-name: card;
|
|
|
14
14
|
|
|
15
15
|
@include m.is(integrate) {
|
|
16
16
|
box-shadow: none;
|
|
17
|
+
--u-card-header-bg: transparent;
|
|
18
|
+
--u-card-action-bg: transparent;
|
|
17
19
|
& > {
|
|
18
20
|
@include m.bem($root-name, header) {
|
|
19
|
-
border-bottom: none;
|
|
20
21
|
padding-bottom: 0;
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
@include m.bem($root-name, action) {
|
|
24
|
-
border-top: none;
|
|
25
|
-
}
|
|
26
23
|
}
|
|
27
24
|
}
|
|
28
25
|
|
|
@@ -40,7 +37,7 @@ $root-name: card;
|
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
@include m.e(header) {
|
|
43
|
-
|
|
40
|
+
background-color: fn.use-var(header-bg, transparent);
|
|
44
41
|
color: fn.use-var(text-color, title);
|
|
45
42
|
}
|
|
46
43
|
|
|
@@ -66,21 +63,10 @@ $root-name: card;
|
|
|
66
63
|
}
|
|
67
64
|
|
|
68
65
|
@include m.e(action) {
|
|
69
|
-
|
|
66
|
+
background-color: fn.use-var(action-bg, transparent);
|
|
70
67
|
|
|
71
68
|
#{fn.bem(button)} + #{fn.bem(button)} {
|
|
72
69
|
margin-left: fn.use-var(gap, default);
|
|
73
70
|
}
|
|
74
71
|
}
|
|
75
|
-
|
|
76
|
-
// @include m.is(always-shadow) {
|
|
77
|
-
// box-shadow: fn.use-var(shadow);
|
|
78
|
-
// }
|
|
79
|
-
|
|
80
|
-
// @include m.is(hover-shadow) {
|
|
81
|
-
// transition: box-shadow 0.25s ease;
|
|
82
|
-
// &:hover {
|
|
83
|
-
// box-shadow: fn.use-var(shadow);
|
|
84
|
-
// }
|
|
85
|
-
// }
|
|
86
72
|
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { createApp, h, nextTick, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import UCollapseItem from '../collapse-item.vue'
|
|
5
|
+
import UCollapse from '../collapse.vue'
|
|
6
|
+
|
|
7
|
+
function mountCollapse(
|
|
8
|
+
props: Record<string, unknown> = {},
|
|
9
|
+
items: Array<{ value: string; title: string }> = [
|
|
10
|
+
{ value: 'a', title: 'Title A' },
|
|
11
|
+
{ value: 'b', title: 'Title B' }
|
|
12
|
+
]
|
|
13
|
+
) {
|
|
14
|
+
const host = document.createElement('div')
|
|
15
|
+
document.body.appendChild(host)
|
|
16
|
+
|
|
17
|
+
const model = ref(props.modelValue)
|
|
18
|
+
const app = createApp({
|
|
19
|
+
render() {
|
|
20
|
+
return h(
|
|
21
|
+
UCollapse,
|
|
22
|
+
{
|
|
23
|
+
...props,
|
|
24
|
+
modelValue: model.value,
|
|
25
|
+
'onUpdate:modelValue': (value: any) => {
|
|
26
|
+
model.value = value
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
() =>
|
|
30
|
+
items.map((item) =>
|
|
31
|
+
h(
|
|
32
|
+
UCollapseItem,
|
|
33
|
+
{ value: item.value, title: item.title },
|
|
34
|
+
() => `Content ${item.value}`
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
app.mount(host)
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
host,
|
|
45
|
+
model,
|
|
46
|
+
unmount() {
|
|
47
|
+
app.unmount()
|
|
48
|
+
host.remove()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('Collapse', () => {
|
|
54
|
+
it('defaultCollapseAll defaults to false, which expands all items when no initial modelValue is provided', async () => {
|
|
55
|
+
const { host, model, unmount } = mountCollapse()
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await nextTick()
|
|
59
|
+
// 默认展开全部:modelValue 应该包含所有项的 value
|
|
60
|
+
expect(model.value).toEqual(['a', 'b'])
|
|
61
|
+
|
|
62
|
+
const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
|
|
63
|
+
expect(activeItems.length).toBe(2)
|
|
64
|
+
} finally {
|
|
65
|
+
unmount()
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('defaultCollapseAll sets to true collapses all items', async () => {
|
|
70
|
+
const { host, model, unmount } = mountCollapse({ defaultCollapseAll: true })
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
await nextTick()
|
|
74
|
+
// 全部折叠:modelValue 应该为空或未定义,没有任何项具有 is-active 样式
|
|
75
|
+
expect(model.value).toBeUndefined()
|
|
76
|
+
|
|
77
|
+
const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
|
|
78
|
+
expect(activeItems.length).toBe(0)
|
|
79
|
+
} finally {
|
|
80
|
+
unmount()
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('defaultCollapseAll sets to empty string collapses all items', async () => {
|
|
85
|
+
const { host, model, unmount } = mountCollapse({ defaultCollapseAll: '' })
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await nextTick()
|
|
89
|
+
expect(model.value).toBeUndefined()
|
|
90
|
+
|
|
91
|
+
const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
|
|
92
|
+
expect(activeItems.length).toBe(0)
|
|
93
|
+
} finally {
|
|
94
|
+
unmount()
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('defaultCollapseAll sets to "true" collapses all items', async () => {
|
|
99
|
+
const { host, model, unmount } = mountCollapse({ defaultCollapseAll: 'true' })
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await nextTick()
|
|
103
|
+
expect(model.value).toBeUndefined()
|
|
104
|
+
|
|
105
|
+
const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
|
|
106
|
+
expect(activeItems.length).toBe(0)
|
|
107
|
+
} finally {
|
|
108
|
+
unmount()
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('respects initial modelValue even if defaultCollapseAll is false', async () => {
|
|
113
|
+
const { host, model, unmount } = mountCollapse({ modelValue: ['a'] })
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await nextTick()
|
|
117
|
+
// 外部传入初始值:即使 defaultCollapseAll 为 false,也仅激活外部指定的项
|
|
118
|
+
expect(model.value).toEqual(['a'])
|
|
119
|
+
|
|
120
|
+
const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
|
|
121
|
+
expect(activeItems.length).toBe(1)
|
|
122
|
+
expect(activeItems[0].querySelector('.u-collapse__title')?.textContent).toBe('Title A')
|
|
123
|
+
} finally {
|
|
124
|
+
unmount()
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('accordion mode with defaultCollapseAll=false only expands the first item', async () => {
|
|
129
|
+
const { host, model, unmount } = mountCollapse({ accordion: true })
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
await nextTick()
|
|
133
|
+
// 手风琴模式且没有初始值,默认只展开第一个
|
|
134
|
+
expect(model.value).toBe('a')
|
|
135
|
+
|
|
136
|
+
const activeItems = host.querySelectorAll('.u-collapse__item.is-active')
|
|
137
|
+
expect(activeItems.length).toBe(1)
|
|
138
|
+
expect(activeItems[0].querySelector('.u-collapse__title')?.textContent).toBe('Title A')
|
|
139
|
+
} finally {
|
|
140
|
+
unmount()
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('toggles collapse items on header click', async () => {
|
|
145
|
+
const { host, model, unmount } = mountCollapse({ defaultCollapseAll: true })
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await nextTick()
|
|
149
|
+
expect(model.value).toBeUndefined()
|
|
150
|
+
|
|
151
|
+
const headers = host.querySelectorAll('.u-collapse__header')
|
|
152
|
+
expect(headers.length).toBe(2)
|
|
153
|
+
|
|
154
|
+
// 点击第一个 header 展开它
|
|
155
|
+
;(headers[0] as HTMLElement).click()
|
|
156
|
+
await nextTick()
|
|
157
|
+
|
|
158
|
+
expect(model.value).toEqual(['a'])
|
|
159
|
+
expect(host.querySelectorAll('.u-collapse__item.is-active').length).toBe(1)
|
|
160
|
+
|
|
161
|
+
// 点击第二个 header 展开它
|
|
162
|
+
;(headers[1] as HTMLElement).click()
|
|
163
|
+
await nextTick()
|
|
164
|
+
|
|
165
|
+
expect(model.value).toEqual(['a', 'b'])
|
|
166
|
+
expect(host.querySelectorAll('.u-collapse__item.is-active').length).toBe(2)
|
|
167
|
+
|
|
168
|
+
// 再次点击第一个 header 收起它
|
|
169
|
+
;(headers[0] as HTMLElement).click()
|
|
170
|
+
await nextTick()
|
|
171
|
+
|
|
172
|
+
expect(model.value).toEqual(['b'])
|
|
173
|
+
expect(host.querySelectorAll('.u-collapse__item.is-active').length).toBe(1)
|
|
174
|
+
} finally {
|
|
175
|
+
unmount()
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
})
|
|
@@ -10,17 +10,11 @@
|
|
|
10
10
|
@keydown.enter.prevent="handleClick"
|
|
11
11
|
@keydown.space.prevent="handleClick"
|
|
12
12
|
>
|
|
13
|
-
<span v-if="showLeftIcon" :class="cls.e('icon')">
|
|
14
|
-
<slot name="icon" :is-active="isActive">
|
|
15
|
-
<UIcon><component :is="iconComponent" /></UIcon>
|
|
16
|
-
</slot>
|
|
17
|
-
</span>
|
|
18
|
-
|
|
19
13
|
<span :class="cls.e('title')">
|
|
20
14
|
<slot name="title">{{ title }}</slot>
|
|
21
15
|
</span>
|
|
22
16
|
|
|
23
|
-
<span
|
|
17
|
+
<span :class="cls.e('icon')">
|
|
24
18
|
<slot name="icon" :is-active="isActive">
|
|
25
19
|
<UIcon><component :is="iconComponent" /></UIcon>
|
|
26
20
|
</slot>
|
|
@@ -55,13 +49,8 @@ const cls = context?.cls ?? bem('collapse')
|
|
|
55
49
|
|
|
56
50
|
const isActive = computed(() => !!context?.activeValues.value.includes(props.value))
|
|
57
51
|
|
|
58
|
-
const iconPosition = computed(() => context?.iconPosition.value ?? 'right')
|
|
59
|
-
|
|
60
52
|
const iconComponent = computed(() => context?.expandIcon.value ?? ArrowRight)
|
|
61
53
|
|
|
62
|
-
const showLeftIcon = computed(() => !props.hideIcon && iconPosition.value === 'left')
|
|
63
|
-
const showRightIcon = computed(() => !props.hideIcon && iconPosition.value === 'right')
|
|
64
|
-
|
|
65
54
|
const classList = computed(() => [
|
|
66
55
|
cls.e('item'),
|
|
67
56
|
bem.is('active', isActive.value),
|
|
@@ -84,6 +73,7 @@ const wrapperEl = ref<HTMLElement>()
|
|
|
84
73
|
const { expandTransition } = context
|
|
85
74
|
|
|
86
75
|
onMounted(() => {
|
|
76
|
+
context?.register(props.value)
|
|
87
77
|
if (!wrapperEl.value) return
|
|
88
78
|
expandTransition.setExpanded(wrapperEl.value, isActive.value)
|
|
89
79
|
})
|
|
@@ -94,6 +84,7 @@ watch(isActive, (active) => {
|
|
|
94
84
|
})
|
|
95
85
|
|
|
96
86
|
onBeforeUnmount(() => {
|
|
87
|
+
context?.unregister(props.value)
|
|
97
88
|
if (!wrapperEl.value) return
|
|
98
89
|
expandTransition.cancel(wrapperEl.value)
|
|
99
90
|
})
|
|
@@ -7,15 +7,9 @@
|
|
|
7
7
|
<script lang="ts" setup>
|
|
8
8
|
import { useFormFallbackProps, useModel } from '@veltra/compositions'
|
|
9
9
|
import { bem, ExpandTransition } from '@veltra/utils'
|
|
10
|
-
import { computed, provide } from 'vue'
|
|
11
|
-
|
|
12
|
-
import type {
|
|
13
|
-
CollapseEmits,
|
|
14
|
-
CollapseModelValue,
|
|
15
|
-
CollapseProps,
|
|
16
|
-
CollapseValue,
|
|
17
|
-
_CollapseExposed
|
|
18
|
-
} from '../../types'
|
|
10
|
+
import { computed, provide, ref } from 'vue'
|
|
11
|
+
|
|
12
|
+
import type { CollapseEmits, CollapseModelValue, CollapseProps, CollapseValue } from '../../types'
|
|
19
13
|
import { CollapseDIKey } from './di'
|
|
20
14
|
|
|
21
15
|
defineOptions({
|
|
@@ -24,8 +18,7 @@ defineOptions({
|
|
|
24
18
|
|
|
25
19
|
const props = withDefaults(defineProps<CollapseProps>(), {
|
|
26
20
|
accordion: false,
|
|
27
|
-
|
|
28
|
-
iconPosition: 'right'
|
|
21
|
+
defaultCollapseAll: false
|
|
29
22
|
})
|
|
30
23
|
const emit = defineEmits<CollapseEmits>()
|
|
31
24
|
|
|
@@ -37,15 +30,9 @@ const expandTransition = new ExpandTransition({
|
|
|
37
30
|
|
|
38
31
|
const { size } = useFormFallbackProps([props], { size: 'default' })
|
|
39
32
|
|
|
40
|
-
const iconPosition = computed(() => props.iconPosition ?? 'right')
|
|
41
33
|
const expandIcon = computed(() => props.expandIcon)
|
|
42
34
|
|
|
43
|
-
const classList = computed(() => [
|
|
44
|
-
cls.b,
|
|
45
|
-
cls.m(size.value),
|
|
46
|
-
cls.m(`icon-${iconPosition.value}`),
|
|
47
|
-
bem.is('bordered', props.bordered)
|
|
48
|
-
])
|
|
35
|
+
const classList = computed(() => [cls.b, cls.m(size.value)])
|
|
49
36
|
|
|
50
37
|
const modelValue = useModel<CollapseProps, 'modelValue'>({
|
|
51
38
|
props,
|
|
@@ -77,51 +64,54 @@ const toggle = (value: CollapseValue) => {
|
|
|
77
64
|
update(values)
|
|
78
65
|
}
|
|
79
66
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
67
|
+
// 收集子组件已注册的 values
|
|
68
|
+
const registeredValues = ref<CollapseValue[]>([])
|
|
69
|
+
|
|
70
|
+
// 初始捕获 modelValue 是否具有有效初值
|
|
71
|
+
const hasInitialValue =
|
|
72
|
+
modelValue.value !== undefined &&
|
|
73
|
+
modelValue.value !== null &&
|
|
74
|
+
(!Array.isArray(modelValue.value) || modelValue.value.length > 0)
|
|
75
|
+
|
|
76
|
+
const register = (value: CollapseValue) => {
|
|
77
|
+
if (!registeredValues.value.includes(value)) {
|
|
78
|
+
registeredValues.value.push(value)
|
|
79
|
+
|
|
80
|
+
// 若默认展开全部(defaultCollapseAll 为 false)且外部未指定初始值,则自动展开该子项
|
|
81
|
+
if (!props.defaultCollapseAll && !hasInitialValue) {
|
|
82
|
+
if (props.accordion) {
|
|
83
|
+
if (registeredValues.value.length === 1) {
|
|
84
|
+
update(value)
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
const current = Array.isArray(modelValue.value)
|
|
88
|
+
? modelValue.value
|
|
89
|
+
: modelValue.value !== undefined && modelValue.value !== null
|
|
90
|
+
? [modelValue.value]
|
|
91
|
+
: []
|
|
92
|
+
if (!current.includes(value)) {
|
|
93
|
+
update([...current, value])
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
85
97
|
}
|
|
86
|
-
update([...activeValues.value, value])
|
|
87
98
|
}
|
|
88
99
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
return
|
|
100
|
+
const unregister = (value: CollapseValue) => {
|
|
101
|
+
const idx = registeredValues.value.indexOf(value)
|
|
102
|
+
if (idx > -1) {
|
|
103
|
+
registeredValues.value.splice(idx, 1)
|
|
94
104
|
}
|
|
95
|
-
update(activeValues.value.filter((v) => v !== value))
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const expandAll = (values: CollapseValue[]) => {
|
|
99
|
-
if (props.accordion) {
|
|
100
|
-
update(values[0] ?? [])
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
update([...values])
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const collapseAll = () => {
|
|
107
|
-
update([])
|
|
108
105
|
}
|
|
109
106
|
|
|
110
107
|
provide(CollapseDIKey, {
|
|
111
108
|
cls,
|
|
112
109
|
size,
|
|
113
|
-
iconPosition,
|
|
114
110
|
expandIcon,
|
|
115
111
|
activeValues,
|
|
116
112
|
toggle,
|
|
117
|
-
expandTransition
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
defineExpose<_CollapseExposed>({
|
|
121
|
-
toggle,
|
|
122
|
-
expand,
|
|
123
|
-
collapse,
|
|
124
|
-
expandAll,
|
|
125
|
-
collapseAll
|
|
113
|
+
expandTransition,
|
|
114
|
+
register,
|
|
115
|
+
unregister
|
|
126
116
|
})
|
|
127
117
|
</script>
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { type BEM, type ComponentSize, ExpandTransition } from '@veltra/utils'
|
|
2
2
|
import type { Component, ComputedRef, InjectionKey } from 'vue'
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { CollapseValue } from '../../types'
|
|
5
5
|
|
|
6
6
|
export interface CollapseContext {
|
|
7
7
|
cls: BEM<'collapse'>
|
|
8
8
|
size: ComputedRef<ComponentSize>
|
|
9
|
-
iconPosition: ComputedRef<CollapseIconPosition>
|
|
10
9
|
expandIcon: ComputedRef<Component | undefined>
|
|
11
10
|
activeValues: ComputedRef<CollapseValue[]>
|
|
12
11
|
toggle: (value: CollapseValue) => void
|
|
13
12
|
expandTransition: ExpandTransition
|
|
13
|
+
register: (value: CollapseValue) => void
|
|
14
|
+
unregister: (value: CollapseValue) => void
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export const CollapseDIKey: InjectionKey<CollapseContext> = Symbol('Collapse')
|
|
@@ -10,54 +10,48 @@ $ease: cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
10
10
|
$transition-fast: 0.18s $ease;
|
|
11
11
|
$transition-normal: 0.24s $ease;
|
|
12
12
|
|
|
13
|
-
// 各尺寸的水平 / 垂直内边距:决定 header 和 content 的呼吸感
|
|
14
|
-
// 节奏在配置项分组场景下既不过紧、也不过松
|
|
15
|
-
$header-padding-x: (
|
|
16
|
-
small: 12px,
|
|
17
|
-
default: 16px,
|
|
18
|
-
large: 20px
|
|
19
|
-
);
|
|
20
|
-
$header-padding-y: (
|
|
21
|
-
small: 10px,
|
|
22
|
-
default: 14px,
|
|
23
|
-
large: 18px
|
|
24
|
-
);
|
|
25
|
-
|
|
26
13
|
@include m.b($root-name) {
|
|
27
14
|
width: 100%;
|
|
28
15
|
font-size: fn.use-var(font-size-main, default);
|
|
29
16
|
color: fn.use-var(text-color, main);
|
|
17
|
+
background-color: transparent;
|
|
30
18
|
|
|
31
|
-
// =====
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
background-color: fn.use-var(bg-color, top);
|
|
37
|
-
box-shadow:
|
|
38
|
-
0 1px 2px rgba(15, 23, 42, 0.04),
|
|
39
|
-
0 1px 3px rgba(15, 23, 42, 0.04);
|
|
40
|
-
overflow: hidden;
|
|
19
|
+
// ===== Item =====
|
|
20
|
+
@include m.e(item) {
|
|
21
|
+
border-radius: calc(fn.use-var(radius, default) + 2px);
|
|
22
|
+
background-color: fn.use-var(bg-color, hover);
|
|
23
|
+
transition: background-color $transition-fast;
|
|
41
24
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
25
|
+
& + #{fn.bem($root-name, item)} {
|
|
26
|
+
margin-top: fn.use-var(gap, default);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@include m.is-not(disabled) {
|
|
30
|
+
&:hover {
|
|
31
|
+
// hover 状态与展开状态保持一致的微加深背景(混入 6% 的正文文字色)
|
|
32
|
+
background-color: color-mix(
|
|
33
|
+
in srgb,
|
|
34
|
+
#{fn.use-var(text-color, main)} 6%,
|
|
35
|
+
#{fn.use-var(bg-color, hover)}
|
|
36
|
+
);
|
|
45
37
|
}
|
|
46
38
|
}
|
|
47
|
-
}
|
|
48
39
|
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
@include m.is(active) {
|
|
41
|
+
// 展开/活动状态背景(混入 6% 的正文文字色)
|
|
42
|
+
background-color: color-mix(
|
|
43
|
+
in srgb,
|
|
44
|
+
#{fn.use-var(text-color, main)} 6%,
|
|
45
|
+
#{fn.use-var(bg-color, hover)}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
51
49
|
@include m.is(disabled) {
|
|
52
50
|
opacity: 0.55;
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
// ===== Header =====
|
|
57
|
-
// 反馈策略:
|
|
58
|
-
// - hover:仅背景轻微变化("我可点击"信号)
|
|
59
|
-
// - active:仅 font-weight 加重 + 箭头旋转("我已展开"信号由内容显现承担)
|
|
60
|
-
// - 颜色不切换到 primary,避免在大量配置块同时展开时产生视觉嘈杂
|
|
61
55
|
@include m.e(header) {
|
|
62
56
|
position: relative;
|
|
63
57
|
display: flex;
|
|
@@ -72,12 +66,7 @@ $header-padding-y: (
|
|
|
72
66
|
transition:
|
|
73
67
|
color $transition-fast,
|
|
74
68
|
background-color $transition-fast;
|
|
75
|
-
|
|
76
|
-
@include m.is-not(disabled) {
|
|
77
|
-
&:hover {
|
|
78
|
-
background-color: fn.use-var(bg-color, hover);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
69
|
+
border-radius: inherit;
|
|
81
70
|
|
|
82
71
|
&:focus-visible {
|
|
83
72
|
box-shadow: inset 0 0 0 2px fn.use-var(color, primary);
|
|
@@ -86,11 +75,6 @@ $header-padding-y: (
|
|
|
86
75
|
@include m.is(disabled) {
|
|
87
76
|
cursor: not-allowed;
|
|
88
77
|
color: fn.use-var(text-color, placeholder);
|
|
89
|
-
|
|
90
|
-
&:hover {
|
|
91
|
-
background-color: transparent;
|
|
92
|
-
color: fn.use-var(text-color, placeholder);
|
|
93
|
-
}
|
|
94
78
|
}
|
|
95
79
|
|
|
96
80
|
@include m.is(active) {
|
|
@@ -124,12 +108,6 @@ $header-padding-y: (
|
|
|
124
108
|
}
|
|
125
109
|
|
|
126
110
|
// ===== Content wrapper =====
|
|
127
|
-
// 高度由共享 expand transition hook 在 isActive 切换时驱动:
|
|
128
|
-
// 未挂载前 → CSS 兜底 height: 0
|
|
129
|
-
// 未激活 → 0px(带 transition)
|
|
130
|
-
// 激活中 → 0 → scrollHeight(CSS transition: height)
|
|
131
|
-
// 已激活 → auto(动画结束后回归,避免锁死内容动态变化)
|
|
132
|
-
// 比 grid-template-rows: 0fr ↔ 1fr 减少每帧 grid 布局重算,嵌套场景显著更流畅。
|
|
133
111
|
@include m.e(content-wrapper) {
|
|
134
112
|
overflow: hidden;
|
|
135
113
|
height: 0;
|
|
@@ -142,50 +120,24 @@ $header-padding-y: (
|
|
|
142
120
|
}
|
|
143
121
|
|
|
144
122
|
// ===== Sizes =====
|
|
145
|
-
// padding-bottom 始终生效;inactive 时由 wrapper height: 0 + overflow: hidden
|
|
146
|
-
// 自然裁切,scrollHeight 计算因此始终一致,避免动画首帧抖动。
|
|
147
123
|
@include m.size using ($size) {
|
|
148
124
|
font-size: fn.use-var(font-size-main, $size);
|
|
149
125
|
|
|
150
126
|
@include m.bem($root-name, header) {
|
|
151
|
-
padding:
|
|
127
|
+
padding: calc(fn.use-var(gap, $size) - 2px) fn.use-var(gap, $size);
|
|
152
128
|
font-size: fn.use-var(font-size-title, $size);
|
|
153
129
|
}
|
|
154
130
|
|
|
155
131
|
@include m.bem($root-name, content) {
|
|
156
132
|
font-size: fn.use-var(font-size-main, $size);
|
|
157
|
-
padding:
|
|
133
|
+
padding: calc(fn.use-var(gap, $size) - 2px) fn.use-var(gap, $size) fn.use-var(gap, $size);
|
|
158
134
|
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ===== Ghost(无边框):项级胶囊化,反馈完全由 item hover 承担 =====
|
|
162
|
-
// 嵌入卡片 / 对话框时使用,不引入额外的容器边界
|
|
163
|
-
@include m.is-not(bordered) {
|
|
164
|
-
background-color: transparent;
|
|
165
135
|
|
|
166
136
|
#{fn.bem($root-name, item)} {
|
|
167
|
-
border-radius: calc(fn.use-var(radius,
|
|
168
|
-
transition: background-color $transition-fast;
|
|
137
|
+
border-radius: calc(fn.use-var(radius, $size) + 2px);
|
|
169
138
|
|
|
170
139
|
& + #{fn.bem($root-name, item)} {
|
|
171
|
-
margin-top:
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
@include m.is-not(disabled) {
|
|
175
|
-
&:hover {
|
|
176
|
-
background-color: fn.use-var(bg-color, hover);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// ghost 下 header 自身不再单独 hover,由 item 整体承载
|
|
182
|
-
#{fn.bem($root-name, header)} {
|
|
183
|
-
border-radius: inherit;
|
|
184
|
-
|
|
185
|
-
@include m.is-not(disabled) {
|
|
186
|
-
&:hover {
|
|
187
|
-
background-color: transparent;
|
|
188
|
-
}
|
|
140
|
+
margin-top: fn.use-var(gap, $size);
|
|
189
141
|
}
|
|
190
142
|
}
|
|
191
143
|
}
|