From f34d2853f4ed16439cd30a78a379f3e940fa52eb Mon Sep 17 00:00:00 2001 From: Alex Sexton Date: Mon, 23 Mar 2026 14:30:45 -0500 Subject: [PATCH 1/5] initial integration --- .../trees/src/components/OverflowText.tsx | 389 ++++++++++++++++++ packages/trees/src/components/Root.tsx | 3 +- packages/trees/src/index.ts | 2 +- packages/trees/src/style.css | 222 +++++++++- .../src/react/components/OverflowText.tsx | 2 +- 5 files changed, 614 insertions(+), 4 deletions(-) create mode 100644 packages/trees/src/components/OverflowText.tsx diff --git a/packages/trees/src/components/OverflowText.tsx b/packages/trees/src/components/OverflowText.tsx new file mode 100644 index 000000000..29234fd74 --- /dev/null +++ b/packages/trees/src/components/OverflowText.tsx @@ -0,0 +1,389 @@ +/** @jsxImportSource preact */ + +import type { ComponentChildren, CSSProperties } from 'preact'; + +type PropsWithChildren = T & { + children?: ComponentChildren; +}; + +export type CSSPropertiesWithVars = CSSProperties & { + [key: `--${string}`]: string | number | undefined; +}; + +export interface MarkerProps extends PropsWithChildren {} + +export type TruncateMode = 'truncate' | 'fruncate'; + +export interface OverflowTextProps extends PropsWithChildren { + mode?: TruncateMode; + style?: Omit; + className?: string; + marker?: ComponentChildren | ((props: MarkerProps) => ComponentChildren); + variant?: 'default' | 'fade'; +} + +export type MiddleTruncateProps = Omit & + AllowableContentGroups & { + minimumLength?: number; + priority?: 'start' | 'end' | 'equal'; + split?: + | 'center' + | 'extension' + | 'leaf-path' + | number + | SplitOffset + | CustomSplitFn; + }; + +export type MiddleTruncateFilteredProps = Pick< + MiddleTruncateProps, + 'priority' | 'variant' +> & { splitIndex?: number; splitOffset?: number }; + +export type CustomSplitFn = ( + contents: string, + props?: MiddleTruncateFilteredProps +) => [string, string]; +export type SplitOffsetType = 'last' | 'first'; +export type SplitOffset = [SplitOffsetType, number]; + +type AllowableContentGroups = + | { + children?: never; + contents: [ComponentChildren, ComponentChildren]; + } + | { + contents?: never; + children: string; + }; + +// Split the contents into two equal segments +export const splitCenter: CustomSplitFn = (contents) => { + if (contents.length < 2) { + return [contents, '']; + } + const splitIndex = Math.ceil(contents.length / 2); + return [contents.slice(0, splitIndex), contents.slice(splitIndex)]; +}; + +// Find the last dot in the contents and split a that index +export const splitExtension: CustomSplitFn = (contents) => { + if (contents.length < 4) { + return [contents, '']; + } + const lastDotIndex = contents.lastIndexOf('.'); + const extensionIndex = lastDotIndex + 1; + const impliedExtensionLength = contents.length - extensionIndex; + const maxExtensionLength = 10; + const isTooLong = impliedExtensionLength > maxExtensionLength; + + const splitIndex = + extensionIndex >= 1 && !isTooLong + ? extensionIndex + : Math.ceil(contents.length / 2); + + return [contents.slice(0, splitIndex), contents.slice(splitIndex)]; +}; + +export const splitLeafPath: CustomSplitFn = (contents) => { + if (contents.length < 4) { + return [contents, '']; + } + const lastSlashIndex = contents.lastIndexOf('/'); + const leafPathIndex = lastSlashIndex + 1; + const impliedLeafPathLength = contents.length - leafPathIndex; + const maxLeafPathLength = 25; + const isTooLong = impliedLeafPathLength > maxLeafPathLength; + const splitIndex = + leafPathIndex >= 1 && !isTooLong + ? leafPathIndex + : Math.ceil(contents.length / 2); + return [contents.slice(0, splitIndex), contents.slice(splitIndex)]; +}; + +export const splitByIndex: CustomSplitFn = (contents, { splitIndex } = {}) => { + if (typeof splitIndex !== 'number') { + const centerIndex = Math.ceil(contents.length / 2); + return [contents.slice(0, centerIndex), contents.slice(centerIndex)]; + } + return [contents.slice(0, splitIndex), contents.slice(splitIndex)]; +}; + +export const splitLast: CustomSplitFn = ( + contents: string, + { splitOffset } = {} +) => { + // fall back to center split if the offset is not valid + if ( + typeof splitOffset !== 'number' || + splitOffset <= 0 || + splitOffset >= contents.length + ) { + const centerIndex = Math.ceil(contents.length / 2); + return [contents.slice(0, centerIndex), contents.slice(centerIndex)]; + } + + const splitIndex = contents.length - splitOffset; + return [contents.slice(0, splitIndex), contents.slice(splitIndex)]; +}; + +export const splitFirst: CustomSplitFn = ( + contents: string, + { splitOffset } = {} +) => { + // fall back to center split if the offset is not valid + if ( + typeof splitOffset !== 'number' || + splitOffset <= 0 || + splitOffset >= contents.length + ) { + const centerIndex = Math.ceil(contents.length / 2); + return [contents.slice(0, centerIndex), contents.slice(centerIndex)]; + } + + const splitIndex = splitOffset; + return [contents.slice(0, splitIndex), contents.slice(splitIndex)]; +}; + +function OverflowMarker({ + children, + marker, + variant = 'default', +}: OverflowTextProps) { + 'use no memo'; + const isFadeVariant = variant === 'fade'; + return ( +
+
+ {typeof marker === 'function' ? ( + marker({ children }) + ) : isFadeVariant ? ( + + ) : ( + marker + )} +
+
+ ); +} + +function OverflowContent(options: OverflowTextProps) { + 'use no memo'; + const { mode, children } = options; + + // The inner span wrapper here is only needed to implement + // the right aligned internals for fruncate + return ( +
+
+ {mode === 'fruncate' ? {children} : children} +
+
+ {mode === 'fruncate' ? {children} : children} +
+
+ ); +} + +export function OverflowText({ + children, + mode = 'truncate', + marker = '…', + variant = 'default', + ...props +}: OverflowTextProps) { + 'use no memo'; + const contentNode = ( + + {children} + + ); + const markerNode = ( + + ); + const fillNode =
; + + return ( +
+
+ {mode === 'truncate' + ? [contentNode, markerNode] + : [markerNode, contentNode, fillNode]} +
+
+ ); +} + +export function Truncate({ + children, + ...props +}: Omit) { + 'use no memo'; + return ( + + {children} + + ); +} + +export function Fruncate({ + children, + ...props +}: Omit) { + 'use no memo'; + return ( + + {children} + + ); +} + +export function MiddleTruncate({ + children, + contents, + priority = 'end', + split = 'center', + minimumLength = 12, + className, + style, + ...props +}: MiddleTruncateProps) { + 'use no memo'; + let firstSegment: ComponentChildren | null = null; + let secondSegment: ComponentChildren | null = null; + if (Array.isArray(contents)) { + if (contents.length !== 2) { + console.error('MiddleTruncate: contents must be an array of two items'); + return null; + } + firstSegment = {contents[0]}; + secondSegment = {contents[1]}; + } else { + // TODO: figure out how to support ReactNode children in the future + if (typeof children !== 'string') { + console.error('MiddleTruncate: children must be a string'); + return null; + } + + // In case styling relies on the presence of the component, we will return a div + if (children.length === 0) { + return
; + } + + // If the minimumLength is not met, we will still truncate the text, + // but we will not split it into two segments. + if (children.length < minimumLength) { + if (priority === 'end') { + return ( + + {children} + + ); + } else { + // 'start' and 'equal' both fall back to standard end-clipping. + return ( + + {children} + + ); + } + } + + let splitFn: CustomSplitFn | null = null; + let splitIndex: number | null = null; + let splitOffset: number | null = null; + + // A little ugly, but want to make it fast? + if (typeof split === 'string') { + if (split === 'center') { + splitFn = splitCenter; + } else if (split === 'extension') { + splitFn = splitExtension; + } else if (split === 'leaf-path') { + splitFn = splitLeafPath; + } + } else if (typeof split === 'number') { + splitFn = splitByIndex; + splitIndex = split; + } else if (Array.isArray(split)) { + const [offsetType, offsetValue] = split; + splitOffset = offsetValue; + if (offsetType === 'last') { + splitFn = splitLast; + } else if (offsetType === 'first') { + splitFn = splitFirst; + } + } else if (typeof split === 'function') { + splitFn = split; + } + + // If we can't determine the split function, use the center split + splitFn ??= splitCenter; + + const [firstHalfMessage, secondHalfMessage] = splitFn(children, { + priority, + variant: props.variant, + splitIndex: typeof splitIndex === 'number' ? splitIndex : undefined, + splitOffset: typeof splitOffset === 'number' ? splitOffset : undefined, + }); + + const firstIsLarger = firstHalfMessage.length >= secondHalfMessage.length; + const secondIsLarger = !firstIsLarger; + + const firstCanBeSimple = priority === 'equal' && secondIsLarger; + const secondCanBeSimple = priority === 'equal' && firstIsLarger; + + const firstPropOverrides: Partial = {}; + const secondPropOverrides: Partial = {}; + + if (firstCanBeSimple) { + firstPropOverrides.marker = ''; + } + if (secondCanBeSimple) { + secondPropOverrides.marker = ''; + } + + firstSegment = ( + + {firstHalfMessage} + + ); + secondSegment = ( + + {secondHalfMessage} + + ); + } + + return ( +
+
+ {firstSegment} +
+
+ {secondSegment} +
+
+ ); +} diff --git a/packages/trees/src/components/Root.tsx b/packages/trees/src/components/Root.tsx index c4fece4f2..c1c0e3ad0 100644 --- a/packages/trees/src/components/Root.tsx +++ b/packages/trees/src/components/Root.tsx @@ -62,6 +62,7 @@ import type { ChildrenSortOption } from '../utils/sortChildren'; import { useContextMenuController } from './hooks/useContextMenuController'; import { useTree } from './hooks/useTree'; import { Icon } from './Icon'; +import { MiddleTruncate } from './OverflowText'; import { VirtualizedList } from './VirtualizedList'; export interface FileTreeRootProps { @@ -334,7 +335,7 @@ function TreeItemInner({ fallbackName={itemName} /> ) : ( - itemName + {itemName} )} diff --git a/packages/trees/src/index.ts b/packages/trees/src/index.ts index 740b182ad..474f4a4b4 100644 --- a/packages/trees/src/index.ts +++ b/packages/trees/src/index.ts @@ -1,6 +1,7 @@ export * from './constants'; export * from './FileTree'; export * from './loader'; +export { default as fileTreeStyles } from './style.css'; export type { ContextMenuAnchorRect, ContextMenuItem, @@ -9,4 +10,3 @@ export type { export * from './utils/expandImplicitParentDirectories'; export * from './utils/sortChildren'; export * from './utils/themeToTreeStyles'; -export { default as fileTreeStyles } from './style.css'; diff --git a/packages/trees/src/style.css b/packages/trees/src/style.css index 431c0c4d9..685e00cbb 100644 --- a/packages/trees/src/style.css +++ b/packages/trees/src/style.css @@ -204,6 +204,7 @@ font-size: var(--trees-font-size); color: var(--trees-fg); background-color: var(--trees-bg); + --truncate-marker-background-color: var(--trees-bg); font-family: var(--trees-font-family); font-weight: var(--trees-font-weight-regular); } @@ -326,6 +327,7 @@ &:hover, &[data-item-context-hover='true'] { background-color: var(--trees-bg-muted); + --truncate-marker-background-color: var(--trees-bg-muted); } &[data-item-focused='true'], @@ -342,6 +344,7 @@ &[data-item-selected='true'] { color: var(--trees-selected-fg); background-color: var(--trees-selected-bg); + --truncate-marker-background-color: var(--trees-selected-bg); z-index: 3; [data-item-section='icon'] { @@ -393,7 +396,8 @@ min-width: 0; overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; + /* Breaks middle truncate component to also set this */ + /* white-space: nowrap; */ } [data-item-section='status'] { @@ -604,4 +608,220 @@ [data-type='context-menu-trigger']:hover { color: var(--trees-fg); } + + /** @pierre/truncate css here, manually copy pasted for now */ + [data-truncate-container] { + /* CUSTOM TO TREES */ + margin-top: -1px; + margin-bottom: -1px; + + /* Width of the fade from default marker to text */ + --truncate-internal-marker-fade-width: var(--truncate-marker-fade-width, 2px); + /* Width of the solid color between the fade from the default marker to the text */ + --truncate-internal-marker-gap: var(--truncate-marker-gap, 0px); + /* Opacity of the marker 'color' property, not of the element itself */ + --truncate-internal-marker-opacity: var(--truncate-marker-opacity, 50%); + /* Opacity of the marker 'color' property specifically for the middle truncate, not opacity of the element itself */ + --truncate-internal-middle-marker-opacity: var( + --truncate-middle-marker-opacity, + 80% + ); + /* Background color of the default marker */ + --truncate-internal-marker-background-color: var( + --truncate-marker-background-color, + light-dark(white, black) + ); + /* Duration of the fade out animation for the marker */ + --truncate-internal-marker-fade-out-duration: var( + --truncate-marker-fade-out-duration, + 0ms + ); + /* Duration of the fade in animation for the marker */ + --truncate-internal-marker-fade-in-duration: var( + --truncate-marker-fade-in-duration, + 100ms + ); + + /* FADE Variant specifics */ + --truncate-internal-fade-marker-color: var( + --truncate-fade-marker-color, + #000 + ); + --truncate-internal-fade-marker-width: var( + --truncate-fade-marker-width, + 0.2lh + ); + + /* + In some special cases people might be adding spacing in other ways + that would benefit from being able to override this, however the container + query below can't use this and would need to be redeclared with the overridden + value. It's a bad time, but better than nothing. + */ + --truncate-internal-single-line-height: 1lh; + + height: var(--truncate-internal-single-line-height); + min-width: 0; + overflow: hidden; + } + + [data-truncate-marker] { + display: flex; + position: absolute; + height: var(--truncate-internal-single-line-height); + z-index: 2; + color: color-mix( + in srgb, + currentColor var(--truncate-internal-marker-opacity), + transparent + ); + + /* Core trick for hiding the marker until overflow occurs */ + opacity: 0; + transition: opacity var(--truncate-internal-marker-fade-out-duration) + ease-in-out; + } + + @container measure (height > 1lh) { + [data-truncate-marker] { + opacity: 1; + transition: opacity var(--truncate-internal-marker-fade-in-duration) + ease-in-out; + } + } + + [data-truncate-grid] { + display: grid; + position: relative; + } + + [data-truncate-content='visible'] { + white-space: nowrap; + } + + [data-truncate-content='overflow'] { + opacity: 0; + pointer-events: none; + user-select: none; + word-break: break-all; + margin-top: calc(-1 * var(--truncate-internal-single-line-height)); + } + + [data-truncate-marker-cell] { + container: measure / size; + overflow: visible; + user-select: none; + pointer-events: none; + } + + [data-truncate-container='truncate'] { + & [data-truncate-grid] { + grid-template-columns: minmax(0, max-content) 0; + } + & [data-truncate-marker] { + right: 0; + } + & [data-truncate-fade] { + margin-right: calc(-2 * var(--truncate-internal-fade-marker-width)); + } + } + + [data-truncate-container='fruncate'] { + & [data-truncate-grid] { + grid-template-columns: 0 minmax(0, max-content) auto; + } + & [data-truncate-content] { + direction: rtl; + } + & [data-truncate-content] > span { + unicode-bidi: plaintext; + } + & [data-truncate-fade] { + margin-left: calc(-2 * var(--truncate-internal-fade-marker-width)); + } + } + + [data-truncate-variant='default'] { + & [data-truncate-marker] { + background-color: var(--truncate-internal-marker-background-color); + } + & [data-truncate-marker]::after, + & [data-truncate-marker]::before { + content: ''; + position: absolute; + width: calc( + var(--truncate-internal-marker-fade-width) + + var(--truncate-internal-marker-gap) + ); + height: var(--truncate-internal-single-line-height); + background: linear-gradient( + var(--truncate-internal-fade-dir), + var(--truncate-internal-marker-background-color) 0%, + var(--truncate-internal-marker-background-color) + var(--truncate-internal-marker-gap), + transparent 100% + ); + } + & [data-truncate-marker]::after { + --truncate-internal-fade-dir: to right; + right: calc( + -1 * + ( + var(--truncate-internal-marker-fade-width) + + var(--truncate-internal-marker-gap) + ) + ); + } + & [data-truncate-marker]::before { + --truncate-internal-fade-dir: to left; + left: calc( + -1 * + ( + var(--truncate-internal-marker-fade-width) + + var(--truncate-internal-marker-gap) + ) + ); + } + } + + [data-truncate-variant='fade'] { + & [data-truncate-marker] { + background: transparent; + } + } + + [data-truncate-fade] { + box-shadow: + 0 0 calc(var(--truncate-internal-fade-marker-width) / 2) + var(--truncate-internal-fade-marker-color), + 0 0 var(--truncate-internal-fade-marker-width) + var(--truncate-internal-fade-marker-color); + width: calc(var(--truncate-internal-fade-marker-width) * 2); + height: calc( + var(--truncate-internal-single-line-height) - + (var(--truncate-internal-fade-marker-width) * 2) + ); + margin: var(--truncate-internal-fade-marker-width) 0; + } + + [data-truncate-group-container='middle'] { + & [data-truncate-container] { + --truncate-marker-opacity: var(--truncate-internal-middle-marker-opacity); + } + + display: flex; + min-width: 0; + + & > div { + min-width: 0; + } + + & > div[data-truncate-segment-priority='1'] { + flex: 0 1 max-content; + } + & > div[data-truncate-segment-priority='2'] { + flex: 0 999999 max-content; + } + } + } diff --git a/packages/truncate/src/react/components/OverflowText.tsx b/packages/truncate/src/react/components/OverflowText.tsx index b209aa894..d2a7f5198 100644 --- a/packages/truncate/src/react/components/OverflowText.tsx +++ b/packages/truncate/src/react/components/OverflowText.tsx @@ -21,7 +21,7 @@ function OverflowMarker({ }: OverflowTextProps) { const isFadeVariant = variant === 'fade'; return ( -
+
{typeof marker === 'function' ? ( marker({ children }) From 42014624d38f13f07ab7c65c1d40a90984a151d6 Mon Sep 17 00:00:00 2001 From: Alex Sexton Date: Mon, 23 Mar 2026 18:46:40 -0500 Subject: [PATCH 2/5] baseline truncation, needs tweaks --- packages/trees/src/components/Root.tsx | 10 +++++++--- packages/trees/src/style.css | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/trees/src/components/Root.tsx b/packages/trees/src/components/Root.tsx index c1c0e3ad0..9a524f255 100644 --- a/packages/trees/src/components/Root.tsx +++ b/packages/trees/src/components/Root.tsx @@ -62,7 +62,7 @@ import type { ChildrenSortOption } from '../utils/sortChildren'; import { useContextMenuController } from './hooks/useContextMenuController'; import { useTree } from './hooks/useTree'; import { Icon } from './Icon'; -import { MiddleTruncate } from './OverflowText'; +import { MiddleTruncate, Truncate } from './OverflowText'; import { VirtualizedList } from './VirtualizedList'; export interface FileTreeRootProps { @@ -140,7 +140,9 @@ function FlattenedDirectoryName({ const isLast = index === segments.length - 1; return ( - {label} + + {label} + {!isLast ? ' / ' : ''} ); @@ -335,7 +337,9 @@ function TreeItemInner({ fallbackName={itemName} /> ) : ( - {itemName} + + {itemName} + )}
diff --git a/packages/trees/src/style.css b/packages/trees/src/style.css index 685e00cbb..7dd816dad 100644 --- a/packages/trees/src/style.css +++ b/packages/trees/src/style.css @@ -611,7 +611,7 @@ /** @pierre/truncate css here, manually copy pasted for now */ [data-truncate-container] { - /* CUSTOM TO TREES */ + /* CUSTOM TO TREES, TO SUPPORT THE OUTLINE */ margin-top: -1px; margin-bottom: -1px; From 1c5e14f2bc72db4b77924531640788c572dbae3b Mon Sep 17 00:00:00 2001 From: Alex Sexton Date: Mon, 23 Mar 2026 18:49:57 -0500 Subject: [PATCH 3/5] format css --- packages/trees/src/style.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/trees/src/style.css b/packages/trees/src/style.css index 7dd816dad..46c4c75d2 100644 --- a/packages/trees/src/style.css +++ b/packages/trees/src/style.css @@ -616,7 +616,10 @@ margin-bottom: -1px; /* Width of the fade from default marker to text */ - --truncate-internal-marker-fade-width: var(--truncate-marker-fade-width, 2px); + --truncate-internal-marker-fade-width: var( + --truncate-marker-fade-width, + 2px + ); /* Width of the solid color between the fade from the default marker to the text */ --truncate-internal-marker-gap: var(--truncate-marker-gap, 0px); /* Opacity of the marker 'color' property, not of the element itself */ @@ -823,5 +826,4 @@ flex: 0 999999 max-content; } } - } From bb901a46294b50bbcdffe96ff962d0bb9aa0cbdd Mon Sep 17 00:00:00 2001 From: Alex Sexton Date: Mon, 23 Mar 2026 18:51:15 -0500 Subject: [PATCH 4/5] fix lint warnings --- .../hooks/useContextMenuController.ts | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/trees/src/components/hooks/useContextMenuController.ts b/packages/trees/src/components/hooks/useContextMenuController.ts index aa4baa0f7..8242d90dc 100644 --- a/packages/trees/src/components/hooks/useContextMenuController.ts +++ b/packages/trees/src/components/hooks/useContextMenuController.ts @@ -93,7 +93,7 @@ export function useContextMenuController({ // Lazily resolve and cache the tree container and its virtualized scroll // child. The cache is invalidated when the container disconnects from DOM. - const getTreeContainer = (): Element | null => { + const getTreeContainer = useCallback((): Element | null => { if ( treeContainerRef.current != null && treeContainerRef.current.isConnected @@ -105,7 +105,7 @@ export function useContextMenuController({ scrollContainerRef.current = container?.querySelector('[data-file-tree-virtualized-scroll]') ?? null; return container; - }; + }, [tree]); const setContextHoverItem = useCallback( (itemId: string | null) => { @@ -138,7 +138,7 @@ export function useContextMenuController({ contextHoverItemElRef.current = itemEl; contextHoverItemIdRef.current = itemId; }, - [tree] + [getTreeContainer] ); const isEventInContextMenu = useCallback((e: Event): boolean => { @@ -204,7 +204,7 @@ export function useContextMenuController({ } return false; - }, [tree]); + }, [getTreeContainer, tree]); const closeContextMenu = useCallback( (notify = true) => { @@ -295,7 +295,7 @@ export function useContextMenuController({ hoveredContextMenuItemRef.current = nextItemId; setContextHoverItem(nextItemId); }, - [setContextHoverItem, tree] + [getTreeContainer, setContextHoverItem] ); const openContextMenuForItem = useCallback( @@ -491,7 +491,12 @@ export function useContextMenuController({ if (scrollTimer != null) clearTimeout(scrollTimer); isScrollingRef.current = false; }; - }, [closeContextMenu, isContextMenuEnabled, setContextHoverItem, tree]); + }, [ + closeContextMenu, + getTreeContainer, + isContextMenuEnabled, + setContextHoverItem, + ]); useEffect( () => () => { @@ -542,7 +547,12 @@ export function useContextMenuController({ `[data-item-id="${focusedItemId}"]` ) as HTMLElement | null; updateTriggerPosition(itemEl); - }, [focusedItemId, isContextMenuEnabled, updateTriggerPosition, tree]); + }, [ + focusedItemId, + getTreeContainer, + isContextMenuEnabled, + updateTriggerPosition, + ]); const handleTreePointerOver = useCallback( (e: PointerEvent) => { From cc632d77fa6d1dc9ad4c0abd3bbfedf65b4e1fc4 Mon Sep 17 00:00:00 2001 From: Alex Sexton Date: Mon, 23 Mar 2026 19:03:11 -0500 Subject: [PATCH 5/5] add an aria label to each button for now, helps with a11y and testing. might want less duplication in the future --- packages/trees/src/components/Root.tsx | 1 + .../trees/test/e2e/git-status-attrs.pw.ts | 9 ++---- packages/trees/test/e2e/touch-dnd.pw.ts | 30 ++++--------------- 3 files changed, 10 insertions(+), 30 deletions(-) diff --git a/packages/trees/src/components/Root.tsx b/packages/trees/src/components/Root.tsx index 9a524f255..0e5ac1a76 100644 --- a/packages/trees/src/components/Root.tsx +++ b/packages/trees/src/components/Root.tsx @@ -297,6 +297,7 @@ function TreeItemInner({