From 48ef0bbfba8cf677be09c5c40e8152b46a64e074 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Thu, 20 Oct 2022 18:55:20 -0400 Subject: [PATCH 1/4] feat(plugins): simplify working with columns among plugins There is now a new "columns" query-API that "does the right thing", based on a "requester" (plugin class). There is a hierarchy of column behaviors that can be statically known, and the resolution of columns allows plugins to be independent of other plugins, while also layering in the correct order, based on if the plugin defines a columns property. --- .../src/-private/interfaces/plugins.ts | 17 + ember-headless-table/src/-private/table.ts | 11 +- .../src/plugins/-private/base.ts | 191 +++++++ .../src/plugins/column-reordering/plugin.ts | 69 +-- .../src/plugins/column-resizing/plugin.ts | 18 +- .../src/plugins/column-visibility/plugin.ts | 64 +-- ember-headless-table/src/plugins/index.ts | 2 +- .../src/plugins/sticky-columns/plugin.ts | 10 +- pnpm-lock.yaml | 73 +-- test-app/package.json | 1 + .../column-reordering/rendering-test.gts | 139 ++++-- .../column-resizing/rendering-test.gts | 2 +- .../column-visibility/rendering-test.gts | 29 +- test-app/tests/plugins/consumption-test.ts | 15 +- .../tests/plugins/queries/columns-test.ts | 464 ++++++++++++++++++ test-app/tests/unit/public-api-test.ts | 1 + 16 files changed, 833 insertions(+), 273 deletions(-) create mode 100644 test-app/tests/plugins/queries/columns-test.ts diff --git a/ember-headless-table/src/-private/interfaces/plugins.ts b/ember-headless-table/src/-private/interfaces/plugins.ts index a63df6a4..a08e4825 100644 --- a/ember-headless-table/src/-private/interfaces/plugins.ts +++ b/ember-headless-table/src/-private/interfaces/plugins.ts @@ -171,6 +171,23 @@ export interface Plugin { * If the plugin has state, this should be used to reset that state */ reset?: () => void; + + /** + * @public + * @kind Table Hook + * + * A plugin may change the columns order, visibility, etc. + * By implementing this getter, this plugin's + * `columns` property will be used by other plugins via + * the `columns.for(table, RequestingPlugin)` api. + * + * For the end-consumer, they may choose to do + * `columns.for(table)`, which will aggregate all column modifications + * from all plugins. + * + * As always, `table.columns` is the way to get the unmodified list of columns. + */ + columns?: Column[]; } /** diff --git a/ember-headless-table/src/-private/table.ts b/ember-headless-table/src/-private/table.ts index f65a5683..86310e1b 100644 --- a/ember-headless-table/src/-private/table.ts +++ b/ember-headless-table/src/-private/table.ts @@ -205,6 +205,8 @@ export class Table extends Resource> { /** * @private + * + * used by other private APIs */ get config() { return this.args.named; @@ -234,15 +236,6 @@ export class Table extends Resource> { }, }); - /** - * @private - * - * TODO: what's this for? - */ - get value() { - return this; - } - /** * @private */ diff --git a/ember-headless-table/src/plugins/-private/base.ts b/ember-headless-table/src/plugins/-private/base.ts index f0882755..c2300d92 100644 --- a/ember-headless-table/src/plugins/-private/base.ts +++ b/ember-headless-table/src/plugins/-private/base.ts @@ -227,6 +227,197 @@ export const preferences = { }, }; +/** + * if a `requester` is not provided, + * Get the columns for the table, considering any and all plugins that could modify columns. + * + * If you are an end-consumer of ember-headless-table, this is the function to use. + * If you are a plugin-author, you'll want to pass your plugin class as the second parameter. + * + * For a given plugin, `requester`, determine what columns should be returned. + * Since multiple plugins could be used in a table, there is an implicit hierarchy of + * column modifications that can occur from each of those plugins. + * + * If a plugin defines other plugins as either *requirements* or *optional requirements*, + * and that upstream plugin defines a `columns` property, then those columns will be returned here. + * + * This works recursively up the plugin tree up until a plugin has no requirements, and then + * all colums from the table are returned. + */ +function columnsFor( + table: Table, + requester?: Plugin | undefined +): Column[] { + assert(`First argument passed to columns.for must be an instance of Table`, table[TABLE_KEY]); + + let visibility = findPlugin(table.plugins, 'columnVisibility'); + let reordering = findPlugin(table.plugins, 'columnOrder'); + + // TODO: actually resolve the graph, rather than use the hardcoded feature names + // atm, this only "happens" to work based on expectations of + // of the currently implemented plugins' capabilities and implied hierarchy. + + if (requester) { + assert( + `[${requester.name}] requested columns from the table, but the plugin, ${requester.name}, ` + + `is not used in this table`, + table.plugins.some((plugin) => plugin instanceof (requester as Class)) + ); + + if (visibility && visibility.constructor === requester) { + return table.columns.values(); + } + + if (reordering && reordering.constructor === requester) { + if (visibility) { + assert( + `<#${visibility.name}> defined a 'columns' property, but did not return valid data.`, + visibility.columns && Array.isArray(visibility.columns) + ); + + return visibility.columns; + } + + return table.columns.values(); + } + + if (reordering) { + assert( + `<#${reordering.name}> defined a 'columns' property, but did not return valid data.`, + reordering.columns && Array.isArray(reordering.columns) + ); + + return reordering.columns; + } + + if (visibility) { + assert( + `<#${visibility.name}> defined a 'columns' property, but did not return valid data.`, + visibility.columns && Array.isArray(visibility.columns) + ); + + return visibility.columns; + } + + return table.columns.values(); + } + + /** + * This flow is the inverse of when we have a requester + */ + + if (reordering) { + assert( + `<#${reordering.name}> defined a 'columns' property, but did not return valid data.`, + reordering.columns && Array.isArray(reordering.columns) + ); + + return reordering.columns; + } + + if (visibility) { + assert( + `<#${visibility.name}> defined a 'columns' property, but did not return valid data.`, + visibility.columns && Array.isArray(visibility.columns) + ); + + return visibility.columns; + } + + return table.columns.values(); +} + +export const columns = { + for: columnsFor, + + /** + * for a given current or reference column, return the column that + * is immediately next, or to the right of that column. + * + * If a plugin class is provided, the hierarchy of column list modifiecations + * will be respected. + */ + next: ( + current: Column, + requester?: Plugin + ): Column | undefined => { + let columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table); + + let referenceIndex = columns.indexOf(current); + + assert( + `index of reference column must be >= 0. column likely not a part of the table`, + referenceIndex >= 0 + ); + + /** + * There can be nothing after the last column + */ + if (referenceIndex >= columns.length - 1) { + return undefined; + } + + return columns[referenceIndex + 1]; + }, + + /** + * for a given current or reference column, return the column that + * is immediately previous, or to the left of that column. + * + * If a plugin class is provided, the hierarchy of column list modifiecations + * will be respected. + */ + previous: ( + current: Column, + requester?: Plugin + ): Column | undefined => { + let columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table); + let referenceIndex = columns.indexOf(current); + + assert( + `index of reference column must be >= 0. column likely not a part of the table`, + referenceIndex >= 0 + ); + + /** + * There can be nothing before the first column + */ + if (referenceIndex === 0) { + return undefined; + } + + return columns[referenceIndex - 1]; + }, + /** + * for a given current or reference column, return the columns that + * should appear before, or to the left of that column. + * + * if a plugin class is provided, the hierarchy of column list modifications + * will be respected. + */ + before: (current: Column, requester?: Plugin): Column[] => { + let columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table); + + let referenceIndex = columns.indexOf(current); + + return columns.slice(0, referenceIndex); + }, + /** + * for a given current or reference column, return the columns that + * should appear after, or to the right of that column. + * + * if a plugin class is provided, the hierarchy of column list modifications + * will be respected. + */ + after: (current: Column, requester?: Plugin): Column[] => { + let columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table); + + let referenceIndex = columns.indexOf(current); + + return columns.slice(referenceIndex + 1); + }, +}; + export const meta = { /** * @public diff --git a/ember-headless-table/src/plugins/column-reordering/plugin.ts b/ember-headless-table/src/plugins/column-reordering/plugin.ts index 66d2fb08..c10ed3dd 100644 --- a/ember-headless-table/src/plugins/column-reordering/plugin.ts +++ b/ember-headless-table/src/plugins/column-reordering/plugin.ts @@ -6,7 +6,7 @@ import { TrackedMap } from 'tracked-built-ins'; import { preferences } from '[public-plugin-types]'; -import { BasePlugin, meta } from '../-private/base'; +import { BasePlugin, columns, meta } from '../-private/base'; import type { PluginPreferences } from '[public-plugin-types]'; import type { Column, Table } from '[public-types]'; @@ -33,7 +33,6 @@ export interface Signature { export class ColumnReordering extends BasePlugin { name = 'column-reordering'; static features = ['columnOrder']; - static requires = ['columnVisibility']; meta = { column: ColumnMeta, @@ -45,6 +44,10 @@ export class ColumnReordering extends BasePlugin { tableMeta.reset(); } + + get columns() { + return meta.forTable(this.table, ColumnReordering).columns; + } } export class ColumnMeta { @@ -102,7 +105,7 @@ export class TableMeta { */ @tracked columnOrder = new ColumnOrder({ - columns: () => this.visibleColumns, + columns: () => this.availableColumns, save: this.save, existingOrder: this.read(), }); @@ -125,7 +128,7 @@ export class TableMeta { reset() { preferences.forTable(this.table, ColumnReordering).delete('order'); this.columnOrder = new ColumnOrder({ - columns: () => this.visibleColumns, + columns: () => this.availableColumns, save: this.save, }); } @@ -160,66 +163,12 @@ export class TableMeta { return this.columnOrder.orderedColumns; } - previousColumn = (referenceColumn: Column) => { - let visible = this.columns; - let referenceIndex = visible.indexOf(referenceColumn); - - assert( - `index of reference column must be >= 0. column likely not a part of the table`, - referenceIndex >= 0 - ); - - /** - * There can be nothing before the first column - */ - if (referenceIndex === 0) { - return null; - } - - return visible[referenceIndex - 1]; - }; - - nextColumn = (referenceColumn: Column) => { - let visible = this.columns; - let referenceIndex = visible.indexOf(referenceColumn); - - assert( - `index of reference column must be >= 0. column likely not a part of the table`, - referenceIndex >= 0 - ); - - /** - * There can be nothing after the last column - */ - if (referenceIndex > visible.length - 1) { - return null; - } - - return visible[referenceIndex + 1]; - }; - - columnsAfter = (referenceColumn: Column) => { - let visible = this.columns; - let referenceIndex = visible.indexOf(referenceColumn); - - return visible.slice(referenceIndex + 1); - }; - - columnsBefore = (referenceColumn: Column) => { - let visible = this.columns; - let referenceIndex = visible.indexOf(referenceColumn); - - return visible.slice(0, referenceIndex); - }; - /** * @private * This isn't our data to expose, but it is useful to alias */ - private get visibleColumns() { - let visiblility = meta.withFeature.forTable(this.table, 'columnVisibility'); - - return visiblility.visibleColumns; + private get availableColumns() { + return columns.for(this.table, ColumnReordering); } } diff --git a/ember-headless-table/src/plugins/column-resizing/plugin.ts b/ember-headless-table/src/plugins/column-resizing/plugin.ts index 675068bb..5afe2484 100644 --- a/ember-headless-table/src/plugins/column-resizing/plugin.ts +++ b/ember-headless-table/src/plugins/column-resizing/plugin.ts @@ -2,7 +2,7 @@ import { cached, tracked } from '@glimmer/tracking'; import { assert } from '@ember/debug'; import { action } from '@ember/object'; -import { BasePlugin, meta, options } from '../-private/base'; +import { BasePlugin, columns, meta, options } from '../-private/base'; import { applyStyles } from '../-private/utils'; import { getAccurateClientHeight, getAccurateClientWidth, totalGapOf } from './utils'; @@ -154,8 +154,7 @@ export class ColumnMeta { } get hasResizeHandle() { - let visiblility = meta.withFeature.forTable(this.column.table, 'columnOrder'); - let previous = visiblility.previousColumn(this.column); + let previous = columns.previous(this.column); if (!previous) return false; @@ -250,9 +249,7 @@ export class TableMeta { } get #availableColumns() { - let otherTableMeta = meta.withFeature.forTable(this.table, 'columnOrder'); - - return otherTableMeta.columns; + return columns.for(this.table, ColumnResizing); } get visibleColumnMetas() { @@ -320,12 +317,11 @@ export class TableMeta { let position = this.options?.handlePosition ?? 'left'; let growingColumn: Column | null | undefined; - let visiblility = meta.withFeature.forTable(this.table, 'columnOrder'); if (position === 'right') { - growingColumn = isDraggingRight ? visiblility.nextColumn(column) : column; + growingColumn = isDraggingRight ? columns.next(column) : column; } else { - growingColumn = isDraggingRight ? visiblility.previousColumn(column) : column; + growingColumn = isDraggingRight ? columns.previous(column) : column; } if (!growingColumn) return; @@ -335,9 +331,7 @@ export class TableMeta { assert('cannot resize a column that does not have a width', growingColumnMeta.width); let shrinkableColumns = - delta > 0 - ? visiblility.columnsAfter(growingColumn) - : visiblility.columnsBefore(growingColumn).reverse(); + delta > 0 ? columns.after(growingColumn) : columns.before(growingColumn).reverse(); let shrinkableColumnsMetas = shrinkableColumns .map((column) => meta.forColumn(column, ColumnResizing)) diff --git a/ember-headless-table/src/plugins/column-visibility/plugin.ts b/ember-headless-table/src/plugins/column-visibility/plugin.ts index 6ec1ce00..062f7649 100644 --- a/ember-headless-table/src/plugins/column-visibility/plugin.ts +++ b/ember-headless-table/src/plugins/column-visibility/plugin.ts @@ -1,5 +1,4 @@ import { cached } from '@glimmer/tracking'; -import { assert } from '@ember/debug'; import { action } from '@ember/object'; import { BasePlugin, meta, options, preferences } from '../-private/base'; @@ -65,6 +64,10 @@ export class ColumnVisibility extends BasePlugin implements Plugin { @@ -149,64 +152,5 @@ export class TableMeta { let columnMeta = meta.forColumn(column, ColumnVisibility); columnMeta.toggle(); - - // TODO: REMOVE - // TODO: remember to reset column widths in toucan-data-table - } - - @action - previousColumn(referenceColumn: Column) { - let visible = this.visibleColumns; - let referenceIndex = visible.indexOf(referenceColumn); - - assert( - `index of reference column must be >= 0. column likely not a part of the table`, - referenceIndex >= 0 - ); - - /** - * There can be nothing before the first column - */ - if (referenceIndex === 0) { - return null; - } - - return visible[referenceIndex - 1]; - } - - @action - nextColumn(referenceColumn: Column) { - let visible = this.visibleColumns; - let referenceIndex = visible.indexOf(referenceColumn); - - assert( - `index of reference column must be >= 0. column likely not a part of the table`, - referenceIndex >= 0 - ); - - /** - * There can be nothing after the last column - */ - if (referenceIndex > visible.length - 1) { - return null; - } - - return visible[referenceIndex + 1]; - } - - @action - columnsAfter(referenceColumn: Column) { - let visible = this.visibleColumns; - let referenceIndex = visible.indexOf(referenceColumn); - - return visible.slice(referenceIndex + 1); - } - - @action - columnsBefore(referenceColumn: Column) { - let visible = this.visibleColumns; - let referenceIndex = visible.indexOf(referenceColumn); - - return visible.slice(0, referenceIndex); } } diff --git a/ember-headless-table/src/plugins/index.ts b/ember-headless-table/src/plugins/index.ts index 2eb29ac1..5e984a47 100644 --- a/ember-headless-table/src/plugins/index.ts +++ b/ember-headless-table/src/plugins/index.ts @@ -1,5 +1,5 @@ // Public API -export { BasePlugin, meta, options, preferences } from './-private/base'; +export { BasePlugin, columns, meta, options, preferences } from './-private/base'; export { applyStyles, removeStyles } from './-private/utils'; // Public Types diff --git a/ember-headless-table/src/plugins/sticky-columns/plugin.ts b/ember-headless-table/src/plugins/sticky-columns/plugin.ts index f8f1071e..3d77fadd 100644 --- a/ember-headless-table/src/plugins/sticky-columns/plugin.ts +++ b/ember-headless-table/src/plugins/sticky-columns/plugin.ts @@ -1,7 +1,7 @@ import { cached } from '@glimmer/tracking'; import { assert } from '@ember/debug'; -import { BasePlugin, meta, options } from '../-private/base'; +import { BasePlugin, columns, meta, options } from '../-private/base'; import { applyStyles } from '../-private/utils'; import type { ColumnApi } from '[public-plugin-types]'; @@ -37,7 +37,7 @@ export class StickyColumns extends BasePlugin { * Other width-management plugins can be used instead of ColumnResizing, but they must declare * that they manage the width of the columns. */ - static requires = ['columnWidth', 'columnVisibility']; + static requires = ['columnWidth']; meta = { table: TableMeta, @@ -98,10 +98,8 @@ export class ColumnMeta { return; } - let visiblility = meta.withFeature.forTable(this.column.table, 'columnVisibility'); - if (this.position === 'left') { - let leftColumns = visiblility.columnsBefore(this.column); + let leftColumns = columns.before(this.column); let left = leftColumns.reduce((acc, column) => { let columnMeta = meta.withFeature.forColumn(column, 'columnWidth'); @@ -116,7 +114,7 @@ export class ColumnMeta { } if (this.position === 'right') { - let rightColumns = visiblility.columnsAfter(this.column); + let rightColumns = columns.after(this.column); let right = rightColumns.reduce((acc, column) => { let columnMeta = meta.withFeature.forColumn(column, 'columnWidth'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 424aa2b0..122a8fd1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -369,6 +369,7 @@ importers: '@types/ember__engine': ^4.0.2 '@types/ember__error': ^4.0.0 '@types/ember__object': ^4.0.4 + '@types/ember__owner': ^4.0.0 '@types/ember__polyfills': ^4.0.0 '@types/ember__routing': ^4.0.10 '@types/ember__runloop': ^4.0.1 @@ -454,6 +455,7 @@ importers: '@types/ember__engine': 4.0.2_@babel+core@7.19.3 '@types/ember__error': 4.0.0 '@types/ember__object': 4.0.4_@babel+core@7.19.3 + '@types/ember__owner': 4.0.0 '@types/ember__polyfills': 4.0.0 '@types/ember__routing': 4.0.11_@babel+core@7.19.3 '@types/ember__runloop': 4.0.1_@babel+core@7.19.3 @@ -2308,7 +2310,7 @@ packages: '@babel/core': 7.19.6 '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.19.6 '@babel/preset-env': 7.19.3_@babel+core@7.19.6 - '@babel/traverse': 7.19.3 + '@babel/traverse': 7.19.4 '@embroider/core': 1.9.0 '@embroider/macros': 1.9.0 '@embroider/shared-internals': 1.8.3 @@ -2498,7 +2500,7 @@ packages: '@embroider/macros': 1.8.3 broccoli-funnel: 3.0.8 ember-cli-babel: 7.26.11 - ember-source: 4.7.0_6zc4kxld457avlfyhj3lzsljlm + ember-source: 4.7.0_@babel+core@7.19.3 transitivePeerDependencies: - supports-color @@ -3438,6 +3440,11 @@ packages: - supports-color dev: true + /@types/ember-resolver/5.0.11: + resolution: {integrity: sha512-2BL9d8kBdNUO9Je6KBF7Q34BSwbQG6vzCzTeSopt8FAmLDfaDU9xDDdyZobpfy9GR36mCSeG9b9wr4bgYh/MYw==} + dependencies: + '@types/ember__object': 4.0.4 + /@types/ember-resolver/5.0.11_@babel+core@7.19.3: resolution: {integrity: sha512-2BL9d8kBdNUO9Je6KBF7Q34BSwbQG6vzCzTeSopt8FAmLDfaDU9xDDdyZobpfy9GR36mCSeG9b9wr4bgYh/MYw==} dependencies: @@ -3601,7 +3608,7 @@ packages: /@types/ember__debug/4.0.1_@babel+core@7.19.6: resolution: {integrity: sha512-qrKk6Ujh6oev7TSB0eB7AEmQWKCt5t84k/K3hDvJXUiLU3YueN0kyt7aPoIAkVjC111A9FqDugl9n60+N5yeEw==} dependencies: - '@types/ember-resolver': 5.0.11_@babel+core@7.19.6 + '@types/ember-resolver': 5.0.11 '@types/ember__object': 4.0.4_@babel+core@7.19.6 transitivePeerDependencies: - '@babel/core' @@ -3624,7 +3631,7 @@ packages: /@types/ember__engine/4.0.2_@babel+core@7.19.6: resolution: {integrity: sha512-x9c8LtRpYwQnyUiUbGpF2+zrZiA0G3e0lPsprghllWEabnIyvN+GMdtnvt4DmpGQVeUz6JKVdoPAmENBUTTcyg==} dependencies: - '@types/ember-resolver': 5.0.11_@babel+core@7.19.6 + '@types/ember-resolver': 5.0.11 '@types/ember__object': 4.0.4_@babel+core@7.19.6 '@types/ember__owner': 4.0.0 transitivePeerDependencies: @@ -3843,7 +3850,7 @@ packages: /@types/glob/8.0.0: resolution: {integrity: sha512-l6NQsDDyQUVeoTynNpC9uRvCUint/gSUXQA2euwmTuWGvPY5LSDUu6tkCtJB2SvGQlJQzLaKqcGZP4//7EDveA==} dependencies: - '@types/minimatch': 3.0.5 + '@types/minimatch': 5.1.2 '@types/node': 18.8.2 /@types/htmlbars-inline-precompile/3.0.0: @@ -3876,7 +3883,6 @@ packages: /@types/minimatch/5.1.2: resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - dev: true /@types/minimist/1.2.2: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} @@ -4535,7 +4541,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.1.4 - es-abstract: 1.20.3 + es-abstract: 1.20.4 get-intrinsic: 1.1.3 is-string: 1.0.7 dev: true @@ -4566,7 +4572,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.1.4 - es-abstract: 1.20.3 + es-abstract: 1.20.4 es-shim-unscopables: 1.0.0 dev: true @@ -6032,7 +6038,7 @@ packages: resolution: {integrity: sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==} hasBin: true dependencies: - caniuse-lite: 1.0.30001416 + caniuse-lite: 1.0.30001420 electron-to-chromium: 1.4.273 dev: true @@ -6165,7 +6171,7 @@ packages: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: browserslist: 4.21.4 - caniuse-lite: 1.0.30001416 + caniuse-lite: 1.0.30001420 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 dev: true @@ -7437,7 +7443,7 @@ packages: semver: 6.3.0 /ee-first/1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} dev: true /electron-to-chromium/1.4.273: @@ -8898,35 +8904,6 @@ packages: string-template: 0.2.1 dev: true - /es-abstract/1.20.3: - resolution: {integrity: sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - es-to-primitive: 1.2.1 - function-bind: 1.1.1 - function.prototype.name: 1.1.5 - get-intrinsic: 1.1.3 - get-symbol-description: 1.0.0 - has: 1.0.3 - has-property-descriptors: 1.0.0 - has-symbols: 1.0.3 - internal-slot: 1.0.3 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-weakref: 1.0.2 - object-inspect: 1.12.2 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.4.3 - safe-regex-test: 1.0.0 - string.prototype.trimend: 1.0.5 - string.prototype.trimstart: 1.0.5 - unbox-primitive: 1.0.2 - /es-abstract/1.20.4: resolution: {integrity: sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==} engines: {node: '>= 0.4'} @@ -9136,7 +9113,7 @@ packages: eslint-import-resolver-node: 0.3.6 eslint-module-utils: 2.7.4_f5mbxhdngza3k4m45us5fj2pj4 has: 1.0.3 - is-core-module: 2.10.0 + is-core-module: 2.11.0 is-glob: 4.0.3 minimatch: 3.1.2 object.values: 1.1.5 @@ -10049,7 +10026,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.1.4 - es-abstract: 1.20.3 + es-abstract: 1.20.4 functions-have-names: 1.2.3 /functional-red-black-tree/1.0.1: @@ -11133,7 +11110,7 @@ packages: dependencies: available-typed-arrays: 1.0.5 call-bind: 1.0.2 - es-abstract: 1.20.3 + es-abstract: 1.20.4 for-each: 0.3.3 has-tostringtag: 1.0.0 dev: true @@ -12368,7 +12345,7 @@ packages: dev: true /mute-stream/0.0.7: - resolution: {integrity: sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==} + resolution: {integrity: sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=} dev: true /mute-stream/0.0.8: @@ -12716,7 +12693,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.1.4 - es-abstract: 1.20.3 + es-abstract: 1.20.4 dev: true /on-finished/2.3.0: @@ -14988,14 +14965,14 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.1.4 - es-abstract: 1.20.3 + es-abstract: 1.20.4 /string.prototype.trimstart/1.0.5: resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==} dependencies: call-bind: 1.0.2 define-properties: 1.1.4 - es-abstract: 1.20.3 + es-abstract: 1.20.4 /string_decoder/0.10.31: resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} @@ -16397,7 +16374,7 @@ packages: dependencies: available-typed-arrays: 1.0.5 call-bind: 1.0.2 - es-abstract: 1.20.3 + es-abstract: 1.20.4 for-each: 0.3.3 has-tostringtag: 1.0.0 is-typed-array: 1.1.9 diff --git a/test-app/package.json b/test-app/package.json index 0cba6106..25f4ba80 100644 --- a/test-app/package.json +++ b/test-app/package.json @@ -62,6 +62,7 @@ "@types/ember__engine": "^4.0.2", "@types/ember__error": "^4.0.0", "@types/ember__object": "^4.0.4", + "@types/ember__owner": "^4.0.0", "@types/ember__polyfills": "^4.0.0", "@types/ember__routing": "^4.0.10", "@types/ember__runloop": "^4.0.1", diff --git a/test-app/tests/plugins/column-reordering/rendering-test.gts b/test-app/tests/plugins/column-reordering/rendering-test.gts index c09a0f0f..60c667e2 100644 --- a/test-app/tests/plugins/column-reordering/rendering-test.gts +++ b/test-app/tests/plugins/column-reordering/rendering-test.gts @@ -11,9 +11,9 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { headlessTable } from 'ember-headless-table'; -import { meta } from 'ember-headless-table/plugins'; -import { ColumnReordering } from 'ember-headless-table/plugins/column-reordering'; -import { ColumnVisibility } from 'ember-headless-table/plugins/column-visibility'; +import { meta, columns } from 'ember-headless-table/plugins'; +import { ColumnReordering, moveLeft, moveRight } from 'ember-headless-table/plugins/column-reordering'; +import { ColumnVisibility, hide, show } from 'ember-headless-table/plugins/column-visibility'; import { DATA } from 'test-app/data'; import type { Column, PreferencesData } from 'ember-headless-table'; @@ -52,31 +52,6 @@ module('Plugins | columnReordering', function (hooks) { return this.args.ctx.table; } - get columns() { - /** - * TODO: this is a dirty cast, and should be removed. - * we should be able to infer the specific Column type since the table - * is specifically typed. - */ - return meta.forTable(this.table, ColumnReordering).columns as Column[]; - } - - moveLeft = (column: Column) => { - return meta.forColumn(column, ColumnReordering).moveLeft(); - }; - - moveRight = (column: Column) => { - return meta.forColumn(column, ColumnReordering).moveRight(); - }; - - hide = (column: Column) => { - return meta.forColumn(column, ColumnVisibility).hide(); - }; - - show = (column: Column) => { - return meta.forColumn(column, ColumnVisibility).show(); - }; - } - module('with unmet requirements', function () { + module('as a solo plugin', function (hooks) { class DefaultOptions extends Context { table = headlessTable(this, { columns: () => this.columns, @@ -149,17 +124,91 @@ module('Plugins | columnReordering', function (hooks) { }); } - test('cannot create a table', async function (assert) { - assert.throws( - () => { - ctx = new DefaultOptions(); - // plugins are lazily instantiated - ctx.table.plugins; - }, - /Configuration is missing requirement: columnVisibility, And is requested by ColumnReordering. Please add a plugin with the columnVisibility feature/, - 'Error was thrown about missing a plugin that provides "column visibility features' + hooks.beforeEach(async function () { + ctx = new DefaultOptions(); + setOwner(ctx, this.owner); + + await render( + + ); + }); + + test('everything is visible and in the original order', async function (assert) { + assert.dom('th').exists({ count: 4 }); + assert.dom(`th.A`).exists(); + assert.dom(`th.B`).exists(); + assert.dom(`th.C`).exists(); + assert.dom(`th.D`).exists(); + assert.dom('thead tr').containsText('A'); + assert.dom('thead tr').containsText('B'); + assert.dom('thead tr').containsText('C'); + assert.dom('thead tr').containsText('D'); + assert.strictEqual( + getColumnOrder(), + 'A B C D', + 'Initial order' ); }); + + test('a column in the middle can be moved to the left', async function (assert) { + assert.strictEqual(getColumnOrder(), 'A B C D'); + + await click('th.B .left'); + + assert.strictEqual(getColumnOrder(), 'B A C D'); + }); + + test('a column in the middle can be moved to the right', async function (assert) { + assert.strictEqual(getColumnOrder(), 'A B C D'); + + await click('th.B .right'); + + assert.strictEqual(getColumnOrder(), 'A C B D'); + }); }); module('with no options specified', function (hooks) { diff --git a/test-app/tests/plugins/column-resizing/rendering-test.gts b/test-app/tests/plugins/column-resizing/rendering-test.gts index 6cd188cf..9d592de6 100644 --- a/test-app/tests/plugins/column-resizing/rendering-test.gts +++ b/test-app/tests/plugins/column-resizing/rendering-test.gts @@ -272,7 +272,7 @@ module('Plugins | resizing', function (hooks) { table = headlessTable(this, { columns: () => this.columns, data: () => [] as unknown[], - plugins: [ColumnResizing, ColumnReordering, ColumnVisibility], + plugins: [ColumnResizing], }); } diff --git a/test-app/tests/plugins/column-visibility/rendering-test.gts b/test-app/tests/plugins/column-visibility/rendering-test.gts index 73527906..eab204ad 100644 --- a/test-app/tests/plugins/column-visibility/rendering-test.gts +++ b/test-app/tests/plugins/column-visibility/rendering-test.gts @@ -8,8 +8,8 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { headlessTable } from 'ember-headless-table'; -import { meta } from 'ember-headless-table/plugins'; -import { ColumnVisibility } from 'ember-headless-table/plugins/column-visibility'; +import { meta, columns } from 'ember-headless-table/plugins'; +import { ColumnVisibility, hide, show } from 'ember-headless-table/plugins/column-visibility'; import { DATA } from 'test-app/data'; import type { ColumnConfig, Column } from 'ember-headless-table'; @@ -40,23 +40,6 @@ module('Plugins | columnVisibility', function (hooks) { return this.args.ctx.table; } - get columns() { - let tableMeta = meta.forTable(this.table, ColumnVisibility) - /** - * TODO: this cast is a dirty one, and should be removed. - * inference "should" be able to work here. - */ - return tableMeta.visibleColumns as Column[]; - } - - hide = (column: Column) => { - return meta.forColumn(column, ColumnVisibility).hide(); - }; - - show = (column: Column) => { - return meta.forColumn(column, ColumnVisibility).show(); - }; -