@bagelink/vue 1.2.65 → 1.2.69

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 (46) hide show
  1. package/bin/experimentalGenTypedRoutes.ts +309 -0
  2. package/dist/components/Alert.vue.d.ts +2 -0
  3. package/dist/components/Alert.vue.d.ts.map +1 -1
  4. package/dist/components/Carousel.vue.d.ts +12 -3
  5. package/dist/components/Carousel.vue.d.ts.map +1 -1
  6. package/dist/components/Carousel2.vue.d.ts +89 -0
  7. package/dist/components/Carousel2.vue.d.ts.map +1 -0
  8. package/dist/components/form/inputs/EmailInput.vue.d.ts +48 -0
  9. package/dist/components/form/inputs/EmailInput.vue.d.ts.map +1 -0
  10. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  11. package/dist/components/form/inputs/TelInput.vue.d.ts +60 -0
  12. package/dist/components/form/inputs/TelInput.vue.d.ts.map +1 -0
  13. package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
  14. package/dist/components/form/inputs/index.d.ts +2 -1
  15. package/dist/components/form/inputs/index.d.ts.map +1 -1
  16. package/dist/components/index.d.ts +1 -0
  17. package/dist/components/index.d.ts.map +1 -1
  18. package/dist/composables/useDevice.d.ts.map +1 -1
  19. package/dist/composables/useSchemaField.d.ts.map +1 -1
  20. package/dist/directives/pattern.d.ts.map +1 -1
  21. package/dist/index.cjs +11366 -10332
  22. package/dist/index.mjs +11367 -10333
  23. package/dist/style.css +1839 -1701
  24. package/dist/utils/BagelFormUtils.d.ts +7 -0
  25. package/dist/utils/BagelFormUtils.d.ts.map +1 -1
  26. package/dist/utils/constants.d.ts +1 -0
  27. package/dist/utils/constants.d.ts.map +1 -1
  28. package/package.json +7 -3
  29. package/src/components/Alert.vue +29 -7
  30. package/src/components/Carousel.vue +8 -0
  31. package/src/components/Carousel2.vue +1012 -0
  32. package/src/components/form/inputs/EmailInput.vue +476 -0
  33. package/src/components/form/inputs/SelectInput.vue +1 -1
  34. package/src/components/form/inputs/TelInput.vue +210 -350
  35. package/src/components/form/inputs/TextInput.vue +1 -1
  36. package/src/components/form/inputs/index.ts +2 -1
  37. package/src/components/index.ts +1 -0
  38. package/src/composables/useDevice.ts +1 -0
  39. package/src/composables/useSchemaField.ts +3 -1
  40. package/src/directives/pattern.ts +3 -2
  41. package/src/styles/inputs.css +137 -138
  42. package/src/styles/layout.css +1466 -1459
  43. package/src/styles/mobilLayout.css +11 -0
  44. package/src/utils/BagelFormUtils.ts +24 -0
  45. package/src/utils/constants.ts +1 -0
  46. package/src/components/form/inputs/PhoneInput.vue +0 -352
@@ -303,6 +303,17 @@
303
303
  width: 100%;
304
304
  }
305
305
 
306
+ .m_min-w-100p,
307
+ .m_w-min-100p,
308
+ .m_min-w100p {
309
+ min-width: 100%;
310
+ }
311
+
312
+ .m_min-w-100px,
313
+ .m_w-min-100px,
314
+ .m_min-w100px {
315
+ min-width: 100px;
316
+ }
306
317
 
307
318
  .m_w300,
308
319
  .m_w300px,
@@ -152,6 +152,30 @@ export function checkField<T, P extends Path<T>>(
152
152
  }
153
153
  }
154
154
 
