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

pglejzer / timepicker-ui / 23090065294

14 Mar 2026 02:41PM UTC coverage: 85.553% (-5.8%) from 91.398%
23090065294

Pull #156

github

web-flow
Merge 13391106a into b6b399a9b
Pull Request #156: update to 4.2.0

2958 of 3719 branches covered (79.54%)

Branch coverage included in aggregate %.

589 of 839 new or added lines in 20 files covered. (70.2%)

21 existing lines in 2 files now uncovered.

3929 of 4331 relevant lines covered (90.72%)

68.7 hits per line

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

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

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

11
  constructor(core: CoreState, emitter: EventEmitter<TimepickerEventMap>) {
12
    this.core = core;
79✔
13
    this.emitter = emitter;
1✔
14
  }
78✔
15

78!
NEW
16
  init(): void {
×
17
    if (!this.core.options.ui.clearButton) return;
78✔
18

35✔
19
    const clearButton = this.getClearButton();
1✔
20
    if (!clearButton) return;
34✔
21

4✔
22
    const handler = (): void => {
30✔
23
      if (this.core.isDestroyed) return;
24
      if (clearButton.getAttribute('aria-disabled') === 'true') return;
78✔
25
      this.handleClearClick();
78✔
26
    };
78✔
27

28
    clearButton.addEventListener('click', handler);
29
    this.cleanupHandlers.push(() => clearButton.removeEventListener('click', handler));
78✔
30

41✔
31
    this.setupInternalEventListeners();
32
  }
78✔
33

32✔
34
  private setupInternalEventListeners(): void {
35
    this.emitter.on('update', () => {
78✔
36
      this.updateClearButtonState();
6✔
37
    });
6✔
38

39
    this.emitter.on('open', () => {
78✔
40
      this.updateClearButtonState();
3✔
41
    });
3✔
42

43
    this.emitter.on('select:hour', () => {
44
      this.updateClearButtonState();
45
      this.reenableConfirmIfCleared();
30✔
46
    });
30!
47

30✔
48
    this.emitter.on('select:minute', () => {
30✔
49
      this.updateClearButtonState();
30✔
50
      this.reenableConfirmIfCleared();
30✔
51
    });
30✔
52
  }
30✔
53

30✔
54
  private handleClearClick(): void {
30✔
55
    const input = this.core.getInput();
56
    const previousValue = input?.value || null;
57

58
    this.clearTimeValue();
59
    this.resetClockToNeutral();
30✔
60
    this.disableConfirmButton();
30✔
61
    this.wasCleared = true;
6✔
62

63
    const modal = this.core.getModalElement();
64
    announceToScreenReader(modal, 'Time cleared');
65

66
    this.emitter.emit('clear', { previousValue });
30!
67
    this.emitter.emit('update', {
30✔
68
      hour: undefined,
30✔
69
      minutes: undefined,
23✔
70
      type: undefined,
71
    });
30✔
72

30✔
73
    const { callbacks } = this.core.options;
30!
NEW
74
    if (callbacks.onClear) {
×
75
      callbacks.onClear({ previousValue });
76
    }
30!
NEW
77
  }
×
78

79
  private clearTimeValue(): void {
80
    const shouldClearInput = this.core.options.clearBehavior?.clearInput !== false;
81
    const input = this.core.getInput();
30✔
82
    if (input && shouldClearInput) {
30✔
83
      input.value = '';
30✔
84
    }
30✔
85

30✔
86
    this.core.setDegreesHours(null);
30✔
87
    this.core.setDegreesMinutes(null);
30!
88

30✔
89
    if (this.core.options.range?.enabled) {
30✔
90
      this.clearRangeValues();
91
    }
30!
92

30✔
93
    if (this.core.options.timezone?.enabled) {
30✔
94
      this.clearTimezoneValue();
95
    }
30✔
96
  }
30!
97

