@bagelink/vue 0.0.1145 → 0.0.1151

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 (53) hide show
  1. package/dist/components/DataPreview.vue.d.ts +12 -35
  2. package/dist/components/DataPreview.vue.d.ts.map +1 -1
  3. package/dist/components/DataTable/DataTable.vue.d.ts +1 -1
  4. package/dist/components/DataTable/DataTable.vue.d.ts.map +1 -1
  5. package/dist/components/DataTable/useTableData.d.ts +10 -2
  6. package/dist/components/DataTable/useTableData.d.ts.map +1 -1
  7. package/dist/components/Draggable/Draggable.vue.d.ts +45 -0
  8. package/dist/components/Draggable/Draggable.vue.d.ts.map +1 -0
  9. package/dist/components/Draggable/index.d.ts +5 -0
  10. package/dist/components/Draggable/index.d.ts.map +1 -0
  11. package/dist/components/Draggable/useDraggable.d.ts +31 -0
  12. package/dist/components/Draggable/useDraggable.d.ts.map +1 -0
  13. package/dist/components/Draggable/vDraggable.d.ts +4 -0
  14. package/dist/components/Draggable/vDraggable.d.ts.map +1 -0
  15. package/dist/components/ListView.vue.d.ts.map +1 -1
  16. package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
  17. package/dist/components/form/FieldArray.vue.d.ts.map +1 -1
  18. package/dist/components/form/index.d.ts +0 -1
  19. package/dist/components/form/index.d.ts.map +1 -1
  20. package/dist/components/form/inputs/FileUpload.vue.d.ts +3 -0
  21. package/dist/components/form/inputs/FileUpload.vue.d.ts.map +1 -1
  22. package/dist/components/form/inputs/NumberInput.vue.d.ts.map +1 -1
  23. package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts +3 -0
  24. package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts.map +1 -1
  25. package/dist/components/index.d.ts +2 -0
  26. package/dist/components/index.d.ts.map +1 -1
  27. package/dist/composables/useSchemaField.d.ts +15 -0
  28. package/dist/composables/useSchemaField.d.ts.map +1 -0
  29. package/dist/index.cjs +1256 -802
  30. package/dist/index.mjs +1258 -804
  31. package/dist/style.css +272 -285
  32. package/package.json +1 -1
  33. package/src/components/DataPreview.vue +45 -116
  34. package/src/components/DataTable/DataTable.vue +18 -12
  35. package/src/components/DataTable/useTableData.ts +50 -16
  36. package/src/components/Draggable/Draggable.vue +64 -0
  37. package/src/components/Draggable/index.ts +4 -0
  38. package/src/components/Draggable/useDraggable.ts +632 -0
  39. package/src/components/Draggable/vDraggable.ts +17 -0
  40. package/src/components/ListView.vue +6 -2
  41. package/src/components/Pill.vue +1 -1
  42. package/src/components/form/BagelForm.vue +16 -101
  43. package/src/components/form/FieldArray.vue +45 -17
  44. package/src/components/form/index.ts +0 -1
  45. package/src/components/form/inputs/FileUpload.vue +10 -6
  46. package/src/components/form/inputs/NumberInput.vue +3 -1
  47. package/src/components/form/inputs/RichText/index.vue +1 -1
  48. package/src/components/form/inputs/Upload/UploadInput.vue +12 -7
  49. package/src/components/index.ts +5 -1
  50. package/src/composables/useSchemaField.ts +193 -0
  51. package/src/styles/text.css +15 -11
  52. package/src/components/form/BglField.vue +0 -132
  53. package/src/components/form/BglForm.vue +0 -157
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import type { BglFormSchemaT, BglFormSchemaFnT, Field } from '@bagelink/vue'
3
3
  import type { VNode } from 'vue'
4
- import { BglForm, CheckInput, DateInput, FieldArray, FileUpload, NumberInput, RichText, SelectInput, TabsNav, TextInput, ToggleInput, UploadInput } from '@bagelink/vue'
5
4
  import { h, watch } from 'vue'
5
+ import { useSchemaField } from '../../composables/useSchemaField'
6
6
 
