@bagelink/vue 1.6.2 → 1.6.7
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/Swiper.vue.d.ts +85 -0
- package/dist/components/Swiper.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts +3 -3
- package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.cjs +46 -20
- package/dist/index.mjs +8840 -4142
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/components/Swiper.vue +507 -0
- package/src/components/form/inputs/CodeEditor/Index.vue +49 -9
- package/src/components/index.ts +1 -0
- package/src/styles/layout.css +57 -1
- package/src/styles/mobilLayout.css +59 -1
- package/src/styles/text.css +45 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bagelink/vue",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.7",
|
|
5
5
|
"description": "Bagel core sdk packages",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Neveh Allon",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"bin/experimentalGenTypedRoutes.ts"
|
|
59
59
|
],
|
|
60
60
|
"devDependencies": {
|
|
61
|
+
"swiper": "^12.0.3",
|
|
61
62
|
"@types/leaflet": "^1.9.18",
|
|
62
63
|
"@types/signature_pad": "^4.0.0",
|
|
63
64
|
"@vue-macros/reactivity-transform": "^1.1.6",
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T = any">
|
|
2
|
+
import type { SwiperContainer } from 'swiper/element/bundle'
|
|
3
|
+
import type { AutoplayOptions, CoverflowEffectOptions, PaginationOptions, SwiperOptions } from 'swiper/types'
|
|
4
|
+
import { Icon } from '@bagelink/vue'
|
|
5
|
+
import { register } from 'swiper/element/bundle'
|
|
6
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
7
|
+
import 'swiper/css'
|
|
8
|
+
import 'swiper/css/navigation'
|
|
9
|
+
import 'swiper/css/pagination'
|
|
10
|
+
import 'swiper/css/effect-fade'
|
|
11
|
+
import 'swiper/css/effect-coverflow'
|
|
12
|
+
import 'swiper/css/effect-cube'
|
|
13
|
+
import 'swiper/css/effect-flip'
|
|
14
|
+
|
|
15
|
+
type SwiperEffect = 'slide' | 'fade' | 'cube' | 'coverflow' | 'flip'
|
|
16
|
+
type SwiperDirection = 'horizontal' | 'vertical'
|
|
17
|
+
type SwiperVariant = 'default' | 'testimonial' | 'gallery' | 'cards' | 'coverflow' | 'hero'
|
|
18
|
+
|
|
19
|
+
interface VariantConfig {
|
|
20
|
+
slidesPerView?: number | 'auto'
|
|
21
|
+
spaceBetween?: number
|
|
22
|
+
effect?: SwiperEffect
|
|
23
|
+
loop?: boolean
|
|
24
|
+
autoplay?: boolean | AutoplayOptions
|
|
25
|
+
pagination?: boolean | PaginationOptions
|
|
26
|
+
navigation?: boolean
|
|
27
|
+
speed?: number
|
|
28
|
+
centeredSlides?: boolean
|
|
29
|
+
coverflowEffect?: Partial<CoverflowEffectOptions>
|
|
30
|
+
breakpoints?: SwiperOptions['breakpoints']
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const props = withDefaults(
|
|
34
|
+
defineProps<{
|
|
35
|
+
// Items to render
|
|
36
|
+
items?: T[]
|
|
37
|
+
|
|
38
|
+
// Active index (v-model support)
|
|
39
|
+
index?: number
|
|
40
|
+
|
|
41
|
+
// Preset variant
|
|
42
|
+
variant?: SwiperVariant
|
|
43
|
+
|
|
44
|
+
// Slide dimensions
|
|
45
|
+
slideWidth?: number | string
|
|
46
|
+
|
|
47
|
+
// Basic swiper options
|
|
48
|
+
effect?: SwiperEffect
|
|
49
|
+
direction?: SwiperDirection
|
|
50
|
+
speed?: number
|
|
51
|
+
loop?: boolean
|
|
52
|
+
initialSlide?: number
|
|
53
|
+
|
|
54
|
+
// Slides display
|
|
55
|
+
slidesPerView?: number | 'auto'
|
|
56
|
+
spaceBetween?: number
|
|
57
|
+
centeredSlides?: boolean
|
|
58
|
+
|
|
59
|
+
// Interaction
|
|
60
|
+
grabCursor?: boolean
|
|
61
|
+
keyboard?: boolean
|
|
62
|
+
mousewheel?: boolean
|
|
63
|
+
|
|
64
|
+
// Auto height
|
|
65
|
+
autoHeight?: boolean
|
|
66
|
+
|
|
67
|
+
// Navigation
|
|
68
|
+
navigation?: boolean
|
|
69
|
+
|
|
70
|
+
// Pagination
|
|
71
|
+
pagination?: boolean | PaginationOptions
|
|
72
|
+
|
|
73
|
+
// Autoplay
|
|
74
|
+
autoplay?: boolean | AutoplayOptions
|
|
75
|
+
|
|
76
|
+
// Effect-specific options
|
|
77
|
+
coverflowEffect?: Partial<CoverflowEffectOptions>
|
|
78
|
+
|
|
79
|
+
// Responsive breakpoints
|
|
80
|
+
breakpoints?: SwiperOptions['breakpoints']
|
|
81
|
+
|
|
82
|
+
// Advanced: allow override with raw options
|
|
83
|
+
advancedOptions?: SwiperOptions
|
|
84
|
+
}>(),
|
|
85
|
+
{
|
|
86
|
+
items: undefined,
|
|
87
|
+
index: undefined,
|
|
88
|
+
variant: 'default',
|
|
89
|
+
slideWidth: undefined,
|
|
90
|
+
effect: undefined,
|
|
91
|
+
direction: 'horizontal',
|
|
92
|
+
speed: undefined,
|
|
93
|
+
loop: undefined,
|
|
94
|
+
initialSlide: 0,
|
|
95
|
+
slidesPerView: undefined,
|
|
96
|
+
spaceBetween: undefined,
|
|
97
|
+
centeredSlides: undefined,
|
|
98
|
+
grabCursor: true,
|
|
99
|
+
keyboard: true,
|
|
100
|
+
mousewheel: false,
|
|
101
|
+
autoHeight: false,
|
|
102
|
+
navigation: undefined,
|
|
103
|
+
pagination: undefined,
|
|
104
|
+
autoplay: undefined,
|
|
105
|
+
coverflowEffect: undefined,
|
|
106
|
+
breakpoints: undefined,
|
|
107
|
+
advancedOptions: undefined,
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
const emit = defineEmits<{
|
|
112
|
+
'update:index': [index: number]
|
|
113
|
+
}>()
|
|
114
|
+
|
|
115
|
+
defineSlots<{
|
|
116
|
+
'default'?: (props: { item: T, index: number, currentIndex: number }) => any
|
|
117
|
+
'prev-button'?: () => any
|
|
118
|
+
'next-button'?: () => any
|
|
119
|
+
'navigation'?: (props: {
|
|
120
|
+
items: T[]
|
|
121
|
+
currentIndex: number
|
|
122
|
+
slideTo: (index: number) => void
|
|
123
|
+
prev: () => void
|
|
124
|
+
next: () => void
|
|
125
|
+
isFirst: boolean
|
|
126
|
+
isLast: boolean
|
|
127
|
+
}) => any
|
|
128
|
+
'pagination'?: () => any
|
|
129
|
+
}>()
|
|
130
|
+
|
|
131
|
+
const variantPresets: Record<SwiperVariant, VariantConfig> = {
|
|
132
|
+
default: {},
|
|
133
|
+
testimonial: {
|
|
134
|
+
slidesPerView: 1,
|
|
135
|
+
autoplay: { delay: 5000, disableOnInteraction: false },
|
|
136
|
+
pagination: { clickable: true },
|
|
137
|
+
loop: true,
|
|
138
|
+
speed: 600,
|
|
139
|
+
},
|
|
140
|
+
gallery: {
|
|
141
|
+
slidesPerView: 1,
|
|
142
|
+
spaceBetween: 0,
|
|
143
|
+
navigation: true,
|
|
144
|
+
loop: true,
|
|
145
|
+
},
|
|
146
|
+
cards: {
|
|
147
|
+
slidesPerView: 3,
|
|
148
|
+
spaceBetween: 20,
|
|
149
|
+
navigation: true,
|
|
150
|
+
breakpoints: {
|
|
151
|
+
320: { slidesPerView: 1, spaceBetween: 10 },
|
|
152
|
+
768: { slidesPerView: 2, spaceBetween: 15 },
|
|
153
|
+
1024: { slidesPerView: 3, spaceBetween: 20 },
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
coverflow: {
|
|
157
|
+
effect: 'coverflow',
|
|
158
|
+
slidesPerView: 'auto',
|
|
159
|
+
centeredSlides: true,
|
|
160
|
+
loop: true,
|
|
161
|
+
navigation: true,
|
|
162
|
+
coverflowEffect: {
|
|
163
|
+
rotate: -2,
|
|
164
|
+
stretch: 0,
|
|
165
|
+
depth: 100,
|
|
166
|
+
slideShadows: true,
|
|
167
|
+
modifier: 1,
|
|
168
|
+
scale: 0.98,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
hero: {
|
|
172
|
+
slidesPerView: 1,
|
|
173
|
+
autoplay: { delay: 4000 },
|
|
174
|
+
pagination: { clickable: true },
|
|
175
|
+
loop: true,
|
|
176
|
+
speed: 800,
|
|
177
|
+
effect: 'fade',
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
register()
|
|
182
|
+
|
|
183
|
+
const swiperEl = $ref<SwiperContainer | null>(null)
|
|
184
|
+
const isFirstSlide = ref(true)
|
|
185
|
+
const isLastSlide = ref(false)
|
|
186
|
+
const currentIndex = ref(props.index ?? props.initialSlide ?? 0)
|
|
187
|
+
let isUpdatingFromExternal = false // Guard to prevent update loops
|
|
188
|
+
|
|
189
|
+
const slideStyles = $computed(() => {
|
|
190
|
+
const styles: Record<string, string> = {}
|
|
191
|
+
|
|
192
|
+
if (props.slideWidth !== undefined) {
|
|
193
|
+
styles.width = typeof props.slideWidth === 'number' ? `${props.slideWidth}px` : props.slideWidth
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return styles
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Apply variant preset configuration
|
|
200
|
+
const variantConfig = $computed(() => variantPresets[props.variant])
|
|
201
|
+
|
|
202
|
+
// Smart default: if slideWidth is set and slidesPerView wasn't set,
|
|
203
|
+
// automatically use 'auto' to let slides determine their own width
|
|
204
|
+
const effectiveSlidesPerView = $computed(() => {
|
|
205
|
+
// Priority: explicit prop > variant config > smart default
|
|
206
|
+
if (props.slidesPerView !== undefined) {
|
|
207
|
+
return props.slidesPerView
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (variantConfig.slidesPerView !== undefined) {
|
|
211
|
+
return variantConfig.slidesPerView
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Smart default: if slideWidth is provided, use 'auto'
|
|
215
|
+
if (props.slideWidth !== undefined) {
|
|
216
|
+
return 'auto'
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Final fallback
|
|
220
|
+
return 1
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const swiperParams = computed((): SwiperOptions => {
|
|
224
|
+
// Helper to get value with priority: prop > variant > default
|
|
225
|
+
const getValue = <K extends keyof VariantConfig>(
|
|
226
|
+
propValue: any,
|
|
227
|
+
variantKey: K,
|
|
228
|
+
defaultValue: any
|
|
229
|
+
) => {
|
|
230
|
+
if (propValue !== undefined) return propValue
|
|
231
|
+
if (variantConfig[variantKey] !== undefined) return variantConfig[variantKey]
|
|
232
|
+
return defaultValue
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const params: SwiperOptions = {
|
|
236
|
+
effect: getValue(props.effect, 'effect', 'slide'),
|
|
237
|
+
direction: props.direction ?? 'horizontal',
|
|
238
|
+
speed: getValue(props.speed, 'speed', 300),
|
|
239
|
+
loop: getValue(props.loop, 'loop', false),
|
|
240
|
+
initialSlide: props.index ?? props.initialSlide,
|
|
241
|
+
slidesPerView: effectiveSlidesPerView,
|
|
242
|
+
spaceBetween: getValue(props.spaceBetween, 'spaceBetween', 0),
|
|
243
|
+
centeredSlides: getValue(props.centeredSlides, 'centeredSlides', false),
|
|
244
|
+
grabCursor: props.grabCursor,
|
|
245
|
+
keyboard: props.keyboard ? { enabled: true } : false,
|
|
246
|
+
mousewheel: props.mousewheel ? { enabled: true } : false,
|
|
247
|
+
autoHeight: props.autoHeight,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Navigation (priority: prop > variant > default)
|
|
251
|
+
const navigationEnabled = getValue(props.navigation, 'navigation', false)
|
|
252
|
+
if (navigationEnabled) {
|
|
253
|
+
params.navigation = true
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Pagination (priority: prop > variant > default)
|
|
257
|
+
const paginationConfig = getValue(props.pagination, 'pagination', false)
|
|
258
|
+
if (paginationConfig) {
|
|
259
|
+
params.pagination = typeof paginationConfig === 'boolean'
|
|
260
|
+
? { clickable: true }
|
|
261
|
+
: paginationConfig
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Autoplay (priority: prop > variant > default)
|
|
265
|
+
const autoplayConfig = getValue(props.autoplay, 'autoplay', false)
|
|
266
|
+
if (autoplayConfig) {
|
|
267
|
+
params.autoplay = typeof autoplayConfig === 'boolean'
|
|
268
|
+
? { delay: 3000 }
|
|
269
|
+
: autoplayConfig
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Effect-specific options (merge variant and explicit)
|
|
273
|
+
const effectType = params.effect || 'slide'
|
|
274
|
+
if (effectType === 'coverflow') {
|
|
275
|
+
const baseCoverflowEffect = {
|
|
276
|
+
rotate: 0,
|
|
277
|
+
stretch: 0,
|
|
278
|
+
depth: 100,
|
|
279
|
+
modifier: 1,
|
|
280
|
+
slideShadows: true,
|
|
281
|
+
}
|
|
282
|
+
params.coverflowEffect = {
|
|
283
|
+
...baseCoverflowEffect,
|
|
284
|
+
...variantConfig.coverflowEffect,
|
|
285
|
+
...props.coverflowEffect,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Breakpoints (priority: prop > variant)
|
|
290
|
+
const breakpointsConfig = getValue(props.breakpoints, 'breakpoints', undefined)
|
|
291
|
+
if (breakpointsConfig) {
|
|
292
|
+
params.breakpoints = breakpointsConfig
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Advanced options override
|
|
296
|
+
if (props.advancedOptions) {
|
|
297
|
+
return { ...params, ...props.advancedOptions }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return params
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
const swiperInstance = $computed(() => swiperEl?.swiper)
|
|
304
|
+
|
|
305
|
+
function updateNavigationState() {
|
|
306
|
+
if (swiperEl?.swiper && !isUpdatingFromExternal) {
|
|
307
|
+
isFirstSlide.value = swiperEl.swiper.isBeginning
|
|
308
|
+
isLastSlide.value = swiperEl.swiper.isEnd
|
|
309
|
+
|
|
310
|
+
// Emit index update for v-model support
|
|
311
|
+
// Use activeIndex for non-loop mode, realIndex for loop mode
|
|
312
|
+
const newIndex = props.loop ? swiperEl.swiper.realIndex : swiperEl.swiper.activeIndex
|
|
313
|
+
currentIndex.value = newIndex
|
|
314
|
+
|
|
315
|
+
// Emit to keep external state in sync
|
|
316
|
+
emit('update:index', newIndex)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function slideTo(index: number) {
|
|
321
|
+
if (!swiperEl?.swiper) return
|
|
322
|
+
swiperEl.swiper.slideTo(index)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function handleSlideNav(dir: 'prev' | 'next') {
|
|
326
|
+
if (!swiperEl?.swiper) return
|
|
327
|
+
|
|
328
|
+
if (dir === 'next') {
|
|
329
|
+
swiperEl.swiper.slideNext()
|
|
330
|
+
} else {
|
|
331
|
+
swiperEl.swiper.slidePrev()
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
onMounted(() => {
|
|
336
|
+
if (!swiperEl) return
|
|
337
|
+
|
|
338
|
+
// Add events to track slide changes for reactive state
|
|
339
|
+
const params = {
|
|
340
|
+
...swiperParams.value,
|
|
341
|
+
on: {
|
|
342
|
+
...swiperParams.value.on,
|
|
343
|
+
init: updateNavigationState,
|
|
344
|
+
slideChange: updateNavigationState,
|
|
345
|
+
slideChangeTransitionEnd: updateNavigationState,
|
|
346
|
+
reachBeginning: updateNavigationState,
|
|
347
|
+
reachEnd: updateNavigationState,
|
|
348
|
+
fromEdge: updateNavigationState,
|
|
349
|
+
},
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
Object.assign(swiperEl, params)
|
|
353
|
+
swiperEl.initialize()
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
watch(
|
|
357
|
+
() => props.initialSlide,
|
|
358
|
+
(index) => {
|
|
359
|
+
if (index !== undefined && swiperEl?.swiper) {
|
|
360
|
+
setTimeout(() => swiperEl.swiper.slideTo(index), 1)
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
{ immediate: true },
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
// Watch for external index changes (v-model support)
|
|
367
|
+
watch(
|
|
368
|
+
() => props.index,
|
|
369
|
+
(newIndex) => {
|
|
370
|
+
if (newIndex !== undefined) {
|
|
371
|
+
currentIndex.value = newIndex
|
|
372
|
+
|
|
373
|
+
if (swiperEl?.swiper) {
|
|
374
|
+
const activeIndex = props.loop ? swiperEl.swiper.realIndex : swiperEl.swiper.activeIndex
|
|
375
|
+
if (newIndex !== activeIndex) {
|
|
376
|
+
isUpdatingFromExternal = true
|
|
377
|
+
swiperEl.swiper.slideTo(newIndex)
|
|
378
|
+
// Reset guard after a short delay to allow transition
|
|
379
|
+
setTimeout(() => {
|
|
380
|
+
isUpdatingFromExternal = false
|
|
381
|
+
}, 50)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
// Watch for param changes and update swiper
|
|
389
|
+
watch(
|
|
390
|
+
swiperParams,
|
|
391
|
+
(newParams) => {
|
|
392
|
+
if (swiperEl?.swiper) {
|
|
393
|
+
Object.assign(swiperEl.swiper.params, newParams)
|
|
394
|
+
swiperEl.swiper.update()
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
{ deep: true },
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
defineExpose({
|
|
401
|
+
swiper: swiperInstance,
|
|
402
|
+
slideNext: () => { handleSlideNav('next') },
|
|
403
|
+
slidePrev: () => { handleSlideNav('prev') },
|
|
404
|
+
slideTo,
|
|
405
|
+
update: () => swiperEl?.swiper?.update(),
|
|
406
|
+
isFirst: isFirstSlide,
|
|
407
|
+
isLast: isLastSlide,
|
|
408
|
+
currentIndex,
|
|
409
|
+
})
|
|
410
|
+
</script>
|
|
411
|
+
|
|
412
|
+
<template>
|
|
413
|
+
<div class="swi-wrap">
|
|
414
|
+
<swiper-container ref="swiperEl" class="swiper" init="false">
|
|
415
|
+
<swiper-slide v-for="(item, index) in items" :key="index" :style="slideStyles">
|
|
416
|
+
<slot :item="item" :index="index" :currentIndex="currentIndex" />
|
|
417
|
+
</swiper-slide>
|
|
418
|
+
</swiper-container>
|
|
419
|
+
|
|
420
|
+
<!-- Custom Navigation Slot -->
|
|
421
|
+
<slot
|
|
422
|
+
name="navigation" :items="items || []" :currentIndex="currentIndex" :slide-to="slideTo"
|
|
423
|
+
:prev="() => handleSlideNav('prev')" :next="() => handleSlideNav('next')" :is-first="isFirstSlide"
|
|
424
|
+
:is-last="isLastSlide"
|
|
425
|
+
>
|
|
426
|
+
<!-- Default Navigation -->
|
|
427
|
+
<div v-if="navigation" class="swi-ctrl">
|
|
428
|
+
<div class="swi-prev hover" @click="handleSlideNav('prev')">
|
|
429
|
+
<slot name="prev-button">
|
|
430
|
+
<Icon name="chevron_left" />
|
|
431
|
+
</slot>
|
|
432
|
+
</div>
|
|
433
|
+
<div class="swi-next hover" @click="handleSlideNav('next')">
|
|
434
|
+
<slot name="next-button">
|
|
435
|
+
<Icon name="chevron_right" />
|
|
436
|
+
</slot>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
</slot>
|
|
440
|
+
|
|
441
|
+
<!-- Custom Pagination Slot -->
|
|
442
|
+
<slot v-if="pagination" name="pagination" />
|
|
443
|
+
</div>
|
|
444
|
+
</template>
|
|
445
|
+
|
|
446
|
+
<style>
|
|
447
|
+
.swiper {
|
|
448
|
+
width: 100%;
|
|
449
|
+
padding-top: 50px;
|
|
450
|
+
padding-bottom: 50px;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
:root {
|
|
454
|
+
--swiper-navigation-color: white;
|
|
455
|
+
--swiper-pagination-color: white;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.swi-ctrl {
|
|
459
|
+
position: absolute;
|
|
460
|
+
top: 50%;
|
|
461
|
+
transform: translateY(-50%);
|
|
462
|
+
width: 100%;
|
|
463
|
+
z-index: 99;
|
|
464
|
+
display: flex;
|
|
465
|
+
justify-content: space-between;
|
|
466
|
+
padding: 0rem 4rem;
|
|
467
|
+
height: 0;
|
|
468
|
+
pointer-events: none;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.swi-ctrl>* {
|
|
472
|
+
pointer-events: auto;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.swi-ctrl img {
|
|
476
|
+
height: 20px;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.swi-wrap {
|
|
480
|
+
position: relative;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.swi-prev,
|
|
484
|
+
.swi-next {
|
|
485
|
+
background: var(--blue);
|
|
486
|
+
height: 40px;
|
|
487
|
+
width: 40px;
|
|
488
|
+
border-radius: 100%;
|
|
489
|
+
padding: 10px;
|
|
490
|
+
display: flex;
|
|
491
|
+
justify-content: center;
|
|
492
|
+
align-items: center;
|
|
493
|
+
cursor: pointer;
|
|
494
|
+
transition: opacity 0.3s ease;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.swi-prev:hover,
|
|
498
|
+
.swi-next:hover {
|
|
499
|
+
opacity: 0.8;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
@media screen and (max-width: 900px) {
|
|
503
|
+
.swi-ctrl {
|
|
504
|
+
padding: 0rem 0.5rem;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
</style>
|
|
@@ -7,7 +7,7 @@ import { useHighlight } from './useHighlight'
|
|
|
7
7
|
const props = defineProps({
|
|
8
8
|
language: { type: String as PropType<Language>, default: 'html' },
|
|
9
9
|
readonly: { type: Boolean, default: false },
|
|
10
|
-
modelValue: { type: String, default: '' },
|
|
10
|
+
modelValue: { type: [String, Object] as PropType<string | Record<string, any>>, default: '' },
|
|
11
11
|
autodetect: { type: Boolean, default: true },
|
|
12
12
|
ignoreIllegals: { type: Boolean, default: true },
|
|
13
13
|
label: { type: String, default: '' },
|
|
@@ -17,13 +17,26 @@ const props = defineProps({
|
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
const emit = defineEmits(['update:modelValue'])
|
|
20
|
+
|
|
21
|
+
// Track whether the original modelValue was an object
|
|
22
|
+
const isObjectMode = computed(() => typeof props.modelValue === 'object')
|
|
23
|
+
|
|
24
|
+
// Helper to convert value to string for display
|
|
25
|
+
function valueToString(value: string | Record<string, any>): string {
|
|
26
|
+
if (typeof value === 'string') return value
|
|
27
|
+
if (typeof value === 'object') {
|
|
28
|
+
return JSON.stringify(value, null, 2)
|
|
29
|
+
}
|
|
30
|
+
return ''
|
|
31
|
+
}
|
|
32
|
+
|
|
20
33
|
// State
|
|
21
|
-
const code = ref(props.modelValue
|
|
34
|
+
const code = ref(valueToString(props.modelValue))
|
|
22
35
|
const editorRef = ref<HTMLDivElement>()
|
|
23
36
|
const { loaded, loadHighlight, highlightCode, setTheme } = useHighlight(props.theme)
|
|
24
37
|
|
|
25
38
|
const maxHeight = computed(() => {
|
|
26
|
-
const h = props.height
|
|
39
|
+
const h = props.height
|
|
27
40
|
return h.match(/^\d+$/) ? `${h}px` : h
|
|
28
41
|
})
|
|
29
42
|
|
|
@@ -39,11 +52,24 @@ const formattedCode = computed(() => {
|
|
|
39
52
|
function handleInput(e: Event) {
|
|
40
53
|
const target = e.target as HTMLTextAreaElement
|
|
41
54
|
code.value = target.value
|
|
42
|
-
|
|
55
|
+
|
|
56
|
+
// If originally an object, try to parse and emit object
|
|
57
|
+
if (isObjectMode.value) {
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(code.value)
|
|
60
|
+
emit('update:modelValue', parsed)
|
|
61
|
+
} catch {
|
|
62
|
+
// If parsing fails, emit the string as-is (user is still typing)
|
|
63
|
+
// This allows intermediate invalid JSON states while editing
|
|
64
|
+
emit('update:modelValue', code.value)
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
emit('update:modelValue', code.value)
|
|
68
|
+
}
|
|
43
69
|
}
|
|
44
70
|
|
|
45
71
|
function handleTab(event: KeyboardEvent) {
|
|
46
|
-
if ('Tab'
|
|
72
|
+
if (event.key === 'Tab') { return }
|
|
47
73
|
|
|
48
74
|
event.preventDefault()
|
|
49
75
|
const target = event.target as HTMLTextAreaElement
|
|
@@ -53,11 +79,24 @@ function handleTab(event: KeyboardEvent) {
|
|
|
53
79
|
// Add tab or indent selected text
|
|
54
80
|
const newValue = `${code.value.substring(0, start)} ${code.value.substring(end)}`
|
|
55
81
|
code.value = newValue
|
|
56
|
-
|
|
82
|
+
|
|
83
|
+
// If originally an object, try to parse and emit object
|
|
84
|
+
if (isObjectMode.value) {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(code.value)
|
|
87
|
+
emit('update:modelValue', parsed)
|
|
88
|
+
} catch {
|
|
89
|
+
// If parsing fails, emit the string as-is
|
|
90
|
+
emit('update:modelValue', code.value)
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
emit('update:modelValue', code.value)
|
|
94
|
+
}
|
|
57
95
|
|
|
58
96
|
// Move cursor position after the inserted tab
|
|
59
97
|
setTimeout(() => {
|
|
60
|
-
target.selectionStart =
|
|
98
|
+
target.selectionStart = start + 2
|
|
99
|
+
target.selectionEnd = start + 2
|
|
61
100
|
}, 0)
|
|
62
101
|
}
|
|
63
102
|
|
|
@@ -68,8 +107,9 @@ onMounted(async () => {
|
|
|
68
107
|
|
|
69
108
|
watch(() => props.theme, (t) => { setTheme(t) })
|
|
70
109
|
watch(() => props.modelValue, (newVal) => {
|
|
71
|
-
|
|
72
|
-
|
|
110
|
+
const newCodeStr = valueToString(newVal)
|
|
111
|
+
if (newCodeStr !== code.value) {
|
|
112
|
+
code.value = newCodeStr
|
|
73
113
|
}
|
|
74
114
|
}, { immediate: true })
|
|
75
115
|
</script>
|
package/src/components/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ export { default as Rating } from './Rating.vue'
|
|
|
41
41
|
export { default as RouterWrapper } from './RouterWrapper.vue'
|
|
42
42
|
export { default as Slider } from './Slider.vue'
|
|
43
43
|
export { default as Spreadsheet } from './Spreadsheet/Index.vue'
|
|
44
|
+
export { default as Swiper } from './Swiper.vue'
|
|
44
45
|
export { default as Title } from './Title.vue'
|
|
45
46
|
export { default as ToolBar } from './ToolBar.vue'
|
|
46
47
|
export { default as TopBar } from './TopBar.vue'
|
package/src/styles/layout.css
CHANGED
|
@@ -55,10 +55,66 @@
|
|
|
55
55
|
border-radius: 100%;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
.aspect-ratio-1
|
|
58
|
+
.aspect-ratio-1,
|
|
59
|
+
.ratio-1 {
|
|
59
60
|
aspect-ratio: 1;
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
.aspect-ratio-4-3,
|
|
64
|
+
.ratio-4-3 {
|
|
65
|
+
aspect-ratio: 4 / 3;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.aspect-ratio-3-4,
|
|
69
|
+
.ratio-3-4 {
|
|
70
|
+
aspect-ratio: 3 / 4;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.aspect-ratio-16-9,
|
|
74
|
+
.ratio-16-9 {
|
|
75
|
+
aspect-ratio: 16 / 9;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.aspect-ratio-9-16,
|
|
79
|
+
.ratio-9-16 {
|
|
80
|
+
aspect-ratio: 9 / 16;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.aspect-ratio-3-2,
|
|
84
|
+
.ratio-3-2 {
|
|
85
|
+
aspect-ratio: 3 / 2;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.aspect-ratio-2-3,
|
|
89
|
+
.ratio-2-3 {
|
|
90
|
+
aspect-ratio: 2 / 3;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.aspect-ratio-5-4,
|
|
94
|
+
.ratio-5-4 {
|
|
95
|
+
aspect-ratio: 5 / 4;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.aspect-ratio-4-5,
|
|
99
|
+
.ratio-4-5 {
|
|
100
|
+
aspect-ratio: 4 / 5;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.aspect-ratio-2-1,
|
|
104
|
+
.ratio-2-1 {
|
|
105
|
+
aspect-ratio: 2 / 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.aspect-ratio-1-2,
|
|
109
|
+
.ratio-1-2 {
|
|
110
|
+
aspect-ratio: 1 / 2;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.aspect-ratio-21-9,
|
|
114
|
+
.ratio-21-9 {
|
|
115
|
+
aspect-ratio: 21 / 9;
|
|
116
|
+
}
|
|
117
|
+
|
|
62
118
|
.vertical-align-middle,
|
|
63
119
|
.vertical-middle {
|
|
64
120
|
vertical-align: middle;
|