155
+ interface EmailInputOptions<T, P extends Path<T>> extends InputOptions<T, P> {
156
+ autocorrect?: boolean
157
+ serverValidate?: boolean
158
+ preventFakeEmails?: boolean
159
+ pattern?: string
160
+ }
161
+
162
+ export function emailField<T, P extends Path<T>>(id: P, label?: string, options?: EmailInputOptions<T, P>): BaseBagelField<T, P> {
163
+ return {
164
+ $el: 'email',
165
+ id,
166
+ label,
167
+ class: options?.class,
168
+ required: options?.required,
169
+ vIf: options?.vIf,
170
+ attrs: {
171
+ autocorrect: options?.autocorrect,
172
+ serverValidate: options?.serverValidate,
173
+ preventFakeEmails: options?.preventFakeEmails,
174
+ pattern: options?.pattern,
175
+ },
176
+ }
177
+ }
178
+
155
179
  export function dateField<T, P extends Path<T>>(
156
180
  id: P,
157
181
  label?: string,
@@ -2,3 +2,4 @@ export const IMAGE_FORMATS = ['jpeg', 'png', 'webp', 'avif', 'apng', 'gif', 'avi
2
2
  export const IMAGE_FORMATS_REGEXP = new RegExp(`(${IMAGE_FORMATS.join('|')})$`, 'i')
3
3
  export const VIDEO_FORMATS = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'flv', 'wmv', 'mkv', 'ts', 'm3u8']
4
4
  export const VIDEO_FORMATS_REGEXP = new RegExp(`(${VIDEO_FORMATS.join('|')})$`, 'i')
5
+ export const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\])|(([a-z\-0-9]+\.)+[a-z]{2,}))$/i
@@ -1,352 +0,0 @@
1
- <script setup lang="ts">
2
- import type { Country } from '@bagelink/vue'
3
- import type { CountryCode } from 'libphonenumber-js'
4
- import { Dropdown, Flag, Icon, TextInput, allCountries } from '@bagelink/vue'
5
- import axios from 'axios'
6
- import { parsePhoneNumberFromString } from 'libphonenumber-js'
7
- import { onMounted, watch, ref } from 'vue'
8
-
9
- const props = defineProps<{
10
- id?: string
11
- label?: string
12
- modelValue: string
13
- placeholder?: string
14
- excludeCountries?: string[]
15
- onlyCountries?: string[]
16
- required?: boolean
17
- disabled?: boolean
18
- }>()
19
-
20
- const emit = defineEmits(['update:modelValue', 'blur', 'focus', 'keydown', 'input', 'paste'])
21
-
22
- let phoneNumber = $ref(props.modelValue)
23
- let search = $ref('')
24
- let activeCountry = $ref<Country>()
25
- const searchInput = $ref<InstanceType<typeof TextInput>>()
26
- let open = $ref(false)
27
- const inputRef = ref<HTMLInputElement | null>(null)
28
- let isValid = $ref(true)
29
-
30
- // Watch for changes to phoneNumber and emit update events
31
- watch(() => phoneNumber, (newValue) => {
32
- emit('update:modelValue', newValue)
33
- })
34
-
35
- // Watch for changes to the modelValue prop
36
- watch(() => props.modelValue, (newValue) => {
37
- if (newValue !== phoneNumber) {
38
- phoneNumber = newValue
39
- }
40
- })
41
-
42
- const countries = $computed(() => {
43
- let filteredCountries = allCountries
44
- if (props.excludeCountries && props.excludeCountries.length) {
45
- const excludeCountries = props.excludeCountries.map(c => c.toLowerCase())
46
- filteredCountries = filteredCountries.filter(c => !excludeCountries.includes(c.iso2.toLowerCase()))
47
- }
48
- if (props.onlyCountries && props.onlyCountries.length) {
49
- const onlyCountries = props.onlyCountries.map(c => c.toLowerCase())
50
- filteredCountries = filteredCountries.filter(c => onlyCountries.includes(c.iso2.toLowerCase()))
51
- }
52
- if (search.length) {
53
- const lowerCaseSearch = search.toLowerCase()
54
- filteredCountries = filteredCountries.filter(c => c.name.toLowerCase().includes(lowerCaseSearch)
55
- || c.iso2.toLowerCase().includes(lowerCaseSearch)
56
- || c.dialCode.includes(search)
57
- )
58
- }
59
- return filteredCountries
60
- })
61
-
62
- const activeCountryCode = $computed(() => activeCountry?.iso2)
63
-
64
- function selectCountry(country: Country) {
65
- activeCountry = country
66
- open = false
67
- search = ''
68
- if (!phoneNumber) phoneNumber = `+${activeCountry.dialCode}`
69
- }
70
-
71
- async function getIp() {
72
- let apiData = sessionStorage.getItem('ipapi')
73
- if (!apiData) {
74
- apiData = (await axios.get('https://ipapi.co/json/')).data
75
- apiData = JSON.stringify(apiData)
76
- sessionStorage.setItem('ipapi', apiData)
77
- }
78
- const { country_code } = JSON.parse(apiData)
79
- selectCountry(countries.find(c => c.iso2 === country_code) ?? countries[0])
80
- }
81
-
82
- // Get the country code for use with libphonenumber-js
83
- function getCountryCode(): CountryCode | undefined {
84
- return activeCountry?.iso2 ? activeCountry.iso2.toUpperCase() as CountryCode : undefined
85
- }
86
-
87
- // Parse and format the phone number
88
- function parseAndFormatPhoneNumber(value: string): string {
89
- if (!value) return value
90
-
91
- try {
92
- const parsedNumber = parsePhoneNumberFromString(value, getCountryCode())
93
- if (parsedNumber && parsedNumber.isValid()) {
94
- return parsedNumber.formatInternational()
95
- }
96
- } catch (error) {
97
- console.error('Error parsing phone number:', error)
98
- }
99
- return value
100
- }
101
-
102
- // Validate the phone number and set custom validity
103
- function validatePhoneNumber() {
104
- if (!inputRef.value) return
105
-
106
- try {
107
- const parsedNumber = parsePhoneNumberFromString(phoneNumber, getCountryCode())
108
- if (parsedNumber && parsedNumber.isValid()) {
109
- inputRef.value.setCustomValidity('')
110
- isValid = true
111
- } else {
112
- inputRef.value.setCustomValidity('Please enter a valid phone number')
113
- isValid = false
114
- }
115
- } catch (error) {
116
- inputRef.value.setCustomValidity('Please enter a valid phone number')
117
- isValid = false
118
- }
119
- }
120
-
121
- function detectCountryFromNumber(value: string): boolean {
122
- if (!value.startsWith('+')) return false
123
-
124
- const digits = value.replace(/\D/g, '')
125
- if (digits.length <= 1) return false
126
-
127
- for (const country of countries) {
128
- if (digits.startsWith(country.dialCode) && country !== activeCountry) {
129
- selectCountry(country)
130
- return true
131
- }
132
- }
133
- return false
134
- }
135
-
136
- async function initializeCountry() {
137
- if (phoneNumber) {
138
- detectCountryFromNumber(phoneNumber)
139
- } else {
140
- await getIp()
141
- }
142
- const formatted = parseAndFormatPhoneNumber(phoneNumber)
143
- if (formatted !== phoneNumber) {
144
- phoneNumber = formatted
145
- emit('input', phoneNumber)
146
- validatePhoneNumber()
147
- }
148
- validatePhoneNumber()
149
- }
150
-
151
- function handlePhoneInput(event: Event) {
152
- const input = event.target as HTMLInputElement
153
- const { value } = input
154
- detectCountryFromNumber(value)
155
-
156
- if (value.startsWith('+')) {
157
- const formatted = parseAndFormatPhoneNumber(value)
158
- if (formatted !== value) {
159
- phoneNumber = formatted
160
- emit('input', event)
161
- validatePhoneNumber()
162
- return
163
- }
164
- }
165
-
166
- phoneNumber = value
167
- emit('input', event)
168
- validatePhoneNumber()
169
- }
170
-
171
- function handleBlur(event: Event) {
172
- if (phoneNumber && !phoneNumber.startsWith('+') && activeCountry) {
173
- const nationalNumber = phoneNumber.replace(/^0+/, '')
174
- phoneNumber = `+${activeCountry.dialCode} ${nationalNumber}`
175
- } else if (phoneNumber) {
176
- phoneNumber = parseAndFormatPhoneNumber(phoneNumber)
177
- }
178
-
179
- validatePhoneNumber()
180
- emit('blur', event)
181
- }
182
-
183
- const disableDropdown = $computed(() => countries.length === 1 && !search)
184
- const searchable = $computed(() => countries.length > 7 || search)
185
-
186
- function focusSearchInput() {
187
- setTimeout(() => searchInput?.focus(), 100)
188
- }
189
-
190
- onMounted(initializeCountry)
191
- </script>
192
-
193
- <template>
194
- <div class="bagel-input text-input" :class="{ invalid: !isValid }">
195
- <label>
196
- {{ label }}
197
- <div
198
- dir="ltr"
199
- class="flex gap-05 tel-input"
200
- tabindex="-1"
201
- aria-label="Country Code Selector"
202
- aria-haspopup="listbox"
203
- :aria-expanded="open"
204
- >
205
- <Dropdown
206
- v-model:shown="open"
207
- placement="bottom-start"
208
- :disabled="disableDropdown"
209
- @show="focusSearchInput"
210
- >
211
- <template #trigger>
212
- <span class="flex gap-05 country-code-display">
213
- <Icon v-if="!disableDropdown" :icon="open ? 'collapse_all' : 'expand_all'" />
214
- <Flag v-if="activeCountryCode" :country="activeCountryCode" />
215
- </span>
216
- </template>
217
- <div class="p-075 tel-countryp-dropdown">
218
- <TextInput
219
- v-if="searchable"
220
- ref="searchInput"
221
- v-model="search"
222
- aria-label="Search by country name or country code"
223
- placeholder="Search"
224
- icon="search"
225
- />
226
-
227
- <ul
228
- class="overflow-y p-0 max-h-300px"
229
- role="listbox"
230
- >
231
- <li
232
- v-for="(pb) in countries"
233
- :key="pb.iso2"
234
- role="option"
235
- class="flex gap-075 pointer hover"
236
- tabindex="-1"
237
- :aria-selected="activeCountryCode === pb.iso2"
238
- @click="selectCountry(pb)"
239
- >
240
- <Flag :country="pb.iso2" />
241
- <p class="tel-country">{{ pb.name }}</p>
242
- <span>
243
- +{{ pb.dialCode }}
244
- </span>
245
- </li>
246
- </ul>
247
- </div>
248
- </Dropdown>
249
- <input
250
- :id="id"
251
- ref="inputRef"
252
- v-model="phoneNumber"
253
- v-pattern.tel
254
- :required="required"
255
- :placeholder="placeholder || label || 'Phone Number'"
256
- :disabled="disabled"
257
- type="tel"
258
- autocomplete="tel"
259
- :name="id"
260
- tabindex="0"
261
- class="national-number-input"
262
- @blur="handleBlur($event)"
263
- @focus="emit('focus', $event)"
264
- @keydown="emit('keydown', $event)"
265
- @input="handlePhoneInput($event)"
266
- @paste="emit('paste', $event)"
267
- >
268
- </div>
269
- </label>
270
- </div>
271
- </template>
272
-
273
- <style scoped>
274
- .tel-input {
275
- direction: ltr;
276
- text-align: left;
277
- background: var(--input-bg);
278
- border: none;
279
- padding-inline-start: 0.7rem;
280
- border-radius: var(--input-border-radius);
281
- color: var(--input-color);
282
- min-width: calc(var(--input-height) * 3);
283
- width: 100%;
284
- display: flex;
285
- align-items: center;
286
- }
287
-
288
- .tel-input:focus-within {
289
- outline: none;
290
- box-shadow: inset 0 0 10px #00000012;
291
- }
292
-
293
- .tel-input input {
294
- background: transparent;
295
- text-align: left;
296
- flex: 1;
297
- }
298
-
299
- .tel-input input:focus-visible {
300
- box-shadow: none;
301
- }
302
-
303
- .country-code-display {
304
- align-items: center;
305
- white-space: nowrap;
306
- }
307
-
308
- .dial-code {
309
- font-size: var(--input-font-size);
310
- color: var(--input-color);
311
- opacity: 0.6;
312
- }
313
-
314
- .tel-country {
315
- font-size: var(--input-font-size);
316
- max-width: 200px;
317
- white-space: nowrap;
318
- text-overflow: ellipsis;
319
- overflow: hidden;
320
- margin-top: 0;
321
- margin-bottom: 0;
322
- }
323
-
324
- .tel-countryp-dropdown {
325
- direction: ltr;
326
- text-align: left;
327
- }
328
-
329
- .national-number-input {
330
- margin-left: 4px;
331
- }
332
-
333
- .country-changed {
334
- animation: highlight-country 1.5s ease-in-out;
335
- }
336
-
337
- .invalid input {
338
- border-color: var(--error-color, red);
339
- }
340
-
341
- @keyframes highlight-country {
342
- 0%, 100% {
343
- background-color: transparent;
344
- }
345
- 30% {
346
- background-color: var(--primary-color-light, rgba(0, 123, 255, 0.2));
347
- }
348
- 70% {
349
- background-color: var(--primary-color-light, rgba(0, 123, 255, 0.2));
350
- }
351
- }
352
- </style>