mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-26 11:21:16 +01:00
Run oxfmt across repo, add format script and docs
Add .oxfmtignore to skip generated bindings and wasm-pack output. Add npm format script, update DEVELOPMENT.md for Vite+ toolchain, and format all non-generated files with oxfmt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { DragEndEvent, DragMoveEvent, DragStartEvent } from '@dnd-kit/core';
|
||||
import type { DragEndEvent, DragMoveEvent, DragStartEvent } from "@dnd-kit/core";
|
||||
import {
|
||||
DndContext,
|
||||
MeasuringStrategy,
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
useDroppable,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
import classNames from 'classnames';
|
||||
import type { ComponentType, MouseEvent, ReactElement, Ref, RefAttributes } from 'react';
|
||||
} from "@dnd-kit/core";
|
||||
import { type } from "@tauri-apps/plugin-os";
|
||||
import classNames from "classnames";
|
||||
import type { ComponentType, MouseEvent, ReactElement, Ref, RefAttributes } from "react";
|
||||
import {
|
||||
forwardRef,
|
||||
memo,
|
||||
@@ -20,14 +20,14 @@ import {
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useKey, useKeyPressEvent } from 'react-use';
|
||||
import type { HotKeyOptions, HotkeyAction } from '../../../hooks/useHotKey';
|
||||
import { useHotKey } from '../../../hooks/useHotKey';
|
||||
import { computeSideForDragMove } from '../../../lib/dnd';
|
||||
import { jotaiStore } from '../../../lib/jotai';
|
||||
import type { ContextMenuProps, DropdownItem } from '../Dropdown';
|
||||
import { ContextMenu } from '../Dropdown';
|
||||
} from "react";
|
||||
import { useKey, useKeyPressEvent } from "react-use";
|
||||
import type { HotKeyOptions, HotkeyAction } from "../../../hooks/useHotKey";
|
||||
import { useHotKey } from "../../../hooks/useHotKey";
|
||||
import { computeSideForDragMove } from "../../../lib/dnd";
|
||||
import { jotaiStore } from "../../../lib/jotai";
|
||||
import type { ContextMenuProps, DropdownItem } from "../Dropdown";
|
||||
import { ContextMenu } from "../Dropdown";
|
||||
import {
|
||||
collapsedFamily,
|
||||
draggingIdsFamily,
|
||||
@@ -35,14 +35,14 @@ import {
|
||||
hoveredParentFamily,
|
||||
isCollapsedFamily,
|
||||
selectedIdsFamily,
|
||||
} from './atoms';
|
||||
import type { SelectableTreeNode, TreeNode } from './common';
|
||||
import { closestVisibleNode, equalSubtree, getSelectedItems, hasAncestor } from './common';
|
||||
import { TreeDragOverlay } from './TreeDragOverlay';
|
||||
import type { TreeItemClickEvent, TreeItemHandle, TreeItemProps } from './TreeItem';
|
||||
import type { TreeItemListProps } from './TreeItemList';
|
||||
import { TreeItemList } from './TreeItemList';
|
||||
import { useSelectableItems } from './useSelectableItems';
|
||||
} from "./atoms";
|
||||
import type { SelectableTreeNode, TreeNode } from "./common";
|
||||
import { closestVisibleNode, equalSubtree, getSelectedItems, hasAncestor } from "./common";
|
||||
import { TreeDragOverlay } from "./TreeDragOverlay";
|
||||
import type { TreeItemClickEvent, TreeItemHandle, TreeItemProps } from "./TreeItem";
|
||||
import type { TreeItemListProps } from "./TreeItemList";
|
||||
import { TreeItemList } from "./TreeItemList";
|
||||
import { useSelectableItems } from "./useSelectableItems";
|
||||
|
||||
/** So we re-calculate after expanding a folder during drag */
|
||||
const measuring = { droppable: { strategy: MeasuringStrategy.Always } };
|
||||
@@ -51,7 +51,7 @@ export interface TreeProps<T extends { id: string }> {
|
||||
root: TreeNode<T>;
|
||||
treeId: string;
|
||||
getItemKey: (item: T) => string;
|
||||
getContextMenu?: (items: T[]) => ContextMenuProps['items'] | Promise<ContextMenuProps['items']>;
|
||||
getContextMenu?: (items: T[]) => ContextMenuProps["items"] | Promise<ContextMenuProps["items"]>;
|
||||
ItemInner: ComponentType<{ treeId: string; item: T }>;
|
||||
ItemLeftSlotInner?: ComponentType<{ treeId: string; item: T }>;
|
||||
ItemRightSlot?: ComponentType<{ treeId: string; item: T }>;
|
||||
@@ -140,7 +140,7 @@ function TreeInner<T extends { id: string }>(
|
||||
return false;
|
||||
}
|
||||
$el.focus();
|
||||
$el.scrollIntoView({ block: 'nearest' });
|
||||
$el.scrollIntoView({ block: "nearest" });
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
@@ -241,7 +241,7 @@ function TreeInner<T extends { id: string }>(
|
||||
};
|
||||
}, [getContextMenu, selectableItems, setSelected, treeId]);
|
||||
|
||||
const handleSelect = useCallback<NonNullable<TreeItemProps<T>['onClick']>>(
|
||||
const handleSelect = useCallback<NonNullable<TreeItemProps<T>["onClick"]>>(
|
||||
(item, { shiftKey, metaKey, ctrlKey }) => {
|
||||
const anchorSelectedId = jotaiStore.get(focusIdsFamily(treeId)).anchorId;
|
||||
const selectedIdsAtom = selectedIdsFamily(treeId);
|
||||
@@ -281,7 +281,7 @@ function TreeInner<T extends { id: string }>(
|
||||
} else {
|
||||
setSelected([item.id], true);
|
||||
}
|
||||
} else if (type() === 'macos' ? metaKey : ctrlKey) {
|
||||
} else if (type() === "macos" ? metaKey : ctrlKey) {
|
||||
const withoutCurr = selectedIds.filter((id) => id !== item.id);
|
||||
if (withoutCurr.length === selectedIds.length) {
|
||||
// It wasn't in there, so add it
|
||||
@@ -299,7 +299,7 @@ function TreeInner<T extends { id: string }>(
|
||||
[selectableItems, setSelected, treeId],
|
||||
);
|
||||
|
||||
const handleClick = useCallback<NonNullable<TreeItemProps<T>['onClick']>>(
|
||||
const handleClick = useCallback<NonNullable<TreeItemProps<T>["onClick"]>>(
|
||||
(item, e) => {
|
||||
if (e.shiftKey || e.ctrlKey || e.metaKey) {
|
||||
handleSelect(item, e);
|
||||
@@ -350,7 +350,7 @@ function TreeInner<T extends { id: string }>(
|
||||
);
|
||||
|
||||
useKey(
|
||||
(e) => e.key === 'ArrowUp' || e.key.toLowerCase() === 'k',
|
||||
(e) => e.key === "ArrowUp" || e.key.toLowerCase() === "k",
|
||||
(e) => {
|
||||
if (!isTreeFocused()) return;
|
||||
e.preventDefault();
|
||||
@@ -361,7 +361,7 @@ function TreeInner<T extends { id: string }>(
|
||||
);
|
||||
|
||||
useKey(
|
||||
(e) => e.key === 'ArrowDown' || e.key.toLowerCase() === 'j',
|
||||
(e) => e.key === "ArrowDown" || e.key.toLowerCase() === "j",
|
||||
(e) => {
|
||||
if (!isTreeFocused()) return;
|
||||
e.preventDefault();
|
||||
@@ -373,7 +373,7 @@ function TreeInner<T extends { id: string }>(
|
||||
|
||||
// If the selected item is a collapsed folder, expand it. Otherwise, select next item
|
||||
useKey(
|
||||
(e) => e.key === 'ArrowRight' || e.key === 'l',
|
||||
(e) => e.key === "ArrowRight" || e.key === "l",
|
||||
(e) => {
|
||||
if (!isTreeFocused()) return;
|
||||
e.preventDefault();
|
||||
@@ -399,7 +399,7 @@ function TreeInner<T extends { id: string }>(
|
||||
// If the selected item is in a folder, select its parent.
|
||||
// If the selected item is an expanded folder, collapse it.
|
||||
useKey(
|
||||
(e) => e.key === 'ArrowLeft' || e.key === 'h',
|
||||
(e) => e.key === "ArrowLeft" || e.key === "h",
|
||||
(e) => {
|
||||
if (!isTreeFocused()) return;
|
||||
e.preventDefault();
|
||||
@@ -422,7 +422,7 @@ function TreeInner<T extends { id: string }>(
|
||||
[selectableItems, handleSelect],
|
||||
);
|
||||
|
||||
useKeyPressEvent('Escape', async () => {
|
||||
useKeyPressEvent("Escape", async () => {
|
||||
if (!treeRef.current?.contains(document.activeElement)) return;
|
||||
clearDragState();
|
||||
const lastSelectedId = jotaiStore.get(focusIdsFamily(treeId)).lastId;
|
||||
@@ -486,11 +486,11 @@ function TreeInner<T extends { id: string }>(
|
||||
let hoveredParent = node.parent;
|
||||
const dragIndex = selectableItems.findIndex((n) => n.node.item.id === item.id) ?? -1;
|
||||
const hovered = selectableItems[dragIndex]?.node ?? null;
|
||||
const hoveredIndex = dragIndex + (side === 'before' ? 0 : 1);
|
||||
let hoveredChildIndex = overSelectableItem.index + (side === 'before' ? 0 : 1);
|
||||
const hoveredIndex = dragIndex + (side === "before" ? 0 : 1);
|
||||
let hoveredChildIndex = overSelectableItem.index + (side === "before" ? 0 : 1);
|
||||
|
||||
// Move into the folder if it's open and we're moving after it
|
||||
if (hovered?.children != null && side === 'after') {
|
||||
if (hovered?.children != null && side === "after") {
|
||||
hoveredParent = hovered;
|
||||
hoveredChildIndex = 0;
|
||||
}
|
||||
@@ -620,7 +620,7 @@ function TreeInner<T extends { id: string }>(
|
||||
|
||||
const treeItemListProps: Omit<
|
||||
TreeItemListProps<T>,
|
||||
'nodes' | 'treeId' | 'activeIdAtom' | 'hoveredParent' | 'hoveredIndex'
|
||||
"nodes" | "treeId" | "activeIdAtom" | "hoveredParent" | "hoveredIndex"
|
||||
> = {
|
||||
getItemKey,
|
||||
getContextMenu: handleGetContextMenu,
|
||||
@@ -670,24 +670,24 @@ function TreeInner<T extends { id: string }>(
|
||||
ref={treeRef}
|
||||
className={classNames(
|
||||
className,
|
||||
'outline-none h-full',
|
||||
'overflow-y-auto overflow-x-hidden',
|
||||
'grid grid-rows-[auto_1fr]',
|
||||
"outline-none h-full",
|
||||
"overflow-y-auto overflow-x-hidden",
|
||||
"grid grid-rows-[auto_1fr]",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'[&_.tree-item.selected_.tree-item-inner]:text-text',
|
||||
'[&:focus-within]:[&_.tree-item.selected]:bg-surface-active',
|
||||
'[&:not(:focus-within)]:[&_.tree-item.selected:not([data-context-menu-open])]:bg-surface-highlight',
|
||||
'[&_.tree-item.selected[data-context-menu-open]]:bg-surface-active',
|
||||
"[&_.tree-item.selected_.tree-item-inner]:text-text",
|
||||
"[&:focus-within]:[&_.tree-item.selected]:bg-surface-active",
|
||||
"[&:not(:focus-within)]:[&_.tree-item.selected:not([data-context-menu-open])]:bg-surface-highlight",
|
||||
"[&_.tree-item.selected[data-context-menu-open]]:bg-surface-active",
|
||||
// Round the items, but only if the ends of the selection.
|
||||
// Also account for the drop marker being in between items
|
||||
'[&_.tree-item]:rounded-md',
|
||||
'[&_.tree-item.selected+.tree-item.selected]:rounded-t-none',
|
||||
'[&_.tree-item.selected+.drop-marker+.tree-item.selected]:rounded-t-none',
|
||||
'[&_.tree-item.selected:has(+.tree-item.selected)]:rounded-b-none',
|
||||
'[&_.tree-item.selected:has(+.drop-marker+.tree-item.selected)]:rounded-b-none',
|
||||
"[&_.tree-item]:rounded-md",
|
||||
"[&_.tree-item.selected+.tree-item.selected]:rounded-t-none",
|
||||
"[&_.tree-item.selected+.drop-marker+.tree-item.selected]:rounded-t-none",
|
||||
"[&_.tree-item.selected:has(+.tree-item.selected)]:rounded-b-none",
|
||||
"[&_.tree-item.selected:has(+.drop-marker+.tree-item.selected)]:rounded-b-none",
|
||||
)}
|
||||
>
|
||||
<TreeItemList
|
||||
@@ -766,7 +766,7 @@ function TreeHotKey<T extends { id: string }>({
|
||||
...options,
|
||||
enable: () => {
|
||||
if (enable == null) return true;
|
||||
if (typeof enable === 'function') return enable();
|
||||
if (typeof enable === "function") return enable();
|
||||
return enable;
|
||||
},
|
||||
},
|
||||
@@ -780,7 +780,7 @@ function TreeHotKeys<T extends { id: string }>({
|
||||
selectableItems,
|
||||
}: {
|
||||
treeId: string;
|
||||
hotkeys: TreeProps<T>['hotkeys'];
|
||||
hotkeys: TreeProps<T>["hotkeys"];
|
||||
selectableItems: SelectableTreeNode<T>[];
|
||||
}) {
|
||||
if (hotkeys == null) return null;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DragOverlay } from '@dnd-kit/core';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { draggingIdsFamily } from './atoms';
|
||||
import type { SelectableTreeNode } from './common';
|
||||
import type { TreeProps } from './Tree';
|
||||
import { TreeItemList } from './TreeItemList';
|
||||
import { DragOverlay } from "@dnd-kit/core";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { draggingIdsFamily } from "./atoms";
|
||||
import type { SelectableTreeNode } from "./common";
|
||||
import type { TreeProps } from "./Tree";
|
||||
import { TreeItemList } from "./TreeItemList";
|
||||
|
||||
export function TreeDragOverlay<T extends { id: string }>({
|
||||
treeId,
|
||||
@@ -14,7 +14,7 @@ export function TreeDragOverlay<T extends { id: string }>({
|
||||
}: {
|
||||
treeId: string;
|
||||
selectableItems: SelectableTreeNode<T>[];
|
||||
} & Pick<TreeProps<T>, 'getItemKey' | 'ItemInner' | 'ItemLeftSlotInner'>) {
|
||||
} & Pick<TreeProps<T>, "getItemKey" | "ItemInner" | "ItemLeftSlotInner">) {
|
||||
const draggingItems = useAtomValue(draggingIdsFamily(treeId));
|
||||
return (
|
||||
<DragOverlay dropAnimation={null}>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { memo } from 'react';
|
||||
import { DropMarker } from '../../DropMarker';
|
||||
import { hoveredParentDepthFamily, isCollapsedFamily, isIndexHoveredFamily } from './atoms';
|
||||
import type { TreeNode } from './common';
|
||||
import classNames from "classnames";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { memo } from "react";
|
||||
import { DropMarker } from "../../DropMarker";
|
||||
import { hoveredParentDepthFamily, isCollapsedFamily, isIndexHoveredFamily } from "./atoms";
|
||||
import type { TreeNode } from "./common";
|
||||
|
||||
export const TreeDropMarker = memo(function TreeDropMarker<T extends { id: string }>({
|
||||
className,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { memo } from 'react';
|
||||
import { hoveredParentDepthFamily, isAncestorHoveredFamily } from './atoms';
|
||||
import classNames from "classnames";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { memo } from "react";
|
||||
import { hoveredParentDepthFamily, isAncestorHoveredFamily } from "./atoms";
|
||||
|
||||
export const TreeIndentGuide = memo(function TreeIndentGuide({
|
||||
treeId,
|
||||
@@ -22,8 +22,8 @@ export const TreeIndentGuide = memo(function TreeIndentGuide({
|
||||
// oxlint-disable-next-line react/no-array-index-key
|
||||
key={i}
|
||||
className={classNames(
|
||||
'w-[calc(1rem+0.5px)] border-r border-r-text-subtlest',
|
||||
!(parentDepth === i + 1 && isHovered) && 'opacity-30',
|
||||
"w-[calc(1rem+0.5px)] border-r border-r-text-subtlest",
|
||||
!(parentDepth === i + 1 && isHovered) && "opacity-30",
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import type { DragMoveEvent } from '@dnd-kit/core';
|
||||
import { useDndContext, useDndMonitor, useDraggable, useDroppable } from '@dnd-kit/core';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { selectAtom } from 'jotai/utils';
|
||||
import type { DragMoveEvent } from "@dnd-kit/core";
|
||||
import { useDndContext, useDndMonitor, useDraggable, useDroppable } from "@dnd-kit/core";
|
||||
import classNames from "classnames";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { selectAtom } from "jotai/utils";
|
||||
import type {
|
||||
MouseEvent,
|
||||
PointerEvent,
|
||||
FocusEvent as ReactFocusEvent,
|
||||
KeyboardEvent as ReactKeyboardEvent,
|
||||
} from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { computeSideForDragMove } from '../../../lib/dnd';
|
||||
import { jotaiStore } from '../../../lib/jotai';
|
||||
import type { ContextMenuProps, DropdownItem } from '../Dropdown';
|
||||
import { ContextMenu } from '../Dropdown';
|
||||
import { Icon } from '../Icon';
|
||||
import { collapsedFamily, isCollapsedFamily, isLastFocusedFamily, isSelectedFamily } from './atoms';
|
||||
import type { TreeNode } from './common';
|
||||
import { getNodeKey } from './common';
|
||||
import type { TreeProps } from './Tree';
|
||||
import { TreeIndentGuide } from './TreeIndentGuide';
|
||||
} from "react";
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { computeSideForDragMove } from "../../../lib/dnd";
|
||||
import { jotaiStore } from "../../../lib/jotai";
|
||||
import type { ContextMenuProps, DropdownItem } from "../Dropdown";
|
||||
import { ContextMenu } from "../Dropdown";
|
||||
import { Icon } from "../Icon";
|
||||
import { collapsedFamily, isCollapsedFamily, isLastFocusedFamily, isSelectedFamily } from "./atoms";
|
||||
import type { TreeNode } from "./common";
|
||||
import { getNodeKey } from "./common";
|
||||
import type { TreeProps } from "./Tree";
|
||||
import { TreeIndentGuide } from "./TreeIndentGuide";
|
||||
|
||||
export interface TreeItemClickEvent {
|
||||
shiftKey: boolean;
|
||||
@@ -29,12 +29,12 @@ export interface TreeItemClickEvent {
|
||||
|
||||
export type TreeItemProps<T extends { id: string }> = Pick<
|
||||
TreeProps<T>,
|
||||
'ItemInner' | 'ItemLeftSlotInner' | 'ItemRightSlot' | 'treeId' | 'getEditOptions' | 'getItemKey'
|
||||
"ItemInner" | "ItemLeftSlotInner" | "ItemRightSlot" | "treeId" | "getEditOptions" | "getItemKey"
|
||||
> & {
|
||||
node: TreeNode<T>;
|
||||
className?: string;
|
||||
onClick?: (item: T, e: TreeItemClickEvent) => void;
|
||||
getContextMenu?: (item: T) => ContextMenuProps['items'] | Promise<ContextMenuProps['items']>;
|
||||
getContextMenu?: (item: T) => ContextMenuProps["items"] | Promise<ContextMenuProps["items"]>;
|
||||
depth: number;
|
||||
setRef?: (item: T, n: TreeItemHandle | null) => void;
|
||||
};
|
||||
@@ -68,7 +68,7 @@ function TreeItem_<T extends { id: string }>({
|
||||
const isCollapsed = useAtomValue(isCollapsedFamily({ treeId, itemId: node.item.id }));
|
||||
const isLastSelected = useAtomValue(isLastFocusedFamily({ treeId, itemId: node.item.id }));
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
const [dropHover, setDropHover] = useState<null | 'drop' | 'animate'>(null);
|
||||
const [dropHover, setDropHover] = useState<null | "drop" | "animate">(null);
|
||||
const startedHoverTimeout = useRef<NodeJS.Timeout>(undefined);
|
||||
const handle = useMemo<TreeItemHandle>(
|
||||
() => ({
|
||||
@@ -88,7 +88,7 @@ function TreeItem_<T extends { id: string }>({
|
||||
return listItemRef.current.getBoundingClientRect();
|
||||
},
|
||||
scrollIntoView: () => {
|
||||
listItemRef.current?.scrollIntoView({ block: 'nearest' });
|
||||
listItemRef.current?.scrollIntoView({ block: "nearest" });
|
||||
},
|
||||
}),
|
||||
[editing, getEditOptions],
|
||||
@@ -162,13 +162,13 @@ function TreeItem_<T extends { id: string }>({
|
||||
async (e: ReactKeyboardEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation(); // Don't trigger other tree keys (like arrows)
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
case "Enter":
|
||||
if (editing) {
|
||||
e.preventDefault();
|
||||
await handleSubmitNameEdit(e.currentTarget);
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
case "Escape":
|
||||
if (editing) {
|
||||
e.preventDefault();
|
||||
setEditing(false);
|
||||
@@ -208,8 +208,8 @@ function TreeItem_<T extends { id: string }>({
|
||||
const isFolder = node.children != null;
|
||||
const hasChildren = (node.children?.length ?? 0) > 0;
|
||||
const isCollapsed = jotaiStore.get(isCollapsedFamily({ treeId, itemId: node.item.id }));
|
||||
if (isCollapsed && isFolder && hasChildren && side === 'after') {
|
||||
setDropHover('animate');
|
||||
if (isCollapsed && isFolder && hasChildren && side === "after") {
|
||||
setDropHover("animate");
|
||||
clearTimeout(startedHoverTimeout.current);
|
||||
startedHoverTimeout.current = setTimeout(() => {
|
||||
jotaiStore.set(isCollapsedFamily({ treeId, itemId: node.item.id }), false);
|
||||
@@ -221,8 +221,8 @@ function TreeItem_<T extends { id: string }>({
|
||||
);
|
||||
});
|
||||
}, HOVER_CLOSED_FOLDER_DELAY);
|
||||
} else if (isFolder && !hasChildren && side === 'after') {
|
||||
setDropHover('drop');
|
||||
} else if (isFolder && !hasChildren && side === "after") {
|
||||
setDropHover("drop");
|
||||
} else {
|
||||
clearDropHover();
|
||||
}
|
||||
@@ -238,7 +238,7 @@ function TreeItem_<T extends { id: string }>({
|
||||
|
||||
// Set data attribute on the list item to preserve active state
|
||||
if (listItemRef.current) {
|
||||
listItemRef.current.setAttribute('data-context-menu-open', 'true');
|
||||
listItemRef.current.setAttribute("data-context-menu-open", "true");
|
||||
}
|
||||
|
||||
const items = await getContextMenu(node.item);
|
||||
@@ -250,7 +250,7 @@ function TreeItem_<T extends { id: string }>({
|
||||
const handleCloseContextMenu = useCallback(() => {
|
||||
// Remove data attribute when context menu closes
|
||||
if (listItemRef.current) {
|
||||
listItemRef.current.removeAttribute('data-context-menu-open');
|
||||
listItemRef.current.removeAttribute("data-context-menu-open");
|
||||
}
|
||||
setShowContextMenu(null);
|
||||
}, []);
|
||||
@@ -290,20 +290,20 @@ function TreeItem_<T extends { id: string }>({
|
||||
onContextMenu={handleContextMenu}
|
||||
className={classNames(
|
||||
className,
|
||||
'tree-item',
|
||||
'h-sm',
|
||||
'grid grid-cols-[auto_minmax(0,1fr)]',
|
||||
editing && 'ring-1 focus-within:ring-focus',
|
||||
dropHover != null && 'relative z-10 ring-2 ring-primary',
|
||||
dropHover === 'animate' && 'animate-blinkRing',
|
||||
isSelected && 'selected',
|
||||
"tree-item",
|
||||
"h-sm",
|
||||
"grid grid-cols-[auto_minmax(0,1fr)]",
|
||||
editing && "ring-1 focus-within:ring-focus",
|
||||
dropHover != null && "relative z-10 ring-2 ring-primary",
|
||||
dropHover === "animate" && "animate-blinkRing",
|
||||
isSelected && "selected",
|
||||
)}
|
||||
>
|
||||
<TreeIndentGuide treeId={treeId} depth={depth} ancestorIds={ancestorIds} />
|
||||
<div
|
||||
className={classNames(
|
||||
'text-text-subtle',
|
||||
'grid grid-cols-[auto_minmax(0,1fr)_auto] gap-x-2 items-center rounded-md',
|
||||
"text-text-subtle",
|
||||
"grid grid-cols-[auto_minmax(0,1fr)_auto] gap-x-2 items-center rounded-md",
|
||||
)}
|
||||
>
|
||||
{showContextMenu && (
|
||||
@@ -321,12 +321,12 @@ function TreeItem_<T extends { id: string }>({
|
||||
onClick={toggleCollapsed}
|
||||
>
|
||||
<Icon
|
||||
icon={node.children.length === 0 ? 'dot' : 'chevron_right'}
|
||||
icon={node.children.length === 0 ? "dot" : "chevron_right"}
|
||||
className={classNames(
|
||||
'transition-transform text-text-subtlest',
|
||||
'ml-auto',
|
||||
'w-[1rem] h-[1rem]',
|
||||
!isCollapsed && node.children.length > 0 && 'rotate-90',
|
||||
"transition-transform text-text-subtlest",
|
||||
"ml-auto",
|
||||
"w-[1rem] h-[1rem]",
|
||||
!isCollapsed && node.children.length > 0 && "rotate-90",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import { Fragment } from 'react';
|
||||
import type { SelectableTreeNode } from './common';
|
||||
import type { TreeProps } from './Tree';
|
||||
import { TreeDropMarker } from './TreeDropMarker';
|
||||
import type { TreeItemHandle, TreeItemProps } from './TreeItem';
|
||||
import { TreeItem } from './TreeItem';
|
||||
import type { CSSProperties } from "react";
|
||||
import { Fragment } from "react";
|
||||
import type { SelectableTreeNode } from "./common";
|
||||
import type { TreeProps } from "./Tree";
|
||||
import { TreeDropMarker } from "./TreeDropMarker";
|
||||
import type { TreeItemHandle, TreeItemProps } from "./TreeItem";
|
||||
import { TreeItem } from "./TreeItem";
|
||||
|
||||
export type TreeItemListProps<T extends { id: string }> = Pick<
|
||||
TreeProps<T>,
|
||||
'ItemInner' | 'ItemLeftSlotInner' | 'ItemRightSlot' | 'treeId' | 'getItemKey' | 'getEditOptions'
|
||||
"ItemInner" | "ItemLeftSlotInner" | "ItemRightSlot" | "treeId" | "getItemKey" | "getEditOptions"
|
||||
> &
|
||||
Pick<TreeItemProps<T>, 'onClick' | 'getContextMenu'> & {
|
||||
Pick<TreeItemProps<T>, "onClick" | "getContextMenu"> & {
|
||||
nodes: SelectableTreeNode<T>[];
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { atom } from 'jotai';
|
||||
import { atomFamily, selectAtom } from 'jotai/utils';
|
||||
import { atomWithKVStorage } from '../../../lib/atoms/atomWithKVStorage';
|
||||
import { atom } from "jotai";
|
||||
import { atomFamily, selectAtom } from "jotai/utils";
|
||||
import { atomWithKVStorage } from "../../../lib/atoms/atomWithKVStorage";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const selectedIdsFamily = atomFamily((_treeId: string) => {
|
||||
@@ -58,7 +58,7 @@ export const isAncestorHoveredFamily = atomFamily(
|
||||
(v) => v.parentId && ancestorIds.includes(v.parentId),
|
||||
Object.is,
|
||||
),
|
||||
(a, b) => a.treeId === b.treeId && a.ancestorIds.join(',') === b.ancestorIds.join(','),
|
||||
(a, b) => a.treeId === b.treeId && a.ancestorIds.join(",") === b.ancestorIds.join(","),
|
||||
);
|
||||
|
||||
export const isIndexHoveredFamily = atomFamily(
|
||||
@@ -76,12 +76,12 @@ export const hoveredParentDepthFamily = atomFamily((treeId: string) =>
|
||||
);
|
||||
|
||||
export const collapsedFamily = atomFamily((workspaceId: string) => {
|
||||
const key = ['sidebar_collapsed', workspaceId ?? 'n/a'];
|
||||
const key = ["sidebar_collapsed", workspaceId ?? "n/a"];
|
||||
return atomWithKVStorage<Record<string, boolean>>(key, {});
|
||||
});
|
||||
|
||||
export const isCollapsedFamily = atomFamily(
|
||||
({ treeId, itemId = 'n/a' }: { treeId: string; itemId: string | undefined }) =>
|
||||
({ treeId, itemId = "n/a" }: { treeId: string; itemId: string | undefined }) =>
|
||||
atom(
|
||||
// --- getter ---
|
||||
(get) => !!get(collapsedFamily(treeId))[itemId],
|
||||
@@ -91,7 +91,7 @@ export const isCollapsedFamily = atomFamily(
|
||||
const a = collapsedFamily(treeId);
|
||||
const prevMap = get(a);
|
||||
const prevValue = !!prevMap[itemId];
|
||||
const value = typeof next === 'function' ? next(prevValue) : next;
|
||||
const value = typeof next === "function" ? next(prevValue) : next;
|
||||
|
||||
if (value === prevValue) return; // no-op
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { jotaiStore } from '../../../lib/jotai';
|
||||
import { collapsedFamily, selectedIdsFamily } from './atoms';
|
||||
import { jotaiStore } from "../../../lib/jotai";
|
||||
import { collapsedFamily, selectedIdsFamily } from "./atoms";
|
||||
|
||||
export interface TreeNode<T extends { id: string }> {
|
||||
children?: TreeNode<T>[];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { SelectableTreeNode, TreeNode } from './common';
|
||||
import { useMemo } from "react";
|
||||
import type { SelectableTreeNode, TreeNode } from "./common";
|
||||
|
||||
export function useSelectableItems<T extends { id: string }>(root: TreeNode<T>) {
|
||||
return useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user