srcdev-nuxt-components 2.1.8 → 2.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.
@@ -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,171 @@
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
+ // Create a global store to track open details elements by name
19
+ const openDetailsByName = reactive(new Map<string, HTMLDetailsElement>());
20
+
21
+ export const useDetailsTransition = (detailsRef: Ref<HTMLDetailsElement | null>, summaryRef: Ref<HTMLElement | null>, contentRef: Ref<HTMLDivElement | null>, name: string) => {
22
+ // State
23
+ const animation = ref<Animation | null>(null);
24
+ const animationDuration = 400;
25
+ const isClosing = ref(false);
26
+ const isExpanding = ref(false);
27
+
28
+ // Check if refs are available
29
+ if (!detailsRef.value || !summaryRef.value || !contentRef.value) {
30
+ console.warn('Details, summary, or content ref is null');
31
+ return {
32
+ clickAction: () => console.warn('Component not fully initialized'),
33
+ };
34
+ }
35
+
36
+ const closeOtherDetailsWithSameName = () => {
37
+ const currentDetails = detailsRef.value;
38
+ if (!currentDetails || !name) return;
39
+
40
+ // Get the currently open details with the same name
41
+ const openDetails = openDetailsByName.get(name);
42
+
43
+ // If there's an open details with the same name and it's not the current one, close it
44
+ if (openDetails && openDetails !== currentDetails && openDetails.open) {
45
+ // Simulate a click on the other details to close it with animation
46
+ const otherSummary = openDetails.querySelector('summary');
47
+ if (otherSummary) {
48
+ otherSummary.click();
49
+ } else {
50
+ // Fallback: close directly without animation
51
+ openDetails.open = false;
52
+ }
53
+ }
54
+
55
+ // Update the map with the current details if it's open
56
+ if (currentDetails.open) {
57
+ openDetailsByName.set(name, currentDetails);
58
+ } else {
59
+ // If it's closed and was the one in the map, remove it
60
+ if (openDetailsByName.get(name) === currentDetails) {
61
+ openDetailsByName.delete(name);
62
+ }
63
+ }
64
+ };
65
+
66
+ const clickAction = () => {
67
+ const details = detailsRef.value;
68
+ const summary = summaryRef.value;
69
+ const content = contentRef.value;
70
+
71
+ if (!details || !summary || !content) return;
72
+
73
+ // Add overflow hidden to avoid content jumping
74
+ details.style.overflow = 'hidden';
75
+
76
+ if (isClosing.value || !details.open) {
77
+ // Close other details with the same name first
78
+ closeOtherDetailsWithSameName();
79
+
80
+ // Open the details
81
+ details.open = true;
82
+ isExpanding.value = true;
83
+ isClosing.value = false;
84
+
85
+ // Get the height of the content
86
+ const detailsHeight = details.offsetHeight;
87
+ const contentHeight = content.offsetHeight;
88
+ const summaryHeight = summary.offsetHeight;
89
+
90
+ const startHeight = `${detailsHeight - contentHeight}px`;
91
+ const endHeight = `${summaryHeight + contentHeight}px`;
92
+
93
+ // If there's an animation running, cancel it
94
+ if (animation.value) {
95
+ animation.value.cancel();
96
+ }
97
+
98
+ // Start animation
99
+ animation.value = details.animate(
100
+ {
101
+ height: [startHeight, endHeight],
102
+ },
103
+ {
104
+ duration: animationDuration,
105
+ easing: 'ease-out',
106
+ }
107
+ );
108
+
109
+ animation.value.onfinish = () => {
110
+ // Animation finished - reset everything
111
+ details.style.height = 'auto';
112
+ details.style.overflow = '';
113
+ isExpanding.value = false;
114
+ animation.value = null;
115
+
116
+ // Register this as the open details for this name
117
+ openDetailsByName.set(name, details);
118
+ };
119
+
120
+ animation.value.oncancel = () => {
121
+ isExpanding.value = false;
122
+ };
123
+ } else if (isExpanding.value || details.open) {
124
+ // Close the details
125
+ isClosing.value = true;
126
+ isExpanding.value = false;
127
+
128
+ // Get the height of the content
129
+ const startHeight = `${details.offsetHeight}px`;
130
+ const endHeight = `${details.offsetHeight - content.offsetHeight}px`;
131
+
132
+ // If there's an animation running, cancel it
133
+ if (animation.value) {
134
+ animation.value.cancel();
135
+ }
136
+
137
+ // Start animation
138
+ animation.value = details.animate(
139
+ {
140
+ height: [startHeight, endHeight],
141
+ },
142
+ {
143
+ duration: animationDuration,
144
+ easing: 'ease-out',
145
+ }
146
+ );
147
+
148
+ animation.value.onfinish = () => {
149
+ // Animation finished - reset everything
150
+ details.open = false;
151
+ details.style.height = 'auto';
152
+ details.style.overflow = '';
153
+ isClosing.value = false;
154
+ animation.value = null;
155
+
156
+ // Remove this from the open details map if it's there
157
+ if (openDetailsByName.get(name) === details) {
158
+ openDetailsByName.delete(name);
159
+ }
160
+ };
161
+
162
+ animation.value.oncancel = () => {
163
+ isClosing.value = false;
164
+ };
165
+ }
166
+ };
167
+
168
+ return {
169
+ clickAction,
170
+ isClosing,
171
+ isExpanding,
172
+ };
173
+ };
174
+ </script>
175
+
17
176
  <script setup lang="ts">
18
177
  const props = defineProps({
19
178
  name: {
@@ -44,12 +203,34 @@ const { elementClasses, resetElementClasses, updateElementClasses } = useStyleCl
44
203
 
45
204
  updateElementClasses([props.iconSize]);
46
205
 
206
+ const detailsRef = ref<HTMLDetailsElement | null>(null);
207
+ const summaryRef = ref<HTMLElement | null>(null);
208
+ const contentRef = ref<HTMLDivElement | null>(null);
209
+
210
+ // Initialize with dummy function that will be replaced when refs are available
211
+ let clickAction = () => console.warn('Component not fully initialized');
212
+
213
+ // Handle click with the current clickAction function
214
+ const handleClick = () => {
215
+ clickAction();
216
+ };
217
+
47
218
  watch(
48
219
  () => props.styleClassPassthrough,
49
220
  () => {
50
221
  resetElementClasses(props.styleClassPassthrough);
51
222
  }
52
223
  );
224
+
225
+ onMounted(() => {
226
+ // Initialize the composable once the component is mounted and refs are available
227
+ if (detailsRef.value && contentRef.value && summaryRef.value) {
228
+ const details = useDetailsTransition(detailsRef, summaryRef, contentRef, props.name);
229
+ clickAction = details.clickAction; // Assign the real click handler
230
+ } else {
231
+ console.error('Refs not available after mounting');
232
+ }
233
+ });
53
234
  </script>
54
235
 
55
236
  <style lang="css">
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "2.1.8",
4
+ "version": "2.1.10",
5
5
  "main": "nuxt.config.ts",
6
6
  "scripts": {
7
7
  "clean": "rm -rf .nuxt && rm -rf .output && rm -rf .playground/.nuxt && rm -rf .playground/.output",