@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,218 @@
1
+ import { cached } from '@glimmer/tracking';
2
+ import { assert } from '@ember/debug';
3
+
4
+ import { BasePlugin, meta, options } from '../-private/base.ts';
5
+
6
+ import type { Row, Table } from '../../index.ts';
7
+ import type { PluginSignature, RowApi } from '../../-private/interfaces';
8
+
9
+ export interface Signature<DataType = any, Key = DataType>
10
+ extends PluginSignature {
11
+ Meta: {
12
+ Table: TableMeta;
13
+ Row: RowMeta;
14
+ };
15
+ Options: {
16
+ Plugin: {
17
+ /**
18
+ * A set of selected things using the same type of Identifier
19
+ * returned from `key`
20
+ */
21
+ selection: Set<Key> | Array<Key>;
22
+ } & (
23
+ | {
24
+ /**
25
+ * For a given row's data, how should the key be determined?
26
+ * this could be a remote id from a database, or some other attribute
27
+ *
28
+ * This could be useful for indicating in UI if a particular item is selected.
29
+ *
30
+ * If not provided, the row's data will be used as the key
31
+ */
32
+ key: (data: DataType) => Key;
33
+ /**
34
+ * When a row is clicked, this will be invoked,
35
+ * allowing you to update your selection object
36
+ */
37
+ onSelect: (item: Key, row: Row<DataType>) => void;
38
+ /**
39
+ * When a row is clicked (and the row is selected), this will be invoked,
40
+ * allowing you to update your selection object
41
+ */
42
+ onDeselect: (item: Key, row: Row<DataType>) => void;
43
+ }
44
+ | {
45
+ /**
46
+ * When a row is clicked (and the row is not selected), this will be invoked,
47
+ * allowing you to update your selection object
48
+ */
49
+ onSelect: (item: DataType | any, row: Row<DataType>) => void;
50
+ /**
51
+ * When a row is clicked (and the row is selected), this will be invoked,
52
+ * allowing you to update your selection object
53
+ */
54
+ onDeselect: (item: DataType | any, row: Row<DataType>) => void;
55
+ }
56
+ );
57
+ };
58
+ }
59
+
60
+ /**
61
+ * This plugin provides a means of managing selection of a single row in a table.
62
+ *
63
+ * The state of what is actually selected is managed by you, but this plugin
64
+ * will wire up the click listeners as well as let you know which *data* is clicked.
65
+ */
66
+ export class RowSelection<DataType = any, Key = DataType> extends BasePlugin<
67
+ Signature<DataType, Key>
68
+ > {
69
+ name = 'row-selection';
70
+
71
+ meta = {
72
+ row: RowMeta,
73
+ table: TableMeta,
74
+ };
75
+
76
+ constructor(table: Table) {
77
+ super(table);
78
+
79
+ const pluginOptions = options.forTable(this.table, RowSelection);
80
+
81
+ assert(
82
+ `selection, onSelect, and onDeselect are all required arguments for the RowSelection plugin. ` +
83
+ `Specify these options via \`RowSelection.with(() => ({ selection, onSelect, onDeselect }))\``,
84
+ pluginOptions.selection &&
85
+ pluginOptions.onSelect &&
86
+ pluginOptions.onDeselect,
87
+ );
88
+ }
89
+
90
+ rowModifier = (element: HTMLElement, { row }: RowApi<Table<any>>) => {
91
+ const handler = (event: Event) => {
92
+ this.#clickHandler(row, event);
93
+ };
94
+
95
+ element.addEventListener('click', handler);
96
+
97
+ return () => {
98
+ element.removeEventListener('click', handler);
99
+ };
100
+ };
101
+
102
+ #clickHandler = (row: Row, event: Event) => {
103
+ assert(
104
+ `expected event.target to be an instance of HTMLElement`,
105
+ event.target instanceof HTMLElement || event.target instanceof SVGElement,
106
+ );
107
+
108
+ const selection = document.getSelection();
109
+
110
+ if (selection) {
111
+ const { type, anchorNode } = selection;
112
+ const isSelectingText =
113
+ type === 'Range' && event.target?.contains(anchorNode);
114
+
115
+ if (isSelectingText) {
116
+ event.stopPropagation();
117
+
118
+ return;
119
+ }
120
+ }
121
+
122
+ // Ignore clicks on interactive elements within the row
123
+ const inputParent = event.target.closest('input, button, label, a, select');
124
+
125
+ if (inputParent) {
126
+ return;
127
+ }
128
+
129
+ const rowMeta = meta.forRow(row, RowSelection);
130
+
131
+ rowMeta.toggle();
132
+ };
133
+ }
134
+
135
+ class TableMeta {
136
+ #table: Table;
137
+
138
+ constructor(table: Table) {
139
+ this.#table = table;
140
+ }
141
+
142
+ @cached
143
+ get selection(): Set<unknown> {
144
+ const passedSelection = options.forTable(
145
+ this.#table,
146
+ RowSelection,
147
+ ).selection;
148
+
149
+ assert(`Cannot access selection because it is undefined`, passedSelection);
150
+
151
+ if (passedSelection instanceof Set) {
152
+ return passedSelection;
153
+ }
154
+
155
+ return new Set(passedSelection);
156
+ }
157
+ }
158
+
159
+ class RowMeta {
160
+ #row: Row<any>;
161
+
162
+ constructor(row: Row<any>) {
163
+ this.#row = row;
164
+ }
165
+
166
+ get isSelected(): boolean {
167
+ const tableMeta = meta.forTable(this.#row.table, RowSelection);
168
+ const pluginOptions = options.forTable(this.#row.table, RowSelection);
169
+
170
+ if ('key' in pluginOptions && pluginOptions.key) {
171
+ const compareWith = pluginOptions.key(this.#row.data);
172
+
173
+ return tableMeta.selection.has(compareWith);
174
+ }
175
+
176
+ const compareWith = this.#row.data;
177
+
178
+ return tableMeta.selection.has(compareWith);
179
+ }
180
+
181
+ toggle = () => {
182
+ if (this.isSelected) {
183
+ this.deselect();
184
+
185
+ return;
186
+ }
187
+
188
+ this.select();
189
+ };
190
+
191
+ select = () => {
192
+ const pluginOptions = options.forTable(this.#row.table, RowSelection);
193
+
194
+ if ('key' in pluginOptions && pluginOptions.key) {
195
+ const key = pluginOptions.key(this.#row.data);
196
+
197
+ pluginOptions.onSelect?.(key, this.#row);
198
+
199
+ return;
200
+ }
201
+
202
+ pluginOptions.onSelect?.(this.#row.data, this.#row);
203
+ };
204
+
205
+ deselect = () => {
206
+ const pluginOptions = options.forTable(this.#row.table, RowSelection);
207
+
208
+ if ('key' in pluginOptions && pluginOptions.key) {
209
+ const key = pluginOptions.key(this.#row.data);
210
+
211
+ pluginOptions.onDeselect?.(key, this.#row);
212
+
213
+ return;
214
+ }
215
+
216
+ pluginOptions.onDeselect?.(this.#row.data, this.#row);
217
+ };
218
+ }
@@ -0,0 +1,59 @@
1
+ import { htmlSafe } from '@ember/template';
2
+
3
+ import { meta } from '../-private/base.ts';
4
+ import { StickyColumns } from './plugin.ts';
5
+
6
+ import type { Column } from '../../index.ts';
7
+
8
+ export const isSticky = <DataType = unknown>(column: Column<DataType>) =>
9
+ meta.forColumn(column, StickyColumns).isSticky;
10
+
11
+ export const styleFor = <DataType = unknown>(
12
+ column: Column<DataType>,
13
+ ): Partial<CSSStyleDeclaration> => meta.forColumn(column, StickyColumns).style;
14
+
15
+ /**
16
+ * In this plugin, both header and cells have the same styles,
17
+ * if applicable.
18
+ *
19
+ * Until this RFC https://github.com/emberjs/rfcs/pull/883
20
+ * is merged and implemented, we can't performantly
21
+ * use modifiers for apply styles.
22
+ *
23
+ * In the mean time, we'll need to append style strings, which is more work
24
+ * for consumers, but is a reasonable trade-off for now.
25
+ */
26
+ export const styleStringFor = <DataType = unknown>(
27
+ column: Column<DataType>,
28
+ ): ReturnType<typeof htmlSafe> => {
29
+ const columnMeta = meta.forColumn(column, StickyColumns);
30
+
31
+ let result = '';
32
+
33
+ if (columnMeta.isSticky) {
34
+ for (const [key, value] of Object.entries(columnMeta.style)) {
35
+ result += `${toStyle(key)}:${value};`;
36
+ }
37
+
38
+ result = ';' + result;
39
+ }
40
+
41
+ return htmlSafe(result);
42
+ };
43
+
44
+ /**
45
+ * the JS API for styles is camel case,
46
+ * but CSS is kebab-case. To save on complexity and
47
+ * amount of code, we have a super small conversion function
48
+ * for only the properties relevant to the sticky plugin.
49
+ */
50
+ const toStyle = (key: string): string => {
51
+ switch (key) {
52
+ case 'zIndex':
53
+ return 'z-index';
54
+ case 'minWidth':
55
+ return 'min-width';
56
+ default:
57
+ return key;
58
+ }
59
+ };
@@ -0,0 +1,7 @@
1
+ // Public API
2
+ export * from './helpers.ts';
3
+ export { StickyColumns } from './plugin.ts';
4
+ export { StickyColumns as Plugin } from './plugin.ts';
5
+
6
+ // Public types
7
+ export type { Signature } from './plugin.ts';
@@ -0,0 +1,201 @@
1
+ import { cached } from '@glimmer/tracking';
2
+ import { assert } from '@ember/debug';
3
+
4
+ import { BasePlugin, columns, meta, options } from '../-private/base.ts';
5
+ import { applyStyles } from '../-private/utils.ts';
6
+
7
+ import type { ColumnApi } from '../../plugins/index.ts';
8
+ import type { Column } from '../../index.ts';
9
+
10
+ const DEFAULT_Z_INDEX = '8';
11
+
12
+ interface ColumnOptions {
13
+ /**
14
+ * Whether or not to enable stickiness on the column
15
+ * (default is false)
16
+ *
17
+ * valid values: 'left', 'right', false
18
+ */
19
+ sticky?: boolean | string;
20
+ }
21
+
22
+ export interface Signature {
23
+ Options: {
24
+ Column: ColumnOptions;
25
+ Plugin: {
26
+ /**
27
+ * Opts this plugin out of engaging in the modifier system
28
+ * and instead requires setting a `style` attribute on
29
+ * th / td tags for getting the "position: sticky" behovior
30
+ * on columns.
31
+ */
32
+ workaroundForModifierTimingUpdateRFC883?: boolean;
33
+ };
34
+ };
35
+ Meta: {
36
+ Table: TableMeta;
37
+ Column: ColumnMeta;
38
+ };
39
+ }
40
+
41
+ export class StickyColumns extends BasePlugin<Signature> {
42
+ name = 'sticky-columns';
43
+
44
+ /**
45
+ * This plugin requires that the resizing plugin be present, because the resizing plugin is
46
+ * what manages the base width of the columns.
47
+ *
48
+ * Other width-management plugins can be used instead of ColumnResizing, but they must declare
49
+ * that they manage the width of the columns.
50
+ */
51
+ static requires = ['columnWidth'];
52
+
53
+ meta = {
54
+ table: TableMeta,
55
+ column: ColumnMeta,
56
+ };
57
+
58
+ conditionallyRemoveStyles = (element: HTMLElement) => {
59
+ if (element.style.getPropertyValue('position') === 'sticky') {
60
+ element.style.removeProperty('position');
61
+ }
62
+
63
+ if (element.style.getPropertyValue('left')) {
64
+ element.style.left = '';
65
+ }
66
+
67
+ if (element.style.getPropertyValue('right')) {
68
+ element.style.right = '';
69
+ }
70
+
71
+ if (element.style.zIndex === DEFAULT_Z_INDEX) {
72
+ element.style.zIndex = '';
73
+ }
74
+ };
75
+
76
+ headerCellModifier = (element: HTMLElement, { column, table }: ColumnApi) => {
77
+ if (
78
+ options.forTable(table, StickyColumns)
79
+ .workaroundForModifierTimingUpdateRFC883
80
+ ) {
81
+ return;
82
+ }
83
+
84
+ const columnMeta = meta.forColumn(column, StickyColumns);
85
+
86
+ if (columnMeta.isSticky) {
87
+ applyStyles(element, columnMeta.style);
88
+ } else {
89
+ this.conditionallyRemoveStyles(element);
90
+ }
91
+ };
92
+
93
+ /**
94
+ * Not yet supported. Pending Ember RFC #883
95
+ *
96
+ * TODO: switch ColumnApi to "RowApi", add the row's data
97
+ */
98
+ cellModifier = (element: HTMLElement, { column, table }: ColumnApi) => {
99
+ if (
100
+ options.forTable(table, StickyColumns)
101
+ .workaroundForModifierTimingUpdateRFC883
102
+ ) {
103
+ return;
104
+ }
105
+
106
+ const columnMeta = meta.forColumn(column, StickyColumns);
107
+
108
+ if (columnMeta.isSticky) {
109
+ applyStyles(element, columnMeta.style);
110
+ } else {
111
+ this.conditionallyRemoveStyles(element);
112
+ }
113
+ };
114
+ }
115
+
116
+ /**
117
+ * @private
118
+ *
119
+ * Contains state and behaviors for the sticiness
120
+ */
121
+ export class ColumnMeta {
122
+ constructor(private column: Column) {}
123
+
124
+ get isSticky() {
125
+ return this.position !== 'none';
126
+ }
127
+
128
+ get position(): 'left' | 'right' | 'none' {
129
+ const sticky = options.forColumn(this.column, StickyColumns)?.sticky;
130
+
131
+ assert(
132
+ `Invalid sticky value, ${sticky}. Valid values: 'left', 'right', false`,
133
+ sticky === 'left' ||
134
+ sticky === 'right' ||
135
+ sticky === false ||
136
+ sticky === undefined,
137
+ );
138
+
139
+ return sticky || 'none';
140
+ }
141
+
142
+ @cached
143
+ get offset() {
144
+ if (!this.isSticky) {
145
+ return;
146
+ }
147
+
148
+ if (this.position === 'left') {
149
+ const leftColumns = columns.before(this.column);
150
+ const left = leftColumns.reduce((acc, column) => {
151
+ const columnMeta = meta.withFeature.forColumn(column, 'columnWidth');
152
+
153
+ if (hasWidth(columnMeta)) {
154
+ return acc + (columnMeta.width ?? 0);
155
+ }
156
+
157
+ return acc;
158
+ }, 0);
159
+
160
+ return `${left}px`;
161
+ }
162
+
163
+ if (this.position === 'right') {
164
+ const rightColumns = columns.after(this.column);
165
+ const right = rightColumns.reduce((acc, column) => {
166
+ const columnMeta = meta.withFeature.forColumn(column, 'columnWidth');
167
+
168
+ if (hasWidth(columnMeta)) {
169
+ return acc + (columnMeta.width ?? 0);
170
+ }
171
+
172
+ return acc;
173
+ }, 0);
174
+
175
+ return `${right}px`;
176
+ }
177
+
178
+ return;
179
+ }
180
+
181
+ get style(): Partial<
182
+ Pick<CSSStyleDeclaration, 'position' | 'left' | 'right' | 'zIndex'>
183
+ > {
184
+ if (this.isSticky) {
185
+ return {
186
+ position: 'sticky',
187
+ [this.position]: this.offset,
188
+ zIndex: DEFAULT_Z_INDEX,
189
+ };
190
+ }
191
+
192
+ return {};
193
+ }
194
+ }
195
+
196
+ function hasWidth(obj: any): obj is { width?: number } {
197
+ return typeof obj === 'object' && obj && 'width' in obj;
198
+ }
199
+
200
+ /* This Plugin does not need table state */
201
+ export class TableMeta {}
@@ -0,0 +1,76 @@
1
+ import { assert } from '@ember/debug';
2
+ import { find, settled, triggerEvent } from '@ember/test-helpers';
3
+
4
+ interface Selectors {
5
+ resizeHandle?: string;
6
+ scrollContainer?: string;
7
+ }
8
+
9
+ export function createHelpers(selectors: Selectors) {
10
+ async function resize(parent: Element, delta: number) {
11
+ assert(
12
+ `Can't use the dragLeft/dragRight/resize helpers without a \`resizeHandle\` selector`,
13
+ selectors.resizeHandle,
14
+ );
15
+
16
+ const element = parent.querySelector(selectors.resizeHandle);
17
+
18
+ assert(`Can't resize without a handle`, element);
19
+
20
+ /**
21
+ * Start the click in exactly the middle of the element.
22
+ * "startX" is the horizontal middle of "element"
23
+ */
24
+ const rect = element.getBoundingClientRect();
25
+ const startX = (rect.right + rect.left) / 2;
26
+
27
+ const targetX = startX + delta;
28
+
29
+ triggerEvent(element, 'mousedown', { clientX: startX, button: 0 });
30
+ triggerEvent(element, 'mousemove', { clientX: targetX, button: 0 });
31
+ triggerEvent(element, 'mouseup', { clientX: targetX, button: 0 });
32
+
33
+ await settled();
34
+ }
35
+
36
+ function horizontalScrollElement() {
37
+ assert(
38
+ `Can't use scrollRight/swipeLeft helpers without a \`scrollContainer\` selector`,
39
+ selectors.scrollContainer,
40
+ );
41
+
42
+ const element = find(selectors.scrollContainer);
43
+
44
+ assert(`scroll container not found`, element instanceof HTMLElement);
45
+
46
+ return element;
47
+ }
48
+
49
+ async function scrollRight(distance: number) {
50
+ const element = horizontalScrollElement();
51
+
52
+ element.scrollLeft += distance;
53
+ await requestAnimationFrameSettled();
54
+ }
55
+
56
+ async function scrollLeft(distance: number) {
57
+ const element = horizontalScrollElement();
58
+
59
+ element.scrollLeft -= distance;
60
+ await requestAnimationFrameSettled();
61
+ }
62
+
63
+ return {
64
+ dragLeft: (column: Element, amount: number) => resize(column, -amount),
65
+ dragRight: (column: Element, amount: number) => resize(column, amount),
66
+ scrollLeft,
67
+ scrollRight,
68
+ swipeLeft: scrollRight,
69
+ swipeRight: scrollLeft,
70
+ };
71
+ }
72
+
73
+ export async function requestAnimationFrameSettled() {
74
+ await new Promise(requestAnimationFrame);
75
+ await settled();
76
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,85 @@
1
+ import { assert } from '@ember/debug';
2
+ import { camelize, underscore } from '@ember/string';
3
+
4
+ import { SortDirection } from './plugins/data-sorting/types.ts';
5
+
6
+ import type { Sort, SortsOptions } from './plugins/data-sorting/types.ts';
7
+
8
+ /**
9
+ * @example
10
+ *
11
+ * ```ts
12
+ * deserializeSorts('first_name.asc', { separator: '.', transform: 'camelize' });
13
+ * // => [{ property: 'firstName', direction: 'ascending' }]
14
+ *
15
+ * deserializeSorts('last_name.desc', { separator: '.', transform: 'camelize' });
16
+ * // => [{ property: 'lastName', direction: 'descending' }]
17
+ * ```
18
+ */
19
+
20
+ export const deserializeSorts = (
21
+ sortString: string,
22
+ options: SortsOptions = { separator: '.', transform: 'camelize' },
23
+ ): Sort[] => {
24
+ if (!sortString) {
25
+ return [];
26
+ }
27
+
28
+ const { transform, separator } = options;
29
+ let [key, direction] = sortString.split(separator);
30
+
31
+ assert(
32
+ `No key found for input: \`${sortString}\` using \`${separator}\` as a separator`,
33
+ key,
34
+ );
35
+
36
+ if (transform === 'camelize') {
37
+ key = camelize(key);
38
+ } else if (transform === 'underscore') {
39
+ key = underscore(key);
40
+ }
41
+
42
+ return [
43
+ {
44
+ property: key,
45
+ direction:
46
+ direction === 'asc'
47
+ ? SortDirection.Ascending
48
+ : SortDirection.Descending,
49
+ },
50
+ ];
51
+ };
52
+
53
+ /**
54
+ * @example
55
+ *
56
+ * ```ts
57
+ * serializeSorts([{ property: 'firstName', direction: 'ascending' }],{ separator: '.', transform: 'camelize' });
58
+ * // => 'first_name.asc'
59
+ *
60
+ * serializeSorts([{ property: 'lastName', direction: 'descending' }],{ separator: '.', transform: 'camelize' });
61
+ * // => 'last_name.desc'
62
+ * ```
63
+ */
64
+ export function serializeSorts(
65
+ sorts: Sort[],
66
+ options: SortsOptions = { separator: '.', transform: 'underscore' },
67
+ ): string {
68
+ const { transform, separator } = options;
69
+
70
+ const sortParameters = sorts.map(({ direction, property }) => {
71
+ const shortDirection = direction === 'ascending' ? 'asc' : 'desc';
72
+
73
+ let sortField = property;
74
+
75
+ if (transform === 'underscore') {
76
+ sortField = underscore(property);
77
+ } else if (transform === 'camelize') {
78
+ sortField = camelize(property);
79
+ }
80
+
81
+ return `${sortField}${separator}${shortDirection}`;
82
+ });
83
+
84
+ return sortParameters.join('+');
85
+ }