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: 12px;
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "2.1.7",
4
+ "version": "2.1.9",
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",