Skip to content

Commit

Permalink
fix: remove generated pickers when adding custom pickers (#3153)
Browse files Browse the repository at this point in the history
  • Loading branch information
sissbruecker authored Dec 7, 2021
1 parent e90cd92 commit 1352f4b
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 62 deletions.
137 changes: 83 additions & 54 deletions packages/date-time-picker/src/vaadin-date-time-picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
import './vaadin-date-time-picker-date-picker.js';
import './vaadin-date-time-picker-time-picker.js';
import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
import { DisabledMixin } from '@vaadin/component-base/src/disabled-mixin.js';
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
Expand Down Expand Up @@ -327,6 +328,24 @@ class DateTimePicker extends FieldMixin(SlotMixin(DisabledMixin(ThemableMixin(El
i18n: {
type: Object,
value: () => ({ ...datePickerI18nDefaults, ...timePickerI18nDefaults })
},

/**
* The current slotted date picker.
* @private
*/
__datePicker: {
type: HTMLElement,
observer: '__datePickerChanged'
},

/**
* The current slotted time picker.
* @private
*/
__timePicker: {
type: HTMLElement,
observer: '__timePickerChanged'
}
};
}
Expand Down Expand Up @@ -378,6 +397,10 @@ class DateTimePicker extends FieldMixin(SlotMixin(DisabledMixin(ThemableMixin(El

this.__changeEventHandler = this.__changeEventHandler.bind(this);
this.__valueChangedEventHandler = this.__valueChangedEventHandler.bind(this);

this._observer = new FlattenedNodesObserver(this, (info) => {
this.__onDomChange(info.addedNodes);
});
}

/** @protected */
Expand All @@ -390,11 +413,8 @@ class DateTimePicker extends FieldMixin(SlotMixin(DisabledMixin(ThemableMixin(El
}
});

this.__datePickerChanged();
this.__timePickerChanged();

this.$.dateSlot.addEventListener('slotchange', this.__datePickerChanged.bind(this));
this.$.timeSlot.addEventListener('slotchange', this.__timePickerChanged.bind(this));
this.__datePicker = this._getDirectSlotChild('date-picker');
this.__timePicker = this._getDirectSlotChild('time-picker');

if (this.autofocus && !this.disabled) {
window.requestAnimationFrame(() => this.focus());
Expand Down Expand Up @@ -449,87 +469,98 @@ class DateTimePicker extends FieldMixin(SlotMixin(DisabledMixin(ThemableMixin(El
}

/** @private */
__datePickerChanged() {
const datePicker = this._getDirectSlotChild('date-picker');
if (this.__datePicker === datePicker || !datePicker) {
__onDomChange(addedNodes) {
addedNodes
.filter((node) => node.nodeType === Node.ELEMENT_NODE)
.forEach((node) => {
const slotAttributeValue = node.getAttribute('slot');

if (slotAttributeValue === 'date-picker') {
this.__datePicker = node;
} else if (slotAttributeValue === 'time-picker') {
this.__timePicker = node;
}
});
}

/** @private */
__datePickerChanged(newDatePicker, existingDatePicker) {
if (!newDatePicker) {
return;
}
if (this.__datePicker) {
if (existingDatePicker) {
// Remove an existing date picker
this.__removeInputListeners(this.__datePicker);
this.__datePicker.remove();
this.__removeInputListeners(existingDatePicker);
existingDatePicker.remove();
}

this.__addInputListeners(datePicker);
this.__datePicker = datePicker;
this.__addInputListeners(newDatePicker);

if (datePicker.__defaultPicker) {
if (newDatePicker.__defaultPicker) {
// Synchronize properties to default date picker
datePicker.placeholder = this.datePlaceholder;
datePicker.invalid = this.invalid;
datePicker.initialPosition = this.initialPosition;
datePicker.showWeekNumbers = this.showWeekNumbers;
this.__syncI18n(datePicker, this, datePickerI18nProps);
newDatePicker.placeholder = this.datePlaceholder;
newDatePicker.invalid = this.invalid;
newDatePicker.initialPosition = this.initialPosition;
newDatePicker.showWeekNumbers = this.showWeekNumbers;
this.__syncI18n(newDatePicker, this, datePickerI18nProps);
} else {
// Synchronize properties from slotted date picker
this.datePlaceholder = datePicker.placeholder;
this.initialPosition = datePicker.initialPosition;
this.showWeekNumbers = datePicker.showWeekNumbers;
this.__syncI18n(this, datePicker, datePickerI18nProps);
this.datePlaceholder = newDatePicker.placeholder;
this.initialPosition = newDatePicker.initialPosition;
this.showWeekNumbers = newDatePicker.showWeekNumbers;
this.__syncI18n(this, newDatePicker, datePickerI18nProps);
}

// min and max are always synchronized from date time picker (host) to inner fields because time picker
// min and max need to be dynamically set depending on currently selected date instead of simple propagation
datePicker.min = this.__formatDateISO(this.__minDateTime, this.__defaultDateMinMaxValue);
datePicker.max = this.__formatDateISO(this.__maxDateTime, this.__defaultDateMinMaxValue);
datePicker.required = this.required;
datePicker.disabled = this.disabled;
datePicker.readonly = this.readonly;
datePicker.autoOpenDisabled = this.autoOpenDisabled;
newDatePicker.min = this.__formatDateISO(this.__minDateTime, this.__defaultDateMinMaxValue);
newDatePicker.max = this.__formatDateISO(this.__maxDateTime, this.__defaultDateMinMaxValue);
newDatePicker.required = this.required;
newDatePicker.disabled = this.disabled;
newDatePicker.readonly = this.readonly;
newDatePicker.autoOpenDisabled = this.autoOpenDisabled;

// Disable default internal validation for the component
datePicker.validate = () => {};
datePicker._validateInput = () => {};
newDatePicker.validate = () => {};
newDatePicker._validateInput = () => {};
}

/** @private */
__timePickerChanged() {
const timePicker = this._getDirectSlotChild('time-picker');
if (this.__timePicker === timePicker || !timePicker) {
__timePickerChanged(newTimePicker, existingTimePicker) {
if (!newTimePicker) {
return;
}
if (this.__timePicker) {
if (existingTimePicker) {
// Remove an existing time picker
this.__removeInputListeners(this.__timePicker);
this.__timePicker.remove();
this.__removeInputListeners(existingTimePicker);
existingTimePicker.remove();
}

this.__addInputListeners(timePicker);
this.__timePicker = timePicker;
this.__addInputListeners(newTimePicker);

if (timePicker.__defaultPicker) {
if (newTimePicker.__defaultPicker) {
// Synchronize properties to default time picker
timePicker.placeholder = this.timePlaceholder;
timePicker.step = this.step;
timePicker.invalid = this.invalid;
this.__syncI18n(timePicker, this, timePickerI18nProps);
newTimePicker.placeholder = this.timePlaceholder;
newTimePicker.step = this.step;
newTimePicker.invalid = this.invalid;
this.__syncI18n(newTimePicker, this, timePickerI18nProps);
} else {
// Synchronize properties from slotted time picker
this.timePlaceholder = timePicker.placeholder;
this.step = timePicker.step;
this.__syncI18n(this, timePicker, timePickerI18nProps);
this.timePlaceholder = newTimePicker.placeholder;
this.step = newTimePicker.step;
this.__syncI18n(this, newTimePicker, timePickerI18nProps);
}

// min and max are always synchronized from parent to slotted because time picker min and max
// need to be dynamically set depending on currently selected date instead of simple propagation
this.__updateTimePickerMinMax();
timePicker.required = this.required;
timePicker.disabled = this.disabled;
timePicker.readonly = this.readonly;
timePicker.autoOpenDisabled = this.autoOpenDisabled;
newTimePicker.required = this.required;
newTimePicker.disabled = this.disabled;
newTimePicker.readonly = this.readonly;
newTimePicker.autoOpenDisabled = this.autoOpenDisabled;

// Disable default internal validation for the component
timePicker.validate = () => {};
newTimePicker.validate = () => {};
}

/** @private */
Expand Down Expand Up @@ -561,12 +592,10 @@ class DateTimePicker extends FieldMixin(SlotMixin(DisabledMixin(ThemableMixin(El

/** @private */
__i18nChanged(changeRecord) {
this.__datePickerChanged();
if (this.__datePicker) {
this.__datePicker.set(changeRecord.path, changeRecord.value);
}

this.__timePickerChanged();
if (this.__timePicker) {
this.__timePicker.set(changeRecord.path, changeRecord.value);
}
Expand Down
34 changes: 26 additions & 8 deletions packages/date-time-picker/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ const fixtures = {
`,
'lazy-inputs': `
<vaadin-date-time-picker>
<vaadin-date-picker slot="setAfterReady"></vaadin-date-picker>
<vaadin-time-picker slot="setAfterReady"></vaadin-time-picker>
</vaadin-date-time-picker>
`,
'default-values': `
Expand All @@ -25,8 +23,6 @@ const fixtures = {
`,
'lazy-values': `
<vaadin-date-time-picker>
<vaadin-date-picker slot="setAfterReady" value="2019-09-16"></vaadin-date-picker>
<vaadin-time-picker slot="setAfterReady" value="15:00"></vaadin-time-picker>
</vaadin-date-time-picker>
`
};
Expand Down Expand Up @@ -303,9 +299,14 @@ describe('Theme attribute', () => {
timePicker = dateTimePicker.querySelector('vaadin-time-picker');

if (set === 'lazy') {
// Assign the slots lazily simulating the case if Flow adds the slotted elements after date time picker is ready
// Add slotted children lazily simulating the case where Flow adds the slotted elements after date time picker is ready
datePicker = document.createElement('vaadin-date-picker');
datePicker.slot = 'date-picker';
timePicker = document.createElement('vaadin-time-picker');
timePicker.slot = 'time-picker';
dateTimePicker.appendChild(datePicker);
dateTimePicker.appendChild(timePicker);
// Wait for FlattenedNodeObserver to trigger
await aTimeout(0);
}
});
Expand Down Expand Up @@ -358,6 +359,16 @@ describe('Theme attribute', () => {
});

if (set === 'lazy') {
it('should not contain original date picker', () => {
expect(originalDatePicker).not.to.be.undefined;
expect(dateTimePicker.contains(originalDatePicker)).to.be.false;
});

it('should not contain original time picker', () => {
expect(originalTimePicker).not.to.be.undefined;
expect(dateTimePicker.contains(originalTimePicker)).to.be.false;
});

it('should not react to changes on discarded pickers', () => {
sinon.spy(dateTimePicker, '__valueChangedEventHandler');
sinon.spy(dateTimePicker, '__changeEventHandler');
Expand Down Expand Up @@ -395,9 +406,16 @@ describe('Theme attribute', () => {
dateTimePicker = fixtureSync(fixtures[`${set}-values`]);

if (set === 'lazy') {
// Assign the slots lazily simulating the case if Flow adds the slotted elements after date time picker is ready
dateTimePicker.querySelector('vaadin-date-picker').slot = 'date-picker';
dateTimePicker.querySelector('vaadin-time-picker').slot = 'time-picker';
// Add slotted children lazily simulating the case where Flow adds the slotted elements after date time picker is ready
const datePicker = document.createElement('vaadin-date-picker');
datePicker.value = '2019-09-16';
datePicker.slot = 'date-picker';
const timePicker = document.createElement('vaadin-time-picker');
timePicker.value = '15:00';
timePicker.slot = 'time-picker';
dateTimePicker.appendChild(datePicker);
dateTimePicker.appendChild(timePicker);
// Wait for FlattenedNodeObserver to trigger
await aTimeout(0);
}
});
Expand Down

0 comments on commit 1352f4b

Please sign in to comment.