@justeattakeaway/pie-modal 0.6.1 → 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.
@@ -1,19 +1,14 @@
1
- [2:38:36 PM] @custom-elements-manifest/analyzer: Created new manifest.
1
+ [4:04:52 PM] @custom-elements-manifest/analyzer: Created new manifest.
2
2
  react wrapper has been added!
3
3
  vite v4.3.9 building for production...
4
4
  transforming...
5
- ✓ 28 modules transformed.
5
+ ✓ 30 modules transformed.
6
6
  rendering chunks...
7
7
  computing gzip size...
8
- dist/index.js  7.43 kB │ gzip: 2.38 kB
8
+ dist/index.js 13.55 kB │ gzip: 4.16 kB
9
9
  dist/react.js 59.04 kB │ gzip: 15.92 kB
10
10
  
11
11
  [vite:dts] Start generate declaration files...
12
- src/index.ts:27:9 - error TS2564: Property '_dialog' has no initializer and is not definitely assigned in the constructor.
13
-
14
- 27 _dialog: HTMLDialogElement;
15
-    ~~~~~~~
16
-
17
- ✓ built in 23.71s
18
- [vite:dts] Declaration files built in 21869ms.
12
+ ✓ built in 28.39s
13
+ [vite:dts] Declaration files built in 26000ms.
19
14
  
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## 0.6.1
4
12
 
5
13
  ### Patch Changes
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
- import { unsafeCSS as g, LitElement as v, html as m } from "lit";
2
- import { unsafeStatic as u, html as b } from "lit/static-html.js";
3
- import { property as s, query as _ } from "lit/decorators.js";
4
- import { property as w } from "lit/decorators/property.js";
5
- var y = Object.defineProperty, $ = Object.getOwnPropertyDescriptor, P = (r, t, n, o) => {
6
- for (var e = o > 1 ? void 0 : o ? $(t, n) : t, a = r.length - 1, i; a >= 0; a--)
7
- (i = r[a]) && (e = (o ? i(t, n, e) : i(e)) || e);
8
- return o && e && y(t, n, e), e;
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 O = (r) => {
11
- class t extends r {
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 o;
26
- return this.dir === "ltr" ? !1 : this.dir === "rtl" || ((o = window == null ? void 0 : window.getComputedStyle(this)) == null ? void 0 : o.direction) === "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 P([
30
- w({ type: String })
31
- ], t.prototype, "dir", 2), t;
32
- }, C = (r, t, n) => function(o, e) {
33
- const a = `#${e}`;
34
- Object.defineProperty(o, e, {
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[a];
36
+ return this[d];
37
37
  },
38
- set(i) {
39
- const f = this[a];
40
- t.includes(i) ? this[a] = i : (console.error(
41
- `<${r}> Invalid value "${i}" provided for property "${e}".`,
42
- `Must be one of: ${t.join(" | ")}.`,
43
- `Falling back to default value: "${n}"`
44
- ), this[a] = n), this.requestUpdate(e, f);
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
- }, x = (r) => function(t, n) {
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(e) {
54
- const a = this[o];
55
- (e == null || e === "") && console.error(`<${r}> Missing required attribute "${n}"`), this[o] = e, this.requestUpdate(n, a);
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
- }, 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)}
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 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;
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 c extends v {
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((t) => {
75
- const [n, o] = t.split(":");
76
- return m`<p class="c-webComponentTestWrapper-label"><b>${n}</b>: <code>${o}</code></p>`;
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 m`
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
- c.styles = g(z);
93
+ b.styles = T(I);
94
+ E([
95
+ u({ type: String })
96
+ ], b.prototype, "propKeyValues", 2);
91
97
  E([
92
- s({ type: String })
93
- ], c.prototype, "propKeyValues", 2);
94
- const h = "web-component-test-wrapper";
95
- customElements.get(h) || customElements.define(h, c);
96
- const S = `.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 .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)}.c-modal .c-modal-closeBtn{position:absolute;right:var(--dt-spacing-d);top:var(--dt-spacing-d)}
97
- `, j = ["h1", "h2", "h3", "h4", "h5", "h6"], L = "pie-modal-close";
98
- var W = Object.defineProperty, V = Object.getOwnPropertyDescriptor, l = (r, t, n, o) => {
99
- for (var e = o > 1 ? void 0 : o ? V(t, n) : t, a = r.length - 1, i; a >= 0; a--)
100
- (i = r[a]) && (e = (o ? i(t, n, e) : i(e)) || e);
101
- return o && e && W(t, n, e), e;
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 p = "pie-modal";
104
- class d extends O(v) {
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(...arguments), this.isOpen = !1, this.headingLevel = "h2", this._handleCloseDialog = () => {
107
- this._dialog.close(), this._onDialogClose();
108
- }, this._onDialogClose = () => {
109
- const t = new CustomEvent(L);
110
- this.dispatchEvent(t);
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
- isOpen: t,
116
- heading: n,
117
- headingLevel: o = "h2"
118
- } = this, e = u(o);
119
- return b`
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
- <${e} class="c-modal-heading">${n}</${e}>
271
+ <${n} class="c-modal-heading">${e}</${n}>
123
272
  <pie-icon-button
124
- @click="${this._handleCloseDialog}"
273
+ @click="${this._triggerCloseModal}"
125
274
  variant="ghost-tertiary"
126
275
  class="c-modal-closeBtn"></pie-icon-button>
127
276
  </header>
128
- <article>
129
- <div class="c-modal-content">
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
- d.styles = g(S);
138
- l([
139
- s({ type: Boolean })
140
- ], d.prototype, "isOpen", 2);
141
- l([
142
- s({ type: String }),
143
- x(p)
144
- ], d.prototype, "heading", 2);
145
- l([
146
- s(),
147
- C(p, j, "h2")
148
- ], d.prototype, "headingLevel", 2);
149
- l([
150
- _("dialog")
151
- ], d.prototype, "_dialog", 2);
152
- customElements.define(p, d);
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
- d as PieModal,
155
- j as headingLevels
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
- * the heading of the modal .
4
+ * (Required) The text to display in the modal's heading.
4
5
  */
5
6
  heading: string;
6
7
  /**
7
- * the rendered heading tag of the modal header.
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: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
11
+ headingLevel: typeof headingLevels[number];
11
12
  /**
12
- * If `true`, the modal will be opened.
13
+ * (Optional) When true, the modal will be open.
13
14
  * @default false
14
15
  */
15
- isOpen?: boolean;
16
+ isOpen: boolean;
16
17
  }
17
- /**
18
- * Modal heading levels/tags
19
- */
20
- export declare const headingLevels: Array<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,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACtD;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,CAAwC,CAAC;AAErG;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,oBAAoB,CAAC"}
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: HTMLDialogElement;
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
- _handleCloseDialog: () => void;
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
- _onDialogClose: () => void;
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,EAAwB,MAAM,QAAQ,CAAC;AAGzE,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,EAAE,iBAAiB,CAAC;IAE/B,MAAM;IA2BN,kBAAkB,aAGhB;IAEF;;;;;OAKG;IACH,cAAc,aAGZ;IAGF,MAAM,CAAC,MAAM,0BAAqB;CACrC;AAID,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,CAAC,iBAAiB,CAAC,EAAE,QAAQ,CAAC;KACjC;CACJ"}
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.6.1",
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
- * the heading of the modal .
5
+ * (Required) The text to display in the modal's heading.
4
6
  */
5
7
  heading: string;
6
8
  /**
7
- * the rendered heading tag of the modal header.
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: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
12
+ headingLevel: typeof headingLevels[number];
11
13
  /**
12
- * If `true`, the modal will be opened.
14
+ * (Optional) When true, the modal will be open.
13
15
  * @default false
14
16
  */
15
- isOpen?: boolean;
17
+ isOpen: boolean;
16
18
  }
