@bagelink/vue 1.8.71 → 1.8.76
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/Pill.vue.d.ts +6 -1
- package/dist/components/Pill.vue.d.ts.map +1 -1
- package/dist/components/Toast.vue.d.ts +14 -0
- package/dist/components/Toast.vue.d.ts.map +1 -0
- package/dist/components/layout/TabsNav.vue.d.ts.map +1 -1
- package/dist/dialog/Dialog.vue.d.ts +8 -0
- package/dist/dialog/Dialog.vue.d.ts.map +1 -1
- package/dist/dialog/DialogConfirm.vue.d.ts.map +1 -1
- package/dist/dialog/DialogForm.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts +14 -0
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/index.cjs +12 -12
- package/dist/index.mjs +5027 -5532
- package/dist/plugins/useToast.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Filter.vue +1 -1
- package/src/components/Pill.vue +39 -282
- package/src/components/Toast.vue +127 -0
- package/src/components/form/inputs/RichText/components/EditorToolbar.vue +1 -1
- package/src/components/layout/TabsNav.vue +9 -2
- package/src/dialog/Dialog.vue +107 -166
- package/src/dialog/DialogConfirm.vue +1 -7
- package/src/dialog/DialogForm.vue +3 -22
- package/src/dialog/DialogOLD.vue +358 -0
- package/src/form-flow/form-flow.ts +115 -15
- package/src/plugins/useToast.ts +46 -11
- package/src/styles/bagel.css +1 -0
- package/src/styles/base-colors.css +1613 -0
- package/src/styles/layout.css +8 -0
- package/src/styles/mobilLayout.css +8 -0
- package/src/styles/theme.css +412 -426
- package/src/styles/toast-overrides.css +24 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Dialog - Native dialog wrapper with animations
|
|
4
|
+
*
|
|
5
|
+
* Uses native <dialog> element for:
|
|
6
|
+
* - Automatic focus trap
|
|
7
|
+
* - ESC key handling
|
|
8
|
+
* - Backdrop via ::backdrop
|
|
9
|
+
* - Browser-managed z-index stacking
|
|
10
|
+
*/
|
|
11
|
+
import type { DialogWidth, DialogPosition } from './dialogTypes'
|
|
12
|
+
import { ref, computed, onMounted, watch } from 'vue'
|
|
13
|
+
import { DIALOG_WIDTHS } from './dialogTypes'
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(defineProps<{
|
|
16
|
+
open: boolean
|
|
17
|
+
title?: string
|
|
18
|
+
width?: DialogWidth
|
|
19
|
+
position?: DialogPosition
|
|
20
|
+
dismissable?: boolean
|
|
21
|
+
}>(), {
|
|
22
|
+
width: 'm',
|
|
23
|
+
position: 'center',
|
|
24
|
+
dismissable: true
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const emit = defineEmits<{
|
|
28
|
+
'update:open': [value: boolean]
|
|
29
|
+
'close': []
|
|
30
|
+
}>()
|
|
31
|
+
|
|
32
|
+
const dialogRef = ref<HTMLDialogElement | null>(null)
|
|
33
|
+
const isClosing = ref(false)
|
|
34
|
+
|
|
35
|
+
const widthStyle = computed(() => DIALOG_WIDTHS[props.width])
|
|
36
|
+
|
|
37
|
+
const positionClass = computed(() => {
|
|
38
|
+
if (props.position === 'left') return 'dialog-left'
|
|
39
|
+
if (props.position === 'right') return 'dialog-right'
|
|
40
|
+
return 'dialog-center'
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Handle open/close
|
|
44
|
+
watch(() => props.open, (isOpen) => {
|
|
45
|
+
if (isOpen) {
|
|
46
|
+
dialogRef.value?.showModal()
|
|
47
|
+
isClosing.value = false
|
|
48
|
+
} else {
|
|
49
|
+
closeWithAnimation()
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
onMounted(() => {
|
|
54
|
+
if (props.open) {
|
|
55
|
+
dialogRef.value?.showModal()
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
function closeWithAnimation() {
|
|
60
|
+
if (isClosing.value) return
|
|
61
|
+
isClosing.value = true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function onAnimationEnd() {
|
|
65
|
+
if (isClosing.value) {
|
|
66
|
+
dialogRef.value?.close()
|
|
67
|
+
isClosing.value = false
|
|
68
|
+
emit('close')
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function onBackdropClick(e: MouseEvent) {
|
|
73
|
+
// Only close if clicking directly on the dialog backdrop (not content)
|
|
74
|
+
if (e.target === dialogRef.value && props.dismissable) {
|
|
75
|
+
emit('update:open', false)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function onCancel(e: Event) {
|
|
80
|
+
// Native ESC key triggers cancel event
|
|
81
|
+
if (!props.dismissable) {
|
|
82
|
+
e.preventDefault()
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
emit('update:open', false)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function close() {
|
|
89
|
+
emit('update:open', false)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
defineExpose({ close })
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<template>
|
|
96
|
+
<dialog
|
|
97
|
+
ref="dialogRef" :class="[positionClass, { 'is-closing': isClosing }]"
|
|
98
|
+
:style="{ '--dialog-width': widthStyle }" @click="onBackdropClick" @cancel="onCancel"
|
|
99
|
+
@animationend="onAnimationEnd"
|
|
100
|
+
>
|
|
101
|
+
<div class="dialog-content" @click.stop>
|
|
102
|
+
<!-- Header -->
|
|
103
|
+
<header v-if="title || dismissable" class="dialog-header">
|
|
104
|
+
<h2 v-if="title" class="dialog-title">
|
|
105
|
+
{{ title }}
|
|
106
|
+
</h2>
|
|
107
|
+
<button v-if="dismissable" type="button" class="dialog-close" aria-label="Close" @click="close">
|
|
108
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
109
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
110
|
+
</svg>
|
|
111
|
+
</button>
|
|
112
|
+
</header>
|
|
113
|
+
|
|
114
|
+
<!-- Body -->
|
|
115
|
+
<div class="dialog-body">
|
|
116
|
+
<slot />
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<!-- Footer -->
|
|
120
|
+
<footer v-if="$slots.footer" class="dialog-footer">
|
|
121
|
+
<slot name="footer" />
|
|
122
|
+
</footer>
|
|
123
|
+
</div>
|
|
124
|
+
</dialog>
|
|
125
|
+
</template>
|
|
126
|
+
|
|
127
|
+
<style scoped>
|
|
128
|
+
dialog {
|
|
129
|
+
border: none;
|
|
130
|
+
border-radius: var(--bgl-card-radius, 12px);
|
|
131
|
+
padding: 0;
|
|
132
|
+
max-width: var(--dialog-width);
|
|
133
|
+
width: calc(100% - 2rem);
|
|
134
|
+
max-height: calc(100vh - 2rem);
|
|
135
|
+
background: var(--bgl-popup-bg, #fff);
|
|
136
|
+
color: var(--bgl-text, #1a1a1a);
|
|
137
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
|
138
|
+
overflow: hidden;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
dialog::backdrop {
|
|
142
|
+
background: var(--bgl-dark-bg, rgba(0, 0, 0, 0.5));
|
|
143
|
+
opacity: 0;
|
|
144
|
+
transition: opacity 0.2s ease-out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
dialog[open]::backdrop {
|
|
148
|
+
opacity: 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Center position (default) */
|
|
152
|
+
dialog.dialog-center {
|
|
153
|
+
margin: auto;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
dialog.dialog-center[open] {
|
|
157
|
+
animation: dialog-fade-in 0.2s ease-out;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
dialog.dialog-center.is-closing {
|
|
161
|
+
animation: dialog-fade-out 0.15s ease-in forwards;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@keyframes dialog-fade-in {
|
|
165
|
+
from {
|
|
166
|
+
opacity: 0;
|
|
167
|
+
transform: scale(0.95) translateY(10px);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
to {
|
|
171
|
+
opacity: 1;
|
|
172
|
+
transform: scale(1) translateY(0);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@keyframes dialog-fade-out {
|
|
177
|
+
from {
|
|
178
|
+
opacity: 1;
|
|
179
|
+
transform: scale(1) translateY(0);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
to {
|
|
183
|
+
opacity: 0;
|
|
184
|
+
transform: scale(0.95) translateY(10px);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Right position */
|
|
189
|
+
dialog.dialog-right {
|
|
190
|
+
margin: 0;
|
|
191
|
+
margin-inline-start: auto;
|
|
192
|
+
margin-inline-end: 1rem;
|
|
193
|
+
margin-top: 1rem;
|
|
194
|
+
margin-bottom: 1rem;
|
|
195
|
+
height: calc(100vh - 2rem);
|
|
196
|
+
max-height: calc(100vh - 2rem);
|
|
197
|
+
border-radius: var(--bgl-card-radius, 12px);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
dialog.dialog-right[open] {
|
|
201
|
+
animation: dialog-slide-in-right 0.25s ease-out;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
dialog.dialog-right.is-closing {
|
|
205
|
+
animation: dialog-slide-out-right 0.2s ease-in forwards;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@keyframes dialog-slide-in-right {
|
|
209
|
+
from {
|
|
210
|
+
opacity: 0;
|
|
211
|
+
transform: translateX(100%);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
to {
|
|
215
|
+
opacity: 1;
|
|
216
|
+
transform: translateX(0);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@keyframes dialog-slide-out-right {
|
|
221
|
+
from {
|
|
222
|
+
opacity: 1;
|
|
223
|
+
transform: translateX(0);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
to {
|
|
227
|
+
opacity: 0;
|
|
228
|
+
transform: translateX(100%);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* Left position */
|
|
233
|
+
dialog.dialog-left {
|
|
234
|
+
margin: 0;
|
|
235
|
+
margin-inline-end: auto;
|
|
236
|
+
margin-inline-start: 1rem;
|
|
237
|
+
margin-top: 1rem;
|
|
238
|
+
margin-bottom: 1rem;
|
|
239
|
+
height: calc(100vh - 2rem);
|
|
240
|
+
max-height: calc(100vh - 2rem);
|
|
241
|
+
border-radius: var(--bgl-card-radius, 12px);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
dialog.dialog-left[open] {
|
|
245
|
+
animation: dialog-slide-in-left 0.25s ease-out;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
dialog.dialog-left.is-closing {
|
|
249
|
+
animation: dialog-slide-out-left 0.2s ease-in forwards;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@keyframes dialog-slide-in-left {
|
|
253
|
+
from {
|
|
254
|
+
opacity: 0;
|
|
255
|
+
transform: translateX(-100%);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
to {
|
|
259
|
+
opacity: 1;
|
|
260
|
+
transform: translateX(0);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@keyframes dialog-slide-out-left {
|
|
265
|
+
from {
|
|
266
|
+
opacity: 1;
|
|
267
|
+
transform: translateX(0);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
to {
|
|
271
|
+
opacity: 0;
|
|
272
|
+
transform: translateX(-100%);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Content structure */
|
|
277
|
+
.dialog-content {
|
|
278
|
+
display: flex;
|
|
279
|
+
flex-direction: column;
|
|
280
|
+
height: 100%;
|
|
281
|
+
max-height: inherit;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.dialog-header {
|
|
285
|
+
display: flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
justify-content: space-between;
|
|
288
|
+
padding: 1rem 1.5rem;
|
|
289
|
+
border-bottom: 1px solid var(--bgl-border-color, #e5e5e5);
|
|
290
|
+
flex-shrink: 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.dialog-title {
|
|
294
|
+
margin: 0;
|
|
295
|
+
font-size: 1.125rem;
|
|
296
|
+
font-weight: 600;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.dialog-close {
|
|
300
|
+
display: flex;
|
|
301
|
+
align-items: center;
|
|
302
|
+
justify-content: center;
|
|
303
|
+
width: 32px;
|
|
304
|
+
height: 32px;
|
|
305
|
+
border: none;
|
|
306
|
+
background: transparent;
|
|
307
|
+
border-radius: 6px;
|
|
308
|
+
cursor: pointer;
|
|
309
|
+
color: var(--bgl-text-secondary, #666);
|
|
310
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.dialog-close:hover {
|
|
314
|
+
background: var(--bgl-gray-tint, #f0f0f0);
|
|
315
|
+
color: var(--bgl-text, #1a1a1a);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.dialog-body {
|
|
319
|
+
flex: 1;
|
|
320
|
+
padding: 1.5rem;
|
|
321
|
+
overflow-y: auto;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.dialog-footer {
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
justify-content: flex-end;
|
|
328
|
+
gap: 0.75rem;
|
|
329
|
+
padding: 1rem 1.5rem;
|
|
330
|
+
border-top: 1px solid var(--bgl-border-color, #e5e5e5);
|
|
331
|
+
flex-shrink: 0;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* Full width */
|
|
335
|
+
dialog[style*="--dialog-width: 100%"] {
|
|
336
|
+
max-width: calc(100vw - 2rem);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
dialog.dialog-left[style*="--dialog-width: 100%"],
|
|
340
|
+
dialog.dialog-right[style*="--dialog-width: 100%"] {
|
|
341
|
+
max-width: calc(100vw - 2rem);
|
|
342
|
+
width: calc(100vw - 2rem);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* Mobile adjustments */
|
|
346
|
+
@media screen and (max-width: 640px) {
|
|
347
|
+
|
|
348
|
+
dialog.dialog-left,
|
|
349
|
+
dialog.dialog-right {
|
|
350
|
+
margin: 0;
|
|
351
|
+
width: 100%;
|
|
352
|
+
max-width: 100%;
|
|
353
|
+
height: 100vh;
|
|
354
|
+
max-height: 100vh;
|
|
355
|
+
border-radius: 0;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
</style>
|
|
@@ -266,6 +266,16 @@ export const $ = {
|
|
|
266
266
|
return new Field('richtext', parseArgs(labelOrConfig, config))
|
|
267
267
|
},
|
|
268
268
|
|
|
269
|
+
json(
|
|
270
|
+
labelOrConfig?: string | (BaseFieldConfig & { language?: string, height?: string }),
|
|
271
|
+
config?: BaseFieldConfig & { language?: string, height?: string }
|
|
272
|
+
): FieldBuilder<Record<string, any>> {
|
|
273
|
+
const parsed = parseArgs(labelOrConfig, config)
|
|
274
|
+
// Default language to 'json' for CodeEditor
|
|
275
|
+
if (!parsed.language) parsed.language = 'json'
|
|
276
|
+
return new Field('json', parsed)
|
|
277
|
+
},
|
|
278
|
+
|
|
269
279
|
checkbox(labelOrConfig?: string | BaseFieldConfig, config?: BaseFieldConfig): FieldBuilder<boolean> {
|
|
270
280
|
return new Field('checkbox', parseArgs(labelOrConfig, config))
|
|
271
281
|
},
|
|
@@ -519,6 +529,7 @@ class Schema implements SchemaDefinition {
|
|
|
519
529
|
datetime: 'string',
|
|
520
530
|
textarea: 'string',
|
|
521
531
|
richtext: 'string',
|
|
532
|
+
json: 'object',
|
|
522
533
|
checkbox: 'boolean',
|
|
523
534
|
toggle: 'boolean',
|
|
524
535
|
radio: 'string',
|
|
@@ -628,6 +639,11 @@ interface JSONSchemaProperty {
|
|
|
628
639
|
'maximum'?: number
|
|
629
640
|
'minLength'?: number
|
|
630
641
|
'maxLength'?: number
|
|
642
|
+
'$ref'?: string
|
|
643
|
+
'anyOf'?: JSONSchemaProperty[]
|
|
644
|
+
'oneOf'?: JSONSchemaProperty[]
|
|
645
|
+
'allOf'?: JSONSchemaProperty[]
|
|
646
|
+
'additionalProperties'?: boolean | JSONSchemaProperty
|
|
631
647
|
'x-ui'?: {
|
|
632
648
|
fieldType?: string
|
|
633
649
|
placeholder?: string
|
|
@@ -650,6 +666,8 @@ interface JSONSchemaObject {
|
|
|
650
666
|
'title'?: string
|
|
651
667
|
'properties'?: Record<string, JSONSchemaProperty>
|
|
652
668
|
'required'?: string[]
|
|
669
|
+
'$defs'?: Record<string, JSONSchemaProperty>
|
|
670
|
+
'definitions'?: Record<string, JSONSchemaProperty>
|
|
653
671
|
'x-ui'?: {
|
|
654
672
|
condition?: string
|
|
655
673
|
class?: string
|
|
@@ -679,7 +697,7 @@ export function fromJSONSchema(jsonSchema: JSONSchemaObject): SchemaDefinition {
|
|
|
679
697
|
|
|
680
698
|
if (jsonSchema.properties) {
|
|
681
699
|
for (const [key, prop] of Object.entries(jsonSchema.properties)) {
|
|
682
|
-
fields[key] = createFieldFromProperty(key, prop, requiredSet.has(key))
|
|
700
|
+
fields[key] = createFieldFromProperty(key, prop, requiredSet.has(key), jsonSchema)
|
|
683
701
|
}
|
|
684
702
|
}
|
|
685
703
|
|
|
@@ -696,23 +714,101 @@ export function fromJSONSchema(jsonSchema: JSONSchemaObject): SchemaDefinition {
|
|
|
696
714
|
return schema
|
|
697
715
|
}
|
|
698
716
|
|
|
717
|
+
/**
|
|
718
|
+
* Resolve a $ref pointer to its definition
|
|
719
|
+
*/
|
|
720
|
+
function resolveRef(ref: string, rootSchema: JSONSchemaObject): JSONSchemaProperty | undefined {
|
|
721
|
+
// Handle local refs like "#/$defs/WaLanguage" or "#/definitions/WaLanguage"
|
|
722
|
+
const match = ref.match(/^#\/(\$defs|definitions)\/(.+)$/)
|
|
723
|
+
if (!match) return undefined
|
|
724
|
+
|
|
725
|
+
const [, defsKey, defName] = match
|
|
726
|
+
const defs = defsKey === '$defs' ? rootSchema.$defs : rootSchema.definitions
|
|
727
|
+
return defName ? defs?.[defName] : undefined
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Resolve anyOf/oneOf to a primary type (ignoring null for nullable fields)
|
|
732
|
+
*/
|
|
733
|
+
function resolveUnionType(prop: JSONSchemaProperty, _rootSchema: JSONSchemaObject): JSONSchemaProperty {
|
|
734
|
+
const unionTypes = prop.anyOf || prop.oneOf
|
|
735
|
+
if (!unionTypes || unionTypes.length === 0) return prop
|
|
736
|
+
|
|
737
|
+
// Filter out null types to find the "real" type
|
|
738
|
+
const nonNullTypes = unionTypes.filter(t => t.type !== 'null')
|
|
739
|
+
|
|
740
|
+
if (nonNullTypes.length === 1) {
|
|
741
|
+
// Single non-null type - merge with parent prop (preserving title, default, etc.)
|
|
742
|
+
const resolved = nonNullTypes[0]
|
|
743
|
+
return {
|
|
744
|
+
...resolved,
|
|
745
|
+
'title': prop.title || resolved.title,
|
|
746
|
+
'description': prop.description || resolved.description,
|
|
747
|
+
'default': prop.default !== undefined ? prop.default : resolved.default,
|
|
748
|
+
'x-ui': { ...resolved['x-ui'], ...prop['x-ui'] },
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Multiple non-null types - try to pick the most useful one
|
|
753
|
+
// Prefer object > array > string > number > boolean
|
|
754
|
+
const typeOrder = ['object', 'array', 'string', 'number', 'integer', 'boolean']
|
|
755
|
+
const sorted = nonNullTypes.sort((a, b) => {
|
|
756
|
+
const aIdx = typeOrder.indexOf(a.type || 'string')
|
|
757
|
+
const bIdx = typeOrder.indexOf(b.type || 'string')
|
|
758
|
+
return aIdx - bIdx
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
const resolved = sorted[0]
|
|
762
|
+
return {
|
|
763
|
+
...resolved,
|
|
764
|
+
'title': prop.title || resolved.title,
|
|
765
|
+
'description': prop.description || resolved.description,
|
|
766
|
+
'default': prop.default !== undefined ? prop.default : resolved.default,
|
|
767
|
+
'x-ui': { ...resolved['x-ui'], ...prop['x-ui'] },
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
699
771
|
/**
|
|
700
772
|
* Create a field builder from a JSONSchema property
|
|
701
773
|
*/
|
|
702
774
|
function createFieldFromProperty(
|
|
703
775
|
key: string,
|
|
704
776
|
prop: JSONSchemaProperty,
|
|
705
|
-
isRequired: boolean
|
|
777
|
+
isRequired: boolean,
|
|
778
|
+
rootSchema: JSONSchemaObject
|
|
706
779
|
): FieldBuilder {
|
|
707
|
-
|
|
708
|
-
|
|
780
|
+
// Resolve $ref if present
|
|
781
|
+
let resolvedProp = prop
|
|
782
|
+
if (prop.$ref) {
|
|
783
|
+
const refDef = resolveRef(prop.$ref, rootSchema)
|
|
784
|
+
if (refDef) {
|
|
785
|
+
// Merge ref definition with property (property values take precedence)
|
|
786
|
+
resolvedProp = {
|
|
787
|
+
...refDef,
|
|
788
|
+
'type': prop.type ?? refDef.type,
|
|
789
|
+
'enum': prop.enum ?? refDef.enum,
|
|
790
|
+
'title': prop.title ?? refDef.title,
|
|
791
|
+
'description': prop.description ?? refDef.description,
|
|
792
|
+
'default': prop.default !== undefined ? prop.default : refDef.default,
|
|
793
|
+
'x-ui': { ...refDef['x-ui'], ...prop['x-ui'] },
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Resolve anyOf/oneOf
|
|
799
|
+
if (resolvedProp.anyOf || resolvedProp.oneOf) {
|
|
800
|
+
resolvedProp = resolveUnionType(resolvedProp, rootSchema)
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const xUi = resolvedProp['x-ui'] || {}
|
|
804
|
+
const fieldType = xUi.fieldType || inferFieldType(resolvedProp)
|
|
709
805
|
|
|
710
806
|
// Build base config
|
|
711
807
|
const config: BaseFieldConfig & Record<string, any> = {}
|
|
712
808
|
|
|
713
|
-
if (
|
|
714
|
-
if (
|
|
715
|
-
if (
|
|
809
|
+
if (resolvedProp.title) config.label = resolvedProp.title
|
|
810
|
+
if (resolvedProp.description) config.helpText = resolvedProp.description
|
|
811
|
+
if (resolvedProp.default !== undefined) config.default = resolvedProp.default
|
|
716
812
|
if (isRequired) config.required = true
|
|
717
813
|
|
|
718
814
|
// UI hints from x-ui
|
|
@@ -724,10 +820,10 @@ function createFieldFromProperty(
|
|
|
724
820
|
if (xUi.cols) config.cols = xUi.cols
|
|
725
821
|
|
|
726
822
|
// Numeric/string constraints
|
|
727
|
-
if (
|
|
728
|
-
if (
|
|
729
|
-
if (
|
|
730
|
-
if (
|
|
823
|
+
if (resolvedProp.minimum !== undefined) config.min = resolvedProp.minimum
|
|
824
|
+
if (resolvedProp.maximum !== undefined) config.max = resolvedProp.maximum
|
|
825
|
+
if (resolvedProp.minLength !== undefined) config.min = resolvedProp.minLength
|
|
826
|
+
if (resolvedProp.maxLength !== undefined) config.max = resolvedProp.maxLength
|
|
731
827
|
|
|
732
828
|
// Create the appropriate field builder
|
|
733
829
|
let field: FieldBuilder
|
|
@@ -763,6 +859,9 @@ function createFieldFromProperty(
|
|
|
763
859
|
case 'richtext':
|
|
764
860
|
field = $.richtext(config)
|
|
765
861
|
break
|
|
862
|
+
case 'json':
|
|
863
|
+
field = $.json(config)
|
|
864
|
+
break
|
|
766
865
|
case 'checkbox':
|
|
767
866
|
field = $.checkbox(config)
|
|
768
867
|
break
|
|
@@ -770,16 +869,16 @@ function createFieldFromProperty(
|
|
|
770
869
|
field = $.toggle(config)
|
|
771
870
|
break
|
|
772
871
|
case 'radio':
|
|
773
|
-
field = $.radio(
|
|
872
|
+
field = $.radio(resolvedProp.enum || [], config)
|
|
774
873
|
break
|
|
775
874
|
case 'select':
|
|
776
|
-
field = $.select(
|
|
875
|
+
field = $.select(resolvedProp.enum || [], config)
|
|
777
876
|
break
|
|
778
877
|
case 'multiselect':
|
|
779
|
-
field = $.multiselect(
|
|
878
|
+
field = $.multiselect(resolvedProp.enum || [], config)
|
|
780
879
|
break
|
|
781
880
|
case 'array':
|
|
782
|
-
field = $.array(
|
|
881
|
+
field = $.array(resolvedProp.title || key, undefined, {
|
|
783
882
|
...config,
|
|
784
883
|
allowAdd: xUi.allowAdd,
|
|
785
884
|
allowDelete: xUi.allowDelete,
|
|
@@ -837,6 +936,7 @@ function inferFieldType(prop: JSONSchemaProperty): string {
|
|
|
837
936
|
case 'number':
|
|
838
937
|
case 'integer': return 'number'
|
|
839
938
|
case 'array': return 'array'
|
|
939
|
+
case 'object': return 'json' // Objects rendered with CodeEditor
|
|
840
940
|
default: return 'text'
|
|
841
941
|
}
|
|
842
942
|
}
|
package/src/plugins/useToast.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { App, InjectionKey, Plugin } from 'vue'
|
|
2
|
+
import type { PluginOptions as ToastOptions } from 'vue-toastification'
|
|
2
3
|
import { inject } from 'vue'
|
|
3
4
|
import Toast, {
|
|
4
|
-
|
|
5
|
+
|
|
5
6
|
POSITION,
|
|
6
|
-
useToast as useVueToast
|
|
7
|
+
useToast as useVueToast
|
|
7
8
|
} from 'vue-toastification'
|
|
9
|
+
import CustomToast from '../components/Toast.vue'
|
|
8
10
|
import 'vue-toastification/dist/index.css'
|
|
11
|
+
import '../styles/toast-overrides.css'
|
|
9
12
|
|
|
10
13
|
export interface ToastApi {
|
|
11
14
|
success: (message: string, options?: any) => void
|
|
@@ -39,20 +42,22 @@ export const ToastPlugin: Plugin<BagelToastOptions[]> = {
|
|
|
39
42
|
install: (app: App, options: BagelToastOptions = {}) => {
|
|
40
43
|
const defaultOptions: ToastOptions = {
|
|
41
44
|
position: POSITION.TOP_RIGHT,
|
|
42
|
-
timeout:
|
|
45
|
+
timeout: 5000,
|
|
43
46
|
closeOnClick: true,
|
|
44
47
|
pauseOnFocusLoss: true,
|
|
45
48
|
pauseOnHover: true,
|
|
46
49
|
draggable: true,
|
|
47
50
|
draggablePercent: 0.6,
|
|
48
51
|
showCloseButtonOnHover: false,
|
|
49
|
-
hideProgressBar:
|
|
50
|
-
closeButton:
|
|
51
|
-
icon:
|
|
52
|
+
hideProgressBar: true,
|
|
53
|
+
closeButton: false,
|
|
54
|
+
icon: false,
|
|
52
55
|
rtl: false,
|
|
53
56
|
transition: 'Vue-Toastification__fade',
|
|
54
57
|
maxToasts: 5,
|
|
55
58
|
newestOnTop: true,
|
|
59
|
+
toastClassName: 'custom-toast-wrapper',
|
|
60
|
+
bodyClassName: 'custom-toast-body',
|
|
56
61
|
...options,
|
|
57
62
|
}
|
|
58
63
|
|
|
@@ -63,23 +68,53 @@ export const ToastPlugin: Plugin<BagelToastOptions[]> = {
|
|
|
63
68
|
const api: ToastApi = {
|
|
64
69
|
success: (message: string, opts?: any) => {
|
|
65
70
|
const toast = useVueToast()
|
|
66
|
-
return toast
|
|
71
|
+
return toast({
|
|
72
|
+
component: CustomToast,
|
|
73
|
+
props: {
|
|
74
|
+
message,
|
|
75
|
+
type: 'success',
|
|
76
|
+
},
|
|
77
|
+
}, opts)
|
|
67
78
|
},
|
|
68
79
|
error: (message: string, opts?: any) => {
|
|
69
80
|
const toast = useVueToast()
|
|
70
|
-
return toast
|
|
81
|
+
return toast({
|
|
82
|
+
component: CustomToast,
|
|
83
|
+
props: {
|
|
84
|
+
message,
|
|
85
|
+
type: 'error',
|
|
86
|
+
},
|
|
87
|
+
}, opts)
|
|
71
88
|
},
|
|
72
89
|
info: (message: string, opts?: any) => {
|
|
73
90
|
const toast = useVueToast()
|
|
74
|
-
return toast
|
|
91
|
+
return toast({
|
|
92
|
+
component: CustomToast,
|
|
93
|
+
props: {
|
|
94
|
+
message,
|
|
95
|
+
type: 'info',
|
|
96
|
+
},
|
|
97
|
+
}, opts)
|
|
75
98
|
},
|
|
76
99
|
warning: (message: string, opts?: any) => {
|
|
77
100
|
const toast = useVueToast()
|
|
78
|
-
return toast
|
|
101
|
+
return toast({
|
|
102
|
+
component: CustomToast,
|
|
103
|
+
props: {
|
|
104
|
+
message,
|
|
105
|
+
type: 'warning',
|
|
106
|
+
},
|
|
107
|
+
}, opts)
|
|
79
108
|
},
|
|
80
109
|
show: (message: string, opts?: any) => {
|
|
81
110
|
const toast = useVueToast()
|
|
82
|
-
return toast(
|
|
111
|
+
return toast({
|
|
112
|
+
component: CustomToast,
|
|
113
|
+
props: {
|
|
114
|
+
message,
|
|
115
|
+
type: 'info',
|
|
116
|
+
},
|
|
117
|
+
}, opts)
|
|
83
118
|
},
|
|
84
119
|
clear: () => {
|
|
85
120
|
const toast = useVueToast()
|