30✔
98
  private resetClockToNeutral(): void {
30✔
99
    const clockType = this.core.options.clock.type;
30✔
100
    const neutralHour = '12';
30✔
101
    const neutralMinutes = '00';
30✔
102
    const neutralType = clockType === '12h' ? 'PM' : undefined;
1✔
103

104
    const hourInput = this.core.getHour();
105
    const minuteInput = this.core.getMinutes();
30✔
106

30!
107
    if (hourInput) {
30✔
108
      hourInput.value = neutralHour;
109
      hourInput.removeAttribute('aria-valuenow');
30✔
110
    }
27✔
111

27✔
112
    if (minuteInput) {
27!
113
      minuteInput.value = neutralMinutes;
27!
114
      minuteInput.removeAttribute('aria-valuenow');
27!
115
    }
27!
116

27!
117
    const clockHand = this.core.getClockHand();
27!
118
    if (clockHand) {
119
      const originalTransition = clockHand.style.transition;
30✔
120
      clockHand.style.transition = 'none';
121
      clockHand.style.transform = 'rotateZ(0deg)';
122

30✔
123
      void clockHand.offsetHeight;
30✔
124

1✔
125
      requestAnimationFrame(() => {
1✔
126
        clockHand.style.transition = originalTransition;
127
      });
30✔
128
    }
30✔
129

30!
130
    this.removeActiveStates();
30!
131

132
    if (hourInput) {
133
      hourInput.click();
30✔
134
    }
30!
NEW
135

×
136
    if (clockType === '12h' && neutralType) {
30✔
137
      const amButton = this.core.getAM();
30✔
138
      const pmButton = this.core.getPM();
139

140
      amButton?.classList.remove('active');
82✔
141
      pmButton?.classList.remove('active');
82!
NEW
142

×
143
      amButton?.setAttribute('aria-pressed', 'false');
82✔
144
      pmButton?.setAttribute('aria-pressed', 'false');
82!
145

82✔
146
      pmButton?.classList.add('active');
82✔
147
      pmButton?.setAttribute('aria-pressed', 'true');
82✔
148
    }
82✔
149

82!
150
    this.emitter.emit('animation:clock', {});
82!
151
  }
82✔
152

82✔
153
  private removeActiveStates(): void {
129✔
154
    const allValueTips = this.core.getAllValueTips();
15✔
155
    allValueTips.forEach((tip) => {
82✔
156
      tip.classList.remove('active');
82✔
157
      tip.removeAttribute('aria-selected');
82✔
158
    });
82✔
159

160
    const hourInput = this.core.getHour();
161
    const minuteInput = this.core.getMinutes();
9✔
162

5✔
163
    hourInput?.removeAttribute('aria-valuenow');
4✔
164
    minuteInput?.removeAttribute('aria-valuenow');
4!
NEW
165
  }
×
166

4✔
167
  private disableConfirmButton(): void {
4✔
168
    const okButton = this.core.getOkButton();
4✔
169
    if (!okButton) return;
170

NEW
171
    okButton.classList.add('disabled');
×
NEW
172
    okButton.setAttribute('aria-disabled', 'true');
×
NEW
173
  }
×
NEW
174

×
NEW
175
  private updateClearButtonState(): void {
×
NEW
176
    const clearButton = this.getClearButton();
×
NEW
177
    if (!clearButton) return;
×
NEW
178

×
NEW
179
    const input = this.core.getInput();
×
NEW
180
    const hasInputValue = input?.value && input.value.trim() !== '';
×
NEW
181

×
NEW
182
    const hourInput = this.core.getHour();
×
NEW
183
    const minuteInput = this.core.getMinutes();
×
NEW
184
    const activeTypeMode = this.core.getActiveTypeMode();
×
NEW
185

×
NEW
186
    const clockType = this.core.options.clock.type;
×
NEW
187
    const currentHour = hourInput?.value || '';
×
NEW
188
    const currentMinutes = minuteInput?.value || '';
×
NEW
189
    const currentType = activeTypeMode?.textContent || '';
×
190

NEW
191
    const isDefaultValue =
×
192
      clockType === '12h'
193
        ? currentHour === '12' && currentMinutes === '00' && currentType === 'PM'
194
        : currentHour === '12' && currentMinutes === '00';
195

196
    const hasChangedValue = !isDefaultValue;
NEW
197

×
NEW
198
    const shouldEnable = hasInputValue || hasChangedValue;
×
NEW
199

×
NEW
200
    clearButton.classList.toggle('disabled', !shouldEnable);
×
NEW
201
    clearButton.setAttribute('aria-disabled', String(!shouldEnable));
×
202
  }
NEW
203

×
204
  private reenableConfirmIfCleared(): void {
205
    if (!this.wasCleared) return;
206

160✔
207
    const okButton = this.core.getOkButton();
160!
208
    if (!okButton) return;
209

NEW
210
    okButton.classList.remove('disabled');
×
NEW
211
    okButton.setAttribute('aria-disabled', 'false');
×
212
    this.wasCleared = false;
213
  }
NEW
214

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

221
    if (fromTime) fromTime.textContent = '--:--';
NEW
222
    if (toTime) toTime.textContent = '--:--';
×
NEW
223

×
224
    fromTab?.classList.add('active');
225
    toTab?.classList.remove('active');
NEW
226

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

NEW
230
    fromTab?.setAttribute('tabindex', '0');
×
NEW
231
    toTab?.setAttribute('tabindex', '-1');
×
232

233
    toTab?.classList.add('disabled');
234
    toTab?.setAttribute('aria-disabled', 'true');
174✔
235

174✔
236
    const hourInput = this.core.getHour();
237
    if (hourInput) {
238
      hourInput.focus();
9✔
239
    }
240

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

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

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

256
    dropdown?.setAttribute('aria-expanded', 'false');
257
  }
258

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

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

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

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

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

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

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

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

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

© 2026 Coveralls, Inc