17
19
 
18
20
  /**
19
- * Modal heading levels/tags
21
+ * Event name for when the modal is closed.
22
+ *
23
+ * @constant
20
24
  */
21
- export const headingLevels: Array<ModalProps['headingLevel']> = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
25
+ export const ON_MODAL_CLOSE_EVENT = 'pie-modal-close';
22
26
 
23
27
  /**
24
- * Event name for when the modal is closed.
28
+ * Event name for when the modal is opened.
25
29
  *
26
30
  * @constant
27
31
  */
28
- export const ON_MODAL_CLOSE_EVENT = 'pie-modal-close';
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'; // eslint-disable-line import/no-extraneous-dependencies
4
- import { RtlMixin, validPropertyValues, requiredProperty } from '@justeattakeaway/pie-webc-core';
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 { ModalProps, headingLevels, ON_MODAL_CLOSE_EVENT } from './defs';
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: HTMLDialogElement;
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" ?open="${isOpen}">
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._handleCloseDialog}"
123
+ @click="${this._triggerCloseModal}"
44
124
  variant="ghost-tertiary"
45
125
  class="c-modal-closeBtn"></pie-icon-button>
46
126
  </header>
47
- <article>
48
- <div class="c-modal-content">
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
- _handleCloseDialog = () => {
57
- this._dialog.close();
58
- this._onDialogClose();
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
- _onDialogClose = () => {
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
- right: var(--dt-spacing-d);
60
- top: var(--dt-spacing-d);
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('`When the Pie Modal is closed`', () => {
31
- test.skip('should dispatch event `pie-modal-close`', async ({ mount }) => {
32
- const messages: string[] = [];
33
- const component = await mount(
34
- PieModal,
35
- {
36
- props: {
37
- isOpen: true,
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
- await component.update({
43
- on: {
44
- click: (event: string) => messages.push(event),
45
- },
47
+ await page.locator('.c-modal-closeBtn').click();
48
+
49
+
50
+ expect(messages).toHaveLength(1);
46
51
  });
52
+ });
47
53
 
48
- await component.locator('.c-modal-closeBtn').click({ });
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
- expect(messages).toHaveLength(1);
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}`, getLitPercyOptions());
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
+ });