@bagelink/vue 1.12.71 → 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 (81) 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/i18n/index.d.ts.map +1 -1
  35. package/dist/index.cjs +135 -135
  36. package/dist/index.mjs +16026 -15661
  37. package/dist/style.css +1 -1
  38. package/dist/types/BagelForm.d.ts.map +1 -1
  39. package/dist/utils/BagelFormUtils.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/components/Dropdown.vue +2 -1
  42. package/src/components/FilterQuery.types.ts +21 -0
  43. package/src/components/FilterQuery.vue +112 -15
  44. package/src/components/form/inputs/ArrayInput.vue +10 -2
  45. package/src/components/form/inputs/CheckInput.vue +28 -9
  46. package/src/components/form/inputs/CodeEditor/Index.vue +15 -2
  47. package/src/components/form/inputs/ColorInput.vue +77 -9
  48. package/src/components/form/inputs/DateInput.vue +10 -3
  49. package/src/components/form/inputs/EmailInput.vue +90 -54
  50. package/src/components/form/inputs/JSONInput.vue +12 -4
  51. package/src/components/form/inputs/MarkdownEditor.vue +12 -4
  52. package/src/components/form/inputs/NumberInput.vue +154 -89
  53. package/src/components/form/inputs/OTP.vue +46 -7
  54. package/src/components/form/inputs/PasswordInput.vue +32 -21
  55. package/src/components/form/inputs/RadioGroup.vue +18 -7
  56. package/src/components/form/inputs/RangeInput.vue +23 -7
  57. package/src/components/form/inputs/RichText/index.vue +21 -12
  58. package/src/components/form/inputs/SelectBtn.vue +34 -6
  59. package/src/components/form/inputs/SelectInput.vue +19 -25
  60. package/src/components/form/inputs/SignaturePad.vue +39 -14
  61. package/src/components/form/inputs/TableField.vue +6 -2
  62. package/src/components/form/inputs/TelInput.vue +52 -4
  63. package/src/components/form/inputs/TextInput.vue +23 -31
  64. package/src/components/form/inputs/ToggleInput.vue +27 -4
  65. package/src/components/form/inputs/Upload/UploadInput.vue +44 -9
  66. package/src/components/form/inputs/Upload/upload.css +15 -0
  67. package/src/components/form/inputs/bagelInputShell.ts +43 -0
  68. package/src/components/form/inputs/index.ts +1 -0
  69. package/src/components/index.ts +1 -0
  70. package/src/dialog/Dialog.vue +12 -1
  71. package/src/i18n/locales/en.json +4 -1
  72. package/src/i18n/locales/es.json +4 -1
  73. package/src/i18n/locales/fr.json +4 -1
  74. package/src/i18n/locales/he.json +4 -1
  75. package/src/i18n/locales/it.json +4 -1
  76. package/src/i18n/locales/ru.json +4 -1
  77. package/src/styles/input-variants.css +12 -13
  78. package/src/styles/inputs.css +134 -15
  79. package/src/styles/text.css +10 -10
  80. package/src/types/BagelForm.ts +11 -1
  81. package/src/utils/BagelFormUtils.ts +1 -0
@@ -5,8 +5,10 @@ import { onClickOutside } from '@vueuse/core'
5
5
  import { ref, computed, onMounted, watch } from 'vue'
6
6
  import { WEEK_START_DAY } from '../../../utils/calendar/time'
7
7
  import DatePicker from './DatePicker.vue'
8
+ import type { BagelInputShellProps } from './bagelInputShell'
9
+ import { useBagelInputShell } from './bagelInputShell'
8
10
 
