@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.
Files changed (85) hide show
  1. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  2. package/dist/components/FilterQuery.types.d.ts +19 -0
  3. package/dist/components/FilterQuery.types.d.ts.map +1 -0
  4. package/dist/components/FilterQuery.vue.d.ts.map +1 -1
  5. package/dist/components/analytics/PieChart.vue.d.ts.map +1 -1
  6. package/dist/components/form/inputs/ArrayInput.vue.d.ts.map +1 -1
  7. package/dist/components/form/inputs/CheckInput.vue.d.ts.map +1 -1
  8. package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -1
  9. package/dist/components/form/inputs/ColorInput.vue.d.ts.map +1 -1
  10. package/dist/components/form/inputs/DateInput.vue.d.ts.map +1 -1
  11. package/dist/components/form/inputs/EmailInput.vue.d.ts.map +1 -1
  12. package/dist/components/form/inputs/JSONInput.vue.d.ts.map +1 -1
  13. package/dist/components/form/inputs/MarkdownEditor.vue.d.ts.map +1 -1
  14. package/dist/components/form/inputs/NumberInput.vue.d.ts.map +1 -1
  15. package/dist/components/form/inputs/OTP.vue.d.ts.map +1 -1
  16. package/dist/components/form/inputs/PasswordInput.vue.d.ts.map +1 -1
  17. package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
  18. package/dist/components/form/inputs/RangeInput.vue.d.ts.map +1 -1
  19. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  20. package/dist/components/form/inputs/SelectBtn.vue.d.ts.map +1 -1
  21. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  22. package/dist/components/form/inputs/SignaturePad.vue.d.ts.map +1 -1
  23. package/dist/components/form/inputs/TableField.vue.d.ts.map +1 -1
  24. package/dist/components/form/inputs/TelInput.vue.d.ts.map +1 -1
  25. package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
  26. package/dist/components/form/inputs/ToggleInput.vue.d.ts.map +1 -1
  27. package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts.map +1 -1
  28. package/dist/components/form/inputs/bagelInputShell.d.ts +21 -0
  29. package/dist/components/form/inputs/bagelInputShell.d.ts.map +1 -0
  30. package/dist/components/form/inputs/index.d.ts.map +1 -1
  31. package/dist/components/index.d.ts.map +1 -1
  32. package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
  33. package/dist/dialog/Dialog.vue.d.ts.map +1 -1
  34. package/dist/dialog/useDialog.d.ts.map +1 -1
  35. package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
  36. package/dist/i18n/index.d.ts.map +1 -1
  37. package/dist/index.cjs +135 -135
  38. package/dist/index.mjs +14829 -14450
  39. package/dist/style.css +1 -1
  40. package/dist/types/BagelForm.d.ts.map +1 -1
  41. package/dist/utils/BagelFormUtils.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/src/components/Dropdown.vue +2 -1
  44. package/src/components/FilterQuery.types.ts +21 -0
  45. package/src/components/FilterQuery.vue +112 -15
  46. package/src/components/form/inputs/ArrayInput.vue +10 -2
  47. package/src/components/form/inputs/CheckInput.vue +28 -9
  48. package/src/components/form/inputs/CodeEditor/Index.vue +15 -2
  49. package/src/components/form/inputs/ColorInput.vue +77 -9
  50. package/src/components/form/inputs/DateInput.vue +10 -3
  51. package/src/components/form/inputs/EmailInput.vue +90 -54
  52. package/src/components/form/inputs/JSONInput.vue +12 -4
  53. package/src/components/form/inputs/MarkdownEditor.vue +12 -4
  54. package/src/components/form/inputs/NumberInput.vue +154 -89
  55. package/src/components/form/inputs/OTP.vue +46 -7
  56. package/src/components/form/inputs/PasswordInput.vue +32 -21
  57. package/src/components/form/inputs/RadioGroup.vue +18 -7
  58. package/src/components/form/inputs/RangeInput.vue +23 -7
  59. package/src/components/form/inputs/RichText/index.vue +21 -12
  60. package/src/components/form/inputs/SelectBtn.vue +34 -6
  61. package/src/components/form/inputs/SelectInput.vue +19 -25
  62. package/src/components/form/inputs/SignaturePad.vue +39 -14
  63. package/src/components/form/inputs/TableField.vue +6 -2
  64. package/src/components/form/inputs/TelInput.vue +52 -4
  65. package/src/components/form/inputs/TextInput.vue +23 -31
  66. package/src/components/form/inputs/ToggleInput.vue +27 -4
  67. package/src/components/form/inputs/Upload/UploadInput.vue +47 -11
  68. package/src/components/form/inputs/Upload/upload.css +23 -0
  69. package/src/components/form/inputs/bagelInputShell.ts +43 -0
  70. package/src/components/form/inputs/index.ts +1 -0
  71. package/src/components/index.ts +1 -0
  72. package/src/dialog/Dialog.vue +12 -1
  73. package/src/dialog/useDialog.ts +2 -1
  74. package/src/form-flow/FormFlow.vue +3 -1
  75. package/src/i18n/locales/en.json +4 -1
  76. package/src/i18n/locales/es.json +4 -1
  77. package/src/i18n/locales/fr.json +4 -1
  78. package/src/i18n/locales/he.json +4 -1
  79. package/src/i18n/locales/it.json +4 -1
  80. package/src/i18n/locales/ru.json +4 -1
  81. package/src/styles/input-variants.css +12 -13
  82. package/src/styles/inputs.css +134 -15
  83. package/src/styles/text.css +534 -528
  84. package/src/types/BagelForm.ts +13 -1
  85. 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
