@bagelink/vue 1.15.277 → 1.15.285
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/Avatar.vue.d.ts +2 -0
- package/dist/components/Avatar.vue.d.ts.map +1 -1
- package/dist/components/Icon/Icon.vue.d.ts +3 -0
- package/dist/components/Icon/Icon.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts +11 -0
- package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TextInput.vue.d.ts +1 -0
- package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
- package/dist/form-flow/FormFlowPlayer.vue.d.ts.map +1 -1
- package/dist/form-flow/schema-fields.d.ts +2 -1
- package/dist/form-flow/schema-fields.d.ts.map +1 -1
- package/dist/form-flow/structure.d.ts +8 -0
- package/dist/form-flow/structure.d.ts.map +1 -1
- package/dist/index.cjs +30 -30
- package/dist/index.mjs +3218 -3183
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Avatar.vue +10 -6
- package/src/components/Icon/Icon.vue +58 -10
- package/src/components/dataTable/DataTable.vue +4 -4
- package/src/components/form/inputs/CodeEditor/Index.vue +38 -2
- package/src/components/form/inputs/TextInput.vue +38 -27
- package/src/form-flow/FormFlowPlayer.vue +5 -1
- package/src/form-flow/schema-fields.ts +6 -2
- package/src/form-flow/structure.ts +8 -0
- package/src/styles/bagel.css +11 -0
- package/src/styles/text.css +40 -0
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// stay circular), use the `innerClass` prop — mirrors `triggerClass` on Dropdown.
|
|
6
6
|
defineOptions({ name: 'BglAvatar' })
|
|
7
7
|
import type { IconType, ThemeType } from '@bagelink/vue'
|
|
8
|
-
import { initials, Icon } from '@bagelink/vue'
|
|
8
|
+
import { initials, Icon, useDevice } from '@bagelink/vue'
|
|
9
9
|
import { computed } from 'vue'
|
|
10
10
|
import Image from './Image.vue'
|
|
11
11
|
|
|
@@ -16,6 +16,8 @@ const props = withDefaults(defineProps<{
|
|
|
16
16
|
icon?: IconType
|
|
17
17
|
alt?: string
|
|
18
18
|
size?: number
|
|
19
|
+
/** Avatar size on mobile. Falls back to `size` when omitted. */
|
|
20
|
+
mobileSize?: number
|
|
19
21
|
/** Rounded-square instead of a circle. `true` = themed radius, or pass a px radius. */
|
|
20
22
|
square?: boolean | number
|
|
21
23
|
/** Theme color → tinted background + matching icon/text color (e.g. an icon chip). */
|
|
@@ -29,9 +31,11 @@ const props = withDefaults(defineProps<{
|
|
|
29
31
|
innerClass?: string
|
|
30
32
|
}>(), { size: 50 })
|
|
31
33
|
|
|
34
|
+
const { isMobile } = useDevice()
|
|
35
|
+
const resolvedSize = computed(() => isMobile.value && props.mobileSize !== undefined ? props.mobileSize : props.size)
|
|
32
36
|
const statusColor = computed(() => (props.status === true ? 'green' : props.status || null))
|
|
33
37
|
// Dot scales with the avatar but stays within sane bounds.
|
|
34
|
-
const dotSize = computed(() => Math.max(7, Math.min(14, Math.round(
|
|
38
|
+
const dotSize = computed(() => Math.max(7, Math.min(14, Math.round(resolvedSize.value * 0.28))))
|
|
35
39
|
|
|
36
40
|
const radius = computed(() => {
|
|
37
41
|
if (!props.square) { return '1000px' } // circle (default)
|
|
@@ -49,15 +53,15 @@ const themed = computed(() => props.color
|
|
|
49
53
|
</script>
|
|
50
54
|
|
|
51
55
|
<template>
|
|
52
|
-
<div class="avatar-wrap" :style="{ width: `${
|
|
56
|
+
<div class="avatar-wrap" :style="{ width: `${resolvedSize}px`, height: `${resolvedSize}px` }">
|
|
53
57
|
<div
|
|
54
58
|
class="overflow-hidden txt-center p-0 avatar flex justify-content-center align-items-center"
|
|
55
59
|
:class="innerClass"
|
|
56
|
-
:style="{ width: `${
|
|
60
|
+
:style="{ width: `${resolvedSize}px`, height: `${resolvedSize}px`, borderRadius: radius, ...themed }"
|
|
57
61
|
>
|
|
58
62
|
<Image v-if="src" :src="src" :alt="alt || name" />
|
|
59
|
-
<Icon v-else-if="icon" :icon="icon" :size="
|
|
60
|
-
<p v-else :style="{ 'line-height': `${
|
|
63
|
+
<Icon v-else-if="icon" :icon="icon" :size="resolvedSize / 40" />
|
|
64
|
+
<p v-else :style="{ 'line-height': `${resolvedSize}px`, 'font-size': `calc(1.5rem * ${resolvedSize} / 50)` }">
|
|
61
65
|
{{ (fallback || initials(name || '')).toUpperCase() }}
|
|
62
66
|
</p>
|
|
63
67
|
</div>
|
|
@@ -13,6 +13,9 @@ const props = withDefaults(defineProps<{
|
|
|
13
13
|
size?: number | string
|
|
14
14
|
mobileSize?: number | string
|
|
15
15
|
color?: string
|
|
16
|
+
/** Circular chip. With `color`, fills the background from that tone
|
|
17
|
+
(theme name → `pair-*`, or a raw CSS color) and contrasts the glyph.
|
|
18
|
+
Without `color`, just the circular shape. */
|
|
16
19
|
round?: boolean
|
|
17
20
|
weight?: number | string
|
|
18
21
|
fontAwesome?: boolean
|
|
@@ -147,21 +150,52 @@ const transitionName = computed(() => {
|
|
|
147
150
|
const style = props.transition === true ? 'slide-up' : props.transition
|
|
148
151
|
return `bgl-icon-x-${style}`
|
|
149
152
|
})
|
|
153
|
+
|
|
154
|
+
const isRawCssColor = computed(() => {
|
|
155
|
+
const c = props.color?.trim()
|
|
156
|
+
if (!c) { return false }
|
|
157
|
+
return c.startsWith('#')
|
|
158
|
+
|| c.startsWith('var(')
|
|
159
|
+
|| /^(rgb|hsl|hwb|lab|lch|oklch|color)\(/i.test(c)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const roundPairClass = computed(() => {
|
|
163
|
+
if (!props.round || !props.color || isRawCssColor.value) { return undefined }
|
|
164
|
+
return `pair-${props.color}`
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const roundClass = computed(() => (props.round ? 'bgl_icon-round' : undefined))
|
|
168
|
+
|
|
169
|
+
const iconStyle = computed(() => {
|
|
170
|
+
const style: Record<string, string> = {
|
|
171
|
+
fontSize: `${computedSize.value}rem`,
|
|
172
|
+
visibility: isCurrentFontReady.value ? 'visible' : 'hidden',
|
|
173
|
+
...animateStyle.value,
|
|
174
|
+
}
|
|
175
|
+
if (iconRenderType.value === 'material') {
|
|
176
|
+
style['font-variation-settings'] = `'FILL' ${props.fill ? 1 : 0}, 'wght' ${props.weight || 400}`
|
|
177
|
+
} else {
|
|
178
|
+
style['font-variation-settings'] = `'wght' ${props.weight || 400}`
|
|
179
|
+
}
|
|
180
|
+
if (props.round) {
|
|
181
|
+
style.width = `calc(${computedSize.value}rem * 2)`
|
|
182
|
+
style.height = `calc(${computedSize.value}rem * 2)`
|
|
183
|
+
if (isRawCssColor.value && props.color) {
|
|
184
|
+
style.backgroundColor = props.color
|
|
185
|
+
}
|
|
186
|
+
} else if (props.color) {
|
|
187
|
+
style.color = props.color
|
|
188
|
+
}
|
|
189
|
+
return style
|
|
190
|
+
})
|
|
150
191
|
</script>
|
|
151
192
|
|
|
152
193
|
<template>
|
|
153
194
|
<Transition :name="transitionName" mode="out-in">
|
|
154
195
|
<span
|
|
155
196
|
v-if="iconRenderType === 'material'" :key="iconRender" class="bgl_icon-font notranslate"
|
|
156
|
-
:class="[animateClass, { '
|
|
157
|
-
|
|
158
|
-
color,
|
|
159
|
-
'font-variation-settings': `'FILL' ${fill ? 1 : 0}, 'wght' ${weight || 400}`,
|
|
160
|
-
'width': round ? `calc(${computedSize}rem * 2)` : 'auto',
|
|
161
|
-
'height': round ? `calc(${computedSize}rem * 2)` : 'auto',
|
|
162
|
-
'visibility': isCurrentFontReady ? 'visible' : 'hidden',
|
|
163
|
-
...animateStyle,
|
|
164
|
-
}" translate="no"
|
|
197
|
+
:class="[animateClass, roundClass, roundPairClass, { 'color-white keep-white': round && isRawCssColor }]"
|
|
198
|
+
:style="iconStyle" translate="no"
|
|
165
199
|
>
|
|
166
200
|
{{ iconRender }}
|
|
167
201
|
</span>
|
|
@@ -169,12 +203,15 @@ const transitionName = computed(() => {
|
|
|
169
203
|
v-else-if="iconRenderType === 'font-awesome'" :key="iconRender" class="fa bgl_icon-font notranslate" :class="[
|
|
170
204
|
`fa-${iconRender}`,
|
|
171
205
|
animateClass,
|
|
206
|
+
roundClass,
|
|
207
|
+
roundPairClass,
|
|
172
208
|
{
|
|
173
209
|
'fa-brands': isFaBrand,
|
|
174
210
|
'fa-solid': fill,
|
|
175
211
|
'far': !fill && !isFaBrand,
|
|
212
|
+
'color-white keep-white': round && isRawCssColor,
|
|
176
213
|
},
|
|
177
|
-
]" :style="
|
|
214
|
+
]" :style="iconStyle"
|
|
178
215
|
translate="no"
|
|
179
216
|
/>
|
|
180
217
|
</Transition>
|
|
@@ -202,6 +239,17 @@ const transitionName = computed(() => {
|
|
|
202
239
|
font-family: 'Material Symbols Outlined', serif !important;
|
|
203
240
|
}
|
|
204
241
|
|
|
242
|
+
.bgl_icon-font.bgl_icon-round {
|
|
243
|
+
display: inline-flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
justify-content: center;
|
|
246
|
+
flex-shrink: 0;
|
|
247
|
+
aspect-ratio: 1;
|
|
248
|
+
border-radius: 1000px;
|
|
249
|
+
line-height: 1;
|
|
250
|
+
box-sizing: border-box;
|
|
251
|
+
}
|
|
252
|
+
|
|
205
253
|
.bgl_icon-font.fa {
|
|
206
254
|
font-family: 'Font Awesome 6 Free', serif !important;
|
|
207
255
|
}
|
|
@@ -235,7 +235,7 @@ const headerFontSize = computed(() => {
|
|
|
235
235
|
:icon="props.emptyIcon ?? 'inbox'"
|
|
236
236
|
:title="props.emptyTitle ?? 'No results'"
|
|
237
237
|
:description="props.emptyDescription"
|
|
238
|
-
|
|
238
|
+
class="h-60p"
|
|
239
239
|
/>
|
|
240
240
|
</slot>
|
|
241
241
|
</div>
|
|
@@ -258,7 +258,7 @@ justify-content: center;
|
|
|
258
258
|
|
|
259
259
|
.col-check {
|
|
260
260
|
width: 1rem;
|
|
261
|
-
padding: 0 0.
|
|
261
|
+
padding: 0 0.5rem;
|
|
262
262
|
vertical-align: middle;
|
|
263
263
|
}
|
|
264
264
|
|
|
@@ -374,7 +374,7 @@ vertical-align: bottom;
|
|
|
374
374
|
|
|
375
375
|
.row.first-row::after {
|
|
376
376
|
content: '';
|
|
377
|
-
border-bottom: 1px solid var(--bgl-border-color);
|
|
377
|
+
/* border-bottom: 1px solid var(--bgl-border-color); */
|
|
378
378
|
position: absolute;
|
|
379
379
|
left: 0;
|
|
380
380
|
right: 0;
|
|
@@ -505,7 +505,7 @@ accent-color: var(--bgl-accent-color);
|
|
|
505
505
|
|
|
506
506
|
th::after {
|
|
507
507
|
content: '';
|
|
508
|
-
border-bottom: 1px solid var(--bgl-border-color);
|
|
508
|
+
/* border-bottom: 1px solid var(--bgl-border-color); */
|
|
509
509
|
position: absolute;
|
|
510
510
|
left: 0;
|
|
511
511
|
right: 0;
|
|
@@ -18,6 +18,8 @@ const props = defineProps({
|
|
|
18
18
|
disabled: { type: Boolean, default: false },
|
|
19
19
|
/** Wrap long lines instead of scrolling horizontally */
|
|
20
20
|
wrap: { type: Boolean, default: false },
|
|
21
|
+
/** One-line input height — matches TextInput / SelectInput. Enter does not insert a newline. */
|
|
22
|
+
singleLine: { type: Boolean, default: false },
|
|
21
23
|
theme: { type: String as PropType<HighlightTheme>, default: 'dark' },
|
|
22
24
|
frame: { type: Boolean, default: undefined },
|
|
23
25
|
outline: { type: Boolean, default: undefined },
|
|
@@ -51,6 +53,7 @@ const isFocused = ref(false)
|
|
|
51
53
|
const { loaded, loadHighlight, highlightCode, setTheme } = useHighlight(props.theme)
|
|
52
54
|
|
|
53
55
|
const maxHeight = computed(() => {
|
|
56
|
+
if (props.singleLine) return undefined
|
|
54
57
|
const h = props.height
|
|
55
58
|
return h.match(/^\d+$/) ? `${h}px` : h
|
|
56
59
|
})
|
|
@@ -97,6 +100,14 @@ function handleInput(e: Event) {
|
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
104
|
+
if (props.singleLine) {
|
|
105
|
+
if (event.key === 'Enter') event.preventDefault()
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
handleTab(event)
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
function handleTab(event: KeyboardEvent) {
|
|
101
112
|
if (event.key !== 'Tab') { return }
|
|
102
113
|
|
|
@@ -156,7 +167,7 @@ watch(() => props.modelValue, () => {
|
|
|
156
167
|
|
|
157
168
|
<template>
|
|
158
169
|
<div
|
|
159
|
-
class="bagel-input code-editor-container ltr" :class="[shellClass, { 'mb-2': label, 'has-value': hasCodeValue }]"
|
|
170
|
+
class="bagel-input code-editor-container ltr" :class="[shellClass, { 'mb-2': label && !singleLine, 'has-value': hasCodeValue, 'single-line': singleLine }]"
|
|
160
171
|
:style="[{ height: maxHeight }, shellStyle]"
|
|
161
172
|
>
|
|
162
173
|
<label v-if="label" class="label">{{ label }}</label>
|
|
@@ -172,7 +183,7 @@ watch(() => props.modelValue, () => {
|
|
|
172
183
|
<textarea
|
|
173
184
|
v-if="!readonly && !disabled" :value="code" class="code-input" spellcheck="false"
|
|
174
185
|
autocomplete="off" autocorrect="off" autocapitalize="off" @input="handleInput"
|
|
175
|
-
@keydown="
|
|
186
|
+
@keydown="handleKeydown"
|
|
176
187
|
@focus="isFocused = true"
|
|
177
188
|
@blur="handleBlur"
|
|
178
189
|
/>
|
|
@@ -304,6 +315,31 @@ watch(() => props.modelValue, () => {
|
|
|
304
315
|
overflow-wrap: anywhere;
|
|
305
316
|
word-break: break-word;
|
|
306
317
|
}
|
|
318
|
+
|
|
319
|
+
.code-editor-container.single-line {
|
|
320
|
+
height: auto;
|
|
321
|
+
margin-bottom: 0;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.code-editor-container.single-line .code-editor-grandpa {
|
|
325
|
+
height: var(--bgl-input-height);
|
|
326
|
+
padding-block: 0;
|
|
327
|
+
padding-inline: 0.7rem;
|
|
328
|
+
overflow-x: auto;
|
|
329
|
+
overflow-y: hidden;
|
|
330
|
+
background: var(--bgl-input-bg);
|
|
331
|
+
border-radius: var(--bgl-input-border-radius);
|
|
332
|
+
box-shadow: none;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.code-editor-container.single-line .code-display,
|
|
336
|
+
.code-editor-container.single-line .code-input,
|
|
337
|
+
.code-editor-container.single-line .code-placeholder {
|
|
338
|
+
padding: 0;
|
|
339
|
+
line-height: var(--bgl-input-height);
|
|
340
|
+
font-size: var(--bgl-input-font-size);
|
|
341
|
+
white-space: pre;
|
|
342
|
+
}
|
|
307
343
|
</style>
|
|
308
344
|
|
|
309
345
|
<style>
|
|
@@ -31,6 +31,7 @@ export interface TextInputProps extends ValidateInputBaseT, BagelInputShellProps
|
|
|
31
31
|
nativeInputAttrs?: { [key: string]: any }
|
|
32
32
|
icon?: IconType
|
|
33
33
|
iconStart?: IconType
|
|
34
|
+
iconColor?: string
|
|
34
35
|
multiline?: boolean
|
|
35
36
|
autoheight?: boolean
|
|
36
37
|
code?: boolean
|
|
@@ -166,6 +167,7 @@ onMounted(async () => {
|
|
|
166
167
|
underlined,
|
|
167
168
|
'textInputIconWrap': icon,
|
|
168
169
|
'txtInputIconStart': iconStart,
|
|
170
|
+
'multiline-field': autoheight || inputRows >= 2,
|
|
169
171
|
'has-error': !!error,
|
|
170
172
|
'has-value': hasValue,
|
|
171
173
|
},
|
|
@@ -173,21 +175,22 @@ onMounted(async () => {
|
|
|
173
175
|
>
|
|
174
176
|
<label :for="id">
|
|
175
177
|
<span v-if="label || (underlined && placeholder)" class="label-text">{{ resolveI18n(label) || resolveI18n(placeholder) }}<span v-if="required"> *</span></span>
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
178
|
+
<span class="input-field">
|
|
179
|
+
<input
|
|
180
|
+
v-if="!autoheight && inputRows < 2" :id ref="input" v-model.trim="inputVal"
|
|
181
|
+
:name :title :autocomplete :type :rows="1"
|
|
182
|
+
:placeholder="underlined ? ' ' : (resolveI18n(placeholder) || resolveI18n(label))"
|
|
183
|
+
:disabled :required v-bind="nativeInputAttrs" :pattern="normalizedPattern" @focusout="onFocusout"
|
|
184
|
+
@focus="onFocus" @input="updateInputVal"
|
|
185
|
+
>
|
|
186
|
+
<textarea
|
|
187
|
+
v-else :id ref="input" v-model="inputVal" :name :title :type :rows="inputRows"
|
|
188
|
+
:placeholder="underlined ? ' ' : (resolveI18n(placeholder) || resolveI18n(label))" :disabled :required
|
|
189
|
+
v-bind="nativeInputAttrs" :pattern="normalizedPattern" @input="updateInputVal" @focusout="onFocusout"
|
|
190
|
+
/>
|
|
191
|
+
<Icon v-if="iconStart" :class="{ ' -top-025 line-height-0 ': underlined }" class="iconStart" :icon="iconStart" :color="iconColor" />
|
|
192
|
+
<Icon v-if="icon" :class="{ ' -top-025 line-height-0 ': underlined }" :icon :color="iconColor" />
|
|
193
|
+
</span>
|
|
191
194
|
</label>
|
|
192
195
|
<p v-if="helptext" class="helptext">{{ helptext }}</p>
|
|
193
196
|
<div v-if="error" class="error-message">
|
|
@@ -269,20 +272,15 @@ gap: 0.5rem;
|
|
|
269
272
|
background: var(--input-disabled-bg);
|
|
270
273
|
}
|
|
271
274
|
|
|
272
|
-
/*
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
of the input row pinned to the BOTTOM of the label (the input is the last row), then
|
|
280
|
-
flex-center it. Bottom-anchored + fixed band height ⇒ centered on the input regardless
|
|
281
|
-
of a label above, and in dense/small modes. Scoped to :not(.underlined) because the
|
|
282
|
-
underlined variant floats its label as an overlay (input isn't pushed down). */
|
|
275
|
+
/* Keep icon positioning relative to the native field, not the optional label. */
|
|
276
|
+
.input-field {
|
|
277
|
+
position: relative;
|
|
278
|
+
display: block;
|
|
279
|
+
width: 100%;
|
|
280
|
+
}
|
|
281
|
+
|
|
283
282
|
.bagel-input:not(.underlined).textInputIconWrap > label,
|
|
284
283
|
.bagel-input:not(.underlined).txtInputIconStart > label {
|
|
285
|
-
position: relative;
|
|
286
284
|
display: block;
|
|
287
285
|
}
|
|
288
286
|
|
|
@@ -293,6 +291,14 @@ bottom: calc(var(--bgl-input-height) / 2);
|
|
|
293
291
|
transform: translateY(50%);
|
|
294
292
|
}
|
|
295
293
|
|
|
294
|
+
/* In a textarea, align icons with the first line instead of the field's bottom. */
|
|
295
|
+
.bagel-input.multiline-field:not(.underlined).textInputIconWrap .bgl_icon-font,
|
|
296
|
+
.bagel-input.multiline-field:not(.underlined).txtInputIconStart .iconStart {
|
|
297
|
+
top: calc(0.7rem + var(--bgl-input-font-size) / 2);
|
|
298
|
+
bottom: auto;
|
|
299
|
+
transform: translateY(-50%);
|
|
300
|
+
}
|
|
301
|
+
|
|
296
302
|
/* underlined: label floats as an overlay, so the original top-center is correct */
|
|
297
303
|
.underlined.textInputIconWrap .bgl_icon-font,
|
|
298
304
|
.underlined.txtInputIconStart .iconStart {
|
|
@@ -300,6 +306,11 @@ top: calc(var(--bgl-input-height) / 2);
|
|
|
300
306
|
transform: translateY(-50%);
|
|
301
307
|
}
|
|
302
308
|
|
|
309
|
+
.underlined.multiline-field.textInputIconWrap .bgl_icon-font,
|
|
310
|
+
.underlined.multiline-field.txtInputIconStart .iconStart {
|
|
311
|
+
top: calc(1.4rem + var(--bgl-input-font-size) / 2);
|
|
312
|
+
}
|
|
313
|
+
|
|
303
314
|
.textInputIconWrap .bgl_icon-font,
|
|
304
315
|
.txtInputIconStart .iconStart {
|
|
305
316
|
color: var(--bgl-input-color);
|
|
@@ -129,6 +129,7 @@ function buildPayload(stepKey: string): FormStepEventPayload {
|
|
|
129
129
|
stepTitle: step?.title ?? '',
|
|
130
130
|
stepIndex: position,
|
|
131
131
|
totalSteps,
|
|
132
|
+
skipSubmit: step?.skipSubmit ?? false,
|
|
132
133
|
formData: { ...formData.value },
|
|
133
134
|
}
|
|
134
135
|
}
|
|
@@ -178,7 +179,10 @@ async function onPrimary() {
|
|
|
178
179
|
submitting.value = true
|
|
179
180
|
const step = currentStep.value
|
|
180
181
|
try {
|
|
181
|
-
|
|
182
|
+
// `skipSubmit` terminals (e.g. disqualification leaves) end the flow
|
|
183
|
+
// without creating a submission — the host's onComplete is bypassed,
|
|
184
|
+
// but analytics + redirect still fire.
|
|
185
|
+
if (!step?.skipSubmit) await props.onComplete?.({ ...formData.value })
|
|
182
186
|
// Emit formComplete with standardised payload before completing.
|
|
183
187
|
if (currentKey.value) emit('formComplete', buildPayload(currentKey.value))
|
|
184
188
|
emit('complete', { ...formData.value })
|
|
@@ -99,7 +99,8 @@ export interface FormField {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export interface JSONSchemaProperty {
|
|
102
|
-
type?: string
|
|
102
|
+
type?: string | string[]
|
|
103
|
+
items?: JSONSchemaProperty
|
|
103
104
|
title?: string
|
|
104
105
|
description?: string
|
|
105
106
|
format?: string
|
|
@@ -298,12 +299,15 @@ export function fieldsToSchema(fields: FormField[]): JSONSchemaObject {
|
|
|
298
299
|
case 'phone':
|
|
299
300
|
case 'url':
|
|
300
301
|
case 'date':
|
|
301
|
-
case 'file':
|
|
302
302
|
case 'signature':
|
|
303
303
|
case 'color':
|
|
304
304
|
case 'hidden':
|
|
305
305
|
prop.type = 'string'
|
|
306
306
|
break
|
|
307
|
+
case 'file':
|
|
308
|
+
prop.type = ['string', 'array']
|
|
309
|
+
prop.items = { type: 'string' }
|
|
310
|
+
break
|
|
307
311
|
case 'number':
|
|
308
312
|
case 'range':
|
|
309
313
|
prop.type = 'number'
|
|
@@ -31,6 +31,12 @@ export interface FormStep {
|
|
|
31
31
|
* (final step or an early-termination leaf). Empty => no redirect.
|
|
32
32
|
*/
|
|
33
33
|
redirectUrl?: string | null
|
|
34
|
+
/**
|
|
35
|
+
* Terminal step that ends the flow *without* creating a submission — e.g. a
|
|
36
|
+
* disqualification / "not a fit" leaf. The step's own content (title + fields)
|
|
37
|
+
* is shown as the ending message; `redirectUrl` still applies if set.
|
|
38
|
+
*/
|
|
39
|
+
skipSubmit?: boolean
|
|
34
40
|
/** Canvas position (designer-only; ignored by the server). */
|
|
35
41
|
x?: number
|
|
36
42
|
y?: number
|
|
@@ -62,6 +68,8 @@ export interface FormStepEventPayload {
|
|
|
62
68
|
stepIndex: number
|
|
63
69
|
/** Projected total along current branch (null if indeterminate). */
|
|
64
70
|
totalSteps: number | null
|
|
71
|
+
/** True when this terminal step ends the flow without submitting (disqualification). */
|
|
72
|
+
skipSubmit: boolean
|
|
65
73
|
formData: Record<string, any>
|
|
66
74
|
}
|
|
67
75
|
|
package/src/styles/bagel.css
CHANGED
|
@@ -53,6 +53,17 @@
|
|
|
53
53
|
font-family: 'Font Awesome 6 Brands', serif !important;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
.bgl_icon-font.bgl_icon-round {
|
|
57
|
+
display: inline-flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
justify-content: center;
|
|
60
|
+
flex-shrink: 0;
|
|
61
|
+
aspect-ratio: 1;
|
|
62
|
+
border-radius: 1000px;
|
|
63
|
+
line-height: 1;
|
|
64
|
+
box-sizing: border-box;
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
/* Reset / base element styles */
|
|
57
68
|
:root {
|
|
58
69
|
--bgl-transition-ease: cubic-bezier(0.1, 0.5, 0.33, 1);
|
package/src/styles/text.css
CHANGED
|
@@ -37,6 +37,21 @@
|
|
|
37
37
|
font-size: 20px;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
.txt21,
|
|
41
|
+
.txt-21 {
|
|
42
|
+
font-size: 21px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.txt22,
|
|
46
|
+
.txt-22 {
|
|
47
|
+
font-size: 22px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.m_txt23,
|
|
51
|
+
.m_txt-23 {
|
|
52
|
+
font-size: 23px;
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
.txt24,
|
|
41
56
|
.txt-24 {
|
|
42
57
|
font-size: 24px;
|
|
@@ -77,6 +92,11 @@
|
|
|
77
92
|
font-size: 48px;
|
|
78
93
|
}
|
|
79
94
|
|
|
95
|
+
.txt49,
|
|
96
|
+
.txt-49 {
|
|
97
|
+
font-size: 49px;
|
|
98
|
+
}
|
|
99
|
+
|
|
80
100
|
.txt50,
|
|
81
101
|
.txt-50 {
|
|
82
102
|
font-size: 50px;
|
|
@@ -1746,6 +1766,21 @@
|
|
|
1746
1766
|
font-size: 20px;
|
|
1747
1767
|
}
|
|
1748
1768
|
|
|
1769
|
+
.m_txt21,
|
|
1770
|
+
.m_txt-21 {
|
|
1771
|
+
font-size: 21px;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
.m_txt22,
|
|
1775
|
+
.m_txt-22 {
|
|
1776
|
+
font-size: 22px;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
.m_txt23,
|
|
1780
|
+
.m_txt-23 {
|
|
1781
|
+
font-size: 23px;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1749
1784
|
.m_txt24,
|
|
1750
1785
|
.m_txt-24 {
|
|
1751
1786
|
font-size: 24px;
|
|
@@ -1786,6 +1821,11 @@
|
|
|
1786
1821
|
font-size: 48px;
|
|
1787
1822
|
}
|
|
1788
1823
|
|
|
1824
|
+
.m_txt49,
|
|
1825
|
+
.m_txt-49 {
|
|
1826
|
+
font-size: 49px;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1789
1829
|
.m_txt50,
|
|
1790
1830
|
.m_txt-50 {
|
|
1791
1831
|
font-size: 50px;
|