@justeattakeaway/pie-modal 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +15 -0
- package/README.md +14 -6
- package/dist/index.js +166 -142
- package/dist/react.js +0 -0
- package/dist/types/index.d.ts +0 -0
- package/dist/types/packages/components/pie-modal/src/defs.d.ts +20 -0
- package/dist/types/packages/components/pie-modal/src/defs.d.ts.map +1 -1
- package/dist/types/packages/components/pie-modal/src/index.d.ts +34 -18
- package/dist/types/packages/components/pie-modal/src/index.d.ts.map +1 -1
- package/dist/types/packages/components/pie-modal/src/react.d.ts +0 -0
- package/dist/types/packages/components/pie-modal/src/react.d.ts.map +0 -0
- package/dist/types/react.d.ts +0 -0
- package/package.json +1 -1
- package/src/defs.ts +26 -0
- package/src/index.ts +93 -53
- package/src/modal.scss +5 -0
- package/test/component/pie-modal.spec.ts +289 -6
- package/test/helpers/index.ts +29 -0
- package/test/visual/pie-modal.spec.ts +80 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,UAAU,EAAW,cAAc,EACtC,MAAM,KAAK,CAAC;AAMb,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EACH,UAAU,EACV,aAAa,EAGb,KAAK,EACR,MAAM,QAAQ,CAAC;AAGhB,OAAO,EAAE,KAAK,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AAEjD,QAAA,MAAM,iBAAiB,cAAc,CAAC;;;;;AAEtC,qBAAa,QAAS,SAAQ,aAAoB;IAGvC,OAAO,EAAG,MAAM,CAAC;IAIjB,YAAY,EAAE,UAAU,CAAC,cAAc,CAAC,CAAQ;IAGhD,aAAa,UAAS;IAGtB,mBAAmB,UAAS;IAG5B,MAAM,UAAS;IAGf,6BAA6B,CAAC,EAAE,MAAM,CAAC;IAIvC,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAY;IAGvC,OAAO,CAAC,EAAE,iBAAiB,CAAC;;IAOhC,iBAAiB,IAAM,IAAI;IAM3B,oBAAoB,IAAM,IAAI;IAM9B,YAAY,CAAE,iBAAiB,EAAE,YAAY,CAAC,UAAU,CAAC,GAAI,IAAI;IASjE,OAAO,CAAE,iBAAiB,EAAE,YAAY,CAAC,UAAU,CAAC,GAAI,IAAI;IAI5D;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB,CAI9B;IAGF,OAAO,CAAC,kCAAkC;IAU1C,OAAO,CAAC,4BAA4B;IAYpC;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAQpB,MAAM;IA2BN;;;OAGG;IACH,OAAO,CAAC,yBAAyB,CAyB/B;IAEF;;;;;;;;;;OAUG;IACH,OAAO,CAAC,yBAAyB,CAO/B;IAEF;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB,CAKqC;IAG9D,MAAM,CAAC,MAAM,0BAAqB;CACrC;AAID,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,CAAC,iBAAiB,CAAC,EAAE,QAAQ,CAAC;KACjC;CACJ"}
|
|
File without changes
|
|
File without changes
|
package/dist/types/react.d.ts
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/src/defs.ts
CHANGED
|
@@ -6,14 +6,40 @@ export interface ModalProps {
|
|
|
6
6
|
* The text to display in the modal's heading.
|
|
7
7
|
*/
|
|
8
8
|
heading: string;
|
|
9
|
+
|
|
9
10
|
/**
|
|
10
11
|
* The HTML heading tag to use for the modal's heading. Can be h1-h6.
|
|
11
12
|
*/
|
|
12
13
|
headingLevel: typeof headingLevels[number];
|
|
14
|
+
|
|
13
15
|
/**
|
|
14
16
|
* When true, the modal will be open.
|
|
15
17
|
*/
|
|
16
18
|
isOpen: boolean;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* When set to `true`:
|
|
22
|
+
* 1. The close button within the modal will be visible.
|
|
23
|
+
* 2. The user can dismiss the modal via the ESCAPE key, clicking the backdrop
|
|
24
|
+
* or via a close button.
|
|
25
|
+
*
|
|
26
|
+
* When set to `false`:
|
|
27
|
+
* 1. The close button within the modal will be hidden.
|
|
28
|
+
* 2. The user can NOT dismiss the modal via the ESCAPE key or clicking the backdrop.
|
|
29
|
+
*
|
|
30
|
+
*/
|
|
31
|
+
isDismissible: boolean;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* This controls whether a *medium-sized* modal will cover the full width of the page when below the mid breakpoint.
|
|
35
|
+
*/
|
|
36
|
+
isFullWidthBelowMid: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The selector for the element that you would like focus to be returned to when the modal is closed, e.g., #skipToMain
|
|
40
|
+
*/
|
|
41
|
+
returnFocusAfterCloseSelector?: string;
|
|
42
|
+
|
|
17
43
|
/**
|
|
18
44
|
* The size of the modal; this controls how wide it will appear on the page.
|
|
19
45
|
*/
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
LitElement, nothing, TemplateResult, unsafeCSS,
|
|
3
|
+
} from 'lit';
|
|
2
4
|
import { html, unsafeStatic } from 'lit/static-html.js';
|
|
3
5
|
import { property, query } from 'lit/decorators.js';
|
|
4
6
|
import {
|
|
@@ -21,9 +23,6 @@ export { type ModalProps, headingLevels, sizes };
|
|
|
21
23
|
const componentSelector = 'pie-modal';
|
|
22
24
|
|
|
23
25
|
export class PieModal extends RtlMixin(LitElement) {
|
|
24
|
-
@property({ type: Boolean })
|
|
25
|
-
public isOpen = false;
|
|
26
|
-
|
|
27
26
|
@property({ type: String })
|
|
28
27
|
@requiredProperty(componentSelector)
|
|
29
28
|
public heading!: string;
|
|
@@ -32,6 +31,18 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
32
31
|
@validPropertyValues(componentSelector, headingLevels, 'h2')
|
|
33
32
|
public headingLevel: ModalProps['headingLevel'] = 'h2';
|
|
34
33
|
|
|
34
|
+
@property({ type: Boolean, reflect: true })
|
|
35
|
+
public isDismissible = false;
|
|
36
|
+
|
|
37
|
+
@property({ type: Boolean })
|
|
38
|
+
public isFullWidthBelowMid = false;
|
|
39
|
+
|
|
40
|
+
@property({ type: Boolean })
|
|
41
|
+
public isOpen = false;
|
|
42
|
+
|
|
43
|
+
@property()
|
|
44
|
+
public returnFocusAfterCloseSelector?: string;
|
|
45
|
+
|
|
35
46
|
@property()
|
|
36
47
|
@validPropertyValues(componentSelector, sizes, 'medium')
|
|
37
48
|
public size: ModalProps['size'] = 'medium';
|
|
@@ -44,8 +55,25 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
44
55
|
this.addEventListener('click', (event) => this._handleDialogLightDismiss(event));
|
|
45
56
|
}
|
|
46
57
|
|
|
58
|
+
connectedCallback () : void {
|
|
59
|
+
super.connectedCallback();
|
|
60
|
+
document.addEventListener(ON_MODAL_OPEN_EVENT, this._handleModalOpened.bind(this));
|
|
61
|
+
document.addEventListener(ON_MODAL_CLOSE_EVENT, this._handleModalClosed.bind(this));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
disconnectedCallback () : void {
|
|
65
|
+
document.removeEventListener(ON_MODAL_OPEN_EVENT, this._handleModalOpened.bind(this));
|
|
66
|
+
document.removeEventListener(ON_MODAL_CLOSE_EVENT, this._handleModalClosed.bind(this));
|
|
67
|
+
super.disconnectedCallback();
|
|
68
|
+
}
|
|
69
|
+
|
|
47
70
|
firstUpdated (changedProperties: DependentMap<ModalProps>) : void {
|
|
71
|
+
this._dialog?.addEventListener('cancel', (event) => this._handleDialogCancelEvent(event));
|
|
48
72
|
this._handleModalOpenStateOnFirstRender(changedProperties);
|
|
73
|
+
|
|
74
|
+
this._dialog?.addEventListener('close', () => {
|
|
75
|
+
this.isOpen = false;
|
|
76
|
+
});
|
|
49
77
|
}
|
|
50
78
|
|
|
51
79
|
updated (changedProperties: DependentMap<ModalProps>) : void {
|
|
@@ -57,9 +85,10 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
57
85
|
*/
|
|
58
86
|
private _handleModalOpened () : void {
|
|
59
87
|
disableBodyScroll(this);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
if (this._dialog?.hasAttribute('open') || !this._dialog?.isConnected) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// The ::backdrop pseudoelement is only shown if the modal is opened via JS
|
|
63
92
|
this._dialog?.showModal();
|
|
64
93
|
}
|
|
65
94
|
|
|
@@ -68,37 +97,29 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
68
97
|
*/
|
|
69
98
|
private _handleModalClosed () : void {
|
|
70
99
|
enableBodyScroll(this);
|
|
71
|
-
// Closes the native dialog element
|
|
72
100
|
this._dialog?.close();
|
|
101
|
+
this._returnFocus();
|
|
73
102
|
}
|
|
74
103
|
|
|
75
104
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
105
|
+
* Prevents the user from dismissing the dialog via the `cancel`
|
|
106
|
+
* event (ESC key) when `isDismissible` is set to false.
|
|
107
|
+
*
|
|
108
|
+
* @param {Event} event - The event object.
|
|
78
109
|
*/
|
|
79
|
-
private
|
|
80
|
-
this.
|
|
110
|
+
private _handleDialogCancelEvent = (event: Event) : void => {
|
|
111
|
+
if (!this.isDismissible) {
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
}
|
|
81
114
|
};
|
|
82
115
|
|
|
83
|
-
connectedCallback () : void {
|
|
84
|
-
super.connectedCallback();
|
|
85
|
-
document.addEventListener(ON_MODAL_OPEN_EVENT, this._handleModalOpened.bind(this));
|
|
86
|
-
document.addEventListener(ON_MODAL_CLOSE_EVENT, this._handleModalClosed.bind(this));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
disconnectedCallback () : void {
|
|
90
|
-
document.removeEventListener(ON_MODAL_OPEN_EVENT, this._handleModalOpened.bind(this));
|
|
91
|
-
document.removeEventListener(ON_MODAL_CLOSE_EVENT, this._handleModalClosed.bind(this));
|
|
92
|
-
super.disconnectedCallback();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
116
|
// Handles the value of the isOpen property on first render of the component
|
|
96
117
|
private _handleModalOpenStateOnFirstRender (changedProperties: DependentMap<ModalProps>) : void {
|
|
97
118
|
// This ensures if the modal is open on first render, the scroll lock and backdrop are applied
|
|
98
119
|
const previousValue = changedProperties.get('isOpen');
|
|
99
120
|
|
|
100
121
|
if (previousValue === undefined && this.isOpen) {
|
|
101
|
-
this.
|
|
122
|
+
this._dispatchModalCustomEvent(ON_MODAL_OPEN_EVENT);
|
|
102
123
|
}
|
|
103
124
|
}
|
|
104
125
|
|
|
@@ -108,18 +129,32 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
108
129
|
|
|
109
130
|
if (previousValue !== undefined) {
|
|
110
131
|
if (previousValue) {
|
|
111
|
-
this.
|
|
132
|
+
this._dispatchModalCustomEvent(ON_MODAL_CLOSE_EVENT);
|
|
112
133
|
} else {
|
|
113
|
-
this.
|
|
134
|
+
this._dispatchModalCustomEvent(ON_MODAL_OPEN_EVENT);
|
|
114
135
|
}
|
|
115
136
|
}
|
|
116
137
|
}
|
|
117
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Return focus to the specified element, providing the selector is valid
|
|
141
|
+
* and the chosen element can be found.
|
|
142
|
+
* Fails silently.
|
|
143
|
+
*/
|
|
144
|
+
private _returnFocus () : void {
|
|
145
|
+
const selector = this.returnFocusAfterCloseSelector?.trim();
|
|
146
|
+
|
|
147
|
+
if (selector) {
|
|
148
|
+
(document.querySelector(selector) as HTMLElement)?.focus();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
118
152
|
render () {
|
|
119
153
|
const {
|
|
120
154
|
heading,
|
|
121
155
|
headingLevel = 'h2',
|
|
122
156
|
size,
|
|
157
|
+
isFullWidthBelowMid,
|
|
123
158
|
} = this;
|
|
124
159
|
|
|
125
160
|
const headingTag = unsafeStatic(headingLevel);
|
|
@@ -127,14 +162,12 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
127
162
|
return html`
|
|
128
163
|
<dialog
|
|
129
164
|
id="dialog"
|
|
165
|
+
class="c-modal"
|
|
130
166
|
size="${size}"
|
|
131
|
-
|
|
167
|
+
?isFullWidthBelowMid=${isFullWidthBelowMid}>
|
|
132
168
|
<header>
|
|
133
169
|
<${headingTag} class="c-modal-heading">${heading}</${headingTag}>
|
|
134
|
-
|
|
135
|
-
@click="${this._triggerCloseModal}"
|
|
136
|
-
variant="ghost-secondary"
|
|
137
|
-
class="c-modal-closeBtn"></pie-icon-button>
|
|
170
|
+
${this.isDismissible ? this.renderCloseButton() : nothing}
|
|
138
171
|
</header>
|
|
139
172
|
<article class="c-modal-content">
|
|
140
173
|
<slot></slot>
|
|
@@ -144,10 +177,14 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
144
177
|
}
|
|
145
178
|
|
|
146
179
|
/**
|
|
147
|
-
* Dismisses the modal on backdrop click
|
|
148
|
-
*
|
|
180
|
+
* Dismisses the modal on backdrop click if `isDismissible` is `true`.
|
|
181
|
+
* @param {MouseEvent} event - the click event targetting the modal/backdrop
|
|
149
182
|
*/
|
|
150
183
|
private _handleDialogLightDismiss = (event: MouseEvent) : void => {
|
|
184
|
+
if (!this.isDismissible) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
151
188
|
const rect = this._dialog?.getBoundingClientRect();
|
|
152
189
|
|
|
153
190
|
const {
|
|
@@ -161,9 +198,9 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
161
198
|
}
|
|
162
199
|
|
|
163
200
|
const isClickOutsideDialog = event.clientY < top ||
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
201
|
+
event.clientY > bottom ||
|
|
202
|
+
event.clientX < left ||
|
|
203
|
+
event.clientX > right;
|
|
167
204
|
|
|
168
205
|
if (isClickOutsideDialog) {
|
|
169
206
|
this.isOpen = false;
|
|
@@ -171,13 +208,18 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
171
208
|
};
|
|
172
209
|
|
|
173
210
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
211
|
+
* Note: We should aim to have a shareable event helper system to allow
|
|
212
|
+
* us to share this across components in-future.
|
|
213
|
+
*
|
|
214
|
+
* Dispatch a custom event.
|
|
176
215
|
*
|
|
177
|
-
*
|
|
216
|
+
* To be used whenever we have behavioural events we want to
|
|
217
|
+
* bubble up through the modal.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} eventType
|
|
178
220
|
*/
|
|
179
|
-
private
|
|
180
|
-
const event = new CustomEvent(
|
|
221
|
+
private _dispatchModalCustomEvent = (eventType: string) : void => {
|
|
222
|
+
const event = new CustomEvent(eventType, {
|
|
181
223
|
bubbles: true,
|
|
182
224
|
composed: true,
|
|
183
225
|
});
|
|
@@ -186,19 +228,17 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
186
228
|
};
|
|
187
229
|
|
|
188
230
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
231
|
+
* Template for the close button element. Called within the
|
|
232
|
+
* main render function.
|
|
191
233
|
*
|
|
192
|
-
* @
|
|
234
|
+
* @private
|
|
193
235
|
*/
|
|
194
|
-
private
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
this.dispatchEvent(event);
|
|
201
|
-
};
|
|
236
|
+
private renderCloseButton = () : TemplateResult => html`
|
|
237
|
+
<pie-icon-button
|
|
238
|
+
@click="${() => { this.isOpen = false; }}"
|
|
239
|
+
variant="ghost-secondary"
|
|
240
|
+
class="c-modal-closeBtn"
|
|
241
|
+
data-test-id="modal-close-button"></pie-icon-button>`;
|
|
202
242
|
|
|
203
243
|
// Renders a `CSSResult` generated from SCSS by Vite
|
|
204
244
|
static styles = unsafeCSS(styles);
|