@bagelink/vue 1.4.109 → 1.4.115
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/bin/generateFormSchema.ts +12 -12
- package/dist/components/Card.vue.d.ts.map +1 -1
- package/dist/components/ImportData.vue.d.ts.map +1 -1
- 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/calendar/Index.vue.d.ts.map +1 -1
- package/dist/components/calendar/index.d.ts +2 -0
- package/dist/components/calendar/index.d.ts.map +1 -0
- package/dist/components/calendar/views/MonthView.vue.d.ts.map +1 -1
- package/dist/components/calendar/views/WeekView.vue.d.ts.map +1 -1
- package/dist/components/dataTable/DataTable.vue.d.ts.map +1 -1
- package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DatePicker.vue.d.ts +2 -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/RichText/utils/media.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 +2 -2
- 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 +25 -13
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/dist/utils/BagelFormUtils.d.ts +11 -8
- 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/Card.vue +1 -2
- package/src/components/DataPreview.vue +1 -1
- package/src/components/ImportData.vue +94 -88
- 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 +183 -0
- package/src/components/analytics/index.ts +4 -0
- package/src/components/calendar/Index.vue +15 -35
- package/src/components/calendar/views/MonthView.vue +84 -88
- package/src/components/calendar/views/WeekView.vue +143 -89
- package/src/components/dataTable/DataTable.vue +2 -3
- package/src/components/form/BagelForm.vue +27 -6
- package/src/components/form/inputs/DateInput.vue +2 -2
- package/src/components/form/inputs/DatePicker.vue +42 -48
- package/src/components/form/inputs/RadioGroup.vue +60 -35
- package/src/components/form/inputs/RichText/utils/media.ts +1 -2
- 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 +125 -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 +38 -10
- package/src/styles/inputs.css +9 -0
- package/src/styles/theme.css +2 -2
- package/src/types/BagelForm.ts +68 -13
- package/src/utils/BagelFormUtils.ts +49 -52
- package/src/utils/calendar/dateUtils.ts +71 -17
- package/src/utils/elementUtils.ts +23 -4
- package/src/utils/useSearch.ts +14 -7
- /package/src/components/{dialog → calendar}/index.ts +0 -0
|
@@ -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) => {
|
|
@@ -34,7 +34,7 @@ export interface UseSchemaFieldOptions<T, SFP extends Path<T>> {
|
|
|
34
34
|
includeUnset?: boolean
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export function useSchemaField<T extends { [key: string]: any }
|
|
37
|
+
export function useSchemaField<T extends { [key: string]: any }>(optns: UseSchemaFieldOptions<T, Path<T>>) {
|
|
38
38
|
const { mode = 'form', getFormData, onUpdateModelValue, includeUnset = false } = optns
|
|
39
39
|
|
|
40
40
|
// Helper function to render objects recursively
|
|
@@ -96,18 +96,18 @@ export function useSchemaField<T extends { [key: string]: any }, SP extends Path
|
|
|
96
96
|
return typeof field.$el === 'object' ? field.$el : componentMap[field.$el as keyof typeof componentMap] ?? field.$el ?? 'div'
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
function renderChild(child: SchemaChild<T,
|
|
99
|
+
function renderChild(child: SchemaChild<T, Path<T>>, slots?: BaseBagelField<T, Path<T>>['slots']) {
|
|
100
100
|
if (typeof child === 'string') return child
|
|
101
101
|
if (isVNode(child)) return child
|
|
102
102
|
return renderField(
|
|
103
|
-
child as BaseBagelField<T,
|
|
103
|
+
child as BaseBagelField<T, Path<T>>,
|
|
104
104
|
slots
|
|
105
105
|
)
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
function renderField(
|
|
109
|
-
field: BaseBagelField<T,
|
|
110
|
-
slots?: BaseBagelField<T,
|
|
109
|
+
field: BaseBagelField<T, Path<T>>,
|
|
110
|
+
slots?: BaseBagelField<T, Path<T>>['slots']
|
|
111
111
|
): VNode | undefined {
|
|
112
112
|
const Component = getComponent(field as Field<T>)
|
|
113
113
|
if (!Component) return
|
|
@@ -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
|
|
@@ -292,7 +320,7 @@ export function useSchemaField<T extends { [key: string]: any }, SP extends Path
|
|
|
292
320
|
return schemaField
|
|
293
321
|
}
|
|
294
322
|
|
|
295
|
-
return renderField(schemaField as BaseBagelField<T,
|
|
323
|
+
return renderField(schemaField as BaseBagelField<T, Path<T>>, slots)
|
|
296
324
|
})
|
|
297
325
|
}
|
|
298
326
|
}
|
|
@@ -301,7 +329,7 @@ export function useSchemaField<T extends { [key: string]: any }, SP extends Path
|
|
|
301
329
|
|
|
302
330
|
// Handle custom slot content from parent
|
|
303
331
|
|
|
304
|
-
const slotContent = field.id ? (slots?.[field.id] as VNodeFn<T,
|
|
332
|
+
const slotContent = field.id ? (slots?.[field.id] as VNodeFn<T, Path<T>> | undefined)?.({ row: rowData, field }) : undefined
|
|
305
333
|
// field.id && slots?.[field.id] && typeof slots[field.id] === 'function' && !Array.isArray(slots?.[field.id])
|
|
306
334
|
// ? (slots[field.id])({ row: rowData, field })
|
|
307
335
|
// : undefined
|
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'
|
|
@@ -14,7 +14,7 @@ export type AttributeValue =
|
|
|
14
14
|
| { [key: string]: any }
|
|
15
15
|
|
|
16
16
|
export type AttributeFn<T, P extends Path<T>> = (
|
|
17
|
-
field:
|
|
17
|
+
field: SmartFieldVal<T, P>,
|
|
18
18
|
row?: T
|
|
19
19
|
) => AttributeValue
|
|
20
20
|
|
|
@@ -34,15 +34,16 @@ export type BagelFieldOptions<T, P extends Path<T>> =
|
|
|
34
34
|
| boolean
|
|
35
35
|
| { [key: string]: any }
|
|
36
36
|
)[]
|
|
37
|
-
| ((val?:
|
|
37
|
+
| ((val?: SmartFieldVal<T, P>, rowData?: T) => Option[] | ((query: string) => Promise<Option[]>))
|
|
38
|
+
| ((query: string, val?: SmartFieldVal<T, P>, rowData?: T) => Promise<Option[]>)
|
|
38
39
|
|
|
39
40
|
export type VIfType<T, P extends Path<T>> =
|
|
40
41
|
| string
|
|
41
42
|
| boolean
|
|
42
|
-
| ((val?:
|
|
43
|
+
| ((val?: SmartFieldVal<T, P>, rowData?: T) => boolean)
|
|
43
44
|
|
|
44
45
|
export type ValidationFn<T, P extends Path<T>> = (
|
|
45
|
-
val?:
|
|
46
|
+
val?: SmartFieldVal<T, P>,
|
|
46
47
|
rowData?: T
|
|
47
48
|
) => string | undefined
|
|
48
49
|
|
|
@@ -58,7 +59,61 @@ export type _Path<
|
|
|
58
59
|
export type Path<T, PO extends PathsOptions = DefaultPathsOptions> =
|
|
59
60
|
FieldVal<T, _Path<T, PO>> extends Array<any>
|
|
60
61
|
? LiteralStringUnion<_Path<T, PO>>
|
|
61
|
-
: _Path<T, PO>
|
|
62
|
+
: _Path<T, PO> | string
|
|
63
|
+
|
|
64
|
+
// Smart field value type that preserves type information when possible
|
|
65
|
+
export type SmartFieldVal<T, P extends Path<T>> =
|
|
66
|
+
P extends string
|
|
67
|
+
? P extends keyof T
|
|
68
|
+
? T[P]
|
|
69
|
+
: any
|
|
70
|
+
: FieldVal<T, P>
|
|
71
|
+
|
|
72
|
+
// Smart bagel field options that preserve type information
|
|
73
|
+
export type SmartBagelFieldOptions<T, P extends Path<T>> =
|
|
74
|
+
| string
|
|
75
|
+
| (
|
|
76
|
+
| {
|
|
77
|
+
label?: string
|
|
78
|
+
value: string | number
|
|
79
|
+
}
|
|
80
|
+
| string
|
|
81
|
+
| number
|
|
82
|
+
| boolean
|
|
83
|
+
| { [key: string]: any }
|
|
84
|
+
)[]
|
|
85
|
+
| ((val?: SmartFieldVal<T, P>, rowData?: T) => Option[] | ((query: string) => Promise<Option[]>))
|
|
86
|
+
| ((query: string, val?: SmartFieldVal<T, P>, rowData?: T) => Promise<Option[]>)
|
|
87
|
+
|
|
88
|
+
// Smart VIf type that preserves type information
|
|
89
|
+
export type SmartVIfType<T, P extends Path<T>> =
|
|
90
|
+
| string
|
|
91
|
+
| boolean
|
|
92
|
+
| ((val?: SmartFieldVal<T, P>, rowData?: T) => boolean)
|
|
93
|
+
|
|
94
|
+
// Smart validation function that preserves type information
|
|
95
|
+
export type SmartValidationFn<T, P extends Path<T>> = (
|
|
96
|
+
val?: SmartFieldVal<T, P>,
|
|
97
|
+
rowData?: T
|
|
98
|
+
) => string | undefined
|
|
99
|
+
|
|
100
|
+
// Smart attribute function that preserves type information
|
|
101
|
+
export type SmartAttributeFn<T, P extends Path<T>> = (
|
|
102
|
+
field: SmartFieldVal<T, P>,
|
|
103
|
+
row?: T
|
|
104
|
+
) => AttributeValue
|
|
105
|
+
|
|
106
|
+
// Smart transform function that preserves type information
|
|
107
|
+
export type SmartTransformFn<T, P extends Path<T>> = (
|
|
108
|
+
val?: SmartFieldVal<T, P>,
|
|
109
|
+
rowData?: T
|
|
110
|
+
) => any
|
|
111
|
+
|
|
112
|
+
// Smart update function that preserves type information
|
|
113
|
+
export type SmartUpdateFn<T, P extends Path<T>> = (
|
|
114
|
+
val?: SmartFieldVal<T, P>,
|
|
115
|
+
rowData?: T
|
|
116
|
+
) => unknown
|
|
62
117
|
|
|
63
118
|
/** The value type at path P in object T. */
|
|
64
119
|
// Fall back to unknown if type resolution fails
|
|
@@ -103,20 +158,20 @@ export interface BaseBagelField<
|
|
|
103
158
|
'id'?: P
|
|
104
159
|
'label'?: string
|
|
105
160
|
'placeholder'?: string
|
|
106
|
-
'class'?: AttributeValue |
|
|
161
|
+
'class'?: AttributeValue | SmartAttributeFn<T, P>
|
|
107
162
|
'attrs'?: Attributes<T, P>
|
|
108
163
|
'required'?: boolean
|
|
109
164
|
'disabled'?: boolean
|
|
110
165
|
'helptext'?: string
|
|
111
|
-
'options'?:
|
|
166
|
+
'options'?: SmartBagelFieldOptions<T, P>
|
|
112
167
|
'children'?: SchemaChild<T, Path<T, PO>, PO>[]
|
|
113
168
|
'slots'?: { [key: string]: SchemaChild<T, Path<T, PO>, PO>[] }
|
|
114
169
|
'defaultValue'?: any
|
|
115
|
-
'vIf'?:
|
|
116
|
-
'v-if'?:
|
|
117
|
-
'transform'?:
|
|
118
|
-
'onUpdate'?:
|
|
119
|
-
'validate'?:
|
|
170
|
+
'vIf'?: SmartVIfType<T, P>
|
|
171
|
+
'v-if'?: SmartVIfType<T, P>
|
|
172
|
+
'transform'?: SmartTransformFn<T, P>
|
|
173
|
+
'onUpdate'?: SmartUpdateFn<T, P>
|
|
174
|
+
'validate'?: SmartValidationFn<T, P>
|
|
120
175
|
}
|
|
121
176
|
|
|
122
177
|
export type _MappedBaseBagelField<
|