- const precision = step.toString().split('.')[1]?.length || 0
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
- const precision = step.toString().split('.')[1]?.length || 0
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 canAdd = computed(() => max === undefined || add(numberValue.value, step) <= max)
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 (!canAdd.value) { return }
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 || 0, step)
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 = formatter.format(num)
117
+ const formattedNum = numberFormatter.value.format(num)
102
118
  const [integerPart, decimalPart] = formattedNum.split('.')
103
119
 
104
- // Add padding zeros if needed
105
- const padding = padZero && padZero > integerPart.replace(/,/g, '').length
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 emptyValue = ['', '-', '.', '-.'].includes(numeric)
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 (emptyValue || isTypingDecimal || isTypingTrailingZeros) {
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 = emptyValue ? '' : formatNumber(numberValue.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
- // Don't reformat if user is currently typing a decimal or trailing zeros
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
- 'textInputSpinnerWrap': icon || spinner,
160
- 'txtInputIconStart': iconStart,
161
- underlined,
162
- 'has-error': !!error,
163
- 'has-value': hasValue,
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 :for="id">
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="[{ 'bgl-big-ctrl-num-btn': layout === 'vertical' }]" tabindex="-1" @click="increment"
179
+ :class="{ 'bgl-big-ctrl-num-btn': layout === 'vertical' }" tabindex="-1" @click="increment"
174
180
  />
175
- <input
176
- :id v-model.trim="formattedValue" v-pattern.number type="text" :class="{
177
- 'txt-center': center,
178
- 'min0': layout,
179
- 'bgl-number-input': layout !== 'vertical' && layout !== 'horizontal',
180
- }" :placeholder="underlined ? ' ' : (resolveI18n(placeholder) || resolveI18n(label))" :disabled :required :readonly="disabled" inputmode="decimal"
181
- autocomplete="off" :pattern="useGrouping ? '^-?\\d{1,3}(,\\d{3})*(\.\\d+)?$' : '^-?\\d*\.?\d*$'"
182
- autocorrect="off" spellcheck="false" @input="inputHandler" @keydown.up.prevent="increment"
183
- @keydown.down.prevent="decrement"
184
- >
185
- <p v-if="helptext" class="opacity-7 light">
186
- {{ helptext }}
187
- </p>
188
- <Icon v-if="iconStart" :class="{ ' bottom-1-25 ': underlined }" class="iconStart" size="0.9" :icon="iconStart" />
189
- <Icon v-if="icon" :class="{ ' bottom-1-25 ': underlined }" :icon size="0.9" />
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
- .txtInputIconStart input {
215
- padding-inline-start: calc(var(--input-height) / 1.5);
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
- .txtInputIconStart .iconStart {
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
- .textInputSpinnerWrap .spinner {
227
- color: var(--input-color);
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
- inset-inline-end: 0;
230
- /* margin-top: calc(var(--input-height) / -1 + 0.15rem); */
231
- margin-top: calc(var(--input-height) / -1 + (var(--input-height) / 40));
232
- line-height: 0;
233
- margin-inline-end: 0.25rem;
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
- gap: 0;
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<{ digitCount: number, default?: string, modelValue?: string, error?: string }>()
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
- const props = withDefaults(
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
- name?: string
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
- lines?: number
31
+ rows?: number | string
32
+ autocomplete?: string
39
33
  autofocus?: boolean
40
- debounceDelay?: number
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
- const password = defineModel<string>('modelValue')
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 = (warning != null && warning !== '') ? [warning] : []
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="props" :type="inputType" class="mb-0" :name="props.name" :error="props.error" :underlined="props.underlined" />
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-accent-color);
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>