@justeattakeaway/pie-modal 0.6.0 → 0.7.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 +5 -10
- package/CHANGELOG.md +14 -0
- package/README.md +51 -24
- package/dist/index.js +239 -92
- package/dist/types/packages/components/pie-modal/src/defs.d.ts +13 -10
- 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 +35 -3
- package/dist/types/packages/components/pie-modal/src/index.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/defs.ts +14 -10
- package/src/index.ts +136 -17
- package/src/modal.scss +13 -2
- package/test/component/pie-modal.spec.ts +40 -17
- package/test/visual/pie-modal.spec.ts +50 -2
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
[
|
|
1
|
+
[4:04:52 PM] @custom-elements-manifest/analyzer: Created new manifest.
|
|
2
2
|
react wrapper has been added!
|
|
3
3
|
[36mvite v4.3.9 [32mbuilding for production...[36m[39m
|
|
4
4
|
transforming...
|
|
5
|
-
[32m✓[39m
|
|
5
|
+
[32m✓[39m 30 modules transformed.
|
|
6
6
|
rendering chunks...
|
|
7
7
|
computing gzip size...
|
|
8
|
-
[2mdist/[22m[36mindex.js [39m[1m[
|
|
8
|
+
[2mdist/[22m[36mindex.js [39m[1m[2m13.55 kB[22m[1m[22m[2m │ gzip: 4.16 kB[22m
|
|
9
9
|
[2mdist/[22m[36mreact.js [39m[1m[2m59.04 kB[22m[1m[22m[2m │ gzip: 15.92 kB[22m
|
|
10
10
|
[32m
|
|
11
11
|
[36m[vite:dts][32m Start generate declaration files...[39m
|
|
12
|
-
[
|
|
13
|
-
|
|
14
|
-
[7m27[0m _dialog: HTMLDialogElement;
|
|
15
|
-
[7m [0m [91m ~~~~~~~[0m
|
|
16
|
-
|
|
17
|
-
[32m✓ built in 28.25s[39m
|
|
18
|
-
[32m[36m[vite:dts][32m Declaration files built in 26028ms.
|
|
12
|
+
[32m✓ built in 28.39s[39m
|
|
13
|
+
[32m[36m[vite:dts][32m Declaration files built in 26000ms.
|
|
19
14
|
[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @justeattakeaway/pie-modal
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [Added] - Modal backdrop functionality ([#559](https://github.com/justeattakeaway/pie/pull/559)) by [@kevinrodrigues](https://github.com/kevinrodrigues)
|
|
8
|
+
|
|
9
|
+
- [Added] - Scroll locking to modal ([#564](https://github.com/justeattakeaway/pie/pull/564)) by [@jamieomaguire](https://github.com/jamieomaguire)
|
|
10
|
+
|
|
11
|
+
## 0.6.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [Changed] - Updated defs to use different array type syntax ([#566](https://github.com/justeattakeaway/pie/pull/566)) by [@ashleynolan](https://github.com/ashleynolan)
|
|
16
|
+
|
|
3
17
|
## 0.6.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -17,65 +17,92 @@
|
|
|
17
17
|
|
|
18
18
|
# pie-modal
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
`pie-modal` is a Web Component built using the Lit library. It offers a simple and accessible modal component for web applications, which uses the native HTML `dialog` element under the hood.
|
|
21
|
+
|
|
22
|
+
This component can be easily integrated into various frontend frameworks and customized through a set of properties.
|
|
21
23
|
|
|
22
24
|
## Local development
|
|
23
25
|
|
|
24
|
-
Install dependencies
|
|
25
|
-
|
|
26
|
+
Install the dependencies. Note that this, and the following commands below, should be run from the **root of the monorepo**:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
26
29
|
yarn
|
|
27
30
|
```
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
```
|
|
31
|
-
cd packages/components/pie-modal
|
|
32
|
-
yarn build
|
|
33
|
-
```
|
|
32
|
+
To build the `pie-modal` package, run the following command:
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
```bash
|
|
35
|
+
yarn build --filter=pie-modal
|
|
36
36
|
```
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
If you'd like to develop using the component storybook, then you should build the component in `watch` mode, and run storybook in a separate terminal tab:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
yarn watch --filter=pie-modal
|
|
42
|
+
|
|
43
|
+
# in a separate terminal tab, run
|
|
44
|
+
yarn dev --filter=pie-storybook
|
|
38
45
|
```
|
|
39
46
|
|
|
40
47
|
### Importing the component
|
|
41
48
|
|
|
42
|
-
```
|
|
49
|
+
```js
|
|
43
50
|
// default
|
|
44
|
-
import { PieModal } from '@justeattakeaway/pie-
|
|
51
|
+
import { PieModal } from '@justeattakeaway/pie-modal';
|
|
45
52
|
|
|
46
53
|
// react
|
|
47
|
-
import { PieModal } from '@justeattakeaway/pie-
|
|
54
|
+
import { PieModal } from '@justeattakeaway/pie-modal/dist/react';
|
|
48
55
|
```
|
|
49
56
|
|
|
50
57
|
## Props
|
|
51
58
|
|
|
52
|
-
| Property
|
|
53
|
-
|
|
54
|
-
| isOpen
|
|
55
|
-
| heading*
|
|
56
|
-
| headingLevel | `String`
|
|
59
|
+
| Property | Type | Default | Description |
|
|
60
|
+
|----------------|-----------|---------|-------------------------------------------------------|
|
|
61
|
+
| isOpen | `Boolean` | `false` | Controls if the modal element is open or closed |
|
|
62
|
+
| heading* | `String` | - | Sets the heading of the modal |
|
|
63
|
+
| headingLevel | `String` | `h2` | Allows you to set the heading tag (from `h1` to `h6`) |
|
|
64
|
+
|
|
57
65
|
|
|
66
|
+
In your markup or JSX, you can then use these to set the properties for the `pie-modal` component:
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
<!-- Native HTML -->
|
|
70
|
+
<pie-modal heading='My Awesome Heading' headingLevel='h3'>Click me!</pie-modal>
|
|
71
|
+
|
|
72
|
+
<!-- JSX -->
|
|
73
|
+
<PieModal heading='My Awesome Heading' headingLevel='h3'>Click me!</PieModal>
|
|
74
|
+
```
|
|
58
75
|
|
|
59
76
|
## Testing
|
|
60
77
|
|
|
61
|
-
###
|
|
78
|
+
### Browser tests
|
|
79
|
+
|
|
80
|
+
To run the browser tests, run the following command from the root of the monorepo:
|
|
62
81
|
|
|
63
|
-
|
|
82
|
+
```bash
|
|
83
|
+
yarn test:browsers --filter=pie-modal
|
|
64
84
|
```
|
|
85
|
+
|
|
86
|
+
### Visual tests
|
|
87
|
+
|
|
88
|
+
To run the visual regression tests, run the following command from the root of the monorepo:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
65
91
|
yarn test:visual --filter=pie-modal
|
|
66
92
|
```
|
|
67
93
|
|
|
68
94
|
Note: To run these locally, you will need to ensure that any environment variables required are set up on your machine to mirror those on CI (such as Percy tokens). How you achieve this will differ between operating systems.
|
|
69
95
|
|
|
70
|
-
|
|
96
|
+
#### Setup via bash
|
|
71
97
|
|
|
72
|
-
```
|
|
98
|
+
```bash
|
|
73
99
|
export PERCY_TOKEN_PIE_MODAL=abcde
|
|
74
100
|
```
|
|
75
101
|
|
|
76
|
-
|
|
102
|
+
#### Setup via package.json
|
|
77
103
|
|
|
78
104
|
Under scripts `test:visual` replace the environment variable with the below:
|
|
79
105
|
|
|
80
|
-
```
|
|
106
|
+
```bash
|
|
81
107
|
PERCY_TOKEN_PIE_MODAL=abcde
|
|
108
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { unsafeCSS as
|
|
2
|
-
import { unsafeStatic as
|
|
3
|
-
import { property as
|
|
4
|
-
import { property as
|
|
5
|
-
var
|
|
6
|
-
for (var
|
|
7
|
-
(
|
|
8
|
-
return
|
|
1
|
+
import { unsafeCSS as T, LitElement as $, html as w } from "lit";
|
|
2
|
+
import { unsafeStatic as D, html as z } from "lit/static-html.js";
|
|
3
|
+
import { property as u, query as B } from "lit/decorators.js";
|
|
4
|
+
import { property as k } from "lit/decorators/property.js";
|
|
5
|
+
var V = Object.defineProperty, R = Object.getOwnPropertyDescriptor, A = (i, e, t, n) => {
|
|
6
|
+
for (var o = n > 1 ? void 0 : n ? R(e, t) : e, r = i.length - 1, d; r >= 0; r--)
|
|
7
|
+
(d = i[r]) && (o = (n ? d(e, t, o) : d(o)) || o);
|
|
8
|
+
return n && o && V(e, t, o), o;
|
|
9
9
|
};
|
|
10
|
-
const
|
|
11
|
-
class
|
|
10
|
+
const W = (i) => {
|
|
11
|
+
class e extends i {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments), this.dir = "";
|
|
14
14
|
}
|
|
@@ -22,135 +22,282 @@ const O = (r) => {
|
|
|
22
22
|
* will not be reactive and is only computed once
|
|
23
23
|
*/
|
|
24
24
|
get isRTL() {
|
|
25
|
-
var
|
|
26
|
-
return this.dir === "ltr" ? !1 : this.dir === "rtl" || ((
|
|
25
|
+
var n;
|
|
26
|
+
return this.dir === "ltr" ? !1 : this.dir === "rtl" || ((n = window == null ? void 0 : window.getComputedStyle(this)) == null ? void 0 : n.direction) === "rtl";
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
const
|
|
34
|
-
Object.defineProperty(o,
|
|
29
|
+
return A([
|
|
30
|
+
k({ type: String })
|
|
31
|
+
], e.prototype, "dir", 2), e;
|
|
32
|
+
}, j = (i, e, t) => function(o, r) {
|
|
33
|
+
const d = `#${r}`;
|
|
34
|
+
Object.defineProperty(o, r, {
|
|
35
35
|
get() {
|
|
36
|
-
return this[
|
|
36
|
+
return this[d];
|
|
37
37
|
},
|
|
38
|
-
set(
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
`<${
|
|
42
|
-
`Must be one of: ${
|
|
43
|
-
`Falling back to default value: "${
|
|
44
|
-
), this[
|
|
38
|
+
set(f) {
|
|
39
|
+
const h = this[d];
|
|
40
|
+
e.includes(f) ? this[d] = f : (console.error(
|
|
41
|
+
`<${i}> Invalid value "${f}" provided for property "${r}".`,
|
|
42
|
+
`Must be one of: ${e.join(" | ")}.`,
|
|
43
|
+
`Falling back to default value: "${t}"`
|
|
44
|
+
), this[d] = t), this.requestUpdate(r, h);
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
-
},
|
|
47
|
+
}, q = (i) => function(t, n) {
|
|
48
48
|
const o = `#${n}`;
|
|
49
49
|
Object.defineProperty(t, n, {
|
|
50
50
|
get() {
|
|
51
51
|
return this[o];
|
|
52
52
|
},
|
|
53
|
-
set(
|
|
54
|
-
const
|
|
55
|
-
(
|
|
53
|
+
set(r) {
|
|
54
|
+
const d = this[o];
|
|
55
|
+
(r == null || typeof r == "string" && r.trim() === "") && console.error(`<${i}> Missing required attribute "${n}"`), this[o] = r, this.requestUpdate(n, d);
|
|
56
56
|
}
|
|
57
57
|
});
|
|
58
|
-
},
|
|
58
|
+
}, I = `.c-webComponentTestWrapper{padding-block:var(--dt-spacing-c);padding-inline:var(--dt-spacing-e);font-family:var(--dt-font-interactive-m-family);font-size:calc(var(--dt-font-size-20) * 1px);border:1px solid var(--dt-color-background-dark);display:grid;grid-template-columns:1fr 1fr}.c-webComponentTestWrapper-label{margin-block:var(--dt-spacing-c)}.c-webComponentTestWrapper-slot{padding:var(--dt-spacing-c);border:1px dashed var(--dt-color-background-dark);grid-column:1/3;margin-block-start:var(--dt-spacing-c)}
|
|
59
59
|
`;
|
|
60
|
-
var
|
|
61
|
-
for (var
|
|
62
|
-
(
|
|
63
|
-
return
|
|
60
|
+
var F = Object.defineProperty, H = Object.getOwnPropertyDescriptor, E = (i, e, t, n) => {
|
|
61
|
+
for (var o = n > 1 ? void 0 : n ? H(e, t) : e, r = i.length - 1, d; r >= 0; r--)
|
|
62
|
+
(d = i[r]) && (o = (n ? d(e, t, o) : d(o)) || o);
|
|
63
|
+
return n && o && F(e, t, o), o;
|
|
64
64
|
};
|
|
65
|
-
class
|
|
65
|
+
class b extends $ {
|
|
66
66
|
constructor() {
|
|
67
|
-
super(...arguments), this.propKeyValues = "";
|
|
67
|
+
super(...arguments), this.propKeyValues = "", this.pageMode = !1;
|
|
68
68
|
}
|
|
69
69
|
// Renders a string such as 'size: small, isFullWidth: true'
|
|
70
70
|
// as HTML such as:
|
|
71
71
|
// <p class="c-webComponentTestWrapper-label"><b>size</b>: <code>small</code></p>
|
|
72
72
|
// <p class="c-webComponentTestWrapper-label"><b>isFullWidth</b>: <code>true</code></p>
|
|
73
73
|
_renderPropKeyValues() {
|
|
74
|
-
return this.propKeyValues.split(",").map((
|
|
75
|
-
const [
|
|
76
|
-
return
|
|
74
|
+
return this.propKeyValues.split(",").map((e) => {
|
|
75
|
+
const [t, n] = e.split(":");
|
|
76
|
+
return w`<p class="c-webComponentTestWrapper-label"><b>${t}</b>: <code>${n}</code></p>`;
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
|
-
// eslint-disable-next-line class-methods-use-this
|
|
80
79
|
render() {
|
|
81
|
-
return
|
|
80
|
+
return this.pageMode ? w`
|
|
81
|
+
<div>
|
|
82
|
+
<slot name="component"></slot>
|
|
83
|
+
<slot name="pageMarkup"></slot>
|
|
84
|
+
</div>` : w`
|
|
82
85
|
<div class="c-webComponentTestWrapper">
|
|
83
86
|
${this._renderPropKeyValues()}
|
|
84
87
|
<div class="c-webComponentTestWrapper-slot">
|
|
85
|
-
<slot></slot>
|
|
88
|
+
<slot name="component"></slot>
|
|
86
89
|
</div>
|
|
87
90
|
</div>`;
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
|
-
|
|
93
|
+
b.styles = T(I);
|
|
94
|
+
E([
|
|
95
|
+
u({ type: String })
|
|
96
|
+
], b.prototype, "propKeyValues", 2);
|
|
91
97
|
E([
|
|
92
|
-
|
|
93
|
-
],
|
|
94
|
-
const
|
|
95
|
-
customElements.get(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
var
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
u({ type: Boolean })
|
|
99
|
+
], b.prototype, "pageMode", 2);
|
|
100
|
+
const C = "web-component-test-wrapper";
|
|
101
|
+
customElements.get(C) || customElements.define(C, b);
|
|
102
|
+
function N(i) {
|
|
103
|
+
if (Array.isArray(i)) {
|
|
104
|
+
for (var e = 0, t = Array(i.length); e < i.length; e++)
|
|
105
|
+
t[e] = i[e];
|
|
106
|
+
return t;
|
|
107
|
+
} else
|
|
108
|
+
return Array.from(i);
|
|
109
|
+
}
|
|
110
|
+
var P = !1;
|
|
111
|
+
if (typeof window < "u") {
|
|
112
|
+
var M = {
|
|
113
|
+
get passive() {
|
|
114
|
+
P = !0;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
window.addEventListener("testPassive", null, M), window.removeEventListener("testPassive", null, M);
|
|
118
|
+
}
|
|
119
|
+
var v = typeof window < "u" && window.navigator && window.navigator.platform && (/iP(ad|hone|od)/.test(window.navigator.platform) || window.navigator.platform === "MacIntel" && window.navigator.maxTouchPoints > 1), s = [], m = !1, x = -1, c = void 0, a = void 0, p = void 0, L = function(e) {
|
|
120
|
+
return s.some(function(t) {
|
|
121
|
+
return !!(t.options.allowTouchMove && t.options.allowTouchMove(e));
|
|
122
|
+
});
|
|
123
|
+
}, g = function(e) {
|
|
124
|
+
var t = e || window.event;
|
|
125
|
+
return L(t.target) || t.touches.length > 1 ? !0 : (t.preventDefault && t.preventDefault(), !1);
|
|
126
|
+
}, Y = function(e) {
|
|
127
|
+
if (p === void 0) {
|
|
128
|
+
var t = !!e && e.reserveScrollBarGap === !0, n = window.innerWidth - document.documentElement.clientWidth;
|
|
129
|
+
if (t && n > 0) {
|
|
130
|
+
var o = parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right"), 10);
|
|
131
|
+
p = document.body.style.paddingRight, document.body.style.paddingRight = o + n + "px";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
c === void 0 && (c = document.body.style.overflow, document.body.style.overflow = "hidden");
|
|
135
|
+
}, U = function() {
|
|
136
|
+
p !== void 0 && (document.body.style.paddingRight = p, p = void 0), c !== void 0 && (document.body.style.overflow = c, c = void 0);
|
|
137
|
+
}, X = function() {
|
|
138
|
+
return window.requestAnimationFrame(function() {
|
|
139
|
+
if (a === void 0) {
|
|
140
|
+
a = {
|
|
141
|
+
position: document.body.style.position,
|
|
142
|
+
top: document.body.style.top,
|
|
143
|
+
left: document.body.style.left
|
|
144
|
+
};
|
|
145
|
+
var e = window, t = e.scrollY, n = e.scrollX, o = e.innerHeight;
|
|
146
|
+
document.body.style.position = "fixed", document.body.style.top = -t, document.body.style.left = -n, setTimeout(function() {
|
|
147
|
+
return window.requestAnimationFrame(function() {
|
|
148
|
+
var r = o - window.innerHeight;
|
|
149
|
+
r && t >= o && (document.body.style.top = -(t + r));
|
|
150
|
+
});
|
|
151
|
+
}, 300);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}, K = function() {
|
|
155
|
+
if (a !== void 0) {
|
|
156
|
+
var e = -parseInt(document.body.style.top, 10), t = -parseInt(document.body.style.left, 10);
|
|
157
|
+
document.body.style.position = a.position, document.body.style.top = a.top, document.body.style.left = a.left, window.scrollTo(t, e), a = void 0;
|
|
158
|
+
}
|
|
159
|
+
}, G = function(e) {
|
|
160
|
+
return e ? e.scrollHeight - e.scrollTop <= e.clientHeight : !1;
|
|
161
|
+
}, J = function(e, t) {
|
|
162
|
+
var n = e.targetTouches[0].clientY - x;
|
|
163
|
+
return L(e.target) ? !1 : t && t.scrollTop === 0 && n > 0 || G(t) && n < 0 ? g(e) : (e.stopPropagation(), !0);
|
|
164
|
+
}, Q = function(e, t) {
|
|
165
|
+
if (!e) {
|
|
166
|
+
console.error("disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll on IOS devices.");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (!s.some(function(o) {
|
|
170
|
+
return o.targetElement === e;
|
|
171
|
+
})) {
|
|
172
|
+
var n = {
|
|
173
|
+
targetElement: e,
|
|
174
|
+
options: t || {}
|
|
175
|
+
};
|
|
176
|
+
s = [].concat(N(s), [n]), v ? X() : Y(t), v && (e.ontouchstart = function(o) {
|
|
177
|
+
o.targetTouches.length === 1 && (x = o.targetTouches[0].clientY);
|
|
178
|
+
}, e.ontouchmove = function(o) {
|
|
179
|
+
o.targetTouches.length === 1 && J(o, e);
|
|
180
|
+
}, m || (document.addEventListener("touchmove", g, P ? { passive: !1 } : void 0), m = !0));
|
|
181
|
+
}
|
|
182
|
+
}, Z = function(e) {
|
|
183
|
+
if (!e) {
|
|
184
|
+
console.error("enableBodyScroll unsuccessful - targetElement must be provided when calling enableBodyScroll on IOS devices.");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
s = s.filter(function(t) {
|
|
188
|
+
return t.targetElement !== e;
|
|
189
|
+
}), v && (e.ontouchstart = null, e.ontouchmove = null, m && s.length === 0 && (document.removeEventListener("touchmove", g, P ? { passive: !1 } : void 0), m = !1)), v ? K() : U();
|
|
102
190
|
};
|
|
103
|
-
const
|
|
104
|
-
|
|
191
|
+
const ee = `.c-modal{--modal-size-s: 450px;--modal-size-m: 600px;--modal-size-l: 1080px;--modal-border-radius: var(--dt-radius-rounded-d);--modal-font: var(--dt-font-interactive-m-family);--modal-bg-color: var(--dt-color-container-default);--modal-elevation: var(--dt-elevation-04);border-radius:var(--modal-border-radius);border:none;font-family:var(--modal-font);background-color:var(--modal-bg-color);padding:0;inline-size:var(--modal-size-m);box-shadow:var(--modal-elevation)}.c-modal::backdrop{background:rgba(0,0,0,.5)}.c-modal .c-modal-heading{--modal-header-font-size: calc(var(--dt-font-heading-m-size--wide) * 1px);--modal-header-font-line-height: calc(var(--dt-font-heading-m-line-height--wide) * 1px);--modal-header-font-weight: var(--dt-font-heading-m-weight);--modal-header-padding: var(--dt-spacing-e);--modal-header-padding-block-end: var(--dt-spacing-d);font-size:var(--modal-header-font-size);line-height:var(--modal-header-font-line-height);font-weight:var(--modal-header-font-weight);margin:0;padding-block:var(--modal-header-padding) var(--modal-header-padding-block-end);padding-inline:var(--modal-header-padding)}.c-modal .c-modal-content{--modal-content-font-size: calc(var(--dt-font-size-16) * 1px);--modal-content-font-weight: var(--dt-font-weight-regular);--modal-content-line-height: calc(var(--dt-font-size-16-line-height) * 1px);--modal-content-padding: var(--dt-spacing-e);--modal-content-padding-block-start: var(--dt-spacing-a);font-size:var(--modal-content-font-size);line-height:var(--modal-content-line-height);font-weight:var(--modal-content-font-weight);padding-block:var(--modal-content-padding-block-start) var(--modal-content-padding);padding-inline:var(--modal-content-padding);overflow-y:scroll;max-block-size:300px}.c-modal .c-modal-closeBtn{position:absolute;inset-inline-end:var(--dt-spacing-d);inset-block-start:var(--dt-spacing-d)}
|
|
192
|
+
`, te = ["h1", "h2", "h3", "h4", "h5", "h6"], _ = "pie-modal-close", O = "pie-modal-open";
|
|
193
|
+
var oe = Object.defineProperty, ne = Object.getOwnPropertyDescriptor, y = (i, e, t, n) => {
|
|
194
|
+
for (var o = n > 1 ? void 0 : n ? ne(e, t) : e, r = i.length - 1, d; r >= 0; r--)
|
|
195
|
+
(d = i[r]) && (o = (n ? d(e, t, o) : d(o)) || o);
|
|
196
|
+
return n && o && oe(e, t, o), o;
|
|
197
|
+
};
|
|
198
|
+
const S = "pie-modal";
|
|
199
|
+
class l extends W($) {
|
|
105
200
|
constructor() {
|
|
106
|
-
super(
|
|
107
|
-
this.
|
|
108
|
-
}, this.
|
|
109
|
-
|
|
110
|
-
this.
|
|
111
|
-
|
|
201
|
+
super(), this.isOpen = !1, this.headingLevel = "h2", this._triggerCloseModal = () => {
|
|
202
|
+
this.isOpen = !1;
|
|
203
|
+
}, this._handleDialogLightDismiss = (e) => {
|
|
204
|
+
var h;
|
|
205
|
+
const t = (h = this._dialog) == null ? void 0 : h.getBoundingClientRect(), {
|
|
206
|
+
top: n = 0,
|
|
207
|
+
bottom: o = 0,
|
|
208
|
+
left: r = 0,
|
|
209
|
+
right: d = 0
|
|
210
|
+
} = t || {};
|
|
211
|
+
if (n === 0 && o === 0 && r === 0 && d === 0)
|
|
212
|
+
return;
|
|
213
|
+
(e.clientY < n || e.clientY > o || e.clientX < r || e.clientX > d) && (this.isOpen = !1);
|
|
214
|
+
}, this._dispatchModalCloseEvent = () => {
|
|
215
|
+
const e = new CustomEvent(_, {
|
|
216
|
+
bubbles: !0,
|
|
217
|
+
composed: !0
|
|
218
|
+
});
|
|
219
|
+
this.dispatchEvent(e);
|
|
220
|
+
}, this._dispatchModalOpenEvent = () => {
|
|
221
|
+
const e = new CustomEvent(O, {
|
|
222
|
+
bubbles: !0,
|
|
223
|
+
composed: !0
|
|
224
|
+
});
|
|
225
|
+
this.dispatchEvent(e);
|
|
226
|
+
}, this.addEventListener("click", (e) => this._handleDialogLightDismiss(e));
|
|
227
|
+
}
|
|
228
|
+
firstUpdated(e) {
|
|
229
|
+
this._handleModalOpenStateOnFirstRender(e);
|
|
230
|
+
}
|
|
231
|
+
updated(e) {
|
|
232
|
+
this._handleModalOpenStateChanged(e);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Opens the dialog element and disables page scrolling
|
|
236
|
+
*/
|
|
237
|
+
_handleModalOpened() {
|
|
238
|
+
var e;
|
|
239
|
+
Q(this), (e = this._dialog) == null || e.showModal();
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Closes the dialog element and re-enables page scrolling
|
|
243
|
+
*/
|
|
244
|
+
_handleModalClosed() {
|
|
245
|
+
var e;
|
|
246
|
+
Z(this), (e = this._dialog) == null || e.close();
|
|
247
|
+
}
|
|
248
|
+
connectedCallback() {
|
|
249
|
+
super.connectedCallback(), document.addEventListener(O, this._handleModalOpened.bind(this)), document.addEventListener(_, this._handleModalClosed.bind(this));
|
|
250
|
+
}
|
|
251
|
+
disconnectedCallback() {
|
|
252
|
+
document.removeEventListener(O, this._handleModalOpened.bind(this)), document.removeEventListener(_, this._handleModalClosed.bind(this)), super.disconnectedCallback();
|
|
253
|
+
}
|
|
254
|
+
// Handles the value of the isOpen property on first render of the component
|
|
255
|
+
_handleModalOpenStateOnFirstRender(e) {
|
|
256
|
+
e.get("isOpen") === void 0 && this.isOpen && this._dispatchModalOpenEvent();
|
|
257
|
+
}
|
|
258
|
+
// Handles changes to the modal isOpen property by dispatching any appropriate events
|
|
259
|
+
_handleModalOpenStateChanged(e) {
|
|
260
|
+
const t = e.get("isOpen");
|
|
261
|
+
t !== void 0 && (t ? this._dispatchModalCloseEvent() : this._dispatchModalOpenEvent());
|
|
112
262
|
}
|
|
113
263
|
render() {
|
|
114
264
|
const {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<dialog id="dialog" class="c-modal" ?open="${t}">
|
|
265
|
+
heading: e,
|
|
266
|
+
headingLevel: t = "h2"
|
|
267
|
+
} = this, n = D(t);
|
|
268
|
+
return z`
|
|
269
|
+
<dialog id="dialog" class="c-modal">
|
|
121
270
|
<header>
|
|
122
|
-
<${
|
|
271
|
+
<${n} class="c-modal-heading">${e}</${n}>
|
|
123
272
|
<pie-icon-button
|
|
124
|
-
@click="${this.
|
|
273
|
+
@click="${this._triggerCloseModal}"
|
|
125
274
|
variant="ghost-tertiary"
|
|
126
275
|
class="c-modal-closeBtn"></pie-icon-button>
|
|
127
276
|
</header>
|
|
128
|
-
<article>
|
|
129
|
-
<
|
|
130
|
-
<slot></slot>
|
|
131
|
-
</div>
|
|
277
|
+
<article class="c-modal-content">
|
|
278
|
+
<slot></slot>
|
|
132
279
|
</article>
|
|
133
280
|
</dialog>
|
|
134
281
|
`;
|
|
135
282
|
}
|
|
136
283
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
],
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
],
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
],
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
],
|
|
152
|
-
customElements.define(
|
|
284
|
+
l.styles = T(ee);
|
|
285
|
+
y([
|
|
286
|
+
u({ type: Boolean })
|
|
287
|
+
], l.prototype, "isOpen", 2);
|
|
288
|
+
y([
|
|
289
|
+
u({ type: String }),
|
|
290
|
+
q(S)
|
|
291
|
+
], l.prototype, "heading", 2);
|
|
292
|
+
y([
|
|
293
|
+
u(),
|
|
294
|
+
j(S, te, "h2")
|
|
295
|
+
], l.prototype, "headingLevel", 2);
|
|
296
|
+
y([
|
|
297
|
+
B("dialog")
|
|
298
|
+
], l.prototype, "_dialog", 2);
|
|
299
|
+
customElements.define(S, l);
|
|
153
300
|
export {
|
|
154
|
-
|
|
155
|
-
|
|
301
|
+
l as PieModal,
|
|
302
|
+
te as headingLevels
|
|
156
303
|
};
|
|
@@ -1,27 +1,30 @@
|
|
|
1
|
+
export declare const headingLevels: readonly ["h1", "h2", "h3", "h4", "h5", "h6"];
|
|
1
2
|
export interface ModalProps {
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* (Required) The text to display in the modal's heading.
|
|
4
5
|
*/
|
|
5
6
|
heading: string;
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
* @default h2
|
|
8
|
+
* (Optional) The HTML heading tag to use for the modal's heading. Can be H1-H6.
|
|
9
|
+
* @default "h2"
|
|
9
10
|
*/
|
|
10
|
-
headingLevel:
|
|
11
|
+
headingLevel: typeof headingLevels[number];
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
+
* (Optional) When true, the modal will be open.
|
|
13
14
|
* @default false
|
|
14
15
|
*/
|
|
15
|
-
isOpen
|
|
16
|
+
isOpen: boolean;
|
|
16
17
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Modal heading levels/tags
|
|
19
|
-
*/
|
|
20
|
-
export declare const headingLevels: ModalProps['headingLevel'][];
|
|
21
18
|
/**
|
|
22
19
|
* Event name for when the modal is closed.
|
|
23
20
|
*
|
|
24
21
|
* @constant
|
|
25
22
|
*/
|
|
26
23
|
export declare const ON_MODAL_CLOSE_EVENT = "pie-modal-close";
|
|
24
|
+
/**
|
|
25
|
+
* Event name for when the modal is opened.
|
|
26
|
+
*
|
|
27
|
+
* @constant
|
|
28
|
+
*/
|
|
29
|
+
export declare const ON_MODAL_OPEN_EVENT = "pie-modal-open";
|
|
27
30
|
//# sourceMappingURL=defs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defs.d.ts","sourceRoot":"","sources":["../../../src/defs.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACvB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"defs.d.ts","sourceRoot":"","sources":["../../../src/defs.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,+CAAgD,CAAC;AAE3E,MAAM,WAAW,UAAU;IACvB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,YAAY,EAAE,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;IAC3C;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,mBAAmB,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
+
import type { DependentMap } from '@justeattakeaway/pie-webc-core';
|
|
2
3
|
import { ModalProps, headingLevels } from './defs';
|
|
3
4
|
export { type ModalProps, headingLevels };
|
|
4
5
|
declare const componentSelector = "pie-modal";
|
|
@@ -10,16 +11,47 @@ export declare class PieModal extends PieModal_base {
|
|
|
10
11
|
isOpen: boolean;
|
|
11
12
|
heading: string;
|
|
12
13
|
headingLevel: ModalProps['headingLevel'];
|
|
13
|
-
_dialog
|
|
14
|
+
_dialog?: HTMLDialogElement;
|
|
15
|
+
constructor();
|
|
16
|
+
firstUpdated(changedProperties: DependentMap<ModalProps>): void;
|
|
17
|
+
updated(changedProperties: DependentMap<ModalProps>): void;
|
|
18
|
+
/**
|
|
19
|
+
* Opens the dialog element and disables page scrolling
|
|
20
|
+
*/
|
|
21
|
+
private _handleModalOpened;
|
|
22
|
+
/**
|
|
23
|
+
* Closes the dialog element and re-enables page scrolling
|
|
24
|
+
*/
|
|
25
|
+
private _handleModalClosed;
|
|
26
|
+
/**
|
|
27
|
+
* This is only to be used inside the component template as direct property
|
|
28
|
+
* reassignment is not allowed.
|
|
29
|
+
*/
|
|
30
|
+
private _triggerCloseModal;
|
|
31
|
+
connectedCallback(): void;
|
|
32
|
+
disconnectedCallback(): void;
|
|
33
|
+
private _handleModalOpenStateOnFirstRender;
|
|
34
|
+
private _handleModalOpenStateChanged;
|
|
14
35
|
render(): import("lit-html").TemplateResult;
|
|
15
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Dismisses the modal on backdrop click
|
|
38
|
+
*
|
|
39
|
+
*/
|
|
40
|
+
private _handleDialogLightDismiss;
|
|
16
41
|
/**
|
|
17
42
|
* Dispatch `ON_MODAL_CLOSE_EVENT` event.
|
|
18
43
|
* To be used whenever we close the modal.
|
|
19
44
|
*
|
|
20
45
|
* @event
|
|
21
46
|
*/
|
|
22
|
-
|
|
47
|
+
private _dispatchModalCloseEvent;
|
|
48
|
+
/**
|
|
49
|
+
* Dispatch `ON_MODAL_OPEN_EVENT` event.
|
|
50
|
+
* To be used whenever we open the modal.
|
|
51
|
+
*
|
|
52
|
+
* @event
|
|
53
|
+
*/
|
|
54
|
+
private _dispatchModalOpenEvent;
|
|
23
55
|
static styles: import("lit").CSSResult;
|
|
24
56
|
}
|
|
25
57
|
declare global {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAM5C,OAAO,EAAE,UAAU,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAM5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EACH,UAAU,EAAE,aAAa,EAC5B,MAAM,QAAQ,CAAC;AAGhB,OAAO,EAAE,KAAK,UAAU,EAAE,aAAa,EAAE,CAAC;AAE1C,QAAA,MAAM,iBAAiB,cAAc,CAAC;;;;;AAEtC,qBAAa,QAAS,SAAQ,aAAoB;IAE1C,MAAM,UAAS;IAIf,OAAO,EAAG,MAAM,CAAC;IAIjB,YAAY,EAAE,UAAU,CAAC,cAAc,CAAC,CAAQ;IAGhD,OAAO,CAAC,EAAE,iBAAiB,CAAC;;IAOhC,YAAY,CAAE,iBAAiB,EAAE,YAAY,CAAC,UAAU,CAAC,GAAI,IAAI;IAIjE,OAAO,CAAE,iBAAiB,EAAE,YAAY,CAAC,UAAU,CAAC,GAAI,IAAI;IAI5D;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;;OAGG;IACH,OAAO,CAAC,kBAAkB,CAExB;IAEF,iBAAiB,IAAM,IAAI;IAM3B,oBAAoB,IAAM,IAAI;IAO9B,OAAO,CAAC,kCAAkC;IAU1C,OAAO,CAAC,4BAA4B;IAYpC,MAAM;IAwBN;;;OAGG;IACH,OAAO,CAAC,yBAAyB,CAqB/B;IAEF;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB,CAO9B;IAEF;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB,CAO7B;IAGF,MAAM,CAAC,MAAM,0BAAqB;CACrC;AAID,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,CAAC,iBAAiB,CAAC,EAAE,QAAQ,CAAC;KACjC;CACJ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justeattakeaway/pie-modal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "PIE design system modal built using web components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,11 @@
|
|
|
22
22
|
"license": "Apache-2.0",
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@justeattakeaway/pie-components-config": "workspace:*",
|
|
25
|
-
"@justeattakeaway/pie-webc-core": "workspace:*"
|
|
25
|
+
"@justeattakeaway/pie-webc-core": "workspace:*",
|
|
26
|
+
"@types/body-scroll-lock": "3.1.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"body-scroll-lock": "4.0.0-beta.0"
|
|
26
30
|
},
|
|
27
31
|
"volta": {
|
|
28
32
|
"extends": "../../../package.json"
|
package/src/defs.ts
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
|
+
export const headingLevels = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;
|
|
2
|
+
|
|
1
3
|
export interface ModalProps {
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
5
|
+
* (Required) The text to display in the modal's heading.
|
|
4
6
|
*/
|
|
5
7
|
heading: string;
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
8
|
-
* @default h2
|
|
9
|
+
* (Optional) The HTML heading tag to use for the modal's heading. Can be H1-H6.
|
|
10
|
+
* @default "h2"
|
|
9
11
|
*/
|
|
10
|
-
headingLevel:
|
|
12
|
+
headingLevel: typeof headingLevels[number];
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
14
|
+
* (Optional) When true, the modal will be open.
|
|
13
15
|
* @default false
|
|
14
16
|
*/
|
|
15
|
-
isOpen
|
|
17
|
+
isOpen: boolean;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
|
-
*
|
|
21
|
+
* Event name for when the modal is closed.
|
|
22
|
+
*
|
|
23
|
+
* @constant
|
|
20
24
|
*/
|
|
21
|
-
export const
|
|
25
|
+
export const ON_MODAL_CLOSE_EVENT = 'pie-modal-close';
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
|
-
* Event name for when the modal is
|
|
28
|
+
* Event name for when the modal is opened.
|
|
25
29
|
*
|
|
26
30
|
* @constant
|
|
27
31
|
*/
|
|
28
|
-
export const
|
|
32
|
+
export const ON_MODAL_OPEN_EVENT = 'pie-modal-open';
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { LitElement, unsafeCSS } from 'lit';
|
|
2
2
|
import { html, unsafeStatic } from 'lit/static-html.js';
|
|
3
|
-
import { property, query } from 'lit/decorators.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import { property, query } from 'lit/decorators.js';
|
|
4
|
+
import {
|
|
5
|
+
RtlMixin, validPropertyValues, requiredProperty,
|
|
6
|
+
} from '@justeattakeaway/pie-webc-core';
|
|
7
|
+
import type { DependentMap } from '@justeattakeaway/pie-webc-core';
|
|
8
|
+
import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock';
|
|
6
9
|
import styles from './modal.scss?inline';
|
|
7
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
ModalProps, headingLevels, ON_MODAL_CLOSE_EVENT, ON_MODAL_OPEN_EVENT,
|
|
12
|
+
} from './defs';
|
|
8
13
|
|
|
9
14
|
// Valid values available to consumers
|
|
10
15
|
export { type ModalProps, headingLevels };
|
|
@@ -24,11 +29,86 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
24
29
|
headingLevel: ModalProps['headingLevel'] = 'h2';
|
|
25
30
|
|
|
26
31
|
@query('dialog')
|
|
27
|
-
_dialog
|
|
32
|
+
_dialog?: HTMLDialogElement;
|
|
33
|
+
|
|
34
|
+
constructor () {
|
|
35
|
+
super();
|
|
36
|
+
this.addEventListener('click', (event) => this._handleDialogLightDismiss(event));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
firstUpdated (changedProperties: DependentMap<ModalProps>) : void {
|
|
40
|
+
this._handleModalOpenStateOnFirstRender(changedProperties);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
updated (changedProperties: DependentMap<ModalProps>) : void {
|
|
44
|
+
this._handleModalOpenStateChanged(changedProperties);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Opens the dialog element and disables page scrolling
|
|
49
|
+
*/
|
|
50
|
+
private _handleModalOpened () : void {
|
|
51
|
+
disableBodyScroll(this);
|
|
52
|
+
// We require this because toggling the prop `isOpen` itself won't
|
|
53
|
+
// allow the dialog to open in the correct way (with the default background),
|
|
54
|
+
// the method `showModal()` needs to be invoked.
|
|
55
|
+
this._dialog?.showModal();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Closes the dialog element and re-enables page scrolling
|
|
60
|
+
*/
|
|
61
|
+
private _handleModalClosed () : void {
|
|
62
|
+
enableBodyScroll(this);
|
|
63
|
+
// Closes the native dialog element
|
|
64
|
+
this._dialog?.close();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* This is only to be used inside the component template as direct property
|
|
69
|
+
* reassignment is not allowed.
|
|
70
|
+
*/
|
|
71
|
+
private _triggerCloseModal = () : void => {
|
|
72
|
+
this.isOpen = false;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
connectedCallback () : void {
|
|
76
|
+
super.connectedCallback();
|
|
77
|
+
document.addEventListener(ON_MODAL_OPEN_EVENT, this._handleModalOpened.bind(this));
|
|
78
|
+
document.addEventListener(ON_MODAL_CLOSE_EVENT, this._handleModalClosed.bind(this));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
disconnectedCallback () : void {
|
|
82
|
+
document.removeEventListener(ON_MODAL_OPEN_EVENT, this._handleModalOpened.bind(this));
|
|
83
|
+
document.removeEventListener(ON_MODAL_CLOSE_EVENT, this._handleModalClosed.bind(this));
|
|
84
|
+
super.disconnectedCallback();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handles the value of the isOpen property on first render of the component
|
|
88
|
+
private _handleModalOpenStateOnFirstRender (changedProperties: DependentMap<ModalProps>) : void {
|
|
89
|
+
// This ensures if the modal is open on first render, the scroll lock and backdrop are applied
|
|
90
|
+
const previousValue = changedProperties.get('isOpen');
|
|
91
|
+
|
|
92
|
+
if (previousValue === undefined && this.isOpen) {
|
|
93
|
+
this._dispatchModalOpenEvent();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Handles changes to the modal isOpen property by dispatching any appropriate events
|
|
98
|
+
private _handleModalOpenStateChanged (changedProperties: DependentMap<ModalProps>) : void {
|
|
99
|
+
const previousValue = changedProperties.get('isOpen');
|
|
100
|
+
|
|
101
|
+
if (previousValue !== undefined) {
|
|
102
|
+
if (previousValue) {
|
|
103
|
+
this._dispatchModalCloseEvent();
|
|
104
|
+
} else {
|
|
105
|
+
this._dispatchModalOpenEvent();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
28
109
|
|
|
29
110
|
render () {
|
|
30
111
|
const {
|
|
31
|
-
isOpen,
|
|
32
112
|
heading,
|
|
33
113
|
headingLevel = 'h2',
|
|
34
114
|
} = this;
|
|
@@ -36,26 +116,46 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
36
116
|
const headingTag = unsafeStatic(headingLevel);
|
|
37
117
|
|
|
38
118
|
return html`
|
|
39
|
-
<dialog id="dialog" class="c-modal"
|
|
119
|
+
<dialog id="dialog" class="c-modal">
|
|
40
120
|
<header>
|
|
41
121
|
<${headingTag} class="c-modal-heading">${heading}</${headingTag}>
|
|
42
122
|
<pie-icon-button
|
|
43
|
-
@click="${this.
|
|
123
|
+
@click="${this._triggerCloseModal}"
|
|
44
124
|
variant="ghost-tertiary"
|
|
45
125
|
class="c-modal-closeBtn"></pie-icon-button>
|
|
46
126
|
</header>
|
|
47
|
-
<article>
|
|
48
|
-
<
|
|
49
|
-
<slot></slot>
|
|
50
|
-
</div>
|
|
127
|
+
<article class="c-modal-content">
|
|
128
|
+
<slot></slot>
|
|
51
129
|
</article>
|
|
52
130
|
</dialog>
|
|
53
131
|
`;
|
|
54
132
|
}
|
|
55
133
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Dismisses the modal on backdrop click
|
|
136
|
+
*
|
|
137
|
+
*/
|
|
138
|
+
private _handleDialogLightDismiss = (event: MouseEvent) : void => {
|
|
139
|
+
const rect = this._dialog?.getBoundingClientRect();
|
|
140
|
+
|
|
141
|
+
const {
|
|
142
|
+
top = 0, bottom = 0, left = 0, right = 0,
|
|
143
|
+
} = rect || {};
|
|
144
|
+
|
|
145
|
+
// This means the dialog is not open due to clicking the close button so
|
|
146
|
+
// we want to escape early
|
|
147
|
+
if (top === 0 && bottom === 0 && left === 0 && right === 0) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const isClickOutsideDialog = event.clientY < top ||
|
|
152
|
+
event.clientY > bottom ||
|
|
153
|
+
event.clientX < left ||
|
|
154
|
+
event.clientX > right;
|
|
155
|
+
|
|
156
|
+
if (isClickOutsideDialog) {
|
|
157
|
+
this.isOpen = false;
|
|
158
|
+
}
|
|
59
159
|
};
|
|
60
160
|
|
|
61
161
|
/**
|
|
@@ -64,8 +164,27 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
64
164
|
*
|
|
65
165
|
* @event
|
|
66
166
|
*/
|
|
67
|
-
|
|
68
|
-
const event = new CustomEvent(ON_MODAL_CLOSE_EVENT
|
|
167
|
+
private _dispatchModalCloseEvent = () : void => {
|
|
168
|
+
const event = new CustomEvent(ON_MODAL_CLOSE_EVENT, {
|
|
169
|
+
bubbles: true,
|
|
170
|
+
composed: true,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
this.dispatchEvent(event);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Dispatch `ON_MODAL_OPEN_EVENT` event.
|
|
178
|
+
* To be used whenever we open the modal.
|
|
179
|
+
*
|
|
180
|
+
* @event
|
|
181
|
+
*/
|
|
182
|
+
private _dispatchModalOpenEvent = () : void => {
|
|
183
|
+
const event = new CustomEvent(ON_MODAL_OPEN_EVENT, {
|
|
184
|
+
bubbles: true,
|
|
185
|
+
composed: true,
|
|
186
|
+
});
|
|
187
|
+
|
|
69
188
|
this.dispatchEvent(event);
|
|
70
189
|
};
|
|
71
190
|
|
package/src/modal.scss
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@use '@justeat/pie-design-tokens/dist/jet.scss' as dt;
|
|
2
|
+
|
|
1
3
|
.c-modal {
|
|
2
4
|
// Custom Property Declarations
|
|
3
5
|
// These are defined here instead of :host to encapsulate them inside Shadow DOM
|
|
@@ -21,6 +23,12 @@
|
|
|
21
23
|
|
|
22
24
|
box-shadow: var(--modal-elevation);
|
|
23
25
|
|
|
26
|
+
// We need to pull in the token directly here because the
|
|
27
|
+
// pseudo element `::backdrop` doesn't seem to pick up custom css properties.
|
|
28
|
+
&::backdrop {
|
|
29
|
+
background: #{dt.$color-overlay};
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
& .c-modal-heading {
|
|
25
33
|
// Modal header Custom Props
|
|
26
34
|
--modal-header-font-size: calc(var(--dt-font-heading-m-size--wide) * 1px);
|
|
@@ -52,11 +60,14 @@
|
|
|
52
60
|
|
|
53
61
|
padding-block: var(--modal-content-padding-block-start) var(--modal-content-padding);
|
|
54
62
|
padding-inline: var(--modal-content-padding);
|
|
63
|
+
|
|
64
|
+
overflow-y: scroll;
|
|
65
|
+
max-block-size: 300px; // This is just a placeholder before we add the proper container styles to slotted content to demonstrate scrollable slot content
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
& .c-modal-closeBtn {
|
|
58
69
|
position: absolute;
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
inset-inline-end: var(--dt-spacing-d);
|
|
71
|
+
inset-block-start: var(--dt-spacing-d);
|
|
61
72
|
}
|
|
62
73
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { test, expect } from '@sand4rt/experimental-ct-web';
|
|
2
|
-
import * as events from 'events';
|
|
3
2
|
import { PieModal } from '@/index';
|
|
4
3
|
import { headingLevels } from '@/defs';
|
|
5
4
|
|
|
@@ -21,33 +20,57 @@ headingLevels.forEach((headingLevel) => test(`should render the correct heading
|
|
|
21
20
|
headingLevel,
|
|
22
21
|
};
|
|
23
22
|
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
24
|
+
// @ts-ignore // Added this as we want to deliberately test with invalid headingLevel (which is an invalid type based on ModalProps)
|
|
24
25
|
const component = await mount(PieModal, { props });
|
|
25
26
|
|
|
26
27
|
// h2 is the default / fallback value
|
|
27
28
|
await expect(component.locator('h2.c-modal-heading')).toContainText(props.heading);
|
|
28
29
|
}));
|
|
29
30
|
|
|
30
|
-
test.describe('`
|
|
31
|
-
test.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
test.describe('`Pie Modal is closed`', () => {
|
|
32
|
+
test.describe('when via the close button click', () => {
|
|
33
|
+
test.skip('should dispatch event `pie-modal-close`', async ({ mount, page }) => {
|
|
34
|
+
const messages: string[] = [];
|
|
35
|
+
await mount(
|
|
36
|
+
PieModal,
|
|
37
|
+
{
|
|
38
|
+
props: {
|
|
39
|
+
isOpen: true
|
|
40
|
+
},
|
|
41
|
+
on: {
|
|
42
|
+
click: (event: string) => messages.push(event),
|
|
43
|
+
},
|
|
38
44
|
},
|
|
39
|
-
|
|
40
|
-
);
|
|
45
|
+
);
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
await page.locator('.c-modal-closeBtn').click();
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
expect(messages).toHaveLength(1);
|
|
46
51
|
});
|
|
52
|
+
});
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
test.describe('when via the backdrop click', () => {
|
|
55
|
+
test.skip('should dispatch event `pie-modal-close`', async ({ mount, page }) => {
|
|
56
|
+
const messages: string[] = [];
|
|
57
|
+
await mount(
|
|
58
|
+
PieModal,
|
|
59
|
+
{
|
|
60
|
+
props: {
|
|
61
|
+
isOpen: true,
|
|
62
|
+
},
|
|
63
|
+
on: {
|
|
64
|
+
click: (event: string) => messages.push(event),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
);
|
|
49
68
|
|
|
50
|
-
|
|
69
|
+
await page.locator('#dialog').click();
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
expect(messages).toHaveLength(1);
|
|
73
|
+
});
|
|
51
74
|
});
|
|
52
75
|
});
|
|
53
76
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { test } from '@sand4rt/experimental-ct-web';
|
|
2
2
|
import percySnapshot from '@percy/playwright';
|
|
3
|
+
import {
|
|
4
|
+
WebComponentTestWrapper,
|
|
5
|
+
} from '@justeattakeaway/pie-webc-core/src/test-helpers/components/web-component-test-wrapper/WebComponentTestWrapper.ts';
|
|
6
|
+
import { WebComponentPropValues } from '@justeattakeaway/pie-webc-core/src/test-helpers/defs';
|
|
3
7
|
import { PieModal } from '@/index';
|
|
4
|
-
import { getLitPercyOptions } from '@justeattakeaway/pie-webc-core/src/test-helpers/percy-lit-options.ts';
|
|
5
8
|
|
|
6
9
|
const propIsOpenValues = [{ heading: 'Modal Heading', isOpen: true }, { isOpen: false }];
|
|
7
10
|
|
|
@@ -16,5 +19,50 @@ propIsOpenValues.forEach((props) => test(`should render Modal correctly when pro
|
|
|
16
19
|
},
|
|
17
20
|
);
|
|
18
21
|
|
|
19
|
-
await percySnapshot(page, `PIE Modal when isOpen is set to ${props.isOpen}
|
|
22
|
+
await percySnapshot(page, `PIE Modal when isOpen is set to ${props.isOpen}`);
|
|
20
23
|
}));
|
|
24
|
+
|
|
25
|
+
const renderTestPieModal = (propVals: WebComponentPropValues) => `<pie-modal ${propVals.isOpen ? 'isOpen' : ''} heading="${propVals.heading}" headingLevel="${propVals.headingLevel}"></pie-modal>`;
|
|
26
|
+
|
|
27
|
+
// Creates a <ul> with a large number of <li> nodes for testing page scrolling
|
|
28
|
+
const createTestPageHTML = () : string => {
|
|
29
|
+
let list = '<ul>';
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < 200; i++) {
|
|
32
|
+
list += `<li>Item ${i}</li>`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
list += '</ul>';
|
|
36
|
+
|
|
37
|
+
return list;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
test('Should not be able to scroll when modal is open', async ({ page, mount }) => {
|
|
41
|
+
const props: WebComponentPropValues = {
|
|
42
|
+
heading: 'I am a Modal!',
|
|
43
|
+
headingLevel: 'h2',
|
|
44
|
+
isOpen: true,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const modalComponent = renderTestPieModal(props);
|
|
48
|
+
|
|
49
|
+
await mount(
|
|
50
|
+
WebComponentTestWrapper,
|
|
51
|
+
{
|
|
52
|
+
props: {
|
|
53
|
+
pageMode: true,
|
|
54
|
+
},
|
|
55
|
+
slots: {
|
|
56
|
+
component: modalComponent,
|
|
57
|
+
pageMarkup: createTestPageHTML(),
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Scroll 800 pixels down the page
|
|
63
|
+
await page.mouse.wheel(0, 800);
|
|
64
|
+
|
|
65
|
+
await page.waitForTimeout(3000); // The mouse.wheel function causes scrolling, but doesn't wait for the scroll to finish before returning.
|
|
66
|
+
|
|
67
|
+
await percySnapshot(page, 'PIE Modal scroll locking');
|
|
68
|
+
});
|