@awes-io/ui 2.44.0 → 2.47.0

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,39 @@
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.47.0](https://github.com/awes-io/client/compare/@awes-io/ui@2.46.0...@awes-io/ui@2.47.0) (2022-03-31)
7
+
8
+
9
+ ### Features
10
+
11
+ * **aw-tel:** placeholder replaced with mask ([e54e324](https://github.com/awes-io/client/commit/e54e324cb9ec576f583070a74a06349c23270685))
12
+
13
+
14
+
15
+
16
+
17
+ # [2.46.0](https://github.com/awes-io/client/compare/@awes-io/ui@2.45.0...@awes-io/ui@2.46.0) (2022-03-17)
18
+
19
+
20
+ ### Features
21
+
22
+ * **store:** user name preformatting added ([36de390](https://github.com/awes-io/client/commit/36de3908308dde463a93198974cca2e79f5945a2))
23
+
24
+
25
+
26
+
27
+
28
+ # [2.45.0](https://github.com/awes-io/client/compare/@awes-io/ui@2.44.0...@awes-io/ui@2.45.0) (2022-03-17)
29
+
30
+
31
+ ### Features
32
+
33
+ * birthday-picker updated ([4b7bedc](https://github.com/awes-io/client/commit/4b7bedc64c5358673540120fb8a7b647d8444e90))
34
+
35
+
36
+
37
+
38
+
6
39
  # [2.44.0](https://github.com/awes-io/client/compare/@awes-io/ui@2.43.0...@awes-io/ui@2.44.0) (2022-03-10)
7
40
 
8
41
 
@@ -23,3 +23,37 @@
23
23
  transform: scale(1.3);
24
24
  }
25
25
  }
26
+
27
+ @keyframes aw-shake {
28
+ 0%,
29
+ 100% {
30
+ transform:translateX(0)
31
+ }
32
+ 10% {
33
+ transform:translateX(-9px)
34
+ }
35
+ 20% {
36
+ transform:translateX(8px)
37
+ }
38
+ 30% {
39
+ transform:translateX(-7px)
40
+ }
41
+ 40% {
42
+ transform:translateX(6px)
43
+ }
44
+ 50% {
45
+ transform:translateX(-5px)
46
+ }
47
+ 60% {
48
+ transform:translateX(4px)
49
+ }
50
+ 70% {
51
+ transform:translateX(-3px)
52
+ }
53
+ 80% {
54
+ transform:translateX(2px)
55
+ }
56
+ 90% {
57
+ transform:translateX(-1px)
58
+ }
59
+ }
@@ -4,86 +4,29 @@
4
4
  display: flex;
5
5
  align-items: center;
6
6
  text-align: center;
7
+ transition: 200ms border-color;
7
8
 
8
9
  &.has-error {
9
10
  @apply border-error;
10
- animation: 700ms shake ease-out-quad;
11
+ animation: 700ms aw-shake ease-out-quad;
12
+ }
13
+
14
+ &:focus-within {
15
+ border-color: var(--c-info);
11
16
  }
12
17
  }
13
18
 
14
19
  &__input {
15
- border: none;
20
+ @apply p-3;
21
+ text-align: center;
22
+ border-width: none;
16
23
  border-radius: 0;
17
- flex: auto;
18
-
19
- input {
20
- text-align: center;
21
- border-radius: 0;
22
- }
23
-
24
- &--day {
25
- &::before, &::after {
26
- @apply left-0 top-0 absolute;
27
- content: '';
28
- z-index: 1;
29
- top: 50%;
30
- transform: translateY(-50%);
31
- width: 1px;
32
- height: 100%;
33
- background-color: rgba(var(--c-on-surface-rgb), 0.1);
34
- }
24
+ flex: 1 1 auto;
35
25
 
36
- &::after {
37
- left: unset;
38
- right: 0;
39
- }
26
+ & + & {
27
+ @apply border-l;
28
+ border-top-left-radius: 0;
29
+ border-bottom-left-radius: 0;
40
30
  }
41
31
  }
42
-
43
-
44
-
45
- /* &__day {
46
- min-width: 6rem;
47
- flex-basis: 20%;
48
- } */
49
-
50
- /* &__month,
51
- &__year {
52
- min-width: 5rem;
53
- flex-basis: 40%;
54
- } */
55
32
  }
56
-
57
- @keyframes shake {
58
- 0%,
59
- 100% {
60
- transform:translateX(0)
61
- }
62
- 10% {
63
- transform:translateX(-9px)
64
- }
65
- 20% {
66
- transform:translateX(8px)
67
- }
68
- 30% {
69
- transform:translateX(-7px)
70
- }
71
- 40% {
72
- transform:translateX(6px)
73
- }
74
- 50% {
75
- transform:translateX(-5px)
76
- }
77
- 60% {
78
- transform:translateX(4px)
79
- }
80
- 70% {
81
- transform:translateX(-3px)
82
- }
83
- 80% {
84
- transform:translateX(2px)
85
- }
86
- 90% {
87
- transform:translateX(-1px)
88
- }
89
- }
@@ -9,6 +9,7 @@
9
9
  position: sticky;
10
10
  bottom: 0;
11
11
  min-height: 4rem;
12
+ z-index: 20;
12
13
 
13
14
  & > * {
14
15
  flex-basis: theme('spacing.20', 5rem);
@@ -29,7 +29,10 @@
29
29
 
30
30
  &__dropdown {
31
31
  width: 100%;
32
- max-height: 50vh;
32
+
33
+ &.aw-dropdown--desktop {
34
+ max-height: 50vh;
35
+ }
33
36
  }
34
37
 
35
38
  &__validity {
@@ -158,7 +158,7 @@
158
158
  */
159
159
 
160
160
  &.has-error {
161
- animation: 700ms shake ease-out-quad;
161
+ animation: 700ms aw-shake ease-out-quad;
162
162
  }
163
163
 
164
164
  &.has-error {
@@ -198,40 +198,6 @@
198
198
  }
199
199
  }
200
200
 
201
- @keyframes shake {
202
- 0%,
203
- 100% {
204
- transform:translateX(0)
205
- }
206
- 10% {
207
- transform:translateX(-9px)
208
- }
209
- 20% {
210
- transform:translateX(8px)
211
- }
212
- 30% {
213
- transform:translateX(-7px)
214
- }
215
- 40% {
216
- transform:translateX(6px)
217
- }
218
- 50% {
219
- transform:translateX(-5px)
220
- }
221
- 60% {
222
- transform:translateX(4px)
223
- }
224
- 70% {
225
- transform:translateX(-3px)
226
- }
227
- 80% {
228
- transform:translateX(2px)
229
- }
230
- 90% {
231
- transform:translateX(-1px)
232
- }
233
- }
234
-
235
201
  @keyframes autoFillStart {
236
202
  from {
237
203
  opacity: 1
@@ -10,7 +10,7 @@
10
10
 
11
11
  &[data-visible] {
12
12
  display: block;
13
- z-index: 20;
13
+ z-index: 19;
14
14
  }
15
15
 
16
16
  &[data-popper-reference-hidden] {
@@ -1,66 +1,79 @@
1
1
  <template>
2
2
  <AwInfo :label="label" class="aw-birthday-picker">
3
- <label
3
+ <div
4
4
  ref="element"
5
5
  v-tooltip.show.prepend="errorTooltip"
6
6
  :class="{ 'has-error': hasError && errorText }"
7
- class="aw-birthday-picker__wrap"
7
+ class="aw-text-field aw-birthday-picker__wrap"
8
8
  >
9
- <AwInput
10
- v-model="displayMonth"
9
+ <input
10
+ v-for="{ key, ...attrs } in _inputs"
11
+ :key="key"
11
12
  :required="required"
12
- :name="_inputKeys.month"
13
- :placeholder="$t('AwBirthdayPicker.month')"
14
- pattern="##"
15
- class="aw-birthday-picker__input"
13
+ :value="$data[key]"
14
+ v-bind="attrs"
16
15
  inputmode="numeric"
16
+ class="aw-text-field__element aw-birthday-picker__input"
17
+ @keydown="onKeydown"
18
+ @input="onInput"
17
19
  @focus="onInputFocus"
18
- @blur="onMonthBlur"
19
20
  />
20
-
21
- <AwInput
22
- ref="dayInput"
23
- v-model="displayDay"
24
- :required="required"
25
- :name="_inputKeys.day"
26
- :placeholder="$t('AwBirthdayPicker.day')"
27
- pattern="##"
28
- inputmode="numeric"
29
- class="aw-birthday-picker__input aw-birthday-picker__input--day"
30
- @focus="onInputFocus"
31
- @blur="onDayBlur"
32
- />
33
-
34
- <AwInput
35
- ref="yearInput"
36
- v-model="year"
37
- :required="required"
38
- :name="_inputKeys.year"
39
- :placeholder="$t('AwBirthdayPicker.year')"
40
- pattern="####"
41
- class="aw-birthday-picker__input"
42
- inputmode="numeric"
43
- @input="onYearInput"
44
- @focus="onInputFocus"
45
- />
46
- </label>
21
+ </div>
47
22
  </AwInfo>
48
23
  </template>
49
24
 
50
25
  <script>
51
26
  import birthdayMixin from '@AwMixins/birthday'
52
- import ErrorMixin from '@AwMixins/error'
27
+ import errorMixin from '@AwMixins/error'
28
+ import { AwBirthdayPicker as _config } from '@AwConfig'
29
+ import { conf } from '@AwUtils/component'
30
+
31
+ const KEY_ATTR = 'data-key'
32
+
33
+ const ALLOWED_KEYS = [
34
+ 'Delete',
35
+ 'Backspace',
36
+ 'Shift',
37
+ 'ArrowLeft',
38
+ 'ArrowRight',
39
+ 'Tab'
40
+ ]
41
+
42
+ const PLACEHOLDER_KEY_MAP = {
43
+ DD: 'day',
44
+ MM: 'month',
45
+ YYYY: 'year'
46
+ }
53
47
 
54
- const DEFAULT_KEYS = {
48
+ const PLACEHOLDER_MAP = {
49
+ day: 'DD',
50
+ month: 'MM',
51
+ year: 'YYYY'
52
+ }
53
+
54
+ const NAME_MAP = {
55
55
  day: 'birthday_day',
56
56
  month: 'birthday_month',
57
57
  year: 'birthday_year'
58
58
  }
59
59
 
60
+ const MASK_MAP = {
61
+ day: '\\d{2}',
62
+ month: '\\d{2}',
63
+ year: '\\d{4}'
64
+ }
65
+
66
+ const PROCESSOR_MAP = {
67
+ day: '_processDay',
68
+ month: '_processMonth'
69
+ }
70
+
60
71
  export default {
61
72
  name: 'AwBirthdayPicker',
62
73
 
63
- mixins: [birthdayMixin, ErrorMixin],
74
+ mixins: [birthdayMixin, errorMixin],
75
+
76
+ _config,
64
77
 
65
78
  props: {
66
79
  value: {
@@ -73,14 +86,6 @@ export default {
73
86
  default: false
74
87
  },
75
88
 
76
- /**
77
- * If true show year select by default
78
- */
79
- showYear: {
80
- type: Boolean,
81
- default: false
82
- },
83
-
84
89
  /**
85
90
  * Inputs names for native form submission,
86
91
  * Accepted values: day, month, year.
@@ -95,27 +100,77 @@ export default {
95
100
  default() {
96
101
  return this.$t('AwBirthdayPicker.birthday')
97
102
  }
103
+ },
104
+
105
+ /**
106
+ * Custom parse format for string dates with year,
107
+ * for example `YYYY-MM-DD`.
108
+ */
109
+ fullParseFormat: {
110
+ type: String,
111
+ default() {
112
+ return conf(this, 'fullParseFormat')
113
+ }
114
+ },
115
+
116
+ /**
117
+ * Custom parse format for string dates without year,
118
+ * for example `MM-DD`.
119
+ */
120
+ shortParseFormat: {
121
+ type: String,
122
+ default() {
123
+ return conf(this, 'shortParseFormat')
124
+ }
125
+ },
126
+
127
+ /**
128
+ * Input fields order
129
+ */
130
+ fieldsOrder: {
131
+ type: String,
132
+ default() {
133
+ return conf(this, 'fieldsOrder')
134
+ }
98
135
  }
99
136
  },
100
137
 
101
138
  data() {
102
139
  return {
103
- month: null,
104
140
  day: null,
141
+ month: null,
105
142
  year: null,
106
- maxDay: 31,
107
- displayDay: null,
108
- displayMonth: null,
109
- inputTimeout: null
143
+ maxDay: 31
110
144
  }
111
145
  },
112
146
 
113
147
  computed: {
114
- _inputKeys() {
115
- return {
116
- ...DEFAULT_KEYS,
117
- ...this.inputNames
118
- }
148
+ _inputsOrder() {
149
+ return this.fieldsOrder
150
+ .split(/(\.|-)/)
151
+ .reduce((acc, placeholder) => {
152
+ const key = PLACEHOLDER_KEY_MAP[placeholder]
153
+ return key ? acc.concat(key) : acc
154
+ }, [])
155
+ },
156
+
157
+ _inputNames() {
158
+ return this._inputsOrder.map((key) => ({
159
+ name: this.inputNames[key] || NAME_MAP[key],
160
+ key
161
+ }))
162
+ },
163
+
164
+ _inputs() {
165
+ return this._inputsOrder.map((key, i) => ({
166
+ key,
167
+ [KEY_ATTR]: key,
168
+ name: this._inputNames[i].name,
169
+ placeholder: this.$t('AwBirthdayPicker.' + key),
170
+ pattern: MASK_MAP[key],
171
+ maxlength: PLACEHOLDER_MAP[key].length,
172
+ inputmode: 'numeric'
173
+ }))
119
174
  }
120
175
  },
121
176
 
@@ -124,37 +179,25 @@ export default {
124
179
  immediate: true,
125
180
  handler(val) {
126
181
  const date = this.parseDate(val)
182
+
127
183
  if (date) {
128
- this.month = date.month + 1
129
- this.day = date.day
130
- this.isYearHidden = !date.year
184
+ const { day, month, year } = date
185
+
131
186
  let d = this.$dayjs()
132
187
  .year(2000)
133
- .month(date.month)
134
- .date(date.day)
188
+ .month(month)
189
+ .date(day)
135
190
  .startOf('day')
136
191
 
137
- if (date.year) {
138
- this.year = date.year
139
- d = d.year(date.year)
192
+ if (year) {
193
+ d = d.year(year)
194
+ this.year = d.format(PLACEHOLDER_MAP.year)
140
195
  }
141
196
 
142
- this.displayDay = d.format('DD')
143
- this.displayMonth = d.format('MM')
197
+ this.day = d.format(PLACEHOLDER_MAP.day)
198
+ this.month = d.format(PLACEHOLDER_MAP.month)
144
199
  }
145
200
  }
146
- },
147
-
148
- displayMonth(val) {
149
- if (val && val.length == 2) {
150
- this.$refs['dayInput'].$el.focus()
151
- }
152
- },
153
-
154
- displayDay(val) {
155
- if (val && val.length == 2) {
156
- this.$refs['yearInput'].$el.focus()
157
- }
158
201
  }
159
202
  },
160
203
 
@@ -169,72 +212,90 @@ export default {
169
212
 
170
213
  this.maxDay = d.daysInMonth()
171
214
  this.day = this.day > this.maxDay ? this.maxDay : this.day
215
+ }
216
+ },
172
217
 
173
- this.displayDay = d
174
- .date(this.day)
175
- .startOf('day')
176
- .format('DD')
218
+ onKeydown($event) {
219
+ if (
220
+ isNaN(parseInt($event.key)) &&
221
+ !ALLOWED_KEYS.includes($event.key)
222
+ ) {
223
+ $event.preventDefault()
177
224
  }
178
225
 
179
- this.emit()
180
- },
226
+ const key = this._getKey($event.target)
181
227
 
182
- onMonthBlur() {
183
- const val = parseInt(this.displayMonth)
228
+ // arrow navigation
229
+ if ($event.key === 'ArrowRight' || $event.key === 'ArrowLeft') {
230
+ const length = $event.target.value.length
231
+ const { selectionStart, selectionEnd } = $event.target
184
232
 
185
- if (isNaN(val)) {
186
- this.month = null
187
- this.displayMonth = null
188
- this.checkMaxDay()
189
- return
190
- }
233
+ let offset = 0
234
+
235
+ if (
236
+ $event.key === 'ArrowRight' &&
237
+ selectionStart === length &&
238
+ selectionEnd === length
239
+ ) {
240
+ offset = 1
241
+ }
191
242
 
192
- if (val !== this.month) {
193
- if (val > 12) {
194
- this.month = 12
195
- } else {
196
- this.month = val
243
+ if (
244
+ $event.key === 'ArrowLeft' &&
245
+ selectionStart === 0 &&
246
+ selectionEnd === 0
247
+ ) {
248
+ offset = -1
197
249
  }
198
250
 
199
- this.checkMaxDay()
251
+ if (offset) {
252
+ $event.preventDefault()
253
+ this._focusByOffset(key, offset)
254
+ }
200
255
  }
201
256
 
202
- const d = this.$dayjs()
203
- .year(this.year || 2000)
204
- .month(this.month - 1)
205
- .startOf('month')
206
- this.displayMonth = d.format('MM')
257
+ // backspace navigation
258
+ if ($event.key === 'Backspace' && !$event.target.value) {
259
+ $event.preventDefault()
260
+ this._focusByOffset(this._getKey($event.target), -1)
261
+ }
207
262
  },
208
263
 
209
- onDayBlur(e) {
210
- const val = parseInt(e.target.value)
264
+ onInput($event) {
265
+ const key = this._getKey($event.target)
266
+ const processor = PROCESSOR_MAP[key]
211
267
 
212
- if (isNaN(val)) {
213
- this.day = null
214
- this.displayDay = null
215
- this.emit()
216
- return
217
- }
268
+ if (processor) {
269
+ const correctValue = this[processor]($event.target.value)
218
270
 
219
- if (val === this.day) {
220
- return
271
+ if (correctValue !== $event.target.value) {
272
+ $event.target.value = correctValue
273
+ }
221
274
  }
222
275
 
223
- this.day = val
276
+ this[key] = $event.target.value
277
+
224
278
  this.checkMaxDay()
225
- },
226
279
 
227
- onInputFocus(e) {
228
- if (e.target.value) {
229
- e.target.setSelectionRange(0, e.target.value.length)
230
- }
280
+ this.$nextTick(() => {
281
+ if (this._isComplete(this[key], key)) {
282
+ this._focusByOffset(key, 1)
283
+ this.emit()
284
+ } else if (this[key] === '') {
285
+ this.emit()
286
+ }
287
+ })
231
288
  },
232
289
 
233
- onYearInput() {
234
- clearTimeout(this.inputTimeout)
235
- this.inputTimeout = setTimeout(() => {
236
- this.checkMaxDay()
237
- }, 200)
290
+ onInputFocus($event) {
291
+ if ($event.target.value) {
292
+ setTimeout(() => {
293
+ $event.target.setSelectionRange(
294
+ 0,
295
+ $event.target.value.length
296
+ )
297
+ }, 10)
298
+ }
238
299
  },
239
300
 
240
301
  emit() {
@@ -257,6 +318,93 @@ export default {
257
318
  formatVal ? d.format(formatVal) : d.format()
258
319
  )
259
320
  }
321
+ },
322
+
323
+ _getKey(el) {
324
+ return el.getAttribute(KEY_ATTR)
325
+ },
326
+
327
+ _getKeyByOffset(key, offset) {
328
+ const index = this._inputsOrder.indexOf(key)
329
+
330
+ return index > -1 ? this._inputsOrder[index + offset] : undefined
331
+ },
332
+
333
+ _getKeyElement(key) {
334
+ return this.$refs.element.querySelector(`[${KEY_ATTR}="${key}"]`)
335
+ },
336
+
337
+ _isComplete(val = '', key) {
338
+ const numVal = parseInt(val)
339
+
340
+ // check if a number
341
+ if (isNaN(numVal)) {
342
+ return false
343
+ }
344
+
345
+ // check value length
346
+ if (
347
+ PLACEHOLDER_MAP[key] &&
348
+ val.length !== PLACEHOLDER_MAP[key].length
349
+ ) {
350
+ return false
351
+ }
352
+
353
+ // check range
354
+ switch (key) {
355
+ case 'day':
356
+ return numVal > 1 && numVal <= this.maxDay
357
+ case 'month':
358
+ return numVal > 1 && numVal <= 12
359
+ case 'year':
360
+ return true
361
+ default:
362
+ return false
363
+ }
364
+ },
365
+
366
+ _focusByOffset(key, offset) {
367
+ const el = this._getKeyElement(this._getKeyByOffset(key, offset))
368
+
369
+ if (el) {
370
+ el.focus()
371
+ }
372
+ },
373
+
374
+ _processDateVal(val, min = 0, max) {
375
+ if (!val) return ''
376
+
377
+ const numVal = parseInt(val)
378
+
379
+ if (isNaN(numVal)) {
380
+ return ''
381
+ }
382
+
383
+ if (numVal > max) {
384
+ return String(max)
385
+ }
386
+
387
+ if (numVal < min) {
388
+ return ''
389
+ }
390
+
391
+ if (val.length === 1) {
392
+ const maxFirstDigit = Math.floor(max / 10)
393
+
394
+ if (numVal > maxFirstDigit) {
395
+ return '0' + String(numVal)
396
+ }
397
+ }
398
+
399
+ return val
400
+ },
401
+
402
+ _processDay(val) {
403
+ return this._processDateVal(val, 0, this.maxDay)
404
+ },
405
+
406
+ _processMonth(val) {
407
+ return this._processDateVal(val, 0, 12)
260
408
  }
261
409
  }
262
410
  }
@@ -3,8 +3,7 @@
3
3
  <AwInput
4
4
  ref="input"
5
5
  v-bind="$attrs"
6
- :value="value"
7
- :placeholder="placeholder"
6
+ :value="phoneValue"
8
7
  v-on="mergedListeners"
9
8
  >
10
9
  <template
@@ -16,11 +15,7 @@
16
15
  v-tooltip.show.prepend="errorTooltip"
17
16
  :aria-describedby="errorText ? errorId : null"
18
17
  :class="cssClass"
19
- :value="
20
- phoneNumber.isValid()
21
- ? phoneNumber.format('INTERNATIONAL')
22
- : phoneNumber.number
23
- "
18
+ :value="phoneValue"
24
19
  type="tel"
25
20
  v-on="mergedListeners"
26
21
  />
@@ -32,7 +27,10 @@
32
27
  @click="_openCountriesDropdown"
33
28
  >
34
29
  <span tabindex="-1" class="aw-tel__flag">
35
- <i class="flag" :class="country.toLowerCase()"></i>
30
+ <i
31
+ class="flag"
32
+ :class="(country || '').toLowerCase()"
33
+ ></i>
36
34
  </span>
37
35
  </button>
38
36
  </template>
@@ -41,15 +39,13 @@
41
39
  v-show="!!value"
42
40
  class="aw-tel__validity"
43
41
  :class="
44
- phoneNumber.isValid()
42
+ phoneNumber.valid
45
43
  ? 'aw-tel__validity--valid'
46
44
  : 'aw-tel__validity--invalid'
47
45
  "
48
46
  >
49
47
  <AwIconSystemMono
50
- :name="
51
- phoneNumber.isValid() ? 'check-light' : 'ban-light'
52
- "
48
+ :name="phoneNumber.valid ? 'check-light' : 'ban-light'"
53
49
  />
54
50
  </span>
55
51
  </template>
@@ -58,21 +54,24 @@
58
54
  ref="dropdown"
59
55
  class="aw-tel__dropdown"
60
56
  :show.sync="showCountries"
57
+ :close-on-action="false"
61
58
  @click.native="_onCountryClick"
62
59
  @keydown.native="_keyFocusItem($event)"
63
60
  >
64
- <AwDropdownButton
65
- v-for="{ name, iso } in countries"
66
- :key="iso"
67
- :active="country === iso"
68
- :data-country="iso"
69
- tabindex="-1"
70
- >
71
- <span class="aw-tel__flag mr-3">
72
- <i class="flag" :class="iso.toLowerCase()"></i>
73
- </span>
74
- {{ name }}
75
- </AwDropdownButton>
61
+ <template v-if="showCountries">
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
+ </template>
76
75
  </AwDropdown>
77
76
  </div>
78
77
  </template>
@@ -153,19 +152,20 @@ export default {
153
152
  },
154
153
 
155
154
  data() {
156
- let country = this.preselectCountry
157
- const phoneNumber = this._parsePhoneNumber(this.value, country)
158
-
159
- if (phoneNumber.isValid()) {
160
- country = phoneNumber.country
161
- }
162
-
163
- const placeholder = this._getPlaceholder(country)
155
+ const phoneNumber = this._parsePhoneNumber(
156
+ this.value,
157
+ this.preselectCountry
158
+ )
159
+ const country = phoneNumber.country
160
+ const countries = this._getCountries(country)
161
+ const phoneValue = this._getPhoneValue(phoneNumber)
164
162
 
165
163
  return {
164
+ showCountries: false,
166
165
  country,
167
- placeholder,
168
- showCountries: false
166
+ countries,
167
+ phoneNumber,
168
+ phoneValue
169
169
  }
170
170
  },
171
171
 
@@ -174,29 +174,17 @@ export default {
174
174
  return {
175
175
  ...this.$listeners,
176
176
  input: this._onInput,
177
- change: this._onInput
177
+ change: this._onInput,
178
+ paste: this._onPaste,
179
+ focus: this._onFocus,
180
+ click: this._onFocus
178
181
  }
179
182
  },
180
183
 
181
- countries() {
182
- return Array.isArray(this.filterCountries) &&
183
- this.filterCountries.length
184
- ? COUNTRIES.filter(
185
- (country) =>
186
- country.iso === this.country ||
187
- this.filterCountries.includes(country.iso)
188
- )
189
- : COUNTRIES
190
- },
191
-
192
184
  callingCode() {
193
185
  return this._getCallingCode(this.country)
194
186
  },
195
187
 
196
- phoneNumber() {
197
- return this._parsePhoneNumber(this.value, this.country)
198
- },
199
-
200
188
  _arrowFocusSelector() {
201
189
  return '[data-country]'
202
190
  },
@@ -211,22 +199,53 @@ export default {
211
199
  handler(iso, oldIso) {
212
200
  if (iso === oldIso) return
213
201
 
214
- this.placeholder = this._getPlaceholder(iso)
215
-
216
- if (oldIso) {
202
+ if (iso && oldIso) {
217
203
  const value = this.value.replace(
218
204
  '+' + this._getCallingCode(oldIso),
219
205
  '+' + this.callingCode
220
206
  )
221
207
  const phoneNumber = this._parsePhoneNumber(value, iso)
222
208
 
209
+ if (!this.phoneNumber.isEqual(phoneNumber)) {
210
+ this.phoneNumber = phoneNumber
211
+ this.phoneValue = this._getPhoneValue(phoneNumber)
212
+
213
+ this._emitInput(phoneNumber)
214
+ }
215
+ } else {
216
+ const phoneNumber = this._parsePhoneNumber(this.value, iso)
217
+
218
+ this.phoneNumber = phoneNumber
219
+ this.phoneValue = this._getPhoneValue(phoneNumber)
220
+
223
221
  this._emitInput(phoneNumber)
224
222
  }
225
223
 
226
- if (this.$refs.element) {
224
+ this.countries = this._getCountries(iso)
225
+
226
+ if (
227
+ this.$refs.element &&
228
+ this.$el.contains(document.activeElement)
229
+ ) {
227
230
  this.$refs.element.focus()
231
+ this.$nextTick(() => {
232
+ this._setFocusToFirstDash(this.$refs.element)
233
+ })
228
234
  }
229
235
  }
236
+ },
237
+
238
+ value(value) {
239
+ const phoneNumber = this._parsePhoneNumber(value, this.country)
240
+
241
+ if (!this.phoneNumber.isEqual(phoneNumber)) {
242
+ this.phoneNumber = phoneNumber
243
+ this.phoneValue = this._getPhoneValue(phoneNumber)
244
+ }
245
+
246
+ if (phoneNumber.country && phoneNumber.country !== this.country) {
247
+ this.country = phoneNumber.country
248
+ }
230
249
  }
231
250
  },
232
251
 
@@ -234,20 +253,100 @@ export default {
234
253
  _onInput($event) {
235
254
  const value = $event.target.value
236
255
  const phoneNumber = this._parsePhoneNumber(value, this.country)
237
-
238
- if (this.phoneNumber.isEqual(phoneNumber)) {
256
+ const phoneValue = this._getPhoneValue(phoneNumber)
257
+
258
+ // open dropdown on backspace
259
+ if (
260
+ phoneNumber.number === '' &&
261
+ phoneValue === this.phoneValue &&
262
+ $event.data === null
263
+ ) {
264
+ this.$nextTick(this._openCountriesDropdown)
239
265
  return
240
266
  }
241
267
 
268
+ // save caret position
269
+ let pos = $event.target.selectionStart
270
+ let digit = pos > 0 ? value[pos - 1] : '_'
271
+ let adjustPosition = false
272
+
273
+ if (phoneValue !== this.phoneValue) {
274
+ adjustPosition = true
275
+
276
+ this.phoneNumber = phoneNumber
277
+ this.phoneValue = phoneValue
278
+
279
+ this._emitInput(phoneNumber)
280
+ }
281
+
282
+ if ($event.target.value !== this.phoneValue) {
283
+ adjustPosition = true
284
+
285
+ $event.target.value = this.phoneValue
286
+ }
287
+
288
+ if (phoneNumber.country && phoneNumber.country !== this.country) {
289
+ this.country = phoneNumber.country
290
+ }
291
+
242
292
  if (this.$refs.input.hasError) {
243
293
  this.$refs.input.setError('')
244
294
  }
245
295
 
246
- if (phoneNumber.isValid() && phoneNumber.country !== this.country) {
296
+ if ($event.data !== null && this.showCountries) {
297
+ this.showCountries = false
298
+ }
299
+
300
+ if (adjustPosition) {
301
+ this.$nextTick(() => {
302
+ const newValue = $event.target.value
303
+ const firstDashIndex = newValue.indexOf('_')
304
+ const max =
305
+ firstDashIndex > -1 ? firstDashIndex : newValue.length
306
+
307
+ pos = Math.min(pos, max)
308
+
309
+ while (
310
+ newValue[pos - 1] !== digit &&
311
+ pos < firstDashIndex
312
+ ) {
313
+ pos++
314
+ }
315
+
316
+ $event.target.setSelectionRange(pos, pos)
317
+ })
318
+ }
319
+ },
320
+
321
+ _onPaste($event) {
322
+ $event.preventDefault()
323
+
324
+ // replace all Data
325
+ const phone = $event.clipboardData.getData('text')
326
+
327
+ const phoneNumber = this._parsePhoneNumber(phone, this.country)
328
+
329
+ if (!this.phoneNumber.isEqual(phoneNumber)) {
330
+ this.phoneNumber = phoneNumber
331
+ this.phoneValue = this._getPhoneValue(phoneNumber)
332
+
333
+ this._emitInput(phoneNumber)
334
+ }
335
+
336
+ if (phoneNumber.country && phoneNumber.country !== this.country) {
247
337
  this.country = phoneNumber.country
248
338
  }
249
339
 
250
- this._emitInput(phoneNumber)
340
+ $event.target.focus()
341
+
342
+ this.$nextTick(() => {
343
+ this._onFocus({ target: $event.target })
344
+ })
345
+ },
346
+
347
+ _onFocus($event) {
348
+ this._setFocusToFirstDash($event.target)
349
+ this.showCountries = false
251
350
  },
252
351
 
253
352
  _onBlur($event) {
@@ -256,17 +355,22 @@ export default {
256
355
  }
257
356
  },
258
357
 
259
- _getPlaceholder(iso) {
358
+ _getPlaceholder(iso, withCode = false) {
260
359
  const phoneNumber = getExampleNumber(iso, examples)
261
360
 
262
361
  if (phoneNumber) {
263
362
  const number = phoneNumber.format('INTERNATIONAL')
264
- const code = '+' + phoneNumber.countryCallingCode
265
363
 
266
- return number
267
- .replace(code, '{code}')
268
- .replace(/\d/g, '_')
269
- .replace('{code}', code)
364
+ if (withCode) {
365
+ const code = '+' + phoneNumber.countryCallingCode
366
+
367
+ return number
368
+ .replace(code, '{code}')
369
+ .replace(/\d/g, '_')
370
+ .replace('{code}', code)
371
+ } else {
372
+ return number.replace(/\d/g, '_')
373
+ }
270
374
  } else {
271
375
  return ''
272
376
  }
@@ -279,20 +383,45 @@ export default {
279
383
  this.$el
280
384
  )
281
385
  this.showCountries = false
386
+
387
+ this.$refs.element.focus()
388
+ this.$nextTick(() => this._setFocusToFirstDash(this.$refs.element))
282
389
  },
283
390
 
284
- _parsePhoneNumber(value, country) {
285
- const number = typeof value === 'string' ? value : ''
286
- const phoneNumber = parsePhoneNumberFromString(number, country)
391
+ _parsePhoneNumber(value = '', country) {
392
+ let number = typeof value === 'string' ? value : ''
393
+
394
+ number = number.replace(/\D/g, '')
395
+
396
+ number = number === this.callingCode ? '' : number
397
+
398
+ number = number && value.startsWith('+') ? '+' + number : number
399
+
400
+ let phoneNumber = parsePhoneNumberFromString(number, country)
287
401
 
288
402
  if (phoneNumber) {
289
- return phoneNumber
403
+ return {
404
+ number: phoneNumber.number,
405
+ country: phoneNumber.country,
406
+ countryCallingCode: phoneNumber.countryCallingCode,
407
+ valid: phoneNumber.isValid(),
408
+ format() {
409
+ return phoneNumber.format.apply(phoneNumber, arguments)
410
+ },
411
+ isValid() {
412
+ return phoneNumber.isValid.apply(phoneNumber, arguments)
413
+ },
414
+ isEqual() {
415
+ return phoneNumber.isEqual.apply(phoneNumber, arguments)
416
+ }
417
+ }
290
418
  } else {
291
419
  // mock PhoneNumber class
292
420
  return {
293
421
  number,
294
422
  country,
295
423
  countryCallingCode: this.callingCode,
424
+ valid: false,
296
425
  format() {
297
426
  return number
298
427
  },
@@ -306,6 +435,30 @@ export default {
306
435
  }
307
436
  },
308
437
 
438
+ _getPhoneValue(phoneNumber) {
439
+ if (phoneNumber.number === '') {
440
+ return this._getPlaceholder(phoneNumber.country, true)
441
+ }
442
+
443
+ const placeholder = this._getPlaceholder(phoneNumber.country)
444
+
445
+ if (placeholder) {
446
+ const digits = phoneNumber.number.replace(/\D/g, '').split('')
447
+ const replaced = placeholder.replace(
448
+ /_/g,
449
+ () => digits.shift() || '_'
450
+ )
451
+
452
+ return digits.length
453
+ ? replaced + digits.join('')
454
+ : phoneNumber.valid
455
+ ? replaced.replace(/_/g, '')
456
+ : replaced
457
+ } else {
458
+ return phoneNumber.number
459
+ }
460
+ },
461
+
309
462
  _getCallingCode(iso) {
310
463
  return pathOr(
311
464
  '',
@@ -314,11 +467,22 @@ export default {
314
467
  )
315
468
  },
316
469
 
470
+ _getCountries(includeIso) {
471
+ return Array.isArray(this.filterCountries) &&
472
+ this.filterCountries.length
473
+ ? COUNTRIES.filter(
474
+ (country) =>
475
+ country.iso === includeIso ||
476
+ this.filterCountries.includes(country.iso)
477
+ )
478
+ : COUNTRIES
479
+ },
480
+
317
481
  _emitInput(phoneNumber) {
318
482
  if (phoneNumber.number === this.value) return
319
483
 
320
484
  this.$emit('input', phoneNumber.number, {
321
- isValid: phoneNumber.isValid(),
485
+ isValid: phoneNumber.valid,
322
486
  country: phoneNumber.country,
323
487
  number: {
324
488
  national: phoneNumber.format('NATIONAL'),
@@ -341,6 +505,15 @@ export default {
341
505
  el.focus()
342
506
  }
343
507
  })
508
+ },
509
+
510
+ _setFocusToFirstDash(inputEl) {
511
+ const firstDashIndex = inputEl.value.indexOf('_')
512
+ const pos = inputEl.selectionStart
513
+
514
+ if (firstDashIndex > -1 && pos > firstDashIndex) {
515
+ inputEl.setSelectionRange(firstDashIndex, firstDashIndex)
516
+ }
344
517
  }
345
518
  }
346
519
  }
@@ -15,7 +15,14 @@
15
15
  class="aw-noty__icon"
16
16
  :class="`text-${props.type}`"
17
17
  />
18
- <svg
18
+ <Component
19
+ :is="$options.components.AwIconSystemMono"
20
+ v-else-if="props.icon === true"
21
+ :name="$options.getDefaultIconName(props.type)"
22
+ class="aw-noty__icon"
23
+ :class="`text-${props.type}`"
24
+ />
25
+ <!-- <svg
19
26
  v-else-if="props.icon"
20
27
  width="20"
21
28
  height="20"
@@ -25,7 +32,7 @@
25
32
  <path
26
33
  d="M10 1.8a8.2 8.2 0 100 16.4 8.2 8.2 0 000-16.4zm4.1 6.5l-4.4 4.4a.7.7 0 01-1 0l-2.2-2.2a.7.7 0 111-1l1.7 1.8 4-4a.7.7 0 111 1zm0 0"
27
34
  />
28
- </svg>
35
+ </svg> -->
29
36
 
30
37
  <!-- content -->
31
38
  <div class="aw-noty__content" :class="{ 'has-text': props.text }">
@@ -100,6 +107,17 @@ export default {
100
107
  }
101
108
  },
102
109
 
103
- sanitize
110
+ sanitize,
111
+
112
+ getDefaultIconName(type) {
113
+ switch (type) {
114
+ case 'error':
115
+ return 'close-solid'
116
+ case 'warning':
117
+ return 'attention'
118
+ default:
119
+ return 'check-solid'
120
+ }
121
+ }
104
122
  }
105
123
  </script>
@@ -13,6 +13,12 @@ export const AwAvatar = {
13
13
  colors: COLORS
14
14
  }
15
15
 
16
+ export const AwBirthdayPicker = {
17
+ fullParseFormat: 'DD.MM.YYYY',
18
+ shortParseFormat: 'DD.MM',
19
+ fieldsOrder: 'DD.MM.YYYY'
20
+ }
21
+
16
22
  export const AwButton = {
17
23
  routerComponent: 'router-link',
18
24
  size: 'md',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awes-io/ui",
3
- "version": "2.44.0",
3
+ "version": "2.47.0",
4
4
  "description": "User Interface (UI) components",
5
5
  "keywords": [
6
6
  "ui",
@@ -124,5 +124,5 @@
124
124
  "vue-template-compiler": "^2.6.10",
125
125
  "webfonts-generator": "^0.4.0"
126
126
  },
127
- "gitHead": "fce6417d348bb3e187df9c4ea498562055ee5c31"
127
+ "gitHead": "fe6bdb700f0209121131bf1e68c0f9251c793b9a"
128
128
  }
package/store/awesIo.js CHANGED
@@ -19,6 +19,7 @@ export const state = () => ({
19
19
  screen: {},
20
20
 
21
21
  profileUrl: null,
22
+ userName: '',
22
23
 
23
24
  logo: {
24
25
  default: { src: '' },
@@ -55,12 +56,14 @@ export const getters = {
55
56
  user(state, getters, rootState) {
56
57
  const user = pathOr({}, 'auth.user', rootState)
57
58
  const src = pathOr('', 'avatar', user)
58
- const name = [
59
- pathOr('', 'profile.first_name', user),
60
- pathOr('', 'profile.last_name', user)
61
- ]
62
- .filter(Boolean)
63
- .join(' ')
59
+ const name =
60
+ state.userName ||
61
+ [
62
+ pathOr('', 'profile.first_name', user),
63
+ pathOr('', 'profile.last_name', user)
64
+ ]
65
+ .filter(Boolean)
66
+ .join(' ')
64
67
  const description = pathOr(
65
68
  pathOr('', 'profile.description', user),
66
69
  'profile.position',