@stackoverflow/stacks 2.7.3 → 2.7.5

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 (85) hide show
  1. package/LICENSE.MD +9 -9
  2. package/README.md +158 -180
  3. package/dist/css/stacks.css +20 -13
  4. package/dist/css/stacks.min.css +1 -1
  5. package/dist/js/stacks.min.js +1 -1
  6. package/lib/atomic/border.less +139 -139
  7. package/lib/atomic/color.less +36 -36
  8. package/lib/atomic/flex.less +426 -426
  9. package/lib/atomic/gap.less +44 -44
  10. package/lib/atomic/grid.less +139 -139
  11. package/lib/atomic/misc.less +374 -374
  12. package/lib/atomic/spacing.less +98 -98
  13. package/lib/atomic/typography.less +266 -264
  14. package/lib/atomic/width-height.less +194 -194
  15. package/lib/base/body.less +44 -44
  16. package/lib/base/configuration-static.less +61 -61
  17. package/lib/base/fieldset.less +5 -5
  18. package/lib/base/icon.less +11 -11
  19. package/lib/base/internal.less +220 -220
  20. package/lib/base/reset-meyer.less +64 -64
  21. package/lib/base/reset-normalize.less +449 -449
  22. package/lib/base/reset.less +20 -20
  23. package/lib/components/activity-indicator/activity-indicator.less +53 -53
  24. package/lib/components/avatar/avatar.less +108 -108
  25. package/lib/components/award-bling/award-bling.less +31 -31
  26. package/lib/components/banner/banner.less +44 -44
  27. package/lib/components/banner/banner.ts +149 -149
  28. package/lib/components/block-link/block-link.less +82 -82
  29. package/lib/components/breadcrumbs/breadcrumbs.less +41 -41
  30. package/lib/components/button-group/button-group.less +82 -82
  31. package/lib/components/card/card.less +37 -37
  32. package/lib/components/check-control/check-control.less +17 -17
  33. package/lib/components/check-group/check-group.less +19 -19
  34. package/lib/components/checkbox_radio/checkbox_radio.less +159 -159
  35. package/lib/components/code-block/code-block.fixtures.ts +88 -88
  36. package/lib/components/code-block/code-block.less +116 -116
  37. package/lib/components/description/description.less +9 -9
  38. package/lib/components/empty-state/empty-state.less +16 -16
  39. package/lib/components/expandable/expandable.less +118 -118
  40. package/lib/components/input-fill/input-fill.less +35 -35
  41. package/lib/components/input-icon/input-icon.less +45 -45
  42. package/lib/components/input-message/input-message.less +49 -49
  43. package/lib/components/input_textarea/input_textarea.less +2 -7
  44. package/lib/components/label/label.less +116 -110
  45. package/lib/components/link-preview/link-preview.less +148 -148
  46. package/lib/components/menu/menu.less +41 -41
  47. package/lib/components/modal/modal.less +118 -118
  48. package/lib/components/modal/modal.ts +383 -383
  49. package/lib/components/navigation/navigation.less +136 -136
  50. package/lib/components/navigation/navigation.ts +128 -128
  51. package/lib/components/page-title/page-title.less +51 -51
  52. package/lib/components/popover/popover.less +159 -159
  53. package/lib/components/popover/popover.ts +651 -651
  54. package/lib/components/post-summary/post-summary.less +457 -457
  55. package/lib/components/progress-bar/progress-bar.less +291 -291
  56. package/lib/components/prose/prose.less +452 -452
  57. package/lib/components/select/select.less +138 -138
  58. package/lib/components/spinner/spinner.less +103 -103
  59. package/lib/components/table/table.ts +296 -296
  60. package/lib/components/table-container/table-container.less +4 -4
  61. package/lib/components/tag/tag.less +186 -186
  62. package/lib/components/toast/toast.less +35 -35
  63. package/lib/components/toast/toast.ts +357 -357
  64. package/lib/components/toggle-switch/toggle-switch.less +104 -104
  65. package/lib/components/topbar/topbar.less +553 -553
  66. package/lib/components/uploader/uploader.less +205 -205
  67. package/lib/components/user-card/user-card.less +129 -129
  68. package/lib/controllers.ts +33 -33
  69. package/lib/exports/color-mixins.less +283 -283
  70. package/lib/exports/constants-helpers.less +108 -108
  71. package/lib/exports/constants-type.less +155 -155
  72. package/lib/exports/exports.less +15 -15
  73. package/lib/exports/mixins.less +334 -333
  74. package/lib/exports/spacing-mixins.less +67 -67
  75. package/lib/index.ts +32 -32
  76. package/lib/input-utils.less +41 -41
  77. package/lib/stacks-dynamic.less +24 -24
  78. package/lib/stacks-static.less +93 -93
  79. package/lib/stacks.less +13 -13
  80. package/lib/test/assertions.ts +36 -36
  81. package/lib/test/less-test-utils.ts +28 -28
  82. package/lib/test/open-wc-testing-patch.d.ts +26 -26
  83. package/lib/tsconfig.build.json +4 -4
  84. package/lib/tsconfig.json +17 -17
  85. package/package.json +27 -23
