@bagelink/vue 1.12.67 → 1.12.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/FilterQuery.types.d.ts +19 -0
- package/dist/components/FilterQuery.types.d.ts.map +1 -0
- package/dist/components/FilterQuery.vue.d.ts.map +1 -1
- package/dist/components/analytics/PieChart.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/ArrayInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/CheckInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/ColorInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DateInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/EmailInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/JSONInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/MarkdownEditor.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/NumberInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/OTP.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/PasswordInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RangeInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectBtn.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SignaturePad.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TableField.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TelInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/ToggleInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/bagelInputShell.d.ts +21 -0
- package/dist/components/form/inputs/bagelInputShell.d.ts.map +1 -0
- package/dist/components/form/inputs/index.d.ts.map +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/dialog/Dialog.vue.d.ts.map +1 -1
- package/dist/dialog/useDialog.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/index.cjs +135 -135
- package/dist/index.mjs +14829 -14450
- package/dist/style.css +1 -1
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/dist/utils/BagelFormUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Dropdown.vue +2 -1
- package/src/components/FilterQuery.types.ts +21 -0
- package/src/components/FilterQuery.vue +112 -15
- package/src/components/form/inputs/ArrayInput.vue +10 -2
- package/src/components/form/inputs/CheckInput.vue +28 -9
- package/src/components/form/inputs/CodeEditor/Index.vue +15 -2
- package/src/components/form/inputs/ColorInput.vue +77 -9
- package/src/components/form/inputs/DateInput.vue +10 -3
- package/src/components/form/inputs/EmailInput.vue +90 -54
- package/src/components/form/inputs/JSONInput.vue +12 -4
- package/src/components/form/inputs/MarkdownEditor.vue +12 -4
- package/src/components/form/inputs/NumberInput.vue +154 -89
- package/src/components/form/inputs/OTP.vue +46 -7
- package/src/components/form/inputs/PasswordInput.vue +32 -21
- package/src/components/form/inputs/RadioGroup.vue +18 -7
- package/src/components/form/inputs/RangeInput.vue +23 -7
- package/src/components/form/inputs/RichText/index.vue +21 -12
- package/src/components/form/inputs/SelectBtn.vue +34 -6
- package/src/components/form/inputs/SelectInput.vue +19 -25
- package/src/components/form/inputs/SignaturePad.vue +39 -14
- package/src/components/form/inputs/TableField.vue +6 -2
- package/src/components/form/inputs/TelInput.vue +52 -4
- package/src/components/form/inputs/TextInput.vue +23 -31
- package/src/components/form/inputs/ToggleInput.vue +27 -4
- package/src/components/form/inputs/Upload/UploadInput.vue +47 -11
- package/src/components/form/inputs/Upload/upload.css +23 -0
- package/src/components/form/inputs/bagelInputShell.ts +43 -0
- package/src/components/form/inputs/index.ts +1 -0
- package/src/components/index.ts +1 -0
- package/src/dialog/Dialog.vue +12 -1
- package/src/dialog/useDialog.ts +2 -1
- package/src/form-flow/FormFlow.vue +3 -1
- package/src/i18n/locales/en.json +4 -1
- package/src/i18n/locales/es.json +4 -1
- package/src/i18n/locales/fr.json +4 -1
- package/src/i18n/locales/he.json +4 -1
- package/src/i18n/locales/it.json +4 -1
- package/src/i18n/locales/ru.json +4 -1
- package/src/styles/input-variants.css +12 -13
- package/src/styles/inputs.css +134 -15
- package/src/styles/text.css +534 -528
- package/src/types/BagelForm.ts +13 -1
- package/src/utils/BagelFormUtils.ts +1 -0
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import type { IconType } from '@bagelink/vue'
|
|
3
3
|
import { Icon, Btn, resolveI18n } from '@bagelink/vue'
|
|
4
4
|
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
|
5
|
+
import type { BagelInputShellProps } from './bagelInputShell'
|
|
6
|
+
import { useBagelInputShell } from './bagelInputShell'
|
|
5
7
|
|
|
6
8
|
type NumberLayout = 'default' | 'vertical' | 'horizontal'
|
|
7
9
|
|
|
8
|
-
interface NumberInputProps {
|
|
10
|
+
interface NumberInputProps extends BagelInputShellProps {
|
|
9
11
|
modelValue?: number | string
|
|
10
12
|
min?: number
|
|
11
13
|
max?: number
|
|
@@ -46,9 +48,19 @@ const {
|
|
|
46
48
|
padZero = 1,
|
|
47
49
|
defaultValue,
|
|
48
50
|
useGrouping = true,
|
|
49
|
-
error
|
|
51
|
+
error,
|
|
52
|
+
frame,
|
|
53
|
+
outline,
|
|
54
|
+
minWidth,
|
|
55
|
+
maxWidth,
|
|
56
|
+
labelColor,
|
|
57
|
+
labelActiveColor,
|
|
50
58
|
} = defineProps<NumberInputProps>()
|
|
51
59
|
|
|
60
|
+
const { shellClass, shellStyle } = useBagelInputShell({
|
|
61
|
+
frame, outline, minWidth, maxWidth, labelColor, labelActiveColor,
|
|
62
|
+
})
|
|
63
|
+
|
|
52
64
|
const emit = defineEmits(['update:modelValue'])
|
|
53
65
|
|
|
54
66
|
const numberValue = ref<number>()
|
|
@@ -61,78 +73,74 @@ onMounted(() => {
|
|
|
61
73
|
|
|
62
74
|
const btnLayouts: NumberLayout[] = ['horizontal', 'vertical']
|
|
63
75
|
|
|
76
|
+
const TRAILING_ZEROS_RE = /\.\d*0$/
|
|
77
|
+
|
|
78
|
+
function getPrecision() {
|
|
79
|
+
return step.toString().split('.')[1]?.length || 0
|
|
80
|
+
}
|
|
81
|
+
|
|
64
82
|
function add(...numbers: (number | undefined)[]) {
|
|
65
83
|
const numArr: number[] = numbers.map(n => n || 0)
|
|
66
|
-
|
|
67
|
-
return Number.parseFloat(numArr.reduce((acc, curr) => acc + curr, 0).toFixed(precision))
|
|
84
|
+
return Number.parseFloat(numArr.reduce((acc, curr) => acc + curr, 0).toFixed(getPrecision()))
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
function subtract(...numbers: (number | undefined)[]) {
|
|
71
88
|
const numArr: number[] = numbers.map(n => n || 0)
|
|
72
89
|
const firstNum = numArr.shift() || 0
|
|
73
|
-
|
|
74
|
-
return Number.parseFloat(numArr.reduce((acc, curr) => acc - curr, firstNum).toFixed(precision))
|
|
90
|
+
return Number.parseFloat(numArr.reduce((acc, curr) => acc - curr, firstNum).toFixed(getPrecision()))
|
|
75
91
|
}
|
|
76
92
|
|
|
77
|
-
const
|
|
93
|
+
const canIncrement = computed(() => max === undefined || add(numberValue.value, step) <= max)
|
|
78
94
|
const canDecrement = computed(() => min === undefined || subtract(numberValue.value, step) >= min)
|
|
79
95
|
|
|
80
|
-
// Methods
|
|
81
96
|
function increment() {
|
|
82
|
-
if (!
|
|
97
|
+
if (!canIncrement.value) { return }
|
|
83
98
|
numberValue.value = add(numberValue.value ?? 0, step)
|
|
84
99
|
emit('update:modelValue', numberValue.value)
|
|
85
100
|
}
|
|
86
101
|
|
|
87
102
|
function decrement() {
|
|
88
103
|
if (!canDecrement.value) { return }
|
|
89
|
-
numberValue.value = subtract(numberValue.value
|
|
104
|
+
numberValue.value = subtract(numberValue.value ?? 0, step)
|
|
90
105
|
emit('update:modelValue', numberValue.value)
|
|
91
106
|
}
|
|
92
107
|
|
|
108
|
+
const numberFormatter = computed(() => new Intl.NumberFormat('en-US', {
|
|
109
|
+
minimumFractionDigits: 0,
|
|
110
|
+
maximumFractionDigits: 20,
|
|
111
|
+
useGrouping,
|
|
112
|
+
}))
|
|
113
|
+
|
|
93
114
|
function formatNumber(num: number) {
|
|
94
115
|
if (Number.isNaN(num)) { return '' }
|
|
95
|
-
const formatter = new Intl.NumberFormat('en-US', {
|
|
96
|
-
minimumFractionDigits: 0,
|
|
97
|
-
maximumFractionDigits: 20,
|
|
98
|
-
useGrouping
|
|
99
|
-
})
|
|
100
116
|
|
|
101
|
-
const formattedNum =
|
|
117
|
+
const formattedNum = numberFormatter.value.format(num)
|
|
102
118
|
const [integerPart, decimalPart] = formattedNum.split('.')
|
|
103
119
|
|
|
104
|
-
|
|
105
|
-
const padding = padZero && padZero >
|
|
106
|
-
? '0'.repeat(padZero - integerPart.replace(/,/g, '').length)
|
|
107
|
-
: ''
|
|
120
|
+
const rawLength = integerPart.replace(/,/g, '').length
|
|
121
|
+
const padding = padZero && padZero > rawLength ? '0'.repeat(padZero - rawLength) : ''
|
|
108
122
|
|
|
109
|
-
return decimalPart
|
|
110
|
-
? `${padding}${integerPart}.${decimalPart}`
|
|
111
|
-
: `${padding}${integerPart}`
|
|
123
|
+
return decimalPart ? `${padding}${integerPart}.${decimalPart}` : `${padding}${integerPart}`
|
|
112
124
|
}
|
|
113
125
|
|
|
114
126
|
const formattedValue = ref('')
|
|
115
127
|
function inputHandler() {
|
|
116
128
|
const numeric = formattedValue.value.replace(/[^\d.-]/g, '')
|
|
117
|
-
const
|
|
118
|
-
const isTypingDecimal = numeric.endsWith('.')
|
|
119
|
-
const isTypingTrailingZeros = /\.\d*0$/.test(numeric) // Check if ending with decimal followed by digits ending in 0
|
|
129
|
+
const isIncomplete = ['', '-', '.', '-.'].includes(numeric) || numeric.endsWith('.') || TRAILING_ZEROS_RE.test(numeric)
|
|
120
130
|
|
|
121
|
-
if (
|
|
131
|
+
if (isIncomplete) {
|
|
122
132
|
emit('update:modelValue', numeric === '-' ? '-' : numeric)
|
|
123
|
-
// Don't reformat while typing decimal values
|
|
124
133
|
return
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
numberValue.value = Number.parseFloat(numeric)
|
|
128
|
-
formattedValue.value =
|
|
137
|
+
formattedValue.value = formatNumber(numberValue.value)
|
|
129
138
|
emit('update:modelValue', numberValue.value)
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
watch(() => numberValue.value, () => {
|
|
133
142
|
nextTick(() => {
|
|
134
|
-
|
|
135
|
-
if (formattedValue.value.endsWith('.') || /\.\d*0$/.test(formattedValue.value)) { return }
|
|
143
|
+
if (formattedValue.value.endsWith('.') || TRAILING_ZEROS_RE.test(formattedValue.value)) { return }
|
|
136
144
|
formattedValue.value = numberValue.value !== undefined ? formatNumber(numberValue.value) : ''
|
|
137
145
|
})
|
|
138
146
|
})
|
|
@@ -147,62 +155,60 @@ watch(() => modelValue, (newVal) => {
|
|
|
147
155
|
}
|
|
148
156
|
}, { immediate: true })
|
|
149
157
|
|
|
150
|
-
const hasValue = computed(() =>
|
|
151
|
-
const val = formattedValue.value
|
|
152
|
-
return Boolean(val && String(val).length > 0)
|
|
153
|
-
})
|
|
158
|
+
const hasValue = computed(() => formattedValue.value.length > 0)
|
|
154
159
|
</script>
|
|
155
160
|
|
|
156
161
|
<template>
|
|
157
162
|
<div
|
|
158
|
-
class="bagel-input num-input" :class="
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
class="bagel-input num-input" :class="[
|
|
164
|
+
shellClass,
|
|
165
|
+
{
|
|
166
|
+
underlined,
|
|
167
|
+
'has-error': !!error,
|
|
168
|
+
'has-value': hasValue,
|
|
169
|
+
},
|
|
170
|
+
]" :style="shellStyle"
|
|
165
171
|
>
|
|
166
|
-
<div
|
|
172
|
+
<div>
|
|
167
173
|
<label v-if="label || (underlined && placeholder)" class="block">
|
|
168
174
|
<span class="label-text">{{ resolveI18n(label) || resolveI18n(placeholder) }}<span v-if="required"> *</span></span>
|
|
169
175
|
</label>
|
|
170
|
-
<div class="gap-025" :class="{ 'column flex': layout === 'vertical', 'flex': layout === 'horizontal' }">
|
|
176
|
+
<div class="gap-025" :class="{ 'column display-flex': layout === 'vertical', 'flex': layout === 'horizontal' }">
|
|
171
177
|
<Btn
|
|
172
178
|
v-if="layout && btnLayouts.includes(layout)" flat icon="add" class="radius"
|
|
173
|
-
:class="
|
|
179
|
+
:class="{ 'bgl-big-ctrl-num-btn': layout === 'vertical' }" tabindex="-1" @click="increment"
|
|
174
180
|
/>
|
|
175
|
-
<input
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
181
|
+
<div class="input-icon-wrap flex-grow-1">
|
|
182
|
+
<Icon v-if="iconStart" class="iconStart" size="0.9" :icon="iconStart" />
|
|
183
|
+
<input
|
|
184
|
+
:id v-model.trim="formattedValue" v-pattern.number type="text" :class="{
|
|
185
|
+
'txt-center': center,
|
|
186
|
+
'min0': layout,
|
|
187
|
+
}" :placeholder="underlined ? ' ' : (resolveI18n(placeholder) || resolveI18n(label))" :disabled :required inputmode="decimal"
|
|
188
|
+
autocomplete="off" :pattern="useGrouping ? '^-?\\d{1,3}(,\\d{3})*(\.\\d+)?$' : '^-?\\d*\.?\d*$'"
|
|
189
|
+
autocorrect="off" spellcheck="false" @input="inputHandler" @keydown.up.prevent="increment"
|
|
190
|
+
@keydown.down.prevent="decrement"
|
|
191
|
+
>
|
|
192
|
+
<Icon v-if="icon" size="0.9" :icon />
|
|
193
|
+
<div v-if="spinner && (!layout || !btnLayouts.includes(layout))" class="flex column spinner-btns">
|
|
194
|
+
<Btn
|
|
195
|
+
icon="add" flat thin class="bgl-ctrl-num-btn ctrl-add top-bgl-ctrl-num-btn" :disabled="!canIncrement"
|
|
196
|
+
tabindex="-1" @click="increment"
|
|
197
|
+
/>
|
|
198
|
+
<Btn
|
|
199
|
+
icon="remove" flat thin class="bgl-ctrl-num-btn ctrl-remove" :disabled="!canDecrement"
|
|
200
|
+
tabindex="-1" @click="decrement"
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
190
204
|
<Btn
|
|
191
205
|
v-if="layout && btnLayouts.includes(layout)" flat icon="remove" class="radius"
|
|
192
206
|
:class="[{ 'bgl-big-ctrl-num-btn': layout === 'vertical' }]" tabindex="-1" @click="decrement"
|
|
193
207
|
/>
|
|
194
|
-
|
|
195
|
-
<div v-if="spinner && (!layout || !btnLayouts.includes(layout))" class="flex column spinner">
|
|
196
|
-
<Btn
|
|
197
|
-
icon="add" flat thin class="bgl-ctrl-num-btn ctrl-add top-bgl-ctrl-num-btn" :disabled="!canAdd"
|
|
198
|
-
tabindex="-1" @click="increment"
|
|
199
|
-
/>
|
|
200
|
-
<Btn
|
|
201
|
-
icon="remove" flat thin class="bgl-ctrl-num-btn ctrl-remove" :disabled="!canDecrement"
|
|
202
|
-
tabindex="-1" @click="decrement"
|
|
203
|
-
/>
|
|
204
|
-
</div>
|
|
205
208
|
</div>
|
|
209
|
+
<p v-if="helptext" class="opacity-7 light">
|
|
210
|
+
{{ helptext }}
|
|
211
|
+
</p>
|
|
206
212
|
</div>
|
|
207
213
|
<div v-if="error" class="error-message">
|
|
208
214
|
{{ error }}
|
|
@@ -211,29 +217,92 @@ const hasValue = computed(() => {
|
|
|
211
217
|
</template>
|
|
212
218
|
|
|
213
219
|
<style scoped>
|
|
214
|
-
.
|
|
215
|
-
|
|
220
|
+
.input-icon-wrap {
|
|
221
|
+
display: flex;
|
|
222
|
+
align-items: center;
|
|
223
|
+
background: var(--input-bg);
|
|
224
|
+
border-radius: var(--input-border-radius);
|
|
225
|
+
height: var(--input-height);
|
|
226
|
+
transition: box-shadow 0.2s ease, outline-color 0.2s ease;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.input-icon-wrap input {
|
|
230
|
+
flex: 1;
|
|
231
|
+
min-width: 0;
|
|
232
|
+
background: transparent !important;
|
|
233
|
+
outline: none !important;
|
|
234
|
+
box-shadow: none !important;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.input-icon-wrap:focus-within {
|
|
238
|
+
outline-color: var(--input-bg);
|
|
239
|
+
box-shadow: inset 0 0 8px #00000018;
|
|
216
240
|
}
|
|
217
241
|
|
|
218
|
-
.
|
|
242
|
+
.input-icon-wrap .iconStart,
|
|
243
|
+
.input-icon-wrap > .bgl_icon-font {
|
|
244
|
+
flex-shrink: 0;
|
|
219
245
|
color: var(--input-color);
|
|
220
|
-
position: absolute;
|
|
221
|
-
inset-inline-start: calc(var(--input-height) / 3 - 0.25rem);
|
|
222
|
-
margin-top: calc(var(--input-height) / 2);
|
|
223
246
|
line-height: 0;
|
|
247
|
+
padding-inline: 0.35rem;
|
|
224
248
|
}
|
|
225
249
|
|
|
226
|
-
|
|
227
|
-
|
|
250
|
+
/* ── frame variant ─────────────────────────────────────────────── */
|
|
251
|
+
.bagel-input.frame .input-icon-wrap {
|
|
252
|
+
background: transparent;
|
|
253
|
+
outline: 1.5px solid var(--border-color);
|
|
254
|
+
outline-offset: -1px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.bagel-input.frame .input-icon-wrap:focus-within {
|
|
258
|
+
outline-color: var(--bgl-input-label-active-color, var(--bgl-primary)) !important;
|
|
259
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--bgl-input-label-active-color, var(--bgl-primary)) 12%, transparent) !important;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* ── outline variant ───────────────────────────────────────────── */
|
|
263
|
+
.bagel-input.bgl-outline .input-icon-wrap {
|
|
264
|
+
outline: 1.5px solid var(--border-color);
|
|
265
|
+
outline-offset: -1px;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* ── underlined variant ────────────────────────────────────────── */
|
|
269
|
+
.bagel-input.underlined .input-icon-wrap {
|
|
270
|
+
position: relative;
|
|
271
|
+
background: transparent;
|
|
272
|
+
border-radius: 0;
|
|
273
|
+
height: auto;
|
|
274
|
+
border-bottom: 1.5px solid var(--border-color);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.bagel-input.underlined .input-icon-wrap::after {
|
|
278
|
+
content: '';
|
|
228
279
|
position: absolute;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
280
|
+
bottom: -1px;
|
|
281
|
+
inset-inline-start: 0;
|
|
282
|
+
width: 0;
|
|
283
|
+
height: 2px;
|
|
284
|
+
background: var(--bgl-input-label-active-color, var(--bgl-primary));
|
|
285
|
+
transition: width 0.6s ease;
|
|
286
|
+
pointer-events: none;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.bagel-input.underlined .input-icon-wrap:focus-within {
|
|
290
|
+
box-shadow: none;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.bagel-input.underlined .input-icon-wrap:focus-within::after {
|
|
294
|
+
width: 100%;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.bagel-input.underlined .input-icon-wrap input {
|
|
298
|
+
border-bottom: none !important;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.spinner-btns {
|
|
302
|
+
flex-shrink: 0;
|
|
234
303
|
display: flex;
|
|
235
304
|
flex-direction: column;
|
|
236
|
-
|
|
305
|
+
margin-inline-end: 0.25rem;
|
|
237
306
|
}
|
|
238
307
|
|
|
239
308
|
.top-bgl-ctrl-num-btn {
|
|
@@ -250,10 +319,6 @@ const hasValue = computed(() => {
|
|
|
250
319
|
isolation: isolate;
|
|
251
320
|
}
|
|
252
321
|
|
|
253
|
-
.bgl-number-input {
|
|
254
|
-
padding-inline-end: 1.75rem !important;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
322
|
.bagel-input.has-error input {
|
|
258
323
|
border-color: var(--bgl-red, #dc3545) !important;
|
|
259
324
|
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { reactive, ref } from 'vue'
|
|
2
|
+
import { computed, reactive, ref } from 'vue'
|
|
3
|
+
import type { BagelInputShellProps } from './bagelInputShell'
|
|
4
|
+
import { useBagelInputShell } from './bagelInputShell'
|
|
3
5
|
|
|
4
|
-
const props = defineProps<{
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
digitCount: number
|
|
8
|
+
default?: string
|
|
9
|
+
modelValue?: string
|
|
10
|
+
error?: string
|
|
11
|
+
} & BagelInputShellProps>()
|
|
12
|
+
|
|
13
|
+
const { shellClass, shellStyle } = useBagelInputShell(props)
|
|
14
|
+
const hasValue = computed(() => digits.some(d => d !== undefined && d !== ''))
|
|
5
15
|
|
|
6
16
|
const emit = defineEmits(['update:modelValue', 'complete'])
|
|
7
17
|
const digits = reactive<(number | string | undefined)[]>([])
|
|
@@ -87,7 +97,10 @@ function isDigitsFull() {
|
|
|
87
97
|
</script>
|
|
88
98
|
|
|
89
99
|
<template>
|
|
90
|
-
<div
|
|
100
|
+
<div
|
|
101
|
+
class="otp-input" :class="[shellClass, { 'has-value': hasValue }]" :style="shellStyle"
|
|
102
|
+
tabindex="-1"
|
|
103
|
+
>
|
|
91
104
|
<div ref="otpCont" class="otp_wrap ltr" :class="{ 'has-error': !!error }">
|
|
92
105
|
<input
|
|
93
106
|
v-for="(_el, ind) in digits" :key="ind" :value="digits[ind] || ''" type="number" inputmode="numeric"
|
|
@@ -121,7 +134,7 @@ function isDigitsFull() {
|
|
|
121
134
|
.digit-box {
|
|
122
135
|
height: 3rem;
|
|
123
136
|
flex-grow: 1;
|
|
124
|
-
border: 1px solid var(--bgl-primary-tint);
|
|
137
|
+
border: 1px solid var(--bgl-input-label-active-color, var(--bgl-primary-tint));
|
|
125
138
|
display: inline-block;
|
|
126
139
|
background: var(--bgl-gray-light);
|
|
127
140
|
border-radius: 5px;
|
|
@@ -145,11 +158,37 @@ function isDigitsFull() {
|
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
.digit-box:focus {
|
|
148
|
-
outline: 1px solid var(--bgl-primary);
|
|
149
|
-
filter: drop-shadow(0 0 0.25rem var(--bgl-primary));
|
|
161
|
+
outline: 1px solid var(--bgl-input-label-active-color, var(--bgl-primary));
|
|
162
|
+
filter: drop-shadow(0 0 0.25rem var(--bgl-input-label-active-color, var(--bgl-primary)));
|
|
163
|
+
outline-offset: 1px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* ── frame variant ──────────────────────────────────────────────────────── */
|
|
167
|
+
|
|
168
|
+
.otp-input.frame .digit-box {
|
|
169
|
+
background: transparent;
|
|
170
|
+
outline: 1.5px solid var(--border-color);
|
|
171
|
+
outline-offset: -1px;
|
|
172
|
+
transition: outline-color 0.2s ease, box-shadow 0.2s ease;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.otp-input.frame .digit-box:focus {
|
|
176
|
+
outline-color: var(--bgl-input-label-active-color, var(--bgl-primary));
|
|
177
|
+
outline-width: 2px;
|
|
178
|
+
box-shadow:
|
|
179
|
+
0 0 0 4px color-mix(in srgb, var(--bgl-input-label-active-color, var(--bgl-primary)) 15%, transparent),
|
|
180
|
+
0 2px 8px color-mix(in srgb, var(--bgl-input-label-active-color, var(--bgl-primary)) 25%, transparent);
|
|
181
|
+
filter: none;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* ── bgl-outline variant ────────────────────────────────────────────────── */
|
|
185
|
+
|
|
186
|
+
.otp-input.bgl-outline .digit-box {
|
|
187
|
+
outline: 1.5px solid var(--border-color);
|
|
188
|
+
outline-offset: -1px;
|
|
150
189
|
}
|
|
151
190
|
|
|
152
|
-
/* Redundant, already covered above */
|
|
191
|
+
/* Redundant, already covered above */
|
|
153
192
|
/* .digit-box[type="number"] {
|
|
154
193
|
-moz-appearance: textfield;
|
|
155
194
|
} */
|
|
@@ -1,49 +1,60 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { IconType } from '@bagelink/vue'
|
|
2
|
+
import type { IconType, ValidateInputBaseT } from '@bagelink/vue'
|
|
3
3
|
import { Btn, TextInput, useI18n } from '@bagelink/vue'
|
|
4
4
|
import { zxcvbn } from '@zxcvbn-ts/core'
|
|
5
5
|
import { computed } from 'vue'
|
|
6
|
+
import type { BagelInputShellProps } from './bagelInputShell'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
defineProps<TextInputProps>(),
|
|
9
|
-
{
|
|
10
|
-
autocomplete: 'current-password',
|
|
11
|
-
label: '',
|
|
12
|
-
strengthMeter: false
|
|
13
|
-
}
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
const { $t } = useI18n()
|
|
17
|
-
|
|
18
|
-
export interface TextInputProps {
|
|
8
|
+
export interface TextInputProps extends ValidateInputBaseT, BagelInputShellProps {
|
|
19
9
|
id?: string
|
|
20
10
|
title?: string
|
|
21
11
|
helptext?: string
|
|
12
|
+
name?: string
|
|
22
13
|
placeholder?: string
|
|
14
|
+
modelValue?: string
|
|
23
15
|
label?: string
|
|
24
16
|
small?: boolean
|
|
25
17
|
dense?: boolean
|
|
26
18
|
required?: boolean
|
|
27
19
|
pattern?: string
|
|
20
|
+
defaultValue?: string
|
|
28
21
|
shrink?: boolean
|
|
29
22
|
underlined?: boolean
|
|
30
23
|
disabled?: boolean
|
|
31
|
-
|
|
24
|
+
type?: string
|
|
32
25
|
nativeInputAttrs?: { [key: string]: any }
|
|
33
26
|
icon?: IconType
|
|
34
27
|
iconStart?: IconType
|
|
35
28
|
multiline?: boolean
|
|
36
29
|
autoheight?: boolean
|
|
37
30
|
code?: boolean
|
|
38
|
-
|
|
31
|
+
rows?: number | string
|
|
32
|
+
autocomplete?: string
|
|
39
33
|
autofocus?: boolean
|
|
40
|
-
|
|
41
|
-
autocomplete?: AutoFillField
|
|
34
|
+
error?: string
|
|
42
35
|
onFocusout?: (e: FocusEvent) => void
|
|
36
|
+
onFocus?: (e: FocusEvent) => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface PasswordInputProps extends TextInputProps {
|
|
43
40
|
strengthMeter?: boolean
|
|
44
|
-
error?: string
|
|
45
41
|
}
|
|
46
|
-
|
|
42
|
+
|
|
43
|
+
const props = withDefaults(defineProps<PasswordInputProps>(), {
|
|
44
|
+
autocomplete: 'current-password',
|
|
45
|
+
label: '',
|
|
46
|
+
strengthMeter: false,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const { $t } = useI18n()
|
|
50
|
+
|
|
51
|
+
const textInputProps = computed(() => {
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
53
|
+
const { strengthMeter: _strengthMeter, modelValue: _modelValue, ...rest } = props
|
|
54
|
+
return rest
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const password = defineModel<string>()
|
|
47
58
|
const showPwd = defineModel<boolean>('showPwd', { default: false })
|
|
48
59
|
|
|
49
60
|
const toggleShowPwdIcon = computed(() => showPwd.value ? 'visibility_off' : 'visibility')
|
|
@@ -94,14 +105,14 @@ const feedbackMessage = computed(() => {
|
|
|
94
105
|
if (!strength)
|
|
95
106
|
return ''
|
|
96
107
|
const { feedback: { warning, suggestions } } = strength
|
|
97
|
-
const warnings =
|
|
108
|
+
const warnings = warning ? [warning] : []
|
|
98
109
|
return [...warnings, ...suggestions].join(' ')
|
|
99
110
|
})
|
|
100
111
|
</script>
|
|
101
112
|
|
|
102
113
|
<template>
|
|
103
114
|
<div class="relative passwordInput" :class="{ 'relative mb-2': props.strengthMeter, 'has-error': !!props.error, 'underlined': props.underlined }">
|
|
104
|
-
<TextInput v-model="password" v-bind="
|
|
115
|
+
<TextInput v-model="password" v-bind="textInputProps" :type="inputType" class="mb-0" />
|
|
105
116
|
<div class="m-password position-bottom-end flex column justify-content-center">
|
|
106
117
|
<Btn flat thin class="mx-05" :icon="toggleShowPwdIcon" @click="showPwd = !showPwd" />
|
|
107
118
|
</div>
|
|
@@ -3,6 +3,8 @@ import type { Option } from '@bagelink/vue'
|
|
|
3
3
|
import { Btn, Icon, resolveI18n } from '@bagelink/vue'
|
|
4
4
|
import { computed, ref, watch } from 'vue'
|
|
5
5
|
import { normalizeOption } from '../../../utils/options'
|
|
6
|
+
import type { BagelInputShellProps } from './bagelInputShell'
|
|
7
|
+
import { useBagelInputShell } from './bagelInputShell'
|
|
6
8
|
|
|
7
9
|
export interface RadioOption<T = any> {
|
|
8
10
|
imgAlt?: string
|
|
@@ -36,10 +38,11 @@ const props = withDefaults(
|
|
|
36
38
|
hideRadio?: boolean
|
|
37
39
|
bgColor?: string
|
|
38
40
|
activeBgColor?: string
|
|
41
|
+
activeTextColor?: string
|
|
39
42
|
borderColor?: string
|
|
40
43
|
textColor?: string
|
|
41
44
|
textAlign?: 'left' | 'center' | 'right'
|
|
42
|
-
}>(),
|
|
45
|
+
} & BagelInputShellProps>(),
|
|
43
46
|
{
|
|
44
47
|
align: 'center'
|
|
45
48
|
}
|
|
@@ -47,6 +50,8 @@ const props = withDefaults(
|
|
|
47
50
|
|
|
48
51
|
const emit = defineEmits(['delete', 'focus', 'blur', 'change'])
|
|
49
52
|
|
|
53
|
+
const { shellClass, shellStyle } = useBagelInputShell(props)
|
|
54
|
+
|
|
50
55
|
const name = computed(
|
|
51
56
|
() => (
|
|
52
57
|
props.groupName
|
|
@@ -78,9 +83,13 @@ watch(() => props.options, () => {
|
|
|
78
83
|
const isFocused = ref(false)
|
|
79
84
|
|
|
80
85
|
const containerClasses = computed(() => ({
|
|
86
|
+
'bagel-input': true,
|
|
87
|
+
...shellClass.value,
|
|
81
88
|
'has-error': !!props.error,
|
|
82
89
|
'is-disabled': props.disabled,
|
|
83
|
-
'is-focused': isFocused.value
|
|
90
|
+
'is-focused': isFocused.value,
|
|
91
|
+
'has-value': selectedOption.value != null && selectedOption.value !== '',
|
|
92
|
+
'open': isFocused.value,
|
|
84
93
|
}))
|
|
85
94
|
|
|
86
95
|
function handleFocus() {
|
|
@@ -99,7 +108,7 @@ function handleChange() {
|
|
|
99
108
|
</script>
|
|
100
109
|
|
|
101
110
|
<template>
|
|
102
|
-
<div :class="containerClasses">
|
|
111
|
+
<div :class="containerClasses" :style="shellStyle">
|
|
103
112
|
<p v-if="label" class="group-label">
|
|
104
113
|
{{ resolveI18n(label) }} <span v-if="required">*</span>
|
|
105
114
|
</p>
|
|
@@ -129,11 +138,11 @@ function handleChange() {
|
|
|
129
138
|
</div>
|
|
130
139
|
</template>
|
|
131
140
|
|
|
141
|
+
|
|
132
142
|
<style scoped>
|
|
133
143
|
.group-label {
|
|
134
144
|
font-size: var(--label-font-size);
|
|
135
145
|
margin: 0 0 0.25rem 0;
|
|
136
|
-
color: var(--label-color);
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
.hideRadio.radio-input-list {
|
|
@@ -142,9 +151,9 @@ function handleChange() {
|
|
|
142
151
|
|
|
143
152
|
.radio-input-list {
|
|
144
153
|
width: auto;
|
|
145
|
-
transform: scale(1.2);
|
|
146
154
|
margin-inline-end: 0.5rem;
|
|
147
155
|
margin-top: 0;
|
|
156
|
+
accent-color: var(--bgl-input-label-active-color, var(--bgl-primary));
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
.radio-input-list.hidden {
|
|
@@ -153,8 +162,9 @@ function handleChange() {
|
|
|
153
162
|
|
|
154
163
|
.active-list-item:has(:checked) {
|
|
155
164
|
background: v-bind('activeBgColor || "var(--bgl-primary-light)"') !important;
|
|
156
|
-
border-color: var(--bgl-primary) !important;
|
|
157
|
-
accent-color: var(--bgl-
|
|
165
|
+
border-color: var(--bgl-input-label-active-color, var(--bgl-primary)) !important;
|
|
166
|
+
accent-color: var(--bgl-input-label-active-color, var(--bgl-primary));
|
|
167
|
+
color: v-bind('activeTextColor || null') !important;
|
|
158
168
|
}
|
|
159
169
|
|
|
160
170
|
.invertedActive:has(:checked) {
|
|
@@ -171,4 +181,5 @@ function handleChange() {
|
|
|
171
181
|
.has-error :is(input[type="radio"]) {
|
|
172
182
|
accent-color: var(--bgl-red);
|
|
173
183
|
}
|
|
184
|
+
|
|
174
185
|
</style>
|