@bagelink/vue 1.15.401 → 1.15.409
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/form/inputs/DateInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DatePicker.vue.d.ts +2 -0
- package/dist/components/form/inputs/DatePicker.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts +3 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/richTextTypes.d.ts +10 -0
- package/dist/components/form/inputs/RichText/richTextTypes.d.ts.map +1 -1
- package/dist/components/form/inputs/index.d.ts +2 -1
- package/dist/components/form/inputs/index.d.ts.map +1 -1
- package/dist/core.css +2 -2
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts +6 -2
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/form-flow/grid-classes.d.ts +19 -1
- package/dist/form-flow/grid-classes.d.ts.map +1 -1
- package/dist/form-flow/schema-fields.d.ts +12 -2
- package/dist/form-flow/schema-fields.d.ts.map +1 -1
- package/dist/index.cjs +47 -45
- package/dist/index.css +1 -1
- package/dist/index.mjs +10402 -10308
- package/dist/style.css +2 -2
- package/package.json +1 -1
- package/src/components/form/inputs/DateInput.vue +7 -0
- package/src/components/form/inputs/DatePicker.vue +2 -1
- package/src/components/form/inputs/RichText/index.vue +16 -3
- package/src/components/form/inputs/RichText/richTextTypes.ts +14 -0
- package/src/components/form/inputs/index.ts +2 -1
- package/src/form-flow/FormFlow.vue +42 -1
- package/src/form-flow/form-flow.ts +10 -3
- package/src/form-flow/grid-classes.ts +94 -4
- package/src/form-flow/schema-fields.ts +32 -5
- package/src/styles/inputs.css +34 -0
package/package.json
CHANGED
|
@@ -275,6 +275,12 @@ function handleIconClick() {
|
|
|
275
275
|
isOpen.value = !isOpen.value
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
function confirmSelection() {
|
|
279
|
+
isTyping.value = false
|
|
280
|
+
syncInputValue()
|
|
281
|
+
isOpen.value = false
|
|
282
|
+
}
|
|
283
|
+
|
|
278
284
|
// Sync input display when date changes externally; coerce datetime → date-only.
|
|
279
285
|
watch(() => selectedDate.value, (val) => {
|
|
280
286
|
const coerced = coerceDateOnlyValue(val)
|
|
@@ -335,6 +341,7 @@ onMounted(() => {
|
|
|
335
341
|
v-model="selectedDate" :min="min" :max="max" :mode="mode"
|
|
336
342
|
:firstDayOfWeek="firstDayOfWeek" :locale="locale" :enableTime="enableTime"
|
|
337
343
|
:range="range"
|
|
344
|
+
@confirm="confirmSelection"
|
|
338
345
|
/>
|
|
339
346
|
</div>
|
|
340
347
|
</Dropdown>
|
|
@@ -39,7 +39,7 @@ const props = withDefaults(
|
|
|
39
39
|
},
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
-
const emit = defineEmits(['update:modelValue'])
|
|
42
|
+
const emit = defineEmits(['update:modelValue', 'confirm'])
|
|
43
43
|
|
|
44
44
|
// Tentative end date hovered during range selection
|
|
45
45
|
const hoverDate = ref<Date | null>(null)
|
|
@@ -542,6 +542,7 @@ function selectDate(date: Date | null) {
|
|
|
542
542
|
layout="vertical" @update:modelValue="handleMinuteInput"
|
|
543
543
|
/>
|
|
544
544
|
</div>
|
|
545
|
+
<Btn value="OK" :disabled="!selectedDate" @click="emit('confirm')" />
|
|
545
546
|
</div>
|
|
546
547
|
</div>
|
|
547
548
|
</template>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!-- eslint-disable vue/multi-word-component-names -->
|
|
2
2
|
<script setup lang="ts">
|
|
3
|
-
import type { CustomToolbarButton, RichTextToolbarApi, ToolbarConfig } from './richTextTypes'
|
|
3
|
+
import type { CustomToolbarButton, RichTextExtension, RichTextToolbarApi, ToolbarConfig } from './richTextTypes'
|
|
4
4
|
import { CodeEditor, copyText, Btn, Dialog, BglVideo, Icon, Card, ColorInput, pathKeyToURL } from '@bagelink/vue'
|
|
5
5
|
import { watch, onUnmounted, onBeforeUnmount, onMounted, ref, computed, useAttrs } from 'vue'
|
|
6
6
|
import CheckInput from '../CheckInput.vue'
|
|
@@ -24,6 +24,7 @@ const props = defineProps<{
|
|
|
24
24
|
modelValue: string
|
|
25
25
|
toolbarConfig?: ToolbarConfig
|
|
26
26
|
customToolbarButtons?: CustomToolbarButton[]
|
|
27
|
+
extensions?: RichTextExtension[]
|
|
27
28
|
debug?: boolean
|
|
28
29
|
label?: string
|
|
29
30
|
height?: number | string
|
|
@@ -50,12 +51,23 @@ const props = defineProps<{
|
|
|
50
51
|
textColor?: string
|
|
51
52
|
// Font size
|
|
52
53
|
fontSize?: number | string
|
|
54
|
+
// Field-specific CSS injected into the editor iframe.
|
|
55
|
+
contentStyles?: string
|
|
53
56
|
} & BagelInputShellProps>()
|
|
54
57
|
|
|
55
58
|
const emit = defineEmits(['update:modelValue'])
|
|
56
59
|
|
|
57
60
|
const { shellClass, shellStyle } = useBagelInputShell(props)
|
|
58
61
|
const hasEditorValue = computed(() => Boolean(props.modelValue?.trim()))
|
|
62
|
+
const extensionToolbarButtons = computed(() => props.extensions?.flatMap(extension => extension.toolbarButtons ?? []) ?? [])
|
|
63
|
+
const effectiveCustomToolbarButtons = computed(() => [
|
|
64
|
+
...(props.customToolbarButtons ?? []),
|
|
65
|
+
...extensionToolbarButtons.value,
|
|
66
|
+
])
|
|
67
|
+
const effectiveContentStyles = computed(() => [
|
|
68
|
+
props.contentStyles,
|
|
69
|
+
...(props.extensions?.map(extension => extension.contentStyles) ?? []),
|
|
70
|
+
].filter(Boolean).join('\n'))
|
|
59
71
|
|
|
60
72
|
const attrs = useAttrs()
|
|
61
73
|
|
|
@@ -3294,13 +3306,14 @@ function updateIframeColors() {
|
|
|
3294
3306
|
color: inherit !important;
|
|
3295
3307
|
font-size: inherit !important;
|
|
3296
3308
|
}
|
|
3309
|
+
${effectiveContentStyles.value}
|
|
3297
3310
|
`
|
|
3298
3311
|
|
|
3299
3312
|
styleElement.textContent = css
|
|
3300
3313
|
}
|
|
3301
3314
|
|
|
3302
3315
|
// Watch for theme changes and update iframe colors
|
|
3303
|
-
watch([currentInputColor, currentTextColor, currentFontFamily], () => {
|
|
3316
|
+
watch([currentInputColor, currentTextColor, currentFontFamily, effectiveContentStyles], () => {
|
|
3304
3317
|
if (hasInitialized.value) {
|
|
3305
3318
|
updateIframeColors()
|
|
3306
3319
|
}
|
|
@@ -3423,7 +3436,7 @@ const imgTransform = computed({
|
|
|
3423
3436
|
<template #end>
|
|
3424
3437
|
<slot name="toolbar-end" :api="toolbarApi">
|
|
3425
3438
|
<Btn
|
|
3426
|
-
v-for="button in
|
|
3439
|
+
v-for="button in effectiveCustomToolbarButtons"
|
|
3427
3440
|
:key="button.name"
|
|
3428
3441
|
v-tooltip="button.label"
|
|
3429
3442
|
:icon="button.icon"
|
|
@@ -77,6 +77,20 @@ export interface CustomToolbarButton {
|
|
|
77
77
|
action: (api: RichTextToolbarApi) => void
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* A reusable editor capability. Extensions can add toolbar actions and the CSS
|
|
82
|
+
* needed to preview their generated HTML inside the editor iframe.
|
|
83
|
+
*/
|
|
84
|
+
export interface RichTextExtension {
|
|
85
|
+
name: string
|
|
86
|
+
toolbarButtons?: CustomToolbarButton[]
|
|
87
|
+
contentStyles?: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function defineRichTextExtension<T extends RichTextExtension>(extension: T): T {
|
|
91
|
+
return extension
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
export interface ToolbarOption {
|
|
81
95
|
name: string
|
|
82
96
|
label?: string
|
|
@@ -15,7 +15,8 @@ export { default as PasswordInput } from './PasswordInput.vue'
|
|
|
15
15
|
export { default as RadioGroup } from './RadioGroup.vue'
|
|
16
16
|
export { default as RangeInput } from './RangeInput.vue'
|
|
17
17
|
export { default as RichText } from './RichText/index.vue'
|
|
18
|
-
export
|
|
18
|
+
export { defineRichTextExtension } from './RichText/richTextTypes'
|
|
19
|
+
export type { CustomToolbarButton, RichTextExtension, RichTextToolbarApi } from './RichText/richTextTypes'
|
|
19
20
|
export { default as SelectBtn } from './SelectBtn.vue'
|
|
20
21
|
export { default as SelectInput } from './SelectInput.vue'
|
|
21
22
|
export { default as SignaturePad } from './SignaturePad.vue'
|
|
@@ -23,6 +23,9 @@ import Divider from '../components/layout/Divider.vue'
|
|
|
23
23
|
import { useI18n, resolveI18n } from '../i18n'
|
|
24
24
|
import { evaluateQuery } from '../utils/queryFilter'
|
|
25
25
|
import { EMAIL_REGEX } from '../utils/constants'
|
|
26
|
+
import {
|
|
27
|
+
classToGridSpans,
|
|
28
|
+
} from './grid-classes'
|
|
26
29
|
|
|
27
30
|
export type FormState = 'idle' | 'submitting' | 'success' | 'error'
|
|
28
31
|
|
|
@@ -220,6 +223,23 @@ const visibleFields = computed(() => {
|
|
|
220
223
|
return fields
|
|
221
224
|
})
|
|
222
225
|
|
|
226
|
+
function fieldLayoutStyle(field: FieldBuilder): Record<string, string> | undefined {
|
|
227
|
+
if (!props.schema._class?.split(/\s+/).includes('free-grid')) return undefined
|
|
228
|
+
const {
|
|
229
|
+
rowStart,
|
|
230
|
+
rowSpan,
|
|
231
|
+
mRowStart: mobileRowStart,
|
|
232
|
+
mRowSpan: mobileRowSpan,
|
|
233
|
+
} = classToGridSpans(field._class)
|
|
234
|
+
if (!rowStart && !mobileRowStart && !rowSpan && !mobileRowSpan) return undefined
|
|
235
|
+
return {
|
|
236
|
+
...(rowStart ? { '--bgl-grid-row-start': String(rowStart) } : {}),
|
|
237
|
+
...(mobileRowStart ? { '--bgl-mobile-grid-row-start': String(mobileRowStart) } : {}),
|
|
238
|
+
...(rowSpan ? { '--bgl-grid-row-span': String(rowSpan) } : {}),
|
|
239
|
+
...(mobileRowSpan ? { '--bgl-mobile-grid-row-span': String(mobileRowSpan) } : {}),
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
223
243
|
function updateField(key: string, value: any, field: FieldBuilder) {
|
|
224
244
|
setNestedValue(formData.value, key, value)
|
|
225
245
|
if (formState.value === 'success' || formState.value === 'error') {
|
|
@@ -338,6 +358,7 @@ const mergedErrors = computed(() => {
|
|
|
338
358
|
function getFieldProps(field: FieldBuilder, key: string) {
|
|
339
359
|
const label = generateLabel(key, field)
|
|
340
360
|
const { helpText, ...configRest } = field._config
|
|
361
|
+
const freeGrid = props.schema._class?.split(/\s+/).includes('free-grid') ?? false
|
|
341
362
|
const baseProps: any = {
|
|
342
363
|
label,
|
|
343
364
|
'modelValue': getFieldValue(key),
|
|
@@ -357,6 +378,21 @@ function getFieldProps(field: FieldBuilder, key: string) {
|
|
|
357
378
|
baseProps.multiselect = true
|
|
358
379
|
}
|
|
359
380
|
|
|
381
|
+
if (freeGrid && field._type === 'richtext') {
|
|
382
|
+
baseProps.fullHeight = true
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (
|
|
386
|
+
freeGrid
|
|
387
|
+
&& (field._type === 'upload' || field._type === 'file')
|
|
388
|
+
) {
|
|
389
|
+
// In free-grid, row span owns the field height. Ignore a persisted
|
|
390
|
+
// fixed upload height so the preview grows and shrinks with its cell.
|
|
391
|
+
baseProps.height = field._config.hideLabel
|
|
392
|
+
? '100%'
|
|
393
|
+
: 'calc(100% - 1.75rem)'
|
|
394
|
+
}
|
|
395
|
+
|
|
360
396
|
// Function option loaders get the live form data as a 2nd arg so options can
|
|
361
397
|
// depend on sibling fields (e.g. `model` filtered by the selected `provider`).
|
|
362
398
|
// SelectInput/useSearch only re-run the loader on search-term change; a change
|
|
@@ -502,7 +538,12 @@ defineExpose({
|
|
|
502
538
|
<slot name="success" :form-data="formData" />
|
|
503
539
|
</template>
|
|
504
540
|
<template v-else>
|
|
505
|
-
<div
|
|
541
|
+
<div
|
|
542
|
+
v-for="{ key, field } in visibleFields"
|
|
543
|
+
:key="fieldKey(key, field)"
|
|
544
|
+
:class="[field._class, field._config.class]"
|
|
545
|
+
:style="fieldLayoutStyle(field)"
|
|
546
|
+
>
|
|
506
547
|
<!-- Custom slot for specific field -->
|
|
507
548
|
<slot
|
|
508
549
|
:name="`field:${key}`"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Component } from 'vue'
|
|
12
12
|
import type { IconType } from '../types'
|
|
13
|
-
import type { CustomToolbarButton } from '../components/form/inputs/RichText/richTextTypes'
|
|
13
|
+
import type { CustomToolbarButton, RichTextExtension } from '../components/form/inputs/RichText/richTextTypes'
|
|
14
14
|
import { getOptionValue } from '../utils/options'
|
|
15
15
|
|
|
16
16
|
// ============================================================================
|
|
@@ -298,8 +298,8 @@ export const $ = {
|
|
|
298
298
|
},
|
|
299
299
|
|
|
300
300
|
richtext(
|
|
301
|
-
labelOrConfig?: string | (BaseFieldConfig & { autoheight?: boolean, basic?: boolean, simple?: boolean, fontSize?: number | string, customToolbarButtons?: CustomToolbarButton[] }),
|
|
302
|
-
config?: BaseFieldConfig & { autoheight?: boolean, basic?: boolean, simple?: boolean, fontSize?: number | string, customToolbarButtons?: CustomToolbarButton[] }
|
|
301
|
+
labelOrConfig?: string | (BaseFieldConfig & { autoheight?: boolean, basic?: boolean, simple?: boolean, fontSize?: number | string, customToolbarButtons?: CustomToolbarButton[], contentStyles?: string, extensions?: RichTextExtension[] }),
|
|
302
|
+
config?: BaseFieldConfig & { autoheight?: boolean, basic?: boolean, simple?: boolean, fontSize?: number | string, customToolbarButtons?: CustomToolbarButton[], contentStyles?: string, extensions?: RichTextExtension[] }
|
|
303
303
|
): FieldBuilder<string> {
|
|
304
304
|
return new Field('richtext', parseArgs(labelOrConfig, config))
|
|
305
305
|
},
|
|
@@ -1365,11 +1365,18 @@ export {
|
|
|
1365
1365
|
DEFAULT_ROW_SPAN,
|
|
1366
1366
|
colSpanToClass,
|
|
1367
1367
|
colStartToClass,
|
|
1368
|
+
mColStartToClass,
|
|
1368
1369
|
mColSpanToClass,
|
|
1370
|
+
mRowStartToClass,
|
|
1371
|
+
mRowSpanToClass,
|
|
1369
1372
|
rowSpanToClass,
|
|
1370
1373
|
classToColSpan,
|
|
1371
1374
|
classToColStart,
|
|
1375
|
+
classToMColStart,
|
|
1372
1376
|
classToMColSpan,
|
|
1377
|
+
classToMRowStart,
|
|
1378
|
+
classToMRowSpan,
|
|
1379
|
+
classToRowStart,
|
|
1373
1380
|
classToRowSpan,
|
|
1374
1381
|
classToGridSpans,
|
|
1375
1382
|
stripGridClasses,
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
* Desktop column span: col-{1..12}/12 (e.g. col-6/12 = span 6)
|
|
9
9
|
* Desktop column start: start-col-{1..12}
|
|
10
10
|
* Mobile column span: m_col-{1..12}/12
|
|
11
|
-
*
|
|
11
|
+
* Desktop row start: start-row-{1..N}
|
|
12
|
+
* Row span: span-row-{1..N} (e.g. span-row-2 = span 2 rows)
|
|
13
|
+
* Mobile column start: m_start-col-{1..12}
|
|
14
|
+
* Mobile row start: m_start-row-{1..N}
|
|
15
|
+
* Mobile row span: m_span-row-{1..N}
|
|
12
16
|
*
|
|
13
17
|
* These helpers round-trip cleanly so the same class string renders identically
|
|
14
18
|
* in FormFlow and the form builder, and survives storage as JSON Schema.
|
|
@@ -23,7 +27,11 @@ export const DEFAULT_ROW_SPAN = 1
|
|
|
23
27
|
const COL_RE = /^col-(\d{1,2})\/12$/
|
|
24
28
|
const COL_START_RE = /^start-col-(\d{1,2})$/
|
|
25
29
|
const M_COL_RE = /^m_col-(\d{1,2})\/12$/
|
|
26
|
-
const
|
|
30
|
+
const ROW_START_RE = /^start-row-(\d+)$/
|
|
31
|
+
const ROW_RE = /^(?:grid-)?span-row-(\d+)$/
|
|
32
|
+
const M_COL_START_RE = /^m_start-col-(\d{1,2})$/
|
|
33
|
+
const M_ROW_START_RE = /^m_start-row-(\d+)$/
|
|
34
|
+
const M_ROW_RE = /^m_(?:grid-)?span-row-(\d+)$/
|
|
27
35
|
|
|
28
36
|
// Fraction aliases (col-1/2, col-2/3, ...) map to a canonical numeric span.
|
|
29
37
|
const FRACTION_TO_SPAN: Record<string, number> = {
|
|
@@ -50,6 +58,10 @@ function clampSpan(span: number): number {
|
|
|
50
58
|
return Math.max(1, Math.min(GRID_COLS, Math.round(span)))
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
function normalizeRowSpan(span: number): number {
|
|
62
|
+
return Math.max(1, Math.round(span))
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
/** Desktop column span -> canonical class (e.g. 6 -> "col-6/12"). */
|
|
54
66
|
export function colSpanToClass(span: number): string {
|
|
55
67
|
return `col-${clampSpan(span)}/12`
|
|
@@ -67,7 +79,24 @@ export function mColSpanToClass(span: number): string {
|
|
|
67
79
|
|
|
68
80
|
/** Row span -> canonical class (e.g. 2 -> "span-row-2"). */
|
|
69
81
|
export function rowSpanToClass(span: number): string {
|
|
70
|
-
return `span-row-${
|
|
82
|
+
return `span-row-${normalizeRowSpan(span)}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Desktop starting row -> canonical class (e.g. 4 -> "start-row-4"). */
|
|
86
|
+
export function rowStartToClass(start: number): string {
|
|
87
|
+
return `start-row-${Math.max(1, Math.round(start))}`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function mColStartToClass(start: number): string {
|
|
91
|
+
return `m_start-col-${clampSpan(start)}`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function mRowStartToClass(start: number): string {
|
|
95
|
+
return `m_start-row-${Math.max(1, Math.round(start))}`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function mRowSpanToClass(span: number): string {
|
|
99
|
+
return `m_span-row-${normalizeRowSpan(span)}`
|
|
71
100
|
}
|
|
72
101
|
|
|
73
102
|
// ─── class -> span ───────────────────────────────────────────────────────────
|
|
@@ -105,12 +134,49 @@ export function classToMColSpan(cls: string | undefined): number | undefined {
|
|
|
105
134
|
return undefined
|
|
106
135
|
}
|
|
107
136
|
|
|
137
|
+
export function classToMColStart(cls: string | undefined): number | undefined {
|
|
138
|
+
if (!cls) return undefined
|
|
139
|
+
for (const token of cls.split(/\s+/)) {
|
|
140
|
+
const match = token.match(M_COL_START_RE)
|
|
141
|
+
if (match) return clampSpan(Number(match[1]))
|
|
142
|
+
}
|
|
143
|
+
return undefined
|
|
144
|
+
}
|
|
145
|
+
|
|
108
146
|
/** Parse the first row span found in a class string, if any. */
|
|
109
147
|
export function classToRowSpan(cls: string | undefined): number | undefined {
|
|
110
148
|
if (!cls) return undefined
|
|
111
149
|
for (const token of cls.split(/\s+/)) {
|
|
112
150
|
const m = token.match(ROW_RE)
|
|
113
|
-
if (m) return
|
|
151
|
+
if (m) return normalizeRowSpan(Number(m[1]))
|
|
152
|
+
}
|
|
153
|
+
return undefined
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Parse the first desktop starting row found in a class string, if any. */
|
|
157
|
+
export function classToRowStart(cls: string | undefined): number | undefined {
|
|
158
|
+
if (!cls) return undefined
|
|
159
|
+
for (const token of cls.split(/\s+/)) {
|
|
160
|
+
const match = token.match(ROW_START_RE)
|
|
161
|
+
if (match) return Math.max(1, Number(match[1]))
|
|
162
|
+
}
|
|
163
|
+
return undefined
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function classToMRowStart(cls: string | undefined): number | undefined {
|
|
167
|
+
if (!cls) return undefined
|
|
168
|
+
for (const token of cls.split(/\s+/)) {
|
|
169
|
+
const match = token.match(M_ROW_START_RE)
|
|
170
|
+
if (match) return Math.max(1, Number(match[1]))
|
|
171
|
+
}
|
|
172
|
+
return undefined
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function classToMRowSpan(cls: string | undefined): number | undefined {
|
|
176
|
+
if (!cls) return undefined
|
|
177
|
+
for (const token of cls.split(/\s+/)) {
|
|
178
|
+
const match = token.match(M_ROW_RE)
|
|
179
|
+
if (match) return normalizeRowSpan(Number(match[1]))
|
|
114
180
|
}
|
|
115
181
|
return undefined
|
|
116
182
|
}
|
|
@@ -127,7 +193,11 @@ export function stripGridClasses(cls: string | undefined): string {
|
|
|
127
193
|
!COL_RE.test(t)
|
|
128
194
|
&& !COL_START_RE.test(t)
|
|
129
195
|
&& !M_COL_RE.test(t)
|
|
196
|
+
&& !ROW_START_RE.test(t)
|
|
130
197
|
&& !ROW_RE.test(t)
|
|
198
|
+
&& !M_COL_START_RE.test(t)
|
|
199
|
+
&& !M_ROW_START_RE.test(t)
|
|
200
|
+
&& !M_ROW_RE.test(t)
|
|
131
201
|
&& !(t in FRACTION_TO_SPAN)
|
|
132
202
|
&& !(t in M_FRACTION_TO_SPAN),
|
|
133
203
|
)
|
|
@@ -137,8 +207,12 @@ export function stripGridClasses(cls: string | undefined): string {
|
|
|
137
207
|
export interface GridSpans {
|
|
138
208
|
colSpan?: number
|
|
139
209
|
colStart?: number
|
|
210
|
+
rowStart?: number
|
|
140
211
|
rowSpan?: number
|
|
141
212
|
mColSpan?: number
|
|
213
|
+
mColStart?: number
|
|
214
|
+
mRowStart?: number
|
|
215
|
+
mRowSpan?: number
|
|
142
216
|
}
|
|
143
217
|
|
|
144
218
|
/**
|
|
@@ -155,12 +229,24 @@ export function applyGridClasses(cls: string | undefined, spans: GridSpans): str
|
|
|
155
229
|
if (spans.colStart !== undefined) {
|
|
156
230
|
parts.push(colStartToClass(spans.colStart))
|
|
157
231
|
}
|
|
232
|
+
if (spans.rowStart !== undefined) {
|
|
233
|
+
parts.push(rowStartToClass(spans.rowStart))
|
|
234
|
+
}
|
|
158
235
|
if (spans.rowSpan !== undefined && spans.rowSpan !== DEFAULT_ROW_SPAN) {
|
|
159
236
|
parts.push(rowSpanToClass(spans.rowSpan))
|
|
160
237
|
}
|
|
161
238
|
if (spans.mColSpan !== undefined && spans.mColSpan !== DEFAULT_COL_SPAN) {
|
|
162
239
|
parts.push(mColSpanToClass(spans.mColSpan))
|
|
163
240
|
}
|
|
241
|
+
if (spans.mColStart !== undefined) {
|
|
242
|
+
parts.push(mColStartToClass(spans.mColStart))
|
|
243
|
+
}
|
|
244
|
+
if (spans.mRowStart !== undefined) {
|
|
245
|
+
parts.push(mRowStartToClass(spans.mRowStart))
|
|
246
|
+
}
|
|
247
|
+
if (spans.mRowSpan !== undefined && spans.mRowSpan !== DEFAULT_ROW_SPAN) {
|
|
248
|
+
parts.push(mRowSpanToClass(spans.mRowSpan))
|
|
249
|
+
}
|
|
164
250
|
return parts.join(' ')
|
|
165
251
|
}
|
|
166
252
|
|
|
@@ -169,7 +255,11 @@ export function classToGridSpans(cls: string | undefined): GridSpans {
|
|
|
169
255
|
return {
|
|
170
256
|
colSpan: classToColSpan(cls),
|
|
171
257
|
colStart: classToColStart(cls),
|
|
258
|
+
rowStart: classToRowStart(cls),
|
|
172
259
|
rowSpan: classToRowSpan(cls),
|
|
173
260
|
mColSpan: classToMColSpan(cls),
|
|
261
|
+
mColStart: classToMColStart(cls),
|
|
262
|
+
mRowStart: classToMRowStart(cls),
|
|
263
|
+
mRowSpan: classToMRowSpan(cls),
|
|
174
264
|
}
|
|
175
265
|
}
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
applyGridClasses,
|
|
11
11
|
classToColSpan,
|
|
12
12
|
classToColStart,
|
|
13
|
+
classToMColStart,
|
|
13
14
|
classToMColSpan,
|
|
15
|
+
classToMRowStart,
|
|
16
|
+
classToMRowSpan,
|
|
17
|
+
classToRowStart,
|
|
14
18
|
classToRowSpan,
|
|
15
19
|
stripGridClasses,
|
|
16
20
|
} from './grid-classes'
|
|
@@ -53,10 +57,18 @@ export interface FormField {
|
|
|
53
57
|
colSpan?: number
|
|
54
58
|
/** Desktop starting column (1-12). Persisted as `start-col-N`. */
|
|
55
59
|
colStart?: number
|
|
56
|
-
/**
|
|
60
|
+
/** Desktop starting row (1+). Persisted as `start-row-N`. */
|
|
61
|
+
rowStart?: number
|
|
62
|
+
/** Row span (1+, default 1). Persisted as `span-row-N`. */
|
|
57
63
|
rowSpan?: number
|
|
58
64
|
/** Mobile column span (1-12, default 12). Persisted as `m_col-N/12`. */
|
|
59
65
|
mColSpan?: number
|
|
66
|
+
/** Mobile starting column (1-12). Persisted as `m_start-col-N`. */
|
|
67
|
+
mColStart?: number
|
|
68
|
+
/** Mobile starting row (1+). Persisted as `m_start-row-N`. */
|
|
69
|
+
mRowStart?: number
|
|
70
|
+
/** Mobile row span (1+). Persisted as `m_span-row-N`. */
|
|
71
|
+
mRowSpan?: number
|
|
60
72
|
/** User-authored (non-grid) classes preserved alongside layout classes. */
|
|
61
73
|
class?: string
|
|
62
74
|
hideLabel?: boolean
|
|
@@ -247,8 +259,12 @@ export function schemaToFields(schema: JSONSchemaObject | undefined): FormField[
|
|
|
247
259
|
required: requiredSet.has(key) || undefined,
|
|
248
260
|
colSpan: classToColSpan(uiClass) ?? 12,
|
|
249
261
|
colStart: classToColStart(uiClass),
|
|
262
|
+
rowStart: classToRowStart(uiClass),
|
|
250
263
|
rowSpan: classToRowSpan(uiClass) ?? defaultRowSpan(type),
|
|
251
264
|
mColSpan: classToMColSpan(uiClass) ?? 12,
|
|
265
|
+
mColStart: classToMColStart(uiClass),
|
|
266
|
+
mRowStart: classToMRowStart(uiClass),
|
|
267
|
+
mRowSpan: classToMRowSpan(uiClass),
|
|
252
268
|
class: stripGridClasses(uiClass) || undefined,
|
|
253
269
|
hideLabel: prop['x-hide-label'] ?? false,
|
|
254
270
|
condition: prop['x-ui']?.condition || undefined,
|
|
@@ -296,7 +312,10 @@ export function schemaToFields(schema: JSONSchemaObject | undefined): FormField[
|
|
|
296
312
|
|
|
297
313
|
// ─── Fields → Schema ─────────────────────────────────────────────────────────
|
|
298
314
|
|
|
299
|
-
export function fieldsToSchema(
|
|
315
|
+
export function fieldsToSchema(
|
|
316
|
+
fields: FormField[],
|
|
317
|
+
options: { freeGrid?: boolean } = {},
|
|
318
|
+
): JSONSchemaObject {
|
|
300
319
|
const properties: Record<string, JSONSchemaProperty> = {}
|
|
301
320
|
const required: string[] = []
|
|
302
321
|
|
|
@@ -392,13 +411,21 @@ export function fieldsToSchema(fields: FormField[]): JSONSchemaObject {
|
|
|
392
411
|
// Options
|
|
393
412
|
if (field.validation?.options) prop['x-options'] = field.validation.options
|
|
394
413
|
|
|
395
|
-
|
|
396
|
-
//
|
|
414
|
+
const rowSpan = field.rowSpan ?? defaultRowSpan(field.type)
|
|
415
|
+
// Free-grid needs every multi-row default persisted because the live
|
|
416
|
+
// renderer owns fixed row geometry. Flow forms preserve the prior
|
|
417
|
+
// behavior and omit field-type defaults.
|
|
397
418
|
const layoutClass = applyGridClasses(field.class, {
|
|
398
419
|
colSpan: field.colSpan,
|
|
399
420
|
colStart: field.colStart,
|
|
400
|
-
|
|
421
|
+
rowStart: field.rowStart,
|
|
422
|
+
rowSpan: options.freeGrid
|
|
423
|
+
? (rowSpan > 1 ? rowSpan : undefined)
|
|
424
|
+
: (rowSpan === defaultRowSpan(field.type) ? undefined : rowSpan),
|
|
401
425
|
mColSpan: field.mColSpan,
|
|
426
|
+
mColStart: field.mColStart,
|
|
427
|
+
mRowStart: field.mRowStart,
|
|
428
|
+
mRowSpan: field.mRowSpan,
|
|
402
429
|
})
|
|
403
430
|
if (layoutClass) prop['x-ui'] = { ...prop['x-ui'], class: layoutClass }
|
|
404
431
|
|
package/src/styles/inputs.css
CHANGED
|
@@ -509,6 +509,22 @@ select {
|
|
|
509
509
|
row-gap: 0.5rem;
|
|
510
510
|
}
|
|
511
511
|
|
|
512
|
+
.grid-form.free-grid {
|
|
513
|
+
grid-auto-rows: 72px;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.grid-form.free-grid > * {
|
|
517
|
+
grid-row-start: var(--bgl-grid-row-start, auto);
|
|
518
|
+
grid-row-end: span var(--bgl-grid-row-span, 1);
|
|
519
|
+
min-height: 0;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.grid-form.free-grid > * > .bagel-input {
|
|
523
|
+
height: 100%;
|
|
524
|
+
min-height: 0;
|
|
525
|
+
margin-bottom: 0;
|
|
526
|
+
}
|
|
527
|
+
|
|
512
528
|
.grid-form.thin .bagel-input,
|
|
513
529
|
.grid-form.dense .bagel-input {
|
|
514
530
|
margin-bottom: 0;
|
|
@@ -620,6 +636,11 @@ select {
|
|
|
620
636
|
}
|
|
621
637
|
|
|
622
638
|
@media screen and (max-width: 910px) {
|
|
639
|
+
.grid-form.free-grid > * {
|
|
640
|
+
grid-row-start: var(--bgl-mobile-grid-row-start, var(--bgl-grid-row-start, auto));
|
|
641
|
+
grid-row-end: span var(--bgl-mobile-grid-row-span, var(--bgl-grid-row-span, 1));
|
|
642
|
+
}
|
|
643
|
+
|
|
623
644
|
.bagel-input.wider input {
|
|
624
645
|
min-width: 120px;
|
|
625
646
|
}
|
|
@@ -717,4 +738,17 @@ select {
|
|
|
717
738
|
.m_col-12\/12 {
|
|
718
739
|
grid-column: span 12 !important;
|
|
719
740
|
}
|
|
741
|
+
|
|
742
|
+
.grid-form.free-grid .m_start-col-1 { grid-column-start: 1 !important; }
|
|
743
|
+
.grid-form.free-grid .m_start-col-2 { grid-column-start: 2 !important; }
|
|
744
|
+
.grid-form.free-grid .m_start-col-3 { grid-column-start: 3 !important; }
|
|
745
|
+
.grid-form.free-grid .m_start-col-4 { grid-column-start: 4 !important; }
|
|
746
|
+
.grid-form.free-grid .m_start-col-5 { grid-column-start: 5 !important; }
|
|
747
|
+
.grid-form.free-grid .m_start-col-6 { grid-column-start: 6 !important; }
|
|
748
|
+
.grid-form.free-grid .m_start-col-7 { grid-column-start: 7 !important; }
|
|
749
|
+
.grid-form.free-grid .m_start-col-8 { grid-column-start: 8 !important; }
|
|
750
|
+
.grid-form.free-grid .m_start-col-9 { grid-column-start: 9 !important; }
|
|
751
|
+
.grid-form.free-grid .m_start-col-10 { grid-column-start: 10 !important; }
|
|
752
|
+
.grid-form.free-grid .m_start-col-11 { grid-column-start: 11 !important; }
|
|
753
|
+
.grid-form.free-grid .m_start-col-12 { grid-column-start: 12 !important; }
|
|
720
754
|
}
|