@awes-io/ui 2.32.4 → 2.34.1

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/CHANGELOG.md CHANGED
@@ -3,6 +3,55 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.34.1](https://github.com/awes-io/client/compare/@awes-io/ui@2.34.0...@awes-io/ui@2.34.1) (2021-11-25)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **aw-tel:** check if given value is string before parsing ([4161fb6](https://github.com/awes-io/client/commit/4161fb67b57ccea772a390769dab200baee15a44))
12
+
13
+
14
+
15
+
16
+
17
+ # [2.34.0](https://github.com/awes-io/client/compare/@awes-io/ui@2.33.0...@awes-io/ui@2.34.0) (2021-11-25)
18
+
19
+
20
+ ### Features
21
+
22
+ * aw-tel component rewrited ([a072dda](https://github.com/awes-io/client/commit/a072dda44254d783b6445416bede84459639ce8d))
23
+
24
+
25
+
26
+
27
+
28
+ # [2.33.0](https://github.com/awes-io/client/compare/@awes-io/ui@2.32.5...@awes-io/ui@2.33.0) (2021-11-15)
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * mask works only in allowed inputs ([3dcc47e](https://github.com/awes-io/client/commit/3dcc47e1c58e4dac1763aaeb7f50d879c34799db))
34
+
35
+
36
+ ### Features
37
+
38
+ * dayjs upgraded ([b1aa822](https://github.com/awes-io/client/commit/b1aa8228f2b7541acc180cad5bcb477a2d2a37d6))
39
+
40
+
41
+
42
+
43
+
44
+ ## [2.32.5](https://github.com/awes-io/client/compare/@awes-io/ui@2.32.4...@awes-io/ui@2.32.5) (2021-11-13)
45
+
46
+
47
+ ### Bug Fixes
48
+
49
+ * **text-fields:** restore caret position respecting masked signs ([3fe927d](https://github.com/awes-io/client/commit/3fe927d7d2918e860c2104977fee570348754c21))
50
+
51
+
52
+
53
+
54
+
6
55
  ## [2.32.4](https://github.com/awes-io/client/compare/@awes-io/ui@2.32.3...@awes-io/ui@2.32.4) (2021-11-13)
7
56
 
8
57
 
@@ -62,6 +62,8 @@
62
62
 
63
63
  @import './tab-nav.css';
64
64
 
65
+ @import './tel.css';
66
+
65
67
  @import './notification.css';
66
68
 
67
69
  @import './tags.css';
@@ -0,0 +1,49 @@
1
+ .aw-tel {
2
+ position: relative;
3
+
4
+ .aw-text-field__prefix {
5
+ overflow: hidden;
6
+ flex-shrink: 0;
7
+ }
8
+
9
+ &__flag {
10
+ display: inline-flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ width: 1.5rem;
14
+ height: 1.5rem;
15
+ vertical-align: middle;
16
+
17
+ .flag {
18
+ display: block !important;
19
+ flex-shrink: 0;
20
+ transform: scale(0.75);
21
+ }
22
+ }
23
+
24
+ &__country-toggler {
25
+ padding-left: 1rem;
26
+ padding-right: 1rem;
27
+ align-self: stretch;
28
+ }
29
+
30
+ &__dropdown {
31
+ width: 100%;
32
+ max-height: 50vh;
33
+ }
34
+
35
+ &__validity {
36
+ @apply px-4;
37
+ display: none;
38
+
39
+ &--valid {
40
+ @apply text-accent;
41
+ display: block;
42
+ }
43
+
44
+ &--invalid {
45
+ @apply text-error;
46
+ display: block;
47
+ }
48
+ }
49
+ }
@@ -1,5 +1,3 @@
1
- @import 'vue-tel-input/dist/vue-tel-input.css';
2
-
3
1
  .aw-text-field {
4
2
  @apply border rounded text-base;
5
3
  position: relative;
@@ -64,83 +62,6 @@
64
62
  }
65
63
  }
66
64
 
67
- /**
68
- * Tel styles (overwrite vue-tel-input)
69
- */
70
- &.is-tel .vue-tel-input {
71
- @apply bg-surface border-none;
72
- border-color: theme('borderColor.default');
73
- border-radius: theme('borderRadius.default');
74
-
75
- input {
76
- /* background-color: transparent; */
77
- box-shadow: none;
78
- min-width: 0;
79
- }
80
-
81
- .vti__dropdown {
82
- position: static;
83
- padding-left: theme('spacing.4');
84
-
85
- &:focus {
86
- outline: none;
87
- }
88
-
89
- &:hover,
90
- &.open {
91
- @apply bg-mono-300;
92
- }
93
- }
94
-
95
- .vti__dropdown-list {
96
- @apply shadow-lg bg-surface;
97
- max-width: 100%;
98
- max-height: 147px; /* include 53px of footer height */
99
- border: none;
100
- margin: 0;
101
- top: 100%;
102
- left: 0;
103
- z-index: 11;
104
-
105
- &:focus {
106
- outline: none;
107
- }
108
-
109
- &.above {
110
- top: auto;
111
- bottom: 100%;
112
- box-shadow: theme('boxShadow.lg-top');
113
- }
114
- }
115
-
116
- &:focus-within {
117
- box-shadow: none !important;
118
- border-color: theme('borderColor.info');
119
- }
120
-
121
- .vti__dropdown-item {
122
- @apply text-sm tracking-wider;
123
- line-height: 19px;
124
- padding: theme('spacing.3') theme('spacing.4');
125
-
126
- &.highlighted,
127
- &:hover,
128
- &:focus {
129
- outline: none;
130
- @apply bg-mono-300;
131
- }
132
- }
133
- }
134
-
135
- &.is-tel.has-error .vue-tel-input {
136
- @apply border-error;
137
-
138
- input::placeholder {
139
- @apply text-error opacity-100;
140
- }
141
- }
142
-
143
-
144
65
  /**
145
66
  * Textarea styles
146
67
  */
@@ -160,6 +81,9 @@
160
81
  background-color: var(--c-surface, inherit);
161
82
  }
162
83
 
84
+ &.is-hidden {
85
+ display: none;
86
+ }
163
87
 
164
88
  /**
165
89
  * Label styles
@@ -72,6 +72,22 @@
72
72
  import TextFieldMixin from '@AwMixins/text-field'
73
73
  import { AwInput as _config } from '@AwConfig'
74
74
 
75
+ const INVALID_TYPES = [
76
+ { type: ',checkbox,', replacer: '<AwCheckbox /> or <AwSwitcher />' },
77
+ { type: ',radio,', replacer: '<AwRadio />' },
78
+ {
79
+ type: ',date,datetime,time,month,week,',
80
+ replacer: '<AwDate />'
81
+ },
82
+ { type: ',tel,', replacer: '<AwTel />' },
83
+ { type: ',color,', replacer: '<AwColor />' }
84
+ ]
85
+
86
+ const getInvalidConfig = (type) => {
87
+ const _type = ',' + type + ','
88
+ return INVALID_TYPES.find((config) => config.type.includes(_type))
89
+ }
90
+
75
91
  export default {
76
92
  name: 'AwInput',
77
93
 
@@ -84,10 +100,15 @@ export default {
84
100
  type: String,
85
101
  default: 'text',
86
102
  validator(type) {
87
- if (['checkbox', 'radio', 'date'].includes(type)) {
88
- console.error(`Use <fb-${type}> instead`)
103
+ const config = getInvalidConfig(type)
104
+
105
+ if (config) {
106
+ console.error(
107
+ `Invalid type: ${type}. Use ${config.replacer} instead`
108
+ )
89
109
  return false
90
110
  }
111
+
91
112
  return true
92
113
  }
93
114
  },
@@ -256,7 +256,7 @@ export default {
256
256
 
257
257
  onFileLoaded($event) {
258
258
  this.openCropModal($event.target.files[0])
259
-
259
+ $event.target.value = '' // reset to allow upload same file
260
260
  this.$refs.dropdown && this.$refs.dropdown.close()
261
261
  },
262
262
 
@@ -11,6 +11,7 @@
11
11
  v-tooltip.show.prepend="errorTooltip"
12
12
  :aria-describedby="errorText ? errorId : null"
13
13
  :class="cssClass"
14
+ type="tel"
14
15
  v-on="_listeners"
15
16
  />
16
17
  </template>
@@ -32,6 +33,8 @@ const AW_INPUT_PROPS_KEYS = keys(AwInput.props)
32
33
  export default {
33
34
  name: 'AwMoney',
34
35
 
36
+ inheritAttrs: false,
37
+
35
38
  _config,
36
39
 
37
40
  components: {
@@ -1,84 +1,348 @@
1
1
  <template>
2
- <div
3
- class="aw-text-field is-tel relative"
4
- :class="{ 'has-error': hasError }"
5
- @invalid.capture="_onInvalid"
6
- >
7
- <VueTelInput
8
- v-tooltip.show.prepend="errorTooltip"
9
- v-bind="props"
10
- v-on="listeners"
11
- />
2
+ <div class="aw-tel f32" @focusout="_onBlur">
3
+ <AwInput
4
+ ref="input"
5
+ v-bind="$attrs"
6
+ :value="value"
7
+ :placeholder="placeholder"
8
+ v-on="mergedListeners"
9
+ >
10
+ <template
11
+ #element="{ cssClass, mergedAttributes, errorTooltip, errorText, errorId }"
12
+ >
13
+ <input
14
+ ref="element"
15
+ v-bind="mergedAttributes"
16
+ v-tooltip.show.prepend="errorTooltip"
17
+ :aria-describedby="errorText ? errorId : null"
18
+ :class="cssClass"
19
+ :value="
20
+ phoneNumber.isValid()
21
+ ? phoneNumber.format('INTERNATIONAL')
22
+ : phoneNumber.number
23
+ "
24
+ type="tel"
25
+ v-on="mergedListeners"
26
+ />
27
+ </template>
28
+ <template #prefix>
29
+ <button
30
+ class="aw-tel__country-toggler focus-outline"
31
+ @click="_openCountriesDropdown"
32
+ >
33
+ <span tabindex="-1" class="aw-tel__flag">
34
+ <i class="flag" :class="country.toLowerCase()"></i>
35
+ </span>
36
+ </button>
37
+ </template>
38
+ <template #postfix>
39
+ <span
40
+ v-show="!!value"
41
+ class="aw-tel__validity"
42
+ :class="
43
+ phoneNumber.isValid()
44
+ ? 'aw-tel__validity--valid'
45
+ : 'aw-tel__validity--invalid'
46
+ "
47
+ >
48
+ <AwIconSystemMono
49
+ :name="phoneNumber.isValid() ? 'check-light' : 'close'"
50
+ />
51
+ </span>
52
+ </template>
53
+ </AwInput>
54
+ <AwDropdown
55
+ ref="dropdown"
56
+ class="aw-tel__dropdown"
57
+ :show.sync="showCountries"
58
+ @click.native="_onCountryClick"
59
+ @keydown.native="_keyFocusItem($event)"
60
+ >
61
+ <AwDropdownButton
62
+ v-for="{ name, iso } in countries"
63
+ :key="iso"
64
+ :active="country === iso"
65
+ :data-country="iso"
66
+ tabindex="-1"
67
+ >
68
+ <span class="aw-tel__flag mr-3">
69
+ <i class="flag" :class="iso.toLowerCase()"></i>
70
+ </span>
71
+ {{ name }}
72
+ </AwDropdownButton>
73
+ </AwDropdown>
12
74
  </div>
13
75
  </template>
14
76
 
15
77
  <script>
16
- import errorMixin from '@AwMixins/error'
17
- import fieldMixin from '@AwMixins/field'
78
+ import {
79
+ getCountries,
80
+ getCountryCallingCode,
81
+ getExampleNumber,
82
+ parsePhoneNumberFromString
83
+ } from 'libphonenumber-js'
84
+ import examples from 'libphonenumber-js/examples.mobile.json'
85
+ import { pathOr } from 'rambdax'
86
+ import { AwTel as _config } from '@AwConfig'
87
+ import { getEventTargetAttribute } from '@AwUtils/events'
88
+ import { conf } from '@AwUtils/component'
89
+ import arrowFocusMixin from '@AwMixins/arrow-focus'
90
+
91
+ let regionName
92
+
93
+ try {
94
+ regionName = new Intl.DisplayNames([navigator.language], { type: 'region' })
95
+ } catch (e) {
96
+ regionName = { of: (code) => code }
97
+ }
98
+
99
+ const COUNTRIES = getCountries()
100
+ .map((iso) => ({
101
+ iso,
102
+ name: regionName.of(iso),
103
+ code: getCountryCallingCode(iso)
104
+ }))
105
+ .sort((a, b) => {
106
+ let i = 0
107
+ let litA = a.name[i].toLowerCase()
108
+ let litB = b.name[i].toLowerCase()
109
+ const max = Math.min(a.name.length - 1, b.name.length - 1)
110
+
111
+ while (i < max && litA === litB) {
112
+ i++
113
+ litA = a.name[i].toLowerCase()
114
+ litB = b.name[i].toLowerCase()
115
+ }
116
+
117
+ return litA > litB ? 1 : litA < litB ? -1 : 0
118
+ })
18
119
 
19
120
  export default {
20
121
  name: 'AwTel',
21
122
 
22
123
  inheritAttrs: false,
23
124
 
24
- components: {
25
- VueTelInput: () =>
26
- import('vue-tel-input').then(({ VueTelInput }) => VueTelInput)
27
- },
125
+ mixins: [arrowFocusMixin],
126
+
127
+ COUNTRIES,
28
128
 
29
- mixins: [errorMixin, fieldMixin],
129
+ _config,
30
130
 
31
131
  props: {
32
- autoFormat: Boolean,
132
+ preselectCountry: {
133
+ type: String,
134
+ default() {
135
+ return conf(this, 'preselectCountry')
136
+ }
137
+ },
138
+
139
+ filterCountries: {
140
+ type: Array,
141
+ default() {
142
+ return conf(this, 'filterCountries')
143
+ }
144
+ },
33
145
 
34
- value: {}
146
+ value: {
147
+ type: String,
148
+ default: ''
149
+ }
35
150
  },
36
151
 
37
152
  data() {
153
+ let country = this.preselectCountry
154
+ const phoneNumber = this._parsePhoneNumber(this.value, country)
155
+
156
+ if (phoneNumber.isValid()) {
157
+ country = phoneNumber.country
158
+ }
159
+
160
+ const placeholder = this._getPlaceholder(country)
161
+
38
162
  return {
39
- VueTelInput: null
163
+ country,
164
+ placeholder,
165
+ showCountries: false
40
166
  }
41
167
  },
42
168
 
43
169
  computed: {
44
- props() {
170
+ mergedListeners() {
45
171
  return {
46
- autoFormat: this.autoFormat,
47
- value: this.value,
48
- inputOptions: {
49
- ...this.$attrs,
50
- ...this.skipAttr,
51
- id: this.id,
52
- type: 'tel',
53
- placeholder: this.label,
54
- styleClasses: 'aw-text-field__element p-3'
55
- }
172
+ ...this.$listeners,
173
+ input: this._onInput,
174
+ change: this._onInput
56
175
  }
57
176
  },
58
177
 
59
- listeners() {
60
- return {
61
- ...this.$listeners,
62
- input: this._onInputProxy
178
+ countries() {
179
+ return Array.isArray(this.filterCountries) &&
180
+ this.filterCountries.length
181
+ ? COUNTRIES.filter(
182
+ (country) =>
183
+ country.iso === this.country ||
184
+ this.filterCountries.includes(country.iso)
185
+ )
186
+ : COUNTRIES
187
+ },
188
+
189
+ callingCode() {
190
+ return this._getCallingCode(this.country)
191
+ },
192
+
193
+ phoneNumber() {
194
+ return this._parsePhoneNumber(this.value, this.country)
195
+ },
196
+
197
+ _arrowFocusSelector() {
198
+ return '[data-country]'
199
+ },
200
+
201
+ _arrowFocusInited() {
202
+ return this.showCountries
203
+ }
204
+ },
205
+
206
+ watch: {
207
+ country: {
208
+ handler(iso, oldIso) {
209
+ if (iso === oldIso) return
210
+
211
+ this.placeholder = this._getPlaceholder(iso)
212
+
213
+ if (oldIso) {
214
+ const value = this.value.replace(
215
+ '+' + this._getCallingCode(oldIso),
216
+ '+' + this.callingCode
217
+ )
218
+ const phoneNumber = this._parsePhoneNumber(value, iso)
219
+
220
+ this._emitInput(phoneNumber)
221
+ }
222
+
223
+ if (this.$refs.element) {
224
+ this.$refs.element.focus()
225
+ }
63
226
  }
64
227
  }
65
228
  },
66
229
 
67
230
  methods: {
68
- // overwrite element getter from error mixin
69
- _getElement() {
70
- return this.$el && this.$el.querySelector('input[type="tel"]')
231
+ _onInput($event) {
232
+ const value = $event.target.value
233
+ const phoneNumber = this._parsePhoneNumber(value, this.country)
234
+
235
+ if (this.phoneNumber.isEqual(phoneNumber)) {
236
+ return
237
+ }
238
+
239
+ if (this.$refs.input.hasError) {
240
+ this.$refs.input.setError('')
241
+ }
242
+
243
+ if (phoneNumber.isValid() && phoneNumber.country !== this.country) {
244
+ this.country = phoneNumber.country
245
+ }
246
+
247
+ this._emitInput(phoneNumber)
248
+ },
249
+
250
+ _onBlur($event) {
251
+ if (!this.$el.contains($event.relatedTarget)) {
252
+ this.showCountries = false
253
+ }
71
254
  },
72
255
 
73
- _onInputProxy(text) {
74
- if (this.hasError && !!text) {
75
- this.setError('')
256
+ _getPlaceholder(iso) {
257
+ const phoneNumber = getExampleNumber(iso, examples)
258
+
259
+ if (phoneNumber) {
260
+ const number = phoneNumber.format('INTERNATIONAL')
261
+ const code = '+' + phoneNumber.countryCallingCode
262
+
263
+ return number
264
+ .replace(code, '{code}')
265
+ .replace(/\d/g, '_')
266
+ .replace('{code}', code)
267
+ } else {
268
+ return ''
76
269
  }
270
+ },
271
+
272
+ _onCountryClick($event) {
273
+ this.country = getEventTargetAttribute(
274
+ $event,
275
+ 'data-country',
276
+ this.$el
277
+ )
278
+ this.showCountries = false
279
+ },
77
280
 
78
- if ((text || this.value) && this.value !== text) {
79
- this.$emit('input', ...arguments)
281
+ _parsePhoneNumber(value, country) {
282
+ const number = typeof value === 'string' ? value : ''
283
+ const phoneNumber = parsePhoneNumberFromString(number, country)
284
+
285
+ if (phoneNumber) {
286
+ return phoneNumber
287
+ } else {
288
+ // mock PhoneNumber class
289
+ return {
290
+ number,
291
+ country,
292
+ countryCallingCode: this.callingCode,
293
+ format() {
294
+ return number
295
+ },
296
+ isValid() {
297
+ return false
298
+ },
299
+ isEqual(pn) {
300
+ return pn.number === number && pn.country === country
301
+ }
302
+ }
80
303
  }
304
+ },
305
+
306
+ _getCallingCode(iso) {
307
+ return pathOr(
308
+ '',
309
+ 'code',
310
+ COUNTRIES.find((country) => iso === country.iso)
311
+ )
312
+ },
313
+
314
+ _emitInput(phoneNumber) {
315
+ if (phoneNumber.number === this.value) return
316
+
317
+ this.$emit('input', phoneNumber.number, {
318
+ isValid: phoneNumber.isValid(),
319
+ country: phoneNumber.country,
320
+ number: {
321
+ national: phoneNumber.format('NATIONAL'),
322
+ international: phoneNumber.format('INTERNATIONAL'),
323
+ e164: phoneNumber.number,
324
+ rfc3966: phoneNumber.format('RFC3966')
325
+ }
326
+ })
327
+ },
328
+
329
+ _openCountriesDropdown() {
330
+ this.showCountries = true
331
+
332
+ this.$nextTick(() => {
333
+ const el =
334
+ this.$refs.dropdown &&
335
+ this.$refs.dropdown.$el.querySelector('.is-active')
336
+
337
+ if (el) {
338
+ el.focus()
339
+ }
340
+ })
81
341
  }
82
342
  }
83
343
  }
84
344
  </script>
345
+
346
+ <style lang="postcss">
347
+ @import 'world-flags-sprite/stylesheets/flags32.css';
348
+ </style>
@@ -209,6 +209,11 @@ export const AwTags = {
209
209
  colors: COLORS
210
210
  }
211
211
 
212
+ export const AwTel = {
213
+ preselectCountry: 'DE',
214
+ filterCountries: []
215
+ }
216
+
212
217
  export default {
213
218
  AwAvatar,
214
219
  AwButton,
@@ -225,5 +230,6 @@ export default {
225
230
  AwPage,
226
231
  AwModal,
227
232
  AwTable,
228
- AwTags
233
+ AwTags,
234
+ AwTel
229
235
  }