@justeattakeaway/pie-modal 0.6.1 → 0.8.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 +17 -0
- package/dist/index.js +222 -109
- package/dist/types/packages/components/pie-modal/src/defs.d.ts +17 -11
- 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 +38 -5
- package/dist/types/packages/components/pie-modal/src/index.d.ts.map +1 -1
- package/package.json +7 -2
- package/src/defs.ts +18 -11
- package/src/index.ts +150 -19
- package/src/modal.scss +38 -4
- package/test/component/pie-modal.spec.ts +56 -17
- package/test/visual/pie-modal.spec.ts +78 -8
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
[
|
|
1
|
+
[11:02:50 AM] @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 23 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[2m12.03 kB[22m[1m[22m[2m │ gzip: 3.79 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 23.71s[39m
|
|
18
|
-
[32m[36m[vite:dts][32m Declaration files built in 21869ms.
|
|
12
|
+
[32m✓ built in 27.07s[39m
|
|
13
|
+
[32m[36m[vite:dts][32m Declaration files built in 24624ms.
|
|
19
14
|
[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @justeattakeaway/pie-modal
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [Added] - New size prop for pie-modal ([#572](https://github.com/justeattakeaway/pie/pull/572)) by [@xander-marjoram](https://github.com/xander-marjoram)
|
|
8
|
+
|
|
9
|
+
[Added] - Visual regression test for each size
|
|
10
|
+
[Changed] - Modal story to include new size prop
|
|
11
|
+
|
|
12
|
+
## 0.7.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- [Added] - Modal backdrop functionality ([#559](https://github.com/justeattakeaway/pie/pull/559)) by [@kevinrodrigues](https://github.com/kevinrodrigues)
|
|
17
|
+
|
|
18
|
+
- [Added] - Scroll locking to modal ([#564](https://github.com/justeattakeaway/pie/pull/564)) by [@jamieomaguire](https://github.com/jamieomaguire)
|
|
19
|
+
|
|
3
20
|
## 0.6.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
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 C, LitElement as x } from "lit";
|
|
2
|
+
import { unsafeStatic as E, html as L } from "lit/static-html.js";
|
|
3
|
+
import { property as y, query as T } from "lit/decorators.js";
|
|
4
|
+
import { property as $ } from "lit/decorators/property.js";
|
|
5
|
+
var D = Object.defineProperty, B = Object.getOwnPropertyDescriptor, k = (i, e, t, n) => {
|
|
6
|
+
for (var o = n > 1 ? void 0 : n ? B(e, t) : e, d = i.length - 1, a; d >= 0; d--)
|
|
7
|
+
(a = i[d]) && (o = (n ? a(e, t, o) : a(o)) || o);
|
|
8
|
+
return n && o && D(e, t, o), o;
|
|
9
9
|
};
|
|
10
|
-
const
|
|
11
|
-
class
|
|
10
|
+
const R = (i) => {
|
|
11
|
+
class e extends i {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments), this.dir = "";
|
|
14
14
|
}
|
|
@@ -22,135 +22,248 @@ 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 a = `#${
|
|
34
|
-
Object.defineProperty(o,
|
|
29
|
+
return k([
|
|
30
|
+
$({ type: String })
|
|
31
|
+
], e.prototype, "dir", 2), e;
|
|
32
|
+
}, S = (i, e, t) => function(o, d) {
|
|
33
|
+
const a = `#${d}`;
|
|
34
|
+
Object.defineProperty(o, d, {
|
|
35
35
|
get() {
|
|
36
36
|
return this[a];
|
|
37
37
|
},
|
|
38
|
-
set(
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
`<${
|
|
42
|
-
`Must be one of: ${
|
|
43
|
-
`Falling back to default value: "${
|
|
44
|
-
), this[a] =
|
|
38
|
+
set(f) {
|
|
39
|
+
const m = this[a];
|
|
40
|
+
e.includes(f) ? this[a] = f : (console.error(
|
|
41
|
+
`<${i}> Invalid value "${f}" provided for property "${d}".`,
|
|
42
|
+
`Must be one of: ${e.join(" | ")}.`,
|
|
43
|
+
`Falling back to default value: "${t}"`
|
|
44
|
+
), this[a] = t), this.requestUpdate(d, m);
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
-
},
|
|
47
|
+
}, A = (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(
|
|
53
|
+
set(d) {
|
|
54
54
|
const a = this[o];
|
|
55
|
-
(
|
|
55
|
+
(d == null || typeof d == "string" && d.trim() === "") && console.error(`<${i}> Missing required attribute "${n}"`), this[o] = d, this.requestUpdate(n, a);
|
|
56
56
|
}
|
|
57
57
|
});
|
|
58
|
-
}, z = `.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
|
-
`;
|
|
60
|
-
var D = Object.defineProperty, T = Object.getOwnPropertyDescriptor, E = (r, t, n, o) => {
|
|
61
|
-
for (var e = o > 1 ? void 0 : o ? T(t, n) : t, a = r.length - 1, i; a >= 0; a--)
|
|
62
|
-
(i = r[a]) && (e = (o ? i(t, n, e) : i(e)) || e);
|
|
63
|
-
return o && e && D(t, n, e), e;
|
|
64
58
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// <p class="c-webComponentTestWrapper-label"><b>isFullWidth</b>: <code>true</code></p>
|
|
73
|
-
_renderPropKeyValues() {
|
|
74
|
-
return this.propKeyValues.split(",").map((t) => {
|
|
75
|
-
const [n, o] = t.split(":");
|
|
76
|
-
return m`<p class="c-webComponentTestWrapper-label"><b>${n}</b>: <code>${o}</code></p>`;
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
// eslint-disable-next-line class-methods-use-this
|
|
80
|
-
render() {
|
|
81
|
-
return m`
|
|
82
|
-
<div class="c-webComponentTestWrapper">
|
|
83
|
-
${this._renderPropKeyValues()}
|
|
84
|
-
<div class="c-webComponentTestWrapper-slot">
|
|
85
|
-
<slot></slot>
|
|
86
|
-
</div>
|
|
87
|
-
</div>`;
|
|
88
|
-
}
|
|
59
|
+
function q(i) {
|
|
60
|
+
if (Array.isArray(i)) {
|
|
61
|
+
for (var e = 0, t = Array(i.length); e < i.length; e++)
|
|
62
|
+
t[e] = i[e];
|
|
63
|
+
return t;
|
|
64
|
+
} else
|
|
65
|
+
return Array.from(i);
|
|
89
66
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
67
|
+
var _ = !1;
|
|
68
|
+
if (typeof window < "u") {
|
|
69
|
+
var z = {
|
|
70
|
+
get passive() {
|
|
71
|
+
_ = !0;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
window.addEventListener("testPassive", null, z), window.removeEventListener("testPassive", null, z);
|
|
75
|
+
}
|
|
76
|
+
var p = typeof window < "u" && window.navigator && window.navigator.platform && (/iP(ad|hone|od)/.test(window.navigator.platform) || window.navigator.platform === "MacIntel" && window.navigator.maxTouchPoints > 1), r = [], v = !1, P = -1, c = void 0, l = void 0, u = void 0, M = function(e) {
|
|
77
|
+
return r.some(function(t) {
|
|
78
|
+
return !!(t.options.allowTouchMove && t.options.allowTouchMove(e));
|
|
79
|
+
});
|
|
80
|
+
}, g = function(e) {
|
|
81
|
+
var t = e || window.event;
|
|
82
|
+
return M(t.target) || t.touches.length > 1 ? !0 : (t.preventDefault && t.preventDefault(), !1);
|
|
83
|
+
}, I = function(e) {
|
|
84
|
+
if (u === void 0) {
|
|
85
|
+
var t = !!e && e.reserveScrollBarGap === !0, n = window.innerWidth - document.documentElement.clientWidth;
|
|
86
|
+
if (t && n > 0) {
|
|
87
|
+
var o = parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right"), 10);
|
|
88
|
+
u = document.body.style.paddingRight, document.body.style.paddingRight = o + n + "px";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
c === void 0 && (c = document.body.style.overflow, document.body.style.overflow = "hidden");
|
|
92
|
+
}, V = function() {
|
|
93
|
+
u !== void 0 && (document.body.style.paddingRight = u, u = void 0), c !== void 0 && (document.body.style.overflow = c, c = void 0);
|
|
94
|
+
}, j = function() {
|
|
95
|
+
return window.requestAnimationFrame(function() {
|
|
96
|
+
if (l === void 0) {
|
|
97
|
+
l = {
|
|
98
|
+
position: document.body.style.position,
|
|
99
|
+
top: document.body.style.top,
|
|
100
|
+
left: document.body.style.left
|
|
101
|
+
};
|
|
102
|
+
var e = window, t = e.scrollY, n = e.scrollX, o = e.innerHeight;
|
|
103
|
+
document.body.style.position = "fixed", document.body.style.top = -t, document.body.style.left = -n, setTimeout(function() {
|
|
104
|
+
return window.requestAnimationFrame(function() {
|
|
105
|
+
var d = o - window.innerHeight;
|
|
106
|
+
d && t >= o && (document.body.style.top = -(t + d));
|
|
107
|
+
});
|
|
108
|
+
}, 300);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}, F = function() {
|
|
112
|
+
if (l !== void 0) {
|
|
113
|
+
var e = -parseInt(document.body.style.top, 10), t = -parseInt(document.body.style.left, 10);
|
|
114
|
+
document.body.style.position = l.position, document.body.style.top = l.top, document.body.style.left = l.left, window.scrollTo(t, e), l = void 0;
|
|
115
|
+
}
|
|
116
|
+
}, H = function(e) {
|
|
117
|
+
return e ? e.scrollHeight - e.scrollTop <= e.clientHeight : !1;
|
|
118
|
+
}, N = function(e, t) {
|
|
119
|
+
var n = e.targetTouches[0].clientY - P;
|
|
120
|
+
return M(e.target) ? !1 : t && t.scrollTop === 0 && n > 0 || H(t) && n < 0 ? g(e) : (e.stopPropagation(), !0);
|
|
121
|
+
}, Y = function(e, t) {
|
|
122
|
+
if (!e) {
|
|
123
|
+
console.error("disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll on IOS devices.");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!r.some(function(o) {
|
|
127
|
+
return o.targetElement === e;
|
|
128
|
+
})) {
|
|
129
|
+
var n = {
|
|
130
|
+
targetElement: e,
|
|
131
|
+
options: t || {}
|
|
132
|
+
};
|
|
133
|
+
r = [].concat(q(r), [n]), p ? j() : I(t), p && (e.ontouchstart = function(o) {
|
|
134
|
+
o.targetTouches.length === 1 && (P = o.targetTouches[0].clientY);
|
|
135
|
+
}, e.ontouchmove = function(o) {
|
|
136
|
+
o.targetTouches.length === 1 && N(o, e);
|
|
137
|
+
}, v || (document.addEventListener("touchmove", g, _ ? { passive: !1 } : void 0), v = !0));
|
|
138
|
+
}
|
|
139
|
+
}, U = function(e) {
|
|
140
|
+
if (!e) {
|
|
141
|
+
console.error("enableBodyScroll unsuccessful - targetElement must be provided when calling enableBodyScroll on IOS devices.");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
r = r.filter(function(t) {
|
|
145
|
+
return t.targetElement !== e;
|
|
146
|
+
}), p && (e.ontouchstart = null, e.ontouchmove = null, v && r.length === 0 && (document.removeEventListener("touchmove", g, _ ? { passive: !1 } : void 0), v = !1)), p ? F() : V();
|
|
102
147
|
};
|
|
103
|
-
const
|
|
104
|
-
|
|
148
|
+
const X = `.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;box-shadow:var(--modal-elevation);font-family:var(--modal-font);background-color:var(--modal-bg-color);padding:0;--modal-max-inline-size: var(--modal-size-m);--modal-inline-size: 75%;max-inline-size:var(--modal-max-inline-size);inline-size:var(--modal-inline-size)}.c-modal[size=small]{--modal-max-inline-size: var(--modal-size-s)}.c-modal[size=large]{--modal-max-inline-size: var(--modal-size-l);--modal-inline-size: 100%}@media (min-width: 768px){.c-modal[size=large]{--modal-inline-size: 75%}}.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)}
|
|
149
|
+
`, W = ["h1", "h2", "h3", "h4", "h5", "h6"], G = ["small", "medium", "large"], w = "pie-modal-close", O = "pie-modal-open";
|
|
150
|
+
var J = Object.defineProperty, Q = Object.getOwnPropertyDescriptor, h = (i, e, t, n) => {
|
|
151
|
+
for (var o = n > 1 ? void 0 : n ? Q(e, t) : e, d = i.length - 1, a; d >= 0; d--)
|
|
152
|
+
(a = i[d]) && (o = (n ? a(e, t, o) : a(o)) || o);
|
|
153
|
+
return n && o && J(e, t, o), o;
|
|
154
|
+
};
|
|
155
|
+
const b = "pie-modal";
|
|
156
|
+
class s extends R(x) {
|
|
105
157
|
constructor() {
|
|
106
|
-
super(
|
|
107
|
-
this.
|
|
108
|
-
}, this.
|
|
109
|
-
|
|
110
|
-
this.
|
|
111
|
-
|
|
158
|
+
super(), this.isOpen = !1, this.headingLevel = "h2", this.size = "medium", this._triggerCloseModal = () => {
|
|
159
|
+
this.isOpen = !1;
|
|
160
|
+
}, this._handleDialogLightDismiss = (e) => {
|
|
161
|
+
var m;
|
|
162
|
+
const t = (m = this._dialog) == null ? void 0 : m.getBoundingClientRect(), {
|
|
163
|
+
top: n = 0,
|
|
164
|
+
bottom: o = 0,
|
|
165
|
+
left: d = 0,
|
|
166
|
+
right: a = 0
|
|
167
|
+
} = t || {};
|
|
168
|
+
if (n === 0 && o === 0 && d === 0 && a === 0)
|
|
169
|
+
return;
|
|
170
|
+
(e.clientY < n || e.clientY > o || e.clientX < d || e.clientX > a) && (this.isOpen = !1);
|
|
171
|
+
}, this._dispatchModalCloseEvent = () => {
|
|
172
|
+
const e = new CustomEvent(w, {
|
|
173
|
+
bubbles: !0,
|
|
174
|
+
composed: !0
|
|
175
|
+
});
|
|
176
|
+
this.dispatchEvent(e);
|
|
177
|
+
}, this._dispatchModalOpenEvent = () => {
|
|
178
|
+
const e = new CustomEvent(O, {
|
|
179
|
+
bubbles: !0,
|
|
180
|
+
composed: !0
|
|
181
|
+
});
|
|
182
|
+
this.dispatchEvent(e);
|
|
183
|
+
}, this.addEventListener("click", (e) => this._handleDialogLightDismiss(e));
|
|
184
|
+
}
|
|
185
|
+
firstUpdated(e) {
|
|
186
|
+
this._handleModalOpenStateOnFirstRender(e);
|
|
187
|
+
}
|
|
188
|
+
updated(e) {
|
|
189
|
+
this._handleModalOpenStateChanged(e);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Opens the dialog element and disables page scrolling
|
|
193
|
+
*/
|
|
194
|
+
_handleModalOpened() {
|
|
195
|
+
var e;
|
|
196
|
+
Y(this), (e = this._dialog) == null || e.showModal();
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Closes the dialog element and re-enables page scrolling
|
|
200
|
+
*/
|
|
201
|
+
_handleModalClosed() {
|
|
202
|
+
var e;
|
|
203
|
+
U(this), (e = this._dialog) == null || e.close();
|
|
204
|
+
}
|
|
205
|
+
connectedCallback() {
|
|
206
|
+
super.connectedCallback(), document.addEventListener(O, this._handleModalOpened.bind(this)), document.addEventListener(w, this._handleModalClosed.bind(this));
|
|
207
|
+
}
|
|
208
|
+
disconnectedCallback() {
|
|
209
|
+
document.removeEventListener(O, this._handleModalOpened.bind(this)), document.removeEventListener(w, this._handleModalClosed.bind(this)), super.disconnectedCallback();
|
|
210
|
+
}
|
|
211
|
+
// Handles the value of the isOpen property on first render of the component
|
|
212
|
+
_handleModalOpenStateOnFirstRender(e) {
|
|
213
|
+
e.get("isOpen") === void 0 && this.isOpen && this._dispatchModalOpenEvent();
|
|
214
|
+
}
|
|
215
|
+
// Handles changes to the modal isOpen property by dispatching any appropriate events
|
|
216
|
+
_handleModalOpenStateChanged(e) {
|
|
217
|
+
const t = e.get("isOpen");
|
|
218
|
+
t !== void 0 && (t ? this._dispatchModalCloseEvent() : this._dispatchModalOpenEvent());
|
|
112
219
|
}
|
|
113
220
|
render() {
|
|
114
221
|
const {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
} = this,
|
|
119
|
-
return
|
|
120
|
-
<dialog
|
|
222
|
+
heading: e,
|
|
223
|
+
headingLevel: t = "h2",
|
|
224
|
+
size: n
|
|
225
|
+
} = this, o = E(t);
|
|
226
|
+
return L`
|
|
227
|
+
<dialog
|
|
228
|
+
id="dialog"
|
|
229
|
+
size="${n}"
|
|
230
|
+
class="c-modal">
|
|
121
231
|
<header>
|
|
122
|
-
<${
|
|
232
|
+
<${o} class="c-modal-heading">${e}</${o}>
|
|
123
233
|
<pie-icon-button
|
|
124
|
-
@click="${this.
|
|
125
|
-
variant="ghost-
|
|
234
|
+
@click="${this._triggerCloseModal}"
|
|
235
|
+
variant="ghost-secondary"
|
|
126
236
|
class="c-modal-closeBtn"></pie-icon-button>
|
|
127
237
|
</header>
|
|
128
|
-
<article>
|
|
129
|
-
<
|
|
130
|
-
<slot></slot>
|
|
131
|
-
</div>
|
|
238
|
+
<article class="c-modal-content">
|
|
239
|
+
<slot></slot>
|
|
132
240
|
</article>
|
|
133
241
|
</dialog>
|
|
134
242
|
`;
|
|
135
243
|
}
|
|
136
244
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
],
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
],
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
],
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
245
|
+
s.styles = C(X);
|
|
246
|
+
h([
|
|
247
|
+
y({ type: Boolean })
|
|
248
|
+
], s.prototype, "isOpen", 2);
|
|
249
|
+
h([
|
|
250
|
+
y({ type: String }),
|
|
251
|
+
A(b)
|
|
252
|
+
], s.prototype, "heading", 2);
|
|
253
|
+
h([
|
|
254
|
+
y(),
|
|
255
|
+
S(b, W, "h2")
|
|
256
|
+
], s.prototype, "headingLevel", 2);
|
|
257
|
+
h([
|
|
258
|
+
y(),
|
|
259
|
+
S(b, G, "medium")
|
|
260
|
+
], s.prototype, "size", 2);
|
|
261
|
+
h([
|
|
262
|
+
T("dialog")
|
|
263
|
+
], s.prototype, "_dialog", 2);
|
|
264
|
+
customElements.define(b, s);
|
|
153
265
|
export {
|
|
154
|
-
|
|
155
|
-
|
|
266
|
+
s as PieModal,
|
|
267
|
+
W as headingLevels,
|
|
268
|
+
G as sizes
|
|
156
269
|
};
|
|
@@ -1,27 +1,33 @@
|
|
|
1
|
+
export declare const headingLevels: readonly ["h1", "h2", "h3", "h4", "h5", "h6"];
|
|
2
|
+
export declare const sizes: readonly ["small", "medium", "large"];
|
|
1
3
|
export interface ModalProps {
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
5
|
+
* The text to display in the modal's heading.
|
|
4
6
|
*/
|
|
5
7
|
heading: string;
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
8
|
-
* @default h2
|
|
9
|
+
* The HTML heading tag to use for the modal's heading. Can be h1-h6.
|
|
9
10
|
*/
|
|
10
|
-
headingLevel:
|
|
11
|
+
headingLevel: typeof headingLevels[number];
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @default false
|
|
13
|
+
* When true, the modal will be open.
|
|
14
14
|
*/
|
|
15
|
-
isOpen
|
|
15
|
+
isOpen: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The size of the modal; this controls how wide it will appear on the page.
|
|
18
|
+
*/
|
|
19
|
+
size: typeof sizes[number];
|
|
16
20
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Modal heading levels/tags
|
|
19
|
-
*/
|
|
20
|
-
export declare const headingLevels: Array<ModalProps['headingLevel']>;
|
|
21
21
|
/**
|
|
22
22
|
* Event name for when the modal is closed.
|
|
23
23
|
*
|
|
24
24
|
* @constant
|
|
25
25
|
*/
|
|
26
26
|
export declare const ON_MODAL_CLOSE_EVENT = "pie-modal-close";
|
|
27
|
+
/**
|
|
28
|
+
* Event name for when the modal is opened.
|
|
29
|
+
*
|
|
30
|
+
* @constant
|
|
31
|
+
*/
|
|
32
|
+
export declare const ON_MODAL_OPEN_EVENT = "pie-modal-open";
|
|
27
33
|
//# 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
|
|
1
|
+
{"version":3,"file":"defs.d.ts","sourceRoot":"","sources":["../../../src/defs.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,+CAAgD,CAAC;AAC3E,eAAO,MAAM,KAAK,uCAAwC,CAAC;AAE3D,MAAM,WAAW,UAAU;IACvB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,YAAY,EAAE,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;IAC3C;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB;;OAEG;IACH,IAAI,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC;CAC9B;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,mBAAmB,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import type { DependentMap } from '@justeattakeaway/pie-webc-core';
|
|
3
|
+
import { ModalProps, headingLevels, sizes } from './defs';
|
|
4
|
+
export { type ModalProps, headingLevels, sizes };
|
|
4
5
|
declare const componentSelector = "pie-modal";
|
|
5
6
|
declare const PieModal_base: (new (...args: any[]) => {
|
|
6
7
|
dir: string;
|
|
@@ -10,16 +11,48 @@ export declare class PieModal extends PieModal_base {
|
|
|
10
11
|
isOpen: boolean;
|
|
11
12
|
heading: string;
|
|
12
13
|
headingLevel: ModalProps['headingLevel'];
|
|
13
|
-
|
|
14
|
+
size: ModalProps['size'];
|
|
15
|
+
_dialog?: HTMLDialogElement;
|
|
16
|
+
constructor();
|
|
17
|
+
firstUpdated(changedProperties: DependentMap<ModalProps>): void;
|
|
18
|
+
updated(changedProperties: DependentMap<ModalProps>): void;
|
|
19
|
+
/**
|
|
20
|
+
* Opens the dialog element and disables page scrolling
|
|
21
|
+
*/
|
|
22
|
+
private _handleModalOpened;
|
|
23
|
+
/**
|
|
24
|
+
* Closes the dialog element and re-enables page scrolling
|
|
25
|
+
*/
|
|
26
|
+
private _handleModalClosed;
|
|
27
|
+
/**
|
|
28
|
+
* This is only to be used inside the component template as direct property
|
|
29
|
+
* reassignment is not allowed.
|
|
30
|
+
*/
|
|
31
|
+
private _triggerCloseModal;
|
|
32
|
+
connectedCallback(): void;
|
|
33
|
+
disconnectedCallback(): void;
|
|
34
|
+
private _handleModalOpenStateOnFirstRender;
|
|
35
|
+
private _handleModalOpenStateChanged;
|
|
14
36
|
render(): import("lit-html").TemplateResult;
|
|
15
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Dismisses the modal on backdrop click
|
|
39
|
+
*
|
|
40
|
+
*/
|
|
41
|
+
private _handleDialogLightDismiss;
|
|
16
42
|
/**
|
|
17
43
|
* Dispatch `ON_MODAL_CLOSE_EVENT` event.
|
|
18
44
|
* To be used whenever we close the modal.
|
|
19
45
|
*
|
|
20
46
|
* @event
|
|
21
47
|
*/
|
|
22
|
-
|
|
48
|
+
private _dispatchModalCloseEvent;
|
|
49
|
+
/**
|
|
50
|
+
* Dispatch `ON_MODAL_OPEN_EVENT` event.
|
|
51
|
+
* To be used whenever we open the modal.
|
|
52
|
+
*
|
|
53
|
+
* @event
|
|
54
|
+
*/
|
|
55
|
+
private _dispatchModalOpenEvent;
|
|
23
56
|
static styles: import("lit").CSSResult;
|
|
24
57
|
}
|
|
25
58
|
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,
|
|
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,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;IAE1C,MAAM,UAAS;IAIf,OAAO,EAAG,MAAM,CAAC;IAIjB,YAAY,EAAE,UAAU,CAAC,cAAc,CAAC,CAAQ;IAIhD,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAY;IAGpC,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;IA4BN;;;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.8.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,12 @@
|
|
|
22
22
|
"license": "Apache-2.0",
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@justeattakeaway/pie-components-config": "workspace:*",
|
|
25
|
-
"@justeattakeaway/pie-
|
|
25
|
+
"@justeattakeaway/pie-icon-button": "workspace:*",
|
|
26
|
+
"@justeattakeaway/pie-webc-core": "workspace:*",
|
|
27
|
+
"@types/body-scroll-lock": "3.1.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"body-scroll-lock": "4.0.0-beta.0"
|
|
26
31
|
},
|
|
27
32
|
"volta": {
|
|
28
33
|
"extends": "../../../package.json"
|
package/src/defs.ts
CHANGED
|
@@ -1,28 +1,35 @@
|
|
|
1
|
+
export const headingLevels = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;
|
|
2
|
+
export const sizes = ['small', 'medium', 'large'] as const;
|
|
3
|
+
|
|
1
4
|
export interface ModalProps {
|
|
2
5
|
/**
|
|
3
|
-
*
|
|
6
|
+
* The text to display in the modal's heading.
|
|
4
7
|
*/
|
|
5
8
|
heading: string;
|
|
6
9
|
/**
|
|
7
|
-
*
|
|
8
|
-
|
|
10
|
+
* The HTML heading tag to use for the modal's heading. Can be h1-h6.
|
|
11
|
+
*/
|
|
12
|
+
headingLevel: typeof headingLevels[number];
|
|
13
|
+
/**
|
|
14
|
+
* When true, the modal will be open.
|
|
9
15
|
*/
|
|
10
|
-
|
|
16
|
+
isOpen: boolean;
|
|
11
17
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @default false
|
|
18
|
+
* The size of the modal; this controls how wide it will appear on the page.
|
|
14
19
|
*/
|
|
15
|
-
|
|
20
|
+
size: typeof sizes[number];
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
|
-
*
|
|
24
|
+
* Event name for when the modal is closed.
|
|
25
|
+
*
|
|
26
|
+
* @constant
|
|
20
27
|
*/
|
|
21
|
-
export const
|
|
28
|
+
export const ON_MODAL_CLOSE_EVENT = 'pie-modal-close';
|
|
22
29
|
|
|
23
30
|
/**
|
|
24
|
-
* Event name for when the modal is
|
|
31
|
+
* Event name for when the modal is opened.
|
|
25
32
|
*
|
|
26
33
|
* @constant
|
|
27
34
|
*/
|
|
28
|
-
export const
|
|
35
|
+
export const ON_MODAL_OPEN_EVENT = 'pie-modal-open';
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
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,
|
|
12
|
+
headingLevels,
|
|
13
|
+
ON_MODAL_CLOSE_EVENT,
|
|
14
|
+
ON_MODAL_OPEN_EVENT,
|
|
15
|
+
sizes,
|
|
16
|
+
} from './defs';
|
|
8
17
|
|
|
9
18
|
// Valid values available to consumers
|
|
10
|
-
export { type ModalProps, headingLevels };
|
|
19
|
+
export { type ModalProps, headingLevels, sizes };
|
|
11
20
|
|
|
12
21
|
const componentSelector = 'pie-modal';
|
|
13
22
|
|
|
@@ -23,39 +32,142 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
23
32
|
@validPropertyValues(componentSelector, headingLevels, 'h2')
|
|
24
33
|
headingLevel: ModalProps['headingLevel'] = 'h2';
|
|
25
34
|
|
|
35
|
+
@property()
|
|
36
|
+
@validPropertyValues(componentSelector, sizes, 'medium')
|
|
37
|
+
size: ModalProps['size'] = 'medium';
|
|
38
|
+
|
|
26
39
|
@query('dialog')
|
|
27
|
-
_dialog
|
|
40
|
+
_dialog?: HTMLDialogElement;
|
|
41
|
+
|
|
42
|
+
constructor () {
|
|
43
|
+
super();
|
|
44
|
+
this.addEventListener('click', (event) => this._handleDialogLightDismiss(event));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
firstUpdated (changedProperties: DependentMap<ModalProps>) : void {
|
|
48
|
+
this._handleModalOpenStateOnFirstRender(changedProperties);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
updated (changedProperties: DependentMap<ModalProps>) : void {
|
|
52
|
+
this._handleModalOpenStateChanged(changedProperties);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Opens the dialog element and disables page scrolling
|
|
57
|
+
*/
|
|
58
|
+
private _handleModalOpened () : void {
|
|
59
|
+
disableBodyScroll(this);
|
|
60
|
+
// We require this because toggling the prop `isOpen` itself won't
|
|
61
|
+
// allow the dialog to open in the correct way (with the default background),
|
|
62
|
+
// the method `showModal()` needs to be invoked.
|
|
63
|
+
this._dialog?.showModal();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Closes the dialog element and re-enables page scrolling
|
|
68
|
+
*/
|
|
69
|
+
private _handleModalClosed () : void {
|
|
70
|
+
enableBodyScroll(this);
|
|
71
|
+
// Closes the native dialog element
|
|
72
|
+
this._dialog?.close();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* This is only to be used inside the component template as direct property
|
|
77
|
+
* reassignment is not allowed.
|
|
78
|
+
*/
|
|
79
|
+
private _triggerCloseModal = () : void => {
|
|
80
|
+
this.isOpen = false;
|
|
81
|
+
};
|
|
82
|
+
|
|
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
|
+
// Handles the value of the isOpen property on first render of the component
|
|
96
|
+
private _handleModalOpenStateOnFirstRender (changedProperties: DependentMap<ModalProps>) : void {
|
|
97
|
+
// This ensures if the modal is open on first render, the scroll lock and backdrop are applied
|
|
98
|
+
const previousValue = changedProperties.get('isOpen');
|
|
99
|
+
|
|
100
|
+
if (previousValue === undefined && this.isOpen) {
|
|
101
|
+
this._dispatchModalOpenEvent();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Handles changes to the modal isOpen property by dispatching any appropriate events
|
|
106
|
+
private _handleModalOpenStateChanged (changedProperties: DependentMap<ModalProps>) : void {
|
|
107
|
+
const previousValue = changedProperties.get('isOpen');
|
|
108
|
+
|
|
109
|
+
if (previousValue !== undefined) {
|
|
110
|
+
if (previousValue) {
|
|
111
|
+
this._dispatchModalCloseEvent();
|
|
112
|
+
} else {
|
|
113
|
+
this._dispatchModalOpenEvent();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
28
117
|
|
|
29
118
|
render () {
|
|
30
119
|
const {
|
|
31
|
-
isOpen,
|
|
32
120
|
heading,
|
|
33
121
|
headingLevel = 'h2',
|
|
122
|
+
size,
|
|
34
123
|
} = this;
|
|
35
124
|
|
|
36
125
|
const headingTag = unsafeStatic(headingLevel);
|
|
37
126
|
|
|
38
127
|
return html`
|
|
39
|
-
<dialog
|
|
128
|
+
<dialog
|
|
129
|
+
id="dialog"
|
|
130
|
+
size="${size}"
|
|
131
|
+
class="c-modal">
|
|
40
132
|
<header>
|
|
41
133
|
<${headingTag} class="c-modal-heading">${heading}</${headingTag}>
|
|
42
134
|
<pie-icon-button
|
|
43
|
-
@click="${this.
|
|
44
|
-
variant="ghost-
|
|
135
|
+
@click="${this._triggerCloseModal}"
|
|
136
|
+
variant="ghost-secondary"
|
|
45
137
|
class="c-modal-closeBtn"></pie-icon-button>
|
|
46
138
|
</header>
|
|
47
|
-
<article>
|
|
48
|
-
<
|
|
49
|
-
<slot></slot>
|
|
50
|
-
</div>
|
|
139
|
+
<article class="c-modal-content">
|
|
140
|
+
<slot></slot>
|
|
51
141
|
</article>
|
|
52
142
|
</dialog>
|
|
53
143
|
`;
|
|
54
144
|
}
|
|
55
145
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Dismisses the modal on backdrop click
|
|
148
|
+
*
|
|
149
|
+
*/
|
|
150
|
+
private _handleDialogLightDismiss = (event: MouseEvent) : void => {
|
|
151
|
+
const rect = this._dialog?.getBoundingClientRect();
|
|
152
|
+
|
|
153
|
+
const {
|
|
154
|
+
top = 0, bottom = 0, left = 0, right = 0,
|
|
155
|
+
} = rect || {};
|
|
156
|
+
|
|
157
|
+
// This means the dialog is not open due to clicking the close button so
|
|
158
|
+
// we want to escape early
|
|
159
|
+
if (top === 0 && bottom === 0 && left === 0 && right === 0) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const isClickOutsideDialog = event.clientY < top ||
|
|
164
|
+
event.clientY > bottom ||
|
|
165
|
+
event.clientX < left ||
|
|
166
|
+
event.clientX > right;
|
|
167
|
+
|
|
168
|
+
if (isClickOutsideDialog) {
|
|
169
|
+
this.isOpen = false;
|
|
170
|
+
}
|
|
59
171
|
};
|
|
60
172
|
|
|
61
173
|
/**
|
|
@@ -64,8 +176,27 @@ export class PieModal extends RtlMixin(LitElement) {
|
|
|
64
176
|
*
|
|
65
177
|
* @event
|
|
66
178
|
*/
|
|
67
|
-
|
|
68
|
-
const event = new CustomEvent(ON_MODAL_CLOSE_EVENT
|
|
179
|
+
private _dispatchModalCloseEvent = () : void => {
|
|
180
|
+
const event = new CustomEvent(ON_MODAL_CLOSE_EVENT, {
|
|
181
|
+
bubbles: true,
|
|
182
|
+
composed: true,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
this.dispatchEvent(event);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Dispatch `ON_MODAL_OPEN_EVENT` event.
|
|
190
|
+
* To be used whenever we open the modal.
|
|
191
|
+
*
|
|
192
|
+
* @event
|
|
193
|
+
*/
|
|
194
|
+
private _dispatchModalOpenEvent = () : void => {
|
|
195
|
+
const event = new CustomEvent(ON_MODAL_OPEN_EVENT, {
|
|
196
|
+
bubbles: true,
|
|
197
|
+
composed: true,
|
|
198
|
+
});
|
|
199
|
+
|
|
69
200
|
this.dispatchEvent(event);
|
|
70
201
|
};
|
|
71
202
|
|
package/src/modal.scss
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
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
|
|
6
|
+
$breakpoint-wide: 768px;
|
|
7
|
+
|
|
4
8
|
--modal-size-s: 450px;
|
|
5
9
|
--modal-size-m: 600px;
|
|
6
10
|
--modal-size-l: 1080px;
|
|
@@ -13,13 +17,40 @@
|
|
|
13
17
|
|
|
14
18
|
border-radius: var(--modal-border-radius);
|
|
15
19
|
border: none;
|
|
20
|
+
box-shadow: var(--modal-elevation);
|
|
16
21
|
font-family: var(--modal-font);
|
|
17
22
|
background-color: var(--modal-bg-color);
|
|
18
23
|
|
|
19
24
|
padding: 0;
|
|
20
|
-
inline-size: var(--modal-size-m);
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
--modal-max-inline-size: var(--modal-size-m);
|
|
27
|
+
--modal-inline-size: 75%;
|
|
28
|
+
|
|
29
|
+
max-inline-size: var(--modal-max-inline-size);
|
|
30
|
+
inline-size: var(--modal-inline-size);
|
|
31
|
+
|
|
32
|
+
&[size='small'] {
|
|
33
|
+
--modal-max-inline-size: var(--modal-size-s);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&[size='medium'] {
|
|
37
|
+
/* Same as default styles */
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&[size='large'] {
|
|
41
|
+
--modal-max-inline-size: var(--modal-size-l);
|
|
42
|
+
--modal-inline-size: 100%;
|
|
43
|
+
|
|
44
|
+
@media (min-width: $breakpoint-wide) {
|
|
45
|
+
--modal-inline-size: 75%;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// We need to pull in the token directly here because the
|
|
50
|
+
// pseudo element `::backdrop` doesn't seem to pick up custom css properties.
|
|
51
|
+
&::backdrop {
|
|
52
|
+
background: #{dt.$color-overlay};
|
|
53
|
+
}
|
|
23
54
|
|
|
24
55
|
& .c-modal-heading {
|
|
25
56
|
// Modal header Custom Props
|
|
@@ -52,11 +83,14 @@
|
|
|
52
83
|
|
|
53
84
|
padding-block: var(--modal-content-padding-block-start) var(--modal-content-padding);
|
|
54
85
|
padding-inline: var(--modal-content-padding);
|
|
86
|
+
|
|
87
|
+
overflow-y: scroll;
|
|
88
|
+
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
89
|
}
|
|
56
90
|
|
|
57
91
|
& .c-modal-closeBtn {
|
|
58
92
|
position: absolute;
|
|
59
|
-
|
|
60
|
-
|
|
93
|
+
inset-inline-end: var(--dt-spacing-d);
|
|
94
|
+
inset-block-start: var(--dt-spacing-d);
|
|
61
95
|
}
|
|
62
96
|
}
|
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { test, expect } from '@sand4rt/experimental-ct-web';
|
|
2
|
-
import
|
|
2
|
+
import { PieIconButton } from '@justeattakeaway/pie-icon-button';
|
|
3
3
|
import { PieModal } from '@/index';
|
|
4
4
|
import { headingLevels } from '@/defs';
|
|
5
5
|
|
|
6
|
+
// Mount any components that are used inside of pie-modal so that
|
|
7
|
+
// they have been registered with the browser before the tests run.
|
|
8
|
+
// There is likely a nicer way to do this but this will temporarily
|
|
9
|
+
// unblock tests.
|
|
10
|
+
test.beforeEach(async ({ page, mount }) => {
|
|
11
|
+
await mount(
|
|
12
|
+
PieIconButton,
|
|
13
|
+
{},
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// Removing the element so it's not present in the tests (but is still registered in the DOM)
|
|
17
|
+
await page.evaluate(() => {
|
|
18
|
+
const element : Element | null = document.querySelector('pie-icon-button');
|
|
19
|
+
element?.remove();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
6
23
|
headingLevels.forEach((headingLevel) => test(`should render the correct heading tag based on the value of headingLevel: ${headingLevel}`, async ({ mount }) => {
|
|
7
24
|
const props = {
|
|
8
25
|
heading: 'Modal Header',
|
|
@@ -21,33 +38,55 @@ headingLevels.forEach((headingLevel) => test(`should render the correct heading
|
|
|
21
38
|
headingLevel,
|
|
22
39
|
};
|
|
23
40
|
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
42
|
+
// @ts-ignore // Added this as we want to deliberately test with invalid headingLevel (which is an invalid type based on ModalProps)
|
|
24
43
|
const component = await mount(PieModal, { props });
|
|
25
44
|
|
|
26
45
|
// h2 is the default / fallback value
|
|
27
46
|
await expect(component.locator('h2.c-modal-heading')).toContainText(props.heading);
|
|
28
47
|
}));
|
|
29
48
|
|
|
30
|
-
test.describe('`
|
|
31
|
-
test.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
test.describe('`Pie Modal is closed`', () => {
|
|
50
|
+
test.describe('when via the close button click', () => {
|
|
51
|
+
test('should dispatch event `pie-modal-close`', async ({ mount, page }) => {
|
|
52
|
+
const messages: string[] = [];
|
|
53
|
+
await mount(
|
|
54
|
+
PieModal,
|
|
55
|
+
{
|
|
56
|
+
props: {
|
|
57
|
+
isOpen: true,
|
|
58
|
+
},
|
|
59
|
+
on: {
|
|
60
|
+
click: (event: string) => messages.push(event),
|
|
61
|
+
},
|
|
38
62
|
},
|
|
39
|
-
|
|
40
|
-
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
await page.locator('.c-modal-closeBtn').click();
|
|
41
66
|
|
|
42
|
-
|
|
43
|
-
on: {
|
|
44
|
-
click: (event: string) => messages.push(event),
|
|
45
|
-
},
|
|
67
|
+
expect(messages).toHaveLength(1);
|
|
46
68
|
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test.describe('when via the backdrop click', () => {
|
|
72
|
+
test('should dispatch event `pie-modal-close`', async ({ mount, page }) => {
|
|
73
|
+
const messages: string[] = [];
|
|
74
|
+
await mount(
|
|
75
|
+
PieModal,
|
|
76
|
+
{
|
|
77
|
+
props: {
|
|
78
|
+
isOpen: true,
|
|
79
|
+
},
|
|
80
|
+
on: {
|
|
81
|
+
click: (event: string) => messages.push(event),
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
);
|
|
47
85
|
|
|
48
|
-
|
|
86
|
+
await page.locator('#dialog').click();
|
|
49
87
|
|
|
50
|
-
|
|
88
|
+
expect(messages).toHaveLength(1);
|
|
89
|
+
});
|
|
51
90
|
});
|
|
52
91
|
});
|
|
53
92
|
|
|
@@ -1,20 +1,90 @@
|
|
|
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-testing/src/helpers/components/web-component-test-wrapper/WebComponentTestWrapper.ts';
|
|
6
|
+
import { PieIconButton } from '@justeattakeaway/pie-icon-button';
|
|
3
7
|
import { PieModal } from '@/index';
|
|
4
|
-
import {
|
|
8
|
+
import { ModalProps, sizes } from '@/defs';
|
|
5
9
|
|
|
6
|
-
|
|
10
|
+
// Renders a <pie-modal> HTML string with the given prop values
|
|
11
|
+
const renderTestPieModal = ({
|
|
12
|
+
heading = 'This is a modal heading',
|
|
13
|
+
headingLevel = 'h2',
|
|
14
|
+
size = 'medium',
|
|
15
|
+
isOpen = true,
|
|
16
|
+
} : Partial<ModalProps> = {}) => `<pie-modal ${isOpen ? 'isOpen' : ''} heading="${heading}" headingLevel="${headingLevel}" size="${size}"></pie-modal>`;
|
|
17
|
+
|
|
18
|
+
// Creates a <ol> with a large number of <li> nodes for testing page scrolling
|
|
19
|
+
const createTestPageHTML = () => `<ol>
|
|
20
|
+
${'<li>List item</li>'.repeat(200)}
|
|
21
|
+
</ol>`;
|
|
22
|
+
|
|
23
|
+
// Mount any components that are used inside of pie-modal so that
|
|
24
|
+
// they have been registered with the browser before the tests run.
|
|
25
|
+
// There is likely a nicer way to do this but this will temporarily
|
|
26
|
+
// unblock tests.
|
|
27
|
+
test.beforeEach(async ({ page, mount }) => {
|
|
28
|
+
await mount(
|
|
29
|
+
PieIconButton,
|
|
30
|
+
{},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Removing the element so it's not present in the tests (but is still registered in the DOM)
|
|
34
|
+
await page.evaluate(() => {
|
|
35
|
+
const element : Element | null = document.querySelector('pie-icon-button');
|
|
36
|
+
element?.remove();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('Should not be able to scroll when modal is open', async ({ page, mount }) => {
|
|
41
|
+
const modalComponent = renderTestPieModal();
|
|
7
42
|
|
|
8
|
-
propIsOpenValues.forEach((props) => test(`should render Modal correctly when prop isOpen is set to ${props.isOpen}`, async ({ page, mount }) => {
|
|
9
43
|
await mount(
|
|
10
|
-
|
|
44
|
+
WebComponentTestWrapper,
|
|
11
45
|
{
|
|
12
|
-
props
|
|
46
|
+
props: {
|
|
47
|
+
pageMode: true,
|
|
48
|
+
},
|
|
13
49
|
slots: {
|
|
14
|
-
|
|
50
|
+
component: modalComponent,
|
|
51
|
+
pageMarkup: createTestPageHTML(),
|
|
15
52
|
},
|
|
16
53
|
},
|
|
17
54
|
);
|
|
18
55
|
|
|
19
|
-
|
|
20
|
-
|
|
56
|
+
// Scroll 800 pixels down the page
|
|
57
|
+
await page.mouse.wheel(0, 800);
|
|
58
|
+
|
|
59
|
+
await page.waitForTimeout(3000); // The mouse.wheel function causes scrolling, but doesn't wait for the scroll to finish before returning.
|
|
60
|
+
|
|
61
|
+
await percySnapshot(page, 'Modal - scroll locking');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('should not render when isOpen = false', async ({ page, mount }) => {
|
|
65
|
+
await mount(PieModal, {
|
|
66
|
+
props: {
|
|
67
|
+
heading: 'This is a modal heading',
|
|
68
|
+
headingLevel: 'h2',
|
|
69
|
+
isOpen: false,
|
|
70
|
+
size: 'medium',
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await percySnapshot(page, 'Modal - isOpen = false');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
sizes.forEach((size) => {
|
|
78
|
+
test(`should render correctly with size = ${size}`, async ({ page, mount }) => {
|
|
79
|
+
await mount(PieModal, {
|
|
80
|
+
props: {
|
|
81
|
+
heading: 'This is a modal heading',
|
|
82
|
+
headingLevel: 'h2',
|
|
83
|
+
isOpen: true,
|
|
84
|
+
size,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await percySnapshot(page, `Modal - size = ${size}`);
|
|
89
|
+
});
|
|
90
|
+
});
|