@stackoverflow/stacks 1.6.2 → 1.6.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.
@@ -13,7 +13,7 @@ export class ModalController extends Stacks.StacksController {
13
13
 
14
14
  private _boundTabTrap!: (event: KeyboardEvent) => void;
15
15
 
16
- connect () {
16
+ connect() {
17
17
  this.validate();
18
18
  }
19
19
 
@@ -22,26 +22,26 @@ export class ModalController extends Stacks.StacksController {
22
22
  */
23
23
  disconnect() {
24
24
  this.unbindDocumentEvents();
25
- };
25
+ }
26
26
 
27
27
  /**
28
28
  * Toggles the visibility of the modal
29
29
  */
30
- toggle (dispatcher: Event|Element|null = null) {
30
+ toggle(dispatcher: Event | Element | null = null) {
31
31
  this._toggle(undefined, dispatcher);
32
32
  }
33
33
 
34
34
  /**
35
35
  * Shows the modal
36
36
  */
37
- show (dispatcher: Event|Element|null = null) {
37
+ show(dispatcher: Event | Element | null = null) {
38
38
  this._toggle(true, dispatcher);
39
39
  }
40
40
 
41
41
  /**
42
42
  * Hides the modal
43
43
  */
44
- hide (dispatcher: Event|Element|null = null) {
44
+ hide(dispatcher: Event | Element | null = null) {
45
45
  this._toggle(false, dispatcher);
46
46
  }
47
47
 
@@ -52,10 +52,15 @@ export class ModalController extends Stacks.StacksController {
52
52
  // check for returnElement support
53
53
  const returnElementSelector = this.data.get("return-element");
54
54
  if (returnElementSelector) {
55
- this.returnElement = <HTMLElement>document.querySelector(returnElementSelector);
55
+ this.returnElement = <HTMLElement>(
56
+ document.querySelector(returnElementSelector)
57
+ );
56
58
 
57
59
  if (!this.returnElement) {
58
- throw "Unable to find element by return-element selector: " + returnElementSelector;
60
+ throw (
61
+ "Unable to find element by return-element selector: " +
62
+ returnElementSelector
63
+ );
59
64
  }
60
65
  }
61
66
  }
@@ -64,9 +69,13 @@ export class ModalController extends Stacks.StacksController {
64
69
  * Toggles the visibility of the modal element
65
70
  * @param show Optional parameter that force shows/hides the element or toggles it if left undefined
66
71
  */
67
- private _toggle (show?: boolean | undefined, dispatcher: Event|Element|null = null) {
72
+ private _toggle(
73
+ show?: boolean | undefined,
74
+ dispatcher: Event | Element | null = null
75
+ ) {
68
76
  let toShow = show;
69
- const isVisible = this.modalTarget.getAttribute("aria-hidden") === "false";
77
+ const isVisible =
78
+ this.modalTarget.getAttribute("aria-hidden") === "false";
70
79
 
71
80
  // if we're letting the class toggle, we need to figure out if the popover is visible manually
72
81
  if (typeof toShow === "undefined") {
@@ -81,10 +90,14 @@ export class ModalController extends Stacks.StacksController {
81
90
  const dispatchingElement = this.getDispatcher(dispatcher);
82
91
 
83
92
  // show/hide events trigger before toggling the class
84
- const triggeredEvent = this.triggerEvent(toShow ? "show" : "hide", {
85
- returnElement: this.returnElement,
86
- dispatcher: this.getDispatcher(dispatchingElement)
87
- }, this.modalTarget);
93
+ const triggeredEvent = this.triggerEvent(
94
+ toShow ? "show" : "hide",
95
+ {
96
+ returnElement: this.returnElement,
97
+ dispatcher: this.getDispatcher(dispatchingElement),
98
+ },
99
+ this.modalTarget
100
+ );
88
101
 
89
102
  // if this pre-show/hide event was prevented, don't attempt to continue changing the modal state
90
103
  if (triggeredEvent.defaultPrevented) {
@@ -97,29 +110,41 @@ export class ModalController extends Stacks.StacksController {
97
110
  if (toShow) {
98
111
  this.bindDocumentEvents();
99
112
  this.focusInsideModal();
100
- }
101
- else {
113
+ } else {
102
114
  this.unbindDocumentEvents();
103
115
  this.focusReturnElement();
104
116
  this.removeModalOnHide();
105
117
  }
106
118
 
107
119
  // check for transitionend support
108
- const supportsTransitionEnd = (this.modalTarget).ontransitionend !== undefined;
120
+ const supportsTransitionEnd =
121
+ this.modalTarget.ontransitionend !== undefined;
109
122
 
110
123
  // shown/hidden events trigger after toggling the class
111
124
  if (supportsTransitionEnd) {
112
125
  // wait until after the modal finishes transitioning to fire the event
113
- this.modalTarget.addEventListener("transitionend", () => {
114
- //TODO this is firing waaay to soon?
115
- this.triggerEvent(toShow ? "shown" : "hidden", {
116
- dispatcher: dispatchingElement
117
- }, this.modalTarget);
118
- }, { once: true });
126
+ this.modalTarget.addEventListener(
127
+ "transitionend",
128
+ () => {
129
+ //TODO this is firing waaay to soon?
130
+ this.triggerEvent(
131
+ toShow ? "shown" : "hidden",
132
+ {
133
+ dispatcher: dispatchingElement,
134
+ },
135
+ this.modalTarget
136
+ );
137
+ },
138
+ { once: true }
139
+ );
119
140
  } else {
120
- this.triggerEvent(toShow ? "shown" : "hidden", {
121
- dispatcher: dispatchingElement
122
- }, this.modalTarget);
141
+ this.triggerEvent(
142
+ toShow ? "shown" : "hidden",
143
+ {
144
+ dispatcher: dispatchingElement,
145
+ },
146
+ this.modalTarget
147
+ );
123
148
  }
124
149
  }
125
150
 
@@ -131,12 +156,19 @@ export class ModalController extends Stacks.StacksController {
131
156
  return;
132
157
  }
133
158
 
134
- this.modalTarget.addEventListener("s-modal:hidden", () => {
135
- // double check the element still exists when the event is called
136
- if (this.returnElement && document.body.contains(this.returnElement)) {
137
- this.returnElement.focus();
138
- }
139
- }, {once: true });
159
+ this.modalTarget.addEventListener(
160
+ "s-modal:hidden",
161
+ () => {
162
+ // double check the element still exists when the event is called
163
+ if (
164
+ this.returnElement &&
165
+ document.body.contains(this.returnElement)
166
+ ) {
167
+ this.returnElement.focus();
168
+ }
169
+ },
170
+ { once: true }
171
+ );
140
172
  }
141
173
 
142
174
  /**
@@ -147,17 +179,26 @@ export class ModalController extends Stacks.StacksController {
147
179
  return;
148
180
  }
149
181
 
150
- this.modalTarget.addEventListener("s-modal:hidden", () => {
151
- this.element.remove();
152
- }, {once: true });
182
+ this.modalTarget.addEventListener(
183
+ "s-modal:hidden",
184
+ () => {
185
+ this.element.remove();
186
+ },
187
+ { once: true }
188
+ );
153
189
  }
154
190
 
155
191
  /**
156
192
  * Gets all elements within the modal that could receive keyboard focus.
157
193
  */
158
194
  private getAllTabbables() {
159
- return Array.from(this.modalTarget.querySelectorAll<HTMLElement>("[href], input, select, textarea, button, [tabindex]"))
160
- .filter((el: Element) => el.matches(":not([disabled]):not([tabindex='-1'])"));
195
+ return Array.from(
196
+ this.modalTarget.querySelectorAll<HTMLElement>(
197
+ "[href], input, select, textarea, button, [tabindex]"
198
+ )
199
+ ).filter((el: Element) =>
200
+ el.matches(":not([disabled]):not([tabindex='-1'])")
201
+ );
161
202
  }
162
203
 
163
204
  /**
@@ -165,7 +206,7 @@ export class ModalController extends Stacks.StacksController {
165
206
  */
166
207
  private firstVisible(elements: HTMLElement[]) {
167
208
  // https://stackoverflow.com/a/21696585
168
- return elements.find(el => el.offsetParent !== null);
209
+ return elements.find((el) => el.offsetParent !== null);
169
210
  }
170
211
 
171
212
  /**
@@ -181,17 +222,22 @@ export class ModalController extends Stacks.StacksController {
181
222
  * Otherwise, the first visible focusable element will receive focus.
182
223
  */
183
224
  private focusInsideModal() {
184
- this.modalTarget.addEventListener("s-modal:shown", () => {
185
- const initialFocus = this.firstVisible(this.initialFocusTargets) ?? this.firstVisible(this.getAllTabbables());
186
- initialFocus?.focus();
187
- }, {once: true });
225
+ this.modalTarget.addEventListener(
226
+ "s-modal:shown",
227
+ () => {
228
+ const initialFocus =
229
+ this.firstVisible(this.initialFocusTargets) ??
230
+ this.firstVisible(this.getAllTabbables());
231
+ initialFocus?.focus();
232
+ },
233
+ { once: true }
234
+ );
188
235
  }
189
236
 
190
237
  /**
191
238
  * Returns keyboard focus to the modal if it has left or is about to leave.
192
239
  */
193
240
  private keepFocusWithinModal(e: KeyboardEvent) {
194
-
195
241
  // If somehow the user has tabbed out of the modal or if focus started outside the modal, push them to the first item.
196
242
  if (!this.modalTarget.contains(<Element>e.target)) {
197
243
  const focusTarget = this.firstVisible(this.getAllTabbables());
@@ -220,7 +266,7 @@ export class ModalController extends Stacks.StacksController {
220
266
  } else if (!e.shiftKey && e.target === lastTabbable) {
221
267
  e.preventDefault();
222
268
  firstTabbable.focus();
223
- }
269
+ }
224
270
  }
225
271
  }
226
272
  }
@@ -228,11 +274,14 @@ export class ModalController extends Stacks.StacksController {
228
274
  /**
229
275
  * Binds global events to the document for hiding popovers on user interaction
230
276
  */
231
- private bindDocumentEvents () {
277
+ private bindDocumentEvents() {
232
278
  // in order for removeEventListener to remove the right event, this bound function needs a constant reference
233
- this._boundClickFn = this._boundClickFn || this.hideOnOutsideClick.bind(this);
234
- this._boundKeypressFn = this._boundKeypressFn || this.hideOnEscapePress.bind(this);
235
- this._boundTabTrap = this._boundTabTrap || this.keepFocusWithinModal.bind(this);
279
+ this._boundClickFn =
280
+ this._boundClickFn || this.hideOnOutsideClick.bind(this);
281
+ this._boundKeypressFn =
282
+ this._boundKeypressFn || this.hideOnEscapePress.bind(this);
283
+ this._boundTabTrap =
284
+ this._boundTabTrap || this.keepFocusWithinModal.bind(this);
236
285
 
237
286
  document.addEventListener("mousedown", this._boundClickFn);
238
287
  document.addEventListener("keyup", this._boundKeypressFn);
@@ -242,7 +291,7 @@ export class ModalController extends Stacks.StacksController {
242
291
  /**
243
292
  * Unbinds global events to the document for hiding popovers on user interaction
244
293
  */
245
- private unbindDocumentEvents () {
294
+ private unbindDocumentEvents() {
246
295
  document.removeEventListener("mousedown", this._boundClickFn);
247
296
  document.removeEventListener("keyup", this._boundKeypressFn);
248
297
  document.removeEventListener("keydown", this._boundTabTrap);
@@ -251,11 +300,16 @@ export class ModalController extends Stacks.StacksController {
251
300
  /**
252
301
  * Forces the popover to hide if a user clicks outside of it or its reference element
253
302
  */
254
- private hideOnOutsideClick (e: Event) {
303
+ private hideOnOutsideClick(e: Event) {
255
304
  const target = <Node>e.target;
256
305
  // check if the document was clicked inside either the toggle element or the modal itself
257
306
  // note: .contains also returns true if the node itself matches the target element
258
- if (!this.modalTarget.querySelector(".s-modal--dialog")!.contains(target) && document.body.contains(target)) {
307
+ if (
308
+ !this.modalTarget
309
+ .querySelector(".s-modal--dialog")
310
+ ?.contains(target) &&
311
+ document.body.contains(target)
312
+ ) {
259
313
  this._toggle(false, e);
260
314
  }
261
315
  }
@@ -263,9 +317,12 @@ export class ModalController extends Stacks.StacksController {
263
317
  /**
264
318
  * Forces the popover to hide if the user presses escape while it, one of its childen, or the reference element are focused
265
319
  */
266
- private hideOnEscapePress (e: KeyboardEvent) {
320
+ private hideOnEscapePress(e: KeyboardEvent) {
267
321
  // if the ESC key (27) wasn't pressed or if no popovers are showing, return
268
- if (e.which !== 27 || this.modalTarget.getAttribute("aria-hidden") === "true") {
322
+ if (
323
+ e.which !== 27 ||
324
+ this.modalTarget.getAttribute("aria-hidden") === "true"
325
+ ) {
269
326
  return;
270
327
  }
271
328
 
@@ -276,14 +333,12 @@ export class ModalController extends Stacks.StacksController {
276
333
  * Determines the correct dispatching element from a potential input
277
334
  * @param dispatcher The event or element to get the dispatcher from
278
335
  */
279
- private getDispatcher(dispatcher: Event|Element|null = null) : Element {
336
+ private getDispatcher(dispatcher: Event | Element | null = null): Element {
280
337
  if (dispatcher instanceof Event) {
281
338
  return <Element>dispatcher.target;
282
- }
283
- else if (dispatcher instanceof Element) {
339
+ } else if (dispatcher instanceof Element) {
284
340
  return dispatcher;
285
- }
286
- else {
341
+ } else {
287
342
  return this.element;
288
343
  }
289
344
  }
@@ -311,7 +366,10 @@ export function hideModal(element: HTMLElement) {
311
366
  * @param show whether to force show/hide the modal; toggles the modal if left undefined
312
367
  */
313
368
  function toggleModal(element: HTMLElement, show?: boolean | undefined) {
314
- const controller = Stacks.application.getControllerForElementAndIdentifier(element, "s-modal") as ModalController;
369
+ const controller = Stacks.application.getControllerForElementAndIdentifier(
370
+ element,
371
+ "s-modal"
372
+ ) as ModalController;
315
373
 
316
374
  if (!controller) {
317
375
  throw "Unable to get s-modal controller from element";
@@ -1,7 +1,6 @@
1
1
  import * as Stacks from "../stacks";
2
2
 
3
3
  export class TabListController extends Stacks.StacksController {
4
-
5
4
  private boundSelectTab!: (event: MouseEvent) => void;
6
5
  private boundHandleKeydown!: (event: KeyboardEvent) => void;
7
6
 
@@ -59,8 +58,12 @@ export class TabListController extends Stacks.StacksController {
59
58
  }
60
59
 
61
60
  // Use circular navigation when users go past the first or last tab.
62
- if (tabIndex < 0) { tabIndex = tabs.length - 1; }
63
- if (tabIndex >= tabs.length) { tabIndex = 0; }
61
+ if (tabIndex < 0) {
62
+ tabIndex = tabs.length - 1;
63
+ }
64
+ if (tabIndex >= tabs.length) {
65
+ tabIndex = 0;
66
+ }
64
67
 
65
68
  tabElement = tabs[tabIndex];
66
69
  this.switchToTab(tabElement);
@@ -74,23 +77,30 @@ export class TabListController extends Stacks.StacksController {
74
77
  * the s-navigation-tablist:select event is prevented.
75
78
  */
76
79
  private switchToTab(newTab: HTMLElement) {
77
-
78
80
  const oldTab = this.selectedTab;
79
- if (oldTab === newTab) { return; }
81
+ if (oldTab === newTab) {
82
+ return;
83
+ }
80
84
 
81
- if (this.triggerEvent("select", { oldTab, newTab }).defaultPrevented) { return; }
85
+ if (this.triggerEvent("select", { oldTab, newTab }).defaultPrevented) {
86
+ return;
87
+ }
82
88
 
83
89
  this.selectedTab = newTab;
84
90
  this.triggerEvent("selected", { oldTab, newTab });
85
91
  }
86
-
92
+
87
93
  /**
88
94
  * Returns the currently selected tab or null if no tabs are selected.
89
95
  */
90
- public get selectedTab() : HTMLElement | null {
91
- return this.tabTargets.find(e => e.getAttribute("aria-selected") === "true") || null;
96
+ public get selectedTab(): HTMLElement | null {
97
+ return (
98
+ this.tabTargets.find(
99
+ (e) => e.getAttribute("aria-selected") === "true"
100
+ ) || null
101
+ );
92
102
  }
93
-
103
+
94
104
  /**
95
105
  * Switches the tablist to the provided tab, updating the tabs and panels
96
106
  * to reflect the change.
@@ -99,20 +109,20 @@ export class TabListController extends Stacks.StacksController {
99
109
  */
100
110
  public set selectedTab(selectedTab: HTMLElement | null) {
101
111
  for (const tab of this.tabTargets) {
102
- const panelId = tab.getAttribute('aria-controls');
112
+ const panelId = tab.getAttribute("aria-controls");
103
113
  const panel = panelId ? document.getElementById(panelId) : null;
104
114
 
105
115
  if (tab === selectedTab) {
106
- tab.classList.add('is-selected');
107
- tab.setAttribute('aria-selected', 'true');
108
- tab.removeAttribute('tabindex');
109
- panel?.classList.remove('d-none');
116
+ tab.classList.add("is-selected");
117
+ tab.setAttribute("aria-selected", "true");
118
+ tab.removeAttribute("tabindex");
119
+ panel?.classList.remove("d-none");
110
120
  } else {
111
- tab.classList.remove('is-selected');
112
- tab.setAttribute('aria-selected', 'false');
113
- tab.setAttribute('tabindex', '-1');
114
- panel?.classList.add('d-none');
121
+ tab.classList.remove("is-selected");
122
+ tab.setAttribute("aria-selected", "false");
123
+ tab.setAttribute("tabindex", "-1");
124
+ panel?.classList.add("d-none");
115
125
  }
116
126
  }
117
127
  }
118
- }
128
+ }