@djangocfg/ui-tools 2.1.413 → 2.1.416
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/file-icon/index.d.cts +1 -1
- package/dist/file-icon/index.d.ts +1 -1
- package/dist/slots-ClRpIzoh.d.cts +88 -0
- package/dist/slots-ClRpIzoh.d.ts +88 -0
- package/dist/tree/index.cjs +1994 -276
- package/dist/tree/index.cjs.map +1 -1
- package/dist/tree/index.d.cts +717 -72
- package/dist/tree/index.d.ts +717 -72
- package/dist/tree/index.mjs +1984 -279
- package/dist/tree/index.mjs.map +1 -1
- package/package.json +10 -6
- package/src/tools/chat/README.md +111 -1
- package/src/tools/chat/composer/Composer.tsx +138 -17
- package/src/tools/chat/composer/ComposerRichTextarea.tsx +25 -0
- package/src/tools/chat/composer/index.ts +22 -0
- package/src/tools/chat/composer/slash/README.md +187 -0
- package/src/tools/chat/composer/slash/SlashHighlightTextarea.tsx +144 -0
- package/src/tools/chat/composer/slash/SlashMenu.tsx +142 -0
- package/src/tools/chat/composer/slash/SlashToken.tsx +57 -0
- package/src/tools/chat/composer/slash/index.ts +44 -0
- package/src/tools/chat/composer/slash/labels.ts +19 -0
- package/src/tools/chat/composer/slash/state.ts +168 -0
- package/src/tools/chat/composer/slash/types.ts +64 -0
- package/src/tools/chat/composer/slash/useSlashCommands.ts +204 -0
- package/src/tools/chat/composer/types.ts +8 -0
- package/src/tools/chat/shell/SuggestedPrompts.tsx +194 -0
- package/src/tools/chat/shell/index.ts +6 -0
- package/src/tools/data/Listbox/lazy.tsx +1 -1
- package/src/tools/data/Masonry/lazy.tsx +1 -1
- package/src/tools/data/Timeline/lazy.tsx +1 -1
- package/src/tools/data/Tree/FinderTree.tsx +42 -0
- package/src/tools/data/Tree/README.md +337 -208
- package/src/tools/data/Tree/TreeDndProvider.tsx +137 -0
- package/src/tools/data/Tree/TreeRoot.tsx +170 -55
- package/src/tools/data/Tree/__tests__/dnd.test.ts +160 -0
- package/src/tools/data/Tree/__tests__/keyboard.test.ts +137 -0
- package/src/tools/data/Tree/__tests__/renameUtils.test.ts +52 -0
- package/src/tools/data/Tree/__tests__/selection.test.ts +227 -0
- package/src/tools/data/Tree/components/TreeDropIndicator.tsx +65 -0
- package/src/tools/data/Tree/components/TreeEmptyArea.tsx +160 -0
- package/src/tools/data/Tree/components/TreeRenameInput.tsx +114 -0
- package/src/tools/data/Tree/components/TreeRow.tsx +92 -8
- package/src/tools/data/Tree/components/index.ts +6 -0
- package/src/tools/data/Tree/context/TreeContext.tsx +204 -363
- package/src/tools/data/Tree/context/TreeContextValue.ts +139 -0
- package/src/tools/data/Tree/context/async-children/collect-ids.ts +27 -0
- package/src/tools/data/Tree/context/async-children/index.ts +8 -0
- package/src/tools/data/Tree/context/async-children/use-async-children.ts +157 -0
- package/src/tools/data/Tree/context/clipboard/index.ts +4 -0
- package/src/tools/data/Tree/context/clipboard/use-clipboard.ts +115 -0
- package/src/tools/data/Tree/context/dnd/index.ts +8 -0
- package/src/tools/data/Tree/context/dnd/use-dnd.ts +194 -0
- package/src/tools/data/Tree/context/expansion/index.ts +4 -0
- package/src/tools/data/Tree/context/expansion/use-expansion.ts +55 -0
- package/src/tools/data/Tree/context/hooks.ts +68 -1
- package/src/tools/data/Tree/context/index.ts +3 -0
- package/src/tools/data/Tree/context/menu/builtin-actions.ts +357 -0
- package/src/tools/data/Tree/context/menu/index.ts +10 -0
- package/src/tools/data/Tree/context/menu/use-resolved-menu.ts +127 -0
- package/src/tools/data/Tree/context/persist/index.ts +4 -0
- package/src/tools/data/Tree/context/persist/use-persist-sync.ts +74 -0
- package/src/tools/data/Tree/context/rename/index.ts +4 -0
- package/src/tools/data/Tree/context/rename/use-rename.ts +113 -0
- package/src/tools/data/Tree/context/selection/index.ts +4 -0
- package/src/tools/data/Tree/context/selection/use-selection.ts +146 -0
- package/src/tools/data/Tree/context/state/index.ts +6 -0
- package/src/tools/data/Tree/context/state/initial.ts +41 -0
- package/src/tools/data/Tree/context/state/reducer.ts +76 -0
- package/src/tools/data/Tree/context/state/types.ts +46 -0
- package/src/tools/data/Tree/data/clipboard.ts +33 -0
- package/src/tools/data/Tree/data/dnd.ts +123 -0
- package/src/tools/data/Tree/data/finderShortcuts.ts +67 -0
- package/src/tools/data/Tree/data/index.ts +19 -0
- package/src/tools/data/Tree/data/renameUtils.ts +51 -0
- package/src/tools/data/Tree/data/selection.ts +157 -0
- package/src/tools/data/Tree/hooks/finder-hotkeys/build-ctx.ts +48 -0
- package/src/tools/data/Tree/hooks/finder-hotkeys/index.ts +8 -0
- package/src/tools/data/Tree/hooks/finder-hotkeys/use-tree-finder-hotkeys.ts +166 -0
- package/src/tools/data/Tree/hooks/index.ts +23 -4
- package/src/tools/data/Tree/hooks/keyboard/activation.ts +27 -0
- package/src/tools/data/Tree/hooks/keyboard/arrow-nav.ts +26 -0
- package/src/tools/data/Tree/hooks/keyboard/expand-collapse.ts +54 -0
- package/src/tools/data/Tree/hooks/keyboard/index.ts +10 -0
- package/src/tools/data/Tree/hooks/keyboard/types.ts +39 -0
- package/src/tools/data/Tree/hooks/keyboard/use-tree-keyboard.ts +196 -0
- package/src/tools/data/Tree/hooks/type-ahead/index.ts +5 -0
- package/src/tools/data/Tree/hooks/type-ahead/match-prefix.ts +42 -0
- package/src/tools/data/Tree/hooks/{useTreeTypeAhead.ts → type-ahead/use-tree-type-ahead.ts} +8 -19
- package/src/tools/data/Tree/index.tsx +25 -2
- package/src/tools/data/Tree/types/activation.ts +30 -0
- package/src/tools/data/Tree/types/adapter.ts +70 -0
- package/src/tools/data/Tree/types/index.ts +27 -0
- package/src/tools/data/Tree/types/labels.ts +97 -0
- package/src/tools/data/Tree/types/loader.ts +9 -0
- package/src/tools/data/Tree/types/node.ts +38 -0
- package/src/tools/data/Tree/types/root-props.ts +142 -0
- package/src/tools/data/Tree/types/selection.ts +3 -0
- package/src/tools/data/Tree/types/slots.ts +64 -0
- package/src/tools/dev/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +1 -1
- package/src/tools/dev/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +1 -1
- package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +85 -0
- package/src/tools/forms/MarkdownEditor/index.ts +1 -0
- package/src/tools/forms/MarkdownEditor/lazy.tsx +6 -0
- package/src/tools/forms/MarkdownEditor/slash/SlashCommandNode.ts +162 -0
- package/src/tools/forms/MarkdownEditor/slash/index.ts +4 -0
- package/src/tools/forms/MarkdownEditor/slash/syncSlashNode.ts +97 -0
- package/src/tools/forms/MarkdownEditor/slash/types.ts +13 -0
- package/src/tools/forms/MarkdownEditor/styles.css +18 -0
- package/src/tools/index.ts +2 -2
- package/dist/types-j2vhn4Kv.d.cts +0 -241
- package/dist/types-j2vhn4Kv.d.ts +0 -241
- package/src/tools/data/Tree/hooks/useTreeKeyboard.ts +0 -171
- package/src/tools/data/Tree/types.ts +0 -217
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
5
|
+
|
|
6
|
+
import { autoSelectRange } from '../data/renameUtils';
|
|
7
|
+
|
|
8
|
+
export interface TreeRenameInputProps {
|
|
9
|
+
initialValue: string;
|
|
10
|
+
isFolder: boolean;
|
|
11
|
+
/** Called with the new (trimmed) name when the user presses Enter / blurs. */
|
|
12
|
+
onCommit: (nextName: string) => void | Promise<unknown>;
|
|
13
|
+
/** Called when the user presses Escape. */
|
|
14
|
+
onCancel: () => void;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Inline rename input rendered in place of `<TreeLabel>` while a row is
|
|
20
|
+
* being renamed. Mounts focused with the *base* portion of the name
|
|
21
|
+
* pre-selected (Finder behaviour — `foo.txt` selects `foo`).
|
|
22
|
+
*
|
|
23
|
+
* Behaviour:
|
|
24
|
+
* - Enter → commit
|
|
25
|
+
* - Escape → cancel (no adapter call)
|
|
26
|
+
* - blur → commit (matches Finder; intentional even for empty
|
|
27
|
+
* names — the host validates and re-opens on error)
|
|
28
|
+
* - all other keys are stopped from bubbling so Tree's container
|
|
29
|
+
* hotkeys (↑↓ delete F2 etc.) don't fire while typing.
|
|
30
|
+
*/
|
|
31
|
+
export function TreeRenameInput({
|
|
32
|
+
initialValue,
|
|
33
|
+
isFolder,
|
|
34
|
+
onCommit,
|
|
35
|
+
onCancel,
|
|
36
|
+
className,
|
|
37
|
+
}: TreeRenameInputProps) {
|
|
38
|
+
const [value, setValue] = useState(initialValue);
|
|
39
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
40
|
+
// Track whether we already committed/cancelled so the blur handler
|
|
41
|
+
// doesn't fire a second time after Enter/Escape.
|
|
42
|
+
const settledRef = useRef(false);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const el = inputRef.current;
|
|
46
|
+
if (!el) return;
|
|
47
|
+
el.focus();
|
|
48
|
+
const [start, end] = autoSelectRange(initialValue, isFolder);
|
|
49
|
+
// setSelectionRange on a freshly focused input — schedule on next
|
|
50
|
+
// tick to dodge browser quirks where focus() resets the selection.
|
|
51
|
+
requestAnimationFrame(() => {
|
|
52
|
+
try {
|
|
53
|
+
el.setSelectionRange(start, end);
|
|
54
|
+
} catch {
|
|
55
|
+
/* Some input types reject setSelectionRange — safe to ignore. */
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// Run only once on mount.
|
|
59
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
const commit = () => {
|
|
63
|
+
if (settledRef.current) return;
|
|
64
|
+
settledRef.current = true;
|
|
65
|
+
void onCommit(value);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const cancel = () => {
|
|
69
|
+
if (settledRef.current) return;
|
|
70
|
+
settledRef.current = true;
|
|
71
|
+
onCancel();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<input
|
|
76
|
+
ref={inputRef}
|
|
77
|
+
type="text"
|
|
78
|
+
value={value}
|
|
79
|
+
// Tree's container hotkeys (delete / arrows / F2 …) must not fire
|
|
80
|
+
// while the user is typing the new name.
|
|
81
|
+
onKeyDown={(e) => {
|
|
82
|
+
e.stopPropagation();
|
|
83
|
+
if (e.key === 'Enter') {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
commit();
|
|
86
|
+
} else if (e.key === 'Escape') {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
cancel();
|
|
89
|
+
}
|
|
90
|
+
}}
|
|
91
|
+
onChange={(e) => setValue(e.target.value)}
|
|
92
|
+
onBlur={commit}
|
|
93
|
+
// Prevent click/dblclick on the row from re-firing while the input
|
|
94
|
+
// is mounted (otherwise a focused click commits + re-selects).
|
|
95
|
+
onClick={(e) => e.stopPropagation()}
|
|
96
|
+
onDoubleClick={(e) => e.stopPropagation()}
|
|
97
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
98
|
+
// Right-click inside the input should be the native input menu,
|
|
99
|
+
// not the row's context menu.
|
|
100
|
+
onContextMenu={(e) => e.stopPropagation()}
|
|
101
|
+
className={cn(
|
|
102
|
+
'min-w-0 flex-1 rounded-sm border border-primary/50 bg-background',
|
|
103
|
+
'px-1 py-0 text-foreground outline-none',
|
|
104
|
+
'focus:ring-1 focus:ring-primary/40',
|
|
105
|
+
className,
|
|
106
|
+
)}
|
|
107
|
+
style={{
|
|
108
|
+
// Match the row's font metrics so the input doesn't visibly jolt.
|
|
109
|
+
fontSize: 'var(--tree-font-size)',
|
|
110
|
+
height: 'calc(var(--tree-row-height) - 4px)',
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Loader2 } from 'lucide-react';
|
|
4
|
-
import { memo } from 'react';
|
|
4
|
+
import { memo, useCallback } from 'react';
|
|
5
|
+
import { useDraggable, useDroppable } from '@dnd-kit/core';
|
|
5
6
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
6
7
|
|
|
7
8
|
import { useTreeContext } from '../context/TreeContext';
|
|
@@ -9,9 +10,11 @@ import { radiusClass, rowStateClasses } from '../data/appearance';
|
|
|
9
10
|
import type { FlatRow, TreeRowRenderProps } from '../types';
|
|
10
11
|
import { TreeChevron } from './TreeChevron';
|
|
11
12
|
import { treeRowDomId } from './TreeContent';
|
|
13
|
+
import { TreeDropIndicator } from './TreeDropIndicator';
|
|
12
14
|
import { TreeIcon } from './TreeIcon';
|
|
13
15
|
import { TreeIndentGuides } from './TreeIndentGuides';
|
|
14
16
|
import { TreeLabel } from './TreeLabel';
|
|
17
|
+
import { TreeRenameInput } from './TreeRenameInput';
|
|
15
18
|
|
|
16
19
|
export interface TreeRowProps<T> {
|
|
17
20
|
row: FlatRow<T>;
|
|
@@ -36,6 +39,7 @@ function TreeRowRaw<T>({ row, className }: TreeRowProps<T>) {
|
|
|
36
39
|
matchingIds,
|
|
37
40
|
select,
|
|
38
41
|
setSelectedIds,
|
|
42
|
+
clickSelect,
|
|
39
43
|
toggle,
|
|
40
44
|
setFocus,
|
|
41
45
|
activate,
|
|
@@ -44,6 +48,11 @@ function TreeRowRaw<T>({ row, className }: TreeRowProps<T>) {
|
|
|
44
48
|
renderLabel,
|
|
45
49
|
renderActions,
|
|
46
50
|
renderContextMenu,
|
|
51
|
+
renamingId,
|
|
52
|
+
commitRename,
|
|
53
|
+
cancelRename,
|
|
54
|
+
clipboard,
|
|
55
|
+
dnd,
|
|
47
56
|
} = ctx;
|
|
48
57
|
|
|
49
58
|
const { node, level, isFolder, isExpanded, isLoading, posInSet, setSize } = row;
|
|
@@ -51,6 +60,8 @@ function TreeRowRaw<T>({ row, className }: TreeRowProps<T>) {
|
|
|
51
60
|
const isFocused = focused === node.id;
|
|
52
61
|
const isMatchingSearch = matchingIds.has(node.id);
|
|
53
62
|
const isMultiSelect = ctx.selectionMode === 'multiple';
|
|
63
|
+
const isCut =
|
|
64
|
+
clipboard?.kind === 'cut' && clipboard.ids.includes(node.id);
|
|
54
65
|
|
|
55
66
|
const slot: TreeRowRenderProps<T> = {
|
|
56
67
|
node,
|
|
@@ -68,33 +79,82 @@ function TreeRowRaw<T>({ row, className }: TreeRowProps<T>) {
|
|
|
68
79
|
// single-click → click activates {preview:false}
|
|
69
80
|
// double-click → click only selects; dblclick activates {preview:false}
|
|
70
81
|
// single-click-preview → click activates {preview:true}; dblclick activates {preview:false}
|
|
82
|
+
const isRenaming = renamingId === node.id;
|
|
83
|
+
const isDragging = dnd.draggingIds.has(node.id);
|
|
84
|
+
const dropTarget = dnd.dropTarget;
|
|
85
|
+
const isDropTarget = dropTarget?.id === node.id;
|
|
86
|
+
const dropPosition = isDropTarget ? dropTarget!.position : null;
|
|
87
|
+
|
|
88
|
+
// ─── DnD wiring ──────────────────────────────────────────────────
|
|
89
|
+
// Hooks are called unconditionally so we don't violate rules-of-
|
|
90
|
+
// hooks; the `disabled` flag short-circuits @dnd-kit when DnD is off
|
|
91
|
+
// or the row is in inline rename.
|
|
92
|
+
//
|
|
93
|
+
// Drop-zone resolution (before/inside/after) is centralised in
|
|
94
|
+
// `<TreeDndProvider>` via `onDragMove` — rows don't need a
|
|
95
|
+
// `onPointerMove` of their own. Saves a listener × N rows.
|
|
96
|
+
const dndDisabled = !dnd.active || isRenaming || node.disabled;
|
|
97
|
+
const draggable = useDraggable({ id: node.id, disabled: dndDisabled });
|
|
98
|
+
const droppable = useDroppable({ id: node.id, disabled: dndDisabled });
|
|
99
|
+
|
|
100
|
+
const setRowEl = useCallback(
|
|
101
|
+
(el: HTMLDivElement | null) => {
|
|
102
|
+
draggable.setNodeRef(el);
|
|
103
|
+
droppable.setNodeRef(el);
|
|
104
|
+
},
|
|
105
|
+
[draggable, droppable],
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const isAllowedDrop =
|
|
109
|
+
dropPosition && !isDragging
|
|
110
|
+
? dnd.isAllowedDrop(node, dropPosition)
|
|
111
|
+
: true;
|
|
71
112
|
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
72
|
-
if (node.disabled) return;
|
|
113
|
+
if (node.disabled || isRenaming) return;
|
|
73
114
|
setFocus(node.id);
|
|
74
|
-
// Multi-select:
|
|
75
|
-
// toggles
|
|
76
|
-
|
|
77
|
-
|
|
115
|
+
// Multi-select: full Finder/Explorer semantics — plain replaces, meta
|
|
116
|
+
// toggles, shift extends range from anchor, shift+meta unions range.
|
|
117
|
+
// Single-select: clickSelect collapses to {id}. None: no-op.
|
|
118
|
+
if (isMultiSelect) {
|
|
119
|
+
clickSelect(node.id, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey });
|
|
78
120
|
} else {
|
|
79
121
|
select(node.id);
|
|
80
122
|
}
|
|
81
123
|
if (isFolder) {
|
|
124
|
+
// Don't toggle on shift/meta clicks — those are pure selection edits.
|
|
125
|
+
if (e.shiftKey || e.metaKey || e.ctrlKey) return;
|
|
82
126
|
toggle(node.id);
|
|
83
127
|
} else if (activationMode === 'single-click') {
|
|
128
|
+
// Selection-only modifier clicks should not activate the leaf.
|
|
129
|
+
if (e.shiftKey || e.metaKey || e.ctrlKey) return;
|
|
84
130
|
activate(node, { preview: false });
|
|
85
131
|
} else if (activationMode === 'single-click-preview') {
|
|
132
|
+
if (e.shiftKey || e.metaKey || e.ctrlKey) return;
|
|
86
133
|
activate(node, { preview: true });
|
|
87
134
|
}
|
|
88
135
|
};
|
|
89
136
|
|
|
90
137
|
const handleDoubleClick = () => {
|
|
91
|
-
if (node.disabled) return;
|
|
138
|
+
if (node.disabled || isRenaming) return;
|
|
92
139
|
if (isFolder) return;
|
|
93
140
|
activate(node, { preview: false });
|
|
94
141
|
};
|
|
95
142
|
|
|
143
|
+
// Finder/Explorer semantics: right-click on an unselected row switches
|
|
144
|
+
// selection to that single row (so menu actions apply to it). Right-
|
|
145
|
+
// click on a row already in the selection leaves the multi-selection
|
|
146
|
+
// intact (so destructive bulk actions stay safe).
|
|
147
|
+
const handleContextMenu = () => {
|
|
148
|
+
if (node.disabled || isRenaming) return;
|
|
149
|
+
setFocus(node.id);
|
|
150
|
+
if (!isSelected) {
|
|
151
|
+
setSelectedIds([node.id]);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
96
155
|
const trigger = (
|
|
97
156
|
<div
|
|
157
|
+
ref={dnd.active ? setRowEl : undefined}
|
|
98
158
|
id={treeRowDomId(node.id)}
|
|
99
159
|
role="treeitem"
|
|
100
160
|
aria-level={level + 1}
|
|
@@ -107,6 +167,8 @@ function TreeRowRaw<T>({ row, className }: TreeRowProps<T>) {
|
|
|
107
167
|
data-id={node.id}
|
|
108
168
|
data-activation-mode={activationMode}
|
|
109
169
|
data-selected={isSelected ? 'true' : undefined}
|
|
170
|
+
data-clipboard={isCut ? 'cut' : undefined}
|
|
171
|
+
data-dragging={isDragging ? 'true' : undefined}
|
|
110
172
|
data-focused={isFocused && !isSelected ? 'true' : undefined}
|
|
111
173
|
data-folder={isFolder || undefined}
|
|
112
174
|
data-expanded={isExpanded || undefined}
|
|
@@ -116,8 +178,11 @@ function TreeRowRaw<T>({ row, className }: TreeRowProps<T>) {
|
|
|
116
178
|
height: 'var(--tree-row-height)',
|
|
117
179
|
gap: 'var(--tree-gap)',
|
|
118
180
|
}}
|
|
181
|
+
{...(dnd.active ? draggable.listeners : {})}
|
|
182
|
+
{...(dnd.active ? draggable.attributes : {})}
|
|
119
183
|
onClick={handleClick}
|
|
120
184
|
onDoubleClick={handleDoubleClick}
|
|
185
|
+
onContextMenu={handleContextMenu}
|
|
121
186
|
onFocus={() => setFocus(node.id)}
|
|
122
187
|
className={cn(
|
|
123
188
|
'group/row relative flex w-full select-none items-center pr-2 text-left',
|
|
@@ -127,6 +192,8 @@ function TreeRowRaw<T>({ row, className }: TreeRowProps<T>) {
|
|
|
127
192
|
rowStateClasses(appearance),
|
|
128
193
|
'focus-visible:ring-1 focus-visible:ring-ring/50',
|
|
129
194
|
isMatchingSearch && 'ring-1 ring-primary/30',
|
|
195
|
+
isCut && 'opacity-60',
|
|
196
|
+
isDragging && 'opacity-40',
|
|
130
197
|
node.disabled && 'opacity-50',
|
|
131
198
|
className,
|
|
132
199
|
)}
|
|
@@ -142,6 +209,16 @@ function TreeRowRaw<T>({ row, className }: TreeRowProps<T>) {
|
|
|
142
209
|
/>
|
|
143
210
|
) : null}
|
|
144
211
|
|
|
212
|
+
{/* DnD drop indicator — top/bottom line for sibling reorder,
|
|
213
|
+
fill for "drop into folder". */}
|
|
214
|
+
{dropPosition && !isDragging ? (
|
|
215
|
+
<TreeDropIndicator
|
|
216
|
+
position={dropPosition}
|
|
217
|
+
indent={6 + level * appearance.indent}
|
|
218
|
+
invalid={!isAllowedDrop}
|
|
219
|
+
/>
|
|
220
|
+
) : null}
|
|
221
|
+
|
|
145
222
|
{showIndentGuides && level > 0 ? (
|
|
146
223
|
<TreeIndentGuides level={level} indent={appearance.indent} />
|
|
147
224
|
) : null}
|
|
@@ -165,7 +242,14 @@ function TreeRowRaw<T>({ row, className }: TreeRowProps<T>) {
|
|
|
165
242
|
className="flex min-w-0 flex-1 items-center"
|
|
166
243
|
style={{ gap: 'var(--tree-gap)' }}
|
|
167
244
|
>
|
|
168
|
-
{
|
|
245
|
+
{renamingId === node.id ? (
|
|
246
|
+
<TreeRenameInput
|
|
247
|
+
initialValue={getItemName(node)}
|
|
248
|
+
isFolder={isFolder}
|
|
249
|
+
onCommit={(next) => commitRename(node.id, next)}
|
|
250
|
+
onCancel={cancelRename}
|
|
251
|
+
/>
|
|
252
|
+
) : renderLabel ? (
|
|
169
253
|
renderLabel(slot)
|
|
170
254
|
) : (
|
|
171
255
|
<TreeLabel isMatchingSearch={isMatchingSearch}>{getItemName(node)}</TreeLabel>
|
|
@@ -20,3 +20,9 @@ export { TreeError } from './TreeError';
|
|
|
20
20
|
export type { TreeErrorProps } from './TreeError';
|
|
21
21
|
export { TreeIndentGuides } from './TreeIndentGuides';
|
|
22
22
|
export type { TreeIndentGuidesProps } from './TreeIndentGuides';
|
|
23
|
+
export { TreeRenameInput } from './TreeRenameInput';
|
|
24
|
+
export type { TreeRenameInputProps } from './TreeRenameInput';
|
|
25
|
+
export { TreeDropIndicator } from './TreeDropIndicator';
|
|
26
|
+
export type { TreeDropIndicatorProps } from './TreeDropIndicator';
|
|
27
|
+
export { TreeEmptyArea } from './TreeEmptyArea';
|
|
28
|
+
export type { TreeEmptyAreaProps } from './TreeEmptyArea';
|