mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-07 12:33:36 +09:00
Allow to hide any item in the tree (#1293)
Ref https://github.com/webstudio-is/webstudio-builder/issues/1116 We need this to hide Fragment instance in navigator which is an implicit child of slot with persistent id.
This commit is contained in:
1 parent
407955c6c0
commit
f53c839fe6
8 files changed
+76
-51
No files matched your search
@@ -207,8 +207,7 @@ const PagesPanel = ({
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<TreeNode
|
||||
hideRoot
|
||||
<TreeNode<PagesTreeNode>
|
||||
selectedItemSelector={[selectedPageId, pagesTree.id]}
|
||||
onSelect={selectTreeNode}
|
||||
itemData={pagesTree}
|
||||
@@ -219,6 +218,7 @@ const PagesPanel = ({
|
||||
}
|
||||
return [];
|
||||
}}
|
||||
isItemHidden={(itemId) => itemId === pagesTree.id}
|
||||
getIsExpanded={() => true}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback } from "react";
|
||||
import { useStore } from "@nanostores/react";
|
||||
import type { Instance } from "@webstudio-is/project-build";
|
||||
import {
|
||||
hoveredInstanceSelectorStore,
|
||||
rootInstanceStore,
|
||||
@@ -7,9 +8,9 @@ import {
|
||||
useDragAndDropState,
|
||||
} from "~/shared/nano-states";
|
||||
import type { InstanceSelector } from "~/shared/tree-utils";
|
||||
import { InstanceTree } from "./tree";
|
||||
import { reparentInstance } from "~/shared/instance-utils";
|
||||
import { textEditingInstanceSelectorStore } from "~/shared/nano-states/instances";
|
||||
import { InstanceTree } from "./tree";
|
||||
|
||||
export const NavigatorTree = () => {
|
||||
const selectedInstanceSelector = useStore(selectedInstanceSelectorStore);
|
||||
@@ -21,6 +22,8 @@ export const NavigatorTree = () => {
|
||||
? state.dragPayload.dragInstanceSelector
|
||||
: undefined;
|
||||
|
||||
const isItemHidden = useCallback((_instanceId: Instance["id"]) => false, []);
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
(payload: {
|
||||
itemSelector: InstanceSelector;
|
||||
@@ -50,6 +53,7 @@ export const NavigatorTree = () => {
|
||||
selectedItemSelector={selectedInstanceSelector}
|
||||
dragItemSelector={dragItemSelector}
|
||||
dropTarget={state.dropTarget}
|
||||
isItemHidden={isItemHidden}
|
||||
onSelect={handleSelect}
|
||||
onHover={hoveredInstanceSelectorStore.set}
|
||||
onDragItemChange={(dragInstanceSelector) => {
|
||||
|
||||
@@ -116,6 +116,7 @@ const render = (
|
||||
findItemById(tree, itemId)?.canAcceptChildren ?? false,
|
||||
getItemChildren: (itemId: ItemId) =>
|
||||
findItemById(tree, itemId)?.children ?? [],
|
||||
isItemHidden: (_itemId: ItemId) => false,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -15,10 +15,12 @@ export const useHorizontalShift = <Data extends { id: ItemId }>({
|
||||
placementIndicator,
|
||||
getIsExpanded,
|
||||
canAcceptChild,
|
||||
isItemHidden,
|
||||
getItemChildren,
|
||||
}: {
|
||||
getItemChildren: (itemId: ItemId) => Data[];
|
||||
canAcceptChild: (itemId: ItemId) => boolean;
|
||||
isItemHidden: (itemId: ItemId) => boolean;
|
||||
dragItemSelector: undefined | ItemSelector;
|
||||
dropTarget: ItemDropTarget | undefined;
|
||||
placementIndicator: undefined | Placement;
|
||||
@@ -37,8 +39,6 @@ export const useHorizontalShift = <Data extends { id: ItemId }>({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const dragItemDepth = dragItemSelector.length - 1;
|
||||
|
||||
const { itemSelector: dropItemSelector, indexWithinChildren } = dropTarget;
|
||||
|
||||
const shiftPlacement = (depth: number) => {
|
||||
@@ -57,13 +57,21 @@ export const useHorizontalShift = <Data extends { id: ItemId }>({
|
||||
return { itemSelector: dropItemSelector, position: "end" };
|
||||
}
|
||||
|
||||
let dropHiddenCount = 0;
|
||||
for (const itemId of dropItemSelector) {
|
||||
if (isItemHidden(itemId)) {
|
||||
dropHiddenCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const dragItemDepth = dragItemSelector.length - 1;
|
||||
const currentDepth = dropItemSelector.length;
|
||||
const desiredDepth = dragItemDepth + horizontalShift;
|
||||
|
||||
const withoutShift = {
|
||||
itemSelector: dropItemSelector,
|
||||
position: indexWithinChildren,
|
||||
placement: shiftPlacement(currentDepth),
|
||||
placement: shiftPlacement(currentDepth - dropHiddenCount),
|
||||
} as const;
|
||||
|
||||
const [dragItemId] = dragItemSelector;
|
||||
@@ -110,7 +118,7 @@ export const useHorizontalShift = <Data extends { id: ItemId }>({
|
||||
return {
|
||||
itemSelector: newParentSelector,
|
||||
position: newPosition,
|
||||
placement: shiftPlacement(currentDepth - shifted),
|
||||
placement: shiftPlacement(currentDepth - dropHiddenCount - shifted),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -155,7 +163,7 @@ export const useHorizontalShift = <Data extends { id: ItemId }>({
|
||||
return {
|
||||
itemSelector: newParentSelector,
|
||||
position: "end",
|
||||
placement: shiftPlacement(currentDepth + shifted),
|
||||
placement: shiftPlacement(currentDepth - dropHiddenCount + shifted),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -166,6 +174,7 @@ export const useHorizontalShift = <Data extends { id: ItemId }>({
|
||||
dragItemSelector,
|
||||
horizontalShift,
|
||||
canAcceptChild,
|
||||
isItemHidden,
|
||||
getItemChildren,
|
||||
getIsExpanded,
|
||||
]);
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ItemSelector } from "./item-utils";
|
||||
export type Item = {
|
||||
id: string;
|
||||
canAcceptChildren: boolean;
|
||||
isHidden?: boolean;
|
||||
children: Item[];
|
||||
};
|
||||
|
||||
|
||||
@@ -395,6 +395,7 @@ export const TreeItemLabel = ({
|
||||
export type TreeNodeProps<Data extends { id: ItemId }> = {
|
||||
itemData: Data;
|
||||
getItemChildren: (itemId: ItemId) => Data[];
|
||||
isItemHidden: (itemId: ItemId) => boolean;
|
||||
renderItem: (props: TreeItemRenderProps<Data>) => React.ReactNode;
|
||||
|
||||
getIsExpanded: (itemSelector: ItemSelector) => boolean;
|
||||
@@ -411,14 +412,15 @@ export type TreeNodeProps<Data extends { id: ItemId }> = {
|
||||
onMouseLeave?: (itemSelector: ItemSelector) => void;
|
||||
|
||||
animate?: boolean;
|
||||
hideRoot?: boolean;
|
||||
|
||||
level?: number;
|
||||
};
|
||||
|
||||
export const TreeNode = <Data extends { id: string }>({
|
||||
itemData,
|
||||
parentSelector,
|
||||
parentIsSelected,
|
||||
hideRoot = false,
|
||||
level = 0,
|
||||
...commonProps
|
||||
}: TreeNodeProps<Data>) => {
|
||||
const {
|
||||
@@ -433,6 +435,7 @@ export const TreeNode = <Data extends { id: string }>({
|
||||
dropTargetItemSelector,
|
||||
renderItem,
|
||||
getItemChildren,
|
||||
isItemHidden,
|
||||
} = commonProps;
|
||||
|
||||
const collapsibleContentRef = useRef<HTMLDivElement>(null);
|
||||
@@ -441,16 +444,12 @@ export const TreeNode = <Data extends { id: string }>({
|
||||
|
||||
const itemSelector = [itemData.id, ...(parentSelector ?? [])];
|
||||
|
||||
const isAlwaysExpanded =
|
||||
// root is always expanded
|
||||
parentSelector === undefined ||
|
||||
// or root children when root is hidden
|
||||
(hideRoot && parentSelector.length === 1);
|
||||
let level = parentSelector?.length ?? 0;
|
||||
// reduce level when root is hidden
|
||||
if (hideRoot && level !== 0) {
|
||||
level -= 1;
|
||||
const itemIsHidden = isItemHidden(itemData.id);
|
||||
const isAlwaysExpanded = itemIsHidden;
|
||||
if (itemIsHidden === false) {
|
||||
level += 1;
|
||||
}
|
||||
|
||||
const isExpandable = itemChildren.length > 0;
|
||||
const isExpanded = getIsExpanded(itemSelector) || isAlwaysExpanded;
|
||||
const isSelected = areItemSelectorsEqual(itemSelector, selectedItemSelector);
|
||||
@@ -486,7 +485,7 @@ export const TreeNode = <Data extends { id: string }>({
|
||||
data-drop-target-id={itemData.id}
|
||||
>
|
||||
{/* optionally prevent rendering root item */}
|
||||
{(parentSelector !== undefined || hideRoot !== true) &&
|
||||
{itemIsHidden === false &&
|
||||
renderItem({
|
||||
dropTargetItemSelector,
|
||||
onMouseEnter,
|
||||
@@ -515,7 +514,7 @@ export const TreeNode = <Data extends { id: string }>({
|
||||
itemData={child}
|
||||
parentSelector={itemSelector}
|
||||
parentIsSelected={isSelected || parentIsSelected}
|
||||
hideRoot={hideRoot}
|
||||
level={level}
|
||||
{...commonProps}
|
||||
/>
|
||||
))
|
||||
|
||||
@@ -32,22 +32,29 @@ export const StressTest = ({ animate }: { animate: boolean }) => {
|
||||
canAcceptChildren: true,
|
||||
children: [
|
||||
{
|
||||
id: `box-${index}.0.0`,
|
||||
id: `hidden-${index}.0.0`,
|
||||
canAcceptChildren: true,
|
||||
isHidden: true,
|
||||
children: [
|
||||
{
|
||||
id: `box-${index}.0.0.0`,
|
||||
canAcceptChildren: true,
|
||||
children: [
|
||||
{
|
||||
id: `heading-${index}.1`,
|
||||
canAcceptChildren: false,
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: `paragraph-${index}.1`,
|
||||
canAcceptChildren: false,
|
||||
children: [],
|
||||
id: `box-${index}.0.0.0.0`,
|
||||
canAcceptChildren: true,
|
||||
children: [
|
||||
{
|
||||
id: `heading-${index}.1`,
|
||||
canAcceptChildren: false,
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: `paragraph-${index}.1`,
|
||||
canAcceptChildren: false,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -77,11 +84,18 @@ export const StressTest = ({ animate }: { animate: boolean }) => {
|
||||
return (
|
||||
<Flex css={{ width: 300, height: 500, flexDirection: "column" }}>
|
||||
<Tree
|
||||
canAcceptChild={(itemId) =>
|
||||
findItemById(root, itemId)?.canAcceptChildren ?? false
|
||||
}
|
||||
canAcceptChild={(itemId) => {
|
||||
const item = findItemById(root, itemId);
|
||||
return (
|
||||
(item?.canAcceptChildren ?? false) &&
|
||||
(item?.isHidden ?? false) === false
|
||||
);
|
||||
}}
|
||||
canLeaveParent={() => true}
|
||||
getItemChildren={(itemId) => findItemById(root, itemId)?.children ?? []}
|
||||
isItemHidden={(itemSelector) =>
|
||||
findItemById(root, itemSelector[0])?.isHidden ?? false
|
||||
}
|
||||
animate={animate}
|
||||
root={root}
|
||||
selectedItemSelector={selectedItemSelector}
|
||||
|
||||
@@ -31,6 +31,7 @@ export type TreeProps<Data extends { id: string }> = {
|
||||
canLeaveParent: (itemId: ItemId) => boolean;
|
||||
canAcceptChild: (itemId: ItemId) => boolean;
|
||||
getItemChildren: (itemId: ItemId) => Data[];
|
||||
isItemHidden: (itemId: ItemId) => boolean;
|
||||
renderItem: (props: TreeItemRenderProps<Data>) => React.ReactNode;
|
||||
|
||||
onSelect?: (itemSelector: ItemSelector) => void;
|
||||
@@ -66,6 +67,7 @@ export const Tree = <Data extends { id: string }>({
|
||||
canLeaveParent,
|
||||
canAcceptChild,
|
||||
getItemChildren,
|
||||
isItemHidden,
|
||||
renderItem,
|
||||
onSelect,
|
||||
onHover,
|
||||
@@ -77,7 +79,6 @@ export const Tree = <Data extends { id: string }>({
|
||||
}: TreeProps<Data>) => {
|
||||
const { getIsExpanded, setIsExpanded } = useExpandState({
|
||||
selectedItemSelector,
|
||||
getItemChildren,
|
||||
});
|
||||
|
||||
const rootRef = useRef<HTMLElement | null>(null);
|
||||
@@ -112,6 +113,7 @@ export const Tree = <Data extends { id: string }>({
|
||||
placementIndicator,
|
||||
getIsExpanded,
|
||||
getItemChildren,
|
||||
isItemHidden,
|
||||
canAcceptChild,
|
||||
});
|
||||
|
||||
@@ -131,11 +133,7 @@ export const Tree = <Data extends { id: string }>({
|
||||
if (dropTarget === undefined) {
|
||||
return;
|
||||
}
|
||||
const [itemId] = dropTarget.itemSelector;
|
||||
if (
|
||||
getItemChildren(itemId).length > 0 ||
|
||||
getIsExpanded(dropTarget.itemSelector) === false
|
||||
) {
|
||||
if (getIsExpanded(dropTarget.itemSelector) === false) {
|
||||
setIsExpanded(dropTarget.itemSelector, true);
|
||||
}
|
||||
},
|
||||
@@ -261,6 +259,7 @@ export const Tree = <Data extends { id: string }>({
|
||||
const keyboardNavigation = useKeyboardNavigation({
|
||||
root,
|
||||
getItemChildren,
|
||||
isItemHidden,
|
||||
selectedItemSelector,
|
||||
getIsExpanded,
|
||||
setIsExpanded,
|
||||
@@ -293,6 +292,7 @@ export const Tree = <Data extends { id: string }>({
|
||||
<TreeNode
|
||||
renderItem={renderItem}
|
||||
getItemChildren={getItemChildren}
|
||||
isItemHidden={isItemHidden}
|
||||
animate={animate}
|
||||
onSelect={onSelect}
|
||||
onMouseEnter={onHover}
|
||||
@@ -324,6 +324,7 @@ const useKeyboardNavigation = <Data extends { id: string }>({
|
||||
root,
|
||||
selectedItemSelector,
|
||||
getItemChildren,
|
||||
isItemHidden,
|
||||
getIsExpanded,
|
||||
setIsExpanded,
|
||||
onEsc,
|
||||
@@ -331,6 +332,7 @@ const useKeyboardNavigation = <Data extends { id: string }>({
|
||||
root: Data;
|
||||
selectedItemSelector: undefined | ItemSelector;
|
||||
getItemChildren: (itemId: ItemId) => Data[];
|
||||
isItemHidden: (itemId: ItemId) => boolean;
|
||||
getIsExpanded: (itemSelector: ItemSelector) => boolean;
|
||||
setIsExpanded: (itemSelector: ItemSelector, isExpanded: boolean) => void;
|
||||
onEsc: () => void;
|
||||
@@ -339,7 +341,9 @@ const useKeyboardNavigation = <Data extends { id: string }>({
|
||||
const result: ItemSelector[] = [];
|
||||
const traverse = (itemSelector: ItemSelector) => {
|
||||
const [itemId] = itemSelector;
|
||||
result.push(itemSelector);
|
||||
if (isItemHidden(itemId) === false) {
|
||||
result.push(itemSelector);
|
||||
}
|
||||
if (getIsExpanded(itemSelector)) {
|
||||
for (const child of getItemChildren(itemId)) {
|
||||
traverse([child.id, ...itemSelector]);
|
||||
@@ -348,7 +352,7 @@ const useKeyboardNavigation = <Data extends { id: string }>({
|
||||
};
|
||||
traverse([root.id]);
|
||||
return result;
|
||||
}, [root, getIsExpanded, getItemChildren]);
|
||||
}, [root, getIsExpanded, getItemChildren, isItemHidden]);
|
||||
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -465,12 +469,10 @@ const useKeyboardNavigation = <Data extends { id: string }>({
|
||||
};
|
||||
};
|
||||
|
||||
const useExpandState = <Data extends { id: string }>({
|
||||
const useExpandState = ({
|
||||
selectedItemSelector,
|
||||
getItemChildren,
|
||||
}: {
|
||||
selectedItemSelector: undefined | ItemSelector;
|
||||
getItemChildren: (itemId: ItemId) => Data[];
|
||||
}) => {
|
||||
const [record, setRecord] = useState<Record<string, boolean>>({});
|
||||
|
||||
@@ -512,14 +514,9 @@ const useExpandState = <Data extends { id: string }>({
|
||||
if (itemSelector.length === 1) {
|
||||
return true;
|
||||
}
|
||||
const [itemId] = itemSelector;
|
||||
|
||||
return (
|
||||
getItemChildren(itemId).length > 0 &&
|
||||
record[itemSelector.join()] === true
|
||||
);
|
||||
return record[itemSelector.join()] === true;
|
||||
},
|
||||
[record, getItemChildren]
|
||||
[record]
|
||||
);
|
||||
|
||||
const setIsExpanded = useCallback(
|
||||
|
||||
Reference in new issue
Block a user