From 40babc11767ebf34c2bf0a76aef6702202bed908 Mon Sep 17 00:00:00 2001 From: peter <20213436+petera2c@users.noreply.github.com> Date: Sun, 26 Jul 2026 12:34:53 -0500 Subject: [PATCH 1/2] feat(vue): add TData/TValue generics with typed cell-renderer and nested-tables demos Mirror Solid/React row typing on the Vue adapter, and migrate two Vue demos as the reference pattern (custom renderers + nested table columns). Co-authored-by: Cursor --- .../demos/cell-renderer/CellRendererDemo.vue | 35 +-- .../cell-renderer/cell-renderer.demo-data.ts | 5 +- .../demos/nested-tables/NestedTablesDemo.vue | 13 +- .../nested-tables/nested-tables.demo-data.ts | 42 +++- packages/vue/src/SimpleTable.ts | 34 ++- .../__tests__/genericRowData.types.test.ts | 131 +++++++++++ .../src/__tests__/inferRowData.types.test.ts | 57 +++++ packages/vue/src/buildVanillaConfig.ts | 90 ++++---- packages/vue/src/index.ts | 8 +- packages/vue/src/types.ts | 203 +++++++++++++----- 10 files changed, 486 insertions(+), 132 deletions(-) create mode 100644 packages/vue/src/__tests__/genericRowData.types.test.ts create mode 100644 packages/vue/src/__tests__/inferRowData.types.test.ts diff --git a/packages/examples/vue/src/demos/cell-renderer/CellRendererDemo.vue b/packages/examples/vue/src/demos/cell-renderer/CellRendererDemo.vue index e05783a15..a07bd73ac 100644 --- a/packages/examples/vue/src/demos/cell-renderer/CellRendererDemo.vue +++ b/packages/examples/vue/src/demos/cell-renderer/CellRendererDemo.vue @@ -1,7 +1,13 @@ diff --git a/packages/examples/vue/src/demos/column-alignment/column-alignment.demo-data.ts b/packages/examples/vue/src/demos/column-alignment/column-alignment.demo-data.ts index 83643bcf6..684a03c78 100644 --- a/packages/examples/vue/src/demos/column-alignment/column-alignment.demo-data.ts +++ b/packages/examples/vue/src/demos/column-alignment/column-alignment.demo-data.ts @@ -1,8 +1,15 @@ // Self-contained demo table setup for this example. import type { VueColumnDef } from "@simple-table/vue"; +export interface ColumnAlignmentPlayer { + id: number; + name: string; + score: number; + rating: number; + status: string; +} -export const columnAlignmentHeaders: VueColumnDef[] = [ +export const columnAlignmentHeaders: VueColumnDef[] = [ { accessor: "id", label: "ID", width: 80, align: "left", type: "number" }, { accessor: "name", label: "Name", minWidth: 100, width: "1fr", align: "center", type: "string" }, { accessor: "score", label: "Score", width: 120, align: "right", type: "number" }, @@ -10,7 +17,7 @@ export const columnAlignmentHeaders: VueColumnDef[] = [ { accessor: "status", label: "Status", width: 120, align: "left", type: "string" }, ]; -export const columnAlignmentData = [ +export const columnAlignmentData: ColumnAlignmentPlayer[] = [ { id: 1, name: "Camila Rodriguez", score: 94, rating: 4.9, status: "Active" }, { id: 2, name: "Enzo Silva", score: 89, rating: 4.6, status: "Active" }, { id: 3, name: "Yuki Kim", score: 96, rating: 4.8, status: "Active" }, @@ -28,4 +35,4 @@ export const columnAlignmentData = [ export const columnAlignmentConfig = { headers: columnAlignmentHeaders, rows: columnAlignmentData, -} as const; +}; diff --git a/packages/examples/vue/src/demos/column-editing/ColumnEditingDemo.vue b/packages/examples/vue/src/demos/column-editing/ColumnEditingDemo.vue index e4adcad22..911c24670 100644 --- a/packages/examples/vue/src/demos/column-editing/ColumnEditingDemo.vue +++ b/packages/examples/vue/src/demos/column-editing/ColumnEditingDemo.vue @@ -14,6 +14,7 @@ import { ref, computed } from "vue"; import { SimpleTable } from "@simple-table/vue"; -import type { Theme, VueColumnDef } from "@simple-table/vue"; +import type { Theme, VueColumnDef, GetRowIdParams } from "@simple-table/vue"; import { columnEditingData, columnEditingHeaders } from "./column-editing.demo-data"; +import type { ColumnEditingEmployee } from "./column-editing.demo-data"; import "@simple-table/vue/styles.css"; withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), { height: "400px", }); -const additionalColumns = ref([]); +const additionalColumns = ref[]>([]); const lastAdded = ref(""); const headers = computed(() => [...columnEditingHeaders, ...additionalColumns.value]); +const getRowId = ({ row }: GetRowIdParams) => row.id; + function addColumn() { const n = additionalColumns.value.length + 1; - const col: VueColumnDef = { + const col: VueColumnDef = { accessor: `custom-${n}`, label: `Custom ${n}`, width: 120, @@ -51,7 +55,7 @@ function addColumn() { lastAdded.value = col.label; } -function handleHeaderEdit(_header: VueColumnDef, newLabel: string) { +function handleHeaderEdit(_header: VueColumnDef, newLabel: string) { lastAdded.value = `Renamed to: ${newLabel}`; } diff --git a/packages/examples/vue/src/demos/column-editing/column-editing.demo-data.ts b/packages/examples/vue/src/demos/column-editing/column-editing.demo-data.ts index d0488eb62..1384722ae 100644 --- a/packages/examples/vue/src/demos/column-editing/column-editing.demo-data.ts +++ b/packages/examples/vue/src/demos/column-editing/column-editing.demo-data.ts @@ -1,8 +1,16 @@ // Self-contained demo table setup for this example. import type { VueColumnDef } from "@simple-table/vue"; +export interface ColumnEditingEmployee { + id: number; + name: string; + age: number; + role: string; + department: string; + email: string; +} -export const columnEditingHeaders: VueColumnDef[] = [ +export const columnEditingHeaders: VueColumnDef[] = [ { accessor: "id", label: "ID", width: 80, type: "number" }, { accessor: "name", label: "Name", minWidth: 120, width: "1fr", type: "string" }, { accessor: "age", label: "Age", width: 100, type: "number" }, @@ -10,7 +18,7 @@ export const columnEditingHeaders: VueColumnDef[] = [ { accessor: "department", label: "Department", width: 150, type: "string" }, ]; -export const columnEditingData = [ +export const columnEditingData: ColumnEditingEmployee[] = [ { id: 1, name: "Marcus Rodriguez", age: 29, role: "Frontend Developer", department: "Engineering", email: "marcus.rodriguez@company.com" }, { id: 2, name: "Sophia Chen", age: 27, role: "UX/UI Designer", department: "Design", email: "sophia.chen@company.com" }, { id: 3, name: "Raj Patel", age: 34, role: "Engineering Manager", department: "Management", email: "raj.patel@company.com" }, @@ -29,4 +37,4 @@ export const columnEditingConfig = { headers: columnEditingHeaders, rows: columnEditingData, tableProps: { enableHeaderEditing: true, selectableColumns: true }, -} as const; +}; diff --git a/packages/examples/vue/src/demos/column-editor-custom-renderer/ColumnEditorCustomRendererDemo.vue b/packages/examples/vue/src/demos/column-editor-custom-renderer/ColumnEditorCustomRendererDemo.vue index d62f5d0cf..c33f22676 100644 --- a/packages/examples/vue/src/demos/column-editor-custom-renderer/ColumnEditorCustomRendererDemo.vue +++ b/packages/examples/vue/src/demos/column-editor-custom-renderer/ColumnEditorCustomRendererDemo.vue @@ -1,11 +1,12 @@ diff --git a/packages/examples/vue/src/demos/column-filtering/column-filtering.demo-data.ts b/packages/examples/vue/src/demos/column-filtering/column-filtering.demo-data.ts index 8f202b94d..443ea7fac 100644 --- a/packages/examples/vue/src/demos/column-filtering/column-filtering.demo-data.ts +++ b/packages/examples/vue/src/demos/column-filtering/column-filtering.demo-data.ts @@ -1,8 +1,17 @@ // Self-contained demo table setup for this example. -import type { Row, VueColumnDef } from "@simple-table/vue"; +import type { VueColumnDef, CellRendererProps } from "@simple-table/vue"; +export interface ColumnFilteringEmployee { + id: number; + name: string; + department: string; + role: string; + salary: number; + startDate: string; + isActive: boolean; +} -export const COLUMN_FILTERING_DATA: Row[] = [ +export const COLUMN_FILTERING_DATA: ColumnFilteringEmployee[] = [ { id: 1, name: "Bianca Rossi", @@ -147,7 +156,7 @@ export const DEPARTMENT_OPTIONS = [ { label: "Quality Assurance", value: "Quality Assurance" }, ]; -export const columnFilteringHeaders: VueColumnDef[] = [ +export const columnFilteringHeaders: VueColumnDef[] = [ { accessor: "id", label: "ID", @@ -191,7 +200,7 @@ export const columnFilteringHeaders: VueColumnDef[] = [ type: "number", sortable: true, filterable: true, - cellRenderer: ({ row }) => { + cellRenderer: ({ row }: CellRendererProps) => { const salary = row.salary as number; return `$${salary.toLocaleString()}`; }, @@ -218,4 +227,4 @@ export const columnFilteringHeaders: VueColumnDef[] = [ export const columnFilteringConfig = { headers: columnFilteringHeaders, rows: COLUMN_FILTERING_DATA, -} as const; +}; diff --git a/packages/examples/vue/src/demos/column-pinning/ColumnPinningDemo.vue b/packages/examples/vue/src/demos/column-pinning/ColumnPinningDemo.vue index 1ce46d8d4..de3c4f7d4 100644 --- a/packages/examples/vue/src/demos/column-pinning/ColumnPinningDemo.vue +++ b/packages/examples/vue/src/demos/column-pinning/ColumnPinningDemo.vue @@ -2,6 +2,7 @@ diff --git a/packages/examples/vue/src/demos/column-pinning/column-pinning.demo-data.ts b/packages/examples/vue/src/demos/column-pinning/column-pinning.demo-data.ts index 37f19efbd..a16c6eb99 100644 --- a/packages/examples/vue/src/demos/column-pinning/column-pinning.demo-data.ts +++ b/packages/examples/vue/src/demos/column-pinning/column-pinning.demo-data.ts @@ -1,8 +1,21 @@ // Self-contained demo table setup for this example. import type { VueColumnDef } from "@simple-table/vue"; +export interface ColumnPinningEmployee { + id: number; + name: string; + email: string; + role: string; + department: string; + location: string; + joinDate: string; + salary: number; + manager: string; + status: string; + projects: number; +} -export const columnPinningHeaders: VueColumnDef[] = [ +export const columnPinningHeaders: VueColumnDef[] = [ { accessor: "name", label: "Name", width: 132, pinned: "left", type: "string" }, { accessor: "email", label: "Email", width: 220, type: "string" }, { accessor: "role", label: "Role", width: 150, type: "string" }, @@ -15,7 +28,7 @@ export const columnPinningHeaders: VueColumnDef[] = [ { accessor: "projects", label: "Projects", width: 100, align: "right", pinned: "right", type: "number" }, ]; -export const columnPinningData = [ +export const columnPinningData: ColumnPinningEmployee[] = [ { id: 1, name: "Zara Nakamura", email: "zara.n@pixelstudio.game", role: "Lead Game Designer", department: "Game Design", location: "Tokyo", joinDate: "2019-03-12", salary: 145000, manager: "Hiroshi Tanaka", status: "Active", projects: 7 }, { id: 2, name: "Phoenix Rodriguez", email: "phoenix.r@pixelstudio.game", role: "3D Artist", department: "Art & Animation", location: "Montreal", joinDate: "2020-11-08", salary: 98000, manager: "Elena Volkov", status: "Active", projects: 4 }, { id: 3, name: "Kai Thompson", email: "kai.t@pixelstudio.game", role: "Senior Programmer", department: "Engineering", location: "San Francisco", joinDate: "2018-05-15", salary: 135000, manager: "Nova Singh", status: "Active", projects: 6 }, @@ -34,4 +47,4 @@ export const columnPinningConfig = { headers: columnPinningHeaders, rows: columnPinningData, tableProps: { columnResizing: true }, -} as const; +}; diff --git a/packages/examples/vue/src/demos/column-reordering/ColumnReorderingDemo.vue b/packages/examples/vue/src/demos/column-reordering/ColumnReorderingDemo.vue index 06d247509..4e3dee8bc 100644 --- a/packages/examples/vue/src/demos/column-reordering/ColumnReorderingDemo.vue +++ b/packages/examples/vue/src/demos/column-reordering/ColumnReorderingDemo.vue @@ -3,6 +3,7 @@ :column-reordering="true" :columns="headers" :rows="columnReorderingConfig.rows" + :get-row-id="getRowId" :height="height" :theme="theme" @column-order-change="handleColumnOrderChange" @@ -12,17 +13,20 @@ diff --git a/packages/examples/vue/src/demos/column-reordering/column-reordering.demo-data.ts b/packages/examples/vue/src/demos/column-reordering/column-reordering.demo-data.ts index 17752fb31..2e8d6cd7a 100644 --- a/packages/examples/vue/src/demos/column-reordering/column-reordering.demo-data.ts +++ b/packages/examples/vue/src/demos/column-reordering/column-reordering.demo-data.ts @@ -1,8 +1,15 @@ // Self-contained demo table setup for this example. import type { VueColumnDef } from "@simple-table/vue"; +export interface CrewMember { + id: number; + name: string; + age: number; + role: string; + department: string; +} -export const columnReorderingHeaders: VueColumnDef[] = [ +export const columnReorderingHeaders: VueColumnDef[] = [ { accessor: "id", label: "ID", width: 60, type: "number" }, { accessor: "name", label: "Name", width: "1fr", type: "string" }, { accessor: "age", label: "Age", width: 80, align: "right", type: "number" }, @@ -29,4 +36,4 @@ export const columnReorderingConfig = { headers: columnReorderingHeaders, rows: columnReorderingData, tableProps: { columnReordering: true }, -} as const; +}; diff --git a/packages/examples/vue/src/demos/column-resizing/ColumnResizingDemo.vue b/packages/examples/vue/src/demos/column-resizing/ColumnResizingDemo.vue index dbac7bbfa..0ebb7e6c9 100644 --- a/packages/examples/vue/src/demos/column-resizing/ColumnResizingDemo.vue +++ b/packages/examples/vue/src/demos/column-resizing/ColumnResizingDemo.vue @@ -10,6 +10,7 @@ :column-resizing="true" :columns="headers" :rows="columnResizingData" + :get-row-id="getRowId" :height="height" :theme="theme" @column-width-change="handleColumnWidthChange" @@ -20,17 +21,24 @@ diff --git a/packages/examples/vue/src/demos/column-selection/column-selection.demo-data.ts b/packages/examples/vue/src/demos/column-selection/column-selection.demo-data.ts index 01684f60f..4aa236a43 100644 --- a/packages/examples/vue/src/demos/column-selection/column-selection.demo-data.ts +++ b/packages/examples/vue/src/demos/column-selection/column-selection.demo-data.ts @@ -1,8 +1,16 @@ // Self-contained demo table setup for this example. import type { VueColumnDef } from "@simple-table/vue"; +export interface TeamMember { + id: number; + name: string; + age: number; + role: string; + department: string; + email: string; +} -export const columnSelectionHeaders: VueColumnDef[] = [ +export const columnSelectionHeaders: VueColumnDef[] = [ { accessor: "id", label: "ID", width: 80, sortable: true, type: "number" }, { accessor: "name", label: "Name", minWidth: 120, width: "1fr", sortable: true, type: "string" }, { accessor: "age", label: "Age", width: 100, sortable: true, type: "number" }, @@ -30,4 +38,4 @@ export const columnSelectionConfig = { headers: columnSelectionHeaders, rows: columnSelectionData, tableProps: { selectableColumns: true }, -} as const; +}; diff --git a/packages/examples/vue/src/demos/column-sorting/ColumnSortingDemo.vue b/packages/examples/vue/src/demos/column-sorting/ColumnSortingDemo.vue index 417d5f689..2e2804b6e 100644 --- a/packages/examples/vue/src/demos/column-sorting/ColumnSortingDemo.vue +++ b/packages/examples/vue/src/demos/column-sorting/ColumnSortingDemo.vue @@ -2,6 +2,7 @@ diff --git a/packages/examples/vue/src/demos/column-sorting/column-sorting.demo-data.ts b/packages/examples/vue/src/demos/column-sorting/column-sorting.demo-data.ts index 093065b3d..2ff24243b 100644 --- a/packages/examples/vue/src/demos/column-sorting/column-sorting.demo-data.ts +++ b/packages/examples/vue/src/demos/column-sorting/column-sorting.demo-data.ts @@ -1,9 +1,16 @@ // Self-contained demo table setup for this example. -import type { Row } from "@simple-table/vue"; import type { VueColumnDef } from "@simple-table/vue"; +export interface FacultyMember { + id: number; + name: string; + age: number; + role: string; + department: string; + startDate: string; +} -export const COLUMN_SORTING_DATA: Row[] = [ +export const COLUMN_SORTING_DATA: FacultyMember[] = [ { id: 1, name: "Dr. Elena Vasquez", @@ -103,7 +110,7 @@ export const COLUMN_SORTING_DATA: Row[] = [ ]; -export const columnSortingHeaders: VueColumnDef[] = [ +export const columnSortingHeaders: VueColumnDef[] = [ { accessor: "id", label: "ID", width: 80, sortable: true, type: "number" }, { accessor: "name", label: "Name", width: 180, sortable: true, type: "string" }, { accessor: "age", label: "Age", width: 80, sortable: true, type: "number" }, @@ -141,3 +148,4 @@ export const columnSortingConfig = { headers: columnSortingHeaders, rows: COLUMN_SORTING_DATA, }; + diff --git a/packages/examples/vue/src/demos/column-visibility/ColumnVisibilityDemo.vue b/packages/examples/vue/src/demos/column-visibility/ColumnVisibilityDemo.vue index b9b6f87fa..16b030e73 100644 --- a/packages/examples/vue/src/demos/column-visibility/ColumnVisibilityDemo.vue +++ b/packages/examples/vue/src/demos/column-visibility/ColumnVisibilityDemo.vue @@ -1,5 +1,6 @@