srcdev-nuxt-components 6.2.11 → 6.2.12

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.
@@ -22,18 +22,31 @@
22
22
  <Icon name="ic:outline-desktop-mac" class="icon" :class="[{ current: canvasName === 'desktopCanvas' }]" />
23
23
  </button>
24
24
  </li>
25
+ <li>
26
+ <button type="button" @click.stop.prevent="updateCanvas('fullWidthCanvas')">
27
+ <Icon
28
+ name="pixelarticons:viewport-wide"
29
+ class="icon"
30
+ :class="[{ current: canvasName === 'fullWidthCanvas' }]"
31
+ />
32
+ </button>
33
+ </li>
25
34
  </ul>
26
35
  </div>
27
36
  </template>
28
37
 
38
+ <script lang="ts">
39
+ export type MediaCanvas = "mobileCanvas" | "tabletCanvas" | "laptopCanvas" | "desktopCanvas" | "fullWidthCanvas"
40
+ </script>
41
+
29
42
  <script setup lang="ts">
30
- import type { MediaCanvas } from '@/types/types.canvasName';
43
+ // import type { MediaCanvas } from "@/types"
31
44
 
32
- const canvasName = defineModel<MediaCanvas>('canvasName');
45
+ const canvasName = defineModel<MediaCanvas>("canvasName")
33
46
 
34
47
  const updateCanvas = (setCanvas: MediaCanvas) => {
35
- canvasName.value = setCanvas;
36
- };
48
+ canvasName.value = setCanvas
49
+ }
37
50
  </script>
38
51
 
39
52
  <style lang="css">
@@ -74,4 +87,20 @@ const updateCanvas = (setCanvas: MediaCanvas) => {
74
87
  }
75
88
  }
76
89
  }
90
+
91
+ .mobileCanvas {
92
+ max-width: 412px;
93
+ }
94
+ .tabletCanvas {
95
+ max-width: 768px;
96
+ }
97
+ .laptopCanvas {
98
+ max-width: 1060px;
99
+ }
100
+ .desktopCanvas {
101
+ max-width: 1280px;
102
+ }
103
+ .fullWidthCanvas {
104
+ max-width: unset;
105
+ }
77
106
  </style>
