@bagelink/vue 1.4.109 → 1.4.111
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/ListItem.vue.d.ts +6 -1
- package/dist/components/ListItem.vue.d.ts.map +1 -1
- package/dist/components/analytics/BarChart.vue.d.ts +39 -0
- package/dist/components/analytics/BarChart.vue.d.ts.map +1 -0
- package/dist/components/analytics/KpiCard.vue.d.ts +24 -0
- package/dist/components/analytics/KpiCard.vue.d.ts.map +1 -0
- package/dist/components/analytics/LineChart.vue.d.ts +26 -0
- package/dist/components/analytics/LineChart.vue.d.ts.map +1 -0
- package/dist/components/analytics/PieChart.vue.d.ts +24 -0
- package/dist/components/analytics/PieChart.vue.d.ts.map +1 -0
- package/dist/components/analytics/index.d.ts +5 -0
- package/dist/components/analytics/index.d.ts.map +1 -0
- package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DatePicker.vue.d.ts +1 -0
- package/dist/components/form/inputs/DatePicker.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RadioGroup.vue.d.ts +6 -10
- package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts +2 -2
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts +34 -0
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -0
- package/dist/components/layout/AppLayout.vue.d.ts +27 -0
- package/dist/components/layout/AppLayout.vue.d.ts.map +1 -0
- package/dist/components/layout/AppSidebar.vue.d.ts +44 -0
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +3 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/composables/useFormField.d.ts.map +1 -1
- package/dist/composables/useSchemaField.d.ts.map +1 -1
- package/dist/index.cjs +19 -19
- package/dist/index.mjs +10 -10
- package/dist/style.css +1 -1
- package/dist/types/BagelForm.d.ts +2 -2
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/dist/utils/BagelFormUtils.d.ts +1 -2
- package/dist/utils/BagelFormUtils.d.ts.map +1 -1
- package/dist/utils/calendar/dateUtils.d.ts +21 -0
- package/dist/utils/calendar/dateUtils.d.ts.map +1 -1
- package/dist/utils/elementUtils.d.ts +5 -0
- package/dist/utils/elementUtils.d.ts.map +1 -1
- package/dist/utils/useSearch.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ListItem.vue +32 -24
- package/src/components/analytics/BarChart.vue +153 -0
- package/src/components/analytics/KpiCard.vue +84 -0
- package/src/components/analytics/LineChart.vue +267 -0
- package/src/components/analytics/PieChart.vue +196 -0
- package/src/components/analytics/index.ts +4 -0
- package/src/components/form/BagelForm.vue +24 -0
- package/src/components/form/inputs/DatePicker.vue +3 -2
- package/src/components/form/inputs/RadioGroup.vue +60 -35
- package/src/components/form/inputs/SelectInput.vue +94 -101
- package/src/components/form/inputs/Upload/upload.css +135 -138
- package/src/components/layout/AppContent.vue +105 -0
- package/src/components/layout/AppLayout.vue +124 -0
- package/src/components/layout/AppSidebar.vue +271 -0
- package/src/components/layout/index.ts +5 -0
- package/src/composables/useFormField.ts +6 -0
- package/src/composables/useSchemaField.ts +31 -3
- package/src/styles/inputs.css +9 -0
- package/src/styles/theme.css +2 -2
- package/src/types/BagelForm.ts +3 -2
- package/src/utils/BagelFormUtils.ts +1 -3
- package/src/utils/calendar/dateUtils.ts +71 -17
- package/src/utils/elementUtils.ts +22 -0
- package/src/utils/useSearch.ts +14 -7
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { inject, computed, ref, watch } from 'vue'
|
|
3
|
+
import { useRoute } from 'vue-router'
|
|
4
|
+
import { Btn, Icon } from '@bagelink/vue'
|
|
5
|
+
import type { NavLink } from '@bagelink/vue'
|
|
6
|
+
|
|
7
|
+
// Extended interface for footer links that can have actions
|
|
8
|
+
interface FooterLink extends NavLink {
|
|
9
|
+
action?: () => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
navLinks: NavLink[]
|
|
14
|
+
footerLinks?: FooterLink[]
|
|
15
|
+
logo?: string
|
|
16
|
+
logoAlt?: string
|
|
17
|
+
card?: boolean
|
|
18
|
+
bgColor?: string
|
|
19
|
+
textColor?: string
|
|
20
|
+
activeColor?: string
|
|
21
|
+
logoHeight?: string
|
|
22
|
+
name?: string
|
|
23
|
+
frame?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
27
|
+
card: true,
|
|
28
|
+
bgColor: 'var(--bgl-white)',
|
|
29
|
+
textColor: 'var(--bgl-black)',
|
|
30
|
+
activeColor: 'var(--bgl-black)',
|
|
31
|
+
logoAlt: 'Logo',
|
|
32
|
+
logoHeight: '2rem',
|
|
33
|
+
name: 'App Name',
|
|
34
|
+
footerLinks: () => [],
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const route = useRoute()
|
|
38
|
+
const isTransitioning = ref(false)
|
|
39
|
+
|
|
40
|
+
// Inject menu state from parent
|
|
41
|
+
const menuState = inject('menuState') as {
|
|
42
|
+
isOpen: { value: boolean }
|
|
43
|
+
isMobile: { value: boolean }
|
|
44
|
+
closeOnMobile: () => void
|
|
45
|
+
sidebarWidth: string
|
|
46
|
+
sidebarCollapsedWidth: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Watch for changes in menu state to handle transitioning
|
|
50
|
+
watch(
|
|
51
|
+
() => menuState.isOpen.value,
|
|
52
|
+
() => {
|
|
53
|
+
if (!menuState.isMobile.value) {
|
|
54
|
+
isTransitioning.value = true
|
|
55
|
+
// Reset after transition completes
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
isTransitioning.value = false
|
|
58
|
+
}, 300) // Match the CSS transition duration
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
// Computed styles
|
|
64
|
+
const sidebarStyles = computed(() => {
|
|
65
|
+
let width = '280px'
|
|
66
|
+
|
|
67
|
+
if (!menuState.isMobile.value) {
|
|
68
|
+
const collapsedWidth = props.card ? '82px' : '76px'
|
|
69
|
+
width = menuState.isOpen.value ? menuState.sidebarWidth : collapsedWidth
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
width,
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
function logout() {
|
|
78
|
+
console.log('Logging out...')
|
|
79
|
+
// Add your logout logic here
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<template>
|
|
84
|
+
<aside
|
|
85
|
+
class="app-sidebar transition-400 fixed inset h-100vh z-99"
|
|
86
|
+
:class="{
|
|
87
|
+
'sidebar-mobile-open': menuState.isMobile.value && menuState.isOpen.value,
|
|
88
|
+
'sidebar-mobile-closed':
|
|
89
|
+
menuState.isMobile.value && !menuState.isOpen.value,
|
|
90
|
+
transitioning: isTransitioning,
|
|
91
|
+
'p-05': props.card,
|
|
92
|
+
'sidebar-collapsed': !menuState.isMobile.value && !menuState.isOpen.value,
|
|
93
|
+
}"
|
|
94
|
+
:style="sidebarStyles"
|
|
95
|
+
>
|
|
96
|
+
<div
|
|
97
|
+
:style="{
|
|
98
|
+
backgroundColor: props.bgColor,
|
|
99
|
+
color: props.textColor,
|
|
100
|
+
...(props.card && { borderRadius: 'var(--card-border-radius)' }),
|
|
101
|
+
}"
|
|
102
|
+
:class="{
|
|
103
|
+
card: props.card,
|
|
104
|
+
'ps-05': !menuState.isOpen.value,
|
|
105
|
+
'scrollbar-gutter-both': menuState.isOpen.value,
|
|
106
|
+
aside_frame: props.frame,
|
|
107
|
+
}"
|
|
108
|
+
class="overflow-hidden flex column flex-stretch gap-1 w100p pt-1 pb-05 h-100p"
|
|
109
|
+
>
|
|
110
|
+
<!-- Logo/Brand -->
|
|
111
|
+
<router-link
|
|
112
|
+
to="/"
|
|
113
|
+
class="decoration-none flex px-05"
|
|
114
|
+
:class="{
|
|
115
|
+
'gap-025': menuState.isOpen.value,
|
|
116
|
+
'gap-0': !menuState.isOpen.value,
|
|
117
|
+
}"
|
|
118
|
+
>
|
|
119
|
+
<img
|
|
120
|
+
v-if="props.logo"
|
|
121
|
+
:src="props.logo"
|
|
122
|
+
:alt="props.logoAlt"
|
|
123
|
+
class="contain"
|
|
124
|
+
:style="{ height: props.logoHeight }"
|
|
125
|
+
/>
|
|
126
|
+
<span class="nav-text">
|
|
127
|
+
{{ props.name }}
|
|
128
|
+
</span>
|
|
129
|
+
</router-link>
|
|
130
|
+
|
|
131
|
+
<!-- Navigation Links -->
|
|
132
|
+
<nav class="sidebar-nav flex column flex-stretch gap-025 align-items-start scrollbar-gutter-stable">
|
|
133
|
+
<Btn v-for="link in props.navLinks" :key="link.to" :title="!menuState.isOpen.value && !menuState.isMobile.value
|
|
134
|
+
? link.label
|
|
135
|
+
: ''
|
|
136
|
+
" fullWidth alignTxt="start" class="flex-shrink-0 px-1" :class="{ 'nav-btn-active': route.path === link.to }" :style="{
|
|
137
|
+
backgroundColor:
|
|
138
|
+
route.path === link.to ? props.activeColor : props.bgColor,
|
|
139
|
+
color: route.path === link.to ? 'white' : props.textColor,
|
|
140
|
+
}" :to="link.to || '/'">
|
|
141
|
+
<Icon :name="link.icon" size="1.2" />
|
|
142
|
+
<span class="nav-text">
|
|
143
|
+
{{ link.label }}
|
|
144
|
+
</span>
|
|
145
|
+
</Btn>
|
|
146
|
+
</nav>
|
|
147
|
+
<!-- Footer -->
|
|
148
|
+
<div class="sidebar-footer flex column flex-stretch gap-025 align-items-start mt-auto scrollbar-gutter-stable">
|
|
149
|
+
<!-- Footer Links -->
|
|
150
|
+
<Btn v-for="link in props.footerLinks" :key="link.to || link.label" :title="!menuState.isOpen.value && !menuState.isMobile.value
|
|
151
|
+
? link.label
|
|
152
|
+
: ''
|
|
153
|
+
" alignTxt="start" fullWidth flat :icon="link.icon" class="flex-shrink-0 px-1" :to="link.to" @click="link.action">
|
|
154
|
+
<span class="nav-text">
|
|
155
|
+
{{ link.label }}
|
|
156
|
+
</span>
|
|
157
|
+
</Btn>
|
|
158
|
+
|
|
159
|
+
<!-- Default Logout Button if no footer links provided -->
|
|
160
|
+
<Btn v-if="props.footerLinks.length === 0" :title="!menuState.isOpen.value && !menuState.isMobile.value ? 'Logout' : ''
|
|
161
|
+
" alignTxt="start" fullWidth flat icon="logout" class="flex-shrink-0 px-1" @click="logout">
|
|
162
|
+
<span class="nav-text"> Logout </span>
|
|
163
|
+
</Btn>
|
|
164
|
+
|
|
165
|
+
<!-- Custom Footer Content Slot -->
|
|
166
|
+
<slot name="footer" />
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</aside>
|
|
170
|
+
</template>
|
|
171
|
+
|
|
172
|
+
<style>
|
|
173
|
+
.aside_frame {
|
|
174
|
+
outline: 1px solid var(--border-color);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.card .aside_frame {
|
|
178
|
+
outline-offset: -1px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.app-sidebar {
|
|
182
|
+
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
183
|
+
--input-font-size: 0.875rem;
|
|
184
|
+
/* 14px */
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* הסתרת סרגל גלילה רק בזמן טרנזישן */
|
|
188
|
+
.sidebar-nav {
|
|
189
|
+
overflow-y: auto;
|
|
190
|
+
flex: 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* הסתרת גלילה בזמן שינוי גודל */
|
|
194
|
+
.app-sidebar.transitioning .sidebar-nav {
|
|
195
|
+
scrollbar-width: none;
|
|
196
|
+
/* Firefox */
|
|
197
|
+
-ms-overflow-style: none;
|
|
198
|
+
/* IE and Edge */
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.app-sidebar.transitioning .sidebar-nav::-webkit-scrollbar {
|
|
202
|
+
display: none;
|
|
203
|
+
/* Chrome, Safari and Opera */
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* אנימציות איטות לטקסט */
|
|
207
|
+
.nav-text {
|
|
208
|
+
transition:
|
|
209
|
+
opacity 0.3s ease,
|
|
210
|
+
transform 0.3s ease;
|
|
211
|
+
white-space: nowrap;
|
|
212
|
+
overflow: hidden;
|
|
213
|
+
opacity: 1;
|
|
214
|
+
transform: translateX(0);
|
|
215
|
+
max-width: 200px;
|
|
216
|
+
font-weight: 300;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.app-sidebar.sidebar-collapsed .bgl_btn_fullWidth {
|
|
220
|
+
width: fit-content !important;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.app-sidebar.sidebar-collapsed .bgl_btn-flex {
|
|
224
|
+
gap: 0 !important;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* אנימציות נכנסות ויוצאות */
|
|
228
|
+
@media (min-width: 911px) {
|
|
229
|
+
.app-sidebar {
|
|
230
|
+
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* כשהמניו סגור - הטקסט נעלם תחילה */
|
|
234
|
+
.app-sidebar.sidebar-collapsed .nav-text {
|
|
235
|
+
opacity: 0;
|
|
236
|
+
transform: translateX(-15px);
|
|
237
|
+
max-width: 0;
|
|
238
|
+
transition:
|
|
239
|
+
opacity 0.15s ease,
|
|
240
|
+
transform 0.15s ease,
|
|
241
|
+
max-width 0.15s ease;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* כשהמניו פתוח - הטקסט מופיע אחרי שהרוחב משתנה */
|
|
245
|
+
.app-sidebar:not(.sidebar-collapsed) .nav-text {
|
|
246
|
+
opacity: 1;
|
|
247
|
+
transform: translateX(0);
|
|
248
|
+
max-width: 200px;
|
|
249
|
+
transition:
|
|
250
|
+
opacity 0.3s ease 0.15s,
|
|
251
|
+
transform 0.3s ease 0.15s,
|
|
252
|
+
max-width 0.3s ease 0.15s;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* במובייל הטקסט תמיד מופיע */
|
|
257
|
+
@media (max-width: 910px) {
|
|
258
|
+
.app-sidebar {
|
|
259
|
+
transform: translateX(-100%);
|
|
260
|
+
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.sidebar-mobile-open {
|
|
264
|
+
transform: translateX(0);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.sidebar-mobile-closed {
|
|
268
|
+
transform: translateX(-100%);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
</style>
|
|
@@ -6,3 +6,8 @@ export { default as TabbedLayout } from './TabbedLayout.vue'
|
|
|
6
6
|
export { default as Tabs } from './Tabs.vue'
|
|
7
7
|
export { default as TabsBody } from './TabsBody.vue'
|
|
8
8
|
export { default as TabsNav } from './TabsNav.vue'
|
|
9
|
+
export { default as AppContent } from './AppContent.vue'
|
|
10
|
+
export { default as AppLayout } from './AppLayout.vue'
|
|
11
|
+
export { default as AppSidebar } from './AppSidebar.vue'
|
|
12
|
+
|
|
13
|
+
|
|
@@ -14,7 +14,13 @@ export function useFormField<T>(props: {
|
|
|
14
14
|
get: () => {
|
|
15
15
|
if (!props.fieldID || !formState) return props.modelValue ?? props.field.defaultValue ?? ''
|
|
16
16
|
const value = formState.getFieldData(props.fieldID)
|
|
17
|
+
// For nested form containers, default to empty object
|
|
17
18
|
if (props.field.$el === 'form' && !value) return {}
|
|
19
|
+
// If no value is set yet and a defaultValue exists, write it into form state once
|
|
20
|
+
if ((value === undefined || value === null || value === '') && props.field.defaultValue !== undefined) {
|
|
21
|
+
formState.updateField(props.fieldID, props.field.defaultValue)
|
|
22
|
+
return props.field.defaultValue
|
|
23
|
+
}
|
|
18
24
|
return value ?? ''
|
|
19
25
|
},
|
|
20
26
|
set: (val: any) => {
|
|
@@ -226,9 +226,37 @@ export function useSchemaField<T extends { [key: string]: any }, SP extends Path
|
|
|
226
226
|
|
|
227
227
|
// Add options if they exist in the field
|
|
228
228
|
if (field.options) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
if (Array.isArray(field.options)) {
|
|
230
|
+
props.options = field.options
|
|
231
|
+
} else if (typeof field.options === 'function') {
|
|
232
|
+
const fn = field.options as any
|
|
233
|
+
// Component-aware mapping
|
|
234
|
+
if (Component === SelectInput) {
|
|
235
|
+
props.options = (query: string) => {
|
|
236
|
+
const row = getFormData?.()
|
|
237
|
+
const val = currentValue
|
|
238
|
+
const out = fn(val, row)
|
|
239
|
+
if (Array.isArray(out)) return out
|
|
240
|
+
if (typeof out === 'function') return out(query)
|
|
241
|
+
if (out && typeof out.then === 'function') return out
|
|
242
|
+
return fn(query, val, row)
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
// Non-search components (e.g., RadioGroup): return a zero-arg async loader using latest row
|
|
246
|
+
props.options = async () => {
|
|
247
|
+
const row = getFormData?.()
|
|
248
|
+
const val = currentValue
|
|
249
|
+
const out = fn(val, row)
|
|
250
|
+
if (Array.isArray(out)) return out
|
|
251
|
+
if (typeof out === 'function') return await out('')
|
|
252
|
+
if (out && typeof out.then === 'function') return await out
|
|
253
|
+
if (fn.length >= 3) return await fn('', val, row)
|
|
254
|
+
return []
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
props.options = field.options
|
|
259
|
+
}
|
|
232
260
|
}
|
|
233
261
|
|
|
234
262
|
// Handle dynamic props and attrs
|
package/src/styles/inputs.css
CHANGED
|
@@ -65,6 +65,15 @@ select {
|
|
|
65
65
|
width: 100%;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
.bagel-input.frame input,
|
|
69
|
+
.bagel-input.frame textarea,
|
|
70
|
+
.bagel-input.frame select,
|
|
71
|
+
.custom-select.frame .input {
|
|
72
|
+
outline: 1px solid var(--border-color);
|
|
73
|
+
outline-offset: -1px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
68
77
|
.bagel-input input::-webkit-input-placeholder,
|
|
69
78
|
.bagel-input textarea::-webkit-input-placeholder,
|
|
70
79
|
.bagel-input select::-webkit-input-placeholder,
|
package/src/styles/theme.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
/* TYPE */
|
|
45
45
|
:root {
|
|
46
46
|
--bgl-font: 'Lexend', 'Ploni', sans-serif;
|
|
47
|
-
--input-font-size:
|
|
47
|
+
--input-font-size: 14px;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/* DIMENSIONS */
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
--pill-font-size: 12px;
|
|
56
56
|
--input-border-radius: 7px;
|
|
57
57
|
--card-border-radius: 12px;
|
|
58
|
-
--btn-border-radius:
|
|
58
|
+
--btn-border-radius: 6px;
|
|
59
59
|
--btn-padding: 30px;
|
|
60
60
|
--btn-height: 40px;
|
|
61
61
|
--pill-border-radius: 8px;
|
package/src/types/BagelForm.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ArrayAttrs, FieldArray, SelectInput, TextInput } from '@bagelink/vue'
|
|
1
|
+
import type { ArrayAttrs, FieldArray, SelectInput, TextInput, Option } from '@bagelink/vue'
|
|
2
2
|
import type { Paths, Get, IterableElement, OmitIndexSignature } from 'type-fest'
|
|
3
3
|
import type { ToString } from 'type-fest/source/internal'
|
|
4
4
|
import type { LiteralStringUnion } from 'type-fest/source/literal-union'
|
|
@@ -34,7 +34,8 @@ export type BagelFieldOptions<T, P extends Path<T>> =
|
|
|
34
34
|
| boolean
|
|
35
35
|
| { [key: string]: any }
|
|
36
36
|
)[]
|
|
37
|
-
| ((val?: FieldVal<T, P>, rowData?: T) =>
|
|
37
|
+
| ((val?: FieldVal<T, P>, rowData?: T) => Option[] | ((query: string) => Promise<Option[]>))
|
|
38
|
+
| ((query: string, val?: FieldVal<T, P>, rowData?: T) => Promise<Option[]>)
|
|
38
39
|
|
|
39
40
|
export type VIfType<T, P extends Path<T>> =
|
|
40
41
|
| string
|
|
@@ -33,7 +33,6 @@ export interface SlctInputOptions<T, K extends Path<T>> extends InputOptions<T,
|
|
|
33
33
|
searchable?: boolean
|
|
34
34
|
multiselect?: boolean
|
|
35
35
|
clearable?: boolean
|
|
36
|
-
onSearch?: (search: string) => any
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
export interface NumFieldOptions<T, K extends Path<T>> extends InputOptions<T, K> {
|
|
@@ -128,7 +127,7 @@ export function selectField<
|
|
|
128
127
|
>(
|
|
129
128
|
id?: P,
|
|
130
129
|
label?: string,
|
|
131
|
-
options?: Option[] | (() => Option[]),
|
|
130
|
+
options?: Option[] | ((query: string) => Promise<Option[]>) | ((val?: any, row?: T) => Option[] | ((query: string) => Promise<Option[]>)) | ((query: string, val?: any, row?: T) => Promise<Option[]>),
|
|
132
131
|
config?: SlctInputOptions<T, P>,
|
|
133
132
|
): SelectBagelField<T, P, PO> {
|
|
134
133
|
return {
|
|
@@ -148,7 +147,6 @@ export function selectField<
|
|
|
148
147
|
disabled: config?.disabled,
|
|
149
148
|
searchable: config?.searchable,
|
|
150
149
|
multiselect: config?.multiselect,
|
|
151
|
-
onSearch: config?.onSearch,
|
|
152
150
|
clearable: config?.clearable,
|
|
153
151
|
autocomplete: config?.autocomplete,
|
|
154
152
|
},
|
|
@@ -9,31 +9,53 @@ interface TimeDeltaOptions {
|
|
|
9
9
|
Week?: number
|
|
10
10
|
Month?: number
|
|
11
11
|
Year?: number
|
|
12
|
+
days?: number
|
|
13
|
+
hours?: number
|
|
14
|
+
minutes?: number
|
|
15
|
+
seconds?: number
|
|
16
|
+
weeks?: number
|
|
17
|
+
months?: number
|
|
18
|
+
years?: number
|
|
19
|
+
day?: number
|
|
20
|
+
hour?: number
|
|
21
|
+
minute?: number
|
|
22
|
+
second?: number
|
|
23
|
+
week?: number
|
|
24
|
+
month?: number
|
|
25
|
+
year?: number
|
|
12
26
|
}
|
|
13
27
|
|
|
14
28
|
export function timeDelta(date: string | Date, options: TimeDeltaOptions) {
|
|
29
|
+
console.log('date', date)
|
|
15
30
|
date = new Date(date)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
console.log('date', date)
|
|
32
|
+
const day = options.Day || options.day || options.days || 0
|
|
33
|
+
const hour = options.Hour || options.hour || options.hours || 0
|
|
34
|
+
const minute = options.Minute || options.minute || options.minutes || 0
|
|
35
|
+
const second = options.Second || options.second || options.seconds || 0
|
|
36
|
+
const week = options.Week || options.week || options.weeks || 0
|
|
37
|
+
const month = options.Month || options.month || options.months || 0
|
|
38
|
+
const year = options.Year || options.year || options.years || 0
|
|
39
|
+
if (day) {
|
|
40
|
+
date.setDate(date.getDate() + day)
|
|
19
41
|
}
|
|
20
|
-
if (
|
|
21
|
-
date.setHours(date.getHours() +
|
|
42
|
+
if (hour) {
|
|
43
|
+
date.setHours(date.getHours() + hour)
|
|
22
44
|
}
|
|
23
|
-
if (
|
|
24
|
-
date.setMinutes(date.getMinutes() +
|
|
45
|
+
if (minute) {
|
|
46
|
+
date.setMinutes(date.getMinutes() + minute)
|
|
25
47
|
}
|
|
26
|
-
if (
|
|
27
|
-
date.setSeconds(date.getSeconds() +
|
|
48
|
+
if (second) {
|
|
49
|
+
date.setSeconds(date.getSeconds() + second)
|
|
28
50
|
}
|
|
29
|
-
if (
|
|
30
|
-
date.setDate(date.getDate() +
|
|
51
|
+
if (week) {
|
|
52
|
+
date.setDate(date.getDate() + week * 7)
|
|
31
53
|
}
|
|
32
|
-
if (
|
|
33
|
-
date.setMonth(date.getMonth() +
|
|
54
|
+
if (month) {
|
|
55
|
+
date.setMonth(date.getMonth() + month)
|
|
34
56
|
}
|
|
35
|
-
if (
|
|
36
|
-
date.setFullYear(date.getFullYear() +
|
|
57
|
+
if (year) {
|
|
58
|
+
date.setFullYear(date.getFullYear() + year)
|
|
37
59
|
}
|
|
38
60
|
return date
|
|
39
61
|
}
|
|
@@ -247,20 +269,52 @@ export interface FormatDateOptions extends Partial<Pick<Intl.DateTimeFormatOptio
|
|
|
247
269
|
locale?: Intl.LocalesArgument
|
|
248
270
|
tz?: string
|
|
249
271
|
}
|
|
272
|
+
|
|
250
273
|
/**
|
|
251
274
|
* Formats a date based on the provided format string, locale, and timezone.
|
|
252
275
|
* @param date The date to format (string or Date object).
|
|
276
|
+
* @param format The format string (default is 'DD.MM.YY').
|
|
277
|
+
* @returns Formatted date string.
|
|
278
|
+
*/
|
|
279
|
+
export function formatDate(date?: DateLike, format?: DateTimeAcceptedFormats): string
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Formats a date based on the provided options.
|
|
283
|
+
* @param date The date to format (string or Date object).
|
|
253
284
|
* @param opts Options for formatting the date.
|
|
254
285
|
* @param opts.fmt The format string (default is 'DD.MM.YY').
|
|
255
286
|
* @param opts.locale The locale to use for formatting (default is browser's locale).
|
|
256
287
|
* @param opts.tz The timezone to use for formatting (default is local timezone).
|
|
257
288
|
* @returns Formatted date string.
|
|
258
289
|
*/
|
|
290
|
+
export function formatDate(date?: DateLike, opts?: FormatDateOptions): string
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Formats a date based on the provided format string, locale, and timezone.
|
|
294
|
+
* @param date The date to format (string or Date object).
|
|
295
|
+
* @param formatOrOpts The format string or options object.
|
|
296
|
+
* @returns Formatted date string.
|
|
297
|
+
*/
|
|
259
298
|
export function formatDate(
|
|
260
299
|
date?: DateLike,
|
|
261
|
-
|
|
300
|
+
formatOrOpts?: DateTimeAcceptedFormats | FormatDateOptions,
|
|
262
301
|
): string {
|
|
263
|
-
let
|
|
302
|
+
let format: DateTimeAcceptedFormats | undefined
|
|
303
|
+
let locale: Intl.LocalesArgument | undefined
|
|
304
|
+
let timeZone: string | undefined
|
|
305
|
+
let rest: Partial<Pick<Intl.DateTimeFormatOptions, 'hour12'>> = {}
|
|
306
|
+
|
|
307
|
+
// Handle both overloads
|
|
308
|
+
if (typeof formatOrOpts === 'string') {
|
|
309
|
+
// First overload: format string directly
|
|
310
|
+
format = formatOrOpts
|
|
311
|
+
} else if (formatOrOpts && typeof formatOrOpts === 'object') {
|
|
312
|
+
// Second overload: options object
|
|
313
|
+
format = formatOrOpts.fmt
|
|
314
|
+
locale = formatOrOpts.locale
|
|
315
|
+
timeZone = formatOrOpts.tz
|
|
316
|
+
rest = formatOrOpts
|
|
317
|
+
}
|
|
264
318
|
|
|
265
319
|
if (!date) return ''
|
|
266
320
|
format = format || 'DD.MM.YY'
|
|
@@ -22,6 +22,14 @@ export interface BtnElementOptions<
|
|
|
22
22
|
target?: '_blank' | '_self' | '_parent' | '_top'
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export interface PillElementOptions<
|
|
26
|
+
T = any,
|
|
27
|
+
PO extends PathsOptions = DefaultPathsOptions
|
|
28
|
+
> extends Partial<BaseElementField<T, PO>> {
|
|
29
|
+
color?: string
|
|
30
|
+
icon?: IconType
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
export interface TxtElementOptions<
|
|
26
34
|
T = any,
|
|
27
35
|
PO extends PathsOptions = DefaultPathsOptions
|
|
@@ -388,6 +396,20 @@ export function img<
|
|
|
388
396
|
}
|
|
389
397
|
}
|
|
390
398
|
|
|
399
|
+
export function pill<
|
|
400
|
+
T = any,
|
|
401
|
+
PO extends PathsOptions = DefaultPathsOptions
|
|
402
|
+
>(options: PillElementOptions<T, PO>): BaseElementField<T, PO> {
|
|
403
|
+
return {
|
|
404
|
+
$el: 'pill',
|
|
405
|
+
id: options?.id,
|
|
406
|
+
class: options?.class,
|
|
407
|
+
vIf: options?.vIf,
|
|
408
|
+
style: options?.style,
|
|
409
|
+
attrs: options?.attrs || {},
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
391
413
|
export function dropdown<
|
|
392
414
|
T = any,
|
|
393
415
|
PO extends PathsOptions = DefaultPathsOptions
|
package/src/utils/useSearch.ts
CHANGED
|
@@ -372,8 +372,10 @@ export function useSearch<T>(
|
|
|
372
372
|
clearTimeout(debounceTimeout)
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
const term = typeof newTerm === 'string' ? newTerm : ''
|
|
376
|
+
|
|
377
|
+
// Allow initial/default fetch when minChars === 0 (including empty term)
|
|
378
|
+
if (term.length < minChars && minChars > 0) {
|
|
377
379
|
serverResults.value = []
|
|
378
380
|
return
|
|
379
381
|
}
|
|
@@ -382,7 +384,7 @@ export function useSearch<T>(
|
|
|
382
384
|
debounceTimeout = window.setTimeout(async () => {
|
|
383
385
|
try {
|
|
384
386
|
isLoading.value = true
|
|
385
|
-
serverResults.value = await serverSearch(
|
|
387
|
+
serverResults.value = await serverSearch(term)
|
|
386
388
|
} catch (error) {
|
|
387
389
|
console.error('Server search error:', error)
|
|
388
390
|
serverResults.value = []
|
|
@@ -390,7 +392,8 @@ export function useSearch<T>(
|
|
|
390
392
|
isLoading.value = false
|
|
391
393
|
}
|
|
392
394
|
}, debounceMs)
|
|
393
|
-
}
|
|
395
|
+
},
|
|
396
|
+
{ immediate: true }
|
|
394
397
|
)
|
|
395
398
|
}
|
|
396
399
|
|
|
@@ -401,9 +404,13 @@ export function useSearch<T>(
|
|
|
401
404
|
const getFilteredResults = (): T[] => {
|
|
402
405
|
const term = getSearchTermValue()
|
|
403
406
|
|
|
404
|
-
// If using server-side search
|
|
405
|
-
if (serverSearch
|
|
406
|
-
|
|
407
|
+
// If using server-side search
|
|
408
|
+
if (serverSearch) {
|
|
409
|
+
if (term && typeof term === 'string' && term.length >= minChars) {
|
|
410
|
+
return serverResults.value as T[]
|
|
411
|
+
}
|
|
412
|
+
// If minChars is 0, allow default server results when empty
|
|
413
|
+
if (minChars === 0) return serverResults.value as T[]
|
|
407
414
|
}
|
|
408
415
|
|
|
409
416
|
// Otherwise use client-side filtering
|