@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.
@@ -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!: 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.modalTemplate.config = options.config;
178
+ this.userPressedLeftNavButtonCallback =
179
+ options.userPressedLeftNavButtonCallback;
156
180
  this.customModalContent = options.customModalContent;
157
181
  this.mode = ModalManagerMode.Open;
158
- await this.modalTemplate.updateComplete;
159
- this.modalTemplate.focus();
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
 
@@ -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: #f5f5f7;
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;