@awes-io/ui 2.44.0 → 2.45.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,17 @@
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.45.0](https://github.com/awes-io/client/compare/@awes-io/ui@2.44.0...@awes-io/ui@2.45.0) (2022-03-17)
7
+
8
+
9
+ ### Features
10
+
11
+ * birthday-picker updated ([4b7bedc](https://github.com/awes-io/client/commit/4b7bedc64c5358673540120fb8a7b647d8444e90))
12
+
13
+
14
+
15
+
16
+
6
17
  # [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
18
 
8
19
 
@@ -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);
@@ -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
  }
@@ -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.45.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": "892aa7ba36966acdbad40c37c3cea659ba22332c"
128
128
  }