@bagelink/vue 1.4.71 → 1.4.77
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/AddressSearch.vue.d.ts +29 -0
- package/dist/components/AddressSearch.vue.d.ts.map +1 -1
- package/dist/components/Btn.vue.d.ts +2 -0
- package/dist/components/Btn.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts +11 -0
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/Icon/Icon.vue.d.ts +1 -0
- package/dist/components/Icon/Icon.vue.d.ts.map +1 -1
- package/dist/components/Modal.vue.d.ts.map +1 -1
- package/dist/components/Slider.vue.d.ts.map +1 -1
- package/dist/components/dataTable/useTableData.d.ts.map +1 -1
- package/dist/components/form/inputs/DateInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts +58 -0
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/index.cjs +20 -20
- package/dist/index.mjs +19 -19
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Btn.vue +42 -29
- package/src/components/Dropdown.vue +72 -31
- package/src/components/Icon/Icon.vue +29 -4
- package/src/components/Modal.vue +3 -0
- package/src/components/Slider.vue +60 -39
- package/src/components/dataTable/useTableData.ts +11 -3
- package/src/components/form/inputs/DateInput.vue +31 -34
- package/src/styles/appearance.css +97 -0
package/package.json
CHANGED
package/src/components/Btn.vue
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { IconType, ThemeType } from '@bagelink/vue'
|
|
3
3
|
import type { SetupContext } from 'vue'
|
|
4
4
|
import { Icon, Loading } from '@bagelink/vue'
|
|
5
|
-
import { useSlots } from 'vue'
|
|
5
|
+
import { useSlots, ref, onMounted, onUnmounted } from 'vue'
|
|
6
6
|
import { RouterLink } from 'vue-router'
|
|
7
7
|
|
|
8
8
|
const props = withDefaults(
|
|
@@ -10,6 +10,8 @@ const props = withDefaults(
|
|
|
10
10
|
disabled?: boolean
|
|
11
11
|
icon?: IconType
|
|
12
12
|
iconEnd?: IconType
|
|
13
|
+
iconSize?: number | string
|
|
14
|
+
iconMobileSize?: number | string
|
|
13
15
|
color?: ThemeType
|
|
14
16
|
theme?: ThemeType
|
|
15
17
|
flat?: boolean
|
|
@@ -41,6 +43,26 @@ const props = withDefaults(
|
|
|
41
43
|
},
|
|
42
44
|
)
|
|
43
45
|
|
|
46
|
+
const isMobileScreen = ref(false)
|
|
47
|
+
|
|
48
|
+
const checkMobile = () => {
|
|
49
|
+
isMobileScreen.value = window.innerWidth <= 910
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
onMounted(() => {
|
|
53
|
+
checkMobile()
|
|
54
|
+
window.addEventListener('resize', checkMobile)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
onUnmounted(() => {
|
|
58
|
+
window.removeEventListener('resize', checkMobile)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const iconSizeComputed = $computed(() => {
|
|
62
|
+
if (props.iconSize !== undefined) return props.iconSize
|
|
63
|
+
return isMobileScreen.value ? 1 : 1.2
|
|
64
|
+
})
|
|
65
|
+
|
|
44
66
|
const isComponent = $computed(() => {
|
|
45
67
|
if (props.disabled) return props.is
|
|
46
68
|
if (props.to) return RouterLink
|
|
@@ -64,34 +86,25 @@ const slots: SetupContext['slots'] = useSlots()
|
|
|
64
86
|
</script>
|
|
65
87
|
|
|
66
88
|
<template>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
>
|
|
85
|
-
|
|
86
|
-
<div v-else class="bgl_btn-flex">
|
|
87
|
-
<Icon v-if="icon" :icon="icon" class="transition-400" />
|
|
88
|
-
<slot />
|
|
89
|
-
<template v-if="!slots.default && value">
|
|
90
|
-
{{ value }}
|
|
91
|
-
</template>
|
|
92
|
-
<Icon v-if="iconEnd" :icon="iconEnd" class="transition-400" />
|
|
93
|
-
</div>
|
|
94
|
-
</component>
|
|
89
|
+
<component :is="isComponent" v-ripple="ripple" v-bind="bind" :disabled="disabled" class="bgl_btn" :class="{
|
|
90
|
+
'bgl_btn-icon': icon && !slots.default && !value,
|
|
91
|
+
thin,
|
|
92
|
+
round,
|
|
93
|
+
'bgl_btn_flat': flat,
|
|
94
|
+
'bgl_btn-border': border || outline,
|
|
95
|
+
[`bgl_btn-${color}`]: color,
|
|
96
|
+
[`bgl_btn-${theme}`]: theme,
|
|
97
|
+
}" :tabindex="disabled ? -1 : 0" @click.stop="onClick" @keydown.enter="onClick" @keydown.space="onClick">
|
|
98
|
+
<Loading v-if="loading" class="h-100p" size="15" />
|
|
99
|
+
<div v-else class="bgl_btn-flex">
|
|
100
|
+
<Icon v-if="icon" :icon="icon" class="transition-400" :size="iconSizeComputed" :mobile-size="iconMobileSize" />
|
|
101
|
+
<slot />
|
|
102
|
+
<template v-if="!slots.default && value">
|
|
103
|
+
{{ value }}
|
|
104
|
+
</template>
|
|
105
|
+
<Icon v-if="iconEnd" :icon="iconEnd" class="transition-400" :size="iconSizeComputed" :mobile-size="iconMobileSize" />
|
|
106
|
+
</div>
|
|
107
|
+
</component>
|
|
95
108
|
</template>
|
|
96
109
|
|
|
97
110
|
<style scoped>
|
|
@@ -24,6 +24,12 @@ const {
|
|
|
24
24
|
noAutoFocus,
|
|
25
25
|
autoHide = true,
|
|
26
26
|
triggers = ['click'],
|
|
27
|
+
showTriggers,
|
|
28
|
+
hideTriggers,
|
|
29
|
+
popperTriggers = [],
|
|
30
|
+
popperShowTriggers = [],
|
|
31
|
+
popperHideTriggers = [],
|
|
32
|
+
delay,
|
|
27
33
|
|
|
28
34
|
} = defineProps<{
|
|
29
35
|
value?: string
|
|
@@ -42,6 +48,12 @@ const {
|
|
|
42
48
|
noAutoFocus?: boolean
|
|
43
49
|
autoHide?: boolean
|
|
44
50
|
triggers?: TriggerEvent[]
|
|
51
|
+
showTriggers?: TriggerEvent[]
|
|
52
|
+
hideTriggers?: TriggerEvent[]
|
|
53
|
+
popperTriggers?: TriggerEvent[]
|
|
54
|
+
popperShowTriggers?: TriggerEvent[]
|
|
55
|
+
popperHideTriggers?: TriggerEvent[]
|
|
56
|
+
delay?: number | { show: number, hide: number }
|
|
45
57
|
// theme
|
|
46
58
|
// referenceNode
|
|
47
59
|
// shown
|
|
@@ -95,45 +107,74 @@ const shouldDisablePositioning = $computed(() => {
|
|
|
95
107
|
return disablePlacement && isMobile
|
|
96
108
|
})
|
|
97
109
|
|
|
110
|
+
// Intelligent trigger configuration
|
|
111
|
+
const computedShowTriggers = $computed((): TriggerEvent[] => {
|
|
112
|
+
if (showTriggers !== undefined) return showTriggers
|
|
113
|
+
return triggers
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const computedHideTriggers = $computed((): TriggerEvent[] => {
|
|
117
|
+
if (hideTriggers !== undefined) return hideTriggers
|
|
118
|
+
// For click-only dropdowns, only hide on click
|
|
119
|
+
if (triggers.length === 1 && triggers[0] === 'click') {
|
|
120
|
+
return ['click'] as TriggerEvent[]
|
|
121
|
+
}
|
|
122
|
+
// For hover dropdowns, add click to hide triggers for better UX
|
|
123
|
+
if (triggers.includes('hover')) {
|
|
124
|
+
return [...triggers, 'click'] as TriggerEvent[]
|
|
125
|
+
}
|
|
126
|
+
return triggers
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const computedPopperTriggers = $computed((): TriggerEvent[] => {
|
|
130
|
+
if (popperTriggers.length > 0) return popperTriggers
|
|
131
|
+
// For hover dropdowns, enable hover on popper to keep it open
|
|
132
|
+
if (triggers.includes('hover')) {
|
|
133
|
+
return ['hover'] as TriggerEvent[]
|
|
134
|
+
}
|
|
135
|
+
return []
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const computedPopperHideTriggers = $computed((): TriggerEvent[] => {
|
|
139
|
+
if (popperHideTriggers && popperHideTriggers.length > 0) return popperHideTriggers
|
|
140
|
+
// For hover dropdowns, make sure popper hides on mouseout
|
|
141
|
+
if (triggers.includes('hover')) {
|
|
142
|
+
return ['hover'] as TriggerEvent[]
|
|
143
|
+
}
|
|
144
|
+
return []
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const computedAutoHide = $computed(() => {
|
|
148
|
+
// Both hover and click dropdowns should support autoHide for outside click behavior
|
|
149
|
+
return autoHide
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const computedDelay = $computed((): number | { show: number, hide: number } | undefined => {
|
|
153
|
+
if (delay !== undefined) return delay
|
|
154
|
+
// For hover dropdowns, add a hide delay
|
|
155
|
+
if (triggers.includes('hover')) {
|
|
156
|
+
return { show: 0, hide: 50 } // 50ms delay before hiding
|
|
157
|
+
}
|
|
158
|
+
// For click dropdowns, no delay
|
|
159
|
+
return 0
|
|
160
|
+
})
|
|
161
|
+
|
|
98
162
|
defineExpose({ show, hide, shown })
|
|
99
163
|
</script>
|
|
100
164
|
|
|
101
165
|
<template>
|
|
102
166
|
<DDown
|
|
103
|
-
ref="ddownRef"
|
|
104
|
-
|
|
105
|
-
:
|
|
106
|
-
:
|
|
107
|
-
:positioning-disabled="shouldDisablePositioning"
|
|
108
|
-
:placement
|
|
109
|
-
:autoHide
|
|
110
|
-
:triggers
|
|
111
|
-
@hide="emit('hide')"
|
|
112
|
-
@show="emit('show')"
|
|
167
|
+
ref="ddownRef" v-model:shown="shown" :disabled :noAutoFocus :positioning-disabled="shouldDisablePositioning"
|
|
168
|
+
:placement :autoHide="computedAutoHide" :delay="computedDelay" :triggers :showTriggers="computedShowTriggers"
|
|
169
|
+
:hideTriggers="computedHideTriggers" :popperTriggers="computedPopperTriggers" :popperShowTriggers
|
|
170
|
+
:popperHideTriggers="computedPopperHideTriggers" @hide="emit('hide')" @show="emit('show')"
|
|
113
171
|
>
|
|
114
172
|
<div ref="triggerRef" />
|
|
115
|
-
<slot
|
|
116
|
-
|
|
117
|
-
:show
|
|
118
|
-
:hide
|
|
119
|
-
>
|
|
120
|
-
<Btn
|
|
121
|
-
:class="triggerClass"
|
|
122
|
-
:iconEnd
|
|
123
|
-
:icon
|
|
124
|
-
:value
|
|
125
|
-
:thin
|
|
126
|
-
:flat
|
|
127
|
-
:outline
|
|
128
|
-
:round
|
|
129
|
-
:color
|
|
130
|
-
/>
|
|
173
|
+
<slot name="trigger" :show :hide :shown>
|
|
174
|
+
<Btn :class="triggerClass" :iconEnd :icon :value :thin :flat :outline :round :color />
|
|
131
175
|
</slot>
|
|
132
|
-
<template #popper="{ hide, show }">
|
|
133
|
-
<slot
|
|
134
|
-
:hide
|
|
135
|
-
:show
|
|
136
|
-
/>
|
|
176
|
+
<template #popper="{ hide: hidePopper, show: showPopper, shown: popperShown }">
|
|
177
|
+
<slot :hide="hidePopper" :show="showPopper" :shown="popperShown" />
|
|
137
178
|
</template>
|
|
138
179
|
</DDown>
|
|
139
180
|
</template>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { IconType } from '@bagelink/vue'
|
|
3
3
|
import { FONT_AWESOME_ICONS, MATERIAL_ICONS, FONT_AWESOME_BRANDS_ICONS } from './constants'
|
|
4
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
4
5
|
|
|
5
6
|
const props = withDefaults(defineProps<{
|
|
6
7
|
icon?: IconType
|
|
7
8
|
name?: IconType
|
|
8
9
|
size?: number | string
|
|
10
|
+
mobileSize?: number | string
|
|
9
11
|
color?: string
|
|
10
12
|
round?: boolean
|
|
11
13
|
weight?: number | string
|
|
@@ -17,6 +19,28 @@ const props = withDefaults(defineProps<{
|
|
|
17
19
|
|
|
18
20
|
const iconRender = $computed(() => props.icon || props.name) as IconType
|
|
19
21
|
|
|
22
|
+
const isMobile = ref(false)
|
|
23
|
+
|
|
24
|
+
const checkMobile = () => {
|
|
25
|
+
isMobile.value = window.innerWidth <= 910
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onMounted(() => {
|
|
29
|
+
checkMobile()
|
|
30
|
+
window.addEventListener('resize', checkMobile)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
onUnmounted(() => {
|
|
34
|
+
window.removeEventListener('resize', checkMobile)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const computedSize = $computed(() => {
|
|
38
|
+
if (isMobile.value && props.mobileSize !== undefined) {
|
|
39
|
+
return props.mobileSize
|
|
40
|
+
}
|
|
41
|
+
return props.size
|
|
42
|
+
})
|
|
43
|
+
|
|
20
44
|
const iconRenderType = $computed(() => {
|
|
21
45
|
if (props.fontAwesome && (FONT_AWESOME_ICONS.includes(iconRender) || FONT_AWESOME_BRANDS_ICONS.includes(iconRender))) return 'font-awesome'
|
|
22
46
|
if (MATERIAL_ICONS.includes(iconRender)) return 'material'
|
|
@@ -32,11 +56,12 @@ const isFaBrand = $computed(() => FONT_AWESOME_BRANDS_ICONS.includes(iconRender)
|
|
|
32
56
|
v-if="iconRenderType === 'material'"
|
|
33
57
|
class="bgl_icon-font notranslate"
|
|
34
58
|
:class="{ 'round flex aspect-ratio-1 justify-content-center': round }"
|
|
35
|
-
:style="{
|
|
59
|
+
:style="{
|
|
60
|
+
'fontSize': `${computedSize}rem`,
|
|
36
61
|
color,
|
|
37
62
|
'font-variation-settings': `'FILL' ${fill ? 1 : 0}, 'wght' ${weight || 400}`,
|
|
38
|
-
|
|
39
|
-
|
|
63
|
+
'width': round ? `calc(${computedSize}rem * 2)` : 'auto',
|
|
64
|
+
'height': round ? `calc(${computedSize}rem * 2)` : 'auto',
|
|
40
65
|
}"
|
|
41
66
|
translate="no"
|
|
42
67
|
>
|
|
@@ -53,7 +78,7 @@ const isFaBrand = $computed(() => FONT_AWESOME_BRANDS_ICONS.includes(iconRender)
|
|
|
53
78
|
'far': !fill && !isFaBrand,
|
|
54
79
|
},
|
|
55
80
|
]"
|
|
56
|
-
:style="{ 'fontSize': `${
|
|
81
|
+
:style="{ 'fontSize': `${computedSize}rem`, color, 'font-variation-settings': `'wght' ${weight || 400}` }"
|
|
57
82
|
translate="no"
|
|
58
83
|
/>
|
|
59
84
|
</template>
|
package/src/components/Modal.vue
CHANGED
|
@@ -93,6 +93,7 @@ onUnmounted(() => {
|
|
|
93
93
|
flat
|
|
94
94
|
icon="close"
|
|
95
95
|
thin
|
|
96
|
+
icon-mobile-size="1.4"
|
|
96
97
|
@click="closeModal"
|
|
97
98
|
/>
|
|
98
99
|
<Title v-if="title" class="modal-title" tag="h3" :label="title" />
|
|
@@ -106,6 +107,7 @@ onUnmounted(() => {
|
|
|
106
107
|
thin
|
|
107
108
|
round
|
|
108
109
|
color="white"
|
|
110
|
+
icon-mobile-size="1.4"
|
|
109
111
|
@click="closeModal"
|
|
110
112
|
/>
|
|
111
113
|
</div>
|
|
@@ -134,6 +136,7 @@ onUnmounted(() => {
|
|
|
134
136
|
text-align: center;
|
|
135
137
|
font-weight: 600;
|
|
136
138
|
font-size: 20px;
|
|
139
|
+
margin-inline-end: 2rem;
|
|
137
140
|
margin-top: 0.5rem;
|
|
138
141
|
margin-bottom: 0 !important;
|
|
139
142
|
width: 100%;
|
|
@@ -351,9 +351,17 @@ function buildSliderFrame(): void {
|
|
|
351
351
|
|
|
352
352
|
const originalCount = innerElements.value.length
|
|
353
353
|
const widthItem = selectorWidth.value / perPage.value
|
|
354
|
+
|
|
355
|
+
// Calculate how many times to repeat items to fill big screens
|
|
356
|
+
const minItemsNeeded = Math.max(perPage.value * 3, 20) // Ensure we have enough content for big screens
|
|
357
|
+
const repetitions = Math.max(1, Math.ceil(minItemsNeeded / originalCount))
|
|
358
|
+
const totalMainItems = originalCount * repetitions
|
|
359
|
+
|
|
360
|
+
// Ultra-aggressive buffer size for really big screens
|
|
361
|
+
const bufferSize = Math.max(perPage.value * 8, 20) // Even more buffer for huge screens
|
|
354
362
|
const itemsToBuild = config.value.loop
|
|
355
|
-
?
|
|
356
|
-
:
|
|
363
|
+
? totalMainItems + (2 * bufferSize)
|
|
364
|
+
: totalMainItems
|
|
357
365
|
|
|
358
366
|
// Create a clean slate for the slider
|
|
359
367
|
carouselRef.value.innerHTML = ''
|
|
@@ -380,7 +388,7 @@ function buildSliderFrame(): void {
|
|
|
380
388
|
|
|
381
389
|
// Add clones before original items (for loop mode)
|
|
382
390
|
if (config.value.loop && innerElements.value.length > 0) {
|
|
383
|
-
for (let i = originalCount -
|
|
391
|
+
for (let i = originalCount - bufferSize; i < originalCount; i++) {
|
|
384
392
|
if (i >= 0 && i < originalCount) {
|
|
385
393
|
const original = innerElements.value[i]
|
|
386
394
|
const clone = original.cloneNode(true) as Element
|
|
@@ -396,16 +404,24 @@ function buildSliderFrame(): void {
|
|
|
396
404
|
}
|
|
397
405
|
}
|
|
398
406
|
|
|
399
|
-
// Add main items
|
|
400
|
-
for (let
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
407
|
+
// Add main items repeated multiple times to fill big screens
|
|
408
|
+
for (let rep = 0; rep < repetitions; rep++) {
|
|
409
|
+
for (let i = 0; i < originalCount; i++) {
|
|
410
|
+
const element = innerElements.value[i]
|
|
411
|
+
const clone = element.cloneNode(true) as Element
|
|
412
|
+
|
|
413
|
+
if (clone instanceof HTMLElement) {
|
|
414
|
+
clone.setAttribute('data-repetition', rep.toString())
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const wrapped = buildSliderFrameItem(clone)
|
|
418
|
+
docFragment.appendChild(wrapped)
|
|
419
|
+
}
|
|
404
420
|
}
|
|
405
421
|
|
|
406
422
|
// Add clones after original items (for loop mode)
|
|
407
423
|
if (config.value.loop && innerElements.value.length > 0) {
|
|
408
|
-
for (let i = 0; i <
|
|
424
|
+
for (let i = 0; i < bufferSize; i++) {
|
|
409
425
|
if (i >= 0 && i < originalCount) {
|
|
410
426
|
const original = innerElements.value[i]
|
|
411
427
|
const clone = original.cloneNode(true) as Element
|
|
@@ -505,9 +521,17 @@ function buildSliderFrameItem(elm: Element): HTMLElement {
|
|
|
505
521
|
elementContainer.style.float = config.value.rtl ? 'right' : 'left'
|
|
506
522
|
elementContainer.style.padding = config.value.slideGap ? `${config.value.slideGap / 2}rem` : '0'
|
|
507
523
|
|
|
524
|
+
// Calculate total items including repetitions for big screens
|
|
525
|
+
const originalCount = innerElements.value.length
|
|
526
|
+
const minItemsNeeded = Math.max(perPage.value * 3, 20)
|
|
527
|
+
const repetitions = Math.max(1, Math.ceil(minItemsNeeded / originalCount))
|
|
528
|
+
const totalMainItems = originalCount * repetitions
|
|
529
|
+
|
|
530
|
+
// Ultra-aggressive buffer size - must match buildSliderFrame buffer size
|
|
531
|
+
const itemBufferSize = Math.max(perPage.value * 8, 20) // Even more buffer for huge screens
|
|
508
532
|
const percentage = config.value.loop
|
|
509
|
-
? 100 / (
|
|
510
|
-
: 100 /
|
|
533
|
+
? 100 / (totalMainItems + (2 * itemBufferSize))
|
|
534
|
+
: 100 / totalMainItems
|
|
511
535
|
|
|
512
536
|
elementContainer.style.width = `${percentage}%`
|
|
513
537
|
elementContainer.appendChild(elm)
|
|
@@ -519,8 +543,10 @@ function slideToCurrent(enableTransitionFlag?: boolean): void {
|
|
|
519
543
|
// Ensure component is still mounted
|
|
520
544
|
if (!isMounted.value || !innerElements.value.length || !sliderFrame.value) return
|
|
521
545
|
|
|
546
|
+
// Ultra-aggressive buffer size - must match buildSliderFrame buffer size
|
|
547
|
+
const slideBufferSize = Math.max(perPage.value * 8, 20) // Even more buffer for huge screens
|
|
522
548
|
const currentSlideValue = config.value.loop
|
|
523
|
-
? currentSlide.value +
|
|
549
|
+
? currentSlide.value + slideBufferSize
|
|
524
550
|
: currentSlide.value
|
|
525
551
|
|
|
526
552
|
const offset = (config.value.rtl ? 1 : -1) * currentSlideValue * (selectorWidth.value / perPage.value)
|
|
@@ -834,7 +860,7 @@ function touchmoveHandler(e: TouchEvent): void {
|
|
|
834
860
|
: currentOffset - dragOffset
|
|
835
861
|
|
|
836
862
|
sliderFrame.value.style[transformProperty.value as any]
|
|
837
|
-
|
|
863
|
+
= `translate3d(${(config.value.rtl ? 1 : -1) * offset}px, 0, 0)`
|
|
838
864
|
}
|
|
839
865
|
}
|
|
840
866
|
}
|
|
@@ -972,7 +998,7 @@ function mousemoveHandler(e: MouseEvent): void {
|
|
|
972
998
|
: currentOffset - dragOffset
|
|
973
999
|
|
|
974
1000
|
sliderFrame.value.style[transformProperty.value as any]
|
|
975
|
-
|
|
1001
|
+
= `translate3d(${(config.value.rtl ? 1 : -1) * offset}px, 0, 0)`
|
|
976
1002
|
}
|
|
977
1003
|
}
|
|
978
1004
|
}
|
|
@@ -1373,16 +1399,11 @@ defineExpose({
|
|
|
1373
1399
|
<div ref="carouselRef" class="carousel-container">
|
|
1374
1400
|
<!-- We'll populate this with cloned content programmatically -->
|
|
1375
1401
|
</div>
|
|
1376
|
-
|
|
1377
1402
|
<!-- Dots navigation (Vue-managed) -->
|
|
1378
1403
|
<div v-if="props.dots && totalDots > 1" class="carousel-dots">
|
|
1379
1404
|
<button
|
|
1380
|
-
v-for="i in totalDots"
|
|
1381
|
-
:
|
|
1382
|
-
type="button"
|
|
1383
|
-
class="carousel-dot" :class="[{ active: (i - 1) === currentSlide }]"
|
|
1384
|
-
:aria-label="`Go to slide ${i}`"
|
|
1385
|
-
@click="goTo(i - 1)"
|
|
1405
|
+
v-for="i in totalDots" :key="i" type="button" class="carousel-dot"
|
|
1406
|
+
:class="[{ active: (i - 1) === currentSlide }]" :aria-label="`Go to slide ${i}`" @click="goTo(i - 1)"
|
|
1386
1407
|
/>
|
|
1387
1408
|
</div>
|
|
1388
1409
|
</div>
|
|
@@ -1390,35 +1411,35 @@ defineExpose({
|
|
|
1390
1411
|
|
|
1391
1412
|
<style scoped>
|
|
1392
1413
|
.carousel-wrapper {
|
|
1393
|
-
|
|
1394
|
-
|
|
1414
|
+
position: relative;
|
|
1415
|
+
width: 100%;
|
|
1395
1416
|
}
|
|
1396
1417
|
|
|
1397
1418
|
.carousel-container {
|
|
1398
|
-
|
|
1399
|
-
|
|
1419
|
+
margin: 0 auto;
|
|
1420
|
+
overflow: hidden;
|
|
1400
1421
|
}
|
|
1401
1422
|
|
|
1402
1423
|
.carousel-dots {
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1424
|
+
display: flex;
|
|
1425
|
+
justify-content: center;
|
|
1426
|
+
gap: 8px;
|
|
1427
|
+
margin-top: 16px;
|
|
1407
1428
|
}
|
|
1408
1429
|
|
|
1409
1430
|
.carousel-dot {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1431
|
+
width: 12px;
|
|
1432
|
+
height: 12px;
|
|
1433
|
+
border-radius: 50px;
|
|
1434
|
+
background-color: var(--bgl-gray-light);
|
|
1435
|
+
border: none;
|
|
1436
|
+
padding: 0;
|
|
1437
|
+
cursor: pointer;
|
|
1438
|
+
transition: all 0.3s ease-in-out;
|
|
1418
1439
|
}
|
|
1419
1440
|
|
|
1420
1441
|
.carousel-dot.active {
|
|
1421
|
-
|
|
1422
|
-
|
|
1442
|
+
background-color: var(--bgl-primary);
|
|
1443
|
+
width: 26px;
|
|
1423
1444
|
}
|
|
1424
1445
|
</style>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { BglFormSchemaT } from '@bagelink/vue'
|
|
1
|
+
import type { BglFormSchemaT, Field } from '@bagelink/vue'
|
|
2
|
+
import type { DefaultPathsOptions } from 'type-fest/source/paths'
|
|
2
3
|
import type { MaybeRefOrGetter } from 'vue'
|
|
3
4
|
import type { SortDirectionsT } from '../../types/TableSchema'
|
|
4
|
-
import { useBglSchema, isDate, keyToLabel } from '@bagelink/vue'
|
|
5
|
+
import { useBglSchema, isDate, keyToLabel, formatDate } from '@bagelink/vue'
|
|
5
6
|
import { computed, ref, watch, toValue } from 'vue'
|
|
6
7
|
|
|
7
8
|
const NON_DIGIT_REGEX = /[^\d.-]/g
|
|
@@ -30,6 +31,13 @@ interface TransformedDataBase {
|
|
|
30
31
|
|
|
31
32
|
type TransformedData<TransDataT> = TransDataT & TransformedDataBase
|
|
32
33
|
|
|
34
|
+
function autoTransform<T>(field: Field<T, DefaultPathsOptions>): Field<T, DefaultPathsOptions> {
|
|
35
|
+
if ((field.id === 'created_at' || field.id === 'updated_at') && !field.transform) {
|
|
36
|
+
field.transform = (val?: any) => val ? formatDate(val) : val
|
|
37
|
+
}
|
|
38
|
+
return field
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
export function useTableData<T extends { [key: string]: any }>(options: UseTableDataOptions<T>) {
|
|
34
42
|
// Sorting state
|
|
35
43
|
const sortField = ref('')
|
|
@@ -55,7 +63,7 @@ export function useTableData<T extends { [key: string]: any }>(options: UseTable
|
|
|
55
63
|
|
|
56
64
|
// If we have a valid schema with fields, filter out fields without an ID
|
|
57
65
|
if (Array.isArray(schema) && schema.length > 0) {
|
|
58
|
-
resolvedSchema.value = schema.filter(field => field && field.id)
|
|
66
|
+
resolvedSchema.value = schema.filter(field => field && field.id).map(autoTransform)
|
|
59
67
|
} else if (Array.isArray(dataValue) && dataValue.length > 0) {
|
|
60
68
|
// If no schema is provided or it's empty, generate a default schema from the data
|
|
61
69
|
const firstItem = dataValue[0]
|
|
@@ -62,9 +62,13 @@ function useFormatting() {
|
|
|
62
62
|
if (parts.length === 3) {
|
|
63
63
|
const [day, month, year] = parts.map(p => Number.parseInt(p, 10))
|
|
64
64
|
if (!Number.isNaN(day) && !Number.isNaN(month) && !Number.isNaN(year)) {
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
|
|
65
|
+
const parsedDate = new Date(year, month - 1, day)
|
|
66
|
+
if (
|
|
67
|
+
parsedDate.getFullYear() === year
|
|
68
|
+
&& parsedDate.getMonth() === month - 1
|
|
69
|
+
&& parsedDate.getDate() === day
|
|
70
|
+
) {
|
|
71
|
+
return parsedDate
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
}
|
|
@@ -72,14 +76,27 @@ function useFormatting() {
|
|
|
72
76
|
return null
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
const normalizeDate = (date: Date): string => {
|
|
80
|
+
if (props.enableTime) {
|
|
81
|
+
// Keep the time when time is enabled
|
|
82
|
+
return date.toISOString()
|
|
83
|
+
} else {
|
|
84
|
+
// Normalize to midnight to emit exact date without time for server compatibility
|
|
85
|
+
const normalizedDate = new Date(date)
|
|
86
|
+
normalizedDate.setHours(0, 0, 0, 0)
|
|
87
|
+
return normalizedDate.toISOString().split('T')[0]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
75
91
|
return {
|
|
76
92
|
formatDisplayDate,
|
|
77
|
-
parseUserInput
|
|
93
|
+
parseUserInput,
|
|
94
|
+
normalizeDate
|
|
78
95
|
}
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
// Initialize formatting
|
|
82
|
-
const { formatDisplayDate, parseUserInput } = useFormatting()
|
|
99
|
+
const { formatDisplayDate, parseUserInput, normalizeDate } = useFormatting()
|
|
83
100
|
|
|
84
101
|
// Input handling composable
|
|
85
102
|
function useInputHandling() {
|
|
@@ -91,7 +108,7 @@ function useInputHandling() {
|
|
|
91
108
|
|
|
92
109
|
const date = parseUserInput(input.value)
|
|
93
110
|
if (date) {
|
|
94
|
-
selectedDate.value = date
|
|
111
|
+
selectedDate.value = normalizeDate(date)
|
|
95
112
|
}
|
|
96
113
|
}
|
|
97
114
|
|
|
@@ -112,7 +129,7 @@ function useInputHandling() {
|
|
|
112
129
|
} else if (event.key === 'Enter' && inputValue.value) {
|
|
113
130
|
const date = parseUserInput(inputValue.value)
|
|
114
131
|
if (date) {
|
|
115
|
-
selectedDate.value = date
|
|
132
|
+
selectedDate.value = normalizeDate(date)
|
|
116
133
|
isOpen.value = false
|
|
117
134
|
}
|
|
118
135
|
}
|
|
@@ -153,43 +170,23 @@ onMounted(() => {
|
|
|
153
170
|
{{ label }}
|
|
154
171
|
<span v-if="required" class="required">*</span>
|
|
155
172
|
</label>
|
|
156
|
-
<Dropdown
|
|
157
|
-
:shown="isOpen"
|
|
158
|
-
placement="bottom-start"
|
|
159
|
-
:autoHide="false"
|
|
160
|
-
:triggers="['click']"
|
|
161
|
-
>
|
|
173
|
+
<Dropdown :shown="isOpen" placement="bottom-start" :autoHide="false" :triggers="['click']">
|
|
162
174
|
<template #trigger>
|
|
163
175
|
<div ref="datePickerRef" class="date-picker-container" @mousedown.stop @click.stop>
|
|
164
176
|
<TextInput
|
|
165
|
-
:modelValue="formatDisplayDate(selectedDate)"
|
|
166
|
-
|
|
167
|
-
:
|
|
168
|
-
:max="formatDisplayDate(max)"
|
|
169
|
-
:required="required"
|
|
170
|
-
:disabled="!editMode"
|
|
171
|
-
class="date-input m-0"
|
|
172
|
-
:class="{
|
|
177
|
+
:modelValue="formatDisplayDate(selectedDate)" iconStart="calendar"
|
|
178
|
+
:min="formatDisplayDate(min)" :max="formatDisplayDate(max)" :required="required"
|
|
179
|
+
:disabled="!editMode" class="date-input m-0" :class="{
|
|
173
180
|
'txt-center': center,
|
|
174
|
-
}"
|
|
175
|
-
:readonly="false"
|
|
176
|
-
@input="handleInput"
|
|
177
|
-
@focus="handleFocus"
|
|
178
|
-
@click="handleClick"
|
|
179
|
-
@keydown="handleKeydown"
|
|
181
|
+
}" :readonly="false" @input="handleInput" @focus="handleFocus" @click="handleClick" @keydown="handleKeydown"
|
|
180
182
|
/>
|
|
181
183
|
</div>
|
|
182
184
|
</template>
|
|
183
185
|
|
|
184
186
|
<div ref="calendarRef" @click.stop>
|
|
185
187
|
<DatePicker
|
|
186
|
-
v-model="selectedDate"
|
|
187
|
-
:
|
|
188
|
-
:max="max"
|
|
189
|
-
:mode="mode"
|
|
190
|
-
:firstDayOfWeek="firstDayOfWeek"
|
|
191
|
-
:locale="locale"
|
|
192
|
-
:enableTime="enableTime"
|
|
188
|
+
v-model="selectedDate" :min="min" :max="max" :mode="mode" :firstDayOfWeek="firstDayOfWeek"
|
|
189
|
+
:locale="locale" :enableTime="enableTime"
|
|
193
190
|
/>
|
|
194
191
|
</div>
|
|
195
192
|
</Dropdown>
|