@stackoverflow/stacks 1.3.0 → 1.3.1

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.
Files changed (36) hide show
  1. package/dist/controllers/s-expandable-control.d.ts +1 -1
  2. package/dist/controllers/s-tooltip.d.ts +16 -1
  3. package/dist/css/stacks.css +715 -527
  4. package/dist/css/stacks.min.css +1 -1
  5. package/dist/js/stacks.js +153 -84
  6. package/dist/js/stacks.min.js +1 -1
  7. package/lib/css/atomic/misc.less +1 -1
  8. package/lib/css/atomic/typography.less +0 -6
  9. package/lib/css/components/activity-indicator.less +18 -17
  10. package/lib/css/components/avatars.less +50 -131
  11. package/lib/css/components/breadcrumbs.less +4 -4
  12. package/lib/css/components/buttons.less +8 -48
  13. package/lib/css/components/empty-states.less +15 -0
  14. package/lib/css/components/{collapsible.less → expandable.less} +0 -0
  15. package/lib/css/components/inputs.less +37 -101
  16. package/lib/css/components/labels.less +98 -0
  17. package/lib/css/components/notices.less +190 -163
  18. package/lib/css/components/post-summary.less +4 -4
  19. package/lib/css/components/progress-bars.less +1 -1
  20. package/lib/css/components/prose.less +4 -4
  21. package/lib/css/components/spinner.less +39 -1
  22. package/lib/css/components/tables.less +1 -5
  23. package/lib/css/components/uploader.less +70 -84
  24. package/lib/css/exports/constants-colors.less +14 -0
  25. package/lib/css/stacks-dynamic.less +0 -1
  26. package/lib/css/stacks-static.less +3 -2
  27. package/lib/ts/controllers/s-expandable-control.ts +23 -19
  28. package/lib/ts/controllers/s-modal.ts +16 -16
  29. package/lib/ts/controllers/s-navigation-tablist.ts +13 -13
  30. package/lib/ts/controllers/s-popover.ts +26 -18
  31. package/lib/ts/controllers/s-table.ts +31 -29
  32. package/lib/ts/controllers/s-tooltip.ts +62 -23
  33. package/lib/ts/stacks.ts +8 -4
  34. package/package.json +17 -17
  35. package/lib/css/components/banners.less +0 -80
  36. package/lib/css/components/blank-states.less +0 -26
@@ -1,11 +1,11 @@
1
1
  import { createPopper, Placement } from '@popperjs/core';
2
+ import type * as Popper from '@popperjs/core';
2
3
  import * as Stacks from "../stacks";
3
4
 
4
5
  type OutsideClickBehavior = "always" | "never" | "if-in-viewport" | "after-dismissal";
5
6
 