@@ -0,0 +1,265 @@
1
+ <template>
2
+ <div class="masonry-grid-ordered" :class="[elementClasses]">
3
+ <div
4
+ class="masonry-grid-ordered-wrapper"
5
+ :class="[{ 'multiple-cols': !isSingleColumn, 'setup-complete': isSetupComplete }]"
6
+ ref="gridWrapper"
7
+ >
8
+ <div v-for="item in props.gridData" :key="item.id" class="masonry-grid-ordered-item" ref="gridItemsRefs">
9
+ <div class="masonry-grid-ordered-content" ref="gridContentRefs">
10
+ <slot :name="item.id"></slot>
11
+ </div>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { computed, nextTick, onMounted, ref, watch, type PropType } from "vue"
19
+ import { useElementSize, useResizeObserver } from "@vueuse/core"
20
+
21
+ const props = defineProps({
22
+ gridData: {
23
+ type: Array as PropType<{ id: string }[]>,
24
+ default: () => [],
25
+ },
26
+ minTileWidth: {
27
+ type: Number,
28
+ default: 312,
29
+ },
30
+ gap: {
31
+ type: Number,
32
+ default: 12,
33
+ },
34
+ styleClassPassthrough: {
35
+ type: [String, Array] as PropType<string | string[]>,
36
+ default: () => [],
37
+ },
38
+ fixedWidth: {
39
+ type: Boolean,
40
+ default: false,
41
+ },
42
+ justify: {
43
+ type: String as PropType<"left" | "center" | "right">,
44
+ default: "left",
45
+ validator: (val: string) => ["left", "center", "right"].includes(val),
46
+ },
47
+ })
48
+
49
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
50
+
51
+ const gridWrapper = ref<null | HTMLDivElement>(null)
52
+ const gridItemsRefs = ref<HTMLDivElement[]>([])
53
+ const gridContentRefs = ref<HTMLDivElement[]>([])
54
+ const { width } = useElementSize(gridWrapper)
55
+
56
+ // Track item properties for masonry positioning
57
+ interface ItemData {
58
+ height: number
59
+ column: number
60
+ row: number
61
+ top: number
62
+ bottom: number
63
+ translateY: number
64
+ }
65
+ const itemDataArray = ref<ItemData[]>([])
66
+ const isSetupComplete = ref(false)
67
+
68
+ const columnCount = computed(() => {
69
+ if (width.value === 0) return 1
70
+ // Match the CSS container query breakpoint (636px hard-coded for now)
71
+ // if (width.value < 636) return 1
72
+ return Math.max(1, Math.floor(width.value / (props.minTileWidth + props.gap)))
73
+ })
74
+
75
+ const isSingleColumn = computed(() => columnCount.value === 1)
76
+
77
+ const gapStr = computed(() => `${props.gap}px`)
78
+
79
+ const minTileWidthStr = computed(() => `${props.minTileWidth}px`)
80
+ const maxTileWidth = computed(() => {
81
+ return props.fixedWidth ? `${props.minTileWidth}px` : "1fr"
82
+ })
83
+
84
+ const justifyContent = computed(() => {
85
+ return props.fixedWidth ? props.justify : "stretch"
86
+ })
87
+
88
+ const updateGrid = () => {
89
+ if (gridWrapper.value !== null && columnCount.value > 1) {
90
+ // Initialize or reset the item data array
91
+ itemDataArray.value = Array(props.gridData.length)
92
+ .fill(null)
93
+ .map(() => ({ height: 0, column: 0, row: 0, top: 0, bottom: 0, translateY: 0 }))
94
+
95
+ // Step 1: Hide items for measurement
96
+ gridItemsRefs.value.forEach((itemEl) => {
97
+ if (itemEl) {
98
+ itemEl.style.setProperty("--_opacity", "0")
99
+ }
100
+ })
101
+
102
+ // Force a reflow to get accurate measurements in grid layout
103
+ void gridWrapper.value.offsetHeight
104
+
105
+ // Step 2: Measure heights and store in array
106
+ gridContentRefs.value.forEach((contentEl, index) => {
107
+ if (!contentEl || !gridItemsRefs.value[index] || !itemDataArray.value[index]) return
108
+
109
+ const itemEl = gridItemsRefs.value[index]
110
+ const contentHeight = contentEl.offsetHeight
111
+
112
+ // Calculate which column this item would be in based on CSS Grid's auto-fit
113
+ const column = index % columnCount.value
114
+
115
+ // Calculate which row this item would be in based on CSS Grid's auto-fit
116
+ const row = Math.floor(index / columnCount.value)
117
+
118
+ let top: number
119
+ let bottom: number
120
+ let translateY: number
121
+
122
+ // Always get the actual measured position first
123
+ const wrapperRect = gridWrapper.value!.getBoundingClientRect()
124
+ const itemRect = itemEl.getBoundingClientRect()
125
+ const actualTop = itemRect.top - wrapperRect.top
126
+
127
+ if (row === 0) {
128
+ // First row: use natural CSS Grid position
129
+ top = actualTop
130
+ bottom = top + contentHeight
131
+ translateY = 0 // No transform needed for first row
132
+ } else {
133
+ // Subsequent rows: position based on item above in same column
134
+ const itemAboveIndex = index - columnCount.value
135
+ const itemAbove = itemDataArray.value[itemAboveIndex]
136
+
137
+ if (itemAbove) {
138
+ top = itemAbove.bottom + props.gap
139
+ bottom = top + contentHeight
140
+ // Calculate the transform needed to move from actual position to desired position
141
+ translateY = top - actualTop
142
+ } else {
143
+ // Fallback to natural position if no item above found
144
+ top = actualTop
145
+ bottom = top + contentHeight
146
+ translateY = 0
147
+ }
148
+ }
149
+
150
+ // Store the data in our tracking array
151
+ itemDataArray.value[index].height = contentHeight
152
+ itemDataArray.value[index].column = column
153
+ itemDataArray.value[index].row = row
154
+ itemDataArray.value[index].top = top
155
+ itemDataArray.value[index].bottom = bottom
156
+ itemDataArray.value[index].translateY = translateY
157
+
158
+ // Set the CSS custom properties
159
+ itemEl.style.setProperty("--_item-height", `${contentHeight}px`)
160
+ itemEl.style.setProperty("--_translate-y", `${translateY}px`)
161
+ itemEl.style.setProperty("--_opacity", "1")
162
+ })
163
+
164
+ // Log the complete item data array when calculations are complete
165
+ // console.log("📊 Item data array:", itemDataArray.value)
166
+
167
+ // Mark setup as complete
168
+ isSetupComplete.value = true
169
+ } else {
170
+ // Reset setup state for single column
171
+ isSetupComplete.value = false
172
+
173
+ // Reset item data array
174
+ itemDataArray.value = []
175
+
176
+ // Single column: reset to normal flow
177
+ gridItemsRefs.value.forEach((itemEl) => {
178
+ if (itemEl) {
179
+ itemEl.style.removeProperty("--_opacity")
180
+ itemEl.style.removeProperty("--_item-height")
181
+ itemEl.style.removeProperty("--_translate-y")
182
+ }
183
+ })
184
+ gridWrapper.value?.style.removeProperty("--_wrapper-height")
185
+ }
186
+ }
187
+
188
+ useResizeObserver(gridWrapper, async () => {
189
+ // console.log("useResizeObserver triggered")
190
+ // itemDataArray.value = [] // Clear previous data
191
+ // await useSleep(100)
192
+ // console.log("useResizeObserver after sleep")
193
+ nextTick(() => updateGrid())
194
+
195
+ // requestAnimationFrame(() => {
196
+ // updateGrid()
197
+ // nextTick(() => updateGrid())
198
+ // })
199
+
200
+ // Add a small delay to ensure DOM is fully rendered and measured
201
+ // setTimeout(() => {
202
+ // nextTick(() => updateGrid())
203
+ // }, 100)
204
+ })
205
+
206
+ onMounted(() => {
207
+ // Add a small delay to ensure DOM is fully rendered and measured
208
+ setTimeout(() => {
209
+ nextTick(() => updateGrid())
210
+ }, 100)
211
+ })
212
+
213
+ watch(
214
+ () => props.fixedWidth,
215
+ () => {
216
+ updateGrid()
217
+ }
218
+ )
219
+
220
+ watch(
221
+ () => props.gridData,
222
+ () => {
223
+ // nextTick(() => updateGrid())
224
+ }
225
+ )
226
+
227
+ watch(
228
+ () => width.value,
229
+ (newWidth) => {
230
+ if (newWidth > 0) {
231
+ // nextTick(() => updateGrid())
232
+ }
233
+ }
234
+ )
235
+
236
+ watch(
237
+ () => props.styleClassPassthrough,
238
+ () => {
239
+ resetElementClasses(props.styleClassPassthrough)
240
+ }
241
+ )
242
+ </script>
243
+
244
+ <style lang="css">
245
+ .masonry-grid-ordered {
246
+ --_transition-duration: 0.3s;
247
+
248
+ .masonry-grid-ordered-wrapper {
249
+ display: grid;
250
+ gap: v-bind(gapStr);
251
+ grid-template-columns: repeat(auto-fit, minmax(v-bind(minTileWidthStr), 1fr));
252
+
253
+ height: var(--_wrapper-height, auto);
254
+
255
+ &.multiple-cols {
256
+ .masonry-grid-ordered-item {
257
+ height: var(--_item-height, auto);
258
+ opacity: var(--_opacity, 1);
259
+ transform: translateY(var(--_translate-y, 0px));
260
+ transition: transform var(--_transition-duration) ease;
261
+ }
262
+ }
263
+ }
264
+ }
265
+ </style>
@@ -5,7 +5,7 @@
5
5
  v-for="(navGroup, groupKey) in responsiveNavLinks"
