srcdev-nuxt-components 2.1.7 → 2.1.9
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<details :name class="display-details" :class="[elementClasses]">
|
|
3
|
-
<summary class="display-details-summary" :id="triggerId" :aria-controls="contentId">
|
|
2
|
+
<details @click.prevent="handleClick()" :name="name" class="display-details" :class="[elementClasses]" ref="detailsRef">
|
|
3
|
+
<summary class="display-details-summary" :id="triggerId" :aria-controls="contentId" ref="summaryRef">
|
|
4
4
|
<span class="label">
|
|
5
5
|
<slot name="summary"></slot>
|
|
6
6
|
</span>
|
|
@@ -8,12 +8,128 @@
|
|
|
8
8
|
<Icon name="bi:caret-down-fill" class="icon mi-12" :class="iconsSize" />
|
|
9
9
|
</slot>
|
|
10
10
|
</summary>
|
|
11
|
-
<div class="display-details-content" :aria-labelledby="triggerId" :id="contentId" role="region">
|
|
11
|
+
<div class="display-details-content" :aria-labelledby="triggerId" :id="contentId" role="region" ref="contentRef">
|
|
12
12
|
<slot name="content"></slot>
|
|
13
13
|
</div>
|
|
14
14
|
</details>
|
|
15
15
|
</template>
|
|
16
16
|
|
|
17
|
+
<script lang="ts">
|
|
18
|
+
export const useDetailsTransition = (detailsRef: Ref<HTMLDetailsElement | null>, summaryRef: Ref<HTMLElement | null>, contentRef: Ref<HTMLDivElement | null>) => {
|
|
19
|
+
// State
|
|
20
|
+
const animation = ref<Animation | null>(null);
|
|
21
|
+
const animationDuration = 400;
|
|
22
|
+
const isClosing = ref(false);
|
|
23
|
+
const isExpanding = ref(false);
|
|
24
|
+
|
|
25
|
+
// Check if refs are available
|
|
26
|
+
if (!detailsRef.value || !summaryRef.value || !contentRef.value) {
|
|
27
|
+
console.warn('Details or content ref is null');
|
|
28
|
+
return {
|
|
29
|
+
clickAction: () => console.warn('Component not fully initialized'),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const clickAction = () => {
|
|
34
|
+
const details = detailsRef.value;
|
|
35
|
+
const summary = summaryRef.value;
|
|
36
|
+
const content = contentRef.value;
|
|
37
|
+
|
|
38
|
+
if (!details || !summary || !content) return;
|
|
39
|
+
|
|
40
|
+
// Add overflow hidden to avoid content jumping
|
|
41
|
+
details.style.overflow = 'hidden';
|
|
42
|
+
|
|
43
|
+
if (isClosing.value || !details.open) {
|
|
44
|
+
// Open the details
|
|
45
|
+
details.open = true;
|
|
46
|
+
isExpanding.value = true;
|
|
47
|
+
isClosing.value = false;
|
|
48
|
+
|
|
49
|
+
// Get the height of the content
|
|
50
|
+
|
|
51
|
+
const detailsHeight = details.offsetHeight;
|
|
52
|
+
const contentHeight = content.offsetHeight;
|
|
53
|
+
const summaryHeight = summary.offsetHeight;
|
|
54
|
+
|
|
55
|
+
const startHeight = `${detailsHeight - contentHeight}px`;
|
|
56
|
+
const endHeight = `${summaryHeight + contentHeight}px`;
|
|
57
|
+
|
|
58
|
+
// If there's an animation running, cancel it
|
|
59
|
+
if (animation.value) {
|
|
60
|
+
animation.value.cancel();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Start animation
|
|
64
|
+
animation.value = details.animate(
|
|
65
|
+
{
|
|
66
|
+
height: [startHeight, endHeight],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
duration: animationDuration,
|
|
70
|
+
easing: 'ease-out',
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
animation.value.onfinish = () => {
|
|
75
|
+
// Animation finished - reset everything
|
|
76
|
+
details.style.height = 'auto';
|
|
77
|
+
details.style.overflow = '';
|
|
78
|
+
isExpanding.value = false;
|
|
79
|
+
animation.value = null;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
animation.value.oncancel = () => {
|
|
83
|
+
isExpanding.value = false;
|
|
84
|
+
};
|
|
85
|
+
} else if (isExpanding.value || details.open) {
|
|
86
|
+
// Close the details
|
|
87
|
+
isClosing.value = true;
|
|
88
|
+
isExpanding.value = false;
|
|
89
|
+
|
|
90
|
+
// Get the height of the content
|
|
91
|
+
const startHeight = `${details.offsetHeight}px`;
|
|
92
|
+
const endHeight = `${details.offsetHeight - content.offsetHeight}px`;
|
|
93
|
+
|
|
94
|
+
// If there's an animation running, cancel it
|
|
95
|
+
if (animation.value) {
|
|
96
|
+
animation.value.cancel();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Start animation
|
|
100
|
+
animation.value = details.animate(
|
|
101
|
+
{
|
|
102
|
+
height: [startHeight, endHeight],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
duration: animationDuration,
|
|
106
|
+
easing: 'ease-out',
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
animation.value.onfinish = () => {
|
|
111
|
+
// Animation finished - reset everything
|
|
112
|
+
details.open = false;
|
|
113
|
+
details.style.height = 'auto';
|
|
114
|
+
details.style.overflow = '';
|
|
115
|
+
isClosing.value = false;
|
|
116
|
+
animation.value = null;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
animation.value.oncancel = () => {
|
|
120
|
+
isClosing.value = false;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
clickAction,
|
|
127
|
+
isClosing,
|
|
128
|
+
isExpanding,
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
</script>
|
|
132
|
+
|
|
17
133
|
<script setup lang="ts">
|
|
18
134
|
const props = defineProps({
|
|
19
135
|
name: {
|
|
@@ -44,19 +160,54 @@ const { elementClasses, resetElementClasses, updateElementClasses } = useStyleCl
|
|
|
44
160
|
|
|
45
161
|
updateElementClasses([props.iconSize]);
|
|
46
162
|
|
|
163
|
+
const detailsRef = ref<HTMLDetailsElement | null>(null);
|
|
164
|
+
const summaryRef = ref<HTMLElement | null>(null);
|
|
165
|
+
const contentRef = ref<HTMLDivElement | null>(null);
|
|
166
|
+
|
|
167
|
+
// Initialize with dummy function that will be replaced when refs are available
|
|
168
|
+
let clickAction = () => console.warn('Component not fully initialized');
|
|
169
|
+
|
|
170
|
+
// Handle click with the current clickAction function
|
|
171
|
+
const handleClick = () => {
|
|
172
|
+
clickAction();
|
|
173
|
+
};
|
|
174
|
+
|
|
47
175
|
watch(
|
|
48
176
|
() => props.styleClassPassthrough,
|
|
49
177
|
() => {
|
|
50
178
|
resetElementClasses(props.styleClassPassthrough);
|
|
51
179
|
}
|
|
52
180
|
);
|
|
181
|
+
|
|
182
|
+
onMounted(() => {
|
|
183
|
+
// Initialize the composable once the component is mounted and refs are available
|
|
184
|
+
if (detailsRef.value && contentRef.value && summaryRef.value) {
|
|
185
|
+
const details = useDetailsTransition(detailsRef, summaryRef, contentRef);
|
|
186
|
+
clickAction = details.clickAction; // Assign the real click handler
|
|
187
|
+
} else {
|
|
188
|
+
console.error('Refs not available after mounting');
|
|
189
|
+
}
|
|
190
|
+
});
|
|
53
191
|
</script>
|
|
54
192
|
|
|
55
193
|
<style lang="css">
|
|
56
194
|
.display-details {
|
|
195
|
+
/* Component setup */
|
|
57
196
|
--_display-details-icon-transform: scaleY(1);
|
|
58
197
|
--_display-details-icon-size: 1.2rem;
|
|
59
198
|
|
|
199
|
+
/* Configurable properties */
|
|
200
|
+
--_display-details-border: none;
|
|
201
|
+
--_display-details-outline: none;
|
|
202
|
+
--_display-details-box-shadow: none;
|
|
203
|
+
--_display-details-border-radius: 0;
|
|
204
|
+
--_display-details-mbe: 1em;
|
|
205
|
+
|
|
206
|
+
--_display-details-summary-gap: 12px;
|
|
207
|
+
--_display-details-summary-flex-direction: row;
|
|
208
|
+
|
|
209
|
+
--_display-details-content-padding: 0;
|
|
210
|
+
|
|
60
211
|
&.medium {
|
|
61
212
|
--_display-details-icon-size: 1.8rem;
|
|
62
213
|
}
|
|
@@ -68,6 +219,12 @@ watch(
|
|
|
68
219
|
--_display-details-icon-transform: scaleY(-1);
|
|
69
220
|
}
|
|
70
221
|
|
|
222
|
+
border: var(--_display-details-border);
|
|
223
|
+
outline: var(--_display-details-outline);
|
|
224
|
+
box-shadow: var(--_display-details-box-shadow);
|
|
225
|
+
border-radius: var(--_display-details-border-radius);
|
|
226
|
+
margin-block-end: var(--_display-details-mbe);
|
|
227
|
+
|
|
71
228
|
.display-details-summary {
|
|
72
229
|
list-style: none;
|
|
73
230
|
|
|
@@ -77,8 +234,10 @@ watch(
|
|
|
77
234
|
}
|
|
78
235
|
|
|
79
236
|
display: flex !important;
|
|
237
|
+
flex-direction: var(--_display-details-summary-flex-direction);
|
|
80
238
|
align-items: center;
|
|
81
|
-
gap:
|
|
239
|
+
gap: var(--_display-details-summary-gap);
|
|
240
|
+
overflow: clip;
|
|
82
241
|
|
|
83
242
|
.label {
|
|
84
243
|
display: block;
|
|
@@ -95,6 +254,7 @@ watch(
|
|
|
95
254
|
}
|
|
96
255
|
|
|
97
256
|
.display-details-content {
|
|
257
|
+
padding: var(--_display-details-content-padding);
|
|
98
258
|
}
|
|
99
259
|
}
|
|
100
260
|
</style>
|
package/package.json
CHANGED