From c06194798cbbe0b3ac065c4702dbfeff9a374559 Mon Sep 17 00:00:00 2001 From: johanrd Date: Fri, 3 Oct 2025 14:00:57 +0200 Subject: [PATCH 1/9] Add generic type parameters for improved type safety Introduces OptionsType and CellArgs as generic parameters across the table system to enable type-safe Cell component signatures and custom options in Glint. - Add OptionsType and CellArgs generics to Table, Column, and related classes - Split CellContext into CellConfigContext (optional fields) and CellContext (required fields) - Export CellContext as part of the public API - Update all internal usages to support the new generic parameters --- .../1-get-started/typescript-and-glint.md | 78 +++++++++++++++++++ table/src/-private/column.ts | 13 ++-- table/src/-private/interfaces/column.ts | 21 +++-- table/src/-private/interfaces/table.ts | 6 +- table/src/-private/js-helper.ts | 22 +++--- table/src/-private/table.ts | 19 ++--- table/src/index.ts | 1 + table/src/plugins/-private/base.ts | 36 ++++----- 8 files changed, 143 insertions(+), 53 deletions(-) diff --git a/docs-app/public/docs/1-get-started/typescript-and-glint.md b/docs-app/public/docs/1-get-started/typescript-and-glint.md index 667c4045..75e19129 100644 --- a/docs-app/public/docs/1-get-started/typescript-and-glint.md +++ b/docs-app/public/docs/1-get-started/typescript-and-glint.md @@ -71,6 +71,84 @@ class Demo { } ``` +## Type-Safe Cell Components and Options + +The table library supports generic type parameters for strongly-typed Cell components and custom options. This enables full type inference in Glint templates and prevents common errors. + +### Basic Usage + +By default, all generic parameters are set to `any` for backward compatibility: + +```ts +const table = headlessTable(this, { + columns: () => [...], + data: () => myData +}); +``` + +### Advanced: Type-Safe Options + +To get type safety for custom options passed to cells: + +```ts +import { headlessTable, type ColumnConfig, type CellContext } from '@universal-ember/table'; + +interface MyData { + id: string; + name: string; +} + +interface MyOptions { + highlightColor?: string; + showBadge?: boolean; +} + +interface MyCellArgs extends CellContext { + // Cell components receive data, column, row, and options +} + +const table = headlessTable(this, { + columns: () => [ + { + key: 'name', + name: 'Name', + Cell: MyCustomCell, // fully typed! + options: ({ row }) => ({ + highlightColor: row.data.id === 'special' ? 'blue' : 'gray', + showBadge: true, + }), + }, + ], + data: () => myData, +}); +``` + +Your Cell component will now have full type inference: + +```ts +import Component from '@glimmer/component'; + +class MyCustomCell extends Component { + get color() { + return this.args.options?.highlightColor ?? 'gray'; + } + + +} +``` + +### CellContext Types + +The library provides two context types: + +- **`CellConfigContext`**: Used when defining column configurations. Has optional fields (`column?`, `row?`, `options?`) for user convenience. +- **`CellContext`**: Used for Cell component signatures. Has required fields since they're always provided at runtime. + ## In Templates [Glint][docs-glint] can be a great choice to help ensure that your code is as bug-free as possible. diff --git a/table/src/-private/column.ts b/table/src/-private/column.ts index 0025cfad..30c10294 100644 --- a/table/src/-private/column.ts +++ b/table/src/-private/column.ts @@ -4,7 +4,7 @@ import { isEmpty } from '@ember/utils'; import type { Row } from './row'; import type { Table } from './table'; import type { ContentValue } from '@glint/template'; -import type { ColumnConfig } from './interfaces'; +import type { ColumnConfig, CellOptions, CellConfigContext } from './interfaces'; const DEFAULT_VALUE = '--'; const DEFAULT_VALUE_KEY = 'defaultValue'; @@ -12,7 +12,7 @@ const DEFAULT_OPTIONS = { [DEFAULT_VALUE_KEY]: DEFAULT_VALUE, }; -export class Column { +export class Column { get Cell() { return this.config.Cell; } @@ -27,7 +27,7 @@ export class Column { constructor( public table: Table, - public config: ColumnConfig, + public config: ColumnConfig, ) {} @action @@ -52,16 +52,17 @@ export class Column { } private getDefaultValue(row: Row) { - return this.getOptionsForRow(row)[DEFAULT_VALUE_KEY]; + const options = this.getOptionsForRow(row) as any; + return options[DEFAULT_VALUE_KEY]; } @action - getOptionsForRow(row: Row) { + getOptionsForRow(row?: Row): OptionsType & CellOptions { const defaults = DEFAULT_OPTIONS; return { ...defaults, ...this.config.options?.({ column: this, row }), - }; + } as OptionsType & CellOptions; } } diff --git a/table/src/-private/interfaces/column.ts b/table/src/-private/interfaces/column.ts index 7b893d0a..e863d9f3 100644 --- a/table/src/-private/interfaces/column.ts +++ b/table/src/-private/interfaces/column.ts @@ -5,9 +5,18 @@ import type { ColumnOptionsFor, SignatureFrom } from './plugins'; import type { Constructor } from '../private-types'; import type { ComponentLike, ContentValue } from '@glint/template'; -export interface CellContext { - column: Column; +// Configuration context (for defining column options) - optional fields for user convenience +export interface CellConfigContext { + column?: Column; + row?: Row; + options?: OptionsType & CellOptions; +} + +// Runtime context (for Cell components) - required fields since they're always provided +export interface CellContext { + column: Column; row: Row; + options?: OptionsType & CellOptions; } type ColumnPluginOption

= P extends BasePlugin @@ -21,7 +30,7 @@ export type CellOptions = { defaultValue?: string; } & Record; -export interface ColumnConfig { +export interface ColumnConfig { /** * the `key` is required for preferences storage, as well as * managing uniqueness of the columns in an easy-to-understand way. @@ -37,14 +46,14 @@ export interface ColumnConfig { /** * Optionally provide a function to determine the value of a row at this column */ - value?: (context: CellContext) => ContentValue; + value?: (context: CellConfigContext) => ContentValue; /** * Recommended property to use for custom components for each cell per column. * Out-of-the-box, this property isn't used, but the provided type may be * a convenience for consumers of the headless table */ - Cell?: ComponentLike>; + Cell?: ComponentLike; /** * The name or title of the column, shown in the column heading / th @@ -54,7 +63,7 @@ export interface ColumnConfig { /** * Bag of extra properties to pass to Cell via `@options`, if desired */ - options?: (context: CellContext) => CellOptions; + options?: (context: CellConfigContext) => OptionsType; /** * Each plugin may provide column options, and provides similar syntax to how diff --git a/table/src/-private/interfaces/table.ts b/table/src/-private/interfaces/table.ts index 5e64859c..3c94b5f4 100644 --- a/table/src/-private/interfaces/table.ts +++ b/table/src/-private/interfaces/table.ts @@ -1,5 +1,5 @@ import type { Plugins } from '../../plugins/-private/utils'; -import type { ColumnConfig } from './column'; +import type { ColumnConfig, CellOptions, CellContext } from './column'; import type { Pagination } from './pagination'; import type { PreferencesAdapter } from './preferences'; import type { Selection } from './selection'; @@ -9,13 +9,13 @@ export interface TableMeta { totalRowsSelectedCount?: number; } -export interface TableConfig { +export interface TableConfig { /** * Configuration describing how the table will crawl through `data` * and render it. Within this `columns` config, there will also be opportunities * to set the behavior of columns when rendered */ - columns: () => ColumnConfig[]; + columns: () => ColumnConfig[]; /** * The data to render, as described via the `columns` option. * diff --git a/table/src/-private/js-helper.ts b/table/src/-private/js-helper.ts index ebc3de96..421d2cc7 100644 --- a/table/src/-private/js-helper.ts +++ b/table/src/-private/js-helper.ts @@ -1,10 +1,10 @@ import { Table } from './table.ts'; -import type { TableConfig } from './interfaces'; +import type { TableConfig, CellContext } from './interfaces'; -type Args = - | [destroyable: object, options: TableConfig] - | [options: TableConfig]; +type Args = + | [destroyable: object, options: TableConfig] + | [options: TableConfig]; /** * Represents a UI-less version of a table @@ -23,7 +23,7 @@ type Args = * } * ``` */ -export function headlessTable(options: TableConfig): Table; +export function headlessTable(options: TableConfig): Table; /** * Represents a UI-less version of a table @@ -42,12 +42,12 @@ export function headlessTable(options: TableConfig): Table; * ``` * */ -export function headlessTable( +export function headlessTable( destroyable: object, - options: TableConfig, -): Table; + options: TableConfig, +): Table; -export function headlessTable(...args: Args): Table { +export function headlessTable(...args: Args): Table { if (args.length === 2) { const [destroyable, options] = args; @@ -56,10 +56,10 @@ export function headlessTable(...args: Args): Table { * otherwise individual-property reactivity can be managed on a per-property * "thunk"-basis */ - return Table.from>(destroyable, () => options); + return Table.from>(destroyable, () => options); } const [options] = args; - return Table.from>(() => options); + return Table.from>(() => options); } diff --git a/table/src/-private/table.ts b/table/src/-private/table.ts index 34e6205d..e59afdd2 100644 --- a/table/src/-private/table.ts +++ b/table/src/-private/table.ts @@ -20,6 +20,7 @@ import { composeFunctionModifiers } from './utils.ts'; import type { BasePlugin, Plugin } from '../plugins/index.ts'; import type { Class } from './private-types.ts'; import type { Destructor, TableConfig } from './interfaces'; +import type { CellOptions, CellContext } from './interfaces/column.ts'; import { compatOwner } from './ember-compat.ts'; const getOwner = compatOwner.getOwner; @@ -30,8 +31,8 @@ const DEFAULT_COLUMN_CONFIG = { minWidth: 128, }; -interface Signature { - Named: TableConfig; +interface Signature { + Named: TableConfig; } /** @@ -54,7 +55,7 @@ const attachContainer = (element: Element, table: Table) => { table.scrollContainerElement = element; }; -export class Table extends Resource> { +export class Table extends Resource> { /** * @private */ @@ -66,11 +67,11 @@ export class Table extends Resource> { /** * @private */ - [COLUMN_META_KEY] = new WeakMap, any>>(); + [COLUMN_META_KEY] = new WeakMap, Map, any>>(); /** * @private */ - [ROW_META_KEY] = new WeakMap, any>>(); + [ROW_META_KEY] = new WeakMap, Map, any>>(); /** * @private @@ -110,7 +111,7 @@ export class Table extends Resource> { /** * @private */ - modify(_: [] | undefined, named: Signature['Named']) { + modify(_: [] | undefined, named: Signature['Named']) { this.args = { named }; // only set the preferences once @@ -157,7 +158,7 @@ export class Table extends Resource> { // With curried+composed modifiers, only the plugin's headerModifier // that has tracked changes would run, leaving the other modifiers alone columnHeader: modifier( - (element: HTMLElement, [column]: [Column]): Destructor => { + (element: HTMLElement, [column]: [Column]): Destructor => { const modifiers = this.plugins.map( (plugin) => plugin.headerCellModifier, ); @@ -251,7 +252,7 @@ export class Table extends Resource> { return dataFn() ?? []; }, - map: (datum) => new Row(this, datum), + map: (datum) => new Row(this, datum), }); columns = map(this, { @@ -286,7 +287,7 @@ export class Table extends Resource> { return result; }, map: (config) => { - return new Column(this, { + return new Column(this, { ...DEFAULT_COLUMN_CONFIG, ...config, }); diff --git a/table/src/index.ts b/table/src/index.ts index e4182017..7c5ba470 100644 --- a/table/src/index.ts +++ b/table/src/index.ts @@ -12,6 +12,7 @@ export { deserializeSorts, serializeSorts } from './utils.ts'; *******************************/ export type { Column } from './-private/column.ts'; export type { + CellContext, ColumnConfig, ColumnKey, Pagination, diff --git a/table/src/plugins/-private/base.ts b/table/src/plugins/-private/base.ts index 8d673a63..1765c787 100644 --- a/table/src/plugins/-private/base.ts +++ b/table/src/plugins/-private/base.ts @@ -296,10 +296,10 @@ export const preferences = { * This works recursively up the plugin tree up until a plugin has no requirements, and then * all columns from the table are returned. */ -function columnsFor( - table: Table, +function columnsFor( + table: Table, requester?: Plugin, -): Column[] { +): Column[] { assert( `First argument passed to columns.for must be an instance of Table`, table[TABLE_KEY], @@ -417,10 +417,10 @@ export const columns = { * If a plugin class is provided, the hierarchy of column list modifications * will be respected. */ - next: ( - current: Column, + next: ( + current: Column, requester?: Plugin, - ): Column | undefined => { + ): Column | undefined => { const columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table); @@ -449,10 +449,10 @@ export const columns = { * If a plugin class is provided, the hierarchy of column list modifications * will be respected. */ - previous: ( - current: Column, + previous: ( + current: Column, requester?: Plugin, - ): Column | undefined => { + ): Column | undefined => { const columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table); @@ -479,10 +479,10 @@ export const columns = { * if a plugin class is provided, the hierarchy of column list modifications * will be respected. */ - before: ( - current: Column, + before: ( + current: Column, requester?: Plugin, - ): Column[] => { + ): Column[] => { const columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table); @@ -498,10 +498,10 @@ export const columns = { * if a plugin class is provided, the hierarchy of column list modifications * will be respected. */ - after: ( - current: Column, + after: ( + current: Column, requester?: Plugin, - ): Column[] => { + ): Column[] => { const columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table); @@ -767,7 +767,7 @@ function getPluginInstance( factory: () => Instance, ): Instance; function getPluginInstance | Row, Instance>( - map: WeakMap, Instance>>, + map: WeakMap | Row, Map, Instance>>, rootKey: RootKey, mapKey: Class, factory: () => Instance, @@ -776,13 +776,13 @@ function getPluginInstance | Row, Instance>( ...args: | [FactoryMap, Class, () => Instance] | [ - WeakMap>, + WeakMap | Row, FactoryMap>, RootKey, Class, () => Instance, ] ): Instance { - let map: WeakMap> | FactoryMap; + let map: WeakMap | Row, FactoryMap> | FactoryMap; let mapKey: Class; let rootKey: RootKey | undefined; let factory: () => Instance; From cd4816bcda9633e9c15659759b67f882c4a16723 Mon Sep 17 00:00:00 2001 From: johanrd Date: Thu, 6 Nov 2025 13:35:05 +0100 Subject: [PATCH 2/9] Add Generic DataType arg to columnOrder --- table/src/-private/column.ts | 6 +++++- table/src/-private/js-helper.ts | 13 +++++++++--- table/src/-private/table.ts | 21 +++++++++++++++---- table/src/plugins/-private/base.ts | 4 +++- .../src/plugins/column-reordering/helpers.ts | 2 +- table/src/plugins/column-reordering/plugin.ts | 12 ++++++----- 6 files changed, 43 insertions(+), 15 deletions(-) diff --git a/table/src/-private/column.ts b/table/src/-private/column.ts index 10f89c4a..7b5cefc6 100644 --- a/table/src/-private/column.ts +++ b/table/src/-private/column.ts @@ -4,7 +4,11 @@ import { isEmpty } from '@ember/utils'; import type { Row } from './row'; import type { Table } from './table'; import type { ContentValue } from '@glint/template'; -import type { ColumnConfig, CellOptions, CellConfigContext } from './interfaces'; +import type { + ColumnConfig, + CellOptions, + CellConfigContext, +} from './interfaces'; const DEFAULT_VALUE = '--'; const DEFAULT_VALUE_KEY = 'defaultValue'; diff --git a/table/src/-private/js-helper.ts b/table/src/-private/js-helper.ts index 421d2cc7..5a2de28c 100644 --- a/table/src/-private/js-helper.ts +++ b/table/src/-private/js-helper.ts @@ -23,7 +23,9 @@ type Args = * } * ``` */ -export function headlessTable(options: TableConfig): Table; +export function headlessTable( + options: TableConfig, +): Table; /** * Represents a UI-less version of a table @@ -47,7 +49,9 @@ export function headlessTable( options: TableConfig, ): Table; -export function headlessTable(...args: Args): Table { +export function headlessTable( + ...args: Args +): Table { if (args.length === 2) { const [destroyable, options] = args; @@ -56,7 +60,10 @@ export function headlessTable(.. * otherwise individual-property reactivity can be managed on a per-property * "thunk"-basis */ - return Table.from>(destroyable, () => options); + return Table.from>( + destroyable, + () => options, + ); } const [options] = args; diff --git a/table/src/-private/table.ts b/table/src/-private/table.ts index e59afdd2..d0f9215b 100644 --- a/table/src/-private/table.ts +++ b/table/src/-private/table.ts @@ -55,7 +55,11 @@ const attachContainer = (element: Element, table: Table) => { table.scrollContainerElement = element; }; -export class Table extends Resource> { +export class Table< + DataType = unknown, + OptionsType = any, + CellArgs = any, +> extends Resource> { /** * @private */ @@ -67,7 +71,10 @@ export class Table extend /** * @private */ - [COLUMN_META_KEY] = new WeakMap, Map, any>>(); + [COLUMN_META_KEY] = new WeakMap< + Column, + Map, any> + >(); /** * @private */ @@ -111,7 +118,10 @@ export class Table extend /** * @private */ - modify(_: [] | undefined, named: Signature['Named']) { + modify( + _: [] | undefined, + named: Signature['Named'], + ) { this.args = { named }; // only set the preferences once @@ -158,7 +168,10 @@ export class Table extend // With curried+composed modifiers, only the plugin's headerModifier // that has tracked changes would run, leaving the other modifiers alone columnHeader: modifier( - (element: HTMLElement, [column]: [Column]): Destructor => { + ( + element: HTMLElement, + [column]: [Column], + ): Destructor => { const modifiers = this.plugins.map( (plugin) => plugin.headerCellModifier, ); diff --git a/table/src/plugins/-private/base.ts b/table/src/plugins/-private/base.ts index 1765c787..73e6f258 100644 --- a/table/src/plugins/-private/base.ts +++ b/table/src/plugins/-private/base.ts @@ -782,7 +782,9 @@ function getPluginInstance | Row, Instance>( () => Instance, ] ): Instance { - let map: WeakMap | Row, FactoryMap> | FactoryMap; + let map: + | WeakMap | Row, FactoryMap> + | FactoryMap; let mapKey: Class; let rootKey: RootKey | undefined; let factory: () => Instance; diff --git a/table/src/plugins/column-reordering/helpers.ts b/table/src/plugins/column-reordering/helpers.ts index 1d5990c7..5673f3c0 100644 --- a/table/src/plugins/column-reordering/helpers.ts +++ b/table/src/plugins/column-reordering/helpers.ts @@ -23,7 +23,7 @@ export const moveRight = (column: Column) => */ export const setColumnOrder = ( table: Table, - order: ColumnOrder, + order: ColumnOrder, ) => meta.forTable(table, ColumnReordering).setOrder(order); /** diff --git a/table/src/plugins/column-reordering/plugin.ts b/table/src/plugins/column-reordering/plugin.ts index 7a72fcba..3462eda6 100644 --- a/table/src/plugins/column-reordering/plugin.ts +++ b/table/src/plugins/column-reordering/plugin.ts @@ -217,7 +217,7 @@ export class TableMeta { * @private * Used for keeping track of and updating column order */ -export class ColumnOrder { +export class ColumnOrder { /** * This map will be empty until we re-order something. */ @@ -237,7 +237,7 @@ export class ColumnOrder { * - Provide `visibleColumns` to indicate which are visible * - Hidden columns maintain their position when toggled */ - columns: () => Column[]; + columns: () => Column[]; /** * Optional: Record of which columns are currently visible. * When provided, moveLeft/moveRight will skip over hidden columns. @@ -496,18 +496,20 @@ export class ColumnOrder { } @cached - get orderedColumns(): Column[] { + get orderedColumns(): Column[] { const allColumns = this.args.columns(); const columnsByKey = allColumns.reduce( (keyMap, column) => { keyMap[column.key] = column; return keyMap; }, - {} as Record, + {} as Record>, ); const mergedOrder = orderOf(allColumns, this.map); - const result: Column[] = Array.from({ length: allColumns.length }); + const result: Column[] = Array.from({ + length: allColumns.length, + }); for (const [key, position] of mergedOrder.entries()) { const column = columnsByKey[key]; From 46a70c74bc417b7a30487313cd6e5443f4f73ea4 Mon Sep 17 00:00:00 2001 From: johanrd Date: Thu, 6 Nov 2025 13:41:21 +0100 Subject: [PATCH 3/9] lint --- .../docs/1-get-started/typescript-and-glint.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs-app/public/docs/1-get-started/typescript-and-glint.md b/docs-app/public/docs/1-get-started/typescript-and-glint.md index 75e19129..98272e19 100644 --- a/docs-app/public/docs/1-get-started/typescript-and-glint.md +++ b/docs-app/public/docs/1-get-started/typescript-and-glint.md @@ -91,7 +91,11 @@ const table = headlessTable(this, { To get type safety for custom options passed to cells: ```ts -import { headlessTable, type ColumnConfig, type CellContext } from '@universal-ember/table'; +import { + headlessTable, + type ColumnConfig, + type CellContext, +} from "@universal-ember/table"; interface MyData { id: string; @@ -110,11 +114,11 @@ interface MyCellArgs extends CellContext { const table = headlessTable(this, { columns: () => [ { - key: 'name', - name: 'Name', + key: "name", + name: "Name", Cell: MyCustomCell, // fully typed! options: ({ row }) => ({ - highlightColor: row.data.id === 'special' ? 'blue' : 'gray', + highlightColor: row.data.id === "special" ? "blue" : "gray", showBadge: true, }), }, From dbea52c848bb6bf1dd4ac726a2a0b3a7780f2509 Mon Sep 17 00:00:00 2001 From: johanrd Date: Thu, 6 Nov 2025 14:11:01 +0100 Subject: [PATCH 4/9] correctly pass through in column-resizing and have = unknown as fallback in CellConfigContext --- table/src/-private/interfaces/column.ts | 6 +++--- test-app/tests/plugins/column-resizing/utils.gts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/table/src/-private/interfaces/column.ts b/table/src/-private/interfaces/column.ts index e863d9f3..0b79f205 100644 --- a/table/src/-private/interfaces/column.ts +++ b/table/src/-private/interfaces/column.ts @@ -6,7 +6,7 @@ import type { Constructor } from '../private-types'; import type { ComponentLike, ContentValue } from '@glint/template'; // Configuration context (for defining column options) - optional fields for user convenience -export interface CellConfigContext { +export interface CellConfigContext { column?: Column; row?: Row; options?: OptionsType & CellOptions; @@ -46,7 +46,7 @@ export interface ColumnConfig { /** * Optionally provide a function to determine the value of a row at this column */ - value?: (context: CellConfigContext) => ContentValue; + value?(context: CellConfigContext): ContentValue; /** * Recommended property to use for custom components for each cell per column. @@ -63,7 +63,7 @@ export interface ColumnConfig { /** * Bag of extra properties to pass to Cell via `@options`, if desired */ - options?: (context: CellConfigContext) => OptionsType; + options?(context: CellConfigContext): OptionsType; /** * Each plugin may provide column options, and provides similar syntax to how diff --git a/test-app/tests/plugins/column-resizing/utils.gts b/test-app/tests/plugins/column-resizing/utils.gts index ab743863..f682e837 100644 --- a/test-app/tests/plugins/column-resizing/utils.gts +++ b/test-app/tests/plugins/column-resizing/utils.gts @@ -51,10 +51,10 @@ export const getColumns = () => { return ths; }; -export class Context { +export class Context { @tracked containerWidth = 1000; - columns: ColumnConfig[] = [ + columns: ColumnConfig[] = [ { name: "A", key: "A" }, { name: "B", key: "B" }, { name: "C", key: "C" }, @@ -66,9 +66,9 @@ export class Context { await new Promise((resolve) => requestAnimationFrame(resolve)); }; - table = headlessTable(this, { + table = headlessTable(this, { columns: () => this.columns, - data: () => [] as unknown[], + data: () => [] as DataType[], plugins: [ColumnResizing], }); } From 623933b541b3e3e853fa3014fde1eb53ba985387 Mon Sep 17 00:00:00 2001 From: johanrd Date: Thu, 6 Nov 2025 15:26:25 +0100 Subject: [PATCH 5/9] revert test --- test-app/tests/plugins/column-resizing/utils.gts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test-app/tests/plugins/column-resizing/utils.gts b/test-app/tests/plugins/column-resizing/utils.gts index f682e837..ab743863 100644 --- a/test-app/tests/plugins/column-resizing/utils.gts +++ b/test-app/tests/plugins/column-resizing/utils.gts @@ -51,10 +51,10 @@ export const getColumns = () => { return ths; }; -export class Context { +export class Context { @tracked containerWidth = 1000; - columns: ColumnConfig[] = [ + columns: ColumnConfig[] = [ { name: "A", key: "A" }, { name: "B", key: "B" }, { name: "C", key: "C" }, @@ -66,9 +66,9 @@ export class Context { await new Promise((resolve) => requestAnimationFrame(resolve)); }; - table = headlessTable(this, { + table = headlessTable(this, { columns: () => this.columns, - data: () => [] as DataType[], + data: () => [] as unknown[], plugins: [ColumnResizing], }); } From f0cffc7978f59d131ec40ce781f08dd5dc426e15 Mon Sep 17 00:00:00 2001 From: johanrd Date: Thu, 6 Nov 2025 15:44:22 +0100 Subject: [PATCH 6/9] simplify --- table/src/plugins/column-reordering/helpers.ts | 2 +- table/src/plugins/column-reordering/plugin.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/table/src/plugins/column-reordering/helpers.ts b/table/src/plugins/column-reordering/helpers.ts index 5673f3c0..1d5990c7 100644 --- a/table/src/plugins/column-reordering/helpers.ts +++ b/table/src/plugins/column-reordering/helpers.ts @@ -23,7 +23,7 @@ export const moveRight = (column: Column) => */ export const setColumnOrder = ( table: Table, - order: ColumnOrder, + order: ColumnOrder, ) => meta.forTable(table, ColumnReordering).setOrder(order); /** diff --git a/table/src/plugins/column-reordering/plugin.ts b/table/src/plugins/column-reordering/plugin.ts index 3462eda6..9933e660 100644 --- a/table/src/plugins/column-reordering/plugin.ts +++ b/table/src/plugins/column-reordering/plugin.ts @@ -124,7 +124,7 @@ export class TableMeta { * Get the curret order/position of a column */ @action - getPosition(column: Column) { + getPosition(column: Column) { return this.columnOrder.get(column.key); } @@ -133,7 +133,7 @@ export class TableMeta { */ @action setPosition( - column: Column, + column: Column, newPosition: number, ) { return this.columnOrder.swapWith(column.key, newPosition); @@ -217,7 +217,7 @@ export class TableMeta { * @private * Used for keeping track of and updating column order */ -export class ColumnOrder { +export class ColumnOrder { /** * This map will be empty until we re-order something. */ @@ -237,7 +237,7 @@ export class ColumnOrder { * - Provide `visibleColumns` to indicate which are visible * - Hidden columns maintain their position when toggled */ - columns: () => Column[]; + columns: () => Column[]; /** * Optional: Record of which columns are currently visible. * When provided, moveLeft/moveRight will skip over hidden columns. @@ -496,18 +496,18 @@ export class ColumnOrder { } @cached - get orderedColumns(): Column[] { + get orderedColumns(): Column[] { const allColumns = this.args.columns(); const columnsByKey = allColumns.reduce( (keyMap, column) => { keyMap[column.key] = column; return keyMap; }, - {} as Record>, + {} as Record, ); const mergedOrder = orderOf(allColumns, this.map); - const result: Column[] = Array.from({ + const result: Column[] = Array.from({ length: allColumns.length, }); From 31a9c299da39f322cb011e05d0710e79dc53acfe Mon Sep 17 00:00:00 2001 From: johanrd Date: Thu, 6 Nov 2025 15:52:58 +0100 Subject: [PATCH 7/9] lint --- table/src/plugins/column-reordering/plugin.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/table/src/plugins/column-reordering/plugin.ts b/table/src/plugins/column-reordering/plugin.ts index 9933e660..7fb7ebdd 100644 --- a/table/src/plugins/column-reordering/plugin.ts +++ b/table/src/plugins/column-reordering/plugin.ts @@ -132,10 +132,7 @@ export class TableMeta { * Swap the column with the column at `newPosition` */ @action - setPosition( - column: Column, - newPosition: number, - ) { + setPosition(column: Column, newPosition: number) { return this.columnOrder.swapWith(column.key, newPosition); } From 513040f9394037eaeb3561d57a592321c9464af6 Mon Sep 17 00:00:00 2001 From: johanrd Date: Thu, 6 Nov 2025 16:06:56 +0100 Subject: [PATCH 8/9] d --- table/src/-private/js-helper.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/table/src/-private/js-helper.ts b/table/src/-private/js-helper.ts index 5a2de28c..03f9db67 100644 --- a/table/src/-private/js-helper.ts +++ b/table/src/-private/js-helper.ts @@ -2,9 +2,9 @@ import { Table } from './table.ts'; import type { TableConfig, CellContext } from './interfaces'; -type Args = - | [destroyable: object, options: TableConfig] - | [options: TableConfig]; +type Args = + | [destroyable: object, options: TableConfig] + | [options: TableConfig]; /** * Represents a UI-less version of a table @@ -23,9 +23,9 @@ type Args = * } * ``` */ -export function headlessTable( - options: TableConfig, -): Table; +export function headlessTable( + options: TableConfig, +): Table; /** * Represents a UI-less version of a table @@ -44,14 +44,14 @@ export function headlessTable( * ``` * */ -export function headlessTable( +export function headlessTable( destroyable: object, - options: TableConfig, -): Table; + options: TableConfig, +): Table; -export function headlessTable( - ...args: Args -): Table { +export function headlessTable( + ...args: Args +): Table { if (args.length === 2) { const [destroyable, options] = args; @@ -60,7 +60,7 @@ export function headlessTable( * otherwise individual-property reactivity can be managed on a per-property * "thunk"-basis */ - return Table.from>( + return Table.from>( destroyable, () => options, ); @@ -68,5 +68,5 @@ export function headlessTable( const [options] = args; - return Table.from>(() => options); + return Table.from>(() => options); } From f40ba751bb1a15870b1e91dae2170a461d699521 Mon Sep 17 00:00:00 2001 From: johanrd Date: Thu, 6 Nov 2025 18:02:27 +0100 Subject: [PATCH 9/9] consistent naming --- table/src/-private/interfaces/column.ts | 24 +++++++++++-------- table/src/plugins/column-visibility/plugin.ts | 12 +++++----- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/table/src/-private/interfaces/column.ts b/table/src/-private/interfaces/column.ts index 0b79f205..6da940ae 100644 --- a/table/src/-private/interfaces/column.ts +++ b/table/src/-private/interfaces/column.ts @@ -6,16 +6,16 @@ import type { Constructor } from '../private-types'; import type { ComponentLike, ContentValue } from '@glint/template'; // Configuration context (for defining column options) - optional fields for user convenience -export interface CellConfigContext { - column?: Column; - row?: Row; +export interface CellConfigContext { + column?: Column; + row?: Row; options?: OptionsType & CellOptions; } // Runtime context (for Cell components) - required fields since they're always provided -export interface CellContext { - column: Column; - row: Row; +export interface CellContext { + column: Column; + row: Row; options?: OptionsType & CellOptions; } @@ -30,7 +30,11 @@ export type CellOptions = { defaultValue?: string; } & Record; -export interface ColumnConfig { +export interface ColumnConfig< + DataType = unknown, + OptionsType = unknown, + CellArgs = unknown, +> { /** * the `key` is required for preferences storage, as well as * managing uniqueness of the columns in an easy-to-understand way. @@ -46,7 +50,7 @@ export interface ColumnConfig { /** * Optionally provide a function to determine the value of a row at this column */ - value?(context: CellConfigContext): ContentValue; + value?(context: CellConfigContext): ContentValue; /** * Recommended property to use for custom components for each cell per column. @@ -63,7 +67,7 @@ export interface ColumnConfig { /** * Bag of extra properties to pass to Cell via `@options`, if desired */ - options?(context: CellConfigContext): OptionsType; + options?(context: CellConfigContext): OptionsType; /** * Each plugin may provide column options, and provides similar syntax to how @@ -79,4 +83,4 @@ export interface ColumnConfig { pluginOptions?: ColumnPluginOption[]; } -export type ColumnKey = NonNullable['key']>; +export type ColumnKey = NonNullable['key']>; diff --git a/table/src/plugins/column-visibility/plugin.ts b/table/src/plugins/column-visibility/plugin.ts index b8f3c37a..531effae 100644 --- a/table/src/plugins/column-visibility/plugin.ts +++ b/table/src/plugins/column-visibility/plugin.ts @@ -64,8 +64,8 @@ export class ColumnVisibility } } -export class ColumnMeta { - constructor(private column: Column) {} +export class ColumnMeta { + constructor(private column: Column) {} get isVisible(): boolean { const columnPreferences = preferences.forColumn( @@ -132,11 +132,11 @@ export class ColumnMeta { }; } -export class TableMeta { - constructor(private table: Table) {} +export class TableMeta { + constructor(private table: Table) {} @cached - get visibleColumns(): Column[] { + get visibleColumns(): Column[] { const allColumns = this.table.columns.values(); return allColumns.filter((column) => { @@ -147,7 +147,7 @@ export class TableMeta { } @action - toggleColumnVisibility(column: Column) { + toggleColumnVisibility(column: Column) { const columnMeta = meta.forColumn(column, ColumnVisibility); columnMeta.toggle();