@@ -1,296 +1,296 @@
1
- import * as Stacks from "../../stacks";
2
-
3
- /**
4
- * The string values of these enumerations should correspond with `aria-sort` valid values.
5
- *
6
- * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-sort#values
7
- */
8
- export enum SortOrder {
9
- Ascending = "ascending",
10
- Descending = "descending",
11
- None = "none",
12
- }
13
-
14
- export class TableController extends Stacks.StacksController {
15
- declare readonly columnTarget: HTMLTableCellElement;
16
- declare readonly columnTargets: HTMLTableCellElement[];
17
-
18
- static targets = ["column"];
19
-
20
- sort(evt: PointerEvent) {
21
- // eslint-disable-next-line @typescript-eslint/no-this-alias
22
- const controller = this;
23
- const sortTriggerEl = evt.currentTarget;
24
- // TODO: support *only* button as trigger in next major release
25
- const triggerIsButton = sortTriggerEl instanceof HTMLButtonElement;
26
- // the below conditional is here for backward compatibility with the old API
27
- // where we did not advise buttons as sortable column head triggers
28
- const colHead = (
29
- triggerIsButton ? sortTriggerEl.parentElement : sortTriggerEl
30
- ) as HTMLTableCellElement;
31
- const table = this.element as HTMLTableElement;
32
- const tbody = table.tBodies[0];
33
-
34
- // the column slot number of the clicked header
35
- const colno = getCellSlot(colHead);
36
-
37
- if (colno < 0) {
38
- // this shouldn't happen if the clicked element is actually a column head
39
- return;
40
- }
41
-
42
- // an index of the <tbody>, so we can find out for each row which <td> element is
43
- // in the same column slot as the header
44
- const slotIndex = buildIndex(tbody);
45
-
46
- // the default behavior when clicking a header is to sort by this column in ascending
47
- // direction, *unless* it is already sorted that way
48
- const direction =
49
- colHead.getAttribute("aria-sort") === SortOrder.Ascending ? -1 : 1;
50
-
51
- const rows = Array.from(table.tBodies[0].rows);
52
-
53
- // if this is still false after traversing the data, that means all values are integers (or empty)
54
- // and thus we'll sort numerically.
55
- let anyNonInt = false;
56
-
57
- // data will be a list of tuples [value, rowNum], where value is what we're sorting by
58
- const data: [string | number, number][] = [];
59
- let firstBottomRow: HTMLTableRowElement;
60
- rows.forEach(function (row, index) {
61
- const force = controller.getElementData(row, "sort-to");
62
- if (force === "top") {
63
- return; // rows not added to the list will automatically end up at the top
64
- } else if (force === "bottom") {
65
- if (!firstBottomRow) {
66
- firstBottomRow = row;
67
- }
68
- return;
69
- }
70
- const cell = slotIndex[index][colno];
71
- if (!cell) {
72
- data.push(["", index]);
73
- return;
74
- }
75
-
76
- // unless the to-be-sorted-by value is explicitly provided on the element via this attribute,
77
- // the value we're using is the cell's text, trimmed of any whitespace
78
- const explicit = controller.getElementData(cell, "sort-val");
79
- const d = explicit ?? cell.textContent?.trim() ?? "";
80
-
81
- if (d !== "" && `${parseInt(d, 10)}` !== d) {
82
- anyNonInt = true;
83
- }
84
- data.push([d, index]);
85
- });
86
-
87
- // If all values were integers (or empty cells), sort numerically, with empty cells treated as
88
- // having the lowest possible value (i.e. sorted to the top if ascending, bottom if descending)
89
- if (!anyNonInt) {
90
- data.forEach(function (tuple) {
91
- tuple[0] =
92
- tuple[0] === ""
93
- ? Number.MIN_VALUE
94
- : parseInt(tuple[0] as string, 10);
95
- });
96
- }
97
-
98
- // We don't sort an array of <tr>, but instead an arrays of row *numbers*, because this way we
99
- // can enforce stable sorting, i.e. rows that compare equal are guaranteed to remain in the same
100
- // order (the JS standard does not gurantee this for sort()).
101
- data.sort(function (a, b) {
102
- // first compare the values (a[0])
103
- if (a[0] > b[0]) {
104
- return 1 * direction;
105
- } else if (a[0] < b[0]) {
106
- return -1 * direction;
107
- } else {
108
- // if the values are equal, compare the row numbers (a[1]) to guarantee stable sorting
109
- // (note that this comparison is independent of the sorting direction)
110
- return a[1] > b[1] ? 1 : -1;
111
- }
112
- });
113
-
114
- // this is the actual reordering of the table rows
115
- data.forEach(([_, rowIndex]) => {
116
- const row = rows[rowIndex];
117
- row.parentElement?.removeChild(row);
118
-
119
- if (firstBottomRow) {
120
- tbody.insertBefore(row, firstBottomRow);
121
- } else {
122
- tbody.appendChild(row);
123
- }
124
- });
125
-
126
- // update the UI and set the `data-sort-direction` attribute if appropriate, so that the next click
127
- // will cause sorting in descending direction
128
- this.updateSortedColumnStyles(
129
- colHead,
130
- direction === 1 ? SortOrder.Ascending : SortOrder.Descending
131
- );
132
- }
133
-
134
- private updateSortedColumnStyles = (
135
- targetColumnHeader: Element,
136
- direction: SortOrder
137
- ): void => {
138
- // Loop through all sortable columns and remove their sorting direction
139
- // (if any), and only leave/set a sorting on `targetColumnHeader`.
140
- this.columnTargets.forEach((header: HTMLTableCellElement) => {
141
- const isCurrent = header === targetColumnHeader;
142
- const classSuffix = isCurrent
143
- ? direction === SortOrder.Ascending
144
- ? "asc"
145
- : "desc"
146
- : SortOrder.None;
147
-
148
- header.classList.toggle(
149
- "is-sorted",
150
- isCurrent && direction !== SortOrder.None
151
- );
152
- header.querySelectorAll(".js-sorting-indicator").forEach((icon) => {
153
- icon.classList.toggle(
154
- "d-none",
155
- !icon.classList.contains(
156
- "js-sorting-indicator-" + classSuffix
157
- )
158
- );
159
- });
160
-
161
- if (isCurrent) {
162
- header.setAttribute("aria-sort", direction);
163
- } else {
164
- header.removeAttribute("aria-sort");
165
- }
166
- });
167
- };
168
- }
169
-
170
- /**
171
- * @internal This function is exported for testing purposes but is not a part of our public API
172
- *
173
- * @param section
174
- */
175
- export function buildIndex(
176
- section: HTMLTableSectionElement
177
- ): HTMLTableCellElement[][] {
178
- const result = buildIndexOrGetCellSlot(section);
179
-
180
- if (!Array.isArray(result)) {
181
- throw "shouldn't happen";
182
- }
183
-
184
- return result;
185
- }
186
-
187
- /**
188
- * @internal This function is exported for testing purposes but is not a part of our public API
189
- *
190
- * @param cell
191
- */
192
- export function getCellSlot(cell: HTMLTableCellElement): number {
193
- const tableElement = cell.parentElement?.parentElement;
194
-
195
- if (!(tableElement instanceof HTMLTableSectionElement)) {
196
- throw "invalid table";
197
- }
198
-
199
- const result = buildIndexOrGetCellSlot(tableElement, cell);
200
-
201
- if (typeof result !== "number") {
202
- throw "shouldn't happen";
203
- }
204
-
205
- return result;
206
- }
207
-
208
- /**
209
- * Just because a <td> is the 4th *child* of its <tr> doesn't mean it belongs to the 4th *column*
210
- * of the table. Previous cells may have a colspan; cells in previous rows may have a rowspan.
211
- * Because we need to know which header cells and data cells belong together, we have to 1) find out
212
- * which column number (or "slot" as we call it here) the header cell has, and 2) for each row find
213
- * out which <td> cell corresponds to this slot (because those are the rows we're sorting by).
214
- *
215
- * That's what the following function does. If the second argument is not given, it returns an index
216
- * of the table, which is an array of arrays. Each of the sub-arrays corresponds to a table row. The
217
- * indices of the sub-array correspond to column slots; the values are the actual table cell elements.
218
- * For example index[4][3] is the <td> or <th> in row 4, column 3 of the table section (<tbody> or <thead>).
219
- * Note that this element is not necessarily even in the 4th (zero-based) <tr> -- if it has a rowSpan > 1,
220
- * it may also be in a previous <tr>.
221
- *
222
- * If the second argument is given, it's a <td> or <th> that we're trying to find, and the algorithm
223
- * stops as soon as it has found it and the function returns its slot number.
224
- */
225
- function buildIndexOrGetCellSlot(
226
- section: HTMLTableSectionElement,
227
- findCell?: HTMLTableCellElement
228
- ) {
229
- const index = [];
230
- let curRow: Element | null = section.children[0];
231
-
232
- // the elements of these two arrays are synchronized; the first array contains table cell elements,
233
- // the second one contains a number that indicates for how many more rows this elements will
234
- // exist (i.e. the value is initially one less than the cell's rowspan, and will be decreased for each row)
235
- const growing: HTMLTableCellElement[] = [];
236
- const growingRowsLeft: number[] = [];
237
-
238
- // continue while we have actual <tr>'s left *or* we still have rowspan'ed elements that aren't done
239
- while (curRow || growingRowsLeft.some((e) => e !== 0)) {
240
- const curIndexRow: HTMLTableCellElement[] = [];
241
- index.push(curIndexRow);
242
-
243
- let curSlot = 0;
244
- if (curRow) {
245
- for (
246
- let curCellIdx = 0;
247
- curCellIdx < curRow.children.length;
248
- curCellIdx++
249
- ) {
250
- while (growingRowsLeft[curSlot]) {
251
- growingRowsLeft[curSlot]--;
252
- curIndexRow[curSlot] = growing[curSlot];
253
- curSlot++;
254
- }
255
-
256
- const cell = curRow.children[curCellIdx];
257
-
258
- if (!(cell instanceof HTMLTableCellElement)) {
259
- throw "invalid table";
260
- }
261
-
262
- if (getComputedStyle(cell).display === "none") {
263
- continue;
264
- }
265
-
266
- if (cell === findCell) {
267
- return curSlot;
268
- }
269
-
270
- const nextFreeSlot = curSlot + cell.colSpan;
271
-
272
- for (; curSlot < nextFreeSlot; curSlot++) {
273
- growingRowsLeft[curSlot] = cell.rowSpan - 1; // if any of these is already growing, the table is broken -- no guarantees of anything
274
- growing[curSlot] = cell;
275
- curIndexRow[curSlot] = cell;
276
- }
277
- }
278
- }
279
-
280
- while (curSlot < growing.length) {
281
- if (growingRowsLeft[curSlot]) {
282
- growingRowsLeft[curSlot]--;
283
- curIndexRow[curSlot] = growing[curSlot];
284
- }
285
-
286
- curSlot++;
287
- }
288
-
289
- if (curRow) {
290
- curRow = curRow.nextElementSibling;
291
- }
292
- }
293
-
294
- // if findCell was given, but we end up here, that means it isn't in this section
295
- return findCell ? -1 : index;
296
- }
1
+ import * as Stacks from "../../stacks";
2
+
3
+ /**
4
+ * The string values of these enumerations should correspond with `aria-sort` valid values.
5
+ *
6
+ * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-sort#values
7
+ */
8
+ export enum SortOrder {
9
+ Ascending = "ascending",
10
+ Descending = "descending",
11
+ None = "none",
12
+ }
13
+
14
+ export class TableController extends Stacks.StacksController {
15
+ declare readonly columnTarget: HTMLTableCellElement;
16
+ declare readonly columnTargets: HTMLTableCellElement[];
17
+
18
+ static targets = ["column"];
19
+
20
+ sort(evt: PointerEvent) {
21
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
22
+ const controller = this;
23
+ const sortTriggerEl = evt.currentTarget;
24
+ // TODO: support *only* button as trigger in next major release
25
+ const triggerIsButton = sortTriggerEl instanceof HTMLButtonElement;
26
+ // the below conditional is here for backward compatibility with the old API
27
+ // where we did not advise buttons as sortable column head triggers
28
+ const colHead = (
29
+ triggerIsButton ? sortTriggerEl.parentElement : sortTriggerEl
30
+ ) as HTMLTableCellElement;
31
+ const table = this.element as HTMLTableElement;
32
+ const tbody = table.tBodies[0];
33
+
34
+ // the column slot number of the clicked header
35
+ const colno = getCellSlot(colHead);
36
+
37
+ if (colno < 0) {
38
+ // this shouldn't happen if the clicked element is actually a column head
39
+ return;
40
+ }
41
+
42
+ // an index of the <tbody>, so we can find out for each row which <td> element is
43
+ // in the same column slot as the header
44
+ const slotIndex = buildIndex(tbody);
45
+
46
+ // the default behavior when clicking a header is to sort by this column in ascending
47
+ // direction, *unless* it is already sorted that way
48
+ const direction =
49
+ colHead.getAttribute("aria-sort") === SortOrder.Ascending ? -1 : 1;
50
+
51
+ const rows = Array.from(table.tBodies[0].rows);
52
+
53
+ // if this is still false after traversing the data, that means all values are integers (or empty)
54
+ // and thus we'll sort numerically.
55
+ let anyNonInt = false;
56
+
57
+ // data will be a list of tuples [value, rowNum], where value is what we're sorting by
58
+ const data: [string | number, number][] = [];
59
+ let firstBottomRow: HTMLTableRowElement;
60
+ rows.forEach(function (row, index) {
61
+ const force = controller.getElementData(row, "sort-to");
62
+ if (force === "top") {
63
+ return; // rows not added to the list will automatically end up at the top
64
+ } else if (force === "bottom") {
65
+ if (!firstBottomRow) {
66
+ firstBottomRow = row;
67
+ }
68
+ return;
69
+ }
70
+ const cell = slotIndex[index][colno];
71
+ if (!cell) {
72
+ data.push(["", index]);
73
+ return;
74
+ }
75
+
76
+ // unless the to-be-sorted-by value is explicitly provided on the element via this attribute,
77
+ // the value we're using is the cell's text, trimmed of any whitespace
78
+ const explicit = controller.getElementData(cell, "sort-val");
79
+ const d = explicit ?? cell.textContent?.trim() ?? "";
80
+
81
+ if (d !== "" && `${parseInt(d, 10)}` !== d) {
82
+ anyNonInt = true;
83
+ }
84
+ data.push([d, index]);
85
+ });
86
+
87
+ // If all values were integers (or empty cells), sort numerically, with empty cells treated as
88
+ // having the lowest possible value (i.e. sorted to the top if ascending, bottom if descending)
89
+ if (!anyNonInt) {
90
+ data.forEach(function (tuple) {
91
+ tuple[0] =
92
+ tuple[0] === ""
93
+ ? Number.MIN_VALUE
94
+ : parseInt(tuple[0] as string, 10);
95
+ });
96
+ }
97
+
98
+ // We don't sort an array of <tr>, but instead an arrays of row *numbers*, because this way we
99
+ // can enforce stable sorting, i.e. rows that compare equal are guaranteed to remain in the same
100
+ // order (the JS standard does not gurantee this for sort()).
101
+ data.sort(function (a, b) {
102
+ // first compare the values (a[0])
103
+ if (a[0] > b[0]) {
104
+ return 1 * direction;
105
+ } else if (a[0] < b[0]) {
106
+ return -1 * direction;
107
+ } else {
108
+ // if the values are equal, compare the row numbers (a[1]) to guarantee stable sorting
109
+ // (note that this comparison is independent of the sorting direction)
110
+ return a[1] > b[1] ? 1 : -1;
111
+ }
112
+ });
113
+
114
+ // this is the actual reordering of the table rows
115
+ data.forEach(([_, rowIndex]) => {
116
+ const row = rows[rowIndex];
117
+ row.parentElement?.removeChild(row);
118
+
119
+ if (firstBottomRow) {
120
+ tbody.insertBefore(row, firstBottomRow);
121
+ } else {
122
+ tbody.appendChild(row);
123
+ }
124
+ });
125
+
126
+ // update the UI and set the `data-sort-direction` attribute if appropriate, so that the next click
127
+ // will cause sorting in descending direction
128
+ this.updateSortedColumnStyles(
129
+ colHead,
130
+ direction === 1 ? SortOrder.Ascending : SortOrder.Descending
131
+ );
132
+ }
133
+
134
+ private updateSortedColumnStyles = (
135
+ targetColumnHeader: Element,
136
+ direction: SortOrder
137
+ ): void => {
138
+ // Loop through all sortable columns and remove their sorting direction
139
+ // (if any), and only leave/set a sorting on `targetColumnHeader`.
140
+ this.columnTargets.forEach((header: HTMLTableCellElement) => {
141
+ const isCurrent = header === targetColumnHeader;
142
+ const classSuffix = isCurrent
143
+ ? direction === SortOrder.Ascending
144
+ ? "asc"
145
+ : "desc"
146
+ : SortOrder.None;
147
+
148
+ header.classList.toggle(
149
+ "is-sorted",
150
+ isCurrent && direction !== SortOrder.None
151
+ );
152
+ header.querySelectorAll(".js-sorting-indicator").forEach((icon) => {
153
+ icon.classList.toggle(
154
+ "d-none",
155
+ !icon.classList.contains(
156
+ "js-sorting-indicator-" + classSuffix
157
+ )
158
+ );
159
+ });
160
+
161
+ if (isCurrent) {
162
+ header.setAttribute("aria-sort", direction);
163
+ } else {
164
+ header.removeAttribute("aria-sort");
165
+ }
166
+ });
167
+ };
168
+ }
169
+
170
+ /**
171
+ * @internal This function is exported for testing purposes but is not a part of our public API
172
+ *
173
+ * @param section
174
+ */
175
+ export function buildIndex(
176
+ section: HTMLTableSectionElement
177
+ ): HTMLTableCellElement[][] {
178
+ const result = buildIndexOrGetCellSlot(section);
179
+
180
+ if (!Array.isArray(result)) {
181
+ throw "shouldn't happen";
182
+ }
183
+
184
+ return result;
185
+ }
186
+
187
+ /**
188
+ * @internal This function is exported for testing purposes but is not a part of our public API
189
+ *
190
+ * @param cell
191
+ */
192
+ export function getCellSlot(cell: HTMLTableCellElement): number {
193
+ const tableElement = cell.parentElement?.parentElement;
194
+
195
+ if (!(tableElement instanceof HTMLTableSectionElement)) {
196
+ throw "invalid table";
197
+ }
198
+
199
+ const result = buildIndexOrGetCellSlot(tableElement, cell);
200
+
201
+ if (typeof result !== "number") {
202
+ throw "shouldn't happen";
203
+ }
204
+
205
+ return result;
206
+ }
207
+
208
+ /**
209
+ * Just because a <td> is the 4th *child* of its <tr> doesn't mean it belongs to the 4th *column*
210
+ * of the table. Previous cells may have a colspan; cells in previous rows may have a rowspan.
211
+ * Because we need to know which header cells and data cells belong together, we have to 1) find out
212
+ * which column number (or "slot" as we call it here) the header cell has, and 2) for each row find
213
+ * out which <td> cell corresponds to this slot (because those are the rows we're sorting by).
214
+ *
215
+ * That's what the following function does. If the second argument is not given, it returns an index
216
+ * of the table, which is an array of arrays. Each of the sub-arrays corresponds to a table row. The
217
+ * indices of the sub-array correspond to column slots; the values are the actual table cell elements.
218
+ * For example index[4][3] is the <td> or <th> in row 4, column 3 of the table section (<tbody> or <thead>).
219
+ * Note that this element is not necessarily even in the 4th (zero-based) <tr> -- if it has a rowSpan > 1,
220
+ * it may also be in a previous <tr>.
221
+ *
222
+ * If the second argument is given, it's a <td> or <th> that we're trying to find, and the algorithm
223
+ * stops as soon as it has found it and the function returns its slot number.
224
+ */
225
+ function buildIndexOrGetCellSlot(
226
+ section: HTMLTableSectionElement,
227
+ findCell?: HTMLTableCellElement
228
+ ) {
229
+ const index = [];
230
+ let curRow: Element | null = section.children[0];
231
+
232
+ // the elements of these two arrays are synchronized; the first array contains table cell elements,
233
+ // the second one contains a number that indicates for how many more rows this elements will
234
+ // exist (i.e. the value is initially one less than the cell's rowspan, and will be decreased for each row)
235
+ const growing: HTMLTableCellElement[] = [];
236
+ const growingRowsLeft: number[] = [];
237
+
238
+ // continue while we have actual <tr>'s left *or* we still have rowspan'ed elements that aren't done
239
+ while (curRow || growingRowsLeft.some((e) => e !== 0)) {
240
+ const curIndexRow: HTMLTableCellElement[] = [];
241
+ index.push(curIndexRow);
242
+
243
+ let curSlot = 0;
244
+ if (curRow) {
245
+ for (
246
+ let curCellIdx = 0;
247
+ curCellIdx < curRow.children.length;
248
+ curCellIdx++
249
+ ) {
250
+ while (growingRowsLeft[curSlot]) {
251
+ growingRowsLeft[curSlot]--;
252
+ curIndexRow[curSlot] = growing[curSlot];
253
+ curSlot++;
254
+ }
255
+
256
+ const cell = curRow.children[curCellIdx];
257
+
258
+ if (!(cell instanceof HTMLTableCellElement)) {
259
+ throw "invalid table";
260
+ }
261
+
262
+ if (getComputedStyle(cell).display === "none") {
263
+ continue;
264
+ }
265
+
266
+ if (cell === findCell) {
267
+ return curSlot;
268
+ }
269
+
270
+ const nextFreeSlot = curSlot + cell.colSpan;
271
+
272
+ for (; curSlot < nextFreeSlot; curSlot++) {
273
+ growingRowsLeft[curSlot] = cell.rowSpan - 1; // if any of these is already growing, the table is broken -- no guarantees of anything
274
+ growing[curSlot] = cell;
275
+ curIndexRow[curSlot] = cell;
276
+ }
277
+ }
278
+ }
279
+
280
+ while (curSlot < growing.length) {
281
+ if (growingRowsLeft[curSlot]) {
282
+ growingRowsLeft[curSlot]--;
283
+ curIndexRow[curSlot] = growing[curSlot];
284
+ }
285
+
286
+ curSlot++;
287
+ }
288
+
289
+ if (curRow) {
290
+ curRow = curRow.nextElementSibling;
291
+ }
292
+ }
293
+
294
+ // if findCell was given, but we end up here, that means it isn't in this section
295
+ return findCell ? -1 : index;
296
+ }
@@ -1,4 +1,4 @@
1
- .s-table-container {
2
- overflow-x: auto;
3
- @scrollbar-styles();
4
- }
1
+ .s-table-container {
2
+ overflow-x: auto;
3
+ @scrollbar-styles();
4
+ }