Skip to content

Commit fd93a72

Browse files
committed
polish the clear icon scenario
1 parent 9f30b4c commit fd93a72

File tree

3 files changed

+135
-34
lines changed

3 files changed

+135
-34
lines changed

src/components/date-range-picker/date-range-picker.spec.ts

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -191,13 +191,9 @@ describe('Date range picker', () => {
191191
await elementUpdated(picker);
192192
expect(picker.visibleMonths).to.equal(1);
193193

194-
picker = await fixture<IgcDateRangePickerComponent>(
195-
html`<igc-date-range-picker
196-
visible-months="11"
197-
></igc-date-range-picker>`
198-
);
199-
194+
picker.setAttribute('visible-months', '11');
200195
await elementUpdated(picker);
196+
201197
expect(picker.visibleMonths).to.equal(2);
202198

203199
picker = await fixture<IgcDateRangePickerComponent>(
@@ -210,11 +206,8 @@ describe('Date range picker', () => {
210206
it('should set value through attribute correctly in case the date values are valid ISO 8601 strings', async () => {
211207
const expectedValue = { start: today.native, end: tomorrow.native };
212208
const attributeValue = { start: today.native, end: tomorrow.native };
213-
picker = await fixture<IgcDateRangePickerComponent>(
214-
html`<igc-date-range-picker
215-
value="${JSON.stringify(attributeValue)}"
216-
></igc-date-range-picker>`
217-
);
209+
picker.useTwoInputs = false;
210+
picker.setAttribute('value', JSON.stringify(attributeValue));
218211
await elementUpdated(picker);
219212

220213
checkSelectedRange(picker, expectedValue, false);
@@ -380,6 +373,7 @@ describe('Date range picker', () => {
380373

381374
it('should set properties of the calendar correctly', async () => {
382375
const props = {
376+
activeDate: new Date(2025, 3, 9),
383377
weekStart: 'friday',
384378
hideOutsideDays: true,
385379
hideHeader: true,
@@ -410,6 +404,7 @@ describe('Date range picker', () => {
410404
expect(picker.orientation).to.equal('horizontal');
411405
expect(calendar.disabledDates).to.be.undefined;
412406
expect(calendar.specialDates).to.be.undefined;
407+
checkDatesEqual(calendar.activeDate, today.native);
413408

414409
Object.assign(picker, props);
415410
await elementUpdated(picker);
@@ -1169,32 +1164,86 @@ describe('Date range picker', () => {
11691164
});
11701165

11711166
it('should clear the inputs on clicking the clear icon - two inputs', async () => {
1167+
const eventSpy = spy(picker, 'emitEvent');
11721168
picker.value = { start: today.native, end: tomorrow.native };
11731169
await elementUpdated(picker);
11741170

1171+
dateTimeInputs[0].focus();
1172+
await elementUpdated(dateTimeInputs[0]);
1173+
11751174
simulateClick(getIcon(picker, clearIcon));
1176-
await elementUpdated(picker);
11771175

1176+
dateTimeInputs[0].blur();
1177+
await elementUpdated(dateTimeInputs[0]);
1178+
1179+
expect(isFocused(dateTimeInputs[0])).to.be.false;
1180+
expect(eventSpy).to.be.calledWith('igcChange', {
1181+
detail: { start: null, end: null },
1182+
});
11781183
expect(picker.open).to.be.false;
11791184
expect(picker.value).to.deep.equal({ start: null, end: null });
11801185
expect(dateTimeInputs[0].value).to.be.null;
11811186
expect(dateTimeInputs[1].value).to.be.null;
11821187
});
11831188

11841189
it('should clear the inputs on clicking the clear icon - single input', async () => {
1190+
const eventSpy = spy(picker, 'emitEvent');
11851191
picker.useTwoInputs = false;
11861192
picker.value = { start: today.native, end: tomorrow.native };
11871193
await elementUpdated(picker);
11881194

1195+
const input = picker.renderRoot!.querySelector(
1196+
IgcInputComponent.tagName
1197+
)!;
1198+
input.focus();
1199+
await elementUpdated(input);
11891200
simulateClick(getIcon(picker, clearIcon));
11901201
await elementUpdated(picker);
1202+
input.blur();
1203+
await elementUpdated(input);
11911204

1205+
expect(isFocused(input)).to.be.false;
1206+
expect(eventSpy).to.be.calledWith('igcChange', {
1207+
detail: { start: null, end: null },
1208+
});
11921209
expect(picker.open).to.be.false;
11931210
expect(picker.value).to.deep.equal({ start: null, end: null });
1211+
expect(input.value).to.equal('');
1212+
});
1213+
1214+
it('should not clear the input(s) via the clear icon when readOnly is true', async () => {
1215+
const eventSpy = spy(picker, 'emitEvent');
1216+
const testValue = { start: today.native, end: tomorrow.native };
1217+
picker.value = testValue;
1218+
picker.readOnly = true;
1219+
await elementUpdated(picker);
1220+
1221+
dateTimeInputs[0].focus();
1222+
await elementUpdated(dateTimeInputs[0]);
1223+
simulateClick(getIcon(picker, clearIcon));
1224+
dateTimeInputs[0].blur();
1225+
await elementUpdated(dateTimeInputs[0]);
1226+
1227+
expect(picker.value).to.deep.equal(testValue);
1228+
expect(eventSpy).not.called;
1229+
checkSelectedRange(picker, testValue);
1230+
1231+
picker.useTwoInputs = false;
1232+
await elementUpdated(picker);
1233+
11941234
const input = picker.renderRoot!.querySelector(
11951235
IgcInputComponent.tagName
11961236
)!;
1197-
expect(input.value).to.equal('');
1237+
input.focus();
1238+
await elementUpdated(input);
1239+
simulateClick(getIcon(picker, clearIcon));
1240+
await elementUpdated(picker);
1241+
input.blur();
1242+
await elementUpdated(input);
1243+
1244+
expect(picker.value).to.deep.equal(testValue);
1245+
expect(eventSpy).not.called;
1246+
checkSelectedRange(picker, testValue, false);
11981247
});
11991248

12001249
it('should emit igcInput and igcChange on input value change', async () => {

src/components/date-range-picker/date-range-picker.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ export default class IgcDateRangePickerComponent extends FormAssociatedRequiredM
225225
private _inputFormat?: string;
226226
private _placeholder?: string;
227227
private _defaultMask!: string;
228-
private _currentValue: DateRangeValue | null = null;
228+
private _oldValue: DateRangeValue | null = null;
229229
private _visibleMonths: 1 | 2 = 2;
230230

231231
private predefinedRanges: CustomDateRange[] = [
@@ -605,9 +605,7 @@ export default class IgcDateRangePickerComponent extends FormAssociatedRequiredM
605605

606606
/** Clears the input parts of the component of any user input */
607607
public clear() {
608-
this.value = null;
609-
this._inputs[0]?.clear();
610-
this._inputs[1]?.clear();
608+
this._clear(false);
611609
}
612610

613611
/** Selects a date range value in the picker */
@@ -646,7 +644,7 @@ export default class IgcDateRangePickerComponent extends FormAssociatedRequiredM
646644
}
647645

648646
protected revertValue() {
649-
this.value = this._currentValue;
647+
this.value = this._oldValue;
650648
}
651649

652650
protected dialogCancel() {
@@ -693,7 +691,7 @@ export default class IgcDateRangePickerComponent extends FormAssociatedRequiredM
693691
this._rootClickController.update();
694692

695693
if (this.open && this.mode === 'dialog') {
696-
this._currentValue = this.value;
694+
this._oldValue = this.value;
697695
}
698696
}
699697

@@ -726,6 +724,13 @@ export default class IgcDateRangePickerComponent extends FormAssociatedRequiredM
726724
protected handleFocusOut({ relatedTarget }: FocusEvent) {
727725
if (!this.contains(relatedTarget as Node)) {
728726
this.checkValidity();
727+
if (
728+
!this.useTwoInputs &&
729+
!this.readOnly &&
730+
this._oldValue !== this.value
731+
) {
732+
this.emitEvent('igcChange', { detail: this.value });
733+
}
729734
}
730735
}
731736

@@ -921,14 +926,30 @@ export default class IgcDateRangePickerComponent extends FormAssociatedRequiredM
921926
}
922927
}
923928

929+
private _clear(checkReadOnly = false) {
930+
if (checkReadOnly && this.readOnly) {
931+
return;
932+
}
933+
this._oldValue = this.value;
934+
this.value = null;
935+
if (this.useTwoInputs) {
936+
this._inputs[0]?.clear();
937+
this._inputs[1]?.clear();
938+
}
939+
}
940+
924941
private renderClearIcon(picker: 'start' | 'end' = 'start') {
925942
const clearIcon = !this.useTwoInputs
926943
? 'clear-icon'
927944
: `clear-icon-${picker}`;
928945
return !this.value || (this.value.start === null && this.value.end === null)
929946
? nothing
930947
: html`
931-
<span slot="suffix" part="${clearIcon}" @click=${this.clear}>
948+
<span
949+
slot="suffix"
950+
part="${clearIcon}"
951+
@click=${() => this._clear(true)}
952+
>
932953
<slot name="${clearIcon}">
933954
<igc-icon
934955
name="input_clear"
@@ -1109,9 +1130,9 @@ export default class IgcDateRangePickerComponent extends FormAssociatedRequiredM
11091130
exportparts="input, label, prefix, suffix"
11101131
>
11111132
${this.renderCalendarIcon(picker)}
1112-
<slot name=${`prefix-${picker}` || 'prefix'} slot="prefix"></slot>
1133+
<slot name=${`prefix-${picker}`} slot="prefix"></slot>
11131134
${this.renderClearIcon(picker)}
1114-
<slot name=${`suffix-${picker}` || 'suffix'} slot="suffix"></slot>
1135+
<slot name=${`suffix-${picker}`} slot="suffix"></slot>
11151136
</igc-date-time-input>
11161137
`;
11171138
}

stories/date-range-picker.stories.ts

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,7 @@ const metadata: Meta<IgcDateRangePickerComponent> = {
4444
},
4545
},
4646
argTypes: {
47-
value: {
48-
type: 'DateRangeValue',
49-
control: 'date',
50-
table: { defaultValue: { summary: 'null' } },
51-
},
47+
value: { type: 'DateRangeValue', control: 'date' },
5248
mode: {
5349
type: '"dropdown" | "dialog"',
5450
description:
@@ -90,16 +86,28 @@ const metadata: Meta<IgcDateRangePickerComponent> = {
9086
table: { defaultValue: { summary: 'false' } },
9187
},
9288
visibleMonths: {
93-
type: 'number',
89+
type: '1 | 2',
9490
description: 'The number of months displayed in the calendar.',
95-
control: 'number',
91+
options: ['1', '2'],
92+
control: { type: 'inline-radio' },
9693
},
97-
9894
label: {
9995
type: 'string',
10096
description: 'The label of the control (single input).',
10197
control: 'text',
10298
},
99+
labelStart: {
100+
type: 'string',
101+
description: 'The label attribute of the start input.',
102+
control: 'text',
103+
table: { defaultValue: { summary: '' } },
104+
},
105+
labelEnd: {
106+
type: 'string',
107+
description: 'The label attribute of the end input.',
108+
control: 'text',
109+
table: { defaultValue: { summary: '' } },
110+
},
103111
placeholder: {
104112
type: 'string',
105113
description: 'The placeholder attribute of the control (single input).',
@@ -129,6 +137,18 @@ const metadata: Meta<IgcDateRangePickerComponent> = {
129137
'The maximum value required for the date range picker to remain valid.',
130138
control: 'date',
131139
},
140+
placeholderStart: {
141+
type: 'string',
142+
description: 'The placeholder attribute of the start input.',
143+
control: 'text',
144+
table: { defaultValue: { summary: '' } },
145+
},
146+
placeholderEnd: {
147+
type: 'string',
148+
description: 'The placeholder attribute of the end input.',
149+
control: 'text',
150+
table: { defaultValue: { summary: '' } },
151+
},
132152
prompt: {
133153
type: 'string',
134154
description: 'The prompt symbol to use for unfilled parts of the mask.',
@@ -242,13 +262,16 @@ const metadata: Meta<IgcDateRangePickerComponent> = {
242262
},
243263
},
244264
args: {
245-
value: 'null',
246265
mode: 'dropdown',
247266
useTwoInputs: false,
248267
readOnly: false,
249268
nonEditable: false,
250269
outlined: false,
251270
usePredefinedRanges: false,
271+
labelStart: '',
272+
labelEnd: '',
273+
placeholderStart: '',
274+
placeholderEnd: '',
252275
prompt: '_',
253276
headerOrientation: 'horizontal',
254277
orientation: 'horizontal',
@@ -280,12 +303,16 @@ interface IgcDateRangePickerArgs {
280303
nonEditable: boolean;
281304
/** Whether the control will have outlined appearance. */
282305
outlined: boolean;
283-
/** Whether the control will show chips with predefined ranges in dialog mode. */
306+
/** Whether the control will show chips with predefined ranges. */
284307
usePredefinedRanges: boolean;
308+
/** The number of months displayed in the calendar. */
309+
visibleMonths: 1 | 2;
285310
/** The label of the control (single input). */
286311
label: string;
287-
/** The number of months displayed in the calendar. */
288-
visibleMonths: number;
312+
/** The label attribute of the start input. */
313+
labelStart: string;
314+
/** The label attribute of the end input. */
315+
labelEnd: string;
289316
/** The placeholder attribute of the control (single input). */
290317
placeholder: string;
291318
/**
@@ -302,6 +329,10 @@ interface IgcDateRangePickerArgs {
302329
min: Date;
303330
/** The maximum value required for the date range picker to remain valid. */
304331
max: Date;
332+
/** The placeholder attribute of the start input. */
333+
placeholderStart: string;
334+
/** The placeholder attribute of the end input. */
335+
placeholderEnd: string;
305336
/** The prompt symbol to use for unfilled parts of the mask. */
306337
prompt: string;
307338
/** The orientation of the calendar header. */

0 commit comments

Comments
 (0)