Skip to content

Commit f9ed225

Browse files
committed
Add Glint support to the AuTimePicker component
1 parent ae167c0 commit f9ed225

File tree

4 files changed

+157
-75
lines changed

4 files changed

+157
-75
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,92 @@
1-
import { AuButton, AuInput, AuLabel } from '@appuniversum/ember-appuniversum';
21
import { fn } from '@ember/helper';
32
import { on } from '@ember/modifier';
43
import { action } from '@ember/object';
54
import { isPresent } from '@ember/utils';
65
import Component from '@glimmer/component';
76
import { trackedReset } from 'tracked-toolbox';
7+
import AuButton from './au-button';
8+
import AuInput from './au-input';
9+
import type { AuInputSignature } from './au-input';
10+
import AuLabel from './au-label';
811

9-
export default class AuTimePicker extends Component {
10-
@trackedReset({
12+
export interface AuTimePickerSignature {
13+
Args: {
14+
hours?: number;
15+
hoursLabel?: string;
16+
minutes?: number;
17+
minutesLabel?: string;
18+
nowLabel?: string;
19+
onChange?: (data: TimeData) => void;
20+
seconds?: number;
21+
secondsLabel?: string;
22+
showNow?: boolean;
23+
showSeconds?: boolean;
24+
};
25+
}
26+
27+
type TimeData = {
28+
hours: number;
29+
minutes: number;
30+
seconds: number;
31+
};
32+
33+
type TimeProperty = 'hours' | 'minutes' | 'seconds';
34+
35+
export default class AuTimePicker extends Component<AuTimePickerSignature> {
36+
@trackedReset<AuTimePicker, number>({
1137
memo: 'args.hours',
12-
update() {
13-
if (this.args.hours || this.args.hours == 0) {
14-
return this.validateTimeValue(this.args.hours, 'hourValue');
38+
update(component: AuTimePicker) {
39+
const { hours } = component.args;
40+
if (hours || hours == 0) {
41+
return component.normalizeTimeValue(hours, 'hours');
1542
} else {
1643
return 12;
1744
}
1845
},
1946
})
20-
hourValue = 12;
47+
hours = 12;
2148

22-
@trackedReset({
49+
@trackedReset<AuTimePicker, number>({
2350
memo: 'args.minutes',
24-
update() {
25-
return this.validateTimeValue(this.args.minutes, 'minuteValue');
51+
update(component: AuTimePicker) {
52+
let { minutes } = component.args;
53+
54+
if (typeof minutes !== 'number' && typeof minutes !== 'string') {
55+
return 0;
56+
}
57+
58+
if (typeof minutes === 'string') {
59+
minutes = parseInt(minutes, 10);
60+
}
61+
62+
return component.normalizeTimeValue(minutes, 'minutes');
2663
},
2764
})
28-
minuteValue = 0;
65+
minutes = 0;
2966

30-
@trackedReset({
67+
@trackedReset<AuTimePicker, number>({
3168
memo: 'args.seconds',
32-
update() {
33-
return this.validateTimeValue(this.args.seconds, 'secondValue');
69+
update(component: AuTimePicker) {
70+
let { seconds } = component.args;
71+
72+
if (typeof seconds !== 'number' && typeof seconds !== 'string') {
73+
return 0;
74+
}
75+
76+
if (typeof seconds === 'string') {
77+
seconds = parseInt(seconds, 10);
78+
}
79+
80+
return component.normalizeTimeValue(seconds, 'seconds');
3481
},
3582
})
36-
secondValue = 0;
37-
38-
get hourValueFormatted() {
39-
return this.formatTimeNumber(this.hourValue);
40-
}
41-
get minuteValueFormatted() {
42-
return this.formatTimeNumber(this.minuteValue);
43-
}
44-
get secondValueFormatted() {
45-
return this.formatTimeNumber(this.secondValue);
46-
}
83+
seconds = 0;
4784

48-
get getTimeObject() {
85+
get timeData(): TimeData {
4986
return {
50-
hours: this.hourValue,
51-
minutes: this.minuteValue,
52-
seconds: this.secondValue,
87+
hours: this.hours,
88+
minutes: this.minutes,
89+
seconds: this.seconds,
5390
};
5491
}
5592

@@ -62,19 +99,25 @@ export default class AuTimePicker extends Component {
6299
}
63100

64101
@action
65-
increment(elem) {
66-
this[elem] = this.validateTimeValue(this[elem] + 1, elem);
67-
this.callBackParent(this.getTimeObject);
102+
increment(propertyName: TimeProperty) {
103+
this[propertyName] = this.normalizeTimeValue(
104+
this[propertyName] + 1,
105+
propertyName,
106+
);
107+
this.onChange(this.timeData);
68108
}
69109

70110
@action
71-
decrement(elem) {
72-
this[elem] = this.validateTimeValue(this[elem] - 1, elem);
73-
this.callBackParent(this.getTimeObject);
111+
decrement(propertyName: TimeProperty) {
112+
this[propertyName] = this.normalizeTimeValue(
113+
this[propertyName] - 1,
114+
propertyName,
115+
);
116+
this.onChange(this.timeData);
74117
}
75118

76119
@action
77-
timeValueKeyPress(type, event) {
120+
timeValueKeyPress(type: TimeProperty, event: KeyboardEvent) {
78121
switch (event.key) {
79122
case 'ArrowUp':
80123
this.increment(type);
@@ -86,38 +129,37 @@ export default class AuTimePicker extends Component {
86129
}
87130

88131
@action
89-
validateTime(type, event) {
90-
this[type] = this.validateTimeValue(event.target.value, type);
91-
this.callBackParent(this.getTimeObject);
132+
validateTime(type: TimeProperty, event: Event) {
133+
const newValue = parseInt(
134+
// We can't use .valueAsNumber since we're using a type="text" input field, which always returns NaN
135+
(event.target as AuInputSignature['Element']).value,
136+
10,
137+
);
138+
this[type] = this.normalizeTimeValue(newValue, type);
139+
this.onChange(this.timeData);
92140
}
93141

94-
validateTimeValue(value, type) {
95-
let tempValue = parseInt(value, 10);
96-
if (isNaN(tempValue)) tempValue = 0;
97-
const max = type === 'hourValue' ? 23 : 59;
98-
tempValue = tempValue < 0 ? 0 : tempValue;
99-
tempValue = tempValue > max ? max : tempValue;
100-
return tempValue;
142+
normalizeTimeValue(value: number, type: TimeProperty) {
143+
let normalizedValue = !isNaN(value) ? value : 0;
144+
const max = type === 'hours' ? 23 : 59;
145+
normalizedValue = normalizedValue < 0 ? 0 : normalizedValue;
146+
normalizedValue = normalizedValue > max ? max : normalizedValue;
147+
return normalizedValue;
101148
}
102149

103-
@action
104-
callBackParent(value) {
150+
onChange(value: TimeData) {
105151
if (typeof this.args.onChange === 'function') {
106152
this.args.onChange(value);
107153
}
108154
}
109155

110156
@action
111157
setCurrentTime() {
112-
let current = new Date();
113-
this.hourValue = current.getHours();
114-
this.minuteValue = current.getMinutes();
115-
this.secondValue = current.getSeconds();
116-
this.callBackParent(this.getTimeObject);
117-
}
118-
119-
formatTimeNumber(number) {
120-
return number.toString().padStart(2, 0);
158+
const current = new Date();
159+
this.hours = current.getHours();
160+
this.minutes = current.getMinutes();
161+
this.seconds = current.getSeconds();
162+
this.onChange(this.timeData);
121163
}
122164

123165
<template>
@@ -132,10 +174,10 @@ export default class AuTimePicker extends Component {
132174
class="au-c-time-picker__input"
133175
name="input-hour"
134176
id="input-hour"
135-
value={{this.hourValueFormatted}}
177+
value={{formatTime this.hours}}
136178
data-test-autimepicker-hourinput
137-
{{on "keyup" (fn this.timeValueKeyPress "hourValue")}}
138-
{{on "input" (fn this.validateTime "hourValue")}}
179+
{{on "keyup" (fn this.timeValueKeyPress "hours")}}
180+
{{on "input" (fn this.validateTime "hours")}}
139181
/>
140182
<div class="au-c-time-picker__button-wrapper">
141183
<button
@@ -144,7 +186,7 @@ export default class AuTimePicker extends Component {
144186
aria-controls="input-hour"
145187
class="au-c-time-picker__button"
146188
data-test-autimepicker-hourincrement
147-
{{on "click" (fn this.increment "hourValue")}}
189+
{{on "click" (fn this.increment "hours")}}
148190
>
149191
+
150192
</button>
@@ -154,7 +196,7 @@ export default class AuTimePicker extends Component {
154196
aria-controls="input-hour"
155197
class="au-c-time-picker__button"
156198
data-test-autimepicker-hourdecrement
157-
{{on "click" (fn this.decrement "hourValue")}}
199+
{{on "click" (fn this.decrement "hours")}}
158200
>
159201
-
160202
</button>
@@ -174,10 +216,10 @@ export default class AuTimePicker extends Component {
174216
class="au-c-time-picker__input"
175217
name="input-minute"
176218
id="input-minute"
177-
value={{this.minuteValueFormatted}}
219+
value={{formatTime this.minutes}}
178220
data-test-autimepicker-minuteinput
179-
{{on "keyup" (fn this.timeValueKeyPress "minuteValue")}}
180-
{{on "input" (fn this.validateTime "minuteValue")}}
221+
{{on "keyup" (fn this.timeValueKeyPress "minutes")}}
222+
{{on "input" (fn this.validateTime "minutes")}}
181223
/>
182224
<div class="au-c-time-picker__button-wrapper">
183225
<button
@@ -186,7 +228,7 @@ export default class AuTimePicker extends Component {
186228
aria-controls="input-minute"
187229
class="au-c-time-picker__button"
188230
data-test-autimepicker-minuteincrement
189-
{{on "click" (fn this.increment "minuteValue")}}
231+
{{on "click" (fn this.increment "minutes")}}
190232
>
191233
+
192234
</button>
@@ -196,7 +238,7 @@ export default class AuTimePicker extends Component {
196238
aria-controls="input-minute"
197239
class="au-c-time-picker__button"
198240
data-test-autimepicker-minutedecrement
199-
{{on "click" (fn this.decrement "minuteValue")}}
241+
{{on "click" (fn this.decrement "minutes")}}
200242
>
201243
-
202244
</button>
@@ -217,10 +259,10 @@ export default class AuTimePicker extends Component {
217259
class="au-c-time-picker__input"
218260
name="input-second"
219261
id="input-second"
220-
value={{this.secondValueFormatted}}
262+
value={{formatTime this.seconds}}
221263
data-test-autimepicker-secondinput
222-
{{on "keyup" (fn this.timeValueKeyPress "secondValue")}}
223-
{{on "input" (fn this.validateTime "secondValue")}}
264+
{{on "keyup" (fn this.timeValueKeyPress "seconds")}}
265+
{{on "input" (fn this.validateTime "seconds")}}
224266
/>
225267
<div class="au-c-time-picker__button-wrapper">
226268
<button
@@ -229,7 +271,7 @@ export default class AuTimePicker extends Component {
229271
aria-controls="input-second"
230272
class="au-c-time-picker__button"
231273
data-test-autimepicker-secondincrement
232-
{{on "click" (fn this.increment "secondValue")}}
274+
{{on "click" (fn this.increment "seconds")}}
233275
>
234276
+
235277
</button>
@@ -239,7 +281,7 @@ export default class AuTimePicker extends Component {
239281
aria-controls="input-second"
240282
class="au-c-time-picker__button"
241283
data-test-autimepicker-seconddecrement
242-
{{on "click" (fn this.decrement "secondValue")}}
284+
{{on "click" (fn this.decrement "seconds")}}
243285
>
244286
-
245287
</button>
@@ -260,12 +302,16 @@ export default class AuTimePicker extends Component {
260302
</div>
261303
{{/if}}
262304

263-
<span class="au-u-hidden-visually">{{this.hourValue}}
305+
<span class="au-u-hidden-visually">{{this.hours}}
264306
{{@hoursLabel}},
265-
{{this.minuteValue}}
307+
{{this.minutes}}
266308
{{@minutesLabel}},
267-
{{this.secondValue}}
309+
{{this.seconds}}
268310
{{@secondsLabel}}.</span>
269311
</div>
270312
</template>
271313
}
314+
315+
function formatTime(number: number): string {
316+
return number.toString().padStart(2, '0');
317+
}

addon/template-registry.ts

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import type AuRadio from '@appuniversum/ember-appuniversum/components/au-radio';
4242
import type AuTable from '@appuniversum/ember-appuniversum/components/au-table';
4343
import type AuTabs from '@appuniversum/ember-appuniversum/components/au-tabs';
4444
import type AuTextarea from '@appuniversum/ember-appuniversum/components/au-textarea';
45+
import type AuTimePicker from '@appuniversum/ember-appuniversum/components/au-time-picker';
4546
import type AuToggleSwitch from '@appuniversum/ember-appuniversum/components/au-toggle-switch';
4647
import type AuToolbar from '@appuniversum/ember-appuniversum/components/au-toolbar';
4748

@@ -94,6 +95,7 @@ export default interface AppuniversumRegistry {
9495
AuTable: typeof AuTable;
9596
AuTabs: typeof AuTabs;
9697
AuTextarea: typeof AuTextarea;
98+
AuTimePicker: typeof AuTimePicker;
9799
AuToggleSwitch: typeof AuToggleSwitch;
98100
AuToolbar: typeof AuToolbar;
99101

tests/integration/components/loose-mode-test.ts

+8
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,14 @@ module('Integration | Component | Loose mode', function (hooks) {
305305
assert.dom('[data-test-textarea]').exists();
306306
});
307307

308+
test('`<AuTimePicker>` resolves in loose mode', async function (assert) {
309+
assert.expect(0);
310+
311+
await render(hbs`
312+
<AuTimePicker />
313+
`);
314+
});
315+
308316
test('`<AuToggleSwitch>` resolves in loose mode', async function (assert) {
309317
await render(hbs`
310318
<AuToggleSwitch data-test-toggle-switch />

types/tracked-toolbox/index.d.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Types based on https://github.com/tracked-tools/tracked-toolbox/pull/23/files but simplified for our use
2+
import Component from '@glimmer/component';
3+
4+
interface TrackedResetConfig<C extends Component = Component, T = unknown> {
5+
/**
6+
* A path to a "remote" property to use to indicate when to reset to the
7+
* default value
8+
*/
9+
memo: string;
10+
11+
/**
12+
* A function which can be used to provide a different value than the original
13+
* on updates.
14+
*/
15+
update: (component: C, key: string, last: T) => T;
16+
}
17+
18+
/**
19+
* Similar to @localCopy, but instead of copying the remote value, it will reset
20+
* to the class field's default value when another value changes.
21+
* @param config a configuration object with an update function, which can be
22+
* used to provide a different value than the original on updates
23+
*/
24+
export function trackedReset<C, T = unknown>(
25+
config: TrackedResetConfig<C, T>,
26+
): PropertyDecorator;

0 commit comments

Comments
 (0)