9
- export interface DateInputProps {
11
+ export interface DateInputProps extends BagelInputShellProps {
10
12
  required?: boolean
11
13
  label?: string
12
14
  placeholder?: string
@@ -43,6 +45,8 @@ const props = withDefaults(
43
45
 
44
46
  const { $t } = useI18n()
45
47
 
48
+ const { shellClass, shellStyle } = useBagelInputShell(props)
49
+
46
50
  const selectedDate = defineModel<string | Date>()
47
51
  const inputValue = ref('')
48
52
  const isValid = ref(true)
@@ -193,7 +197,8 @@ onMounted(() => {
193
197
  <template>
194
198
  <div
195
199
  class="bagel-input"
196
- :class="{ small, 'has-error': !!error || !isValid, underlined, 'has-value': hasValue, 'open': isOpen }"
200
+ :class="[shellClass, { small, 'has-error': !!error || !isValid, underlined, 'has-value': hasValue, 'open': isOpen }]"
201
+ :style="shellStyle"
197
202
  >
198
203
  <label>
199
204
  <span v-if="label || (underlined && placeholder)" class="label-text">{{ resolveI18n(label || placeholder)
@@ -206,7 +211,9 @@ onMounted(() => {
206
211
  :placeholder="underlined ? ' ' : (resolveI18n(placeholder) || (enableTime ? 'DD.MM.YY HH:mm' : 'DD.MM.YY'))"
207
212
  :required="underlined ? false : required" :disabled="!editMode"
208
213
  :error="!isValid ? $t('date.invalidFormat') : error" :class="{ 'txt-center': center }"
209
- :underlined="underlined" class="date-input m-0" @input="handleInput" @focus="handleFocus"
214
+ :underlined="underlined" :frame="frame" :outline="outline" :min-width="minWidth"
215
+ :max-width="maxWidth" :label-color="labelColor" :label-active-color="labelActiveColor"
216
+ class="date-input m-0" @input="handleInput" @focus="handleFocus"
210
217
  @blur="handleBlur" @click.stop @keydown="handleKeydown" @iconClick="handleIconClick"
211
218
  />
212
219
  </div>
@@ -1,8 +1,10 @@
1
1
  <script setup lang="ts">
2
2
  import type { IconType, ValidateInputBaseT } from '@bagelink/vue'
3
3
  import { Icon, useDebounceFn, useI18n, resolveI18n } from '@bagelink/vue'
4
- import { computed, onMounted, ref, watch, nextTick } from 'vue'
4
+ import { computed, onMounted, ref, watch } from 'vue'
5
5
  import { EMAIL_REGEX } from '../../../utils/constants'
6
+ import type { BagelInputShellProps } from './bagelInputShell'
7
+ import { useBagelInputShell } from './bagelInputShell'
6
8
 
7
9
  const props = withDefaults(
8
10
  defineProps<EmailInputProps>(),
@@ -11,15 +13,18 @@ const props = withDefaults(
11
13
  autocorrect: true,
12
14
  serverValidate: false,
13
15
  preventFakeEmails: false,
16
+ formatValidation: false,
14
17
  pattern: '[a-z0-9._%+\\-]+@[a-z0-9.\\-]+\\.[a-z]{2,}',
15
18
  },
16
19
  )
17
20
 
18
21
  const emit = defineEmits(['update:modelValue', 'debounce'])
19
22
 
23
+ const { shellClass, shellStyle } = useBagelInputShell(props)
24
+
20
25
  const { $t } = useI18n()
21
26
 
22
- export interface EmailInputProps extends ValidateInputBaseT {
27
+ export interface EmailInputProps extends ValidateInputBaseT, BagelInputShellProps {
23
28
  id?: string
24
29
  title?: string
25
30
  helptext?: string
@@ -37,7 +42,7 @@ export interface EmailInputProps extends ValidateInputBaseT {
37
42
  nativeInputAttrs?: { [key: string]: any }
38
43
  icon?: IconType
39
44
  iconStart?: IconType
40
- autocomplete?: AutoFillField
45
+ autocomplete?: string
41
46
  autofocus?: boolean
42
47
  error?: string
43
48
  onFocusout?: (e: FocusEvent) => void
@@ -46,6 +51,7 @@ export interface EmailInputProps extends ValidateInputBaseT {
46
51
  autocorrect?: boolean
47
52
  serverValidate?: boolean
48
53
  preventFakeEmails?: boolean
54
+ formatValidation?: boolean
49
55
  }
50
56
 
51
57
  // Common email providers for autocorrection
@@ -100,7 +106,6 @@ const inputVal = ref<string>('')
100
106
  const suggestedCorrection = ref<string | null>(null)
101
107
  const validationMessage = ref('')
102
108
  const isValidating = ref(false)
103
- const isValidEmail = ref(true)
104
109
  const validatedEmails = new Map<string, boolean>()
105
110
 
106
111
  const input = ref<HTMLInputElement>()
@@ -120,7 +125,7 @@ function validateEmail(value: string) {
120
125
  if (domain) {
121
126
  const domainLower = domain.toLowerCase()
122
127
  if (FAKE_EMAIL_DOMAINS.includes(domainLower)) {
123
- return 'Please use a non-disposable email address'
128
+ return $t('email.disposableEmail')
124
129
  }
125
130
  }
126
131
  }
@@ -151,9 +156,11 @@ function validateInput() {
151
156
  async function validateEmailWithServer(email: string) {
152
157
  if (!props.serverValidate || !email || !EMAIL_REGEX.test(email)) { return }
153
158
 
154
- // If we've already validated this email, use cached result
155
159
  if (validatedEmails.has(email)) {
156
- isValidEmail.value = validatedEmails.get(email) || false
160
+ if (!validatedEmails.get(email)) {
161
+ validationMessage.value = $t('email.invalidDomain')
162
+ input.value?.setCustomValidity(validationMessage.value)
163
+ }
157
164
  return
158
165
  }
159
166
 
@@ -172,7 +179,6 @@ async function validateEmailWithServer(email: string) {
172
179
  const result = await response.json()
173
180
  const isValid = result.status === 'valid' || result.has_mx === true
174
181
 
175
- isValidEmail.value = isValid
176
182
  validatedEmails.set(email, isValid)
177
183
 
178
184
  if (!isValid) {
@@ -271,10 +277,6 @@ const debouncedServerValidate = useDebounceFn(() => validateEmailWithServer(inpu
271
277
  function updateInputVal() {
272
278
  if (props.disabled) { return }
273
279
 
274
- // Remove typo checking while typing - only do this on focusout now
275
- // checkForTypos(inputVal.value)
276
-
277
- // Clear any previous suggestions
278
280
  suggestedCorrection.value = null
279
281
 
280
282
  validateInput()
@@ -289,7 +291,6 @@ function updateInputVal() {
289
291
  }
290
292
 
291
293
  function handleFocusout(e: FocusEvent) {
292
- // Check for typos when the user leaves the field
293
294
  checkForTypos(inputVal.value)
294
295
 
295
296
  // Immediately validate on blur
@@ -305,25 +306,23 @@ watch(
305
306
  (newVal) => {
306
307
  if (newVal !== inputVal.value) {
307
308
  inputVal.value = newVal || ''
308
- nextTick(() => { validateInput() })
309
309
  }
310
310
  },
311
311
  { immediate: true },
312
312
  )
313
313
 
314
- watch(
315
- () => inputVal.value,
316
- () => { validateInput() },
317
- { immediate: true }
318
- )
314
+ watch(() => inputVal.value, () => { validateInput() }, { immediate: true })
319
315
 
320
316
  const hasFocus = () => document.activeElement === input.value
321
317
  const focus = () => input.value?.focus()
322
318
  defineExpose({ focus, hasFocus })
323
319
 
324
- const hasValue = computed(() => {
325
- const val = inputVal.value
326
- return val !== undefined && val !== '' && String(val).length > 0
320
+ const hasValue = computed(() => inputVal.value.length > 0)
321
+
322
+ const formatValidationClass = computed(() => {
323
+ if (!props.formatValidation) return null
324
+ if (!hasValue.value) return 'format-empty'
325
+ return EMAIL_REGEX.test(inputVal.value) ? 'format-valid' : 'format-invalid'
327
326
  })
328
327
 
329
328
  onMounted(() => {
@@ -334,16 +333,20 @@ onMounted(() => {
334
333
 
335
334
  <template>
336
335
  <div
337
- class="bagel-input text-input" :class="{
338
- small,
339
- shrink,
340
- underlined,
341
- 'textInputIconWrap': icon,
342
- 'txtInputIconStart': iconStart,
343
- 'is-validating': isValidating,
344
- 'has-error': !!error,
345
- 'has-value': hasValue,
346
- }" :title="title"
336
+ class="bagel-input text-input" :class="[
337
+ shellClass,
338
+ formatValidationClass,
339
+ {
340
+ small,
341
+ shrink,
342
+ underlined,
343
+ 'textInputIconWrap': icon,
344
+ 'txtInputIconStart': iconStart,
345
+ 'is-validating': isValidating,
346
+ 'has-error': !!error,
347
+ 'has-value': hasValue,
348
+ },
349
+ ]" :style="shellStyle" :title="title"
347
350
  >
348
351
  <label :for="id">
349
352
  <span v-if="label || (underlined && placeholder)" class="label-text">{{ resolveI18n(label) || resolveI18n(placeholder) }}<span v-if="required"> *</span></span>
@@ -353,9 +356,9 @@ onMounted(() => {
353
356
  v-if="suggestedCorrection" class="pointer nowrap inline-block ms-auto color-red txt-10px p-0"
354
357
  @click.prevent="applyCorrection"
355
358
  >
356
- did you mean {{ suggestedCorrection }}?
359
+ {{ $t('email.didYouMean', { suggestion: suggestedCorrection }) }}
357
360
  </span>
358
- <span v-if="isValidating" class="validating">Validating email...</span>
361
+ <span v-if="isValidating" class="validating">{{ $t('email.validating') }}</span>
359
362
  </div>
360
363
  <input
361
364
  :id ref="input" v-model="inputVal" v-pattern:lower class="ltr" :name :title autocomplete="email"
@@ -381,29 +384,61 @@ onMounted(() => {
381
384
  .bagel-input label {
382
385
  font-size: var(--label-font-size);
383
386
  }
384
- </style>
385
387
 
386
- <style scoped>
387
- .bagel-input textarea {
388
- min-height: unset;
389
- font-size: var(--input-font-size);
388
+ .bagel-input.format-invalid,
389
+ .bagel-input.format-valid,
390
+ .bagel-input.format-empty {
391
+ position: relative;
390
392
  }
391
393
 
392
- .bagel-input.text-input textarea {
393
- resize: none;
394
+ .bagel-input.format-invalid::after {
395
+ content: '';
396
+ position: absolute;
397
+ width: 7px;
398
+ height: 7px;
399
+ border-radius: 50%;
400
+ background: var(--bgl-orange);
401
+ right: 0.6rem;
402
+ bottom: calc(var(--input-height) / 2 - 3.5px);
403
+ pointer-events: none;
394
404
  }
395
405
 
396
- .code textarea {
397
- font-family: 'Inconsolata', monospace;
398
- background: var(--bgl-code-bg) !important;
399
- color: var(--bgl-light-text) !important;
406
+ .bagel-input.format-invalid.textInputIconWrap::after,
407
+ .bagel-input.format-valid.textInputIconWrap::after,
408
+ .bagel-input.format-empty.textInputIconWrap::after {
409
+ right: calc(var(--input-height) / 3 + 1.8rem);
400
410
  }
401
411
 
402
- .code textarea::placeholder {
403
- color: var(--bgl-light-text) !important;
404
- opacity: 0.3;
412
+ .bagel-input.format-empty::after {
413
+ content: '';
414
+ position: absolute;
415
+ width: 7px;
416
+ height: 7px;
417
+ border-radius: 50%;
418
+ background: var(--bgl-gray, #aaa);
419
+ right: 0.6rem;
420
+ bottom: calc(var(--input-height) / 2 - 3.5px);
421
+ pointer-events: none;
405
422
  }
406
423
 
424
+ .bagel-input.format-valid {
425
+ position: relative;
426
+ }
427
+
428
+ .bagel-input.format-valid::after {
429
+ content: '';
430
+ position: absolute;
431
+ width: 7px;
432
+ height: 7px;
433
+ border-radius: 50%;
434
+ background: var(--bgl-green);
435
+ right: 0.6rem;
436
+ bottom: calc(var(--input-height) / 2 - 3.5px);
437
+ pointer-events: none;
438
+ }
439
+ </style>
440
+
441
+ <style scoped>
407
442
  .bagel-input.small {
408
443
  margin-bottom: 0;
409
444
  height: 30px;
@@ -423,29 +458,30 @@ onMounted(() => {
423
458
  font-size: var(--label-font-size);
424
459
  }
425
460
 
461
+ /* email input is always LTR — use physical properties so icons stay in the correct
462
+ visual position regardless of the document's text direction */
426
463
  .textInputIconWrap .bgl_icon-font {
427
464
  color: var(--input-color);
428
465
  position: absolute;
429
- inset-inline-end: calc(var(--input-height) / 3 - 0.25rem);
466
+ right: calc(var(--input-height) / 3 - 0.25rem);
430
467
  margin-top: calc(var(--input-height) / 2 + 0.1rem);
431
468
  line-height: 0;
432
469
  }
433
470
 
434
471
  .textInputIconWrap input {
435
- padding-inline-end: calc(var(--input-height) / 3 + 1.5rem);
472
+ padding-right: calc(var(--input-height) / 3 + 1.5rem);
436
473
  }
437
474
 
438
475
  .txtInputIconStart .iconStart {
439
476
  color: var(--input-color);
440
477
  position: absolute;
441
- inset-inline-start: calc(var(--input-height) / 3 - 0.25rem);
478
+ left: calc(var(--input-height) / 3 - 0.25rem);
442
479
  margin-top: calc(var(--input-height) / 2);
443
480
  line-height: 0;
444
481
  }
445
482
 
446
- .txtInputIconStart input,
447
- .txtInputIconStart textarea {
448
- padding-inline-start: calc(var(--input-height) / 3 + 1.5rem);
483
+ .txtInputIconStart input {
484
+ padding-left: calc(var(--input-height) / 3 + 1.5rem);
449
485
  }
450
486
 
451
487
  .bagel-input.small textarea {
@@ -1,8 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  // const props =
3
+ import { computed } from 'vue'
3
4
  import { resolveI18n } from '@bagelink/vue'
5
+ import type { BagelInputShellProps } from './bagelInputShell'
6
+ import { useBagelInputShell } from './bagelInputShell'
4
7
 
5
- withDefaults(
8
+ const props = withDefaults(
6
9
  defineProps<{
7
10
  description?: string
8
11
  label?: string
@@ -10,7 +13,7 @@ withDefaults(
10
13
  placeholder?: string
11
14
  editMode?: boolean
12
15
  small?: boolean
13
- }>(),
16
+ } & BagelInputShellProps>(),
14
17
  {
15
18
  description: '',
16
19
  editMode: true,
@@ -18,7 +21,9 @@ withDefaults(
18
21
  label: '',
19
22
  },
20
23
  )
21
- // TODO: remove this Nati ?
24
+
25
+ const { shellClass, shellStyle } = useBagelInputShell(props)
26
+ const hasValue = computed(() => props.modelValue != null && String(props.modelValue).length > 0)
22
27
 
23
28
  const emits = defineEmits(['update:modelValue'])
24
29
  function handleInput(e: Event) {
@@ -28,7 +33,10 @@ function handleInput(e: Event) {
28
33
  </script>
29
34
 
30
35
  <template>
31
- <div class="bagel-input" :title="description" :class="{ small }">
36
+ <div
37
+ class="bagel-input" :title="description"
38
+ :class="[shellClass, { small, 'has-value': hasValue }]" :style="shellStyle"
39
+ >
32
40
  <label v-if="label">
33
41
  <LangText :input="label" />
34
42
  </label>
@@ -1,8 +1,10 @@
1
1
  <script lang="ts" setup>
2
2
  import { Btn, TextInput } from '@bagelink/vue'
3
3
  import { computed, nextTick, ref } from 'vue'
4
+ import type { BagelInputShellProps } from './bagelInputShell'
5
+ import { useBagelInputShell } from './bagelInputShell'
4
6
 
5
- interface Props {
7
+ interface Props extends BagelInputShellProps {
6
8
  maxLength?: number
7
9
  placeholder?: string
8
10
  label?: string
@@ -10,7 +12,13 @@ interface Props {
10
12
  showFormatting?: boolean
11
13
  }
12
14
 
13
- const { maxLength, placeholder = 'Enter Text', label = 'Body', showFormatting = true } = defineProps<Props>()
15
+ const props = withDefaults(defineProps<Props>(), {
16
+ placeholder: 'Enter Text',
17
+ label: 'Body',
18
+ showFormatting: true,
19
+ })
20
+ const { maxLength, placeholder, label, showFormatting, rtl } = props
21
+ const { shellClass, shellStyle } = useBagelInputShell(props)
14
22
 
15
23
  const text = defineModel<string>('modelValue', { default: '' })
16
24
 
@@ -85,7 +93,7 @@ defineExpose({ html })
85
93
 
86
94
  <template>
87
95
  <div class="markdown-editor">
88
- <div class="bagel-input mb-0">
96
+ <div class="bagel-input mb-0" :class="[shellClass, { 'has-value': charCount > 0 }]" :style="shellStyle">
89
97
  <div v-if="maxLength" class="txt10 opacity-6 -mb-1 w100p txt-end">
90
98
  {{ charCount }} / {{ maxLength }}
91
99
  </div>
@@ -102,7 +110,7 @@ defineExpose({ html })
102
110
  />
103
111
  </div>
104
112
 
105
- <div v-if="showFormatting" class="flex justify-content-end -mt-025 -mb-05">
113
+ <div v-if="showFormatting" class="flex justify-content-end -mt-025 -mb-05 ltr">
106
114
  <Btn thin icon-size="0.9" flat icon="format_bold" @click="insertToBody('bold')" />
107
115
  <Btn thin icon-size="0.9" flat icon="format_italic" @click="insertToBody('italic')" />
108
116
  <Btn thin icon-size="0.9" flat icon="format_underlined" @click="insertToBody('underline')" />