@awes-io/ui 2.32.5 → 2.34.2

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.2](https://github.com/awes-io/client/compare/@awes-io/ui@2.34.1...@awes-io/ui@2.34.2) (2021-11-26)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **aw-tel:** type button added to dropdown button ([c1369cd](https://github.com/awes-io/client/commit/c1369cd16efb9b569f5a41a94da2eee746a8e40d))
12
+
13
+
14
+
15
+
16
+
17
+ ## [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)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * **aw-tel:** check if given value is string before parsing ([4161fb6](https://github.com/awes-io/client/commit/4161fb67b57ccea772a390769dab200baee15a44))
23
+
24
+
25
+
26
+
27
+
28
+ # [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)
29
+
30
+
31
+ ### Features
32
+
33
+ * aw-tel component rewrited ([a072dda](https://github.com/awes-io/client/commit/a072dda44254d783b6445416bede84459639ce8d))
34
+
35
+
36
+
37
+
38
+
39
+ # [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)
40
+
41
+
42
+ ### Bug Fixes
43
+
44
+ * mask works only in allowed inputs ([3dcc47e](https://github.com/awes-io/client/commit/3dcc47e1c58e4dac1763aaeb7f50d879c34799db))
45
+
46
+
47
+ ### Features
48
+
49
+ * dayjs upgraded ([b1aa822](https://github.com/awes-io/client/commit/b1aa8228f2b7541acc180cad5bcb477a2d2a37d6))
50
+
51
+
52
+
53
+
54
+
6
55
  ## [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)
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,349 @@
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
+ type="button"
31
+ class="aw-tel__country-toggler focus-outline"
32
+ @click="_openCountriesDropdown"
33
+ >
34
+ <span tabindex="-1" class="aw-tel__flag">
35
+ <i class="flag" :class="country.toLowerCase()"></i>
36
+ </span>
37
+ </button>
38
+ </template>
39
+ <template #postfix>
40
+ <span
41
+ v-show="!!value"
42
+ class="aw-tel__validity"
43
+ :class="
44
+ phoneNumber.isValid()
45
+ ? 'aw-tel__validity--valid'
46
+ : 'aw-tel__validity--invalid'
47
+ "
48
+ >
49
+ <AwIconSystemMono
50
+ :name="phoneNumber.isValid() ? 'check-light' : 'close'"
51
+ />
52
+ </span>
53
+ </template>
54
+ </AwInput>
55
+ <AwDropdown
56
+ ref="dropdown"
57
+ class="aw-tel__dropdown"
58
+ :show.sync="showCountries"
59
+ @click.native="_onCountryClick"
60
+ @keydown.native="_keyFocusItem($event)"
61
+ >
62
+ <AwDropdownButton
63
+ v-for="{ name, iso } in countries"
64
+ :key="iso"
65
+ :active="country === iso"
66
+ :data-country="iso"
67
+ tabindex="-1"
68
+ >
69
+ <span class="aw-tel__flag mr-3">
70
+ <i class="flag" :class="iso.toLowerCase()"></i>
71
+ </span>
72
+ {{ name }}
73
+ </AwDropdownButton>
74
+ </AwDropdown>
12
75
  </div>
13
76
  </template>
14
77
 
15
78
  <script>
16
- import errorMixin from '@AwMixins/error'
17
- import fieldMixin from '@AwMixins/field'
79
+ import {
80
+ getCountries,
81
+ getCountryCallingCode,
82
+ getExampleNumber,
83
+ parsePhoneNumberFromString
84
+ } from 'libphonenumber-js'
85
+ import examples from 'libphonenumber-js/examples.mobile.json'
86
+ import { pathOr } from 'rambdax'
87
+ import { AwTel as _config } from '@AwConfig'
88
+ import { getEventTargetAttribute } from '@AwUtils/events'
89
+ import { conf } from '@AwUtils/component'
90
+ import arrowFocusMixin from '@AwMixins/arrow-focus'
91
+
92
+ let regionName
93
+
94
+ try {
95
+ regionName = new Intl.DisplayNames([navigator.language], { type: 'region' })
96
+ } catch (e) {
97
+ regionName = { of: (code) => code }
98
+ }
99
+
100
+ const COUNTRIES = getCountries()
101
+ .map((iso) => ({
102
+ iso,
103
+ name: regionName.of(iso),
104
+ code: getCountryCallingCode(iso)
105
+ }))
106
+ .sort((a, b) => {
107
+ let i = 0
108
+ let litA = a.name[i].toLowerCase()
109
+ let litB = b.name[i].toLowerCase()
110
+ const max = Math.min(a.name.length - 1, b.name.length - 1)
111
+
112
+ while (i < max && litA === litB) {
113
+ i++
114
+ litA = a.name[i].toLowerCase()
115
+ litB = b.name[i].toLowerCase()
116
+ }
117
+
118
+ return litA > litB ? 1 : litA < litB ? -1 : 0
119
+ })
18
120
 
19
121
  export default {
20
122
  name: 'AwTel',
21
123
 
22
124
  inheritAttrs: false,
23
125
 
24
- components: {
25
- VueTelInput: () =>
26
- import('vue-tel-input').then(({ VueTelInput }) => VueTelInput)
27
- },
126
+ mixins: [arrowFocusMixin],
127
+
128
+ COUNTRIES,
28
129
 
29
- mixins: [errorMixin, fieldMixin],
130
+ _config,
30
131
 
31
132
  props: {
32
- autoFormat: Boolean,
133
+ preselectCountry: {
134
+ type: String,
135
+ default() {
136
+ return conf(this, 'preselectCountry')
137
+ }
138
+ },
139
+
140
+ filterCountries: {
141
+ type: Array,
142
+ default() {
143
+ return conf(this, 'filterCountries')
144
+ }
145
+ },
33
146
 
34
- value: {}
147
+ value: {
148
+ type: String,
149
+ default: ''
150
+ }
35
151
  },
36
152
 
37
153
  data() {
154
+ let country = this.preselectCountry
155
+ const phoneNumber = this._parsePhoneNumber(this.value, country)
156
+
157
+ if (phoneNumber.isValid()) {
158
+ country = phoneNumber.country
159
+ }
160
+
161
+ const placeholder = this._getPlaceholder(country)
162
+
38
163
  return {
39
- VueTelInput: null
164
+ country,
165
+ placeholder,
166
+ showCountries: false
40
167
  }
41
168
  },
42
169
 
43
170
  computed: {
44
- props() {
171
+ mergedListeners() {
45
172
  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
- }
173
+ ...this.$listeners,
174
+ input: this._onInput,
175
+ change: this._onInput
56
176
  }
57
177
  },
58
178
 
59
- listeners() {
60
- return {
61
- ...this.$listeners,
62
- input: this._onInputProxy
179
+ countries() {
180
+ return Array.isArray(this.filterCountries) &&
181
+ this.filterCountries.length
182
+ ? COUNTRIES.filter(
183
+ (country) =>
184
+ country.iso === this.country ||
185
+ this.filterCountries.includes(country.iso)
186
+ )
187
+ : COUNTRIES
188
+ },
189
+
190
+ callingCode() {
191
+ return this._getCallingCode(this.country)
192
+ },
193
+
194
+ phoneNumber() {
195
+ return this._parsePhoneNumber(this.value, this.country)
196
+ },
197
+
198
+ _arrowFocusSelector() {
199
+ return '[data-country]'
200
+ },
201
+
202
+ _arrowFocusInited() {
203
+ return this.showCountries
204
+ }
205
+ },
206
+
207
+ watch: {
208
+ country: {
209
+ handler(iso, oldIso) {
210
+ if (iso === oldIso) return
211
+
212
+ this.placeholder = this._getPlaceholder(iso)
213
+
214
+ if (oldIso) {
215
+ const value = this.value.replace(
216
+ '+' + this._getCallingCode(oldIso),
217
+ '+' + this.callingCode
218
+ )
219
+ const phoneNumber = this._parsePhoneNumber(value, iso)
220
+
221
+ this._emitInput(phoneNumber)
222
+ }
223
+
224
+ if (this.$refs.element) {
225
+ this.$refs.element.focus()
226
+ }
63
227
  }
64
228
  }
65
229
  },
66
230
 
67
231
  methods: {
68
- // overwrite element getter from error mixin
69
- _getElement() {
70
- return this.$el && this.$el.querySelector('input[type="tel"]')
232
+ _onInput($event) {
233
+ const value = $event.target.value
234
+ const phoneNumber = this._parsePhoneNumber(value, this.country)
235
+
236
+ if (this.phoneNumber.isEqual(phoneNumber)) {
237
+ return
238
+ }
239
+
240
+ if (this.$refs.input.hasError) {
241
+ this.$refs.input.setError('')
242
+ }
243
+
244
+ if (phoneNumber.isValid() && phoneNumber.country !== this.country) {
245
+ this.country = phoneNumber.country
246
+ }
247
+
248
+ this._emitInput(phoneNumber)
249
+ },
250
+
251
+ _onBlur($event) {
252
+ if (!this.$el.contains($event.relatedTarget)) {
253
+ this.showCountries = false
254
+ }
71
255
  },
72
256
 
73
- _onInputProxy(text) {
74
- if (this.hasError && !!text) {
75
- this.setError('')
257
+ _getPlaceholder(iso) {
258
+ const phoneNumber = getExampleNumber(iso, examples)
259
+
260
+ if (phoneNumber) {
261
+ const number = phoneNumber.format('INTERNATIONAL')
262
+ const code = '+' + phoneNumber.countryCallingCode
263
+
264
+ return number
265
+ .replace(code, '{code}')
266
+ .replace(/\d/g, '_')
267
+ .replace('{code}', code)
268
+ } else {
269
+ return ''
76
270
  }
271
+ },
272
+
273
+ _onCountryClick($event) {
274
+ this.country = getEventTargetAttribute(
275
+ $event,
276
+ 'data-country',
277
+ this.$el
278
+ )
279
+ this.showCountries = false
280
+ },
77
281
 
78
- if ((text || this.value) && this.value !== text) {
79
- this.$emit('input', ...arguments)
282
+ _parsePhoneNumber(value, country) {
283
+ const number = typeof value === 'string' ? value : ''
284
+ const phoneNumber = parsePhoneNumberFromString(number, country)
285
+
286
+ if (phoneNumber) {
287
+ return phoneNumber
288
+ } else {
289
+ // mock PhoneNumber class
290
+ return {
291
+ number,
292
+ country,
293
+ countryCallingCode: this.callingCode,
294
+ format() {
295
+ return number
296
+ },
297
+ isValid() {
298
+ return false
299
+ },
300
+ isEqual(pn) {
301
+ return pn.number === number && pn.country === country
302
+ }
303
+ }
80
304
  }
305
+ },
306
+
307
+ _getCallingCode(iso) {
308
+ return pathOr(
309
+ '',
310
+ 'code',
311
+ COUNTRIES.find((country) => iso === country.iso)
312
+ )
313
+ },
314
+
315
+ _emitInput(phoneNumber) {
316
+ if (phoneNumber.number === this.value) return
317
+
318
+ this.$emit('input', phoneNumber.number, {
319
+ isValid: phoneNumber.isValid(),
320
+ country: phoneNumber.country,
321
+ number: {
322
+ national: phoneNumber.format('NATIONAL'),
323
+ international: phoneNumber.format('INTERNATIONAL'),
324
+ e164: phoneNumber.number,
325
+ rfc3966: phoneNumber.format('RFC3966')
326
+ }
327
+ })
328
+ },
329
+
330
+ _openCountriesDropdown() {
331
+ this.showCountries = true
332
+
333
+ this.$nextTick(() => {
334
+ const el =
335
+ this.$refs.dropdown &&
336
+ this.$refs.dropdown.$el.querySelector('.is-active')
337
+
338
+ if (el) {
339
+ el.focus()
340
+ }
341
+ })
81
342
  }
82
343
  }
83
344
  }
84
345
  </script>
346
+
347
+ <style lang="postcss">
348
+ @import 'world-flags-sprite/stylesheets/flags32.css';
349
+ </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
  }