@ditojs/ui 3.2.6 → 3.3.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.
@@ -0,0 +1,264 @@
1
+ <template lang="pug">
2
+ .dito-time-picker-popup(
3
+ role="dialog"
4
+ aria-label="Choose time"
5
+ )
6
+ .dito-time-picker-panel(
7
+ v-for="column in columns"
8
+ :key="column.name"
9
+ )
10
+ ul(
11
+ :ref="element => setListElement(column.name, element)"
12
+ :class="`dito-time-picker-${column.name}`"
13
+ role="listbox"
14
+ :aria-label="column.label"
15
+ )
16
+ li(
17
+ v-for="value in column.values"
18
+ :key="value"
19
+ :ref="element => setOptionElement(column.name, value, element)"
20
+ role="option"
21
+ :aria-label="leftPad(value)"
22
+ :aria-selected="value === column.value"
23
+ :class="{ selected: value === column.value }"
24
+ @click="setTime({ [column.name]: value })"
25
+ ) {{ leftPad(value) }}
26
+ </template>
27
+
28
+ <script>
29
+ import { alterDate } from '../utils/date.js'
30
+ import { getKeyNavigation } from '../utils/event.js'
31
+ import {
32
+ getEnabledValues,
33
+ getEnabledValue,
34
+ stepEnabledValue
35
+ } from '../utils/time.js'
36
+
37
+ // The panel of `DitoTimePicker` and `DitoDateTimePicker`, with a list of the
38
+ // enabled values for each of the hour, minute and second. The picker's input
39
+ // navigates it through `handleKey()`.
40
+ export default {
41
+ emits: ['update:modelValue', 'select'],
42
+
43
+ props: {
44
+ modelValue: { type: Date, default: null },
45
+ disabledHour: { type: Function, default: () => false },
46
+ disabledMinute: { type: Function, default: () => false },
47
+ disabledSecond: { type: Function, default: () => false }
48
+ },
49
+
50
+ computed: {
51
+ currentDate() {
52
+ return (
53
+ this.modelValue ||
54
+ // Create a new Date() object with the time set to 0, to be used when
55
+ // first setting any of the times, for meaningful dates in case the
56
+ // object is shared with a calendar, e.g. in DateTimePicker.
57
+ alterDate(new Date(), { hour: 0, minute: 0, second: 0, millisecond: 0 })
58
+ )
59
+ },
60
+
61
+ // The enabled values of each column, apart from the current date, so they
62
+ // aren't collected again with each change of the time.
63
+ enabledValues() {
64
+ return {
65
+ hour: getEnabledValues(24, this.disabledHour),
66
+ minute: getEnabledValues(60, this.disabledMinute),
67
+ second: getEnabledValues(60, this.disabledSecond)
68
+ }
69
+ },
70
+
71
+ columns() {
72
+ const { currentDate, enabledValues } = this
73
+ return [
74
+ {
75
+ name: 'hour',
76
+ label: 'Hour',
77
+ value: currentDate.getHours(),
78
+ values: enabledValues.hour
79
+ },
80
+ {
81
+ name: 'minute',
82
+ label: 'Minute',
83
+ value: currentDate.getMinutes(),
84
+ values: enabledValues.minute
85
+ },
86
+ {
87
+ name: 'second',
88
+ label: 'Second',
89
+ value: currentDate.getSeconds(),
90
+ values: enabledValues.second
91
+ }
92
+ ]
93
+ }
94
+ },
95
+
96
+ watch: {
97
+ modelValue(to, from) {
98
+ if (+to !== +from) {
99
+ this.scrollToSelectedOptions({ smooth: true })
100
+ }
101
+ }
102
+ },
103
+
104
+ created() {
105
+ // The rendered lists and their options, by column name and value.
106
+ this.listElements = {}
107
+ this.optionElements = { hour: {}, minute: {}, second: {} }
108
+ },
109
+
110
+ mounted() {
111
+ this.scrollToSelectedOptions({ smooth: false })
112
+ },
113
+
114
+ methods: {
115
+ leftPad(value) {
116
+ return ('0' + value).slice(-2)
117
+ },
118
+
119
+ setListElement(name, element) {
120
+ this.listElements[name] = element
121
+ },
122
+
123
+ setOptionElement(name, value, element) {
124
+ if (element) {
125
+ this.optionElements[name][value] = element
126
+ } else {
127
+ delete this.optionElements[name][value]
128
+ }
129
+ },
130
+
131
+ // All changes of the time go through here, so disabled times are never
132
+ // set: The parts that aren't overridden move on to their next enabled
133
+ // value, so that any part can be picked first.
134
+ setTime(overrides) {
135
+ const time = { millisecond: 0 }
136
+ for (const { name, value, values } of this.columns) {
137
+ time[name] = getEnabledValue(values, overrides[name] ?? value)
138
+ if (time[name] === null) return
139
+ }
140
+ this.$emit('update:modelValue', alterDate(this.currentDate, time))
141
+ },
142
+
143
+ // Handles a keydown event of the picker's input, with the caret inside the
144
+ // date part `part`, and returns whether it was handled.
145
+ handleKey(event, part) {
146
+ const { ver: step, enter } = getKeyNavigation(event)
147
+ if (enter) {
148
+ this.$emit('select')
149
+ return true
150
+ }
151
+ const column = (
152
+ step &&
153
+ this.columns.find(({ name }) => name === part?.name)
154
+ )
155
+ if (column) {
156
+ const value = stepEnabledValue(column.values, column.value, step)
157
+ if (value !== null) {
158
+ this.setTime({ [column.name]: value })
159
+ }
160
+ return true
161
+ }
162
+ return false
163
+ },
164
+
165
+ scrollToSelectedOptions({ smooth }) {
166
+ this.$nextTick(() => {
167
+ for (const { name, value } of this.columns) {
168
+ const list = this.listElements[name]
169
+ const option = this.optionElements[name][value]
170
+ if (list && option) {
171
+ // Center the selected option in the list.
172
+ const top = Math.round(
173
+ option.offsetTop -
174
+ (list.clientHeight - option.offsetHeight) / 2
175
+ )
176
+ const distance = Math.abs(list.scrollTop - top)
177
+ list.scrollTo({
178
+ top,
179
+ behavior: smooth && distance < 100 ? 'smooth' : 'auto'
180
+ })
181
+ }
182
+ }
183
+ })
184
+ }
185
+ }
186
+ }
187
+ </script>
188
+
189
+ <style lang="scss">
190
+ @import '../styles/_imports';
191
+
192
+ $time-picker-line-height: 24px;
193
+
194
+ .dito-time-picker-popup {
195
+ max-width: 160px;
196
+ margin: $popup-margin;
197
+ list-style: none;
198
+ background: $color-white;
199
+ border: $border-style;
200
+ border-radius: $border-radius;
201
+ box-shadow: $shadow-window;
202
+ overflow: hidden;
203
+ }
204
+
205
+ .dito-time-picker-popup .dito-time-picker-panel {
206
+ float: left;
207
+ border: $border-style;
208
+ border-width: 0 1px 0;
209
+ margin-left: -1px;
210
+ box-sizing: border-box;
211
+ width: calc(100% / 3 + 1px);
212
+ overflow: hidden;
213
+
214
+ &:last-child {
215
+ border-right: 0;
216
+ }
217
+
218
+ ul {
219
+ // The offset parent of the options, see `scrollToSelectedOptions()`.
220
+ position: relative;
221
+ overflow-x: hidden;
222
+ overflow-y: auto;
223
+ list-style: none;
224
+ width: 100%;
225
+ margin: 0;
226
+ // Hide scrollbar:
227
+ box-sizing: content-box;
228
+ padding: 0 17px 0 0;
229
+ height: 7 * $time-picker-line-height;
230
+
231
+ & > li {
232
+ box-sizing: content-box;
233
+ background: $color-white;
234
+ width: 100%;
235
+ height: $time-picker-line-height;
236
+ line-height: $time-picker-line-height;
237
+ text-align: center;
238
+ font-variant-numeric: tabular-nums;
239
+ cursor: pointer;
240
+ white-space: nowrap;
241
+ overflow: hidden;
242
+ @include user-select(none);
243
+
244
+ &:first-child {
245
+ margin-top: 3 * $time-picker-line-height;
246
+ }
247
+
248
+ &:last-child {
249
+ margin-bottom: 3 * $time-picker-line-height;
250
+ }
251
+
252
+ &:hover {
253
+ background: $color-highlight;
254
+ }
255
+
256
+ &.selected,
257
+ &.selected:hover {
258
+ color: $color-text-inverted;
259
+ background: $color-active;
260
+ }
261
+ }
262
+ }
263
+ }
264
+ </style>