@bagelink/vue 1.12.38 → 1.12.43
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/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/Slider.vue.d.ts.map +1 -1
- package/dist/components/Spreadsheet/Index.vue.d.ts.map +1 -1
- package/dist/components/Spreadsheet/SpreadsheetCell.vue.d.ts.map +1 -1
- package/dist/components/Spreadsheet/SpreadsheetTable.vue.d.ts.map +1 -1
- package/dist/components/calendar/views/WeekView.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/dialog/DialogForm.vue.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/form-flow/MultiStepForm.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/form-flow/schema-fields.d.ts +127 -0
- package/dist/form-flow/schema-fields.d.ts.map +1 -0
- package/dist/index.cjs +73 -73
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +7912 -7693
- package/dist/style.css +1 -1
- package/dist/utils/useSearch.d.ts.map +1 -1
- package/package.json +102 -101
- package/src/components/Slider.vue +2 -1
- package/src/components/Spreadsheet/Index.vue +3 -3
- package/src/components/Spreadsheet/SpreadsheetCell.vue +1 -1
- package/src/components/calendar/views/WeekView.vue +20 -5
- package/src/components/form/inputs/RadioGroup.vue +1 -0
- package/src/components/form/inputs/TextInput.vue +3 -3
- package/src/components/layout/AppSidebar.vue +8 -7
- package/src/dialog/DialogForm.vue +1 -1
- package/src/form-flow/FormFlow.vue +7 -5
- package/src/form-flow/MultiStepForm.vue +6 -3
- package/src/form-flow/form-flow.ts +4 -0
- package/src/form-flow/schema-fields.ts +470 -0
- package/src/index.ts +2 -0
- package/src/styles/scrollbar.css +7 -1
- package/src/styles/text.css +8 -0
- package/src/utils/useSearch.ts +1 -1
- package/LICENSE +0 -21
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Fields — JSON Schema ↔ FormField conversion utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides bidirectional conversion between JSON Schema (with x- layout extensions)
|
|
5
|
+
* and the FormField representation used by form renderers and builders.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { computed, type Ref } from 'vue'
|
|
9
|
+
|
|
10
|
+
// ─── Field Types ──────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export type FormFieldType =
|
|
13
|
+
| 'text' | 'textarea' | 'richtext' | 'markdown'
|
|
14
|
+
| 'number' | 'date' | 'email' | 'phone' | 'url'
|
|
15
|
+
| 'select' | 'radio' | 'checkbox' | 'toggle'
|
|
16
|
+
| 'file' | 'signature' | 'range' | 'color' | 'json'
|
|
17
|
+
| 'heading' | 'divider'
|
|
18
|
+
|
|
19
|
+
export interface SelectOption {
|
|
20
|
+
label: string
|
|
21
|
+
value: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface FormFieldValidation {
|
|
25
|
+
minLength?: number
|
|
26
|
+
maxLength?: number
|
|
27
|
+
min?: number
|
|
28
|
+
max?: number
|
|
29
|
+
step?: number
|
|
30
|
+
pattern?: string
|
|
31
|
+
options?: SelectOption[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FormField {
|
|
35
|
+
id: string
|
|
36
|
+
key: string
|
|
37
|
+
type: FormFieldType
|
|
38
|
+
label: string
|
|
39
|
+
placeholder?: string
|
|
40
|
+
description?: string
|
|
41
|
+
required?: boolean
|
|
42
|
+
colSpan?: number
|
|
43
|
+
colStart?: number
|
|
44
|
+
rowSpan?: number
|
|
45
|
+
rowStart?: number
|
|
46
|
+
mColSpan?: number
|
|
47
|
+
mColStart?: number
|
|
48
|
+
mRowSpan?: number
|
|
49
|
+
mRowStart?: number
|
|
50
|
+
hideLabel?: boolean
|
|
51
|
+
richTextVariant?: 'full' | 'basic' | 'simple'
|
|
52
|
+
markdownShowFormatting?: boolean
|
|
53
|
+
textareaRows?: number
|
|
54
|
+
textareaAutoHeight?: boolean
|
|
55
|
+
numberLayout?: 'default' | 'vertical' | 'horizontal'
|
|
56
|
+
numberSpinner?: boolean
|
|
57
|
+
phoneOnlyCountries?: string
|
|
58
|
+
selectMultiselect?: boolean
|
|
59
|
+
fileMultiple?: boolean
|
|
60
|
+
fileHeight?: number
|
|
61
|
+
fileAccept?: string
|
|
62
|
+
fileTheme?: 'dropzone' | 'basic'
|
|
63
|
+
fileShowIcon?: boolean
|
|
64
|
+
fileIcon?: string
|
|
65
|
+
dateEnableTime?: boolean
|
|
66
|
+
dateMin?: string
|
|
67
|
+
dateMax?: string
|
|
68
|
+
rangeMulti?: boolean
|
|
69
|
+
radioThin?: boolean
|
|
70
|
+
radioHideRadio?: boolean
|
|
71
|
+
radioAlign?: 'start' | 'center' | 'end'
|
|
72
|
+
textIcon?: string
|
|
73
|
+
textIconStart?: string
|
|
74
|
+
validation?: FormFieldValidation
|
|
75
|
+
defaultValue?: any
|
|
76
|
+
content?: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface JSONSchemaProperty {
|
|
80
|
+
type?: string
|
|
81
|
+
title?: string
|
|
82
|
+
description?: string
|
|
83
|
+
format?: string
|
|
84
|
+
enum?: any[]
|
|
85
|
+
minimum?: number
|
|
86
|
+
maximum?: number
|
|
87
|
+
minLength?: number
|
|
88
|
+
maxLength?: number
|
|
89
|
+
pattern?: string
|
|
90
|
+
multipleOf?: number
|
|
91
|
+
default?: any
|
|
92
|
+
'x-col-span'?: number
|
|
93
|
+
'x-col-start'?: number
|
|
94
|
+
'x-row-span'?: number
|
|
95
|
+
'x-row-start'?: number
|
|
96
|
+
'x-m-col-span'?: number
|
|
97
|
+
'x-m-col-start'?: number
|
|
98
|
+
'x-m-row-span'?: number
|
|
99
|
+
'x-m-row-start'?: number
|
|
100
|
+
'x-field-type'?: FormFieldType
|
|
101
|
+
'x-placeholder'?: string
|
|
102
|
+
'x-options'?: SelectOption[]
|
|
103
|
+
'x-rich-text-variant'?: 'full' | 'basic' | 'simple'
|
|
104
|
+
'x-textarea-rows'?: number
|
|
105
|
+
'x-textarea-autoheight'?: boolean
|
|
106
|
+
'x-number-layout'?: 'default' | 'vertical' | 'horizontal'
|
|
107
|
+
'x-number-spinner'?: boolean
|
|
108
|
+
'x-phone-only-countries'?: string
|
|
109
|
+
'x-select-multiselect'?: boolean
|
|
110
|
+
'x-file-multiple'?: boolean
|
|
111
|
+
'x-file-height'?: number
|
|
112
|
+
'x-file-accept'?: string
|
|
113
|
+
'x-file-theme'?: 'dropzone' | 'basic'
|
|
114
|
+
'x-file-show-icon'?: boolean
|
|
115
|
+
'x-file-icon'?: string
|
|
116
|
+
'x-date-enable-time'?: boolean
|
|
117
|
+
'x-date-min'?: string
|
|
118
|
+
'x-date-max'?: string
|
|
119
|
+
'x-range-multi'?: boolean
|
|
120
|
+
'x-radio-thin'?: boolean
|
|
121
|
+
'x-radio-hide-radio'?: boolean
|
|
122
|
+
'x-radio-align'?: 'start' | 'center' | 'end'
|
|
123
|
+
'x-text-icon'?: string
|
|
124
|
+
'x-text-icon-start'?: string
|
|
125
|
+
'x-hide-label'?: boolean
|
|
126
|
+
'x-markdown-show-formatting'?: boolean
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface JSONSchemaObject {
|
|
130
|
+
$schema?: string
|
|
131
|
+
type: 'object'
|
|
132
|
+
properties?: Record<string, JSONSchemaProperty>
|
|
133
|
+
required?: string[]
|
|
134
|
+
'x-dynamic-height'?: boolean
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
export function defaultRowSpan(type: FormFieldType): number {
|
|
140
|
+
if (type === 'richtext') return 3
|
|
141
|
+
if (type === 'textarea' || type === 'json' || type === 'radio' || type === 'file' || type === 'signature') return 2
|
|
142
|
+
return 1
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function inferFieldType(prop: JSONSchemaProperty): FormFieldType {
|
|
146
|
+
if (prop.format) {
|
|
147
|
+
switch (prop.format) {
|
|
148
|
+
case 'email': return 'email'
|
|
149
|
+
case 'tel': return 'phone'
|
|
150
|
+
case 'uri':
|
|
151
|
+
case 'url': return 'url'
|
|
152
|
+
case 'date':
|
|
153
|
+
case 'date-time': return 'date'
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (prop.enum) return 'select'
|
|
157
|
+
switch (prop.type) {
|
|
158
|
+
case 'number':
|
|
159
|
+
case 'integer': return 'number'
|
|
160
|
+
case 'boolean': return 'checkbox'
|
|
161
|
+
}
|
|
162
|
+
return 'text'
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─── Schema → Fields ─────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
export function schemaToFields(schema: JSONSchemaObject | undefined): FormField[] {
|
|
168
|
+
if (!schema?.properties) return []
|
|
169
|
+
|
|
170
|
+
const requiredSet = new Set(schema.required ?? [])
|
|
171
|
+
const fields: FormField[] = []
|
|
172
|
+
|
|
173
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
174
|
+
const type = prop['x-field-type'] ?? inferFieldType(prop)
|
|
175
|
+
|
|
176
|
+
const validation: FormFieldValidation = {}
|
|
177
|
+
if (prop.minLength !== undefined) validation.minLength = prop.minLength
|
|
178
|
+
if (prop.maxLength !== undefined) validation.maxLength = prop.maxLength
|
|
179
|
+
if (prop.minimum !== undefined) validation.min = prop.minimum
|
|
180
|
+
if (prop.maximum !== undefined) validation.max = prop.maximum
|
|
181
|
+
if (prop.multipleOf !== undefined) validation.step = prop.multipleOf
|
|
182
|
+
if (prop.pattern !== undefined) validation.pattern = prop.pattern
|
|
183
|
+
if (prop['x-options']) {
|
|
184
|
+
validation.options = prop['x-options']
|
|
185
|
+
} else if (prop.enum) {
|
|
186
|
+
validation.options = prop.enum.map((v: any) => ({ label: String(v), value: String(v) }))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const field: FormField = {
|
|
190
|
+
id: crypto.randomUUID(),
|
|
191
|
+
key,
|
|
192
|
+
type,
|
|
193
|
+
label: prop.title ?? key,
|
|
194
|
+
placeholder: prop['x-placeholder'],
|
|
195
|
+
description: prop.description,
|
|
196
|
+
required: requiredSet.has(key) || undefined,
|
|
197
|
+
colSpan: prop['x-col-span'] ?? 12,
|
|
198
|
+
colStart: prop['x-col-start'],
|
|
199
|
+
rowSpan: prop['x-row-span'] ?? defaultRowSpan(type),
|
|
200
|
+
rowStart: prop['x-row-start'],
|
|
201
|
+
mColSpan: prop['x-m-col-span'] ?? 12,
|
|
202
|
+
mColStart: prop['x-m-col-start'],
|
|
203
|
+
mRowSpan: prop['x-m-row-span'],
|
|
204
|
+
mRowStart: prop['x-m-row-start'],
|
|
205
|
+
hideLabel: prop['x-hide-label'] ?? false,
|
|
206
|
+
richTextVariant: prop['x-rich-text-variant'],
|
|
207
|
+
markdownShowFormatting: prop['x-markdown-show-formatting'],
|
|
208
|
+
textareaRows: prop['x-textarea-rows'],
|
|
209
|
+
textareaAutoHeight: prop['x-textarea-autoheight'],
|
|
210
|
+
numberLayout: prop['x-number-layout'],
|
|
211
|
+
numberSpinner: prop['x-number-spinner'] ?? true,
|
|
212
|
+
phoneOnlyCountries: prop['x-phone-only-countries'],
|
|
213
|
+
selectMultiselect: prop['x-select-multiselect'],
|
|
214
|
+
fileMultiple: prop['x-file-multiple'],
|
|
215
|
+
fileHeight: prop['x-file-height'],
|
|
216
|
+
fileAccept: prop['x-file-accept'],
|
|
217
|
+
fileTheme: prop['x-file-theme'],
|
|
218
|
+
fileShowIcon: prop['x-file-show-icon'] === false ? false : undefined,
|
|
219
|
+
fileIcon: prop['x-file-icon'],
|
|
220
|
+
dateEnableTime: prop['x-date-enable-time'],
|
|
221
|
+
dateMin: prop['x-date-min'],
|
|
222
|
+
dateMax: prop['x-date-max'],
|
|
223
|
+
rangeMulti: prop['x-range-multi'],
|
|
224
|
+
radioThin: prop['x-radio-thin'],
|
|
225
|
+
radioHideRadio: prop['x-radio-hide-radio'],
|
|
226
|
+
radioAlign: prop['x-radio-align'],
|
|
227
|
+
textIcon: prop['x-text-icon'],
|
|
228
|
+
textIconStart: prop['x-text-icon-start'],
|
|
229
|
+
validation: Object.keys(validation).length > 0 ? validation : undefined,
|
|
230
|
+
defaultValue: prop.default,
|
|
231
|
+
content: prop['x-field-type'] === 'heading' ? prop.title : undefined,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fields.push(field)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return fields
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── Fields → Schema ─────────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
export function fieldsToSchema(fields: FormField[], options?: { dynamicHeight?: boolean }): JSONSchemaObject {
|
|
243
|
+
const properties: Record<string, JSONSchemaProperty> = {}
|
|
244
|
+
const required: string[] = []
|
|
245
|
+
|
|
246
|
+
for (const field of fields) {
|
|
247
|
+
const prop: JSONSchemaProperty = {}
|
|
248
|
+
|
|
249
|
+
// Layout-only fields
|
|
250
|
+
if (field.type === 'heading' || field.type === 'divider') {
|
|
251
|
+
prop.type = 'string'
|
|
252
|
+
prop.title = field.content || field.label
|
|
253
|
+
prop['x-field-type'] = field.type
|
|
254
|
+
} else {
|
|
255
|
+
// Map field type to JSON Schema type
|
|
256
|
+
switch (field.type) {
|
|
257
|
+
case 'text':
|
|
258
|
+
case 'textarea':
|
|
259
|
+
case 'richtext':
|
|
260
|
+
case 'markdown':
|
|
261
|
+
case 'email':
|
|
262
|
+
case 'phone':
|
|
263
|
+
case 'url':
|
|
264
|
+
case 'date':
|
|
265
|
+
case 'file':
|
|
266
|
+
case 'signature':
|
|
267
|
+
case 'color':
|
|
268
|
+
prop.type = 'string'
|
|
269
|
+
break
|
|
270
|
+
case 'number':
|
|
271
|
+
case 'range':
|
|
272
|
+
prop.type = 'number'
|
|
273
|
+
break
|
|
274
|
+
case 'checkbox':
|
|
275
|
+
case 'toggle':
|
|
276
|
+
prop.type = 'boolean'
|
|
277
|
+
break
|
|
278
|
+
case 'json':
|
|
279
|
+
prop.type = 'object'
|
|
280
|
+
break
|
|
281
|
+
case 'select':
|
|
282
|
+
case 'radio':
|
|
283
|
+
prop.type = 'string'
|
|
284
|
+
if (field.validation?.options) {
|
|
285
|
+
prop.enum = field.validation.options.map(o => o.value)
|
|
286
|
+
}
|
|
287
|
+
break
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Format hints
|
|
291
|
+
if (field.type === 'email') prop.format = 'email'
|
|
292
|
+
if (field.type === 'phone') prop.format = 'tel'
|
|
293
|
+
if (field.type === 'url') prop.format = 'uri'
|
|
294
|
+
if (field.type === 'date') prop.format = field.dateEnableTime ? 'date-time' : 'date'
|
|
295
|
+
if (field.type === 'color') prop.format = 'color'
|
|
296
|
+
|
|
297
|
+
// Title & description
|
|
298
|
+
prop.title = field.label
|
|
299
|
+
if (field.description) prop.description = field.description
|
|
300
|
+
if (field.defaultValue !== undefined) prop.default = field.defaultValue
|
|
301
|
+
|
|
302
|
+
// x-field-type (skip for types that can be inferred)
|
|
303
|
+
if (field.type !== 'text') {
|
|
304
|
+
prop['x-field-type'] = field.type
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Validation constraints
|
|
308
|
+
if (field.validation?.minLength !== undefined) prop.minLength = field.validation.minLength
|
|
309
|
+
if (field.validation?.maxLength !== undefined) prop.maxLength = field.validation.maxLength
|
|
310
|
+
if (field.validation?.min !== undefined) prop.minimum = field.validation.min
|
|
311
|
+
if (field.validation?.max !== undefined) prop.maximum = field.validation.max
|
|
312
|
+
if (field.validation?.step !== undefined) prop.multipleOf = field.validation.step
|
|
313
|
+
if (field.validation?.pattern !== undefined) prop.pattern = field.validation.pattern
|
|
314
|
+
|
|
315
|
+
// Required
|
|
316
|
+
if (field.required) required.push(field.key)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Placeholder
|
|
320
|
+
if (field.placeholder) prop['x-placeholder'] = field.placeholder
|
|
321
|
+
|
|
322
|
+
// Options
|
|
323
|
+
if (field.validation?.options) prop['x-options'] = field.validation.options
|
|
324
|
+
|
|
325
|
+
// Layout extensions — only write when non-default
|
|
326
|
+
if (field.colSpan !== undefined && field.colSpan !== 12) prop['x-col-span'] = field.colSpan
|
|
327
|
+
if (field.colStart !== undefined) prop['x-col-start'] = field.colStart
|
|
328
|
+
if (field.rowSpan !== undefined && field.rowSpan !== defaultRowSpan(field.type)) prop['x-row-span'] = field.rowSpan
|
|
329
|
+
if (field.rowStart !== undefined) prop['x-row-start'] = field.rowStart
|
|
330
|
+
if (field.mColSpan !== undefined && field.mColSpan !== 12) prop['x-m-col-span'] = field.mColSpan
|
|
331
|
+
if (field.mColStart !== undefined) prop['x-m-col-start'] = field.mColStart
|
|
332
|
+
if (field.mRowSpan !== undefined) prop['x-m-row-span'] = field.mRowSpan
|
|
333
|
+
if (field.mRowStart !== undefined) prop['x-m-row-start'] = field.mRowStart
|
|
334
|
+
|
|
335
|
+
// Boolean/enum x- extensions — only write when non-default
|
|
336
|
+
if (field.hideLabel === true) prop['x-hide-label'] = true
|
|
337
|
+
if (field.richTextVariant) prop['x-rich-text-variant'] = field.richTextVariant
|
|
338
|
+
if (field.markdownShowFormatting === true) prop['x-markdown-show-formatting'] = true
|
|
339
|
+
if (field.textareaRows !== undefined) prop['x-textarea-rows'] = field.textareaRows
|
|
340
|
+
if (field.textareaAutoHeight === true) prop['x-textarea-autoheight'] = true
|
|
341
|
+
if (field.numberLayout && field.numberLayout !== 'default') prop['x-number-layout'] = field.numberLayout
|
|
342
|
+
if (field.numberSpinner === false) prop['x-number-spinner'] = false
|
|
343
|
+
if (field.phoneOnlyCountries) prop['x-phone-only-countries'] = field.phoneOnlyCountries
|
|
344
|
+
if (field.selectMultiselect === true) prop['x-select-multiselect'] = true
|
|
345
|
+
if (field.fileMultiple === true) prop['x-file-multiple'] = true
|
|
346
|
+
if (field.fileHeight !== undefined) prop['x-file-height'] = field.fileHeight
|
|
347
|
+
if (field.fileAccept) prop['x-file-accept'] = field.fileAccept
|
|
348
|
+
if (field.fileTheme) prop['x-file-theme'] = field.fileTheme
|
|
349
|
+
if (field.fileShowIcon === false) prop['x-file-show-icon'] = false
|
|
350
|
+
if (field.fileIcon) prop['x-file-icon'] = field.fileIcon
|
|
351
|
+
if (field.dateEnableTime === true) prop['x-date-enable-time'] = true
|
|
352
|
+
if (field.dateMin) prop['x-date-min'] = field.dateMin
|
|
353
|
+
if (field.dateMax) prop['x-date-max'] = field.dateMax
|
|
354
|
+
if (field.rangeMulti === true) prop['x-range-multi'] = true
|
|
355
|
+
if (field.radioThin === true) prop['x-radio-thin'] = true
|
|
356
|
+
if (field.radioHideRadio === true) prop['x-radio-hide-radio'] = true
|
|
357
|
+
if (field.radioAlign) prop['x-radio-align'] = field.radioAlign
|
|
358
|
+
if (field.textIcon) prop['x-text-icon'] = field.textIcon
|
|
359
|
+
if (field.textIconStart) prop['x-text-icon-start'] = field.textIconStart
|
|
360
|
+
|
|
361
|
+
properties[field.key] = prop
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const schema: JSONSchemaObject = {
|
|
365
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
366
|
+
type: 'object',
|
|
367
|
+
properties,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (required.length > 0) schema.required = required
|
|
371
|
+
if (options?.dynamicHeight) schema['x-dynamic-height'] = true
|
|
372
|
+
|
|
373
|
+
return schema
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ─── Form Data Utilities ─────────────────────────────────────────────────────
|
|
377
|
+
|
|
378
|
+
export function initFormData(fields: FormField[], existing?: Record<string, any>): Record<string, any> {
|
|
379
|
+
const data: Record<string, any> = {}
|
|
380
|
+
|
|
381
|
+
for (const field of fields) {
|
|
382
|
+
if (field.type === 'heading' || field.type === 'divider') continue
|
|
383
|
+
|
|
384
|
+
if (existing && field.key in existing) {
|
|
385
|
+
data[field.key] = existing[field.key]
|
|
386
|
+
continue
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (field.defaultValue !== undefined) {
|
|
390
|
+
data[field.key] = field.defaultValue
|
|
391
|
+
continue
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
switch (field.type) {
|
|
395
|
+
case 'checkbox':
|
|
396
|
+
case 'toggle':
|
|
397
|
+
data[field.key] = false
|
|
398
|
+
break
|
|
399
|
+
case 'number':
|
|
400
|
+
case 'range':
|
|
401
|
+
data[field.key] = field.validation?.min ?? 0
|
|
402
|
+
break
|
|
403
|
+
default:
|
|
404
|
+
data[field.key] = ''
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return data
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function validateFormData(fields: FormField[], data: Record<string, any>): Record<string, string> {
|
|
412
|
+
const errors: Record<string, string> = {}
|
|
413
|
+
|
|
414
|
+
for (const field of fields) {
|
|
415
|
+
if (field.type === 'heading' || field.type === 'divider') continue
|
|
416
|
+
|
|
417
|
+
const value = data[field.key]
|
|
418
|
+
|
|
419
|
+
// Required check
|
|
420
|
+
if (field.required && (value === undefined || value === null || value === '')) {
|
|
421
|
+
errors[field.key] = `${field.label} is required`
|
|
422
|
+
continue
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Skip further validation if empty and not required
|
|
426
|
+
if (value === undefined || value === null || value === '') continue
|
|
427
|
+
|
|
428
|
+
const v = field.validation
|
|
429
|
+
|
|
430
|
+
// String length checks
|
|
431
|
+
if (typeof value === 'string') {
|
|
432
|
+
if (v?.minLength !== undefined && value.length < v.minLength) {
|
|
433
|
+
errors[field.key] = `${field.label} must be at least ${v.minLength} characters`
|
|
434
|
+
continue
|
|
435
|
+
}
|
|
436
|
+
if (v?.maxLength !== undefined && value.length > v.maxLength) {
|
|
437
|
+
errors[field.key] = `${field.label} must be at most ${v.maxLength} characters`
|
|
438
|
+
continue
|
|
439
|
+
}
|
|
440
|
+
if (v?.pattern) {
|
|
441
|
+
const regex = new RegExp(v.pattern)
|
|
442
|
+
if (!regex.test(value)) {
|
|
443
|
+
errors[field.key] = `${field.label} format is invalid`
|
|
444
|
+
continue
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Number range checks
|
|
450
|
+
if (typeof value === 'number') {
|
|
451
|
+
if (v?.min !== undefined && value < v.min) {
|
|
452
|
+
errors[field.key] = `${field.label} must be at least ${v.min}`
|
|
453
|
+
continue
|
|
454
|
+
}
|
|
455
|
+
if (v?.max !== undefined && value > v.max) {
|
|
456
|
+
errors[field.key] = `${field.label} must be at most ${v.max}`
|
|
457
|
+
continue
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return errors
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ─── Vue Composable ──────────────────────────────────────────────────────────
|
|
466
|
+
|
|
467
|
+
export function useSchemaToFields(schema: Ref<JSONSchemaObject | undefined>) {
|
|
468
|
+
const fields = computed(() => schemaToFields(schema.value))
|
|
469
|
+
return { fields }
|
|
470
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ export * from './directives'
|
|
|
10
10
|
export * from './form-flow/form-flow'
|
|
11
11
|
export { default as FormFlow } from './form-flow/FormFlow.vue'
|
|
12
12
|
export { default as MultiStepForm } from './form-flow/MultiStepForm.vue'
|
|
13
|
+
export type { FormFieldType, FormFieldValidation, FormField, JSONSchemaProperty, JSONSchemaObject } from './form-flow/schema-fields'
|
|
14
|
+
export { defaultRowSpan, inferFieldType, schemaToFields, fieldsToSchema, initFormData, validateFormData, useSchemaToFields } from './form-flow/schema-fields'
|
|
13
15
|
// i18n exports
|
|
14
16
|
export {
|
|
15
17
|
bagelinkLocales,
|
package/src/styles/scrollbar.css
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
body>div ::-webkit-scrollbar {
|
|
2
2
|
width: var(--bgl-scrollbar-size);
|
|
3
3
|
height: var(--bgl-scrollbar-size);
|
|
4
|
+
|
|
4
5
|
}
|
|
5
6
|
|
|
6
7
|
body>div ::-webkit-scrollbar-track {
|
|
@@ -11,11 +12,16 @@ body>div ::-webkit-scrollbar-track {
|
|
|
11
12
|
body>div ::-webkit-scrollbar-thumb {
|
|
12
13
|
background-color: var(--bgl-scrollbar-thumb);
|
|
13
14
|
border-radius: 1rem;
|
|
14
|
-
|
|
15
15
|
border: calc(var(--bgl-scrollbar-size) / 4) solid transparent;
|
|
16
16
|
background-clip: content-box;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
body>div ::-webkit-scrollbar-corner {
|
|
20
20
|
background: transparent;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.bgl-scrollbar-bordered::-webkit-scrollbar-track,
|
|
24
|
+
.scrollbar-border::-webkit-scrollbar-track {
|
|
25
|
+
margin-block: 0 !important;
|
|
26
|
+
border-inline-start: 1px solid rgba(0, 0, 0, .08) !important;
|
|
21
27
|
}
|
package/src/styles/text.css
CHANGED
|
@@ -1517,7 +1517,15 @@
|
|
|
1517
1517
|
font-style: italic;
|
|
1518
1518
|
}
|
|
1519
1519
|
|
|
1520
|
+
.monospace {
|
|
1521
|
+
font-family: monospace !important;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1520
1524
|
@media screen and (max-width: 910px) {
|
|
1525
|
+
.m_monospace {
|
|
1526
|
+
font-family: monospace !important;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1521
1529
|
.m_italic {
|
|
1522
1530
|
font-style: italic;
|
|
1523
1531
|
}
|
package/src/utils/useSearch.ts
CHANGED
|
@@ -116,7 +116,7 @@ function calculateRelevance(stringValue: string, searchTerms: string[]): number
|
|
|
116
116
|
export interface SearchItemParams<T> {
|
|
117
117
|
searchTerm?: MaybeRefOrGetter<string>
|
|
118
118
|
items?: MaybeRefOrGetter<T[]>
|
|
119
|
-
keysToSearch?:
|
|
119
|
+
keysToSearch?: string[] // Key paths to search within items
|
|
120
120
|
fieldWeights?: Record<string, number> // Use simple string keys for weights
|
|
121
121
|
minChars?: number // Minimum characters required to trigger search
|
|
122
122
|
serverSearch?: (query: string) => Promise<T[]> // Function to perform server-side search
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2023 Bagel Studio
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|