@universal-ember/table 3.0.0 → 3.0.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 (168) hide show
  1. package/dist/-private/-type-tests/plugin-properties.test.js +27 -0
  2. package/dist/-private/-type-tests/plugin-properties.test.js.map +1 -0
  3. package/dist/-private/-type-tests/plugin-with.test.js +20 -0
  4. package/dist/-private/-type-tests/plugin-with.test.js.map +1 -0
  5. package/dist/-private/-type-tests/plugins-accessors.test.js +36 -0
  6. package/dist/-private/-type-tests/plugins-accessors.test.js.map +1 -0
  7. package/dist/-private/-type-tests/plugins-signature-from.test.js +15 -0
  8. package/dist/-private/-type-tests/plugins-signature-from.test.js.map +1 -0
  9. package/dist/-private/-type-tests/plugins-signature-utils.test.js +36 -0
  10. package/dist/-private/-type-tests/plugins-signature-utils.test.js.map +1 -0
  11. package/dist/-private/-type-tests/table-api.test.js +17 -0
  12. package/dist/-private/-type-tests/table-api.test.js.map +1 -0
  13. package/dist/-private/-type-tests/table-config.test.js +55 -0
  14. package/dist/-private/-type-tests/table-config.test.js.map +1 -0
  15. package/dist/-private/column.js +62 -0
  16. package/dist/-private/column.js.map +1 -0
  17. package/dist/-private/ember-compat.js +17 -0
  18. package/dist/-private/ember-compat.js.map +1 -0
  19. package/dist/-private/interfaces/column.js +2 -0
  20. package/dist/-private/interfaces/column.js.map +1 -0
  21. package/dist/-private/interfaces/index.js +2 -0
  22. package/dist/-private/interfaces/index.js.map +1 -0
  23. package/dist/-private/interfaces/modifier.js +2 -0
  24. package/dist/-private/interfaces/modifier.js.map +1 -0
  25. package/dist/-private/interfaces/pagination.js +2 -0
  26. package/dist/-private/interfaces/pagination.js.map +1 -0
  27. package/dist/-private/interfaces/plugins.js +2 -0
  28. package/dist/-private/interfaces/plugins.js.map +1 -0
  29. package/dist/-private/interfaces/preferences.js +2 -0
  30. package/dist/-private/interfaces/preferences.js.map +1 -0
  31. package/dist/-private/interfaces/selection.js +2 -0
  32. package/dist/-private/interfaces/selection.js.map +1 -0
  33. package/dist/-private/interfaces/table.js +2 -0
  34. package/dist/-private/interfaces/table.js.map +1 -0
  35. package/dist/-private/js-helper.js +55 -0
  36. package/dist/-private/js-helper.js.map +1 -0
  37. package/dist/-private/preferences.js +143 -0
  38. package/dist/-private/preferences.js.map +1 -0
  39. package/dist/-private/private-types.js +2 -0
  40. package/dist/-private/private-types.js.map +1 -0
  41. package/dist/-private/row.js +51 -0
  42. package/dist/-private/row.js.map +1 -0
  43. package/dist/-private/table.js +273 -0
  44. package/dist/-private/table.js.map +1 -0
  45. package/dist/-private/utils.js +15 -0
  46. package/dist/-private/utils.js.map +1 -0
  47. package/dist/_rollupPluginBabelHelpers-BpiaYhlf.js +63 -0
  48. package/dist/_rollupPluginBabelHelpers-BpiaYhlf.js.map +1 -0
  49. package/dist/index.js +4 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/plugins/-private/base.js +524 -0
  52. package/dist/plugins/-private/base.js.map +1 -0
  53. package/dist/plugins/-private/utils.js +103 -0
  54. package/dist/plugins/-private/utils.js.map +1 -0
  55. package/dist/plugins/column-reordering/helpers.js +44 -0
  56. package/dist/plugins/column-reordering/helpers.js.map +1 -0
  57. package/dist/plugins/column-reordering/index.js +3 -0
  58. package/dist/plugins/column-reordering/index.js.map +1 -0
  59. package/dist/plugins/column-reordering/plugin.js +359 -0
  60. package/dist/plugins/column-reordering/plugin.js.map +1 -0
  61. package/dist/plugins/column-reordering/utils.js +34 -0
  62. package/dist/plugins/column-reordering/utils.js.map +1 -0
  63. package/dist/plugins/column-resizing/handle.js +241 -0
  64. package/dist/plugins/column-resizing/handle.js.map +1 -0
  65. package/dist/plugins/column-resizing/helpers.js +71 -0
  66. package/dist/plugins/column-resizing/helpers.js.map +1 -0
  67. package/dist/plugins/column-resizing/index.js +4 -0
  68. package/dist/plugins/column-resizing/index.js.map +1 -0
  69. package/dist/plugins/column-resizing/plugin.js +328 -0
  70. package/dist/plugins/column-resizing/plugin.js.map +1 -0
  71. package/dist/plugins/column-resizing/resize-observer.js +44 -0
  72. package/dist/plugins/column-resizing/resize-observer.js.map +1 -0
  73. package/dist/plugins/column-resizing/utils.js +44 -0
  74. package/dist/plugins/column-resizing/utils.js.map +1 -0
  75. package/dist/plugins/column-visibility/helpers.js +25 -0
  76. package/dist/plugins/column-visibility/helpers.js.map +1 -0
  77. package/dist/plugins/column-visibility/index.js +3 -0
  78. package/dist/plugins/column-visibility/index.js.map +1 -0
  79. package/dist/plugins/column-visibility/plugin.js +92 -0
  80. package/dist/plugins/column-visibility/plugin.js.map +1 -0
  81. package/dist/plugins/data-sorting/helpers.js +49 -0
  82. package/dist/plugins/data-sorting/helpers.js.map +1 -0
  83. package/dist/plugins/data-sorting/index.js +4 -0
  84. package/dist/plugins/data-sorting/index.js.map +1 -0
  85. package/dist/plugins/data-sorting/plugin.js +132 -0
  86. package/dist/plugins/data-sorting/plugin.js.map +1 -0
  87. package/dist/plugins/data-sorting/types.js +14 -0
  88. package/dist/plugins/data-sorting/types.js.map +1 -0
  89. package/dist/plugins/index.js +3 -0
  90. package/dist/plugins/index.js.map +1 -0
  91. package/dist/plugins/metadata/helpers.js +12 -0
  92. package/dist/plugins/metadata/helpers.js.map +1 -0
  93. package/dist/plugins/metadata/index.js +3 -0
  94. package/dist/plugins/metadata/index.js.map +1 -0
  95. package/dist/plugins/metadata/plugin.js +25 -0
  96. package/dist/plugins/metadata/plugin.js.map +1 -0
  97. package/dist/plugins/row-selection/helpers.js +10 -0
  98. package/dist/plugins/row-selection/helpers.js.map +1 -0
  99. package/dist/plugins/row-selection/index.js +3 -0
  100. package/dist/plugins/row-selection/index.js.map +1 -0
  101. package/dist/plugins/row-selection/plugin.js +118 -0
  102. package/dist/plugins/row-selection/plugin.js.map +1 -0
  103. package/dist/plugins/sticky-columns/helpers.js +49 -0
  104. package/dist/plugins/sticky-columns/helpers.js.map +1 -0
  105. package/dist/plugins/sticky-columns/index.js +3 -0
  106. package/dist/plugins/sticky-columns/index.js.map +1 -0
  107. package/dist/plugins/sticky-columns/plugin.js +139 -0
  108. package/dist/plugins/sticky-columns/plugin.js.map +1 -0
  109. package/dist/test-support/index.js +62 -0
  110. package/dist/test-support/index.js.map +1 -0
  111. package/dist/utils.js +77 -0
  112. package/dist/utils.js.map +1 -0
  113. package/package.json +3 -2
  114. package/src/-private/-type-tests/plugin-properties.test.ts +38 -0
  115. package/src/-private/-type-tests/plugin-with.test.ts +23 -0
  116. package/src/-private/-type-tests/plugins-accessors.test.ts +86 -0
  117. package/src/-private/-type-tests/plugins-signature-from.test.ts +66 -0
  118. package/src/-private/-type-tests/plugins-signature-utils.test.ts +154 -0
  119. package/src/-private/-type-tests/table-api.test.ts +20 -0
  120. package/src/-private/-type-tests/table-config.test.ts +70 -0
  121. package/src/-private/column.ts +67 -0
  122. package/src/-private/ember-compat.ts +26 -0
  123. package/src/-private/interfaces/column.ts +73 -0
  124. package/src/-private/interfaces/index.ts +7 -0
  125. package/src/-private/interfaces/modifier.ts +7 -0
  126. package/src/-private/interfaces/pagination.ts +13 -0
  127. package/src/-private/interfaces/plugins.ts +349 -0
  128. package/src/-private/interfaces/preferences.ts +82 -0
  129. package/src/-private/interfaces/selection.ts +38 -0
  130. package/src/-private/interfaces/table.ts +121 -0
  131. package/src/-private/js-helper.ts +65 -0
  132. package/src/-private/preferences.ts +176 -0
  133. package/src/-private/private-types.ts +8 -0
  134. package/src/-private/row.ts +66 -0
  135. package/src/-private/table.ts +310 -0
  136. package/src/-private/utils.ts +21 -0
  137. package/src/index.ts +25 -0
  138. package/src/plugins/-private/base.ts +836 -0
  139. package/src/plugins/-private/utils.ts +166 -0
  140. package/src/plugins/column-reordering/helpers.ts +50 -0
  141. package/src/plugins/column-reordering/index.ts +6 -0
  142. package/src/plugins/column-reordering/plugin.ts +489 -0
  143. package/src/plugins/column-reordering/utils.ts +48 -0
  144. package/src/plugins/column-resizing/handle.ts +280 -0
  145. package/src/plugins/column-resizing/helpers.ts +79 -0
  146. package/src/plugins/column-resizing/index.ts +7 -0
  147. package/src/plugins/column-resizing/plugin.ts +490 -0
  148. package/src/plugins/column-resizing/resize-observer.ts +48 -0
  149. package/src/plugins/column-resizing/utils.ts +54 -0
  150. package/src/plugins/column-visibility/helpers.ts +28 -0
  151. package/src/plugins/column-visibility/index.ts +6 -0
  152. package/src/plugins/column-visibility/plugin.ts +155 -0
  153. package/src/plugins/data-sorting/helpers.ts +56 -0
  154. package/src/plugins/data-sorting/index.ts +8 -0
  155. package/src/plugins/data-sorting/plugin.ts +222 -0
  156. package/src/plugins/data-sorting/types.ts +26 -0
  157. package/src/plugins/index.ts +20 -0
  158. package/src/plugins/metadata/helpers.ts +12 -0
  159. package/src/plugins/metadata/index.ts +7 -0
  160. package/src/plugins/metadata/plugin.ts +26 -0
  161. package/src/plugins/row-selection/helpers.ts +13 -0
  162. package/src/plugins/row-selection/index.ts +7 -0
  163. package/src/plugins/row-selection/plugin.ts +218 -0
  164. package/src/plugins/sticky-columns/helpers.ts +59 -0
  165. package/src/plugins/sticky-columns/index.ts +7 -0
  166. package/src/plugins/sticky-columns/plugin.ts +201 -0
  167. package/src/test-support/index.ts +76 -0
  168. package/src/utils.ts +85 -0
