@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.
- package/dist/components/DataPreview.vue.d.ts +12 -35
- package/dist/components/DataPreview.vue.d.ts.map +1 -1
- package/dist/components/DataTable/DataTable.vue.d.ts +1 -1
- package/dist/components/DataTable/DataTable.vue.d.ts.map +1 -1
- package/dist/components/DataTable/useTableData.d.ts +10 -2
- package/dist/components/DataTable/useTableData.d.ts.map +1 -1
- package/dist/components/Draggable/Draggable.vue.d.ts +45 -0
- package/dist/components/Draggable/Draggable.vue.d.ts.map +1 -0
- package/dist/components/Draggable/index.d.ts +5 -0
- package/dist/components/Draggable/index.d.ts.map +1 -0
- package/dist/components/Draggable/useDraggable.d.ts +31 -0
- package/dist/components/Draggable/useDraggable.d.ts.map +1 -0
- package/dist/components/Draggable/vDraggable.d.ts +4 -0
- package/dist/components/Draggable/vDraggable.d.ts.map +1 -0
- package/dist/components/ListView.vue.d.ts.map +1 -1
- package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
- package/dist/components/form/FieldArray.vue.d.ts.map +1 -1
- package/dist/components/form/index.d.ts +0 -1
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/inputs/FileUpload.vue.d.ts +3 -0
- package/dist/components/form/inputs/FileUpload.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/NumberInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts +3 -0
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/composables/useSchemaField.d.ts +15 -0
- package/dist/composables/useSchemaField.d.ts.map +1 -0
- package/dist/index.cjs +1256 -802
- package/dist/index.mjs +1258 -804
- package/dist/style.css +272 -285
- package/package.json +1 -1
- package/src/components/DataPreview.vue +45 -116
- package/src/components/DataTable/DataTable.vue +18 -12
- package/src/components/DataTable/useTableData.ts +50 -16
- package/src/components/Draggable/Draggable.vue +64 -0
- package/src/components/Draggable/index.ts +4 -0
- package/src/components/Draggable/useDraggable.ts +632 -0
- package/src/components/Draggable/vDraggable.ts +17 -0
- package/src/components/ListView.vue +6 -2
- package/src/components/Pill.vue +1 -1
- package/src/components/form/BagelForm.vue +16 -101
- package/src/components/form/FieldArray.vue +45 -17
- package/src/components/form/index.ts +0 -1
- package/src/components/form/inputs/FileUpload.vue +10 -6
- package/src/components/form/inputs/NumberInput.vue +3 -1
- package/src/components/form/inputs/RichText/index.vue +1 -1
- package/src/components/form/inputs/Upload/UploadInput.vue +12 -7
- package/src/components/index.ts +5 -1
- package/src/composables/useSchemaField.ts +193 -0
- package/src/styles/text.css +15 -11
- package/src/components/form/BglField.vue +0 -132
- 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 {
|
|
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="
|
|
82
|
-
<div v-for="(_, i) in data" :key="i" outline thin class="mb-05 itemBox transition
|
|
83
|
-
<
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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>
|
|
109
|
+
<p>{{ label }}</p>
|
|
96
110
|
</Btn>
|
|
97
111
|
</div>
|
|
98
112
|
<template v-else>
|
|
99
|
-
<
|
|
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>
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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>
|
package/src/components/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/styles/text.css
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
+
}
|