7
7
  interface Props {
8
8
  modelValue: Record<string, any>
@@ -39,30 +39,6 @@ const isDirty = $computed(() => {
39
39
  return current !== initial
40
40
  })
41
41
 
42
- function getComponent(field: Field) {
43
- const componentMap = {
44
- text: TextInput,
45
- textarea: TextInput,
46
- number: NumberInput,
47
- array: FieldArray,
48
- select: SelectInput,
49
- toggle: ToggleInput,
50
- check: CheckInput,
51
- richtext: RichText,
52
- upload: UploadInput,
53
- file: FileUpload,
54
- date: DateInput,
55
- tabs: TabsNav,
56
- bglform: BglForm
57
- }
58
-
59
- if (field.$el === 'textarea' && !field.attrs?.multiline) {
60
- field.attrs = { ...field.attrs, multiline: true }
61
- }
62
-
63
- return componentMap[field.$el as keyof typeof componentMap] ?? field.$el ?? 'div'
64
- }
65
-
66
42
  function updateFormData(fieldId: string, value: any) {
67
43
  formData = {
68
44
  ...formData,
@@ -76,82 +52,6 @@ function handleSubmit() {
76
52
  initialFormData = { ...formData }
77
53
  }
78
54
 
79
- function renderSchemaField(field: Field): VNode | null {
80
- const Component = getComponent(field)
81
- if (!Component) return null
82
-
83
- // Check if this field should be rendered based on v-if conditions
84
- if (!shouldRenderField(field)) return null
85
-
86
- const {
87
- $el,
88
- vIf,
89
- 'v-if': vIf2,
90
- children,
91
- options,
92
- attrs,
93
- class: fieldClass,
94
- id,
95
- onUpdate,
96
- // Common form field props that need special handling
97
- required,
98
- label,
99
- placeholder,
100
- disabled,
101
- ...fieldProps
102
- } = field
103
-
104
- const currentValue = field.id ? formData[field.id] : undefined
105
-
106
- const props: Record<string, any> = {
107
- ...fieldProps,
108
- required,
109
- label,
110
- placeholder,
111
- disabled,
112
- 'modelValue': currentValue,
113
- 'onUpdate:modelValue': (value: any) => {
114
- if (!field.id) return
115
- updateFormData(field.id, value)
116
- field.onUpdate?.(value, formData)
117
- }
118
- }
119
-
120
- // Remove undefined props to avoid vue warnings
121
- Object.keys(props).forEach(key => props[key] === undefined && delete props[key])
122
-
123
- // Add options if they exist in the field
124
- if (field.options) {
125
- props.options = typeof field.options === 'function'
126
- ? field.options(formData[field.id as string], formData)
127
- : field.options
128
- }
129
-
130
- // Handle dynamic props
131
- if (field.attrs) {
132
- Object.entries(field.attrs).forEach(([key, value]) => {
133
- props[key] = typeof value === 'function' ? value(formData[field.id as string], formData) : value
134
- })
135
- }
136
-
137
- if (field.class) {
138
- props.class = typeof field.class === 'function'
139
- ? field.class(formData[field.id as string], formData)
140
- : field.class
141
- }
142
-
143
- const slots = field.children?.length
144
- ? {
145
- default: () => field.children!
146
- .map(child => typeof child === 'string' ? child : renderSchemaField(child))
147
- .filter(Boolean),
148
- ...field.slots
149
- }
150
- : field.slots
151
-
152
- return h(Component as any, props, slots)
153
- }
154
-
155
55
  function shouldRenderField(field: Field): boolean {
156
56
  const condition = field.vIf ?? field['v-if']
157
57
  if (condition === undefined) return true
@@ -165,6 +65,21 @@ function validateForm() {
165
65
  return form.reportValidity()
166
66
  }
167
67
 
68
+ const { renderField } = useSchemaField<Record<string, any>>({
69
+ mode: 'form',
70
+ getRowData: () => formData,
71
+ onUpdate: (field, value) => {
72
+ if (!field.id) return
73
+ updateFormData(field.id, value)
74
+ field.onUpdate?.(value, formData)
75
+ }
76
+ })
77
+
78
+ function renderSchemaField(field: Field): VNode | null {
79
+ if (!shouldRenderField(field)) return null
80
+ return renderField(field)
81
+ }
82
+
168
83
  defineExpose({ form, isDirty, validateForm })
169
84
  </script>
170
85
 
@@ -7,7 +7,8 @@ import type {
7
7
  BglFormSchemaFnT,
8
8
  Field,
9
9
  } from '@bagelink/vue'
10
- import { BglForm, BglField, Btn } from '@bagelink/vue'
10
+ import { BagelForm, Btn } from '@bagelink/vue'
11
+ import { useSchemaField } from '../../composables/useSchemaField'
11
12
 
12
13
  const props = withDefaults(
13
14
  defineProps<{
@@ -70,6 +71,18 @@ const computedField = $computed(
70
71
  $el: props.el,
71
72
  }) as Field<T>
72
73
  ) as Field<Record<string, any>>
74
+
75
+ const { renderField } = useSchemaField<Record<string, any>>({
76
+ mode: 'form',
77
+ getRowData: () => data,
78
+ onUpdate: (field, value) => {
79
+ if (!field.id) return
80
+ const index = Number.parseInt(field.id)
81
+ if (Number.isNaN(index)) return
82
+ data[index] = value
83
+ emitValue()
84
+ }
85
+ })
73
86
  </script>
74
87
 
75
88
  <template>
@@ -78,34 +91,46 @@ const computedField = $computed(
78
91
  {{ label }}
79
92
  </p>
80
93
 
81
- <div v-if="schema" class="-ms-05 ps-05 border-start">
82
- <div v-for="(_, i) in data" :key="i" outline thin class="mb-05 itemBox transition p-05">
83
- <BglForm v-model="data[i]" :schema="schema" @update:model-value="emitValue" />
84
- <Btn
85
- v-if="props.delete"
86
- icon="delete"
87
- value="Delete"
88
- class="txt10 opacity-7 color-red"
89
- thin
90
- flat
91
- @click="deleteItem(i)"
92
- />
94
+ <div v-if="schema" class="ps-025 border-start">
95
+ <div v-for="(_, i) in data" :key="i" outline thin class="mb-05 itemBox transition ps-05 pb-025 pt-025 radius-05 gap-05 overflow-hidden">
96
+ <BagelForm v-model="data[i]" :schema="schema" @update:model-value="emitValue" />
97
+ <div class="bg-gray-80 -my-05 px-025 pt-065 txt-center">
98
+ <Btn
99
+ v-if="props.delete"
100
+ icon="delete"
101
+ class="txt10 opacity-7"
102
+ thin
103
+ flat
104
+ @click="deleteItem(i)"
105
+ />
106
+ </div>
93
107
  </div>
94
108
  <Btn v-if="add" thin icon="add" color="gray" class="txt12" @click="addItem">
95
- <p>Add {{ label }}</p>
109
+ <p>{{ label }}</p>
96
110
  </Btn>
97
111
  </div>
98
112
  <template v-else>
99
- <BglField v-for="(_, i) in data" :key="i" v-model="data[i]" :field="computedField" @update:model-value="emitValue" />
113
+ <component
114
+ :is="renderField({ ...computedField, id: String(i) })"
115
+ v-for="(_, i) in data"
116
+ :key="i"
117
+ @update:model-value="emitValue"
118
+ />
100
119
  </template>
101
120
  </div>
102
121
  </template>
103
122
 
104
123
  <style>
105
124
  .itemBox{
106
- /* border-top: 2px solid var(--bgl-gray); */
107
- /* border-bottom: 2px solid var(--bgl-gray); */
108
125
  background: var(--input-bg);
126
+ grid-template-columns: 1fr auto;
127
+ display: grid;
128
+ --label-font-size: 0.6rem;
129
+ --input-height: 30px;
130
+ --input-font-size: 14px;
131
+ }
132
+ .pt-065{
133
+ padding-top: 0.65rem;
109
134
  }
110
135
 
111
136
  .itemBox .bagel-input input,
@@ -114,4 +139,7 @@ const computedField = $computed(
114
139
  .itemBox .custom-select .input {
115
140
  background: var(--bgl-white) !important;
116
141
  }
142
+ .itemBox .bagel-input{
143
+ margin-bottom: 0.15rem !important;
144
+ }
117
145
  </style>
@@ -1,5 +1,4 @@
1
1
  export { default as BglForm } from './BagelForm.vue'
2
2
  export { default as BagelForm } from './BagelForm.vue'
3
- export { default as BglField } from './BglField.vue'
4
3
  export { default as FieldArray } from './FieldArray.vue'
5
4
  export * from './inputs'
@@ -43,6 +43,9 @@ const {
43
43
  accept?: string
44
44
  required?: boolean
45
45
  disabled?: boolean
46
+ dropPlaceholder?: string
47
+ noFilePlaceholder?: string
48
+ btnPlaceholder?: string
46
49
  }>()
47
50
 
48
51
  const emit = defineEmits(['update:modelValue', 'addFileStart'])
@@ -258,10 +261,9 @@ function drop(e: DragEvent) {
258
261
  class="px-1-5"
259
262
  icon="upload"
260
263
  outline
264
+ :value="btnPlaceholder || 'Upload'"
261
265
  @click="browse"
262
- >
263
- Upload
264
- </Btn>
266
+ />
265
267
 
266
268
  <div
267
269
  v-for="file in storageFiles"
@@ -304,7 +306,7 @@ function drop(e: DragEvent) {
304
306
  <Icon icon="draft" :size="1.5" />
305
307
  <p
306
308
  v-lightbox="{ src: file.url, download: true }"
307
- class="ellipsis-1 word-break-all h-20 m-0 color-black txt18"
309
+ class="ellipsis-1 word-break-all h-20 m-0 color-black txt16"
308
310
  >
309
311
  {{ file.name }}
310
312
  </p>
@@ -323,7 +325,7 @@ function drop(e: DragEvent) {
323
325
  v-if="!storageFiles.length && !fileQueue.length"
324
326
  class="txt-gray txt-12"
325
327
  >
326
- No file selected
328
+ {{ noFilePlaceholder || 'No file selected' }}
327
329
  </span>
328
330
  </Card>
329
331
  <div
@@ -493,7 +495,9 @@ function drop(e: DragEvent) {
493
495
  class="p-1 flex column hover fileUploadPlaceHolder justify-content-center mb-05"
494
496
  >
495
497
  <Icon icon="upload_2" />
496
- Drop files here or click to upload
498
+ <span class=" pretty balance">
499
+ {{ dropPlaceholder || 'Drag and Drop files here or click to upload' }}
500
+ </span>
497
501
  </p>
498
502
  </slot>
499
503
  </div>
@@ -103,7 +103,9 @@ watch(() => modelValue, (newVal) => {
103
103
  }"
104
104
  >
105
105
  <div :for="id">
106
- {{ label }}
106
+ <label>
107
+ {{ label }}
108
+ </label>
107
109
  <div class="gap-025" :class="{ 'column flex': layout === 'vertical', 'flex': layout === 'horizontal' }">
108
110
  <Btn v-if="layout && btnLayouts.includes(layout)" flat icon="add" class="radius" :class="[{ 'bgl-big-ctrl-num-btn': layout === 'vertical' }]" tabindex="-1" @click="increment" />
109
111
 
@@ -88,7 +88,7 @@ watch(() => editor.state.content, (newValue) => {
88
88
  <template>
89
89
  <div class="bagel-input">
90
90
  <label>{{ label }}</label>
91
- <div class="rich-text-editor rounded pt-05 px-05 pb-075 mb-05" :class="{ 'fullscreen-mode': editor.state.isFullscreen }">
91
+ <div class="rich-text-editor rounded pt-05 px-05 pb-075" :class="{ 'fullscreen-mode': editor.state.isFullscreen }">
92
92
  <EditorToolbar
93
93
  v-if="editor.state.hasInit" :config="toolbarConfig" :selectedStyles="editor.state.selectedStyles"
94
94
  @action="commands.execute"
@@ -18,6 +18,10 @@ const props = withDefaults(defineProps<{
18
18
  required?: boolean
19
19
  disabled?: boolean
20
20
  baseURL?: string
21
+ dropPlaceholder?: string
22
+ noFilePlaceholder?: string
23
+ btnPlaceholder?: string
24
+
21
25
  }>(), {
22
26
  height: '215px',
23
27
  theme: 'dropzone',
@@ -155,10 +159,9 @@ watch(() => props.dirPath, () => {
155
159
  class="px-1-5"
156
160
  icon="upload"
157
161
  outline
162
+ :value="btnPlaceholder || 'Upload'"
158
163
  @click="browse"
159
- >
160
- Upload
161
- </Btn>
164
+ />
162
165
 
163
166
  <template v-for="path_key in pathKeys" :key="path_key">
164
167
  <div class="txt-gray txt-12 flex">
@@ -188,7 +191,7 @@ watch(() => props.dirPath, () => {
188
191
  <Icon icon="draft" :size="1.5" />
189
192
  <p
190
193
  v-lightbox="{ src: pathToUrl(path_key), download: true }"
191
- class="ellipsis-1 word-break-all h-20 m-0 color-black txt18"
194
+ class="ellipsis-1 word-break-all h-20 m-0 color-black txt16"
192
195
  >
193
196
  {{ path_key.split('/').pop() }}
194
197
  </p>
@@ -201,7 +204,7 @@ watch(() => props.dirPath, () => {
201
204
  v-if="!pathKeys.length && !fileQueue.length"
202
205
  class="txt-gray txt-12"
203
206
  >
204
- No file selected
207
+ {{ noFilePlaceholder || 'No file selected' }}
205
208
  </span>
206
209
  </Card>
207
210
 
@@ -363,9 +366,11 @@ watch(() => props.dirPath, () => {
363
366
  :fileQueue
364
367
  :browse
365
368
  >
366
- <p class="p-1 flex column hover fileUploadPlaceHolder justify-content-center mb-05">
369
+ <p class="p-1 flex column hover fileUploadPlaceHolder justify-content-center mb-05 ">
367
370
  <Icon icon="upload_2" />
368
- Drop files here or click to upload
371
+ <span class=" pretty balance">
372
+ {{ dropPlaceholder || 'Drag and Drop files here or click to upload' }}
373
+ </span>
369
374
  </p>
370
375
  </slot>
371
376
  </div>
@@ -12,7 +12,9 @@ export { default as Carousel } from './Carousel.vue'
12
12
  export * from './dashboard'
13
13
  export { default as DataPreview } from './DataPreview.vue'
14
14
  export { default as DataTable } from './DataTable/DataTable.vue'
15
+ /** @deprecated Use DataTable instead of TableSchema. They are the same component. */
15
16
  export { default as TableSchema } from './DataTable/DataTable.vue'
17
+ export { Draggable, useDraggable, vDraggable } from './Draggable'
16
18
  export { default as Dropdown } from './Dropdown.vue'
17
19
  export { default as FieldSetVue } from './FieldSetVue.vue'
18
20
  export { default as Flag } from './Flag.vue'
@@ -22,15 +24,16 @@ export { default as IframeVue } from './IframeVue.vue'
22
24
  export { default as Image } from './Image.vue'
23
25
  export * from './layout'
24
26
  export { default as ListItem } from './ListItem.vue'
27
+
25
28
  export { default as ListView } from './ListView.vue'
26
29
  export { default as Loading } from './Loading.vue'
27
-
28
30
  export { default as MapEmbed } from './MapEmbed.vue'
29
31
  export { default as Modal } from './Modal.vue'
30
32
  export { default as ModalConfirm } from './ModalConfirm.vue'
31
33
  export { default as ModalForm } from './ModalForm.vue'
32
34
  export { default as NavBar } from './NavBar.vue'
33
35
  export { default as PageTitle } from './PageTitle.vue'
36
+
34
37
  export { default as Pill } from './Pill.vue'
35
38
  export { default as RouterWrapper } from './RouterWrapper.vue'
36
39
 
@@ -38,4 +41,5 @@ export { default as Title } from './Title.vue'
38
41
  export { default as ToolBar } from './ToolBar.vue'
39
42
 
40
43
  export { default as TopBar } from './TopBar.vue'
44
+
41
45
  export { default as Zoomer } from './Zoomer.vue'
@@ -0,0 +1,193 @@
1
+ import type { VNode } from 'vue'
2
+ import type { BaseBagelField } from '../types/BagelForm'
3
+ import {
4
+ TextInput,
5
+ NumberInput,
6
+ FieldArray,
7
+ SelectInput,
8
+ ToggleInput,
9
+ CheckInput,
10
+ RichText,
11
+ UploadInput,
12
+ FileUpload,
13
+ DateInput,
14
+ TabsNav,
15
+ BglForm,
16
+ bindAttrs,
17
+ classify,
18
+ keyToLabel
19
+ } from '@bagelink/vue'
20
+ import { h } from 'vue'
21
+
22
+ const SLOT_VALUE_COMPONENTS = new Set(['div', 'span', 'p'])
23
+
24
+ const SRC_VALUE_COMPONENTS = new Set(['img', 'iframe'])
25
+
26
+ export interface UseSchemaFieldOptions<T> {
27
+ mode?: 'form' | 'preview' | 'table'
28
+ getRowData?: () => T
29
+ onUpdate?: (field: BaseBagelField<T>, value: any) => void
30
+ }
31
+
32
+ export function useSchemaField<T extends Record<string, any>>(options: UseSchemaFieldOptions<T>) {
33
+ const { mode = 'form', getRowData, onUpdate } = options
34
+
35
+ function getComponent(field: BaseBagelField<T>) {
36
+ const componentMap = {
37
+ text: TextInput,
38
+ textarea: TextInput,
39
+ number: NumberInput,
40
+ array: FieldArray,
41
+ select: SelectInput,
42
+ toggle: ToggleInput,
43
+ check: CheckInput,
44
+ richtext: RichText,
45
+ upload: UploadInput,
46
+ file: FileUpload,
47
+ date: DateInput,
48
+ tabs: TabsNav,
49
+ form: BglForm
50
+ }
51
+
52
+ if (field.$el === 'textarea' && !field.attrs?.multiline) {
53
+ field.attrs = { ...field.attrs, multiline: true }
54
+ }
55
+
56
+ return typeof field.$el === 'object' ? field.$el : componentMap[field.$el as keyof typeof componentMap] ?? field.$el ?? 'div'
57
+ }
58
+
59
+ function renderField(
60
+ field: BaseBagelField<T>,
61
+ slots?: Record<string, (props: { row: T, field: BaseBagelField<T> }) => any>
62
+ ): VNode | null {
63
+ const Component = getComponent(field)
64
+ if (!Component) return null
65
+
66
+ const rowData = getRowData?.() || {} as T
67
+
68
+ const {
69
+ $el,
70
+ children,
71
+ options,
72
+ attrs,
73
+ class: fieldClass,
74
+ id,
75
+ transform,
76
+ slots: fieldSlots,
77
+ required,
78
+ label,
79
+ placeholder,
80
+ disabled,
81
+ ...fieldProps
82
+ } = field
83
+
84
+ const currentValue = field.id ? rowData[field.id as keyof T] : undefined
85
+ const transformedValue = transform ? transform(currentValue, rowData) : currentValue
86
+
87
+ // First bind any function attributes with the current value and row data
88
+ const boundFieldProps = bindAttrs(fieldProps, currentValue, rowData)
89
+
90
+ // Check if this component should receive value as slot
91
+ const isSlotValueComponent = typeof Component === 'string' && SLOT_VALUE_COMPONENTS.has(Component)
92
+ // Check if this component should receive value as src
93
+ const isSrcValueComponent = typeof Component === 'string' && SRC_VALUE_COMPONENTS.has(Component)
94
+
95
+ const props: Record<string, any> = {
96
+ ...boundFieldProps,
97
+ required,
98
+ label,
99
+ placeholder,
100
+ disabled,
101
+ }
102
+
103
+ // For form mode, always use the original value for modelValue
104
+ if (mode === 'form') {
105
+ props.modelValue = currentValue
106
+ props['onUpdate:modelValue'] = (value: any) => {
107
+ onUpdate?.(field, value)
108
+ }
109
+ } else {
110
+ // For display modes (table/preview), use transformed value based on component type
111
+ if (isSlotValueComponent) {
112
+ // Slot components don't need a value prop
113
+ } else if (isSrcValueComponent) {
114
+ props.src = transformedValue
115
+ } else {
116
+ props.modelValue = transformedValue
117
+ }
118
+ }
119
+
120
+ // Remove undefined props to avoid vue warnings
121
+ Object.keys(props).forEach(key => props[key] === undefined && delete props[key])
122
+
123
+ // Add options if they exist in the field
124
+ if (field.options) {
125
+ props.options = typeof field.options === 'function'
126
+ ? field.options(currentValue, rowData)
127
+ : field.options
128
+ }
129
+
130
+ // Handle dynamic props and attrs
131
+ if (field.attrs) {
132
+ const boundAttrs = bindAttrs(field.attrs, currentValue, rowData)
133
+ Object.entries(boundAttrs).forEach(([key, value]) => {
134
+ if (typeof value === 'function') {
135
+ props[key] = value(currentValue, rowData)
136
+ } else {
137
+ props[key] = value
138
+ }
139
+ })
140
+ }
141
+
142
+ // Handle class binding last to ensure all transformations are applied
143
+ props.class = classify(currentValue, rowData, fieldClass, props.class)
144
+
145
+ // Handle component slots
146
+ const componentSlots: Record<string, any> = {}
147
+
148
+ // Add default slot if there are children
149
+ if (children?.length) {
150
+ componentSlots.default = () => children.map((child) => {
151
+ if (typeof child === 'string') return child
152
+ return renderField(child, slots)
153
+ })
154
+ }
155
+
156
+ // For slot value components, add the transformed value as default slot content
157
+ if (isSlotValueComponent && transformedValue !== undefined) {
158
+ componentSlots.default = () => transformedValue?.toString() || ''
159
+ }
160
+
161
+ // Add any custom slots from the field
162
+ if (fieldSlots) {
163
+ Object.entries(fieldSlots).forEach(([name, slot]) => {
164
+ componentSlots[name] = typeof slot === 'function'
165
+ ? () => slot(currentValue, rowData)
166
+ : () => slot
167
+ })
168
+ }
169
+
170
+ // Handle custom slot content from parent
171
+ const slotContent = field.id && slots?.[field.id]
172
+ ? slots[field.id]({ row: rowData, field })
173
+ : undefined
174
+
175
+ if (mode === 'preview') {
176
+ return h('div', { class: 'preview-field' }, [
177
+ h('div', { class: 'field-label' }, field.label || keyToLabel(field.id || '')),
178
+ h('div', { class: 'field-value' }, [
179
+ slotContent || (typeof field.$el === 'object'
180
+ ? h(Component as any, props, componentSlots)
181
+ : transformedValue?.toString() || '')
182
+ ])
183
+ ])
184
+ }
185
+
186
+ return slotContent || h(Component as any, props, componentSlots)
187
+ }
188
+
189
+ return {
190
+ renderField,
191
+ getComponent
192
+ }
193
+ }
@@ -114,7 +114,6 @@
114
114
  font-size: 100px;
115
115
  }
116
116
 
117
-
118
117
  .txt110,
119
118
  .txt-110 {
120
119
  font-size: 110px;
@@ -140,7 +139,6 @@
140
139
  font-size: 150px;
141
140
  }
142
141
 
143
-
144
142
  .txt18,
145
143
  .txt-18 {
146
144
  font-size: 18px;
@@ -357,6 +355,18 @@
357
355
  cursor: pointer;
358
356
  }
359
357
 
358
+ .grab {
359
+ cursor: grab;
360
+ }
361
+
362
+ .grab:active {
363
+ cursor: grabbing;
364
+ }
365
+
366
+ .not-allowed {
367
+ cursor: not-allowed;
368
+ }
369
+
360
370
  .decoration-none {
361
371
  text-decoration: none;
362
372
  }
@@ -387,7 +397,7 @@
387
397
  }
388
398
 
389
399
  .bgl_icon-font {
390
- font-family: "Material Symbols Outlined", serif !important;
400
+ font-family: 'Material Symbols Outlined', serif !important;
391
401
  }
392
402
 
393
403
  .nowrap {
@@ -406,9 +416,7 @@
406
416
  white-space: nowrap;
407
417
  }
408
418
 
409
-
410
419
  @media screen and (max-width: 910px) {
411
-
412
420
  .txt20,
413
421
  .txt-20 {
414
422
  font-size: 18px;
@@ -525,7 +533,6 @@
525
533
  font-size: 72px;
526
534
  }
527
535
 
528
-
529
536
  .m_txt80,
530
537
  .m_txt-80 {
531
538
  font-size: 80px;
@@ -541,7 +548,6 @@
541
548
  font-size: 100px;
542
549
  }
543
550
 
544
-
545
551
  .m_txt110,
546
552
  .m_txt-110 {
547
553
  font-size: 110px;
@@ -792,9 +798,8 @@
792
798
  text-decoration: underline !important;
793
799
  }
794
800
 
795
-
796
801
  .m_bgl_icon-font {
797
- font-family: "Material Symbols Outlined", serif;
802
+ font-family: 'Material Symbols Outlined', serif;
798
803
  }
799
804
 
800
805
  .m_nowrap {
@@ -828,5 +833,4 @@
828
833
  .m_capitalize {
829
834
  text-transform: capitalize;
830
835
  }
831
-
832
- }
836
+ }