made a start on data grid implementation
This commit is contained in:
parent
a543259fe4
commit
70c15c4094
12 changed files with 1083 additions and 210 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue