@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.
Files changed (87) hide show
  1. package/bin/generateFormSchema.ts +12 -12
  2. package/dist/components/Card.vue.d.ts.map +1 -1
  3. package/dist/components/ImportData.vue.d.ts.map +1 -1
  4. package/dist/components/ListItem.vue.d.ts +6 -1
  5. package/dist/components/ListItem.vue.d.ts.map +1 -1
  6. package/dist/components/analytics/BarChart.vue.d.ts +39 -0
  7. package/dist/components/analytics/BarChart.vue.d.ts.map +1 -0
  8. package/dist/components/analytics/KpiCard.vue.d.ts +24 -0
  9. package/dist/components/analytics/KpiCard.vue.d.ts.map +1 -0
  10. package/dist/components/analytics/LineChart.vue.d.ts +26 -0
  11. package/dist/components/analytics/LineChart.vue.d.ts.map +1 -0
  12. package/dist/components/analytics/PieChart.vue.d.ts +24 -0
  13. package/dist/components/analytics/PieChart.vue.d.ts.map +1 -0
  14. package/dist/components/analytics/index.d.ts +5 -0
  15. package/dist/components/analytics/index.d.ts.map +1 -0
  16. package/dist/components/calendar/Index.vue.d.ts.map +1 -1
  17. package/dist/components/calendar/index.d.ts +2 -0
  18. package/dist/components/calendar/index.d.ts.map +1 -0
  19. package/dist/components/calendar/views/MonthView.vue.d.ts.map +1 -1
  20. package/dist/components/calendar/views/WeekView.vue.d.ts.map +1 -1
  21. package/dist/components/dataTable/DataTable.vue.d.ts.map +1 -1
  22. package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
  23. package/dist/components/form/inputs/DatePicker.vue.d.ts +2 -0
  24. package/dist/components/form/inputs/DatePicker.vue.d.ts.map +1 -1
  25. package/dist/components/form/inputs/RadioGroup.vue.d.ts +6 -10
  26. package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
  27. package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -1
  28. package/dist/components/form/inputs/SelectInput.vue.d.ts +2 -2
  29. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  30. package/dist/components/layout/AppContent.vue.d.ts +34 -0
  31. package/dist/components/layout/AppContent.vue.d.ts.map +1 -0
  32. package/dist/components/layout/AppLayout.vue.d.ts +27 -0
  33. package/dist/components/layout/AppLayout.vue.d.ts.map +1 -0
  34. package/dist/components/layout/AppSidebar.vue.d.ts +44 -0
  35. package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -0
  36. package/dist/components/layout/index.d.ts +3 -0
  37. package/dist/components/layout/index.d.ts.map +1 -1
  38. package/dist/composables/useFormField.d.ts.map +1 -1
  39. package/dist/composables/useSchemaField.d.ts +2 -2
  40. package/dist/composables/useSchemaField.d.ts.map +1 -1
  41. package/dist/index.cjs +19 -19
  42. package/dist/index.mjs +10 -10
  43. package/dist/style.css +1 -1
  44. package/dist/types/BagelForm.d.ts +25 -13
  45. package/dist/types/BagelForm.d.ts.map +1 -1
  46. package/dist/utils/BagelFormUtils.d.ts +11 -8
  47. package/dist/utils/BagelFormUtils.d.ts.map +1 -1
  48. package/dist/utils/calendar/dateUtils.d.ts +21 -0
  49. package/dist/utils/calendar/dateUtils.d.ts.map +1 -1
  50. package/dist/utils/elementUtils.d.ts +5 -0
  51. package/dist/utils/elementUtils.d.ts.map +1 -1
  52. package/dist/utils/useSearch.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/components/Card.vue +1 -2
  55. package/src/components/DataPreview.vue +1 -1
  56. package/src/components/ImportData.vue +94 -88
  57. package/src/components/ListItem.vue +32 -24
  58. package/src/components/analytics/BarChart.vue +153 -0
  59. package/src/components/analytics/KpiCard.vue +84 -0
  60. package/src/components/analytics/LineChart.vue +267 -0
  61. package/src/components/analytics/PieChart.vue +183 -0
  62. package/src/components/analytics/index.ts +4 -0
  63. package/src/components/calendar/Index.vue +15 -35
  64. package/src/components/calendar/views/MonthView.vue +84 -88
  65. package/src/components/calendar/views/WeekView.vue +143 -89
  66. package/src/components/dataTable/DataTable.vue +2 -3
  67. package/src/components/form/BagelForm.vue +27 -6
  68. package/src/components/form/inputs/DateInput.vue +2 -2
  69. package/src/components/form/inputs/DatePicker.vue +42 -48
  70. package/src/components/form/inputs/RadioGroup.vue +60 -35
  71. package/src/components/form/inputs/RichText/utils/media.ts +1 -2
  72. package/src/components/form/inputs/SelectInput.vue +94 -101
  73. package/src/components/form/inputs/Upload/upload.css +135 -138
  74. package/src/components/layout/AppContent.vue +125 -0
  75. package/src/components/layout/AppLayout.vue +124 -0
  76. package/src/components/layout/AppSidebar.vue +271 -0
  77. package/src/components/layout/index.ts +5 -0
  78. package/src/composables/useFormField.ts +6 -0
  79. package/src/composables/useSchemaField.ts +38 -10
  80. package/src/styles/inputs.css +9 -0
  81. package/src/styles/theme.css +2 -2
  82. package/src/types/BagelForm.ts +68 -13
  83. package/src/utils/BagelFormUtils.ts +49 -52
  84. package/src/utils/calendar/dateUtils.ts +71 -17
  85. package/src/utils/elementUtils.ts +23 -4
  86. package/src/utils/useSearch.ts +14 -7
  87. /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 }, SP extends Path<T>>(optns: UseSchemaFieldOptions<T, SP>) {
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, SP>, slots?: BaseBagelField<T, SP>['slots']) {
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, SP>,
103
+ child as BaseBagelField<T, Path<T>>,
104
104
  slots
105
105
  )
106
106
  }
107
107
 
108
108
  function renderField(
109
- field: BaseBagelField<T, SP>,
110
- slots?: BaseBagelField<T, SP>['slots']
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
- props.options = typeof field.options === 'function'
230
- ? field.options(currentValue, rowData)
231
- : field.options
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, SP>, slots)
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, SP> | undefined)?.({ row: rowData, field }) : undefined
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
@@ -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,
@@ -44,7 +44,7 @@
44
44
  /* TYPE */
45
45
  :root {
46
46
  --bgl-font: 'Lexend', 'Ploni', sans-serif;
47
- --input-font-size: 16px;
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: 10px;
58
+ --btn-border-radius: 6px;
59
59
  --btn-padding: 30px;
60
60
  --btn-height: 40px;
61
61
  --pill-border-radius: 8px;
@@ -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: FieldVal<T, P>,
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?: FieldVal<T, P>, rowData?: T) => void)
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?: FieldVal<T, P>, rowData?: T) => boolean)
43
+ | ((val?: SmartFieldVal<T, P>, rowData?: T) => boolean)
43
44
 
44
45
  export type ValidationFn<T, P extends Path<T>> = (
45
- val?: FieldVal<T, P>,
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 | AttributeFn<T, P>
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'?: BagelFieldOptions<T, P>
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'?: VIfType<T, P>
116
- 'v-if'?: VIfType<T, P>
117
- 'transform'?: (val?: FieldVal<T, P>, rowData?: T) => any
118
- 'onUpdate'?: (val?: FieldVal<T, P>, rowData?: T) => unknown
119
- 'validate'?: ValidationFn<T, P>
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<