made a start on data grid implementation

This commit is contained in:
Chris Kruining 2024-10-03 15:33:25 +02:00
parent a543259fe4
commit 70c15c4094
12 changed files with 1083 additions and 210 deletions

View file

@ -3,11 +3,11 @@ import { Portal, isServer } from "solid-js/web";
import { createStore } from "solid-js/store";
export interface MenuContextType {
ref: Accessor<JSX.Element|undefined>;
setRef: Setter<JSX.Element|undefined>;
ref: Accessor<JSX.Element | undefined>;
setRef: Setter<JSX.Element | undefined>;
addItems(items: (Item|ItemWithChildren)[]): void;
items: Accessor<(Item|ItemWithChildren)[]>;
addItems(items: (Item | ItemWithChildren)[]): void;
items: Accessor<(Item | ItemWithChildren)[]>;
commands(): Command[];
};
@ -41,20 +41,20 @@ export interface ItemWithChildren {
const MenuContext = createContext<MenuContextType>();
export const createCommand = (command: () => any, shortcut?: Command['shortcut']): Command => {
if(shortcut) {
if (shortcut) {
(command as Command).shortcut = { key: shortcut.key.toLowerCase(), modifier: shortcut.modifier };
}
return command;
};
export const MenuProvider: ParentComponent = (props) => {
const [ ref, setRef ] = createSignal<JSX.Element|undefined>();
const [ _items, setItems ] = createSignal<Map<string, Item&{ children?: Map<string, Item> }>>(new Map());
const [ref, setRef] = createSignal<JSX.Element | undefined>();
const [_items, setItems] = createSignal<Map<string, Item & { children?: Map<string, Item> }>>(new Map());
const [ store, setStore ] = createStore<{ items: Record<string, Item|ItemWithChildren> }>({ items: {} });
const [store, setStore] = createStore<{ items: Record<string, Item | ItemWithChildren> }>({ items: {} });
const addItems = (items: (Item|ItemWithChildren)[]) => setStore('items', values => {
const addItems = (items: (Item | ItemWithChildren)[]) => setStore('items', values => {
for (const item of items) {
values[item.id] = item;
}
@ -67,22 +67,22 @@ export const MenuProvider: ParentComponent = (props) => {
return <MenuContext.Provider value={{ ref, setRef, addItems, items, commands }}>{props.children}</MenuContext.Provider>;
}
const useMenu = () => {
const useMenu = () => {
const context = useContext(MenuContext);
if(context === undefined) {
if (context === undefined) {
throw new Error(`MenuContext is called outside of a <MenuProvider />`);
}
return context;
}
type ItemProps = { label: string, children: JSX.Element }|{ label: string, command: Command };
type ItemProps = { label: string, children: JSX.Element } | { label: string, command: Command };
const Item: Component<ItemProps> = (props) => {
const id = createUniqueId();
if(props.command) {
if (props.command) {
return mergeProps(props, { id }) as unknown as JSX.Element;
}
@ -98,8 +98,8 @@ const Item: Component<ItemProps> = (props) => {
const Root: ParentComponent<{}> = (props) => {
const menu = useMenu();
const [ current, setCurrent ] = createSignal<HTMLElement>();
const items = (isServer
const [current, setCurrent] = createSignal<HTMLElement>();
const items = (isServer
? props.children
: props.children?.map(c => c())) ?? [];
@ -108,7 +108,7 @@ const Root: ParentComponent<{}> = (props) => {
const close = () => {
const el = current();
if(el) {
if (el) {
el.hidePopover();
setCurrent(undefined);
@ -123,55 +123,51 @@ const Root: ParentComponent<{}> = (props) => {
}
};
const Button: Component<{ label: string, command: Command }|{ [key: string]: any }> = (props) => {
const [ local, rest ] = splitProps(props, ['label', 'command']);
const Button: Component<{ label: string, command: Command } | { [key: string]: any }> = (props) => {
const [local, rest] = splitProps(props, ['label', 'command']);
return <button class="menu-item" type="button" on:pointerdown={onExecute(local.command)} {...rest}>{local.label}</button>;
};
return <Portal mount={menu.ref()}>
<For each={items}>{
item => {
const [] = createSignal();
return <>
<Show when={item.children}>
<div
class="menu-child"
id={`child-${item.id}`}
style={`position-anchor: --menu-${item.id};`}
popover
on:toggle={(e: ToggleEvent) => {
if(e.newState === 'open' && e.target !== null) {
return setCurrent(e.target as HTMLElement);
}
}}
>
<For each={item.children}>
{(child) => <Button label={child.label} command={child.command} />}
</For>
</div>
</Show>
<Button
label={item.label}
on:pointerenter={(e) => {
if(!item.children) {
return;
item => <>
<Show when={item.children}>
<div
class="menu-child"
id={`child-${item.id}`}
style={`position-anchor: --menu-${item.id};`}
popover
on:toggle={(e: ToggleEvent) => {
if (e.newState === 'open' && e.target !== null) {
return setCurrent(e.target as HTMLElement);
}
const el = current();
if(!el){
return;
}
el.hidePopover();
}}
{...(item.children ? { popovertarget: `child-${item.id}`, style: `anchor-name: --menu-${item.id};`, command: item.command } : {})}
/>
</>;
}
>
<For each={item.children}>
{(child) => <Button label={child.label} command={child.command} />}
</For>
</div>
</Show>
<Button
label={item.label}
on:pointerenter={(e) => {
if (!item.children) {
return;
}
const el = current();
if (!el) {
return;
}
el.hidePopover();
}}
{...(item.children ? { popovertarget: `child-${item.id}`, style: `anchor-name: --menu-${item.id};` } : { command: item.command })}
/>
</>
}</For>
</Portal>
};
@ -179,7 +175,7 @@ const Root: ParentComponent<{}> = (props) => {
declare module "solid-js" {
namespace JSX {
interface HTMLAttributes<T> {
anchor?: string|undefined;
anchor?: string | undefined;
}
interface Directives {
@ -190,20 +186,20 @@ declare module "solid-js" {
export const asMenuRoot = (element: Element) => {
const menu = useMenu();
const c = 'menu-root';
const listener = (e: KeyboardEvent) => {
const key = e.key.toLowerCase();
const modifiers =
const modifiers =
(e.shiftKey ? 1 : 0) << 0 |
(e.ctrlKey ? 1 : 0) << 1 |
(e.metaKey ? 1 : 0) << 2 |
(e.altKey ? 1 : 0) << 3 ;
(e.altKey ? 1 : 0) << 3;
const commands = menu.commands();
const command = commands.find(c => c.shortcut?.key === key && (c.shortcut.modifier === undefined || c.shortcut.modifier === modifiers));
if(command === undefined) {
if (command === undefined) {
return;
}