• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pglejzer / timepicker-ui / 21936181445

12 Feb 2026 06:32AM UTC coverage: 86.02% (-4.9%) from 90.93%
21936181445

Pull #136

github

web-flow
Merge 8d0891447 into 659ec7bd0
Pull Request #136: add clear button functionality

2644 of 3312 branches covered (79.83%)

Branch coverage included in aggregate %.

21 of 175 new or added lines in 5 files covered. (12.0%)

2 existing lines in 1 file now uncovered.

3300 of 3598 relevant lines covered (91.72%)

58.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.87
/app/src/managers/ClearButtonManager.ts
1
import type { CoreState } from '../timepicker/CoreState';
2
import type { EventEmitter, TimepickerEventMap } from '../utils/EventEmitter';
5✔
3
import { announceToScreenReader } from '../utils/accessibility';
5✔
4

5
export default class ClearButtonManager {
6
  private core: CoreState;
186✔
7
  private emitter: EventEmitter<TimepickerEventMap>;
186✔
8
  private cleanupHandlers: Array<() => void> = [];
186✔
9

10
  constructor(core: CoreState, emitter: EventEmitter<TimepickerEventMap>) {
11
    this.core = core;
35!
12
    this.emitter = emitter;
35✔
NEW
13
  }
×
NEW
14

×
NEW
15
  init(): void {
×
NEW
16
    if (!this.core.options.ui.enableClearButton) return;
×
NEW
17

×
NEW
18
    const clearButton = this.getClearButton();
×
NEW
19
    if (!clearButton) return;
×
20

NEW
21
    const handler = (): void => {
×
NEW
22
      if (this.core.isDestroyed) return;
×
NEW
23
      this.handleClearClick();
×
24
    };
25

NEW
26
    clearButton.addEventListener('click', handler);
×
NEW
27
    this.cleanupHandlers.push(() => clearButton.removeEventListener('click', handler));
×
NEW
28

×
29
    this.setupInternalEventListeners();
NEW
30
  }
×
NEW
31

×
32
  private setupInternalEventListeners(): void {
NEW
33
    this.emitter.on('update', () => {
×
NEW
34
      this.updateClearButtonState();
×
35
      this.updateConfirmButtonState();
NEW
36
    });
×
NEW
37

×
38
    this.emitter.on('open', () => {
39
      this.updateClearButtonState();
40
    });
NEW
41

×
NEW
42
    this.emitter.on('select:hour', () => {
×
NEW
43
      this.updateClearButtonState();
×
NEW
44
    });
×
NEW
45

×
NEW
46
    this.emitter.on('select:minute', () => {
×
NEW
47
      this.updateClearButtonState();
×
NEW
48
    });
×
NEW
49
  }
×
50

51
  private handleClearClick(): void {
52
    const input = this.core.getInput();
53
    const previousValue = input?.value || null;
NEW
54

×
NEW
55
    this.clearTimeValue();
×
NEW
56
    this.resetClockToNeutral();
×
57
    this.disableConfirmButton();
58

59
    const modal = this.core.getModalElement();
60
    announceToScreenReader(modal, 'Time cleared');
NEW
61

×
NEW
62
    this.emitter.emit('clear', { previousValue });
×
NEW
63
    this.emitter.emit('update', {
×
64
      hour: undefined,
NEW
65
      minutes: undefined,
×
NEW
66
      type: undefined,
×
NEW
67
    });
×
NEW
68

×
69
    const { callbacks } = this.core.options;
NEW
70
    if (callbacks.onClear) {
×
NEW
71
      callbacks.onClear({ previousValue });
×
72
    }
73
  }
74

NEW
75
  private clearTimeValue(): void {
×
NEW
76
    const input = this.core.getInput();
×
NEW
77
    if (input) {
×
NEW
78
      input.value = '';
×
NEW
79
    }
×
NEW
80

×
NEW
81
    this.core.setDegreesHours(null);
×
NEW
82
    this.core.setDegreesMinutes(null);
×
NEW
83

×
84
    if (this.core.options.range?.enabled) {
NEW
85
      this.clearRangeValues();
×
NEW
86
    }
×
NEW
87

×
88
    if (this.core.options.timezone?.enabled) {
NEW
89
      this.clearTimezoneValue();
×
NEW
90
    }
×
NEW
91
  }
×
NEW
92

×
NEW
93
  private resetClockToNeutral(): void {
×
NEW
94
    const clockType = this.core.options.clock.type;
×
NEW
95
    const neutralHour = clockType === '12h' ? '12' : '12';
×
NEW
96
    const neutralMinutes = '00';
×
97
    const neutralType = clockType === '12h' ? 'PM' : undefined;
98

NEW
99
    const hourInput = this.core.getHour();
×
NEW
100
    const minuteInput = this.core.getMinutes();
×
NEW
101

×
102
    if (hourInput) {
NEW
103
      hourInput.value = neutralHour;
×
NEW
104
      hourInput.removeAttribute('aria-valuenow');
×
NEW
105
    }
×
NEW
106

×
NEW
107
    if (minuteInput) {
×
NEW
108
      minuteInput.value = neutralMinutes;
×
NEW
109
      minuteInput.removeAttribute('aria-valuenow');
×
NEW
110
    }
×
NEW
111

×
112
    const clockHand = this.core.getClockHand();
NEW
113
    if (clockHand) {
×
114
      const originalTransition = clockHand.style.transition;
115
      clockHand.style.transition = 'none';
NEW
116
      clockHand.style.transform = 'rotateZ(0deg)';
×
NEW
117

×
NEW
118
      void clockHand.offsetHeight;
×
NEW
119

×
120
      requestAnimationFrame(() => {
NEW
121
        clockHand.style.transition = originalTransition;
×
NEW
122
      });
×
NEW
123
    }
×
NEW
124

×
125
    this.removeActiveStates();
126

NEW
127
    if (hourInput) {
×
NEW
128
      hourInput.click();
×
NEW
129
    }
×
NEW
130

×
NEW
131
    if (clockType === '12h' && neutralType) {
×
132
      const amButton = this.core.getAM();
133
      const pmButton = this.core.getPM();
NEW
134

×
NEW
135
      amButton?.classList.remove('active');
×
NEW
136
      pmButton?.classList.remove('active');
×
NEW
137

×
NEW
138
      amButton?.setAttribute('aria-pressed', 'false');
×
NEW
139
      pmButton?.setAttribute('aria-pressed', 'false');
×
NEW
140

×
NEW
141
      pmButton?.classList.add('active');
×
NEW
142
      pmButton?.setAttribute('aria-pressed', 'true');
×
NEW
143
    }
×
NEW
144

×
NEW
145
    this.emitter.emit('animation:clock', {});
×
NEW
146
  }
×
147

×
148
  private removeActiveStates(): void {
×
NEW
149
    const allValueTips = this.core.getAllValueTips();
×
NEW
150
    allValueTips.forEach((tip) => {
×
NEW
151
      tip.classList.remove('active');
×
NEW
152
      tip.removeAttribute('aria-selected');
×
153
    });
154

NEW
155
    const hourInput = this.core.getHour();
×
NEW
156
    const minuteInput = this.core.getMinutes();
×
NEW
157

×
NEW
158
    hourInput?.removeAttribute('aria-valuenow');
×
NEW
159
    minuteInput?.removeAttribute('aria-valuenow');
×
NEW
160
  }
×
NEW
161

×
NEW
162
  private disableConfirmButton(): void {
×
NEW
163
    const okButton = this.core.getOkButton();
×
NEW
164
    if (!okButton) return;
×
NEW
165

×
166
    okButton.classList.add('disabled');
167
    okButton.setAttribute('aria-disabled', 'true');
168
  }
NEW
169

×
NEW
170
  private updateClearButtonState(): void {
×
NEW
171
    const clearButton = this.getClearButton();
×
NEW
172
    if (!clearButton) return;
×
NEW
173

×
NEW
174
    const input = this.core.getInput();
×
NEW
175
    const hasInputValue = input?.value && input.value.trim() !== '';
×
NEW
176

×
NEW
177
    const hourInput = this.core.getHour();
×
NEW
178
    const minuteInput = this.core.getMinutes();
×
NEW
179
    const activeTypeMode = this.core.getActiveTypeMode();
×
NEW
180

×
NEW
181
    const clockType = this.core.options.clock.type;
×
NEW
182
    const currentHour = hourInput?.value || '';
×
NEW
183
    const currentMinutes = minuteInput?.value || '';
×
NEW
184
    const currentType = activeTypeMode?.textContent || '';
×
NEW
185

×
NEW
186
    const isDefaultValue =
×
NEW
187
      clockType === '12h'
×
188
        ? currentHour === '12' && currentMinutes === '00' && currentType === 'PM'
NEW
189
        : currentHour === '12' && currentMinutes === '00';
×
190

191
    const hasChangedValue = !isDefaultValue;
192

193
    const shouldEnable = hasInputValue || hasChangedValue;
194

NEW
195
    clearButton.classList.toggle('disabled', !shouldEnable);
×
NEW
196
    clearButton.setAttribute('aria-disabled', String(!shouldEnable));
×
NEW
197
  }
×
NEW
198

×
NEW
199
  private updateConfirmButtonState(): void {
×
200
    const okButton = this.core.getOkButton();
NEW
201
    if (!okButton) return;
×
202

203
    const hourInput = this.core.getHour();
NEW
204
    const minuteInput = this.core.getMinutes();
×
NEW
205

×
206
    const hasHourValue = hourInput?.value && hourInput.value.trim() !== '';
207
    const hasMinuteValue = minuteInput?.value && minuteInput.value.trim() !== '';
NEW
208

×
NEW
209
    const hasSelection = hasHourValue && hasMinuteValue;
×
210

211
    if (hasSelection) {
NEW
212
      okButton.classList.remove('disabled');
×
NEW
213
      okButton.setAttribute('aria-disabled', 'false');
×
214
    }
215
  }
NEW
216

×
NEW
217
  private clearRangeValues(): void {
×
218
    const fromTab = this.getFromTab();
219
    const toTab = this.getToTab();
NEW
220
    const fromTime = this.getFromTimeDisplay();
×
NEW
221
    const toTime = this.getToTimeDisplay();
×
222

223
    if (fromTime) fromTime.textContent = '--:--';
NEW
224
    if (toTime) toTime.textContent = '--:--';
×
NEW
225

×
226
    fromTab?.classList.add('active');
227
    toTab?.classList.remove('active');
NEW
228

×
NEW
229
    fromTab?.setAttribute('aria-selected', 'true');
×
230
    toTab?.setAttribute('aria-selected', 'false');
231

232
    fromTab?.setAttribute('tabindex', '0');
134✔
233
    toTab?.setAttribute('tabindex', '-1');
134✔
234

235
    toTab?.classList.add('disabled');
236
    toTab?.setAttribute('aria-disabled', 'true');
5✔
237

238
    const hourInput = this.core.getHour();
239
    if (hourInput) {
240
      hourInput.focus();
241
    }
242

243
    this.emitter.emit('range:switch', {
244
      active: 'from',
245
      disabledTime: null,
246
    });
247
  }
248

249
  private clearTimezoneValue(): void {
250
    const dropdown = this.getTimezoneDropdown();
251
    const selected = this.getTimezoneSelected();
252

253
    if (selected) {
254
      const placeholder = selected.getAttribute('data-placeholder') || 'Timezone...';
255
      selected.textContent = placeholder;
256
    }
257

258
    dropdown?.setAttribute('aria-expanded', 'false');
259
  }
260

261
  private getClearButton(): HTMLButtonElement | null {
262
    const modal = this.core.getModalElement();
263
    return modal?.querySelector('.tp-ui-clear-btn') || null;
264
  }
265

266
  private getFromTab(): HTMLButtonElement | null {
267
    const modal = this.core.getModalElement();
268
    return modal?.querySelector('.tp-ui-range-from') || null;
269
  }
270

271
  private getToTab(): HTMLButtonElement | null {
272
    const modal = this.core.getModalElement();
273
    return modal?.querySelector('.tp-ui-range-to') || null;
274
  }
275

276
  private getFromTimeDisplay(): HTMLElement | null {
277
    const modal = this.core.getModalElement();
278
    return modal?.querySelector('.tp-ui-range-from-time') || null;
279
  }
280

281
  private getToTimeDisplay(): HTMLElement | null {
282
    const modal = this.core.getModalElement();
283
    return modal?.querySelector('.tp-ui-range-to-time') || null;
284
  }
285

286
  private getTimezoneDropdown(): HTMLElement | null {
287
    const modal = this.core.getModalElement();
288
    return modal?.querySelector('.tp-ui-timezone-dropdown') || null;
289
  }
290

291
  private getTimezoneSelected(): HTMLElement | null {
292
    const modal = this.core.getModalElement();
293
    return modal?.querySelector('.tp-ui-timezone-selected') || null;
294
  }
295

296
  destroy(): void {
297
    this.cleanupHandlers.forEach((cleanup) => cleanup());
298
    this.cleanupHandlers = [];
299
  }
300
}
301

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc