@internetarchive/modal-manager 2.0.4-alpha-webdev7960.1 → 2.0.4

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,305 +1,305 @@
1
- import {
2
- LitElement,
3
- html,
4
- css,
5
- CSSResult,
6
- TemplateResult,
7
- PropertyValues,
8
- } from 'lit';
9
- import { property, customElement, query } from 'lit/decorators.js';
10
-
11
- import Modal from './shoelace/modal';
12
- import { getDeepestActiveElement } from './shoelace/active-elements';
13
-
14
- import './modal-template';
15
- import { ModalTemplate } from './modal-template';
16
- import { ModalConfig } from './modal-config';
17
- import { ModalManagerHostBridge } from './modal-manager-host-bridge';
18
- import { ModalManagerInterface } from './modal-manager-interface';
19
- import { ModalManagerHostBridgeInterface } from './modal-manager-host-bridge-interface';
20
- import { ModalManagerMode } from './modal-manager-mode';
21
-
22
- @customElement('modal-manager')
23
- export class ModalManager extends LitElement implements ModalManagerInterface {
24
- /**
25
- * The current mode of the ModalManager
26
- *
27
- * Current options are `modal` or `closed`
28
- *
29
- * @type {ModalManagerMode}
30
- * @memberof ModalManager
31
- */
32
- @property({ type: String, reflect: true }) mode: ModalManagerMode =
33
- ModalManagerMode.Closed;
34
-
35
- /**
36
- * Custom content to display in the modal's content slot
37
- *
38
- * @type {(TemplateResult | undefined)}
39
- * @memberof ModalManager
40
- */
41
- @property({ type: Object }) customModalContent?: TemplateResult;
42
-
43
- /**
44
- * This hostBridge handles environmental-specific interactions such as adding classes
45
- * to the body tag or event listeners needed to support the modal manager in the host environment.
46
- *
47
- * There is a default `ModalManagerHostBridge`, but consumers can override it with a custom
48
- * `ModalManagerHostBridgeInterface`
49
- *
50
- * @type {ModalManagerHostBridgeInterface}
51
- * @memberof ModalManager
52
- */
53
- @property({ type: Object })
54
- hostBridge: ModalManagerHostBridgeInterface = new ModalManagerHostBridge(
55
- this
56
- );
57
-
58
- /**
59
- * Reference to the ModalTemplate DOM element
60
- *
61
- * @private
62
- * @type {ModalTemplate}
63
- * @memberof ModalManager
64
- */
65
- @query('modal-template') private modalTemplate?: ModalTemplate;
66
-
67
- // Imported tab handling from shoelace
68
- public modal = new Modal(this);
69
-
70
- async firstUpdated(): Promise<void> {
71
- // Give the browser a chance to paint
72
- // eslint-disable-next-line no-promise-executor-return
73
- await new Promise(r => setTimeout(r, 0));
74
-
75
- if (this.closeOnBackdropClick) {
76
- this.addEventListener('keydown', (e: KeyboardEvent) => {
77
- if (e.key === 'Escape') {
78
- this.backdropClicked();
79
- }
80
- });
81
- }
82
- }
83
-
84
- disconnectedCallback() {
85
- super.disconnectedCallback();
86
- this.modal.deactivate();
87
- }
88
-
89
- /** @inheritdoc */
90
- render(): TemplateResult {
91
- return html`
92
- <div class="container">
93
- <div class="backdrop" @click=${this.backdropClicked}></div>
94
- <modal-template
95
- @closeButtonPressed=${this.closeButtonPressed}
96
- @leftNavButtonPressed=${this.callUserPressedLeftNavButtonCallback}
97
- tabindex="-1"
98
- >
99
- ${this.customModalContent}
100
- </modal-template>
101
- </div>
102
- `;
103
- }
104
-
105
- /** @inheritdoc */
106
- getMode(): ModalManagerMode {
107
- return this.mode;
108
- }
109
-
110
- /** @inheritdoc */
111
- closeModal(): void {
112
- this.mode = ModalManagerMode.Closed;
113
- this.customModalContent = undefined;
114
- if (this.modalTemplate) this.modalTemplate.config = new ModalConfig();
115
- this.modal.deactivate();
116
-
117
- // Return focus to the triggering element, if possible
118
- (this.triggeringElement as HTMLElement)?.focus?.();
119
- this.triggeringElement = undefined;
120
- }
121
-
122
- /**
123
- * Whether the modal should close if the user taps on the backdrop
124
- *
125
- * @private
126
- * @memberof ModalManager
127
- */
128
- private closeOnBackdropClick = true;
129
-
130
- /**
131
- * The element that had focus when the modal was opened, so that we can return focus
132
- * to it after the modal closes.
133
- */
134
- private triggeringElement?: Element;
135
-
136
- /**
137
- * A callback if the user closes the modal
138
- *
139
- * @private
140
- * @memberof ModalManager
141
- */
142
- private userClosedModalCallback?: () => void;
143
-
144
- /**
145
- * A callback if the user presses the left nav button
146
- *
147
- * @private
148
- * @memberof ModalManager
149
- */
150
- private userPressedLeftNavButtonCallback?: () => void;
151
-
152
- /**
153
- * Call the userClosedModalCallback and reset it if it exists
154
- *
155
- * @private
156
- * @memberof ModalManager
157
- */
158
- private callUserClosedModalCallback(): void {
159
- // we assign the callback to a temp var and undefine it before calling it
160
- // otherwise, we run into the potential for an infinite loop if the
161
- // callback triggers another `showModal()`, which would execute `userClosedModalCallback`
162
- const callback = this.userClosedModalCallback;
163
- this.userClosedModalCallback = undefined;
164
- if (callback) callback();
165
- }
166
-
167
- /**
168
- * Call the user pressed left nav button callback and reset it if it exists
169
- *
170
- * @private
171
- * @memberof ModalManager
172
- */
173
- private callUserPressedLeftNavButtonCallback(): void {
174
- // avoids an infinite showModal() loop, as above
175
- const callback = this.userPressedLeftNavButtonCallback;
176
- this.userPressedLeftNavButtonCallback = undefined;
177
- if (callback) callback();
178
- }
179
-
180
- /** @inheritdoc */
181
- async showModal(options: {
182
- config: ModalConfig;
183
- customModalContent?: TemplateResult;
184
- userClosedModalCallback?: () => void;
185
- userPressedLeftNavButtonCallback?: () => void;
186
- }): Promise<void> {
187
- // If the dialog is being opened, make note of what element was focused beforehand
188
- if (this.mode === ModalManagerMode.Closed) this.captureFocusedElement();
189
-
190
- this.closeOnBackdropClick = options.config.closeOnBackdropClick;
191
- this.userClosedModalCallback = options.userClosedModalCallback;
192
- this.userPressedLeftNavButtonCallback =
193
- options.userPressedLeftNavButtonCallback;
194
- this.customModalContent = options.customModalContent;
195
- this.mode = ModalManagerMode.Open;
196
- if (this.modalTemplate) {
197
- this.modalTemplate.config = options.config;
198
- await this.modalTemplate.updateComplete;
199
- this.modalTemplate.focus();
200
- }
201
- this.modal.activate();
202
- }
203
-
204
- /**
205
- * Sets the triggering element to the one that is currently focused, as deep
206
- * within Shadow DOM as possible.
207
- */
208
- private captureFocusedElement(): void {
209
- this.triggeringElement = getDeepestActiveElement();
210
- }
211
-
212
- /** @inheritdoc */
213
- updated(changed: PropertyValues): void {
214
- /* istanbul ignore else */
215
- if (changed.has('mode')) {
216
- this.handleModeChange();
217
- }
218
- }
219
-
220
- /**
221
- * Called when the backdrop is clicked
222
- *
223
- * @private
224
- * @memberof ModalManager
225
- */
226
- private backdropClicked(): void {
227
- if (this.closeOnBackdropClick) {
228
- this.closeModal();
229
- this.callUserClosedModalCallback();
230
- }
231
- }
232
-
233
- /**
234
- * Handle the mode change
235
- *
236
- * @private
237
- * @memberof ModalManager
238
- */
239
- private handleModeChange(): void {
240
- this.hostBridge.handleModeChange(this.mode);
241
- this.emitModeChangeEvent();
242
- }
243
-
244
- /**
245
- * Emit a modeChange event
246
- *
247
- * @private
248
- * @memberof ModalManager
249
- */
250
- private emitModeChangeEvent(): void {
251
- const event = new CustomEvent('modeChanged', {
252
- detail: { mode: this.mode },
253
- });
254
- this.dispatchEvent(event);
255
- }
256
-
257
- /**
258
- * Called when the modal close button is pressed. Closes the modal.
259
- *
260
- * @private
261
- * @memberof ModalManager
262
- */
263
- private closeButtonPressed(): void {
264
- this.closeModal();
265
- this.callUserClosedModalCallback();
266
- }
267
-
268
- /** @inheritdoc */
269
- static get styles(): CSSResult {
270
- const modalBackdropColor = css`var(--modalBackdropColor, rgba(10, 10, 10, 0.9))`;
271
- const modalBackdropZindex = css`var(--modalBackdropZindex, 1000)`;
272
-
273
- const modalWidth = css`var(--modalWidth, 32rem)`;
274
- const modalMaxWidth = css`var(--modalMaxWidth, 95%)`;
275
- const modalZindex = css`var(--modalZindex, 2000)`;
276
-
277
- return css`
278
- .container {
279
- width: 100%;
280
- height: 100%;
281
- }
282
-
283
- .backdrop {
284
- position: fixed;
285
- top: 0;
286
- left: 0;
287
- background-color: ${modalBackdropColor};
288
- width: 100%;
289
- height: 100%;
290
- z-index: ${modalBackdropZindex};
291
- }
292
-
293
- modal-template {
294
- outline: 0;
295
- position: fixed;
296
- top: 0;
297
- left: 50%;
298
- transform: translate(-50%, 0);
299
- z-index: ${modalZindex};
300
- width: ${modalWidth};
301
- max-width: ${modalMaxWidth};
302
- }
303
- `;
304
- }
305
- }
1
+ import {
2
+ LitElement,
3
+ html,
4
+ css,
5
+ CSSResult,
6
+ TemplateResult,
7
+ PropertyValues,
8
+ } from 'lit';
9
+ import { property, customElement, query } from 'lit/decorators.js';
10
+
11
+ import Modal from './shoelace/modal';
12
+ import { getDeepestActiveElement } from './shoelace/active-elements';
13
+
14
+ import './modal-template';
15
+ import { ModalTemplate } from './modal-template';
16
+ import { ModalConfig } from './modal-config';
17
+ import { ModalManagerHostBridge } from './modal-manager-host-bridge';
18
+ import { ModalManagerInterface } from './modal-manager-interface';
19
+ import { ModalManagerHostBridgeInterface } from './modal-manager-host-bridge-interface';
20
+ import { ModalManagerMode } from './modal-manager-mode';
21
+
22
+ @customElement('modal-manager')
23
+ export class ModalManager extends LitElement implements ModalManagerInterface {
24
+ /**
25
+ * The current mode of the ModalManager
26
+ *
27
+ * Current options are `modal` or `closed`
28
+ *
29
+ * @type {ModalManagerMode}
30
+ * @memberof ModalManager
31
+ */
32
+ @property({ type: String, reflect: true }) mode: ModalManagerMode =
33
+ ModalManagerMode.Closed;
34
+
35
+ /**
36
+ * Custom content to display in the modal's content slot
37
+ *
38
+ * @type {(TemplateResult | undefined)}
39
+ * @memberof ModalManager
40
+ */
41
+ @property({ type: Object }) customModalContent?: TemplateResult;
42
+
43
+ /**
44
+ * This hostBridge handles environmental-specific interactions such as adding classes
45
+ * to the body tag or event listeners needed to support the modal manager in the host environment.
46
+ *
47
+ * There is a default `ModalManagerHostBridge`, but consumers can override it with a custom
48
+ * `ModalManagerHostBridgeInterface`
49
+ *
50
+ * @type {ModalManagerHostBridgeInterface}
51
+ * @memberof ModalManager
52
+ */
53
+ @property({ type: Object })
54
+ hostBridge: ModalManagerHostBridgeInterface = new ModalManagerHostBridge(
55
+ this
56
+ );
57
+
58
+ /**
59
+ * Reference to the ModalTemplate DOM element
60
+ *
61
+ * @private
62
+ * @type {ModalTemplate}
63
+ * @memberof ModalManager
64
+ */
65
+ @query('modal-template') private modalTemplate?: ModalTemplate;
66
+
67
+ // Imported tab handling from shoelace
68
+ public modal = new Modal(this);
69
+
70
+ async firstUpdated(): Promise<void> {
71
+ // Give the browser a chance to paint
72
+ // eslint-disable-next-line no-promise-executor-return
73
+ await new Promise(r => setTimeout(r, 0));
74
+
75
+ if (this.closeOnBackdropClick) {
76
+ this.addEventListener('keydown', (e: KeyboardEvent) => {
77
+ if (e.key === 'Escape') {
78
+ this.backdropClicked();
79
+ }
80
+ });
81
+ }
82
+ }
83
+
84
+ disconnectedCallback() {
85
+ super.disconnectedCallback();
86
+ this.modal.deactivate();
87
+ }
88
+
89
+ /** @inheritdoc */
90
+ render(): TemplateResult {
91
+ return html`
92
+ <div class="container">
93
+ <div class="backdrop" @click=${this.backdropClicked}></div>
94
+ <modal-template
95
+ @closeButtonPressed=${this.closeButtonPressed}
96
+ @leftNavButtonPressed=${this.callUserPressedLeftNavButtonCallback}
97
+ tabindex="-1"
98
+ >
99
+ ${this.customModalContent}
100
+ </modal-template>
101
+ </div>
102
+ `;
103
+ }
104
+
105
+ /** @inheritdoc */
106
+ getMode(): ModalManagerMode {
107
+ return this.mode;
108
+ }
109
+
110
+ /** @inheritdoc */
111
+ closeModal(): void {
112
+ this.mode = ModalManagerMode.Closed;
113
+ this.customModalContent = undefined;
114
+ if (this.modalTemplate) this.modalTemplate.config = new ModalConfig();
115
+ this.modal.deactivate();
116
+
117
+ // Return focus to the triggering element, if possible
118
+ (this.triggeringElement as HTMLElement)?.focus?.();
119
+ this.triggeringElement = undefined;
120
+ }
121
+
122
+ /**
123
+ * Whether the modal should close if the user taps on the backdrop
124
+ *
125
+ * @private
126
+ * @memberof ModalManager
127
+ */
128
+ private closeOnBackdropClick = true;
129
+
130
+ /**
131
+ * The element that had focus when the modal was opened, so that we can return focus
132
+ * to it after the modal closes.
133
+ */
134
+ private triggeringElement?: Element;
135
+
136
+ /**
137
+ * A callback if the user closes the modal
138
+ *
139
+ * @private
140
+ * @memberof ModalManager
141
+ */
142
+ private userClosedModalCallback?: () => void;
143
+
144
+ /**
145
+ * A callback if the user presses the left nav button
146
+ *
147
+ * @private
148
+ * @memberof ModalManager
149
+ */
150
+ private userPressedLeftNavButtonCallback?: () => void;
151
+
152
+ /**
153
+ * Call the userClosedModalCallback and reset it if it exists
154
+ *
155
+ * @private
156
+ * @memberof ModalManager
157
+ */
158
+ private callUserClosedModalCallback(): void {
159
+ // we assign the callback to a temp var and undefine it before calling it
160
+ // otherwise, we run into the potential for an infinite loop if the
161
+ // callback triggers another `showModal()`, which would execute `userClosedModalCallback`
162
+ const callback = this.userClosedModalCallback;
163
+ this.userClosedModalCallback = undefined;
164
+ if (callback) callback();
165
+ }
166
+
167
+ /**
168
+ * Call the user pressed left nav button callback and reset it if it exists
169
+ *
170
+ * @private
171
+ * @memberof ModalManager
172
+ */
173
+ private callUserPressedLeftNavButtonCallback(): void {
174
+ // avoids an infinite showModal() loop, as above
175
+ const callback = this.userPressedLeftNavButtonCallback;
176
+ this.userPressedLeftNavButtonCallback = undefined;
177
+ if (callback) callback();
178
+ }
179
+
180
+ /** @inheritdoc */
181
+ async showModal(options: {
182
+ config: ModalConfig;
183
+ customModalContent?: TemplateResult;
184
+ userClosedModalCallback?: () => void;
185
+ userPressedLeftNavButtonCallback?: () => void;
186
+ }): Promise<void> {
187
+ // If the dialog is being opened, make note of what element was focused beforehand
188
+ if (this.mode === ModalManagerMode.Closed) this.captureFocusedElement();
189
+
190
+ this.closeOnBackdropClick = options.config.closeOnBackdropClick;
191
+ this.userClosedModalCallback = options.userClosedModalCallback;
192
+ this.userPressedLeftNavButtonCallback =
193
+ options.userPressedLeftNavButtonCallback;
194
+ this.customModalContent = options.customModalContent;
195
+ this.mode = ModalManagerMode.Open;
196
+ if (this.modalTemplate) {
197
+ this.modalTemplate.config = options.config;
198
+ await this.modalTemplate.updateComplete;
199
+ this.modalTemplate.focus();
200
+ }
201
+ this.modal.activate();
202
+ }
203
+
204
+ /**
205
+ * Sets the triggering element to the one that is currently focused, as deep
206
+ * within Shadow DOM as possible.
207
+ */
208
+ private captureFocusedElement(): void {
209
+ this.triggeringElement = getDeepestActiveElement();
210
+ }
211
+
212
+ /** @inheritdoc */
213
+ updated(changed: PropertyValues): void {
214
+ /* istanbul ignore else */
215
+ if (changed.has('mode')) {
216
+ this.handleModeChange();
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Called when the backdrop is clicked
222
+ *
223
+ * @private
224
+ * @memberof ModalManager
225
+ */
226
+ private backdropClicked(): void {
227
+ if (this.closeOnBackdropClick) {
228
+ this.closeModal();
229
+ this.callUserClosedModalCallback();
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Handle the mode change
235
+ *
236
+ * @private
237
+ * @memberof ModalManager
238
+ */
239
+ private handleModeChange(): void {
240
+ this.hostBridge.handleModeChange(this.mode);
241
+ this.emitModeChangeEvent();
242
+ }
243
+
244
+ /**
245
+ * Emit a modeChange event
246
+ *
247
+ * @private
248
+ * @memberof ModalManager
249
+ */
250
+ private emitModeChangeEvent(): void {
251
+ const event = new CustomEvent('modeChanged', {
252
+ detail: { mode: this.mode },
253
+ });
254
+ this.dispatchEvent(event);
255
+ }
256
+
257
+ /**
258
+ * Called when the modal close button is pressed. Closes the modal.
259
+ *
260
+ * @private
261
+ * @memberof ModalManager
262
+ */
263
+ private closeButtonPressed(): void {
264
+ this.closeModal();
265
+ this.callUserClosedModalCallback();
266
+ }
267
+
268
+ /** @inheritdoc */
269
+ static get styles(): CSSResult {
270
+ const modalBackdropColor = css`var(--modalBackdropColor, rgba(10, 10, 10, 0.9))`;
271
+ const modalBackdropZindex = css`var(--modalBackdropZindex, 1000)`;
272
+
273
+ const modalWidth = css`var(--modalWidth, 32rem)`;
274
+ const modalMaxWidth = css`var(--modalMaxWidth, 95%)`;
275
+ const modalZindex = css`var(--modalZindex, 2000)`;
276
+
277
+ return css`
278
+ .container {
279
+ width: 100%;
280
+ height: 100%;
281
+ }
282
+
283
+ .backdrop {
284
+ position: fixed;
285
+ top: 0;
286
+ left: 0;
287
+ background-color: ${modalBackdropColor};
288
+ width: 100%;
289
+ height: 100%;
290
+ z-index: ${modalBackdropZindex};
291
+ }
292
+
293
+ modal-template {
294
+ outline: 0;
295
+ position: fixed;
296
+ top: 0;
297
+ left: 50%;
298
+ transform: translate(-50%, 0);
299
+ z-index: ${modalZindex};
300
+ width: ${modalWidth};
301
+ max-width: ${modalMaxWidth};
302
+ }
303
+ `;
304
+ }
305
+ }