@ditojs/ui 2.100.0 → 3.1.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/dist/dito-ui.es.js +1157 -939
- package/dist/dito-ui.umd.js +6 -6
- package/package.json +7 -6
- package/src/components/DitoCalendar.vue +58 -4
- package/src/components/DitoTimePicker.vue +28 -4
- package/src/components/DitoTrigger.vue +11 -2
- package/src/utils/date.js +21 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dito.js UI is a simple UI library, built with Vue.js for use in Dito.js Admin and elsewhere",
|
|
6
6
|
"repository": "https://github.com/ditojs/dito/tree/main/packages/ui",
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"src/",
|
|
22
|
-
"dist/"
|
|
22
|
+
"dist/",
|
|
23
|
+
"!src/**/*.test.js",
|
|
24
|
+
"!src/**/__snapshots__/"
|
|
23
25
|
],
|
|
24
26
|
"scripts": {
|
|
25
27
|
"build:icons": "inline-assets src/icons/*.svg --template src/icons/template.js > src/styles/mixins/_icons.scss",
|
|
@@ -38,12 +40,11 @@
|
|
|
38
40
|
"ie_mob >= 11"
|
|
39
41
|
],
|
|
40
42
|
"dependencies": {
|
|
41
|
-
"@ditojs/utils": "^
|
|
42
|
-
"@soda/get-current-script": "^1.0.2"
|
|
43
|
+
"@ditojs/utils": "^3.1.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
|
-
"@ditojs/build": "^
|
|
46
|
+
"@ditojs/build": "^3.1.0",
|
|
46
47
|
"vite": "^8.3.2"
|
|
47
48
|
},
|
|
48
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "97314265c5cbac6a248d7350f43fea8ea92b995f"
|
|
49
50
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<!-- Derived from ATUI, and further extended: https://aliqin.github.io/atui/ -->
|
|
2
2
|
<template lang="pug">
|
|
3
|
-
.dito-calendar
|
|
3
|
+
.dito-calendar(
|
|
4
|
+
role="dialog"
|
|
5
|
+
aria-label="Choose date"
|
|
6
|
+
)
|
|
4
7
|
.dito-calendar-popup
|
|
5
8
|
.dito-calendar-inner
|
|
6
9
|
template(
|
|
@@ -8,22 +11,40 @@
|
|
|
8
11
|
)
|
|
9
12
|
.dito-calendar-header
|
|
10
13
|
a.dito-calendar-step-prev.dito-calendar-step-year(
|
|
14
|
+
role="button"
|
|
15
|
+
tabindex="0"
|
|
16
|
+
aria-label="Previous year"
|
|
11
17
|
@click="stepYear(-1)"
|
|
12
18
|
)
|
|
13
19
|
a.dito-calendar-step-prev.dito-calendar-step-month(
|
|
20
|
+
role="button"
|
|
21
|
+
tabindex="0"
|
|
22
|
+
aria-label="Previous month"
|
|
14
23
|
@click="stepMonth(-1)"
|
|
15
24
|
)
|
|
16
25
|
span
|
|
17
26
|
a.dito-calendar-select-year(
|
|
27
|
+
role="button"
|
|
28
|
+
tabindex="0"
|
|
29
|
+
aria-label="Switch to year selection"
|
|
18
30
|
@click="setMode('year')"
|
|
19
31
|
) {{ dateToString(currentValue, { year: true }) }}
|
|
20
32
|
a.dito-calendar-select-month(
|
|
33
|
+
role="button"
|
|
34
|
+
tabindex="0"
|
|
35
|
+
aria-label="Switch to month selection"
|
|
21
36
|
@click="setMode('month')"
|
|
22
37
|
) {{ dateToString(currentValue, { month: true }) }}
|
|
23
38
|
a.dito-calendar-step-next.dito-calendar-step-month(
|
|
39
|
+
role="button"
|
|
40
|
+
tabindex="0"
|
|
41
|
+
aria-label="Next month"
|
|
24
42
|
@click="stepMonth(1)"
|
|
25
43
|
)
|
|
26
44
|
a.dito-calendar-step-next.dito-calendar-step-year(
|
|
45
|
+
role="button"
|
|
46
|
+
tabindex="0"
|
|
47
|
+
aria-label="Next year"
|
|
27
48
|
@click="stepYear(1)"
|
|
28
49
|
)
|
|
29
50
|
.dito-calendar-body
|
|
@@ -31,15 +52,21 @@
|
|
|
31
52
|
span(
|
|
32
53
|
v-for="weekday in weekdayNames"
|
|
33
54
|
) {{ weekday.short }}
|
|
34
|
-
.dito-calendar-dates
|
|
55
|
+
.dito-calendar-dates(role="grid")
|
|
35
56
|
span(
|
|
36
57
|
v-for="date in dateRange"
|
|
37
58
|
:class="date.state && `dito-calendar-item-${date.state}`"
|
|
59
|
+
role="gridcell"
|
|
60
|
+
:aria-label="dateToString(date.date)"
|
|
61
|
+
:aria-selected="date.state === 'active'"
|
|
62
|
+
:aria-disabled="date.state === 'disabled'"
|
|
38
63
|
@click="date.state !== 'disabled' && selectDate(date.date)"
|
|
39
64
|
) {{ date.text }}
|
|
40
65
|
.dito-calendar-footer
|
|
41
66
|
a.dito-calendar-select-now(
|
|
42
67
|
role="button"
|
|
68
|
+
tabindex="0"
|
|
69
|
+
:aria-label="`Select today (${dateToString(new Date())})`"
|
|
43
70
|
:title="dateToString(new Date())"
|
|
44
71
|
@click="selectDate(new Date())"
|
|
45
72
|
)
|
|
@@ -48,20 +75,35 @@
|
|
|
48
75
|
)
|
|
49
76
|
.dito-calendar-header
|
|
50
77
|
a.dito-calendar-step-prev(
|
|
78
|
+
role="button"
|
|
79
|
+
tabindex="0"
|
|
80
|
+
aria-label="Previous year"
|
|
51
81
|
@click="stepYear(-1)"
|
|
52
82
|
)
|
|
53
83
|
span
|
|
54
84
|
a.dito-calendar-select-year(
|
|
85
|
+
role="button"
|
|
86
|
+
tabindex="0"
|
|
87
|
+
aria-label="Switch to year selection"
|
|
55
88
|
@click="setMode('year')"
|
|
56
89
|
) {{ dateToString(currentValue, { year: true }) }}
|
|
57
90
|
a.dito-calendar-step-next(
|
|
91
|
+
role="button"
|
|
92
|
+
tabindex="0"
|
|
93
|
+
aria-label="Next year"
|
|
58
94
|
@click="stepYear(1)"
|
|
59
95
|
)
|
|
60
96
|
.dito-calendar-body
|
|
61
|
-
.dito-calendar-months
|
|
97
|
+
.dito-calendar-months(
|
|
98
|
+
role="listbox"
|
|
99
|
+
aria-label="Month"
|
|
100
|
+
)
|
|
62
101
|
span(
|
|
63
102
|
v-for="(month, index) in monthNames"
|
|
64
103
|
:class="getMonthClass(index)"
|
|
104
|
+
role="option"
|
|
105
|
+
:aria-label="month.long"
|
|
106
|
+
:aria-selected="getMonthClass(index)['dito-calendar-item-active']"
|
|
65
107
|
@click="selectMonth(index)"
|
|
66
108
|
) {{ month.short }}
|
|
67
109
|
template(
|
|
@@ -69,17 +111,29 @@
|
|
|
69
111
|
)
|
|
70
112
|
.dito-calendar-header
|
|
71
113
|
a.dito-calendar-step-prev(
|
|
114
|
+
role="button"
|
|
115
|
+
tabindex="0"
|
|
116
|
+
aria-label="Previous decade"
|
|
72
117
|
@click="stepDecade(-1)"
|
|
73
118
|
)
|
|
74
119
|
span {{ decadeToString(currentValue) }}
|
|
75
120
|
a.dito-calendar-step-next(
|
|
121
|
+
role="button"
|
|
122
|
+
tabindex="0"
|
|
123
|
+
aria-label="Next decade"
|
|
76
124
|
@click="stepDecade(1)"
|
|
77
125
|
)
|
|
78
126
|
.dito-calendar-body
|
|
79
|
-
.dito-calendar-years
|
|
127
|
+
.dito-calendar-years(
|
|
128
|
+
role="listbox"
|
|
129
|
+
aria-label="Year"
|
|
130
|
+
)
|
|
80
131
|
span(
|
|
81
132
|
v-for="year in yearRange"
|
|
82
133
|
:class="getYearClass(year)"
|
|
134
|
+
role="option"
|
|
135
|
+
:aria-label="String(year)"
|
|
136
|
+
:aria-selected="getYearClass(year)['dito-calendar-item-active']"
|
|
83
137
|
@click="selectYear(year)"
|
|
84
138
|
) {{ year }}
|
|
85
139
|
</template>
|
|
@@ -32,34 +32,58 @@ DitoTrigger.dito-time-picker(
|
|
|
32
32
|
)
|
|
33
33
|
slot(name="suffix")
|
|
34
34
|
template(#popup)
|
|
35
|
-
.dito-time-picker-popup
|
|
35
|
+
.dito-time-picker-popup(
|
|
36
|
+
role="dialog"
|
|
37
|
+
aria-label="Choose time"
|
|
38
|
+
)
|
|
36
39
|
.dito-time-picker-panel
|
|
37
|
-
ul.dito-time-picker-hour(
|
|
40
|
+
ul.dito-time-picker-hour(
|
|
41
|
+
ref="hour"
|
|
42
|
+
role="listbox"
|
|
43
|
+
aria-label="Hour"
|
|
44
|
+
)
|
|
38
45
|
template(
|
|
39
46
|
v-for="index in 24"
|
|
40
47
|
)
|
|
41
48
|
li(
|
|
42
49
|
v-if="!disabledHour(index - 1)"
|
|
50
|
+
role="option"
|
|
51
|
+
:aria-label="leftPad(index - 1)"
|
|
52
|
+
:aria-selected="hour === index - 1"
|
|
43
53
|
:class="{ selected: hour === index - 1 }"
|
|
44
54
|
@click="hour = index - 1"
|
|
45
55
|
) {{ leftPad(index - 1) }}
|
|
46
56
|
.dito-time-picker-panel
|
|
47
|
-
ul.dito-time-picker-minute(
|
|
57
|
+
ul.dito-time-picker-minute(
|
|
58
|
+
ref="minute"
|
|
59
|
+
role="listbox"
|
|
60
|
+
aria-label="Minute"
|
|
61
|
+
)
|
|
48
62
|
template(
|
|
49
63
|
v-for="index in 60"
|
|
50
64
|
)
|
|
51
65
|
li(
|
|
52
66
|
v-if="!disabledMinute(index - 1)"
|
|
67
|
+
role="option"
|
|
68
|
+
:aria-label="leftPad(index - 1)"
|
|
69
|
+
:aria-selected="minute === index - 1"
|
|
53
70
|
:class="{ selected: minute === index - 1 }"
|
|
54
71
|
@click="minute = index - 1"
|
|
55
72
|
) {{ leftPad(index - 1) }}
|
|
56
73
|
.dito-time-picker-panel
|
|
57
|
-
ul.dito-time-picker-second(
|
|
74
|
+
ul.dito-time-picker-second(
|
|
75
|
+
ref="second"
|
|
76
|
+
role="listbox"
|
|
77
|
+
aria-label="Second"
|
|
78
|
+
)
|
|
58
79
|
template(
|
|
59
80
|
v-for="index in 60"
|
|
60
81
|
)
|
|
61
82
|
li(
|
|
62
83
|
v-if="!disabledSecond(index - 1)"
|
|
84
|
+
role="option"
|
|
85
|
+
:aria-label="leftPad(index - 1)"
|
|
86
|
+
:aria-selected="second === index - 1"
|
|
63
87
|
:class="{ selected: second === index - 1 }"
|
|
64
88
|
@click="second = index - 1"
|
|
65
89
|
) {{ leftPad(index - 1) }}
|
|
@@ -174,6 +174,10 @@ export default {
|
|
|
174
174
|
},
|
|
175
175
|
|
|
176
176
|
methods: {
|
|
177
|
+
isPopupFocused() {
|
|
178
|
+
return !!this.$refs.popup?.matches(':focus-within')
|
|
179
|
+
},
|
|
180
|
+
|
|
177
181
|
updatePosition() {
|
|
178
182
|
const { trigger, popup } = this.$refs
|
|
179
183
|
if (this.show && popup.offsetWidth === 0) {
|
|
@@ -291,7 +295,7 @@ export default {
|
|
|
291
295
|
blur: () => {
|
|
292
296
|
// Use timeout to allow clicked inputs to grab focus
|
|
293
297
|
this.blurTimer = setTimeout(() => {
|
|
294
|
-
if (!this
|
|
298
|
+
if (!this.isPopupFocused()) {
|
|
295
299
|
this.showPopup = false
|
|
296
300
|
}
|
|
297
301
|
}, 0)
|
|
@@ -323,7 +327,12 @@ export default {
|
|
|
323
327
|
// becomes editable and still focused again.
|
|
324
328
|
setTimeout(() => {
|
|
325
329
|
input.removeAttribute('readonly')
|
|
326
|
-
|
|
330
|
+
// Only bring the focus back from the popup, not if the focus
|
|
331
|
+
// moved elsewhere in the meantime, e.g. by clicking outside.
|
|
332
|
+
if (
|
|
333
|
+
!event.target.matches('input, textarea, button') &&
|
|
334
|
+
this.isPopupFocused()
|
|
335
|
+
) {
|
|
327
336
|
input.focus()
|
|
328
337
|
}
|
|
329
338
|
input = null
|
package/src/utils/date.js
CHANGED
|
@@ -20,6 +20,11 @@ export function parseDate(string, {
|
|
|
20
20
|
time = true
|
|
21
21
|
} = {}) {
|
|
22
22
|
const timeDefault = time ? null : 0
|
|
23
|
+
// Extract the 12-hour clock period, e.g. in en-US "11:30:00 AM".
|
|
24
|
+
const period = string?.match(/\s*\b([ap])\.?m\.?$/i)
|
|
25
|
+
if (period) {
|
|
26
|
+
string = string.slice(0, period.index)
|
|
27
|
+
}
|
|
23
28
|
let [
|
|
24
29
|
day,
|
|
25
30
|
sep1,
|
|
@@ -36,10 +41,19 @@ export function parseDate(string, {
|
|
|
36
41
|
millisecond = timeDefault
|
|
37
42
|
] = getDateParts(string, { date, time })
|
|
38
43
|
if (year && year.length >= 4) {
|
|
39
|
-
if (
|
|
44
|
+
if (
|
|
40
45
|
// American format: MM/DD/YYYY
|
|
46
|
+
(locale === 'en-US' && sep1 === '/' && sep2 === '/') ||
|
|
47
|
+
// Month name first, e.g. en-US "May 14, 2026"
|
|
48
|
+
(isNaN(+day) && !isNaN(+month))
|
|
49
|
+
) {
|
|
41
50
|
;[day, month] = [month, day]
|
|
42
51
|
}
|
|
52
|
+
if (period && hour !== null) {
|
|
53
|
+
const pm = period[1].toLowerCase() === 'p'
|
|
54
|
+
hour = (+hour % 12) + (pm ? 12 : 0)
|
|
55
|
+
hour = String(hour).padStart(2, '0')
|
|
56
|
+
}
|
|
43
57
|
month = getMonthIndex(month, { locale })
|
|
44
58
|
const isValidTimePart = part => part === null || part.length === 2
|
|
45
59
|
if (
|
|
@@ -51,7 +65,7 @@ export function parseDate(string, {
|
|
|
51
65
|
)
|
|
52
66
|
)
|
|
53
67
|
) {
|
|
54
|
-
|
|
68
|
+
const result = new Date(
|
|
55
69
|
+year,
|
|
56
70
|
+month,
|
|
57
71
|
+day,
|
|
@@ -60,6 +74,11 @@ export function parseDate(string, {
|
|
|
60
74
|
+second,
|
|
61
75
|
+millisecond
|
|
62
76
|
)
|
|
77
|
+
// Reject invalid dates, including out-of-range parts that `Date` would
|
|
78
|
+
// roll over, e.g. day 45.
|
|
79
|
+
return result.getMonth() === +month && result.getDate() === +day
|
|
80
|
+
? result
|
|
81
|
+
: null
|
|
63
82
|
}
|
|
64
83
|
}
|
|
65
84
|
return null
|