@hachej/boring-workspace 0.1.47 → 0.1.49

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.
@@ -0,0 +1,315 @@
1
+ import { jsx as d, jsxs as y } from "react/jsx-runtime";
2
+ import { useRef as C, useMemo as I, useEffect as M, useCallback as k, createContext as E, useContext as J } from "react";
3
+ import { Tree as K } from "react-arborist";
4
+ import { FolderOpenIcon as U, FolderIcon as V, ChevronRightIcon as X, Loader2Icon as Y } from "lucide-react";
5
+ import { L as Z } from "./WorkspaceProvider-CbvaXxgT.js";
6
+ import { EmptyState as G, Input as H } from "@hachej/boring-ui-kit";
7
+ import { c as g } from "./utils-B6yFEsav.js";
8
+ import { createDragDropManager as Q } from "dnd-core";
9
+ import { HTML5Backend as tt } from "react-dnd-html5-backend";
10
+ const D = Symbol.for("@hachej/boring-workspace/file-tree-dnd-manager");
11
+ function et() {
12
+ const r = globalThis;
13
+ return r[D] ?? (r[D] = Q(
14
+ tt,
15
+ typeof window > "u" ? void 0 : window
16
+ )), r[D];
17
+ }
18
+ const W = /* @__PURE__ */ new Set(), j = E({
19
+ onContextMenu: void 0,
20
+ editing: null,
21
+ pendingPaths: W,
22
+ onSubmitEdit: void 0,
23
+ onCancelEdit: void 0
24
+ });
25
+ function rt({
26
+ initialValue: r,
27
+ onSubmit: u,
28
+ onCancel: s,
29
+ isDraft: l
30
+ }) {
31
+ const e = C(null), a = C(!1);
32
+ M(() => {
33
+ const t = e.current;
34
+ if (t)
35
+ if (t.focus(), !l && r.includes(".")) {
36
+ const i = r.lastIndexOf(".");
37
+ t.setSelectionRange(0, i);
38
+ } else
39
+ t.select();
40
+ }, [r, l]);
41
+ const n = () => {
42
+ var i;
43
+ if (a.current) return;
44
+ a.current = !0;
45
+ const t = ((i = e.current) == null ? void 0 : i.value.trim()) ?? "";
46
+ !t || t === r ? s() : u(t);
47
+ };
48
+ return /* @__PURE__ */ d(
49
+ H,
50
+ {
51
+ ref: e,
52
+ type: "text",
53
+ defaultValue: r,
54
+ "data-testid": "file-tree-edit-input",
55
+ "aria-label": l ? "Name" : "Rename",
56
+ onPointerDown: (t) => t.stopPropagation(),
57
+ onClick: (t) => t.stopPropagation(),
58
+ onKeyDown: (t) => {
59
+ t.stopPropagation(), t.key === "Enter" ? (t.preventDefault(), n()) : t.key === "Escape" && (t.preventDefault(), a.current = !0, s());
60
+ },
61
+ onBlur: n,
62
+ className: "h-5 min-w-0 flex-1 rounded-sm border-[color:var(--accent)]/60 px-1 text-[13px] leading-[1.2] focus-visible:ring-[color:var(--accent)]"
63
+ }
64
+ );
65
+ }
66
+ function nt(r) {
67
+ const u = /* @__PURE__ */ new Set();
68
+ let s = 0;
69
+ const l = (e) => {
70
+ const a = [];
71
+ for (const n of e) {
72
+ if (typeof (n == null ? void 0 : n.path) != "string" || n.path.length === 0 || u.has(n.path)) {
73
+ s++;
74
+ continue;
75
+ }
76
+ u.add(n.path), a.push(
77
+ n.children && n.children.length > 0 ? { ...n, children: l(n.children) } : n
78
+ );
79
+ }
80
+ return a;
81
+ };
82
+ return { nodes: l(r), dropped: s };
83
+ }
84
+ function ot(r, u) {
85
+ if (!(u != null && u.trim())) return r.length;
86
+ const s = u.trim().toLowerCase(), l = (e) => {
87
+ var n;
88
+ let a = 0;
89
+ for (const t of e) {
90
+ const i = t.name.toLowerCase().includes(s), m = (n = t.children) != null && n.length ? l(t.children) : 0;
91
+ (i || m > 0) && (a += 1);
92
+ }
93
+ return a;
94
+ };
95
+ return l(r);
96
+ }
97
+ function it({ node: r, style: u, dragHandle: s }) {
98
+ const { onContextMenu: l, editing: e, pendingPaths: a, onSubmitEdit: n, onCancelEdit: t } = J(j), i = r.data, m = i.kind === "dir", h = (e == null ? void 0 : e.path) === i.path, w = a.has(i.path), F = m ? r.isOpen ? U : V : Z(i.name || "untitled");
99
+ return /* @__PURE__ */ y(
100
+ "div",
101
+ {
102
+ ref: s,
103
+ style: u,
104
+ className: g(
105
+ "group relative mx-1 flex items-center gap-1.5 rounded-md px-2 py-0.5 text-[13px] leading-[1.4] cursor-pointer select-none text-foreground",
106
+ "transition-colors duration-150 ease-[cubic-bezier(0.22,1,0.36,1)]",
107
+ !h && "hover:bg-foreground/[0.04]",
108
+ r.isSelected && !h && "bg-[oklch(from_var(--accent)_l_c_h/0.10)] text-foreground font-medium",
109
+ r.willReceiveDrop && "bg-foreground/5 outline outline-1 outline-border"
110
+ ),
111
+ onClick: (f) => {
112
+ h || (f.stopPropagation(), m ? r.toggle() : (r.select(), r.activate()));
113
+ },
114
+ onContextMenu: (f) => {
115
+ h || i.isDraft || (f.preventDefault(), f.stopPropagation(), l == null || l(f, i));
116
+ },
117
+ children: [
118
+ m ? /* @__PURE__ */ d(
119
+ X,
120
+ {
121
+ className: g(
122
+ "h-3 w-3 shrink-0 text-muted-foreground/70 transition-transform duration-150 ease-[cubic-bezier(0.22,1,0.36,1)]",
123
+ r.isOpen && "rotate-90"
124
+ ),
125
+ strokeWidth: 2
126
+ }
127
+ ) : /* @__PURE__ */ d("span", { className: "w-3 shrink-0" }),
128
+ /* @__PURE__ */ d(
129
+ F,
130
+ {
131
+ className: g(
132
+ "h-4 w-4 shrink-0",
133
+ r.isSelected ? "text-[color:var(--accent)]" : "text-muted-foreground/80"
134
+ ),
135
+ strokeWidth: 1.5
136
+ }
137
+ ),
138
+ h ? /* @__PURE__ */ d(
139
+ rt,
140
+ {
141
+ initialValue: (e == null ? void 0 : e.initialValue) ?? i.name ?? "",
142
+ isDraft: !!(e != null && e.isDraft),
143
+ onSubmit: (f) => n == null ? void 0 : n(i.path, f),
144
+ onCancel: () => t == null ? void 0 : t()
145
+ }
146
+ ) : /* @__PURE__ */ d(
147
+ "span",
148
+ {
149
+ className: g(
150
+ "truncate",
151
+ w && "text-muted-foreground italic"
152
+ ),
153
+ children: i.name
154
+ }
155
+ ),
156
+ w && !h && /* @__PURE__ */ d(
157
+ Y,
158
+ {
159
+ "data-testid": "file-tree-pending-spinner",
160
+ "aria-label": "Pending",
161
+ className: "ml-auto h-3 w-3 shrink-0 animate-spin text-muted-foreground/70",
162
+ strokeWidth: 2
163
+ }
164
+ )
165
+ ]
166
+ }
167
+ );
168
+ }
169
+ function ht({
170
+ files: r,
171
+ selectedPath: u,
172
+ searchQuery: s,
173
+ height: l = 400,
174
+ editing: e,
175
+ revealPath: a,
176
+ pendingPaths: n,
177
+ onSelect: t,
178
+ onExpand: i,
179
+ onCollapse: m,
180
+ onContextMenu: h,
181
+ onSubmitEdit: w,
182
+ onCancelEdit: F,
183
+ onRevealHandled: f,
184
+ onDragDrop: A,
185
+ className: P
186
+ }) {
187
+ const b = C(null), { nodes: T, dropped: v } = I(
188
+ () => nt(r),
189
+ [r]
190
+ ), L = C(0);
191
+ M(() => {
192
+ v > 0 && v !== L.current && console.warn(
193
+ `[filesystem] dropped ${v} file-tree node(s) with a missing or duplicate path`
194
+ ), L.current = v;
195
+ }, [v]), M(() => {
196
+ if (!(e != null && e.isDraft)) return;
197
+ const o = requestAnimationFrame(() => {
198
+ var c;
199
+ (c = b.current) == null || c.scrollTo(e.path);
200
+ });
201
+ return () => cancelAnimationFrame(o);
202
+ }, [e == null ? void 0 : e.isDraft, e == null ? void 0 : e.path]), M(() => {
203
+ if (!a) return;
204
+ let o = 0;
205
+ const c = requestAnimationFrame(() => {
206
+ const p = b.current;
207
+ if (!p) return;
208
+ p.openParents(a);
209
+ const x = p.get(a);
210
+ x && (x.isInternal && x.open(), o = requestAnimationFrame(() => {
211
+ var N;
212
+ (N = b.current) == null || N.scrollTo(a), f == null || f(a);
213
+ }));
214
+ });
215
+ return () => {
216
+ cancelAnimationFrame(c), cancelAnimationFrame(o);
217
+ };
218
+ }, [r, f, a]);
219
+ const O = I(
220
+ () => u || void 0,
221
+ [u]
222
+ ), R = k(
223
+ (o) => {
224
+ o.data.kind === "file" && (t == null || t(o.data.path));
225
+ },
226
+ [t]
227
+ ), q = k(
228
+ (o) => {
229
+ var p;
230
+ const c = (p = b.current) == null ? void 0 : p.get(o);
231
+ c && (c.isOpen ? i == null || i(c.data.path) : m == null || m(c.data.path));
232
+ },
233
+ [i, m]
234
+ ), z = k(
235
+ (o) => {
236
+ if (!A) return;
237
+ const c = !o.parentNode || o.parentNode.isRoot, p = c ? "." : o.parentNode.data.path;
238
+ if (!(!c && o.parentNode.data.kind !== "dir"))
239
+ for (const x of o.dragNodes) {
240
+ const N = x.data.path;
241
+ if (p === N || p !== "." && p.startsWith(N + "/")) return;
242
+ A(N, p);
243
+ }
244
+ },
245
+ [A]
246
+ ), B = k(
247
+ (o) => {
248
+ if (!o.parentNode || o.parentNode.isRoot) return !1;
249
+ if (o.parentNode.data.kind !== "dir") return !0;
250
+ for (const c of o.dragNodes)
251
+ if (o.parentNode.data.path === c.data.path || o.parentNode.data.path.startsWith(c.data.path + "/")) return !0;
252
+ return !1;
253
+ },
254
+ []
255
+ ), S = k(
256
+ (o, c) => o.data.name.toLowerCase().includes(c.toLowerCase()),
257
+ []
258
+ ), _ = I(
259
+ () => ({
260
+ onContextMenu: h,
261
+ editing: e ?? null,
262
+ pendingPaths: n ?? W,
263
+ onSubmitEdit: w,
264
+ onCancelEdit: F
265
+ }),
266
+ [h, e, n, w, F]
267
+ ), $ = I(
268
+ () => ot(T, s),
269
+ [T, s]
270
+ );
271
+ return T.length === 0 ? /* @__PURE__ */ d(
272
+ "div",
273
+ {
274
+ className: g(
275
+ "flex h-full items-center justify-center text-sm text-muted-foreground",
276
+ P
277
+ ),
278
+ children: "No files"
279
+ }
280
+ ) : $ === 0 ? /* @__PURE__ */ d("div", { className: g("flex h-full items-center justify-center p-6", P), children: /* @__PURE__ */ d(
281
+ G,
282
+ {
283
+ className: "min-h-0 border-0",
284
+ title: "No matching files",
285
+ description: s != null && s.trim() ? `No files match “${s.trim()}”.` : "No files match the current filter."
286
+ }
287
+ ) }) : /* @__PURE__ */ d(j.Provider, { value: _, children: /* @__PURE__ */ d("div", { "data-boring-workspace-part": "file-tree", className: g("file-tree", P), children: /* @__PURE__ */ d(
288
+ K,
289
+ {
290
+ ref: b,
291
+ data: T,
292
+ idAccessor: "path",
293
+ childrenAccessor: "children",
294
+ openByDefault: !1,
295
+ width: "100%",
296
+ height: l,
297
+ rowHeight: 26,
298
+ indent: 14,
299
+ selection: O,
300
+ searchTerm: s ?? "",
301
+ searchMatch: S,
302
+ onActivate: R,
303
+ onToggle: q,
304
+ onMove: z,
305
+ disableDrop: B,
306
+ disableEdit: !0,
307
+ dndManager: et(),
308
+ children: it
309
+ }
310
+ ) }) });
311
+ }
312
+ export {
313
+ ht as FileTree,
314
+ nt as sanitizeFileTree
315
+ };