Merge branch 'feature/add-language' of https://github.com/chris-kruining/calque into feature/add-language

This commit is contained in:
Chris Kruining 2024-12-17 13:25:37 +01:00
commit 0749d5904f
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
2 changed files with 70 additions and 43 deletions

View file

@ -12,12 +12,17 @@ export type Column<T> = {
sortable?: boolean, sortable?: boolean,
group?: string, group?: string,
readonly groupBy?: (rows: DataSetRowNode<keyof T, T>[]) => DataSetNode<keyof T, T>[], readonly groupBy?: (rows: DataSetRowNode<keyof T, T>[]) => DataSetNode<keyof T, T>[],
readonly groupBy?: (rows: DataSetRowNode<keyof T, T>[]) => DataSetNode<keyof T, T>[],
}; };
export type CellEditor<T extends Record<string, any>, K extends keyof T> = (cell: { row: number, column: K, value: T[K] }) => JSX.Element;
export type CellEditors<T extends Record<string, any>> = { [K in keyof T]?: CellEditor<T, K> };
export type CellEditor<T extends Record<string, any>, K extends keyof T> = (cell: { row: number, column: K, value: T[K] }) => JSX.Element; export type CellEditor<T extends Record<string, any>, K extends keyof T> = (cell: { row: number, column: K, value: T[K] }) => JSX.Element;
export type CellEditors<T extends Record<string, any>> = { [K in keyof T]?: CellEditor<T, K> }; export type CellEditors<T extends Record<string, any>> = { [K in keyof T]?: CellEditor<T, K> };
export interface TableApi<T extends Record<string, any>> { export interface TableApi<T extends Record<string, any>> {
readonly selection: Accessor<SelectionItem<keyof T, T>[]>;
readonly rows: Accessor<DataSet<T>>;
readonly selection: Accessor<SelectionItem<keyof T, T>[]>; readonly selection: Accessor<SelectionItem<keyof T, T>[]>;
readonly rows: Accessor<DataSet<T>>; readonly rows: Accessor<DataSet<T>>;
readonly columns: Accessor<Column<T>[]>; readonly columns: Accessor<Column<T>[]>;
@ -25,6 +30,10 @@ export interface TableApi<T extends Record<string, any>> {
clear(): void; clear(): void;
} }
interface TableContextType<T extends Record<string, any>> {
readonly rows: Accessor<DataSet<T>>,
readonly columns: Accessor<Column<T>[]>,
readonly selection: Accessor<SelectionItem<keyof T, T>[]>,
interface TableContextType<T extends Record<string, any>> { interface TableContextType<T extends Record<string, any>> {
readonly rows: Accessor<DataSet<T>>, readonly rows: Accessor<DataSet<T>>,
readonly columns: Accessor<Column<T>[]>, readonly columns: Accessor<Column<T>[]>,
@ -46,6 +55,7 @@ type TableProps<T extends Record<string, any>> = {
class?: string, class?: string,
summary?: string, summary?: string,
rows: DataSet<T>, rows: DataSet<T>,
rows: DataSet<T>,
columns: Column<T>[], columns: Column<T>[],
selectionMode?: SelectionMode, selectionMode?: SelectionMode,
children?: CellEditors<T>, children?: CellEditors<T>,
@ -55,18 +65,20 @@ type TableProps<T extends Record<string, any>> = {
export function Table<T extends Record<string, any>>(props: TableProps<T>) { export function Table<T extends Record<string, any>>(props: TableProps<T>) {
const [selection, setSelection] = createSignal<SelectionItem<keyof T, T>[]>([]); const [selection, setSelection] = createSignal<SelectionItem<keyof T, T>[]>([]);
const rows = createMemo(() => props.rows);
const rows = createMemo(() => props.rows); const rows = createMemo(() => props.rows);
const columns = createMemo<Column<T>[]>(() => props.columns ?? []); const columns = createMemo<Column<T>[]>(() => props.columns ?? []);
const selectionMode = createMemo(() => props.selectionMode ?? SelectionMode.None); const selectionMode = createMemo(() => props.selectionMode ?? SelectionMode.None);
const cellRenderers = createMemo<CellEditors<T>>(() => props.children ?? {}); const cellRenderers = createMemo<CellEditors<T>>(() => props.children ?? {});
const context: TableContextType<T> = { const context: TableContextType<T> = {
rows, const context: TableContextType<T> = {
columns, rows,
selection, columns,
selectionMode, selection,
cellRenderers, selectionMode,
}; cellRenderers,
};
return <TableContext.Provider value={context}> return <TableContext.Provider value={context}>
<SelectionProvider selection={setSelection} multiSelect={props.selectionMode === SelectionMode.Multiple}> <SelectionProvider selection={setSelection} multiSelect={props.selectionMode === SelectionMode.Multiple}>
@ -77,10 +89,12 @@ export function Table<T extends Record<string, any>>(props: TableProps<T>) {
</TableContext.Provider>; </TableContext.Provider>;
}; };
type InnerTableProps<T extends Record<string, any>> = { class?: string, summary?: string, rows: DataSet<T> };
type InnerTableProps<T extends Record<string, any>> = { class?: string, summary?: string, rows: DataSet<T> }; type InnerTableProps<T extends Record<string, any>> = { class?: string, summary?: string, rows: DataSet<T> };
function InnerTable<T extends Record<string, any>>(props: InnerTableProps<T>) { function InnerTable<T extends Record<string, any>>(props: InnerTableProps<T>) {
const table = useTable<T>(); const table = useTable<T>();
const table = useTable<T>();
const selectable = createMemo(() => table.selectionMode() !== SelectionMode.None); const selectable = createMemo(() => table.selectionMode() !== SelectionMode.None);
const columnCount = createMemo(() => table.columns().length); const columnCount = createMemo(() => table.columns().length);
@ -95,8 +109,9 @@ function InnerTable<T extends Record<string, any>>(props: InnerTableProps<T>) {
<tbody class={css.main}> <tbody class={css.main}>
<For each={props.rows.value()}>{ <For each={props.rows.value()}>{
node => <Node node={node} depth={0} /> <For each={props.rows.value()}>{
}</For> node => <Node node={node} depth={0} />
}</For>
</tbody> </tbody>
{/* <Show when={true}> {/* <Show when={true}>
@ -112,8 +127,11 @@ function InnerTable<T extends Record<string, any>>(props: InnerTableProps<T>) {
function Api<T extends Record<string, any>>(props: { api: undefined | ((api: TableApi<T>) => any) }) { function Api<T extends Record<string, any>>(props: { api: undefined | ((api: TableApi<T>) => any) }) {
const table = useTable<T>(); const table = useTable<T>();
const selectionContext = useSelection<T>(); const selectionContext = useSelection<T>();
const table = useTable<T>();
const selectionContext = useSelection<T>();
const api: TableApi<T> = { const api: TableApi<T> = {
selection: selectionContext.selection,
selection: selectionContext.selection, selection: selectionContext.selection,
rows: table.rows, rows: table.rows,
columns: table.columns, columns: table.columns,
@ -163,6 +181,7 @@ function Head(props: {}) {
<For each={table.columns()}>{ <For each={table.columns()}>{
({ id, label, sortable }) => { ({ id, label, sortable }) => {
const sort = createMemo(() => table.rows().sort());
const sort = createMemo(() => table.rows().sort()); const sort = createMemo(() => table.rows().sort());
const by = String(id); const by = String(id);
@ -200,45 +219,53 @@ function Head(props: {}) {
}; };
function Node<T extends Record<string, any>>(props: { node: DataSetNode<keyof T, T>, depth: number, groupedBy?: keyof T }) { function Node<T extends Record<string, any>>(props: { node: DataSetNode<keyof T, T>, depth: number, groupedBy?: keyof T }) {
return <Switch> function Node<T extends Record<string, any>>(props: { node: DataSetNode<keyof T, T>, depth: number, groupedBy?: keyof T }) {
<Match when={props.node.kind === 'row' ? props.node : undefined}>{ return <Switch>
row => <Row key={row().key} value={row().value} depth={props.depth} groupedBy={props.groupedBy} /> <Match when={props.node.kind === 'row' ? props.node : undefined}>{
}</Match> row => <Row key={row().key} value={row().value} depth={props.depth} groupedBy={props.groupedBy} />
}</Match>
<Match when={props.node.kind === 'group' ? props.node : undefined}>{ <Match when={props.node.kind === 'group' ? props.node : undefined}>{
group => <Group key={group().key} groupedBy={group().groupedBy} nodes={group().nodes} depth={props.depth} /> group => <Group key={group().key} groupedBy={group().groupedBy} nodes={group().nodes} depth={props.depth} />
}</Match> }</Match>
</Switch>; </Switch>;
} }
function Row<T extends Record<string, any>>(props: { key: keyof T, value: T, depth: number, groupedBy?: keyof T }) { function Row<T extends Record<string, any>>(props: { key: keyof T, value: T, depth: number, groupedBy?: keyof T }) {
const table = useTable<T>(); const table = useTable<T>();
const context = useSelection<T>(); const context = useSelection<T>();
const columns = table.columns; const columns = table.columns;
const isSelected = context.isSelected(props.key); function Row<T extends Record<string, any>>(props: { key: keyof T, value: T, depth: number, groupedBy?: keyof T }) {
const table = useTable<T>();
const context = useSelection<T>();
const columns = table.columns;
return <tr class={css.row} style={{ '--depth': props.depth }} use:selectable={{ value: props.value, key: props.key }}> const isSelected = context.isSelected(props.key);
<Show when={table.selectionMode() !== SelectionMode.None}>
<th class={css.checkbox}>
<input type="checkbox" checked={isSelected()} on:input={() => context.select([props.key])} on:pointerdown={e => e.stopPropagation()} />
</th>
</Show>
<For each={columns()}>{ return <tr class={css.row} style={{ '--depth': props.depth }} use:selectable={{ value: props.value, key: props.key }}>
({ id }) => <td class={'css.cell'}>{table.cellRenderers()[id]?.({ row: props.key as number, column: id, value: props.value[id] }) ?? props.value[id]}</td> <Show when={table.selectionMode() !== SelectionMode.None}>
}</For> <th class={css.checkbox}>
</tr>; <input type="checkbox" checked={isSelected()} on:input={() => context.select([props.key])} on:pointerdown={e => e.stopPropagation()} />
}; </th>
</Show>
function Group<T extends Record<string, any>>(props: { key: keyof T, groupedBy: keyof T, nodes: DataSetNode<keyof T, T>[], depth: number }) { <For each={columns()}>{
const table = useTable(); ({ id }) => <td class={'css.cell'}>{table.cellRenderers()[id]?.({ row: props.key as number, column: id, value: props.value[id] }) ?? props.value[id]}</td>
}</For>
</tr>;
};
return <details open> function Group<T extends Record<string, any>>(props: { key: keyof T, groupedBy: keyof T, nodes: DataSetNode<keyof T, T>[], depth: number }) {
<summary style={{ '--depth': props.depth }}>{String(props.key)}</summary> function Group<T extends Record<string, any>>(props: { key: keyof T, groupedBy: keyof T, nodes: DataSetNode<keyof T, T>[], depth: number }) {
const table = useTable();
<For each={props.nodes}>{ return <details open>
node => <Node node={node} depth={props.depth + 1} groupedBy={props.groupedBy} /> <summary style={{ '--depth': props.depth }}>{String(props.key)}</summary>
}</For> <summary style={{ '--depth': props.depth }}>{String(props.key)}</summary>
</details>;
}; <For each={props.nodes}>{
node => <Node node={node} depth={props.depth + 1} groupedBy={props.groupedBy} />
}</For>
</details>;
};

View file

@ -2,8 +2,8 @@ import { Sidebar } from '~/components/sidebar';
import { Column, createDataSet, DataSetGroupNode, DataSetNode, DataSetRowNode, GroupOptions, SelectionMode, SortOptions, Table } from '~/components/table'; import { Column, createDataSet, DataSetGroupNode, DataSetNode, DataSetRowNode, GroupOptions, SelectionMode, SortOptions, Table } from '~/components/table';
import { createStore } from 'solid-js/store'; import { createStore } from 'solid-js/store';
import { Person, people } from './experimental.data'; import { Person, people } from './experimental.data';
import css from './table.module.css';
import { createEffect, createMemo, For } from 'solid-js'; import { createEffect, createMemo, For } from 'solid-js';
import css from './table.module.css';
export default function TableExperiment() { export default function TableExperiment() {
const columns: Column<Person>[] = [ const columns: Column<Person>[] = [