@bagelink/vue 1.2.65 → 1.2.71
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/bin/experimentalGenTypedRoutes.ts +309 -0
- package/dist/components/Alert.vue.d.ts +2 -0
- package/dist/components/Alert.vue.d.ts.map +1 -1
- package/dist/components/Carousel.vue.d.ts +12 -3
- package/dist/components/Carousel.vue.d.ts.map +1 -1
- package/dist/components/Carousel2.vue.d.ts +89 -0
- package/dist/components/Carousel2.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/EmailInput.vue.d.ts +48 -0
- package/dist/components/form/inputs/EmailInput.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TelInput.vue.d.ts +60 -0
- package/dist/components/form/inputs/TelInput.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/index.d.ts +2 -1
- package/dist/components/form/inputs/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/composables/useDevice.d.ts.map +1 -1
- package/dist/composables/useSchemaField.d.ts.map +1 -1
- package/dist/directives/pattern.d.ts.map +1 -1
- package/dist/index.cjs +11302 -10747
- package/dist/index.mjs +11303 -10748
- package/dist/style.css +1822 -1755
- package/dist/utils/BagelFormUtils.d.ts +7 -0
- package/dist/utils/BagelFormUtils.d.ts.map +1 -1
- package/dist/utils/constants.d.ts +1 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/package.json +7 -3
- package/src/components/Alert.vue +29 -7
- package/src/components/Carousel.vue +8 -0
- package/src/components/Carousel2.vue +1012 -0
- package/src/components/form/inputs/EmailInput.vue +476 -0
- package/src/components/form/inputs/SelectInput.vue +1 -1
- package/src/components/form/inputs/TelInput.vue +210 -350
- package/src/components/form/inputs/TextInput.vue +1 -1
- package/src/components/form/inputs/index.ts +2 -1
- package/src/components/index.ts +2 -1
- package/src/composables/useDevice.ts +1 -0
- package/src/composables/useSchemaField.ts +3 -1
- package/src/directives/pattern.ts +3 -2
- package/src/styles/inputs.css +137 -138
- package/src/styles/layout.css +1466 -1459
- package/src/styles/mobilLayout.css +11 -0
- package/src/utils/BagelFormUtils.ts +24 -0
- package/src/utils/constants.ts +1 -0
- 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,
|
package/src/utils/constants.ts
CHANGED
|
@@ -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>
|