6
7
  export abstract class BasePopoverController extends Stacks.StacksController {
7
- // @ts-ignore
8
- private popper!: Popper;
8
+ private popper!: Popper.Instance;
9
9
 
10
10
  protected popoverElement!: HTMLElement;
11
11
 
@@ -87,6 +87,8 @@ export abstract class BasePopoverController extends Stacks.StacksController {
87
87
  this.hide();
88
88
  if (this.popper) {
89
89
  this.popper.destroy();
90
+ // eslint-disable-next-line
91
+ // @ts-ignore The operand of a 'delete' operator must be optional .ts(2790)
90
92
  delete this.popper;
91
93
  }
92
94
  super.disconnect();
@@ -105,7 +107,7 @@ export abstract class BasePopoverController extends Stacks.StacksController {
105
107
  show(dispatcher: Event|Element|null = null) {
106
108
  if (this.isVisible) { return; }
107
109
 
108
- let dispatcherElement = this.getDispatcher(dispatcher);
110
+ const dispatcherElement = this.getDispatcher(dispatcher);
109
111
 
110
112
  if (this.triggerEvent("show", {
111
113
  dispatcher: dispatcherElement
@@ -115,7 +117,7 @@ export abstract class BasePopoverController extends Stacks.StacksController {
115
117
  this.initializePopper();
116
118
  }
117
119
 
118
- this.popoverElement!.classList.add("is-visible");
120
+ this.popoverElement.classList.add("is-visible");
119
121
 
120
122
  // ensure the popper has been positioned correctly
121
123
  this.scheduleUpdate();
@@ -129,7 +131,7 @@ export abstract class BasePopoverController extends Stacks.StacksController {
129
131
  hide(dispatcher: Event|Element|null = null) {
130
132
  if (!this.isVisible) { return; }
131
133
 
132
- let dispatcherElement = this.getDispatcher(dispatcher);
134
+ const dispatcherElement = this.getDispatcher(dispatcher);
133
135
 
134
136
  if (this.triggerEvent("hide", {
135
137
  dispatcher: dispatcherElement
@@ -140,6 +142,8 @@ export abstract class BasePopoverController extends Stacks.StacksController {
140
142
  if (this.popper) {
141
143
  // completely destroy the popper on hide; this is in line with Popper.js's performance recommendations
142
144
  this.popper.destroy();
145
+ // eslint-disable-next-line
146
+ // @ts-ignore The operand of a 'delete' operator must be optional .ts(2790)
143
147
  delete this.popper;
144
148
  }
145
149
 
@@ -182,7 +186,6 @@ export abstract class BasePopoverController extends Stacks.StacksController {
182
186
  * Initializes the Popper for this instance
183
187
  */
184
188
  private initializePopper() {
185
- // @ts-ignore
186
189
  this.popper = createPopper(this.referenceElement, this.popoverElement, {
187
190
  placement: this.data.get("placement") as Placement || "bottom",
188
191
  modifiers: [
@@ -206,7 +209,7 @@ export abstract class BasePopoverController extends Stacks.StacksController {
206
209
  * Validates the popover settings and attempts to set necessary internal variables
207
210
  */
208
211
  private validate() {
209
- var referenceSelector = this.data.get("reference-selector");
212
+ const referenceSelector = this.data.get("reference-selector");
210
213
 
211
214
  this.referenceElement = <HTMLElement>this.element;
212
215
 
@@ -221,7 +224,7 @@ export abstract class BasePopoverController extends Stacks.StacksController {
221
224
 
222
225
  const popoverId = this.referenceElement.getAttribute(this.popoverSelectorAttribute);
223
226
 
224
- var popoverElement = null;
227
+ let popoverElement: HTMLElement | null = null;
225
228
 
226
229
  // if the popover is named, attempt to fetch it (and throw an error if it doesn't exist)
227
230
  if (popoverId) {
@@ -264,7 +267,7 @@ export abstract class BasePopoverController extends Stacks.StacksController {
264
267
  */
265
268
  protected scheduleUpdate() {
266
269
  if (this.popper && this.isVisible) {
267
- this.popper.update();
270
+ void this.popper.update();
268
271
  }
269
272
  }
270
273
  }
@@ -274,8 +277,8 @@ export class PopoverController extends BasePopoverController {
274
277
 
275
278
  protected popoverSelectorAttribute = "aria-controls";
276
279
 
277
- private boundHideOnOutsideClick!: any;
278
- private boundHideOnEscapePress!: any;
280
+ private boundHideOnOutsideClick!: (event: MouseEvent) => void;
281
+ private boundHideOnEscapePress!: (event: KeyboardEvent) => void;
279
282
 
280
283
  /**
281
284
  * Toggles optional classes and accessibility attributes in addition to BasePopoverController.shown
@@ -332,7 +335,7 @@ export class PopoverController extends BasePopoverController {
332
335
  const target = <Node>e.target;
333
336
  // check if the document was clicked inside either the reference element or the popover itself
334
337
  // note: .contains also returns true if the node itself matches the target element
335
- if (this.shouldHideOnOutsideClick && !this.referenceElement.contains(target) && !this.popoverElement!.contains(target) && document.body.contains(target)) {
338
+ if (this.shouldHideOnOutsideClick && !this.referenceElement.contains(target) && !this.popoverElement.contains(target) && document.body.contains(target)) {
336
339
  this.hide(e);
337
340
  }
338
341
  };
@@ -349,7 +352,7 @@ export class PopoverController extends BasePopoverController {
349
352
 
350
353
  // check if the target was inside the popover element and refocus the triggering element
351
354
  // note: .contains also returns true if the node itself matches the target element
352
- if (this.popoverElement!.contains(<Node>e.target)) {
355
+ if (this.popoverElement.contains(<Node>e.target)) {
353
356
  this.referenceElement.focus();
354
357
  }
355
358
 
@@ -364,8 +367,10 @@ export class PopoverController extends BasePopoverController {
364
367
  if (!this.data.has("toggle-class")) {
365
368
  return;
366
369
  }
367
- var cl = this.referenceElement.classList;
368
- this.data.get("toggle-class")!.split(/\s+/).forEach(function (cls: string) {
370
+
371
+ const toggleClass = this.data.get("toggle-class") || "";
372
+ const cl = this.referenceElement.classList;
373
+ toggleClass.split(/\s+/).forEach(function (cls: string) {
369
374
  cl.toggle(cls, show);
370
375
  });
371
376
  }
@@ -375,7 +380,9 @@ export class PopoverController extends BasePopoverController {
375
380
  * @param {boolean=} show - A boolean indicating whether this is being triggered by a show or hide.
376
381
  */
377
382
  private toggleAccessibilityAttributes(show?: boolean) {
378
- this.referenceElement.ariaExpanded = show?.toString() || this.referenceElement.ariaExpanded || 'false';
383
+ const expandedValue = show?.toString() || this.referenceElement.ariaExpanded || "false";
384
+ this.referenceElement.ariaExpanded = expandedValue;
385
+ this.referenceElement.setAttribute("aria-expanded", expandedValue);
379
386
  }
380
387
  }
381
388
 
@@ -453,6 +460,7 @@ export function attachPopover(element: Element, popover: Element | string, optio
453
460
  }
454
461
 
455
462
  if (typeof popover === 'string') {
463
+ // eslint-disable-next-line no-unsanitized/method
456
464
  const elements = document.createRange().createContextualFragment(popover).children;
457
465
  if (elements.length !== 1) {
458
466
  throw "popover should contain a single element";
@@ -461,7 +469,7 @@ export function attachPopover(element: Element, popover: Element | string, optio
461
469
  }
462
470
 
463
471
  const existingId = referenceElement.getAttribute("aria-controls");
464
- var popoverId = popover.id;
472
+ let popoverId = popover.id;
465
473
 
466
474
  if (!popover.classList.contains('s-popover')) {
467
475
  throw `popover should have the "s-popover" class but had class="${popover.className}"`;
@@ -557,7 +565,7 @@ function getPopover(element: Element): GetPopoverResult {
557
565
  * @param include Whether to add the controllerName value
558
566
  */
559
567
  function toggleController(el: Element, controllerName: string, include: boolean) {
560
- var controllers = new Set(el.getAttribute('data-controller')?.split(/\s+/));
568
+ const controllers = new Set(el.getAttribute('data-controller')?.split(/\s+/));
561
569
  if (include) {
562
570
  controllers.add(controllerName);
563
571
  } else {
@@ -9,14 +9,15 @@ export class TableController extends Stacks.StacksController {
9
9
  if (["asc", "desc", "none"].indexOf(direction) < 0) {
10
10
  throw "direction must be one of asc, desc, or none"
11
11
  }
12
- var controller = this;
12
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
13
+ const controller = this;
13
14
  this.columnTargets.forEach(function (target) {
14
- var isCurrrent = target === headElem;
15
+ const isCurrrent = target === headElem;
15
16
 
16
17
  target.classList.toggle("is-sorted", isCurrrent && direction !== "none");
17
18
 
18
19
  target.querySelectorAll(".js-sorting-indicator").forEach(function (icon) {
19
- var visible = isCurrrent ? direction : "none";
20
+ const visible = isCurrrent ? direction : "none";
20
21
  icon.classList.toggle("d-none", !icon.classList.contains("js-sorting-indicator-" + visible));
21
22
  });
22
23
 
@@ -29,16 +30,17 @@ export class TableController extends Stacks.StacksController {
29
30
  };
30
31
 
31
32
  sort(evt: Event) {
32
- var controller = this;
33
- var colHead = evt.currentTarget;
33
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
34
+ const controller = this;
35
+ const colHead = evt.currentTarget;
34
36
  if (!(colHead instanceof HTMLTableCellElement)) {
35
37
  throw "invalid event target";
36
38
  }
37
- var table = this.element as HTMLTableElement;
38
- var tbody = table.tBodies[0];
39
+ const table = this.element as HTMLTableElement;
40
+ const tbody = table.tBodies[0];
39
41
 
40
42
  // the column slot number of the clicked header
41
- var colno = getCellSlot(colHead);
43
+ const colno = getCellSlot(colHead);
42
44
 
43
45
  if (colno < 0) { // this shouldn't happen if the clicked element is actually a column head
44
46
  return;
@@ -46,23 +48,23 @@ export class TableController extends Stacks.StacksController {
46
48
 
47
49
  // an index of the <tbody>, so we can find out for each row which <td> element is
48
50
  // in the same column slot as the header
49
- var slotIndex = buildIndex(tbody);
51
+ const slotIndex = buildIndex(tbody);
50
52
 
51
53
  // the default behavior when clicking a header is to sort by this column in ascending
52
54
  // direction, *unless* it is already sorted that way
53
- var direction = this.getElementData(colHead, "sort-direction") === "asc" ? -1 : 1;
55
+ const direction = this.getElementData(colHead, "sort-direction") === "asc" ? -1 : 1;
54
56
 
55
- var rows = Array.from(table.tBodies[0].rows);
57
+ const rows = Array.from(table.tBodies[0].rows);
56
58
 
57
59
  // if this is still false after traversing the data, that means all values are integers (or empty)
58
60
  // and thus we'll sort numerically.
59
- var anyNonInt = false;
61
+ let anyNonInt = false;
60
62
 
61
63
  // data will be a list of tuples [value, rowNum], where value is what we're sorting by
62
- var data: [string | number, number][] = [];
63
- var firstBottomRow: HTMLTableRowElement;
64
+ const data: [string | number, number][] = [];
65
+ let firstBottomRow: HTMLTableRowElement;
64
66
  rows.forEach(function (row, index) {
65
- var force = controller.getElementData(row, "sort-to");
67
+ const force = controller.getElementData(row, "sort-to");
66
68
  if (force === "top") {
67
69
  return; // rows not added to the list will automatically end up at the top
68
70
  } else if (force === "bottom") {
@@ -71,7 +73,7 @@ export class TableController extends Stacks.StacksController {
71
73
  }
72
74
  return;
73
75
  }
74
- var cell = slotIndex[index][colno];
76
+ const cell = slotIndex[index][colno];
75
77
  if (!cell) {
76
78
  data.push(["", index]);
77
79
  return;
@@ -79,10 +81,10 @@ export class TableController extends Stacks.StacksController {
79
81
 
80
82
  // unless the to-be-sorted-by value is explicitly provided on the element via this attribute,
81
83
  // the value we're using is the cell's text, trimmed of any whitespace
82
- var explicit = controller.getElementData(cell, "sort-val");
83
- var d = typeof explicit === "string" ? explicit : cell.textContent!.trim();
84
+ const explicit = controller.getElementData(cell, "sort-val");
85
+ const d = typeof explicit === "string" ? explicit : cell.textContent!.trim();
84
86
 
85
- if ((d !== "") && (parseInt(d, 10) + "" !== d)) {
87
+ if ((d !== "") && (`${parseInt(d, 10)}` !== d)) {
86
88
  anyNonInt = true;
87
89
  }
88
90
  data.push([d, index]);
@@ -114,7 +116,7 @@ export class TableController extends Stacks.StacksController {
114
116
 
115
117
  // this is the actual reordering of the table rows
116
118
  data.forEach(function (tup) {
117
- var row = rows[tup[1]];
119
+ const row = rows[tup[1]];
118
120
  row.parentElement!.removeChild(row);
119
121
  if (firstBottomRow) {
120
122
  tbody.insertBefore(row, firstBottomRow);
@@ -165,29 +167,29 @@ function getCellSlot(cell: HTMLTableCellElement): number {
165
167
  // If the second argument is given, it's a <td> or <th> that we're trying to find, and the algorithm
166
168
  // stops as soon as it has found it and the function returns its slot number.
167
169
  function buildIndexOrGetCellSlot(section: HTMLTableSectionElement, findCell?: HTMLTableCellElement) {
168
- var index = [];
169
- var curRow = section.children[0];
170
+ const index = [];
171
+ let curRow = section.children[0];
170
172
 
171
173
  // the elements of these two arrays are synchronized; the first array contains table cell elements,
172
174
  // the second one contains a number that indicates for how many more rows this elements will
173
175
  // exist (i.e. the value is initially one less than the cell's rowspan, and will be decreased for each row)
174
- var growing: HTMLTableCellElement[] = [];
175
- var growingRowsLeft: number[] = [];
176
+ const growing: HTMLTableCellElement[] = [];
177
+ const growingRowsLeft: number[] = [];
176
178
 
177
179
  // continue while we have actual <tr>'s left *or* we still have rowspan'ed elements that aren't done
178
180
  while (curRow || growingRowsLeft.some(function (e) { return e !== 0; })) {
179
- var curIndexRow: HTMLTableCellElement[] = [];
181
+ const curIndexRow: HTMLTableCellElement[] = [];
180
182
  index.push(curIndexRow);
181
183
 
182
- var curSlot = 0;
184
+ let curSlot = 0;
183
185
  if (curRow) {
184
- for (var curCellInd = 0; curCellInd < curRow.children.length; curCellInd++) {
186
+ for (let curCellInd = 0; curCellInd < curRow.children.length; curCellInd++) {
185
187
  while (growingRowsLeft[curSlot]) {
186
188
  growingRowsLeft[curSlot]--;
187
189
  curIndexRow[curSlot] = growing[curSlot];
188
190
  curSlot++;
189
191
  }
190
- var cell = curRow.children[curCellInd];
192
+ const cell = curRow.children[curCellInd];
191
193
  if (!(cell instanceof HTMLTableCellElement)) {
192
194
  throw "invalid table"
193
195
  }
@@ -197,7 +199,7 @@ function buildIndexOrGetCellSlot(section: HTMLTableSectionElement, findCell?: HT
197
199
  if (cell === findCell) {
198
200
  return curSlot;
199
201
  }
200
- var nextFreeSlot = curSlot + cell.colSpan;
202
+ const nextFreeSlot = curSlot + cell.colSpan;
201
203
  for (; curSlot < nextFreeSlot; curSlot++) {
202
204
  growingRowsLeft[curSlot] = cell.rowSpan - 1; // if any of these is already growing, the table is broken -- no guarantees of anything
203
205
  growing[curSlot] = cell;
@@ -10,10 +10,12 @@ export class TooltipController extends BasePopoverController {
10
10
 
11
11
  protected popoverSelectorAttribute = "aria-describedby";
12
12
 
13
- private boundScheduleShow!: any;
14
- private boundHide!: any;
15
- private boundHideIfWithin!: any;
16
- private activeTimeout!: any;
13
+ private boundScheduleShow!: () => void;
14
+ private boundHide!: () => void;
15
+ private boundHideIfWithin!: () => void;
16
+ private boundHideOnEscapeKeyEvent!: () => void;
17
+ private boundClearActiveTimeout!: () => void;
18
+ private activeTimeout!: number;
17
19
 
18
20
  /**
19
21
  * Binds mouseover and mouseout events in addition to BasePopoverController.connect
@@ -27,12 +29,14 @@ export class TooltipController extends BasePopoverController {
27
29
  if (window.matchMedia("(hover: hover)").matches) {
28
30
  this.bindMouseEvents();
29
31
  }
32
+ this.bindKeyboardEvents();
30
33
  }
31
34
 
32
35
  /**
33
36
  * Unbinds mouse events in addition to BasePopoverController.disconnect
34
37
  */
35
38
  disconnect() {
39
+ this.unbindKeyboardEvents();
36
40
  this.unbindMouseEvents();
37
41
  super.disconnect();
38
42
  }
@@ -43,7 +47,7 @@ export class TooltipController extends BasePopoverController {
43
47
  */
44
48
  show(dispatcher: Event|Element|null = null) {
45
49
  // check and see if this controller coexists with a popover
46
- var controller = Stacks.application.getControllerForElementAndIdentifier(this.element, "s-popover");
50
+ const controller = Stacks.application.getControllerForElementAndIdentifier(this.element, "s-popover");
47
51
 
48
52
  // if the controller exists and already has a visible popover, don't show the tooltip
49
53
  if (controller && (<PopoverController>controller).isVisible) {
@@ -64,11 +68,16 @@ export class TooltipController extends BasePopoverController {
64
68
  /**
65
69
  * Cancels the scheduled tooltip popover display and hides it if already displayed
66
70
  */
67
- hide(dispatcher: Event | Element | null = null) {
71
+ scheduleHide(dispatcher: Event | Element | null = null) {
68
72
  window.clearTimeout(this.activeTimeout);
69
- this.activeTimeout = null;
73
+ this.activeTimeout = window.setTimeout(() => super.hide(dispatcher), 100);
74
+ }
70
75
 
71
- super.hide(dispatcher);
76
+ /**
77
+ * Cancels the activeTimeout
78
+ */
79
+ clearActiveTimeout() {
80
+ clearTimeout(this.activeTimeout);
72
81
  }
73
82
 
74
83
  /**
@@ -76,13 +85,14 @@ export class TooltipController extends BasePopoverController {
76
85
  */
77
86
  applyTitleAttributes() {
78
87
 
79
- var content: Node;
88
+ let content: Node;
80
89
 
81
- var htmlTitle = this.data.get("html-title");
90
+ const htmlTitle = this.data.get("html-title");
82
91
  if (htmlTitle) {
92
+ // eslint-disable-next-line no-unsanitized/method
83
93
  content = document.createRange().createContextualFragment(htmlTitle);
84
94
  } else {
85
- var plainTitle = this.element.getAttribute("title");
95
+ const plainTitle = this.element.getAttribute("title");
86
96
  if (plainTitle) {
87
97
  content = document.createTextNode(plainTitle);
88
98
  } else {
@@ -93,21 +103,20 @@ export class TooltipController extends BasePopoverController {
93
103
  this.data.delete("html-title");
94
104
  this.element.removeAttribute("title");
95
105
 
96
- var popoverId = this.element.getAttribute("aria-describedby");
106
+ let popoverId = this.element.getAttribute("aria-describedby");
97
107
  if (!popoverId) {
98
108
  popoverId = TooltipController.generateId();
99
109
  this.element.setAttribute("aria-describedby", popoverId);
100
110
  }
101
111
 
102
- var popover = document.getElementById(popoverId);
112
+ let popover = document.getElementById(popoverId);
103
113
  if (!popover) {
104
114
  popover = document.createElement("div");
105
115
  popover.id = popoverId;
106
- popover.className = "s-popover s-popover__tooltip pe-none";
107
- popover.setAttribute("aria-hidden", "true");
116
+ popover.className = "s-popover s-popover__tooltip";
108
117
  popover.setAttribute("role", "tooltip");
109
118
 
110
- var parentNode = this.element.parentNode;
119
+ const parentNode = this.element.parentNode;
111
120
  if (parentNode) {
112
121
  // insertBefore inserts at end if element.nextSibling is null.
113
122
  parentNode.insertBefore(popover, this.element.nextSibling);
@@ -165,21 +174,49 @@ export class TooltipController extends BasePopoverController {
165
174
  */
166
175
  private hideIfWithin(event: Event) {
167
176
  if ((<Element>event.target!).contains(this.referenceElement)) {
168
- this.hide();
177
+ this.scheduleHide();
178
+ }
179
+ }
180
+
181
+ private hideOnEscapeKeyEvent(event: KeyboardEvent) {
182
+ if (event.key === "Escape") {
183
+ this.scheduleHide();
169
184
  }
170
185
  }
186
+ /**
187
+ * Binds mouse events to show/hide on reference element hover
188
+ */
189
+ private bindKeyboardEvents() {
190
+ this.boundScheduleShow = this.boundScheduleShow || this.scheduleShow.bind(this);
191
+ this.boundHide = this.boundHide || this.scheduleHide.bind(this);
192
+ this.boundHideOnEscapeKeyEvent = this.boundHideOnEscapeKeyEvent || this.hideOnEscapeKeyEvent.bind(this);
193
+
194
+ this.referenceElement.addEventListener("focus", this.boundScheduleShow);
195
+ this.referenceElement.addEventListener("blur", this.boundHide);
196
+ document.addEventListener("keyup", this.boundHideOnEscapeKeyEvent);
197
+ }
198
+ /**
199
+ * Unbinds all mouse events
200
+ */
201
+ private unbindKeyboardEvents() {
202
+ this.referenceElement.removeEventListener("focus", this.boundScheduleShow);
203
+ this.referenceElement.removeEventListener("blur", this.boundHide);
204
+ document.removeEventListener("keyup", this.boundHideOnEscapeKeyEvent);
205
+ }
206
+
171
207
 
172
208
  /**
173
209
  * Binds mouse events to show/hide on reference element hover
174
210
  */
175
211
  private bindMouseEvents() {
176
212
  this.boundScheduleShow = this.boundScheduleShow || this.scheduleShow.bind(this);
177
- this.boundHide = this.boundHide || this.hide.bind(this);
213
+ this.boundHide = this.boundHide || this.scheduleHide.bind(this);
214
+ this.boundClearActiveTimeout = this.boundClearActiveTimeout || this.clearActiveTimeout.bind(this);
178
215
 
179
216
  this.referenceElement.addEventListener("mouseover", this.boundScheduleShow);
180
217
  this.referenceElement.addEventListener("mouseout", this.boundHide);
181
- this.referenceElement.addEventListener("focus", this.boundScheduleShow);
182
- this.referenceElement.addEventListener("blur", this.boundHide);
218
+ this.popoverElement.addEventListener("mouseover", this.boundClearActiveTimeout);
219
+ this.popoverElement.addEventListener("mouseout", this.boundHide);
183
220
  }
184
221
 
185
222
  /**
@@ -190,6 +227,8 @@ export class TooltipController extends BasePopoverController {
190
227
  this.referenceElement.removeEventListener("mouseout", this.boundHide);
191
228
  this.referenceElement.removeEventListener("focus", this.boundScheduleShow);
192
229
  this.referenceElement.removeEventListener("blur", this.boundHide);
230
+ this.popoverElement.removeEventListener("mouseover", this.boundClearActiveTimeout);
231
+ this.popoverElement.removeEventListener("mouseout", this.boundHide);
193
232
  }
194
233
 
195
234
  /**
@@ -231,16 +270,16 @@ export function setTooltipText(element: Element, text: string, options?: Tooltip
231
270
  * @param options Options for rendering the tooltip.
232
271
  */
233
272
  function applyOptionsAndTitleAttributes(element: Element, options?: TooltipOptions) {
234
-
235
273
  if (options && options.placement) {
236
274
  element.setAttribute("data-s-tooltip-placement", options.placement);
237
275
  }
238
276
 
239
- var controller = <TooltipController>Stacks.application.getControllerForElementAndIdentifier(element, "s-tooltip");
277
+ const controller = <TooltipController>Stacks.application.getControllerForElementAndIdentifier(element, "s-tooltip");
240
278
 
241
279
  if (controller) {
242
280
  controller.applyTitleAttributes();
243
281
  } else {
244
- element.setAttribute("data-controller", element.getAttribute("data-controller") + " s-tooltip");
282
+ const dataController = element.getAttribute("data-controller");
283
+ element.setAttribute("data-controller", `${dataController ? dataController : ""} s-tooltip`);
245
284
  }
246
285
  }
package/lib/ts/stacks.ts CHANGED
@@ -9,7 +9,7 @@ export class StacksApplication extends Stimulus.Application {
9
9
  const definitions = Array.isArray(head) ? head : [head, ...rest];
10
10
 
11
11
  for (const definition of definitions) {
12
- var hasPrefix = /^s-/.test(definition.identifier);
12
+ const hasPrefix = /^s-/.test(definition.identifier);
13
13
  if (StacksApplication._initializing && !hasPrefix) {
14
14
  throw "Stacks-created Stimulus controller names must start with \"s-\".";
15
15
  }
@@ -23,6 +23,7 @@ export class StacksApplication extends Stimulus.Application {
23
23
 
24
24
  static start(element?: Element, schema?: Stimulus.Schema): StacksApplication {
25
25
  const application = new StacksApplication(element, schema);
26
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
26
27
  application.start();
27
28
  return application;
28
29
  }
@@ -46,13 +47,14 @@ export class StacksController extends Stimulus.Controller {
46
47
  };
47
48
  protected triggerEvent<T>(eventName: string, detail?: T, optionalElement?: Element) {
48
49
  const namespacedName = this.identifier + ":" + eventName;
49
- var event : CustomEvent<T>;
50
+ let event : CustomEvent<T>;
50
51
  try {
51
52
  event = new CustomEvent(namespacedName, {bubbles: true, cancelable: true, detail: detail});
52
53
  } catch (ex) {
53
54
  // Internet Explorer
55
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
54
56
  event = document.createEvent("CustomEvent");
55
- event.initCustomEvent(namespacedName, true, true, detail!);
57
+ event.initCustomEvent(namespacedName, true, true, detail);
56
58
  }
57
59
  (optionalElement || this.element).dispatchEvent(event);
58
60
  return event;
@@ -66,11 +68,13 @@ export interface ControllerDefinition {
66
68
  targets?: string[];
67
69
  }
68
70
  export function createController(controllerDefinition: ControllerDefinition): typeof StacksController {
71
+ // eslint-disable-next-line no-prototype-builtins
69
72
  const Controller = controllerDefinition.hasOwnProperty("targets")
70
73
  ? class Controller extends StacksController { static targets = controllerDefinition.targets! }
71
74
  : class Controller extends StacksController {};
72
75
 
73
- for (var prop in controllerDefinition) {
76
+ for (const prop in controllerDefinition) {
77
+ // eslint-disable-next-line no-prototype-builtins
74
78
  if (prop !== "targets" && controllerDefinition.hasOwnProperty(prop)) {
75
79
  Object.defineProperty(
76
80
  Controller.prototype,
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "https://github.com/StackExchange/Stacks.git"
7
7
  },
8
- "version": "1.3.0",
8
+ "version": "1.3.1",
9
9
  "files": [
10
10
  "dist",
11
11
  "lib"
@@ -30,27 +30,27 @@
30
30
  },
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
- "@popperjs/core": "^2.11.5",
33
+ "@popperjs/core": "^2.11.6",
34
34
  "stimulus": "^2.0.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@11ty/eleventy": "^1.0.1",
37
+ "@11ty/eleventy": "^1.0.2",
38
38
  "@highlightjs/cdn-assets": "^11.6.0",
39
- "@stackoverflow/stacks-editor": "^0.6.1",
40
- "@stackoverflow/stacks-icons": "^3.0.0",
41
- "@typescript-eslint/eslint-plugin": "^5.31.0",
42
- "@typescript-eslint/parser": "^5.31.0",
43
- "backstopjs": "^6.1.0",
39
+ "@stackoverflow/stacks-editor": "^0.7.0",
40
+ "@stackoverflow/stacks-icons": "^3.0.2",
41
+ "@typescript-eslint/eslint-plugin": "^5.35.1",
42
+ "@typescript-eslint/parser": "^5.35.1",
43
+ "backstopjs": "^6.1.1",
44
44
  "concurrently": "^7.3.0",
45
45
  "css-loader": "^6.7.1",
46
- "cssnano": "^5.1.12",
46
+ "cssnano": "^5.1.13",
47
47
  "docsearch.js": "^2.6.3",
48
48
  "eleventy-plugin-highlightjs": "^1.1.0",
49
49
  "eleventy-plugin-nesting-toc": "^1.3.0",
50
- "eslint": "^8.20.0",
50
+ "eslint": "^8.23.0",
51
51
  "eslint-config-prettier": "^8.5.0",
52
52
  "eslint-plugin-no-unsanitized": "^4.0.1",
53
- "jquery": "^3.6.0",
53
+ "jquery": "^3.6.1",
54
54
  "less-loader": "^11.0.0",
55
55
  "list.js": "^2.3.1",
56
56
  "markdown-it": "^13.0.1",
@@ -58,13 +58,13 @@
58
58
  "postcss-less": "^6.0.0",
59
59
  "postcss-loader": "^7.0.1",
60
60
  "prettier": "^2.7.1",
61
- "stylelint": "^14.9.1",
62
- "stylelint-config-recommended": "^8.0.0",
63
- "stylelint-config-standard": "^26.0.0",
64
- "terser-webpack-plugin": "^5.3.3",
61
+ "stylelint": "^14.11.0",
62
+ "stylelint-config-recommended": "^9.0.0",
63
+ "stylelint-config-standard": "^28.0.0",
64
+ "terser-webpack-plugin": "^5.3.5",
65
65
  "ts-loader": "^9.3.1",
66
- "typescript": "^4.7.4",
67
- "webpack": "^5.73.0",
66
+ "typescript": "^4.8.2",
67
+ "webpack": "^5.74.0",
68
68
  "webpack-cli": "^4.10.0",
69
69
  "webpack-merge": "^5.8.0"
70
70
  },