Skip to content

Commit 4c26bef

Browse files
tlouissedaKmoRJoren BroekemaMikhail BashkirovCubLion
committed
feat: update to latest overlay system
Co-authored-by: Thomas Allmer <Thomas.Allmer@ing.com> Co-authored-by: Joren Broekema <Joren.Broekema@ing.com> Co-authored-by: Mikhail Bashkirov <Mikhail.Bashkirov@ing.com> Co-authored-by: Alex Ghiu <Alex.Ghiu@ing.com>
1 parent 364f185 commit 4c26bef

19 files changed

+245
-261
lines changed

packages/calendar/src/LionCalendar.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,6 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
221221
firstUpdated() {
222222
super.firstUpdated();
223223
this.__contentWrapperElement = this.shadowRoot.getElementById('js-content-wrapper');
224-
225224
this.__addEventDelegationForClickDate();
226225
this.__addEventDelegationForFocusDate();
227226
this.__addEventDelegationForBlurDate();
@@ -501,6 +500,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
501500
}
502501

503502
__removeEventDelegations() {
503+
if (!this.__contentWrapperElement) {
504+
return;
505+
}
504506
this.__contentWrapperElement.removeEventListener('click', this.__clickDateDelegation);
505507
this.__contentWrapperElement.removeEventListener('focus', this.__focusDateDelegation);
506508
this.__contentWrapperElement.removeEventListener('blur', this.__blurDateDelegation);

packages/input-datepicker/src/LionInputDatepicker.js

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { html, render, ifDefined } from '@lion/core';
1+
import { html, ifDefined, render } from '@lion/core';
22
import { LionInputDate } from '@lion/input-date';
3-
import { overlays, ModalDialogController } from '@lion/overlays';
4-
import { Unparseable, isValidatorApplied } from '@lion/validate';
3+
import { OverlayController, withModalDialogConfig, OverlayMixin } from '@lion/overlays';
4+
import { isValidatorApplied } from '@lion/validate';
55
import '@lion/calendar/lion-calendar.js';
66
import './lion-calendar-overlay-frame.js';
77

88
/**
99
* @customElement lion-input-datepicker
1010
* @extends {LionInputDate}
1111
*/
12-
export class LionInputDatepicker extends LionInputDate {
12+
export class LionInputDatepicker extends OverlayMixin(LionInputDate) {
1313
static get properties() {
1414
return {
1515
/**
@@ -46,7 +46,11 @@ export class LionInputDatepicker extends LionInputDate {
4646
get slots() {
4747
return {
4848
...super.slots,
49-
[this._calendarInvokerSlot]: () => this.__createPickerAndReturnInvokerNode(),
49+
[this._calendarInvokerSlot]: () => {
50+
const renderParent = document.createElement('div');
51+
render(this._invokerTemplate(), renderParent);
52+
return renderParent.firstElementChild;
53+
},
5054
};
5155
}
5256

@@ -137,12 +141,8 @@ export class LionInputDatepicker extends LionInputDate {
137141
return this.querySelector(`#${this.__invokerId}`);
138142
}
139143

140-
get _calendarOverlayElement() {
141-
return this._overlayCtrl.contentNode;
142-
}
143-
144144
get _calendarElement() {
145-
return this._calendarOverlayElement.querySelector('#calendar');
145+
return this._overlayCtrl.contentNode.querySelector('#calendar');
146146
}
147147

148148
constructor() {
@@ -200,7 +200,12 @@ export class LionInputDatepicker extends LionInputDate {
200200
}
201201
}
202202

203-
_calendarOverlayTemplate() {
203+
/**
204+
* Defining this overlay as a templates lets OverlayInteraceMixin
205+
* this is our source to give as .contentNode to OverlayController.
206+
* Important: do not change the name of this method.
207+
*/
208+
_overlayTemplate() {
204209
return html`
205210
<lion-calendar-overlay-frame @dialog-close=${() => this._overlayCtrl.hide()}>
206211
<span slot="heading">${this.calendarHeading}</span>
@@ -240,8 +245,6 @@ export class LionInputDatepicker extends LionInputDate {
240245
type="button"
241246
@click="${this.__openCalendarOverlay}"
242247
id="${this.__invokerId}"
243-
aria-haspopup="dialog"
244-
aria-expanded="false"
245248
aria-label="${this.msgLit('lion-input-datepicker:openDatepickerLabel')}"
246249
title="${this.msgLit('lion-input-datepicker:openDatepickerLabel')}"
247250
>
@@ -250,27 +253,26 @@ export class LionInputDatepicker extends LionInputDate {
250253
`;
251254
}
252255

253-
__createPickerAndReturnInvokerNode() {
254-
const renderParent = document.createElement('div');
255-
render(this._invokerTemplate(), renderParent);
256-
const invokerNode = renderParent.firstElementChild;
257-
258-
// TODO: ModalDialogController could be replaced by a more flexible
259-
// overlay, allowing the overlay to switch on smaller screens, for instance from dropdown to
260-
// bottom sheet via DynamicOverlayController
261-
this._overlayCtrl = overlays.add(
262-
new ModalDialogController({
263-
contentTemplate: () => this._calendarOverlayTemplate(),
264-
elementToFocusAfterHide: invokerNode,
265-
}),
266-
);
267-
return invokerNode;
256+
/**
257+
* @override Configures OverlayMixin
258+
* @desc returns an instance of a (dynamic) overlay controller
259+
* @returns {OverlayController}
260+
*/
261+
// eslint-disable-next-line class-methods-use-this
262+
_defineOverlay({ contentNode, invokerNode }) {
263+
const ctrl = new OverlayController({
264+
...withModalDialogConfig(),
265+
contentNode,
266+
invokerNode,
267+
elementToFocusAfterHide: invokerNode,
268+
});
269+
return ctrl;
268270
}
269271

270272
async __openCalendarOverlay() {
271273
this._overlayCtrl.show();
272274
await Promise.all([
273-
this._calendarOverlayElement.updateComplete,
275+
this._overlayCtrl.contentNode.updateComplete,
274276
this._calendarElement.updateComplete,
275277
]);
276278
this._onCalendarOverlayOpened();
@@ -301,7 +303,7 @@ export class LionInputDatepicker extends LionInputDate {
301303
* @returns {Date|undefined} a 'guarded' modelValue
302304
*/
303305
static __getSyncDownValue(modelValue) {
304-
return modelValue instanceof Unparseable ? undefined : modelValue;
306+
return modelValue instanceof Date ? modelValue : undefined;
305307
}
306308

307309
/**
@@ -327,4 +329,11 @@ export class LionInputDatepicker extends LionInputDate {
327329
}
328330
});
329331
}
332+
333+
/**
334+
* @override Configures OverlayMixin
335+
*/
336+
get _overlayInvokerNode() {
337+
return this._invokerElement;
338+
}
330339
}

packages/input-datepicker/test-helpers/DatepickerInputObject.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export class DatepickerInputObject {
2424
return Promise.all(completePromises);
2525
}
2626

27+
async closeCalendar() {
28+
this.overlayCloseButtonEl.click();
29+
}
30+
2731
async selectMonthDay(day) {
2832
this.overlayController.show();
2933
await this.calendarEl.updateComplete;

packages/input-datepicker/test/lion-input-datepicker.test.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { expect, fixture, defineCE } from '@open-wc/testing';
22
import sinon from 'sinon';
3-
import { localizeTearDown } from '@lion/localize/test-helpers.js';
43
import { html, LitElement } from '@lion/core';
54
import {
65
maxDateValidator,
@@ -15,10 +14,6 @@ import { LionInputDatepicker } from '../src/LionInputDatepicker.js';
1514
import '../lion-input-datepicker.js';
1615

1716
describe('<lion-input-datepicker>', () => {
18-
beforeEach(() => {
19-
localizeTearDown();
20-
});
21-
2217
describe('Calendar Overlay', () => {
2318
it('implements calendar-overlay Style component', async () => {
2419
const el = await fixture(html`
@@ -287,14 +282,16 @@ describe('<lion-input-datepicker>', () => {
287282
expect(elObj.invokerEl.getAttribute('aria-label')).to.equal('Open date picker');
288283
});
289284

290-
// TODO: move this functionality to GlobalOverlay
291-
it('adds aria-haspopup="dialog" and aria-expanded="true" to invoker button', async () => {
285+
it('adds [aria-expanded] to invoker button', async () => {
292286
const el = await fixture(html`
293287
<lion-input-datepicker></lion-input-datepicker>
294288
`);
295289
const elObj = new DatepickerInputObject(el);
296290

297-
expect(elObj.invokerEl.getAttribute('aria-haspopup')).to.equal('dialog');
291+
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal('false');
292+
await elObj.openCalendar();
293+
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal('true');
294+
await elObj.closeCalendar();
298295
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal('false');
299296
});
300297
});

packages/option/src/LionOption.js

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export class LionOption extends DisabledMixin(ChoiceInputMixin(FormRegisteringMi
2828
padding: 4px;
2929
}
3030
31-
:host([active]) {
31+
:host([active]),
32+
:host(:hover) {
3233
background-color: #ddd;
3334
}
3435
@@ -46,7 +47,7 @@ export class LionOption extends DisabledMixin(ChoiceInputMixin(FormRegisteringMi
4647
constructor() {
4748
super();
4849
this.active = false;
49-
this.__registerEventListener();
50+
this.__registerEventListeners();
5051
}
5152

5253
_requestUpdate(name, oldValue) {
@@ -81,35 +82,16 @@ export class LionOption extends DisabledMixin(ChoiceInputMixin(FormRegisteringMi
8182
this.setAttribute('role', 'option');
8283
}
8384

84-
disconnectedCallback() {
85-
super.disconnectedCallback();
86-
this.__unRegisterEventListeners();
87-
}
88-
89-
__registerEventListener() {
85+
__registerEventListeners() {
9086
this.__onClick = () => {
9187
if (!this.disabled) {
9288
this.checked = true;
9389
}
9490
};
95-
this.__onMouseEnter = () => {
96-
if (!this.disabled) {
97-
this.active = true;
98-
}
99-
};
100-
this.__onMouseLeave = () => {
101-
if (!this.disabled) {
102-
this.active = false;
103-
}
104-
};
10591
this.addEventListener('click', this.__onClick);
106-
this.addEventListener('mouseenter', this.__onMouseEnter);
107-
this.addEventListener('mouseleave', this.__onMouseLeave);
10892
}
10993

11094
__unRegisterEventListeners() {
11195
this.removeEventListener('click', this.__onClick);
112-
this.removeEventListener('mouseenter', this.__onMouseEnter);
113-
this.removeEventListener('mouseleave', this.__onMouseLeave);
11496
}
11597
}

packages/option/test/lion-option.test.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -80,24 +80,6 @@ describe('lion-option', () => {
8080
expect(el.hasAttribute('active')).to.be.false;
8181
});
8282

83-
it('does become active on [mouseenter]', async () => {
84-
const el = await fixture(html`
85-
<lion-option .choiceValue=${10}></lion-option>
86-
`);
87-
expect(el.active).to.be.false;
88-
el.dispatchEvent(new Event('mouseenter'));
89-
expect(el.active).to.be.true;
90-
});
91-
92-
it('does become un-active on [mouseleave]', async () => {
93-
const el = await fixture(html`
94-
<lion-option .choiceValue=${10} active></lion-option>
95-
`);
96-
expect(el.active).to.be.true;
97-
el.dispatchEvent(new Event('mouseleave'));
98-
expect(el.active).to.be.false;
99-
});
100-
10183
it('does become checked on [click]', async () => {
10284
const el = await fixture(html`
10385
<lion-option .choiceValue=${10}></lion-option>

packages/popup/src/LionPopup.js

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,40 @@
1-
import { UpdatingElement } from '@lion/core';
2-
import { overlays, LocalOverlayController } from '@lion/overlays';
3-
4-
export class LionPopup extends UpdatingElement {
5-
static get properties() {
6-
return {
7-
popperConfig: {
8-
type: Object,
9-
},
10-
};
1+
import { LitElement, html } from '@lion/core';
2+
import { OverlayMixin, OverlayController } from '@lion/overlays';
3+
4+
export class LionPopup extends OverlayMixin(LitElement) {
5+
render() {
6+
return html`
7+
<slot name="invoker"></slot>
8+
<slot name="content"></slot>
9+
`;
1110
}
1211

13-
get popperConfig() {
14-
return this._popperConfig;
12+
get _overlayContentNode() {
13+
return this.querySelector('[slot=content]');
1514
}
1615

17-
set popperConfig(config) {
18-
this._popperConfig = {
19-
...this._popperConfig,
20-
...config,
21-
};
16+
get _overlayInvokerNode() {
17+
return this.querySelector('[slot=invoker]');
18+
}
2219

23-
if (this._controller && this._controller._popper) {
24-
this._controller.updatePopperConfig(this._popperConfig);
25-
}
20+
// eslint-disable-next-line class-methods-use-this
21+
_defineOverlay() {
22+
return new OverlayController({
23+
placementMode: 'local',
24+
contentNode: this._overlayContentNode,
25+
invokerNode: this._overlayInvokerNode,
26+
handlesAccessibility: true,
27+
});
2628
}
2729

2830
connectedCallback() {
2931
super.connectedCallback();
30-
this.contentNode = this.querySelector('[slot="content"]');
31-
this.invokerNode = this.querySelector('[slot="invoker"]');
32-
33-
this._controller = overlays.add(
34-
new LocalOverlayController({
35-
hidesOnEsc: true,
36-
hidesOnOutsideClick: true,
37-
popperConfig: this.popperConfig,
38-
contentNode: this.contentNode,
39-
invokerNode: this.invokerNode,
40-
}),
41-
);
42-
this._show = () => this._controller.show();
43-
this._hide = () => this._controller.hide();
44-
this._toggle = () => this._controller.toggle();
45-
46-
this.invokerNode.addEventListener('click', this._toggle);
32+
this.__toggle = () => this._overlayCtrl.toggle();
33+
this._overlayInvokerNode.addEventListener('click', this.__toggle);
4734
}
4835

4936
disconnectedCallback() {
5037
super.disconnectedCallback();
51-
this.invokerNode.removeEventListener('click', this._toggle);
38+
this._overlayInvokerNode.removeEventListener('click', this._toggle);
5239
}
5340
}

0 commit comments

Comments
 (0)