woop in a working state again

This commit is contained in:
Chris Kruining 2024-12-18 16:32:21 +01:00
parent 4a5f0cf2d1
commit ab68df340f
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
15 changed files with 259 additions and 159 deletions

View file

@ -7,6 +7,10 @@ export default defineConfig({
html: {
cspNonce: 'KAAS_IS_AWESOME',
},
// css: {
// postcss: {
// },
// },
plugins: [
solidSvg()
// VitePWA({

View file

@ -1,11 +1,14 @@
import { Accessor, createContext, createEffect, createMemo, createSignal, JSX, useContext } from "solid-js";
import { createStore } from "solid-js/store";
import { deepCopy, deepDiff, Mutation } from "~/utilities";
import { SelectionMode, Table, Column as TableColumn, TableApi, CellEditors, CellEditor, createDataSet, DataSet, DataSetNode } from "~/components/table";
import { Mutation } from "~/utilities";
import { SelectionMode, Table, Column as TableColumn, TableApi, DataSet, CellRenderer } from "~/components/table";
import css from './grid.module.css';
export interface CellEditor<T extends Record<string, any>, K extends keyof T> {
(cell: Parameters<CellRenderer<T, K>>[0] & { mutate: (next: T[K]) => any }): JSX.Element;
}
export interface Column<T extends Record<string, any>> extends TableColumn<T> {
editor?: (cell: { row: number, column: keyof T, value: T[keyof T], mutate: (next: T[keyof T]) => any }) => JSX.Element;
editor?: CellEditor<T, keyof T>;
}
export interface GridApi<T extends Record<string, any>> extends TableApi<T> {
@ -16,7 +19,6 @@ export interface GridApi<T extends Record<string, any>> extends TableApi<T> {
}
interface GridContextType<T extends Record<string, any>> {
readonly rows: Accessor<DataSetNode<keyof T, T>[]>;
readonly mutations: Accessor<Mutation[]>;
readonly selection: TableApi<T>['selection'];
mutate<K extends keyof T>(row: number, column: K, value: T[K]): void;
@ -29,36 +31,31 @@ const GridContext = createContext<GridContextType<any>>();
const useGrid = () => useContext(GridContext)!;
type GridProps<T extends Record<string, any>> = { class?: string, groupBy?: keyof T, columns: Column<T>[], rows: T[], api?: (api: GridApi<T>) => any };
type GridProps<T extends Record<string, any>> = { class?: string, groupBy?: keyof T, columns: Column<T>[], rows: DataSet<T>, api?: (api: GridApi<T>) => any };
// type GridState<T extends Record<string, any>> = { data: DataSet<T>, columns: Column<T>[], numberOfRows: number };
export function Grid<T extends Record<string, any>>(props: GridProps<T>) {
const [table, setTable] = createSignal<TableApi<T>>();
const data = createMemo(() => createDataSet(props.rows));
const rows = createMemo(() => data().value());
const mutations = createMemo(() => data().mutations());
const rows = createMemo(() => props.rows);
const columns = createMemo(() => props.columns);
const mutations = createMemo(() => rows().mutations());
const ctx: GridContextType<T> = {
rows,
mutations,
selection: createMemo(() => table()?.selection() ?? []),
mutate<K extends keyof T>(row: number, column: K, value: T[K]) {
data().mutate(row, column, value);
rows().mutate(row, column, value);
},
remove(rows: number[]) {
// setState('rows', (r) => r.filter((_, i) => rows.includes(i) === false));
remove(indices: number[]) {
rows().remove(indices);
table()?.clear();
},
insert(row: T, at?: number) {
if (at === undefined) {
// setState('rows', state.rows.length, row);
} else {
}
rows().insert(row, at);
},
addColumn(column: keyof T, value: T[keyof T]): void {
@ -70,10 +67,8 @@ export function Grid<T extends Record<string, any>>(props: GridProps<T>) {
props.columns
.filter(c => c.editor !== undefined)
.map(c => {
const Editor: CellEditor<T, keyof T> = ({ row, column, value }) => {
const Editor: CellRenderer<T, keyof T> = ({ row, column, value }) => {
const mutate = (next: T[keyof T]) => {
console.log('KAAS', { next })
ctx.mutate(row, column, next);
};
@ -87,11 +82,9 @@ export function Grid<T extends Record<string, any>>(props: GridProps<T>) {
return <GridContext.Provider value={ctx}>
<Api api={props.api} table={table()} />
<form style="all: inherit; display: contents;">
<Table api={setTable} class={`${css.grid} ${props.class}`} rows={data()} columns={columns()} selectionMode={SelectionMode.Multiple}>{
cellEditors()
}</Table>
</form>
<Table api={setTable} class={`${css.grid} ${props.class}`} rows={rows()} columns={columns()} selectionMode={SelectionMode.Multiple}>{
cellEditors()
}</Table>
</GridContext.Provider>;
};
@ -114,8 +107,8 @@ function Api<T extends Record<string, any>>(props: { api: undefined | ((api: Gri
insert(row: T, at?: number) {
gridContext.insert(row, at);
},
addColumn(column: keyof T, value: T[keyof T]): void {
gridContext.addColumn(column, value);
addColumn(column: keyof T): void {
// gridContext.addColumn(column, value);
},
};
});

View file

@ -1,4 +1,4 @@
export type { DataSetRowNode, DataSetGroupNode, DataSetNode, SelectionMode, SortingFunction, SortOptions, GroupingFunction, GroupOptions } from '../table';
export type { GridApi, Column } from './grid';
export type { GridApi, Column, CellEditor } from './grid';
export { Grid } from './grid';

View file

@ -1,4 +1,4 @@
import { createEffect, createSignal, createUniqueId, JSX, onMount, ParentComponent, Show } from "solid-js";
import { createEffect, createSignal, JSX, ParentComponent, Show } from "solid-js";
import css from './prompt.module.css';
export interface PromptApi {
@ -72,4 +72,7 @@ export const Prompt: ParentComponent<{ api: (api: PromptApi) => any, title?: str
</footer>
</form>
</dialog>;
};
};
let idCounter = 0;
const createUniqueId = () => `prompt-${idCounter++}`;

View file

@ -1,6 +1,6 @@
import { Accessor, createMemo } from "solid-js";
import { createStore, NotWrappable, StoreSetter } from "solid-js/store";
import { snapshot } from "vinxi/dist/types/runtime/storage";
import { Accessor, createEffect, createMemo } from "solid-js";
import { createStore, NotWrappable, StoreSetter, unwrap } from "solid-js/store";
import { CustomPartial } from "solid-js/store/types/store.js";
import { deepCopy, deepDiff, Mutation } from "~/utilities";
@ -17,54 +17,63 @@ export interface SortOptions<T extends Record<string, any>> {
with?: SortingFunction<T>;
}
export interface GroupingFunction<T> {
(nodes: DataSetRowNode<keyof T, T>[]): DataSetNode<keyof T, T>[];
export interface GroupingFunction<K, T> {
(nodes: DataSetRowNode<K, T>[]): DataSetNode<K, T>[];
}
export interface GroupOptions<T extends Record<string, any>> {
by: keyof T;
with: GroupingFunction<T>;
with?: GroupingFunction<number, T>;
}
interface DataSetState<T extends Record<string, any>> {
value: DataSetRowNode<keyof T, T>[];
snapshot: DataSetRowNode<keyof T, T>[];
value: (T | undefined)[];
snapshot: (T | undefined)[];
sorting?: SortOptions<T>;
grouping?: GroupOptions<T>;
}
export type Setter<T> =
| T
| CustomPartial<T>
| ((prevState: T) => T | CustomPartial<T>);
export interface DataSet<T extends Record<string, any>> {
data: T[];
value: Accessor<DataSetNode<keyof T, T>[]>;
mutations: Accessor<Mutation[]>;
sort: Accessor<SortOptions<T> | undefined>;
sorting: Accessor<SortOptions<T> | undefined>;
grouping: Accessor<GroupOptions<T> | undefined>;
// mutate<K extends keyof T>(index: number, value: T): void;
mutate<K extends keyof T>(index: number, prop: K, value: T[K]): void;
remove(indices: number[]): void;
insert(item: T, at?: number): void;
setSorting(options: SortOptions<T> | undefined): void;
setGrouping(options: GroupOptions<T> | undefined): void;
sort(options: Setter<SortOptions<T> | undefined>): DataSet<T>;
group(options: Setter<GroupOptions<T> | undefined>): DataSet<T>;
}
const defaultComparer = <T>(a: T, b: T) => a < b ? -1 : a > b ? 1 : 0;
function defaultGroupingFunction<T>(groupBy: keyof T): GroupingFunction<T> {
return (nodes: DataSetRowNode<keyof T, T>[]): DataSetNode<keyof T, T>[] => Object.entries(Object.groupBy(nodes, r => r.value[groupBy] as PropertyKey))
.map(([key, nodes]) => ({ kind: 'group', key, groupedBy: groupBy, nodes: nodes! } as DataSetGroupNode<keyof T, T>));
function defaultGroupingFunction<T>(groupBy: keyof T): GroupingFunction<number, T> {
return <K>(nodes: DataSetRowNode<K, T>[]): DataSetNode<K, T>[] => Object.entries(Object.groupBy(nodes, r => r.value[groupBy] as PropertyKey))
.map(([key, nodes]) => ({ kind: 'group', key, groupedBy: groupBy, nodes: nodes! } as DataSetGroupNode<K, T>));
}
export const createDataSet = <T extends Record<string, any>>(data: T[]): DataSet<T> => {
const nodes = data.map<DataSetRowNode<keyof T, T>>((value, key) => ({ kind: 'row', key: key as keyof T, value }));
export const createDataSet = <T extends Record<string, any>>(data: T[], initialOptions?: { sort?: SortOptions<T>, group?: GroupOptions<T> }): DataSet<T> => {
const nodes = data;
const [state, setState] = createStore<DataSetState<T>>({
value: deepCopy(nodes),
snapshot: nodes,
sorting: undefined,
grouping: undefined,
sorting: initialOptions?.sort,
grouping: initialOptions?.group,
});
const value = createMemo(() => {
const sorting = state.sorting;
const grouping = state.grouping;
let value = state.value as DataSetNode<keyof T, T>[];
let value: DataSetNode<number, T>[] = state.value
.map<DataSetRowNode<number, T> | undefined>((value, key) => value === undefined ? undefined : ({ kind: 'row', key, value }))
.filter(node => node !== undefined);
if (sorting) {
const comparer = sorting.with ?? defaultComparer;
@ -79,37 +88,57 @@ export const createDataSet = <T extends Record<string, any>>(data: T[]): DataSet
if (grouping) {
const implementation = grouping.with ?? defaultGroupingFunction(grouping.by);
value = implementation(value as DataSetRowNode<keyof T, T>[]);
value = implementation(value as DataSetRowNode<number, T>[]);
}
return value;
return value as DataSetNode<keyof T, T>[];
});
const mutations = createMemo(() => {
// enumerate all values to make sure the memo is recalculated on any change
Object.values(state.value).map(entry => Object.values(entry));
Object.values(state.value).map(entry => Object.values(entry ?? {}));
return deepDiff(state.snapshot, state.value).toArray();
});
const sort = createMemo(() => state.sorting);
return {
const sorting = createMemo(() => state.sorting);
const grouping = createMemo(() => state.grouping);
const set: DataSet<T> = {
data,
value,
mutations,
sort,
sorting,
grouping,
mutate(index, prop, value) {
console.log({ index, prop, value });
// setState('value', index, 'value', prop as any, value);
setState('value', index, prop as any, value);
},
setSorting(options) {
remove(indices) {
setState('value', value => value.map((item, i) => indices.includes(i) ? undefined : item));
},
insert(item, at) {
if (at === undefined) {
setState('value', state.value.length, item);
} else {
}
},
sort(options) {
setState('sorting', options);
return set;
},
setGrouping(options) {
group(options) {
setState('grouping', options)
return set;
},
};
return set
};

View file

@ -1,5 +1,5 @@
export type { Column, TableApi, CellEditor, CellEditors } from './table';
export type { Column, TableApi, CellRenderer, CellRenderers } from './table';
export type { DataSet, DataSetGroupNode, DataSetRowNode, DataSetNode, SortingFunction, SortOptions, GroupingFunction, GroupOptions } from './dataset';
export { SelectionMode, Table } from './table';
export { createDataSet } from './dataset';

View file

@ -32,7 +32,7 @@
& :is(.cell:first-child, .checkbox + .cell) {
position: sticky;
inset-inline-start: 1px;
padding-inline-start: calc(var(--depth, 0) * 1em + var(--padding-m));
padding-inline-start: calc(var(--depth, 0) * (1em + var(--padding-s)) + var(--padding-m));
z-index: 1;
&::after {
@ -61,7 +61,6 @@
}
& .caption {
/* grid-column: 1 / -1; */
position: sticky;
inset-inline-start: 0;
}
@ -148,40 +147,58 @@
font-weight: var(--text-bold);
}
& details {
& .group {
display: contents;
background-color: inherit;
&::details-content {
grid-column: 1 / -1;
display: block grid;
grid-template-columns: subgrid;
& > td {
display: contents;
background-color: inherit;
}
&:not([open])::details-content {
display: none;
}
& > table {
grid-column: 1 / -1;
grid-template-columns: subgrid;
background-color: inherit;
overflow: visible;
& > summary {
position: sticky;
inset-inline-start: 1px;
grid-column: 1;
padding: var(--padding-m);
padding-inline-start: calc(var(--depth) * 1em + var(--padding-m));
& > .header {
border-block-end-color: transparent;
&::after {
content: '';
position: absolute;
inset-inline-start: calc(100% - 1px);
inset-block-start: -.5px;
display: block;
inline-size: 2em;
block-size: 100%;
animation: column-scroll-shadow linear both;
animation-timeline: scroll(inline);
animation-range: 0 2em;
pointer-events: none;
& .cell {
justify-content: start;
column-gap: var(--padding-s);
& > label {
--state: 0;
display: contents;
& input[type="checkbox"] {
display: none;
}
& > svg {
rotate: calc(var(--state) * -.25turn);
transition: rotate .3s ease-in-out;
inline-size: 1em;
aspect-ratio: 1;
}
&:has(input:not(:checked)) {
--state: 1;
}
}
}
}
& > .main {
block-size: calc-size(auto, size);
transition: block-size .3s ease-in-out;
overflow: clip;
}
&:has(> .header input:not(:checked)) > .main {
block-size: 0;
}
}
}
}

View file

@ -1,7 +1,7 @@
import { Accessor, createContext, createEffect, createMemo, createSignal, For, JSX, Match, Show, Switch, useContext } from "solid-js";
import { selectable, SelectionItem, SelectionProvider, useSelection } from "~/features/selectable";
import { DataSetRowNode, DataSetNode, DataSet } from './dataset';
import { FaSolidSort, FaSolidSortDown, FaSolidSortUp } from "solid-icons/fa";
import { FaSolidAngleDown, FaSolidSort, FaSolidSortDown, FaSolidSortUp } from "solid-icons/fa";
import css from './table.module.css';
selectable;
@ -14,8 +14,8 @@ export type Column<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 CellRenderer<T extends Record<string, any>, K extends keyof T> = (cell: { row: number, column: K, value: T[K] }) => JSX.Element;
export type CellRenderers<T extends Record<string, any>> = { [K in keyof T]?: CellRenderer<T, K> };
export interface TableApi<T extends Record<string, any>> {
readonly selection: Accessor<SelectionItem<keyof T, T>[]>;
@ -30,7 +30,7 @@ interface TableContextType<T extends Record<string, any>> {
readonly columns: Accessor<Column<T>[]>,
readonly selection: Accessor<SelectionItem<keyof T, T>[]>,
readonly selectionMode: Accessor<SelectionMode>,
readonly cellRenderers: Accessor<CellEditors<T>>,
readonly cellRenderers: Accessor<CellRenderers<T>>,
}
const TableContext = createContext<TableContextType<any>>();
@ -48,7 +48,7 @@ type TableProps<T extends Record<string, any>> = {
rows: DataSet<T>,
columns: Column<T>[],
selectionMode?: SelectionMode,
children?: CellEditors<T>,
children?: CellRenderers<T>,
api?: (api: TableApi<T>) => any,
};
@ -58,7 +58,7 @@ export function Table<T extends Record<string, any>>(props: TableProps<T>) {
const rows = createMemo(() => props.rows);
const columns = createMemo<Column<T>[]>(() => props.columns ?? []);
const selectionMode = createMemo(() => props.selectionMode ?? SelectionMode.None);
const cellRenderers = createMemo<CellEditors<T>>(() => props.children ?? {});
const cellRenderers = createMemo<CellRenderers<T>>(() => props.children ?? {});
const context: TableContextType<T> = {
rows,
@ -86,11 +86,11 @@ function InnerTable<T extends Record<string, any>>(props: InnerTableProps<T>) {
const columnCount = createMemo(() => table.columns().length);
return <table class={`${css.table} ${selectable() ? css.selectable : ''} ${props.class}`} style={{ '--columns': columnCount() }}>
<Show when={(props.summary?.length ?? 0) > 0 ? props.summary : undefined}>{
{/* <Show when={(props.summary?.length ?? 0) > 0 ? props.summary : undefined}>{
summary => {
return <caption class={css.caption}>{summary()}</caption>;
}
}</Show>
}</Show> */}
<Groups />
<Head />
@ -165,7 +165,7 @@ function Head(props: {}) {
<For each={table.columns()}>{
({ id, label, sortable }) => {
const sort = createMemo(() => table.rows().sort());
const sort = createMemo(() => table.rows().sorting());
const by = String(id);
const onPointerDown = (e: PointerEvent) => {
@ -173,27 +173,27 @@ function Head(props: {}) {
return;
}
// table.setSort(current => {
// if (current?.by !== by) {
// return { by, reversed: false };
// }
table.rows().sort(current => {
if (current?.by !== by) {
return { by, reversed: false };
}
// if (current.reversed === true) {
// return undefined;
// }
if (current.reversed === true) {
return undefined;
}
// return { by, reversed: true };
// });
return { by, reversed: true };
});
};
return <th scope="col" class={`${css.cell} ${sort()?.by === by ? css.sorted : ''}`} onpointerdown={onPointerDown}>
{label}
{/* <Switch>
<Switch>
<Match when={sortable && sort()?.by !== by}><FaSolidSort /></Match>
<Match when={sortable && sort()?.by === by && sort()?.reversed !== true}><FaSolidSortUp /></Match>
<Match when={sortable && sort()?.by === by && sort()?.reversed === true}><FaSolidSortDown /></Match>
</Switch> */}
</Switch>
</th>;
}
}</For>
@ -239,13 +239,29 @@ function Row<T extends Record<string, any>>(props: { key: keyof T, value: T, dep
};
function Group<T extends Record<string, any>>(props: { key: keyof T, groupedBy: keyof T, nodes: DataSetNode<keyof T, T>[], depth: number }) {
return <details open>
<summary style={{ '--depth': props.depth }}>{String(props.key)}</summary>
const table = useTable();
<For each={props.nodes}>{
node => <Node node={node} depth={props.depth + 1} groupedBy={props.groupedBy} />
}</For>
</details>;
return <tr class={css.group}>
<td colSpan={table.columns().length}>
<table class={css.table}>
<thead class={css.header}>
<tr><th class={css.cell} colSpan={table.columns().length} style={{ '--depth': props.depth }}>
<label>
<input type="checkbox" checked name="collapse" />
<FaSolidAngleDown />
{String(props.key)}</label>
</th></tr>
</thead>
<tbody class={css.main}>
<For each={props.nodes}>{
node => <Node node={node} depth={props.depth + 1} groupedBy={props.groupedBy} />
}</For>
</tbody>
</table>
</td>
</tr>;
};
declare module "solid-js" {

View file

@ -1,4 +1,4 @@
import { Accessor, Component, For, JSX, Match, ParentComponent, Setter, Show, Switch, children, createContext, createEffect, createMemo, createSignal, createUniqueId, mergeProps, onCleanup, onMount, useContext } from "solid-js";
import { Accessor, Component, For, JSX, Match, ParentComponent, Setter, Show, Switch, children, createContext, createEffect, createMemo, createSignal, mergeProps, useContext } from "solid-js";
import { Portal } from "solid-js/web";
import { createStore } from "solid-js/store";
import { CommandType, Command, useCommands } from "../command";
@ -347,6 +347,9 @@ function SearchableList<T>(props: SearchableListProps<T>): JSX.Element {
</search>;
};
let keyCounter = 0;
const createUniqueId = () => `key-${keyCounter++}`;
declare module "solid-js" {
namespace JSX {
interface HTMLAttributes<T> {

View file

@ -119,9 +119,9 @@ export function SelectionProvider<T extends object>(props: ParentProps<{ selecti
keyIdMap.set(key, id);
idKeyMap.set(id, key);
}
setState('data', state.data.length, { key, value, element: new WeakRef(element) });
setState('data', state.data.length, { key, value, element: new WeakRef(element) });
}
return keyIdMap.get(key)!;
},

View file

@ -1,5 +1,5 @@
import { Link, Meta, Title } from "@solidjs/meta";
import { Component, createMemo, createSignal, createUniqueId, ErrorBoundary, ParentProps, Show } from "solid-js";
import { Component, createMemo, createSignal, ParentProps, Show } from "solid-js";
import { FilesProvider } from "~/features/file";
import { CommandPalette, CommandPaletteApi, Menu, MenuProvider } from "~/features/menu";
import { A, RouteDefinition, useBeforeLeave } from "@solidjs/router";
@ -121,3 +121,6 @@ const ErrorComp: Component<{ error: Error }> = (props) => {
<a href="/">Return to start</a>
</div>;
};
let keyCounter = 0;
const createUniqueId = () => `key-${keyCounter++}`;

View file

@ -21,18 +21,27 @@
flex-flow: column;
gap: var(--padding-m);
}
ol {
margin-block: 0;
}
}
& .content {
display: block grid;
grid: 1fr 1fr / 100%;
background-color: var(--surface-500);
border-top-left-radius: var(--radii-xl);
& > header {
padding-inline-start: var(--padding-l);
}
padding: var(--padding-m);
& .table {
border-radius: inherit;
}
& > fieldset {
border-radius: var(--radii-l);
overflow: auto;
background-color: inherit;
}
}
}

View file

@ -1,12 +1,14 @@
import { Sidebar } from '~/components/sidebar';
import { Column, DataSetGroupNode, DataSetNode, DataSetRowNode, Grid, GridApi } from '~/components/grid';
import { CellEditor, Column, DataSetGroupNode, DataSetNode, DataSetRowNode, Grid, GridApi } from '~/components/grid';
import { people, Person } from './experimental.data';
import { Component, createEffect, createMemo, createSignal, For, Match, Switch } from 'solid-js';
import { Created, debounce, Deleted, MutarionKind, Mutation, Updated } from '~/utilities';
import { debounce, MutarionKind, Mutation } from '~/utilities';
import { createDataSet, Table } from '~/components/table';
import css from './grid.module.css';
export default function GridExperiment() {
const editor: CellEditor<any, any> = ({ value, mutate }) => <input value={value} oninput={debounce(e => mutate(e.target.value.trim()), 300)} />
const columns: Column<Person>[] = [
{
id: 'id',
@ -24,35 +26,37 @@ export default function GridExperiment() {
id: 'name',
label: 'Name',
sortable: true,
editor,
},
{
id: 'email',
label: 'Email',
sortable: true,
editor: ({ value, mutate }) => <input value={value} oninput={debounce(e => {
console.log('WHAAAAT????', e);
return mutate(e.target.value.trim());
}, 100)} />,
editor,
},
{
id: 'address',
label: 'Address',
sortable: true,
editor,
},
{
id: 'currency',
label: 'Currency',
sortable: true,
editor,
},
{
id: 'phone',
label: 'Phone',
sortable: true,
editor,
},
{
id: 'country',
label: 'Country',
sortable: true,
editor,
},
];
@ -60,46 +64,55 @@ export default function GridExperiment() {
const mutations = createMemo(() => api()?.mutations() ?? [])
// createEffect(() => {
// console.log(mutations());
// });
const rows = createDataSet(people.slice(0, 20), {
// group: { by: 'country' },
sort: { by: 'name', reversed: false },
});
return <div class={css.root}>
<Sidebar as="aside" label={'Grid options'} class={css.sidebar}>
<fieldset>
<legend>Commands</legend>
<button onclick={() => api()?.insert({ id: 'some guid', name: 'new person', address: '', country: '', currency: '', email: 'some@email.email', phone: '' })}>add row</button>
<button onclick={() => api()?.insert({ id: crypto.randomUUID(), name: '', address: '', country: '', currency: '', email: '', phone: '' })}>add row</button>
<button onclick={() => api()?.remove(api()?.selection()?.map(i => i.key as any) ?? [])} disabled={api()?.selection().length === 0}>Remove {api()?.selection().length} items</button>
</fieldset>
<fieldset>
<legend>Selection ({api()?.selection().length})</legend>
<pre>{JSON.stringify(api()?.selection().map(i => i.key))}</pre>
</fieldset>
<fieldset>
<legend>Mutations ({mutations().length})</legend>
<Mutations mutations={mutations()} />
<ol>
<For each={api()?.selection()}>{
item => <li value={item.key}>{item.value().name}</li>
}</For>
</ol>
</fieldset>
</Sidebar>
<div class={css.content}>
<Grid api={setApi} rows={people} columns={columns} groupBy="country" />
<Grid class={css.table} api={setApi} rows={rows} columns={columns} groupBy="country" />
<fieldset class={css.mutaions}>
<legend>Mutations ({mutations().length})</legend>
<Mutations mutations={mutations()} />
</fieldset>
</div>
</div >;
}
type M = { kind: MutarionKind, key: string, original?: any, value?: any };
const Mutations: Component<{ mutations: Mutation[] }> = (props) => {
const columns: Column<M>[] = [{ id: 'key', label: 'Key' }, { id: 'original', label: 'original' }, { id: 'value', label: 'Value' }];
const columns: Column<M>[] = [{ id: 'key', label: 'Key' }, { id: 'original', label: 'Old' }, { id: 'value', label: 'New' }];
const rows = createMemo(() => createDataSet<M>(props.mutations));
return <Table rows={rows()} columns={columns} groupBy='kind'>{{
original: ({ value }) => <del>{value}</del>,
value: ({ value }) => <ins>{value}</ins>,
createEffect(() => {
rows().group({ by: 'kind' });
});
return <Table rows={rows()} columns={columns}>{{
original: ({ value }) => value ? <del><pre>{JSON.stringify(value, null, 2)}</pre></del> : null,
value: ({ value }) => value ? <ins><pre>{JSON.stringify(value, null, 2)}</pre></ins> : null,
}}</Table>
};

View file

@ -3,9 +3,8 @@ import { Column, createDataSet, DataSetGroupNode, DataSetNode, DataSetRowNode, G
import { createStore } from 'solid-js/store';
import { Person, people } from './experimental.data';
import { createEffect, createMemo, For } from 'solid-js';
import css from './table.module.css';
import { Menu } from '~/features/menu';
import { Command, createCommand, Modifier } from '~/features/command';
import css from './table.module.css';
export default function TableExperiment() {
const columns: Column<Person>[] = [
@ -53,20 +52,31 @@ export default function TableExperiment() {
},
];
const [store, setStore] = createStore<{ selectionMode: SelectionMode, group?: GroupOptions<Person>, sort?: SortOptions<Person> }>({
const [store, setStore] = createStore<{ selectionMode: SelectionMode, grouping?: GroupOptions<Person>, sorting?: SortOptions<Person> }>({
selectionMode: SelectionMode.None,
group: undefined,
sort: undefined,
grouping: { by: 'country' },
sorting: { by: 'country', reversed: false },
});
const rows = createMemo(() => createDataSet(people));
const rows = createMemo(() => createDataSet(people, {
group: { by: 'country' },
sort: { by: 'country', reversed: false },
}));
createEffect(() => {
rows().setGrouping(store.group);
rows().group(store.grouping);
});
createEffect(() => {
rows().setSorting(store.sort);
rows().sort(store.sorting);
});
createEffect(() => {
setStore('sorting', rows().sorting());
});
createEffect(() => {
setStore('grouping', rows().grouping());
});
return <div class={css.root}>
@ -93,7 +103,7 @@ export default function TableExperiment() {
<label>
Group by
<select value={store.group?.by ?? ''} oninput={e => setStore('group', 'by', (e.target.value || undefined) as any)}>
<select value={store.grouping?.by ?? ''} oninput={e => setStore('grouping', e.target.value ? { by: e.target.value as keyof Person } : undefined)}>
<option value=''>None</option>
<For each={columns}>{
column => <option value={column.id}>{column.label}</option>
@ -108,7 +118,7 @@ export default function TableExperiment() {
<label>
by
<select value={store.sort?.by ?? ''} oninput={e => setStore('sort', prev => e.target.value ? { by: e.target.value as keyof Person, reversed: prev?.reversed } : undefined)}>
<select value={store.sorting?.by ?? ''} oninput={e => setStore('sorting', prev => e.target.value ? { by: e.target.value as keyof Person, reversed: prev?.reversed } : undefined)}>
<option value=''>None</option>
<For each={columns}>{
column => <option value={column.id}>{column.label}</option>
@ -119,7 +129,7 @@ export default function TableExperiment() {
<label>
reversed
<input type="checkbox" checked={store.sort?.reversed ?? false} oninput={e => setStore('sort', prev => prev !== undefined ? { by: prev.by, reversed: e.target.checked || undefined } : undefined)} />
<input type="checkbox" checked={store.sorting?.reversed ?? false} oninput={e => setStore('sorting', prev => prev !== undefined ? { by: prev.by, reversed: e.target.checked || undefined } : undefined)} />
</label>
</fieldset>
</Sidebar>

View file

@ -181,7 +181,7 @@ const bufferredIterator = <T extends readonly [string | number, any]>(subject: I
done = res.done ?? false;
if (!done) {
buffer.push(res.value)
buffer.push(res.value);
}
};