6
6
  :key="groupKey"
7
7
  class="main-navigation-list"
8
- :ref="(el: HTMLUListElement | null) => setNavRef(String(groupKey), el as HTMLUListElement | null)"
8
+ :ref="(el: Element | ComponentPublicInstance | null) => setNavRef(String(groupKey), el as HTMLUListElement | null)"
9
9
  >
10
10
  <li
11
11
  v-for="(link, localIndex) in navGroup"
@@ -263,8 +263,8 @@ const mainNavigationState = ref<ResponsiveHeaderState>({
263
263
 
264
264
  const navRefs = ref<Record<string, HTMLUListElement | null>>({})
265
265
 
266
- const setNavRef = (key: string, el: HTMLUListElement | null) => {
267
- navRefs.value[key] = el
266
+ const setNavRef = (key: string, el: Element | ComponentPublicInstance | null) => {
267
+ navRefs.value[key] = el as HTMLUListElement | null
268
268
  }
269
269
 
270
270
  const navigationWrapperRects = computed({
@@ -1,4 +1,4 @@
1
- import type { IFlooredRect } from "@/types/responsiveHeader"
1
+ import type { IFlooredRect } from "@/types"
2
2
 
3
3
  export const useNavigationState = () => {
4
4
  // Simple reactive refs that persist across route changes using useState
@@ -4,3 +4,4 @@ export * from "../components/carousel-basic/CarouselBasic.vue"
4
4
  export * from "../components/image-galleries/SliderGallery.vue"
5
5
  export * from "../components/display-toast/DisplayToast.vue"
6
6
  export * from "../components/alert-mask/AlertMaskCore.vue"
7
+ export * from "../components/canvas-switcher/CanvasSwitcher.vue"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "6.2.11",
4
+ "version": "6.2.12",
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",