couple of bug fixes

This commit is contained in:
Chris Kruining 2024-12-19 15:54:11 +01:00
parent f6af76f0ba
commit c33e99b105
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
5 changed files with 53 additions and 45 deletions

View file

@ -2,11 +2,13 @@ import { Accessor, Component, createEffect, createMemo, createSignal } from "sol
import { debounce, Mutation } from "~/utilities";
import { Column, GridApi as GridCompApi, Grid as GridComp } from "~/components/grid";
import { createDataSet, DataSetNode, DataSetRowNode } from "~/components/table";
import { SelectionItem } from "../selectable";
import css from "./grid.module.css"
export type Entry = { key: string } & { [lang: string]: string };
export interface GridApi {
readonly mutations: Accessor<Mutation[]>;
readonly selection: Accessor<SelectionItem<number, Entry>[]>;
remove(indices: number[]): void;
addKey(key: string): void;
addLocale(locale: string): void;
@ -25,9 +27,9 @@ const groupBy = (rows: DataSetRowNode<number, Entry>[]) => {
return group(rows.map<R>(r => ({ ...r, _key: r.value.key }))) as any;
}
export function Grid(props: { class?: string, rows: Entry[], api?: (api: GridApi) => any }) {
export function Grid(props: { class?: string, rows: Entry[], locales: string[], api?: (api: GridApi) => any }) {
const rows = createMemo(() => createDataSet<Entry>(props.rows, { group: { by: 'key', with: groupBy } }));
const locales = createMemo(() => Object.keys(rows().value().at(0) ?? {}).filter(k => k !== 'key'));
const locales = createMemo(() => props.locales);
const columns = createMemo<Column<Entry>[]>(() => [
{
id: 'key',
@ -45,11 +47,14 @@ export function Grid(props: { class?: string, rows: Entry[], api?: (api: GridApi
}))
]);
const [api, setApi] = createSignal<GridCompApi<Entry>>();
createEffect(() => {
const r = rows();
props.api?.({
mutations: r.mutations,
selection: createMemo(() => api()?.selection() ?? []),
remove: r.remove,
addKey(key) {
r.insert({ key, ...Object.fromEntries(locales().map(l => [l, ''])) });
@ -60,7 +65,7 @@ export function Grid(props: { class?: string, rows: Entry[], api?: (api: GridApi
});
});
return <GridComp rows={rows()} columns={columns()} />;
return <GridComp rows={rows()} columns={columns()} api={setApi} />;
};
const TextArea: Component<{ row: number, key: string, lang: string, value: string, oninput?: (event: InputEvent) => any }> = (props) => {

View file

@ -22,48 +22,48 @@ export interface SelectionItem<K, T> {
element: WeakRef<HTMLElement>;
};
export interface SelectionContextType<T extends object> {
readonly selection: Accessor<SelectionItem<keyof T, T>[]>;
export interface SelectionContextType<K, T extends object> {
readonly selection: Accessor<SelectionItem<K, T>[]>;
readonly length: Accessor<number>;
select(selection: (keyof T)[], options?: Partial<{ mode: SelectionMode }>): void;
select(selection: K[], options?: Partial<{ mode: SelectionMode }>): void;
selectAll(): void;
clear(): void;
isSelected(key: keyof T): Accessor<boolean>;
isSelected(key: K): Accessor<boolean>;
}
interface InternalSelectionContextType<T extends object> {
interface InternalSelectionContextType<K, T extends object> {
readonly latest: Signal<HTMLElement | undefined>,
readonly modifier: Signal<Modifier>,
readonly selectables: Signal<HTMLElement[]>,
readonly keyMap: Map<string, keyof T>,
add(key: keyof T, value: Accessor<T>, element: HTMLElement): string;
readonly keyMap: Map<string, K>,
add(key: K, value: Accessor<T>, element: HTMLElement): string;
}
export interface SelectionHandler<T extends object> {
(selection: T[]): any;
}
const SelectionContext = createContext<SelectionContextType<any>>();
const InternalSelectionContext = createContext<InternalSelectionContextType<any>>();
const SelectionContext = createContext<SelectionContextType<any, any>>();
const InternalSelectionContext = createContext<InternalSelectionContextType<any, any>>();
export function useSelection<T extends object = object>(): SelectionContextType<T> {
export function useSelection<K, T extends object = object>() {
const context = useContext(SelectionContext);
if (context === undefined) {
throw new Error('selection context is used outside of a provider');
}
return context as SelectionContextType<T>;
return context as SelectionContextType<K, T>;
};
function useInternalSelection<T extends object>() {
return useContext(InternalSelectionContext)! as InternalSelectionContextType<T>;
function useInternalSelection<K, T extends object>() {
return useContext(InternalSelectionContext)! as InternalSelectionContextType<K, T>;
}
interface State<T extends object> {
selection: (keyof T)[];
data: SelectionItem<keyof T, T>[];
interface State<K, T extends object> {
selection: K[];
data: SelectionItem<K, T>[];
}
export function SelectionProvider<T extends object>(props: ParentProps<{ selection?: SelectionHandler<T>, multiSelect?: boolean }>) {
const [state, setState] = createStore<State<T>>({ selection: [], data: [] });
export function SelectionProvider<K, T extends object>(props: ParentProps<{ selection?: SelectionHandler<T>, multiSelect?: boolean }>) {
const [state, setState] = createStore<State<K, T>>({ selection: [], data: [] });
const selection = createMemo(() => state.data.filter(({ key }) => state.selection.includes(key)));
const length = createMemo(() => state.data.length);
@ -71,7 +71,7 @@ export function SelectionProvider<T extends object>(props: ParentProps<{ selecti
props.selection?.(selection().map(({ value }) => value()));
});
const context: SelectionContextType<T> = {
const context: SelectionContextType<K, T> = {
selection,
length,
select(selection, { mode = SelectionMode.Normal } = {}) {
@ -106,9 +106,9 @@ export function SelectionProvider<T extends object>(props: ParentProps<{ selecti
},
};
const keyIdMap = new Map<keyof T, string>();
const idKeyMap = new Map<string, keyof T>();
const internal: InternalSelectionContextType<T> = {
const keyIdMap = new Map<K, string>();
const idKeyMap = new Map<string, K>();
const internal: InternalSelectionContextType<K, T> = {
modifier: createSignal<Modifier>(Modifier.None),
latest: createSignal<HTMLElement>(),
selectables: createSignal<HTMLElement[]>([]),
@ -201,9 +201,9 @@ const Root: ParentComponent = (props) => {
return <div ref={setRoot} tabIndex={0} onKeyDown={onKeyboardEvent} onKeyUp={onKeyboardEvent} class={css.root}>{c()}</div>;
};
export function selectable<T extends object>(element: HTMLElement, options: Accessor<{ value: T, key: keyof T }>) {
const context = useSelection<T>();
const internal = useInternalSelection<T>();
export function selectable<K, T extends object>(element: HTMLElement, options: Accessor<{ value: T, key: K }>) {
const context = useSelection<K, T>();
const internal = useInternalSelection<K, T>();
const key = options().key;
const value = createMemo(() => options().value);
@ -211,17 +211,17 @@ export function selectable<T extends object>(element: HTMLElement, options: Acce
const selectionKey = internal.add(key, value, element);
const createRange = (a?: HTMLElement, b?: HTMLElement): (keyof T)[] => {
const createRange = (a?: HTMLElement, b?: HTMLElement): K[] => {
if (!a && !b) {
return [];
}
if (!a) {
return [b!.dataset.selecatableKey! as keyof T];
return [b!.dataset.selecatableKey! as K];
}
if (!b) {
return [a!.dataset.selecatableKey! as keyof T];
return [a!.dataset.selecatableKey! as K];
}
if (a === b) {