@@ -0,0 +1,490 @@
1
+ import { cached, tracked } from '@glimmer/tracking';
2
+ import { assert } from '@ember/debug';
3
+ import { isDestroyed, isDestroying } from '@ember/destroyable';
4
+ import { action } from '@ember/object';
5
+
6
+ import { preferences } from '../../plugins/index.ts';
7
+
8
+ import { BasePlugin, columns, meta, options } from '../-private/base.ts';
9
+ import { applyStyles } from '../-private/utils.ts';
10
+ import {
11
+ getAccurateClientHeight,
12
+ getAccurateClientWidth,
13
+ totalGapOf,
14
+ } from './utils.ts';
15
+
16
+ import type { ColumnApi, PluginPreferences } from '../../plugins/index.ts';
17
+ import type { Column, Table } from '../../index.ts';
18
+
19
+ interface ColumnResizePreferences extends PluginPreferences {
20
+ columns: {
21
+ [columnKey: string]: {
22
+ width?: number;
23
+ };
24
+ };
25
+ }
26
+
27
+ declare module '@universal-ember/table/plugins' {
28
+ interface Registry {
29
+ ColumnResizing?: ColumnResizePreferences;
30
+ }
31
+ }
32
+
33
+ export interface ColumnOptions {
34
+ /**
35
+ * Force a starting width
36
+ * This may not be less than the minWidth
37
+ */
38
+ width?: number;
39
+ /**
40
+ * Default: 128px
41
+ */
42
+ minWidth?: number;
43
+ /**
44
+ * Flip if the column is resizable or not.
45
+ * The default is whatever the table's plugin option is set to
46
+ * (and then yet again true, if not set at all)
47
+ */
48
+ isResizable?: boolean;
49
+ }
50
+
51
+ export interface TableOptions {
52
+ /**
53
+ * Toggle whether the table is able to be resized at all
54
+ *
55
+ * default :true
56
+ */
57
+ enabled?: boolean;
58
+
59
+ /**
60
+ * By default, each column's "handle" position is on the
61
+ * left-hand side of the column.
62
+ *
63
+ * If, for style-reasons, you want to move it to the right,
64
+ * this option should reflect that so that the calculations can be
65
+ * updated to match the expected behavior of which column(s) grow/shrink
66
+ *
67
+ * Valid values are 'left' or 'right'
68
+ */
69
+ handlePosition?: string;
70
+ }
71
+
72
+ interface Signature {
73
+ Meta: {
74
+ Column: ColumnMeta;
75
+ Table: TableMeta;
76
+ };
77
+ Options: {
78
+ Plugin: TableOptions;
79
+ Column: ColumnOptions;
80
+ };
81
+ }
82
+
83
+ /**
84
+ * One instance of a plugin exists per table
85
+ * but a plugin can have a "Meta" for each column
86
+ */
87
+ export class ColumnResizing extends BasePlugin<Signature> {
88
+ name = 'column-resizing';
89
+ static features = ['columnWidth'];
90
+
91
+ meta = {
92
+ column: ColumnMeta,
93
+ table: TableMeta,
94
+ };
95
+
96
+ headerCellModifier = (element: HTMLElement, { column }: ColumnApi) => {
97
+ const columnMeta = meta.forColumn(column, ColumnResizing);
98
+
99
+ element.setAttribute('data-test-is-resizable', `${columnMeta.isResizable}`);
100
+
101
+ applyStyles(element, columnMeta.style);
102
+ };
103
+
104
+ /**
105
+ * This is what ends up calling resize when the browser changes
106
+ * (assuming that the containing element's styles stretch to fill the space)
107
+ *
108
+ * Later, when container queries are more broadly supported, we'll want to watch
109
+ * the container instead of the window to prevent unneeded updates (as a window can change
110
+ * size without the container changing size)
111
+ */
112
+ containerModifier = resizeObserver;
113
+
114
+ reset() {
115
+ preferences.forAllColumns(this.table, ColumnResizing).delete('width');
116
+ }
117
+ }
118
+
119
+ const DEFAULT_COLUMN_OPTIONS = {
120
+ minWidth: 128,
121
+ };
122
+
123
+ const ALLOWED_COLUMN_OPTIONS = ['minWidth', 'width', 'isResizable'];
124
+
125
+ /**
126
+ * @private
127
+ *
128
+ * Contains resizable information for a particular column
129
+ */
130
+ export class ColumnMeta {
131
+ constructor(private column: Column) {}
132
+
133
+ @tracked _width?: number;
134
+ @tracked isResizing = false;
135
+
136
+ get tableMeta() {
137
+ return meta.forTable(this.column.table, ColumnResizing);
138
+ }
139
+
140
+ @cached
141
+ get options() {
142
+ const columnOptions = options.forColumn(this.column, ColumnResizing);
143
+ const filteredOptions = Object.entries(columnOptions || {}).reduce(
144
+ (result, [k, v]) => {
145
+ if (ALLOWED_COLUMN_OPTIONS.includes(k)) {
146
+ result[k] = v;
147
+ }
148
+
149
+ return result;
150
+ },
151
+ {} as Record<string, unknown>,
152
+ ) as ColumnOptions;
153
+
154
+ return {
155
+ ...DEFAULT_COLUMN_OPTIONS,
156
+ ...filteredOptions,
157
+ };
158
+ }
159
+
160
+ get key() {
161
+ return this.column.key;
162
+ }
163
+
164
+ get minWidth() {
165
+ return this.options.minWidth;
166
+ }
167
+
168
+ get initialWidth() {
169
+ const savedWidth = preferences
170
+ .forColumn(this.column, ColumnResizing)
171
+ .get('width');
172
+
173
+ if (!savedWidth) {
174
+ return this.options.width;
175
+ }
176
+
177
+ if (typeof savedWidth !== 'string') {
178
+ assert(
179
+ 'saved width must be a number or string',
180
+ typeof savedWidth === 'number',
181
+ );
182
+ return savedWidth;
183
+ }
184
+
185
+ return parseInt(savedWidth, 10);
186
+ }
187
+
188
+ get canShrink() {
189
+ return this.width && this.width > this.minWidth;
190
+ }
191
+
192
+ get roomToShrink() {
193
+ return this.width ? this.width - this.minWidth : 0;
194
+ }
195
+
196
+ get isResizable() {
197
+ return this.options.isResizable ?? this.tableMeta.isResizable;
198
+ }
199
+
200
+ get hasResizeHandle() {
201
+ const previous = columns.previous(this.column);
202
+
203
+ if (!previous) return false;
204
+
205
+ return (
206
+ this.isResizable && meta.forColumn(previous, ColumnResizing).isResizable
207
+ );
208
+ }
209
+
210
+ get width() {
211
+ let width = this._width ?? this.initialWidth;
212
+
213
+ if (!width) {
214
+ const { defaultColumnWidth } = this.tableMeta;
215
+
216
+ width = defaultColumnWidth
217
+ ? Math.max(defaultColumnWidth, this.minWidth)
218
+ : this.minWidth;
219
+ }
220
+
221
+ return width;
222
+ }
223
+
224
+ set width(value) {
225
+ this._width = value;
226
+ }
227
+
228
+ get style() {
229
+ const styles: Partial<Pick<CSSStyleDeclaration, 'width' | 'minWidth'>> = {};
230
+
231
+ if (this.width) styles.width = `${this.width}px`;
232
+ if (this.minWidth) styles.minWidth = `${this.minWidth}px`;
233
+
234
+ return styles;
235
+ }
236
+
237
+ @action
238
+ resize(delta: number) {
239
+ this.tableMeta.resizeColumn(this.column, delta);
240
+ }
241
+
242
+ @action
243
+ save() {
244
+ this.tableMeta.saveColWidths(this.tableMeta.visibleColumnMetas);
245
+ }
246
+ }
247
+
248
+ /**
249
+ * @private
250
+ *
251
+ * individual column width must exclude:
252
+ * - padding
253
+ * - margin
254
+ * - gap (partial)
255
+ * - any other positioning offsets
256
+ *
257
+ * Otherwise the table will infinitely resize itself
258
+ */
259
+ function distributeDelta(delta: number, visibleColumns: Column[]) {
260
+ if (delta === 0) return;
261
+
262
+ const metas = visibleColumns.map((column) =>
263
+ meta.forColumn(column, ColumnResizing),
264
+ );
265
+
266
+ const resizableMetas = metas.filter(
267
+ (meta) => meta.isResizable && (delta < 0 ? meta.canShrink : true),
268
+ );
269
+
270
+ const columnDelta = delta / resizableMetas.length;
271
+
272
+ for (const meta of resizableMetas) {
273
+ assert('cannot resize a column that does not have a width', meta.width);
274
+ meta.width = Math.max(meta.width + columnDelta, meta.minWidth);
275
+ }
276
+ }
277
+
278
+ /**
279
+ * @private
280
+ *
281
+ * Contains resizable and width information regarding the table as a whole
282
+ */
283
+ export class TableMeta {
284
+ constructor(private table: Table) {}
285
+
286
+ @tracked scrollContainerHeight?: number;
287
+ @tracked scrollContainerWidth?: number;
288
+
289
+ get options() {
290
+ return options.forTable(this.table, ColumnResizing);
291
+ }
292
+
293
+ get isResizable() {
294
+ return this.options?.enabled ?? true;
295
+ }
296
+
297
+ get defaultColumnWidth() {
298
+ if (!this.scrollContainerWidth) return;
299
+
300
+ return (
301
+ (this.scrollContainerWidth - this.totalInitialColumnWidths) /
302
+ this.columnsWithoutInitialWidth.length
303
+ );
304
+ }
305
+
306
+ get #availableColumns() {
307
+ return columns.for(this.table, ColumnResizing);
308
+ }
309
+
310
+ get visibleColumnMetas() {
311
+ return this.#availableColumns.map((column) =>
312
+ meta.forColumn(column, ColumnResizing),
313
+ );
314
+ }
315
+
316
+ get totalInitialColumnWidths() {
317
+ return this.visibleColumnMetas.reduce(
318
+ (acc, meta) => (acc += meta.initialWidth ?? 0),
319
+ 0,
320
+ );
321
+ }
322
+
323
+ get columnsWithoutInitialWidth() {
324
+ return this.visibleColumnMetas.filter((meta) => !meta.initialWidth);
325
+ }
326
+
327
+ get totalVisibleColumnsWidth() {
328
+ return this.visibleColumnMetas.reduce(
329
+ (acc, column) => (acc += column.width ?? 0),
330
+ 0,
331
+ );
332
+ }
333
+
334
+ @action
335
+ saveColWidths(visibleColumnMetas: ColumnMeta[]) {
336
+ const tablePrefs = this.table.preferences;
337
+
338
+ for (const column of visibleColumnMetas) {
339
+ const existing = tablePrefs.storage.forPlugin('ColumnResizing');
340
+ const columnPrefs = existing.forColumn(column.key);
341
+
342
+ columnPrefs.set('width', column.width.toString());
343
+ }
344
+
345
+ tablePrefs.persist();
346
+ }
347
+
348
+ @action
349
+ reset() {
350
+ if (!this.scrollContainerWidth) return;
351
+
352
+ for (const column of this.visibleColumnMetas) {
353
+ column._width = undefined;
354
+ }
355
+ }
356
+
357
+ @action
358
+ onTableResize(entry: ResizeObserverEntry) {
359
+ assert(
360
+ 'scroll container element must be an HTMLElement',
361
+ entry.target instanceof HTMLElement,
362
+ );
363
+
364
+ this.scrollContainerWidth = getAccurateClientWidth(entry.target);
365
+ this.scrollContainerHeight = getAccurateClientHeight(entry.target);
366
+
367
+ // TODO: extract this to card-list and remove it from the plugin
368
+ // card-list will provide its own column-resizing plugin
369
+ // by sub-classing this one, and defining its own way of calculating the "diff"
370
+ const totalGap = totalGapOf(entry.target.querySelector('[role="row"]'));
371
+ const diff =
372
+ this.scrollContainerWidth - this.totalVisibleColumnsWidth - totalGap;
373
+
374
+ distributeDelta(diff, this.#availableColumns);
375
+ }
376
+
377
+ @action
378
+ resizeColumn(column: Column, delta: number) {
379
+ if (delta === 0) return;
380
+
381
+ /**
382
+ * When the delta is negative, we are dragging to the next
383
+ * when positive, we are dragging to the right
384
+ * when dragging to the right, we want to grow the column
385
+ * when dragging to the left, we grow the "next" column,
386
+ * which shrinks the column we're dragging
387
+ *
388
+ * This assumes the resize handle for any column is on the right-hand
389
+ * side of the column header
390
+ *
391
+ * If the resize handle were on the left-hand side of the column header
392
+ * we'd want the column.next to be column.previous
393
+ *
394
+ * This is CSS dependent, and can be configured in plugin
395
+ * options
396
+ */
397
+ const isDraggingRight = delta > 0;
398
+ const position = this.options?.handlePosition ?? 'left';
399
+
400
+ let growingColumn: Column | null | undefined;
401
+
402
+ if (position === 'right') {
403
+ growingColumn = isDraggingRight ? columns.next(column) : column;
404
+ } else {
405
+ growingColumn = isDraggingRight ? columns.previous(column) : column;
406
+ }
407
+
408
+ if (!growingColumn) return;
409
+
410
+ const growingColumnMeta = meta.forColumn(growingColumn, ColumnResizing);
411
+
412
+ assert(
413
+ 'cannot resize a column that does not have a width',
414
+ growingColumnMeta.width,
415
+ );
416
+
417
+ const shrinkableColumns =
418
+ delta > 0
419
+ ? columns.after(growingColumn)
420
+ : columns.before(growingColumn).reverse();
421
+
422
+ const shrinkableColumnsMetas = shrinkableColumns
423
+ .map((column) => meta.forColumn(column, ColumnResizing))
424
+ .filter((meta) => meta.canShrink);
425
+
426
+ let remainder = Math.abs(delta);
427
+
428
+ while (shrinkableColumnsMetas.length > 0) {
429
+ const shrinkingColumnMeta = shrinkableColumnsMetas.shift();
430
+
431
+ assert(
432
+ 'cannot resize a column that does not have a width',
433
+ shrinkingColumnMeta?.width,
434
+ );
435
+
436
+ const actualDelta = Math.min(remainder, shrinkingColumnMeta.roomToShrink);
437
+
438
+ growingColumnMeta.width += actualDelta;
439
+ shrinkingColumnMeta.width -= actualDelta;
440
+ remainder -= actualDelta;
441
+ }
442
+ }
443
+ }
444
+
445
+ /**
446
+ * @private
447
+ * included in the same file as the plugin due to circular dependency
448
+ *
449
+ * This goes on the containing element
450
+ *
451
+ * @example
452
+ * ```hbs
453
+ * <div {{resizeObserver @table}}>
454
+ * <table>
455
+ * ```
456
+ */
457
+ function resizeObserver(element: HTMLElement, table: Table) {
458
+ const observer = getObserver(element, table);
459
+
460
+ observer.observe(element);
461
+
462
+ return () => {
463
+ observer.unobserve(element);
464
+ };
465
+ }
466
+
467
+ const CACHE = new WeakMap<HTMLElement, ResizeObserver>();
468
+
469
+ /**
470
+ * This is technically "inefficient" as you don't want too many resize
471
+ * observers on a page, but tables are so big, that I don't see too many use cases
472
+ * where you'd have 10+ tables on a page
473
+ */
474
+ function getObserver(element: HTMLElement, table: Table): ResizeObserver {
475
+ let existing = CACHE.get(element);
476
+
477
+ if (existing) return existing;
478
+
479
+ existing = new ResizeObserver((entries: ResizeObserverEntry[]) => {
480
+ if (isDestroyed(table) || isDestroying(table)) {
481
+ return;
482
+ }
483
+
484
+ for (const entry of entries) {
485
+ meta.forTable(table, ColumnResizing).onTableResize(entry);
486
+ }
487
+ });
488
+
489
+ return existing;
490
+ }
@@ -0,0 +1,48 @@
1
+ import { isDestroyed, isDestroying } from '@ember/destroyable';
2
+
3
+ /**
4
+ * @private
5
+ * included in the same file as the plugin due to circular dependency
6
+ *
7
+ * This goes on the containing element
8
+ *
9
+ * @example
10
+ * ```hbs
11
+ * <div {{resizeObserver @table}}>
12
+ * <table>
13
+ * ```
14
+ */
15
+ export function resizeObserver(element: HTMLElement, table: any) {
16
+ const observer = getObserver(element, table);
17
+
18
+ observer.observe(element);
19
+
20
+ return () => {
21
+ observer.unobserve(element);
22
+ };
23
+ }
24
+
25
+ const CACHE = new WeakMap<HTMLElement, ResizeObserver>();
26
+
27
+ /**
28
+ * This is technically "inefficient" as you don't want too many resize
29
+ * observers on a page, but tables are so big, that I don't see too many use cases
30
+ * where you'd have 10+ tables on a page
31
+ */
32
+ function getObserver(element: HTMLElement, table: any): ResizeObserver {
33
+ let existing = CACHE.get(element);
34
+
35
+ if (existing) return existing;
36
+
37
+ existing = new ResizeObserver((entries: ResizeObserverEntry[]) => {
38
+ if (isDestroyed(table) || isDestroying(table)) {
39
+ return;
40
+ }
41
+
42
+ for (const entry of entries) {
43
+ table.handleScrollContainerResize(entry);
44
+ }
45
+ });
46
+
47
+ return existing;
48
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ We want to make sure we get the clientWidth rather than the
3
+ offsetWidth so that the width of the scrollbar is not included when we're
4
+ resizing the columns in the table so that they fit within the scroll
5
+ container
6
+
7
+ Ideally we would just use `entry.contentRect.width`, which we can access
8
+ without triggering any reflows. Unfortunately there are differences in the
9
+ way that this works in Chrome vs Firefox. In Chrome, the
10
+ `entry.contentRect.width` works the same as `entry.target.clientWidth`,
11
+ which does not include the width that is taken up by the vertical scrollbar
12
+ if the element overflows. In Firefox the `entry.contentRect.width` is the
13
+ same as `entry.target.offsetWidth`, which does include the width taken up by
14
+ the scrollbar.
15
+
16
+ We use `getBoundingClientRect()` because it does not round the value to an
17
+ integer, which can sometimes cause subpixel gaps.
18
+ **/
19
+ export const getAccurateClientWidth = (element: HTMLElement) => {
20
+ const style = getComputedStyle(element);
21
+ const padding =
22
+ parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
23
+ const scrollbarWidth = element.offsetWidth - element.clientWidth;
24
+
25
+ return element.getBoundingClientRect().width - padding - scrollbarWidth;
26
+ };
27
+
28
+ export const totalGapOf = (element?: Element | null) => {
29
+ if (!element) return 0;
30
+
31
+ const style = getComputedStyle(element);
32
+ const gapSize = parseFloat(style.columnGap);
33
+ const cells = element.querySelectorAll(
34
+ '[role="cell"], [role="columnheader"]',
35
+ );
36
+
37
+ let totalCellPadding = 0;
38
+
39
+ for (const cell of cells) {
40
+ const style = getComputedStyle(cell);
41
+ const padding =
42
+ parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
43
+
44
+ totalCellPadding += padding;
45
+ }
46
+
47
+ return gapSize * (element.children.length - 1) + totalCellPadding;
48
+ };
49
+
50
+ export const getAccurateClientHeight = (element: HTMLElement) => {
51
+ const scrollbarHeight = element.offsetHeight - element.clientHeight;
52
+
53
+ return element.getBoundingClientRect().height - scrollbarHeight;
54
+ };
@@ -0,0 +1,28 @@
1
+ import { meta } from '../-private/base.ts';
2
+ import { ColumnVisibility } from './plugin.ts';
3
+
4
+ import type { Column } from '../../index.ts';
5
+
6
+ /**
7
+ * Hide a column
8
+ */
9
+ export const hide = (column: Column) =>
10
+ meta.forColumn(column, ColumnVisibility).hide();
11
+
12
+ /**
13
+ * Show a column
14
+ */
15
+ export const show = (column: Column) =>
16
+ meta.forColumn(column, ColumnVisibility).show();
17
+
18
+ /**
19
+ * Ask if a column is presently supposed to be visible
20
+ */
21
+ export const isVisible = (column: Column) =>
22
+ meta.forColumn(column, ColumnVisibility).isVisible;
23
+
24
+ /**
25
+ * Ask if a column is presently supposed to be hidden
26
+ */
27
+ export const isHidden = (column: Column) =>
28
+ meta.forColumn(column, ColumnVisibility).isHidden;
@@ -0,0 +1,6 @@
1
+ export * from './helpers.ts';
2
+ export { ColumnVisibility } from './plugin.ts';
3
+ export { ColumnVisibility as Plugin } from './plugin.ts';
4
+
5
+ // Types
6
+ export type { Signature } from './plugin.ts';