@bagelink/vue 0.0.1035 → 0.0.1041
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/BglComponent.vue.d.ts +24 -0
- package/dist/components/BglComponent.vue.d.ts.map +1 -0
- package/dist/components/FieldSetVue.vue.d.ts +22 -0
- package/dist/components/FieldSetVue.vue.d.ts.map +1 -0
- package/dist/components/Pill.vue.d.ts +1 -0
- package/dist/components/Pill.vue.d.ts.map +1 -1
- package/dist/components/TableSchema.vue.d.ts.map +1 -1
- package/dist/components/form/BglField.vue.d.ts +8 -6
- package/dist/components/form/BglField.vue.d.ts.map +1 -1
- package/dist/components/form/BglForm.vue.d.ts +5 -4
- package/dist/components/form/BglForm.vue.d.ts.map +1 -1
- package/dist/components/form/BglMultiStepForm.vue.d.ts +2 -2
- package/dist/components/form/FieldArray.vue.d.ts +1 -0
- package/dist/components/form/FieldArray.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/NumberInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RadioGroup.vue.d.ts +1 -0
- package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RangeInput.vue.d.ts +4 -3
- package/dist/components/form/inputs/RangeInput.vue.d.ts.map +1 -1
- package/dist/components/form/useBagelFormState.d.ts +11 -0
- package/dist/components/form/useBagelFormState.d.ts.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.cjs +7085 -9121
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +7091 -9127
- package/dist/style.css +504 -495
- package/dist/utils/BagelFormUtils.d.ts +31 -17
- package/dist/utils/BagelFormUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/BglComponent.vue +174 -0
- package/src/components/FieldSetVue.vue +23 -0
- package/src/components/ModalForm.vue +1 -1
- package/src/components/Pill.vue +3 -2
- package/src/components/TableSchema.vue +3 -2
- package/src/components/form/BglField.vue +50 -77
- package/src/components/form/BglForm.vue +44 -31
- package/src/components/form/FieldArray.vue +25 -19
- package/src/components/form/inputs/NumberInput.vue +7 -8
- package/src/components/form/inputs/RadioGroup.vue +2 -0
- package/src/components/form/inputs/RangeInput.vue +75 -42
- package/src/components/form/useBagelFormState.ts +87 -0
- package/src/components/index.ts +5 -2
- package/src/index.ts +3 -1
- package/src/utils/BagelFormUtils.ts +12 -1
- package/src/components/FieldSet.vue +0 -15
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { BglField, type BglFormSchemaFnT, Title, useBglSchema, useModal } from '@bagelink/vue'
|
|
3
|
-
import { useSlots, watch } from 'vue'
|
|
3
|
+
import { inject, useSlots, watch } from 'vue'
|
|
4
|
+
import { FORM_STATE_KEY, provideBagelFormState, type BagelFormState } from './useBagelFormState'
|
|
4
5
|
|
|
5
6
|
export type FormStatus = 'idle' | 'loading' | 'success' | 'error'
|
|
6
7
|
|
|
@@ -22,41 +23,55 @@ const props = withDefaults(
|
|
|
22
23
|
const emit = defineEmits(['update:modelValue', 'submit', 'dirty'])
|
|
23
24
|
|
|
24
25
|
const slots = useSlots()
|
|
25
|
-
|
|
26
26
|
const { showModal } = useModal()
|
|
27
|
-
|
|
28
27
|
const instAt = new Date()
|
|
29
28
|
const timeSinceInst = () => Date.now() - instAt.getTime()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
|
|
30
|
+
// Check if we're a nested form
|
|
31
|
+
const existingFormState = inject<BagelFormState | undefined>(FORM_STATE_KEY)
|
|
32
|
+
const isNested = Boolean(existingFormState && props.id)
|
|
33
|
+
|
|
34
|
+
// Only provide new form state if we're not nested
|
|
35
|
+
const { data, isDirty } = isNested
|
|
36
|
+
? existingFormState!
|
|
37
|
+
: provideBagelFormState(props.modelValue)
|
|
38
|
+
|
|
39
|
+
// Only watch for external updates if we're not nested
|
|
40
|
+
if (!isNested) {
|
|
41
|
+
watch(() => props.modelValue, (newVal) => {
|
|
42
|
+
if (JSON.stringify(newVal) !== JSON.stringify(data.value)) {
|
|
43
|
+
data.value = newVal
|
|
44
|
+
}
|
|
45
|
+
}, { deep: true, immediate: true })
|
|
46
|
+
|
|
47
|
+
// Emit updates when internal state changes
|
|
48
|
+
watch(data, (newVal) => {
|
|
49
|
+
if (timeSinceInst() > 100 && JSON.stringify(newVal) !== JSON.stringify(props.modelValue)) {
|
|
35
50
|
emit('dirty')
|
|
36
|
-
|
|
51
|
+
emit('update:modelValue', newVal)
|
|
37
52
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
get: () => formData,
|
|
42
|
-
})
|
|
53
|
+
}, { deep: true })
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
const form = $ref<HTMLFormElement>()
|
|
44
57
|
|
|
45
58
|
function validateForm() {
|
|
46
|
-
|
|
47
|
-
setTimeout(() => form?.reportValidity(), 300) // TODO: check if browser is safari
|
|
48
|
-
|
|
59
|
+
setTimeout(() => form?.reportValidity(), 300)
|
|
49
60
|
if (!form) return false
|
|
50
61
|
return form.reportValidity()
|
|
51
62
|
}
|
|
52
63
|
|
|
53
|
-
|
|
64
|
+
function clearForm() {
|
|
65
|
+
if (!isNested) {
|
|
66
|
+
data.value = {}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
54
69
|
|
|
55
70
|
watch(
|
|
56
71
|
() => props.status,
|
|
57
72
|
(status) => {
|
|
58
|
-
if (status === 'success') {
|
|
59
|
-
isDirty = false
|
|
73
|
+
if (status === 'success' && !isNested) {
|
|
74
|
+
isDirty.value = false
|
|
60
75
|
clearForm()
|
|
61
76
|
}
|
|
62
77
|
},
|
|
@@ -66,9 +81,10 @@ watch(
|
|
|
66
81
|
function runSubmit() {
|
|
67
82
|
const isValid = validateForm()
|
|
68
83
|
if (!isValid) return
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
84
|
+
emit('submit', { ...data.value })
|
|
85
|
+
if (!isNested) {
|
|
86
|
+
isDirty.value = false
|
|
87
|
+
}
|
|
72
88
|
}
|
|
73
89
|
|
|
74
90
|
const i18nT = (val: string) => val
|
|
@@ -91,9 +107,7 @@ function deleteItem() {
|
|
|
91
107
|
)
|
|
92
108
|
}
|
|
93
109
|
|
|
94
|
-
const computedSchema = $computed(
|
|
95
|
-
() => useBglSchema({ schema: props.schema })
|
|
96
|
-
)
|
|
110
|
+
const computedSchema = $computed(() => useBglSchema({ schema: props.schema }))
|
|
97
111
|
|
|
98
112
|
defineExpose({
|
|
99
113
|
submit: runSubmit,
|
|
@@ -106,14 +120,13 @@ defineExpose({
|
|
|
106
120
|
|
|
107
121
|
<template>
|
|
108
122
|
<template v-if="id">
|
|
109
|
-
<Title v-if="label" tag="h4" :label="label" />
|
|
110
123
|
<BglField
|
|
111
124
|
v-for="(field, i) in computedSchema"
|
|
125
|
+
:id="[id, field.id].filter(Boolean).join('.')"
|
|
112
126
|
:key="field.id || `${i}p`"
|
|
113
|
-
v-model="data"
|
|
114
127
|
:field="field"
|
|
115
128
|
/>
|
|
116
|
-
<slot name="submit" :submit="runSubmit" :isDirty :validateForm />
|
|
129
|
+
<slot name="submit" :submit="runSubmit" :isDirty="isDirty" :validateForm="validateForm" />
|
|
117
130
|
</template>
|
|
118
131
|
<form
|
|
119
132
|
v-else-if="!slots.success || status !== 'success'"
|
|
@@ -123,11 +136,11 @@ defineExpose({
|
|
|
123
136
|
<Title v-if="label" tag="h4" :label="label" />
|
|
124
137
|
<BglField
|
|
125
138
|
v-for="(field, i) in computedSchema"
|
|
139
|
+
:id="field.id"
|
|
126
140
|
:key="field.id || `${i}p`"
|
|
127
|
-
v-model="data"
|
|
128
141
|
:field="field"
|
|
129
142
|
/>
|
|
130
|
-
<slot name="submit" :submit="runSubmit" :isDirty :validateForm />
|
|
143
|
+
<slot name="submit" :submit="runSubmit" :isDirty="isDirty" :validateForm="validateForm" />
|
|
131
144
|
</form>
|
|
132
145
|
<slot v-if="status === 'success'" name="success" />
|
|
133
146
|
<slot v-if="status === 'error'" name="error" />
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
BglFormSchemaFnT,
|
|
8
8
|
Field,
|
|
9
9
|
} from '@bagelink/vue'
|
|
10
|
-
import { BglForm, Btn } from '@bagelink/vue'
|
|
10
|
+
import { BglForm, BglField, Btn } from '@bagelink/vue'
|
|
11
11
|
|
|
12
12
|
const props = withDefaults(
|
|
13
13
|
defineProps<{
|
|
@@ -25,6 +25,7 @@ const props = withDefaults(
|
|
|
25
25
|
defaultValue?: any
|
|
26
26
|
add?: boolean
|
|
27
27
|
delete?: boolean
|
|
28
|
+
transform?: (value: T) => T
|
|
28
29
|
schema: BglFormSchemaFnT
|
|
29
30
|
modelValue: T[]
|
|
30
31
|
}>(),
|
|
@@ -53,31 +54,33 @@ function addItem() {
|
|
|
53
54
|
emitValue()
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
const computedField = $computed(
|
|
58
|
+
() => ({
|
|
59
|
+
label: props.label,
|
|
60
|
+
placeholder: props.placeholder,
|
|
61
|
+
children: props.children,
|
|
62
|
+
// class: props.class,
|
|
63
|
+
attrs: props.attrs,
|
|
64
|
+
required: props.required,
|
|
65
|
+
disabled: props.disabled,
|
|
66
|
+
helptext: props.helptext,
|
|
67
|
+
options: props.options,
|
|
68
|
+
defaultValue: props.defaultValue,
|
|
69
|
+
transform: props.transform,
|
|
70
|
+
$el: props.el,
|
|
71
|
+
}) as Field<T>
|
|
72
|
+
) as Field<Record<string, any>>
|
|
71
73
|
</script>
|
|
72
74
|
|
|
73
75
|
<template>
|
|
74
|
-
<div>
|
|
76
|
+
<div :class="props.class">
|
|
75
77
|
<p class="label mb-05">
|
|
76
78
|
{{ label }}
|
|
77
79
|
</p>
|
|
78
|
-
|
|
80
|
+
|
|
81
|
+
<div v-if="schema" class="-ms-05 ps-05 border-start">
|
|
79
82
|
<div v-for="(_, i) in data" :key="i" outline thin class="mb-05 itemBox transition p-05">
|
|
80
|
-
<BglForm v-
|
|
83
|
+
<BglForm v-model="data[i]" :schema="schema" @update:model-value="emitValue" />
|
|
81
84
|
<Btn
|
|
82
85
|
v-if="props.delete"
|
|
83
86
|
icon="delete"
|
|
@@ -92,6 +95,9 @@ function addItem() {
|
|
|
92
95
|
<p>Add {{ label }}</p>
|
|
93
96
|
</Btn>
|
|
94
97
|
</div>
|
|
98
|
+
<template v-else>
|
|
99
|
+
<BglField v-for="(_, i) in data" :key="i" v-model="data[i]" :field="computedField" @update:model-value="emitValue" />
|
|
100
|
+
</template>
|
|
95
101
|
</div>
|
|
96
102
|
</template>
|
|
97
103
|
|
|
@@ -47,17 +47,16 @@ const btnLayouts: NumberLayout[] = ['horizontal', 'vertical']
|
|
|
47
47
|
|
|
48
48
|
// Methods
|
|
49
49
|
function increment() {
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
50
|
+
if (max !== undefined && (numberValue + step) > max) return
|
|
51
|
+
numberValue = (numberValue || 0) + step
|
|
52
|
+
emit('update:modelValue', numberValue)
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
function decrement() {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
numberValue = numberValue || 0
|
|
57
|
+
if (min !== undefined && (numberValue - step) < min) return
|
|
58
|
+
numberValue = numberValue - step
|
|
59
|
+
emit('update:modelValue', numberValue)
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
function formatNumber(num: number) {
|
|
@@ -15,6 +15,7 @@ defineProps<{
|
|
|
15
15
|
groupName: string
|
|
16
16
|
options: RadioOption<ContextObjType>[]
|
|
17
17
|
deletable?: boolean
|
|
18
|
+
required?: boolean
|
|
18
19
|
}>()
|
|
19
20
|
defineEmits(['delete'])
|
|
20
21
|
|
|
@@ -36,6 +37,7 @@ const selectedOption = defineModel('modelValue')
|
|
|
36
37
|
type="radio"
|
|
37
38
|
:name="groupName"
|
|
38
39
|
:value="opt.value"
|
|
40
|
+
:required="required"
|
|
39
41
|
>
|
|
40
42
|
<div class="flex w-100 gap-1 flex-wrap m_gap-05 m_gap-row-025">
|
|
41
43
|
<img
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { watch } from 'vue'
|
|
3
3
|
|
|
4
|
-
interface
|
|
4
|
+
export interface RangeInputProps {
|
|
5
5
|
modelValue: number | [number, number]
|
|
6
6
|
min?: number
|
|
7
7
|
max?: number
|
|
@@ -11,45 +11,81 @@ interface props {
|
|
|
11
11
|
disabled?: boolean
|
|
12
12
|
id?: string
|
|
13
13
|
rtl?: boolean
|
|
14
|
+
multiRange?: boolean
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
-
|
|
17
|
+
const props = defineProps<RangeInputProps>()
|
|
18
18
|
const emit = defineEmits(['update:modelValue'])
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const {
|
|
21
|
+
min = 0,
|
|
22
|
+
max = 100,
|
|
23
|
+
step = 1,
|
|
24
|
+
label,
|
|
25
|
+
disabled,
|
|
26
|
+
id,
|
|
27
|
+
rtl,
|
|
28
|
+
multiRange
|
|
29
|
+
} = props
|
|
21
30
|
|
|
22
31
|
let from = $ref<number>(min)
|
|
23
32
|
let to = $ref<number>(max)
|
|
24
33
|
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
let isExternalUpdate = $ref(true)
|
|
35
|
+
|
|
36
|
+
const validFrom = $computed(() => Math.min(Math.max(from, min), multiRange ? to : max))
|
|
37
|
+
const validTo = $computed(() => Math.max(Math.min(to, max), from))
|
|
38
|
+
|
|
39
|
+
watch(() => props.modelValue, (newVal) => {
|
|
40
|
+
isExternalUpdate = true
|
|
41
|
+
if (multiRange && Array.isArray(newVal)) {
|
|
42
|
+
from = newVal[0]
|
|
43
|
+
to = newVal[1]
|
|
44
|
+
} else if (!multiRange && !Array.isArray(newVal)) {
|
|
45
|
+
from = newVal ?? min
|
|
46
|
+
to = max
|
|
47
|
+
}
|
|
48
|
+
}, { immediate: true })
|
|
49
|
+
|
|
50
|
+
watch([() => validFrom, () => validTo], () => {
|
|
51
|
+
if (!isExternalUpdate) {
|
|
52
|
+
if (multiRange) {
|
|
53
|
+
emit('update:modelValue', [validFrom, validTo])
|
|
54
|
+
} else {
|
|
55
|
+
emit('update:modelValue', validFrom)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
isExternalUpdate = false
|
|
59
|
+
})
|
|
27
60
|
|
|
28
|
-
function
|
|
29
|
-
|
|
61
|
+
function handleInput(value: number, isFromInput: boolean) {
|
|
62
|
+
isExternalUpdate = false
|
|
63
|
+
if (isFromInput) {
|
|
64
|
+
from = value
|
|
65
|
+
} else {
|
|
66
|
+
to = value
|
|
67
|
+
}
|
|
30
68
|
}
|
|
31
69
|
|
|
32
|
-
watch(() => validFrom, (newVal) => {
|
|
33
|
-
if (from !== newVal) from = newVal
|
|
34
|
-
})
|
|
35
|
-
watch(() => validTo, (newVal) => {
|
|
36
|
-
if (to !== newVal) to = newVal
|
|
37
|
-
})
|
|
38
|
-
|
|
39
70
|
const fromPercentage = $computed(() => ((validFrom - min) / (max - min)) * 100)
|
|
40
71
|
const toPercentage = $computed(() => ((validTo - min) / (max - min)) * 100)
|
|
41
72
|
|
|
42
73
|
const rangeStyle = $computed(() => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
74
|
+
if (multiRange) {
|
|
75
|
+
return {
|
|
76
|
+
left: `${fromPercentage}%`,
|
|
77
|
+
width: `${toPercentage - fromPercentage}%`
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return rtl
|
|
81
|
+
? { left: `${100 - fromPercentage}%`, width: `${fromPercentage}%` }
|
|
82
|
+
: { left: '0', width: `${fromPercentage}%` }
|
|
47
83
|
})
|
|
48
84
|
</script>
|
|
49
85
|
|
|
50
86
|
<template>
|
|
51
87
|
<div>
|
|
52
|
-
<label class="label">{{ label }}</label>
|
|
88
|
+
<label v-if="label" class="label">{{ label }}</label>
|
|
53
89
|
<div class="range-slider relative w-100" :dir="rtl ? 'rtl' : 'ltr'">
|
|
54
90
|
<input
|
|
55
91
|
:id="id"
|
|
@@ -57,25 +93,25 @@ const rangeStyle = $computed(() => {
|
|
|
57
93
|
class="from"
|
|
58
94
|
type="range"
|
|
59
95
|
:min="min"
|
|
60
|
-
:max="max"
|
|
96
|
+
:max="multiRange ? to : max"
|
|
61
97
|
:step="step"
|
|
62
98
|
:required="required"
|
|
63
99
|
:disabled="disabled"
|
|
64
|
-
aria-label="Minimum value"
|
|
65
|
-
@
|
|
100
|
+
:aria-label="multiRange ? 'Minimum value' : 'Value'"
|
|
101
|
+
@input="(e) => handleInput(+(e.target as HTMLInputElement).value, true)"
|
|
66
102
|
>
|
|
67
103
|
<input
|
|
68
|
-
v-if="
|
|
104
|
+
v-if="multiRange"
|
|
69
105
|
v-model="to"
|
|
70
106
|
class="to"
|
|
71
107
|
type="range"
|
|
72
|
-
:min="
|
|
108
|
+
:min="from"
|
|
73
109
|
:max="max"
|
|
74
110
|
:step="step"
|
|
75
111
|
:required="required"
|
|
76
112
|
:disabled="disabled"
|
|
77
113
|
aria-label="Maximum value"
|
|
78
|
-
@
|
|
114
|
+
@input="(e) => handleInput(+(e.target as HTMLInputElement).value, false)"
|
|
79
115
|
>
|
|
80
116
|
<div class="track absolute pointer-events-none overflow-hidden round">
|
|
81
117
|
<div
|
|
@@ -84,31 +120,23 @@ const rangeStyle = $computed(() => {
|
|
|
84
120
|
/>
|
|
85
121
|
</div>
|
|
86
122
|
<p
|
|
87
|
-
v-if="
|
|
123
|
+
v-if="validFrom !== min"
|
|
88
124
|
class="txt-center txt-12 range-slider-position-txt absolute line-height-1 opacity-0"
|
|
89
125
|
:style="{ '--progress': `${fromPercentage}` }"
|
|
90
126
|
>
|
|
91
|
-
|
|
92
|
-
{{ from }}
|
|
93
|
-
</span>
|
|
127
|
+
{{ validFrom }}
|
|
94
128
|
</p>
|
|
95
129
|
<p
|
|
96
|
-
v-if="
|
|
130
|
+
v-if="multiRange && validTo !== max"
|
|
97
131
|
class="txt-center txt-12 range-slider-position-txt opacity-0 line-height-1 absolute"
|
|
98
132
|
:style="{ '--progress': `${toPercentage}` }"
|
|
99
133
|
>
|
|
100
|
-
|
|
101
|
-
{{ to }}
|
|
102
|
-
</span>
|
|
134
|
+
{{ validTo }}
|
|
103
135
|
</p>
|
|
104
136
|
</div>
|
|
105
137
|
<p class="txt-center txt-14 range-slider-txt flex space-between opacity-4 mx-05">
|
|
106
|
-
<span>
|
|
107
|
-
|
|
108
|
-
</span>
|
|
109
|
-
<span>
|
|
110
|
-
{{ max }}
|
|
111
|
-
</span>
|
|
138
|
+
<span>{{ min }}</span>
|
|
139
|
+
<span>{{ max }}</span>
|
|
112
140
|
</p>
|
|
113
141
|
</div>
|
|
114
142
|
</template>
|
|
@@ -125,8 +153,8 @@ const rangeStyle = $computed(() => {
|
|
|
125
153
|
opacity: 1;
|
|
126
154
|
transform: scale(1);
|
|
127
155
|
top: calc(var(--bgl-range-thumb-size) / 1) ;
|
|
128
|
-
|
|
129
156
|
}
|
|
157
|
+
|
|
130
158
|
.range-slider {
|
|
131
159
|
height: var(--bgl-range-track-size);
|
|
132
160
|
display: flex;
|
|
@@ -158,9 +186,10 @@ input[type="range"]::-webkit-slider-thumb {
|
|
|
158
186
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
159
187
|
position: relative;
|
|
160
188
|
z-index: 2;
|
|
161
|
-
cursor:
|
|
189
|
+
cursor: grab;
|
|
162
190
|
transition: var(--bgl-transition);
|
|
163
191
|
}
|
|
192
|
+
|
|
164
193
|
input[type="range"]::-webkit-slider-thumb:hover {
|
|
165
194
|
box-shadow: 0 0 0 calc(var(--bgl-range-thumb-size) / 2) var(--bgl-primary-tint);
|
|
166
195
|
filter: brightness(90%);
|
|
@@ -171,6 +200,10 @@ input[type="range"]:focus::-webkit-slider-thumb {
|
|
|
171
200
|
box-shadow: 0 0 0 calc(var(--bgl-range-thumb-size) / 2.2) var(--bgl-primary-tint);
|
|
172
201
|
}
|
|
173
202
|
|
|
203
|
+
input[type="range"]:active::-webkit-slider-thumb {
|
|
204
|
+
cursor: grabbing;
|
|
205
|
+
}
|
|
206
|
+
|
|
174
207
|
.track {
|
|
175
208
|
height: var(--bgl-range-track-height);
|
|
176
209
|
background: var(--bgl-bg);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { inject, provide, ref, type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export const FORM_STATE_KEY = Symbol('bagelFormState')
|
|
4
|
+
|
|
5
|
+
export interface BagelFormState<T = any> {
|
|
6
|
+
data: Ref<T>
|
|
7
|
+
getFieldData: (path?: string) => any
|
|
8
|
+
updateField: (path: string, value: any) => void
|
|
9
|
+
isDirty: Ref<boolean>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Helper function to safely clone objects without circular references
|
|
13
|
+
function safeClone(obj: any): any {
|
|
14
|
+
if (obj === null || typeof obj !== 'object') return obj
|
|
15
|
+
|
|
16
|
+
const seen = new WeakSet()
|
|
17
|
+
return JSON.parse(JSON.stringify(obj, (key, value) => {
|
|
18
|
+
if (typeof value === 'object' && value !== null) {
|
|
19
|
+
if (seen.has(value)) {
|
|
20
|
+
return undefined // Remove circular reference
|
|
21
|
+
}
|
|
22
|
+
seen.add(value)
|
|
23
|
+
}
|
|
24
|
+
return value
|
|
25
|
+
}))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function provideBagelFormState<T>(initialData: T) {
|
|
29
|
+
const data = ref(initialData) as Ref<T>
|
|
30
|
+
const isDirty = ref(false)
|
|
31
|
+
|
|
32
|
+
const getFieldData = (path?: string) => {
|
|
33
|
+
if (!path) return ''
|
|
34
|
+
const keys = path.split(/[.[]/)
|
|
35
|
+
let current = data.value as any
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < keys.length; i++) {
|
|
38
|
+
const key = keys[i]
|
|
39
|
+
if (!current || typeof current !== 'object' || !(key in current)) {
|
|
40
|
+
return ''
|
|
41
|
+
}
|
|
42
|
+
current = current[key]
|
|
43
|
+
}
|
|
44
|
+
return current ?? ''
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const updateField = (path: string, value: any) => {
|
|
48
|
+
const keys = path.split(/[.[]/)
|
|
49
|
+
|
|
50
|
+
// Initialize the root if it's not an object
|
|
51
|
+
if (typeof data.value !== 'object' || data.value === null) {
|
|
52
|
+
data.value = {} as T
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let current = data.value as any
|
|
56
|
+
|
|
57
|
+
// Build the path, ensuring each level is an object
|
|
58
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
59
|
+
const key = keys[i]
|
|
60
|
+
if (!(key in current) || typeof current[key] !== 'object' || current[key] === null) {
|
|
61
|
+
current[key] = {}
|
|
62
|
+
}
|
|
63
|
+
current = current[key]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Safely clone the value to remove circular references
|
|
67
|
+
const safeValue = safeClone(value)
|
|
68
|
+
current[keys[keys.length - 1]] = safeValue
|
|
69
|
+
isDirty.value = true
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const state: BagelFormState<T> = {
|
|
73
|
+
data,
|
|
74
|
+
getFieldData,
|
|
75
|
+
updateField,
|
|
76
|
+
isDirty
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
provide(FORM_STATE_KEY, state)
|
|
80
|
+
return state
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function useBagelFormState<T>() {
|
|
84
|
+
const state = inject<BagelFormState<T>>(FORM_STATE_KEY)
|
|
85
|
+
if (!state) throw new Error('BagelFormState must be provided')
|
|
86
|
+
return state
|
|
87
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { default as AddressSearch } from './AddressSearch.vue'
|
|
|
4
4
|
export { default as Alert } from './Alert.vue'
|
|
5
5
|
export { default as Avatar } from './Avatar.vue'
|
|
6
6
|
export { default as Badge } from './Badge.vue'
|
|
7
|
+
export { default as BglComponent } from './BglComponent.vue'
|
|
7
8
|
export { default as BglVideo } from './BglVideo.vue'
|
|
8
9
|
export { default as Btn } from './Btn.vue'
|
|
9
10
|
export { default as Card } from './Card.vue'
|
|
@@ -11,6 +12,7 @@ export { default as Carousel } from './Carousel.vue'
|
|
|
11
12
|
export * from './dashboard'
|
|
12
13
|
export { default as DataPreview } from './DataPreview.vue'
|
|
13
14
|
export { default as Dropdown } from './Dropdown.vue'
|
|
15
|
+
export { default as FieldSetVue } from './FieldSetVue.vue'
|
|
14
16
|
export { default as Flag } from './Flag.vue'
|
|
15
17
|
export * from './form'
|
|
16
18
|
export { default as IframeVue } from './IframeVue.vue'
|
|
@@ -22,17 +24,18 @@ export { default as Loading } from './Loading.vue'
|
|
|
22
24
|
export { default as MapEmbed } from './MapEmbed.vue'
|
|
23
25
|
export { default as MaterialIcon } from './MaterialIcon.vue'
|
|
24
26
|
export { default as Icon } from './MaterialIcon.vue'
|
|
27
|
+
|
|
25
28
|
export { default as Modal } from './Modal.vue'
|
|
26
29
|
export { default as ModalConfirm } from './ModalConfirm.vue'
|
|
27
30
|
export { default as ModalForm } from './ModalForm.vue'
|
|
28
|
-
|
|
29
31
|
export { default as NavBar } from './NavBar.vue'
|
|
30
32
|
export { default as PageTitle } from './PageTitle.vue'
|
|
31
33
|
export { default as Pill } from './Pill.vue'
|
|
32
34
|
export { default as RouterWrapper } from './RouterWrapper.vue'
|
|
33
35
|
export { default as TableSchema } from './TableSchema.vue'
|
|
36
|
+
|
|
34
37
|
export { default as Title } from './Title.vue'
|
|
35
38
|
export { default as ToolBar } from './ToolBar.vue'
|
|
36
|
-
export { default as TopBar } from './TopBar.vue'
|
|
37
39
|
|
|
40
|
+
export { default as TopBar } from './TopBar.vue'
|
|
38
41
|
export { default as Zoomer } from './Zoomer.vue'
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './components'
|
|
2
|
+
export * from './components/form/useBagelFormState'
|
|
2
3
|
export * from './composables'
|
|
3
4
|
export {
|
|
4
5
|
bagelInjectionKey,
|
|
@@ -13,5 +14,6 @@ export * from './types'
|
|
|
13
14
|
export * from './utils'
|
|
14
15
|
export * from './utils/allCountries'
|
|
15
16
|
export * from './utils/constants'
|
|
16
|
-
export * from './utils/timeAgo'
|
|
17
17
|
import './styles/bagel.css'
|
|
18
|
+
|
|
19
|
+
export * from './utils/timeAgo'
|
|
@@ -8,6 +8,7 @@ interface InputOptions {
|
|
|
8
8
|
defaultValue?: string
|
|
9
9
|
disabled?: boolean
|
|
10
10
|
helptext?: string
|
|
11
|
+
vIf?: boolean | ((item: any, row: any) => boolean)
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
type DateOptions = InputOptions
|
|
@@ -50,6 +51,7 @@ export function richText(
|
|
|
50
51
|
required: options?.required,
|
|
51
52
|
id,
|
|
52
53
|
label,
|
|
54
|
+
vIf: options?.vIf,
|
|
53
55
|
placeholder: options?.placeholder,
|
|
54
56
|
attrs: {},
|
|
55
57
|
}
|
|
@@ -66,6 +68,7 @@ export function txtField(
|
|
|
66
68
|
required: options?.required,
|
|
67
69
|
id,
|
|
68
70
|
label,
|
|
71
|
+
vIf: options?.vIf,
|
|
69
72
|
disabled: options?.disabled,
|
|
70
73
|
placeholder: options?.placeholder,
|
|
71
74
|
defaultValue: options?.defaultValue,
|
|
@@ -93,6 +96,7 @@ export function slctField(
|
|
|
93
96
|
required: config?.required,
|
|
94
97
|
label,
|
|
95
98
|
defaultValue: config?.defaultValue,
|
|
99
|
+
vIf: config?.vIf,
|
|
96
100
|
attrs: {
|
|
97
101
|
disabled: config?.disabled,
|
|
98
102
|
searchable: config?.searchable,
|
|
@@ -130,6 +134,7 @@ export function dateField(
|
|
|
130
134
|
disabled: options?.disabled,
|
|
131
135
|
label,
|
|
132
136
|
defaultValue: options?.defaultValue,
|
|
137
|
+
vIf: options?.vIf,
|
|
133
138
|
attrs: {
|
|
134
139
|
disabled: options?.disabled,
|
|
135
140
|
},
|
|
@@ -151,6 +156,7 @@ export function numField(
|
|
|
151
156
|
disabled: options?.disabled,
|
|
152
157
|
placeholder: options?.placeholder,
|
|
153
158
|
helptext: options?.helptext,
|
|
159
|
+
vIf: options?.vIf,
|
|
154
160
|
attrs: {
|
|
155
161
|
step: options?.step,
|
|
156
162
|
min: options?.min,
|
|
@@ -176,7 +182,10 @@ export function uploadField(id: string, label?: string, options?: UploadOptions)
|
|
|
176
182
|
$el: 'upload',
|
|
177
183
|
id,
|
|
178
184
|
label,
|
|
179
|
-
|
|
185
|
+
vIf: options?.vIf,
|
|
186
|
+
attrs: {
|
|
187
|
+
...options,
|
|
188
|
+
},
|
|
180
189
|
}
|
|
181
190
|
}
|
|
182
191
|
|
|
@@ -203,6 +212,7 @@ export function telField(id: string, label?: string, options?: { [key: string]:
|
|
|
203
212
|
$el: markRaw(TelInput),
|
|
204
213
|
id,
|
|
205
214
|
label,
|
|
215
|
+
vIf: options?.vIf,
|
|
206
216
|
attrs: options,
|
|
207
217
|
}
|
|
208
218
|
}
|
|
@@ -228,6 +238,7 @@ export function arrField(id: string, label: string, schema: BglFormSchemaT, opti
|
|
|
228
238
|
label,
|
|
229
239
|
id,
|
|
230
240
|
$el: 'array',
|
|
241
|
+
vIf: options?.vIf,
|
|
231
242
|
attrs: { schema, delete: true, add: true, ...options },
|
|
232
243
|
}
|
|
233
244
|
}
|