@internetarchive/modal-manager 2.0.1 → 2.0.3
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/dist/src/assets/arrow-left-icon.d.ts +2 -0
- package/dist/src/assets/arrow-left-icon.js +15 -0
- package/dist/src/assets/arrow-left-icon.js.map +1 -0
- package/dist/src/modal-config.d.ts +12 -0
- package/dist/src/modal-config.js +6 -4
- package/dist/src/modal-config.js.map +1 -1
- package/dist/src/modal-manager-interface.d.ts +2 -0
- package/dist/src/modal-manager-interface.js.map +1 -1
- package/dist/src/modal-manager.d.ts +16 -1
- package/dist/src/modal-manager.js +23 -4
- package/dist/src/modal-manager.js.map +1 -1
- package/dist/src/modal-template.d.ts +8 -0
- package/dist/src/modal-template.js +54 -1
- package/dist/src/modal-template.js.map +1 -1
- package/dist/test/modal-config.test.js +8 -0
- package/dist/test/modal-config.test.js.map +1 -1
- package/dist/test/modal-manager.test.js +41 -19
- package/dist/test/modal-manager.test.js.map +1 -1
- package/dist/test/modal-template.test.js +84 -12
- package/dist/test/modal-template.test.js.map +1 -1
- package/index.html +18 -0
- package/package.json +1 -1
- package/src/assets/arrow-left-icon.ts +15 -0
- package/src/modal-config.ts +17 -1
- package/src/modal-manager-interface.ts +2 -0
- package/src/modal-manager.ts +32 -5
- package/src/modal-template.ts +57 -1
- package/test/modal-config.test.ts +8 -0
- package/test/modal-manager.test.ts +28 -0
- package/test/modal-template.test.ts +82 -0
package/src/modal-manager.ts
CHANGED
|
@@ -61,7 +61,7 @@ export class ModalManager extends LitElement implements ModalManagerInterface {
|
|
|
61
61
|
* @type {ModalTemplate}
|
|
62
62
|
* @memberof ModalManager
|
|
63
63
|
*/
|
|
64
|
-
@query('modal-template') private modalTemplate
|
|
64
|
+
@query('modal-template') private modalTemplate?: ModalTemplate;
|
|
65
65
|
|
|
66
66
|
// Imported tab handling from shoelace
|
|
67
67
|
public modal = new Modal(this);
|
|
@@ -92,6 +92,7 @@ export class ModalManager extends LitElement implements ModalManagerInterface {
|
|
|
92
92
|
<div class="backdrop" @click=${this.backdropClicked}></div>
|
|
93
93
|
<modal-template
|
|
94
94
|
@closeButtonPressed=${this.closeButtonPressed}
|
|
95
|
+
@leftNavButtonPressed=${this.callUserPressedLeftNavButtonCallback}
|
|
95
96
|
tabindex="-1"
|
|
96
97
|
>
|
|
97
98
|
${this.customModalContent}
|
|
@@ -109,7 +110,7 @@ export class ModalManager extends LitElement implements ModalManagerInterface {
|
|
|
109
110
|
closeModal(): void {
|
|
110
111
|
this.mode = ModalManagerMode.Closed;
|
|
111
112
|
this.customModalContent = undefined;
|
|
112
|
-
this.modalTemplate.config = new ModalConfig();
|
|
113
|
+
if (this.modalTemplate) this.modalTemplate.config = new ModalConfig();
|
|
113
114
|
this.modal.deactivate();
|
|
114
115
|
}
|
|
115
116
|
|
|
@@ -129,6 +130,14 @@ export class ModalManager extends LitElement implements ModalManagerInterface {
|
|
|
129
130
|
*/
|
|
130
131
|
private userClosedModalCallback?: () => void;
|
|
131
132
|
|
|
133
|
+
/**
|
|
134
|
+
* A callback if the user presses the left nav button
|
|
135
|
+
*
|
|
136
|
+
* @private
|
|
137
|
+
* @memberof ModalManager
|
|
138
|
+
*/
|
|
139
|
+
private userPressedLeftNavButtonCallback?: () => void;
|
|
140
|
+
|
|
132
141
|
/**
|
|
133
142
|
* Call the userClosedModalCallback and reset it if it exists
|
|
134
143
|
*
|
|
@@ -144,19 +153,37 @@ export class ModalManager extends LitElement implements ModalManagerInterface {
|
|
|
144
153
|
if (callback) callback();
|
|
145
154
|
}
|
|
146
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Call the user pressed left nav button callback and reset it if it exists
|
|
158
|
+
*
|
|
159
|
+
* @private
|
|
160
|
+
* @memberof ModalManager
|
|
161
|
+
*/
|
|
162
|
+
private callUserPressedLeftNavButtonCallback(): void {
|
|
163
|
+
// avoids an infinite showModal() loop, as above
|
|
164
|
+
const callback = this.userPressedLeftNavButtonCallback;
|
|
165
|
+
this.userPressedLeftNavButtonCallback = undefined;
|
|
166
|
+
if (callback) callback();
|
|
167
|
+
}
|
|
168
|
+
|
|
147
169
|
/** @inheritdoc */
|
|
148
170
|
async showModal(options: {
|
|
149
171
|
config: ModalConfig;
|
|
150
172
|
customModalContent?: TemplateResult;
|
|
151
173
|
userClosedModalCallback?: () => void;
|
|
174
|
+
userPressedLeftNavButtonCallback?: () => void;
|
|
152
175
|
}): Promise<void> {
|
|
153
176
|
this.closeOnBackdropClick = options.config.closeOnBackdropClick;
|
|
154
177
|
this.userClosedModalCallback = options.userClosedModalCallback;
|
|
155
|
-
this.
|
|
178
|
+
this.userPressedLeftNavButtonCallback =
|
|
179
|
+
options.userPressedLeftNavButtonCallback;
|
|
156
180
|
this.customModalContent = options.customModalContent;
|
|
157
181
|
this.mode = ModalManagerMode.Open;
|
|
158
|
-
|
|
159
|
-
|
|
182
|
+
if (this.modalTemplate) {
|
|
183
|
+
this.modalTemplate.config = options.config;
|
|
184
|
+
await this.modalTemplate.updateComplete;
|
|
185
|
+
this.modalTemplate.focus();
|
|
186
|
+
}
|
|
160
187
|
this.modal.activate();
|
|
161
188
|
}
|
|
162
189
|
|
package/src/modal-template.ts
CHANGED
|
@@ -6,6 +6,7 @@ import '@internetarchive/icon-close';
|
|
|
6
6
|
|
|
7
7
|
import { ModalConfig } from './modal-config';
|
|
8
8
|
import IALogoIcon from './assets/ia-logo-icon';
|
|
9
|
+
import arrowLeftIcon from './assets/arrow-left-icon';
|
|
9
10
|
|
|
10
11
|
@customElement('modal-template')
|
|
11
12
|
export class ModalTemplate extends LitElement {
|
|
@@ -23,6 +24,9 @@ export class ModalTemplate extends LitElement {
|
|
|
23
24
|
<div class="modal-wrapper">
|
|
24
25
|
<div class="modal-container">
|
|
25
26
|
<header style="background-color: ${this.config.headerColor}">
|
|
27
|
+
${this.config.showLeftNavButton
|
|
28
|
+
? this.leftNavButtonTemplate
|
|
29
|
+
: nothing}
|
|
26
30
|
${this.config.showCloseButton ? this.closeButtonTemplate : ''}
|
|
27
31
|
${this.config.showHeaderLogo
|
|
28
32
|
? html`<div class="logo-icon">${IALogoIcon}</div>`
|
|
@@ -84,6 +88,25 @@ export class ModalTemplate extends LitElement {
|
|
|
84
88
|
this.dispatchEvent(event);
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Dispatch the `leftNavButtonPressed` event to the consumer
|
|
93
|
+
*
|
|
94
|
+
* @private
|
|
95
|
+
* @memberof ModalTemplate
|
|
96
|
+
*/
|
|
97
|
+
private handleLeftNavButtonPressed(e: Event): void {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
if (
|
|
100
|
+
e.type === 'keydown' &&
|
|
101
|
+
(e as KeyboardEvent).key !== ' ' &&
|
|
102
|
+
(e as KeyboardEvent).key !== 'Enter'
|
|
103
|
+
) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const event = new Event('leftNavButtonPressed');
|
|
107
|
+
this.dispatchEvent(event);
|
|
108
|
+
}
|
|
109
|
+
|
|
87
110
|
/**
|
|
88
111
|
* The close button template
|
|
89
112
|
*
|
|
@@ -105,6 +128,17 @@ export class ModalTemplate extends LitElement {
|
|
|
105
128
|
`;
|
|
106
129
|
}
|
|
107
130
|
|
|
131
|
+
private get leftNavButtonTemplate(): TemplateResult {
|
|
132
|
+
return html`<button
|
|
133
|
+
type="button"
|
|
134
|
+
class="back-button"
|
|
135
|
+
@click=${this.handleLeftNavButtonPressed}
|
|
136
|
+
@keydown=${this.handleLeftNavButtonPressed}
|
|
137
|
+
>
|
|
138
|
+
${arrowLeftIcon} ${this.config.leftNavButtonText ?? ''}
|
|
139
|
+
</button> `;
|
|
140
|
+
}
|
|
141
|
+
|
|
108
142
|
/** @inheritdoc */
|
|
109
143
|
static get styles(): CSSResult {
|
|
110
144
|
const modalLogoSize = css`var(--modalLogoSize, 6.5rem)`;
|
|
@@ -186,7 +220,7 @@ export class ModalTemplate extends LitElement {
|
|
|
186
220
|
}
|
|
187
221
|
|
|
188
222
|
.modal-body {
|
|
189
|
-
background-color: #
|
|
223
|
+
background-color: #fbfbfd;
|
|
190
224
|
border-radius: 0 0 calc(${modalCornerRadius}) calc(${modalCornerRadius});
|
|
191
225
|
border: ${modalBorder};
|
|
192
226
|
border-top: 0;
|
|
@@ -261,6 +295,28 @@ export class ModalTemplate extends LitElement {
|
|
|
261
295
|
0 4px 4px 0 rgba(0, 0, 0, 0.08);
|
|
262
296
|
}
|
|
263
297
|
|
|
298
|
+
.back-button {
|
|
299
|
+
position: absolute;
|
|
300
|
+
left: 1.2rem;
|
|
301
|
+
top: 1.2rem;
|
|
302
|
+
height: 2rem;
|
|
303
|
+
background-color: transparent;
|
|
304
|
+
outline: none;
|
|
305
|
+
border: none;
|
|
306
|
+
padding: 0;
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
color: white;
|
|
309
|
+
font-family: inherit;
|
|
310
|
+
display: flex;
|
|
311
|
+
flex-direction: row;
|
|
312
|
+
align-items: center;
|
|
313
|
+
gap: 0.5rem;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.back-button svg {
|
|
317
|
+
height: 1.5rem;
|
|
318
|
+
}
|
|
319
|
+
|
|
264
320
|
.sr-only {
|
|
265
321
|
position: absolute;
|
|
266
322
|
width: 1px;
|
|
@@ -24,6 +24,8 @@ describe('Modal Config', () => {
|
|
|
24
24
|
const showProcessingIndicator = true;
|
|
25
25
|
const processingImageMode = 'processing';
|
|
26
26
|
const showCloseButton = false;
|
|
27
|
+
const showLeftNavButton = false;
|
|
28
|
+
const leftNavButtonText = 'Previous';
|
|
27
29
|
const showHeaderLogo = false;
|
|
28
30
|
const closeOnBackdropClick = false;
|
|
29
31
|
|
|
@@ -36,6 +38,8 @@ describe('Modal Config', () => {
|
|
|
36
38
|
showProcessingIndicator: showProcessingIndicator,
|
|
37
39
|
processingImageMode: processingImageMode,
|
|
38
40
|
showCloseButton: showCloseButton,
|
|
41
|
+
showLeftNavButton: showLeftNavButton,
|
|
42
|
+
leftNavButtonText: leftNavButtonText,
|
|
39
43
|
showHeaderLogo: showHeaderLogo,
|
|
40
44
|
closeOnBackdropClick: closeOnBackdropClick,
|
|
41
45
|
});
|
|
@@ -49,6 +53,8 @@ describe('Modal Config', () => {
|
|
|
49
53
|
expect(config.showProcessingIndicator).to.equal(showProcessingIndicator);
|
|
50
54
|
expect(config.processingImageMode).to.equal(processingImageMode);
|
|
51
55
|
expect(config.showCloseButton).to.equal(showCloseButton);
|
|
56
|
+
expect(config.showLeftNavButton).to.equal(showLeftNavButton);
|
|
57
|
+
expect(config.leftNavButtonText).to.equal(leftNavButtonText);
|
|
52
58
|
expect(config.showHeaderLogo).to.equal(showHeaderLogo);
|
|
53
59
|
expect(config.closeOnBackdropClick).to.equal(closeOnBackdropClick);
|
|
54
60
|
});
|
|
@@ -63,6 +69,8 @@ describe('Modal Config', () => {
|
|
|
63
69
|
expect(config.showProcessingIndicator).to.equal(false);
|
|
64
70
|
expect(config.processingImageMode).to.equal('complete');
|
|
65
71
|
expect(config.showCloseButton).to.equal(true);
|
|
72
|
+
expect(config.showLeftNavButton).to.equal(false);
|
|
73
|
+
expect(config.leftNavButtonText).to.equal('');
|
|
66
74
|
expect(config.showHeaderLogo).to.equal(true);
|
|
67
75
|
expect(config.closeOnBackdropClick).to.equal(true);
|
|
68
76
|
});
|
|
@@ -151,6 +151,34 @@ describe('Modal Manager', () => {
|
|
|
151
151
|
expect(callbackCalled).to.equal(false);
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
+
it('calls the userPressedLeftNavButtonCallback when the user clicks the left nav button', async () => {
|
|
155
|
+
const el = (await fixture(html`
|
|
156
|
+
<modal-manager></modal-manager>
|
|
157
|
+
`)) as ModalManager;
|
|
158
|
+
|
|
159
|
+
const config = new ModalConfig();
|
|
160
|
+
config.showLeftNavButton = true;
|
|
161
|
+
|
|
162
|
+
let callbackCalled = false;
|
|
163
|
+
const callback = (): void => {
|
|
164
|
+
callbackCalled = true;
|
|
165
|
+
};
|
|
166
|
+
el.showModal({
|
|
167
|
+
config,
|
|
168
|
+
userPressedLeftNavButtonCallback: callback,
|
|
169
|
+
});
|
|
170
|
+
await elementUpdated(el);
|
|
171
|
+
|
|
172
|
+
const modalTemplate = el.shadowRoot?.querySelector('modal-template');
|
|
173
|
+
expect(modalTemplate).to.exist;
|
|
174
|
+
|
|
175
|
+
modalTemplate?.dispatchEvent(new Event('leftNavButtonPressed'));
|
|
176
|
+
|
|
177
|
+
await elementUpdated(el);
|
|
178
|
+
|
|
179
|
+
expect(callbackCalled).to.equal(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
154
182
|
it('mode is set to closed when close button is pressed', async () => {
|
|
155
183
|
const el = (await fixture(html`
|
|
156
184
|
<modal-manager></modal-manager>
|
|
@@ -57,6 +57,40 @@ describe('Modal Template', () => {
|
|
|
57
57
|
expect(response).to.exist;
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
it('emits leftNavButtonPressed event when left nav button is pressed', async () => {
|
|
61
|
+
const config = new ModalConfig();
|
|
62
|
+
config.showLeftNavButton = true;
|
|
63
|
+
const el = await fixture(html`
|
|
64
|
+
<modal-template .config=${config}></modal-template>
|
|
65
|
+
`);
|
|
66
|
+
|
|
67
|
+
const leftNavButton = el.shadowRoot?.querySelector('.back-button');
|
|
68
|
+
const clickEvent = new MouseEvent('click');
|
|
69
|
+
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
leftNavButton?.dispatchEvent(clickEvent);
|
|
72
|
+
});
|
|
73
|
+
const response = await oneEvent(el, 'leftNavButtonPressed', false);
|
|
74
|
+
expect(response).to.exist;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('emits leftNavButtonPressed event when left nav button gets spacebar pressed', async () => {
|
|
78
|
+
const config = new ModalConfig();
|
|
79
|
+
config.showLeftNavButton = true;
|
|
80
|
+
const el = await fixture(html`
|
|
81
|
+
<modal-template .config=${config}></modal-template>
|
|
82
|
+
`);
|
|
83
|
+
|
|
84
|
+
const leftNavButton = el.shadowRoot?.querySelector('.back-button');
|
|
85
|
+
const clickEvent = new KeyboardEvent('keydown', { key: ' ' });
|
|
86
|
+
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
leftNavButton?.dispatchEvent(clickEvent);
|
|
89
|
+
});
|
|
90
|
+
const response = await oneEvent(el, 'leftNavButtonPressed', false);
|
|
91
|
+
expect(response).to.exist;
|
|
92
|
+
});
|
|
93
|
+
|
|
60
94
|
it('shows the processing indicator if configured to', async () => {
|
|
61
95
|
const config = new ModalConfig();
|
|
62
96
|
config.showProcessingIndicator = true;
|
|
@@ -70,6 +104,54 @@ describe('Modal Template', () => {
|
|
|
70
104
|
expect('hidden' in classList).to.equal(false);
|
|
71
105
|
});
|
|
72
106
|
|
|
107
|
+
it('shows the left nav button if configured to', async () => {
|
|
108
|
+
const config = new ModalConfig();
|
|
109
|
+
config.showLeftNavButton = true;
|
|
110
|
+
const el = await fixture(html`
|
|
111
|
+
<modal-template .config=${config}></modal-template>
|
|
112
|
+
`);
|
|
113
|
+
|
|
114
|
+
const leftNavButton = el.shadowRoot?.querySelector('.back-button');
|
|
115
|
+
expect(leftNavButton).to.exist;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('hides the left nav button if configured to', async () => {
|
|
119
|
+
const config = new ModalConfig();
|
|
120
|
+
config.showCloseButton = false;
|
|
121
|
+
const el = await fixture(html`
|
|
122
|
+
<modal-template .config=${config}></modal-template>
|
|
123
|
+
`);
|
|
124
|
+
|
|
125
|
+
const closeButton = el.shadowRoot?.querySelector('.close-button');
|
|
126
|
+
expect(closeButton).to.not.exist;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('uses custom text for the left nav button if configured to', async () => {
|
|
130
|
+
const config = new ModalConfig();
|
|
131
|
+
config.showLeftNavButton = true;
|
|
132
|
+
config.leftNavButtonText = 'Previous';
|
|
133
|
+
const el = await fixture(html`
|
|
134
|
+
<modal-template .config=${config}></modal-template>
|
|
135
|
+
`);
|
|
136
|
+
|
|
137
|
+
const leftNavButton = el.shadowRoot?.querySelector('.back-button');
|
|
138
|
+
|
|
139
|
+
expect(leftNavButton).to.exist;
|
|
140
|
+
expect(leftNavButton?.innerHTML).to.contain('Previous');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('does not use any text for the left nav button if not configured to', async () => {
|
|
144
|
+
const config = new ModalConfig();
|
|
145
|
+
config.showLeftNavButton = true;
|
|
146
|
+
|
|
147
|
+
const el = await fixture(html`
|
|
148
|
+
<modal-template .config=${config}></modal-template>
|
|
149
|
+
`);
|
|
150
|
+
|
|
151
|
+
const leftNavButton = el.shadowRoot?.querySelector('.back-button');
|
|
152
|
+
expect(leftNavButton?.innerHTML).not.to.contain('Previous');
|
|
153
|
+
});
|
|
154
|
+
|
|
73
155
|
it('shows the close button if configured to', async () => {
|
|
74
156
|
const config = new ModalConfig();
|
|
75
157
|
config.showCloseButton = true;
|