@tutti-os/workspace-file-reference 0.0.13 → 0.0.15

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/ui/index.js CHANGED
@@ -1,19 +1,22 @@
1
1
  import {
2
+ ROOT_CHILDREN_KEY,
3
+ createReferenceSourcePickerController,
2
4
  normalizeDirectoryPath,
3
5
  useWorkspaceFileReferencePickerView
4
- } from "../chunk-LS7P3J6Z.js";
5
- import "../chunk-PAR2R2G5.js";
6
+ } from "../chunk-KSK6PILG.js";
7
+ import {
8
+ REFERENCE_FILTER_CATEGORIES,
9
+ WORKSPACE_ROOT_GROUP_NODE_ID,
10
+ nodeRefKey,
11
+ selectedReferenceToWorkspaceFileReference
12
+ } from "../chunk-HRC2MZ5H.js";
6
13
 
7
14
  // src/ui/internal/reference/WorkspaceFileReferencePicker.tsx
8
- import { useId } from "react";
9
- import { createPortal } from "react-dom";
10
15
  import {
11
- Button as Button3,
12
- Card,
13
- CardContent,
14
- CardHeader,
15
- CardTitle,
16
- CloseIcon,
16
+ Dialog,
17
+ DialogContent,
18
+ DialogHeader,
19
+ DialogTitle,
17
20
  cn as cn2
18
21
  } from "@tutti-os/ui-system";
19
22
 
@@ -45,7 +48,7 @@ import {
45
48
  } from "@tutti-os/ui-system";
46
49
  import { AddIcon as AddLinedIcon } from "@tutti-os/ui-system/icons";
47
50
  import { jsx, jsxs } from "react/jsx-runtime";
48
- var workspaceFileReferenceTreeIndent = 24;
51
+ var workspaceFileReferenceTreeIndent = 28;
49
52
  var workspaceFileReferenceTreeCollapseDurationMs = 200;
50
53
  function WorkspaceFileReferencePickerBrowserPane({
51
54
  browseRootEntries,
@@ -154,6 +157,12 @@ function WorkspaceFileReferencePickerTreeEntry({
154
157
  }, workspaceFileReferenceTreeCollapseDurationMs);
155
158
  return () => window.clearTimeout(timeoutId);
156
159
  }, [expanded]);
160
+ const handleRowSelect = () => {
161
+ onFocusPath(entry.path);
162
+ if (isFolder) {
163
+ onToggleFolder(entry);
164
+ }
165
+ };
157
166
  const shouldBuildChildContent = expanded || shouldRenderChildContent;
158
167
  const childContent = shouldBuildChildContent ? childState?.loading ? /* @__PURE__ */ jsxs(
159
168
  "div",
@@ -196,14 +205,24 @@ function WorkspaceFileReferencePickerTreeEntry({
196
205
  /* @__PURE__ */ jsxs(
197
206
  "div",
198
207
  {
208
+ "aria-current": focused ? "true" : void 0,
199
209
  className: cn(
200
- "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-2 rounded-[6px] py-1.5 pr-1 pl-2 transition-colors",
210
+ "flex cursor-pointer items-center gap-2 rounded-[6px] py-1.5 pr-1 transition-colors select-none",
201
211
  "nodrag [-webkit-app-region:no-drag]",
202
212
  focused || selected ? "bg-transparency-block" : "hover:bg-transparency-block"
203
213
  ),
214
+ role: "button",
215
+ tabIndex: 0,
204
216
  style: {
205
217
  paddingLeft: `${(childDepth - 1) * workspaceFileReferenceTreeIndent + 8}px`
206
218
  },
219
+ onClick: handleRowSelect,
220
+ onKeyDown: (event) => {
221
+ if (event.key === "Enter" || event.key === " ") {
222
+ event.preventDefault();
223
+ handleRowSelect();
224
+ }
225
+ },
207
226
  children: [
208
227
  isFolder ? /* @__PURE__ */ jsx(
209
228
  "button",
@@ -225,25 +244,11 @@ function WorkspaceFileReferencePickerTreeEntry({
225
244
  }
226
245
  )
227
246
  }
228
- ) : /* @__PURE__ */ jsx("span", { className: "block size-5 shrink-0" }),
229
- /* @__PURE__ */ jsxs(
230
- "button",
231
- {
232
- className: "nodrag flex min-w-0 items-center gap-2 text-left [-webkit-app-region:no-drag]",
233
- type: "button",
234
- onClick: (event) => {
235
- event.stopPropagation();
236
- onFocusPath(entry.path);
237
- if (entry.kind === "folder") {
238
- onToggleFolder(entry);
239
- }
240
- },
241
- children: [
242
- isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 shrink-0 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 shrink-0 text-[var(--text-tertiary)]" }),
243
- /* @__PURE__ */ jsx("span", { className: "truncate text-[13px] text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) })
244
- ]
245
- }
246
- ),
247
+ ) : null,
248
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [
249
+ isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 shrink-0 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 shrink-0 text-[var(--text-tertiary)]" }),
250
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[13px] text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) })
251
+ ] }),
247
252
  /* @__PURE__ */ jsx(
248
253
  Button,
249
254
  {
@@ -252,7 +257,7 @@ function WorkspaceFileReferencePickerTreeEntry({
252
257
  size: "icon-sm",
253
258
  type: "button",
254
259
  variant: "ghost",
255
- className: "nodrag [-webkit-app-region:no-drag]",
260
+ className: "nodrag shrink-0 [-webkit-app-region:no-drag]",
256
261
  onClick: (event) => {
257
262
  event.stopPropagation();
258
263
  onFocusPath(entry.path);
@@ -299,30 +304,29 @@ function WorkspaceFileReferencePickerSearchEntry({
299
304
  return /* @__PURE__ */ jsxs(
300
305
  "div",
301
306
  {
307
+ "aria-current": focused ? "true" : void 0,
302
308
  className: cn(
303
- "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[6px] border py-2.5 pr-1 pl-3 transition-colors",
309
+ "grid cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[6px] border py-2.5 pr-1 pl-3 transition-colors select-none",
304
310
  "nodrag [-webkit-app-region:no-drag]",
305
311
  focused || selected ? "border-border bg-transparency-block" : "border-transparent bg-transparent hover:border-border/70 hover:bg-transparency-block"
306
312
  ),
313
+ role: "button",
314
+ tabIndex: 0,
315
+ onClick: () => onFocusPath(entry.path),
316
+ onKeyDown: (event) => {
317
+ if (event.key === "Enter" || event.key === " ") {
318
+ event.preventDefault();
319
+ onFocusPath(entry.path);
320
+ }
321
+ },
307
322
  children: [
308
- /* @__PURE__ */ jsxs(
309
- "button",
310
- {
311
- className: "nodrag flex min-w-0 items-center gap-3 text-left [-webkit-app-region:no-drag]",
312
- type: "button",
313
- onClick: (event) => {
314
- event.stopPropagation();
315
- onFocusPath(entry.path);
316
- },
317
- children: [
318
- /* @__PURE__ */ jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-[var(--transparency-block)] text-[var(--text-tertiary)]", children: isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 text-[var(--text-tertiary)]" }) }),
319
- /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
320
- /* @__PURE__ */ jsx("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) }),
321
- /* @__PURE__ */ jsx("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: entry.path })
322
- ] })
323
- ]
324
- }
325
- ),
323
+ /* @__PURE__ */ jsxs("div", { className: "nodrag flex min-w-0 items-center gap-3 text-left [-webkit-app-region:no-drag]", children: [
324
+ /* @__PURE__ */ jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-[var(--transparency-block)] text-[var(--text-tertiary)]", children: isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 text-[var(--text-tertiary)]" }) }),
325
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
326
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) }),
327
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: entry.path })
328
+ ] })
329
+ ] }),
326
330
  /* @__PURE__ */ jsx(
327
331
  Button,
328
332
  {
@@ -540,8 +544,6 @@ function WorkspaceFileReferencePickerFeedback2({
540
544
 
541
545
  // src/ui/internal/reference/WorkspaceFileReferencePicker.tsx
542
546
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
543
- var workspaceFileReferencePickerBackdropMotionClassName = "motion-safe:animate-in motion-safe:fade-in-0 motion-safe:duration-[180ms] motion-safe:ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:animate-none";
544
- var workspaceFileReferencePickerPanelMotionClassName = "motion-safe:animate-in motion-safe:fade-in-0 motion-safe:zoom-in-[0.96] motion-safe:duration-[250ms] motion-safe:ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:animate-none";
545
547
  function WorkspaceFileReferencePicker({
546
548
  copy,
547
549
  fileAdapter,
@@ -549,9 +551,9 @@ function WorkspaceFileReferencePicker({
549
551
  onClose,
550
552
  onConfirm,
551
553
  open,
554
+ scoped = false,
552
555
  workspaceId
553
556
  }) {
554
- const titleId = useId();
555
557
  const {
556
558
  browseRootEntries,
557
559
  directoryStateByPath,
@@ -576,52 +578,27 @@ function WorkspaceFileReferencePicker({
576
578
  open,
577
579
  workspaceId
578
580
  });
579
- if (!open) {
580
- return null;
581
- }
582
- const dialog = /* @__PURE__ */ jsx3(
583
- "div",
581
+ return /* @__PURE__ */ jsx3(
582
+ Dialog,
584
583
  {
585
- className: cn2(
586
- "nodrag fixed inset-0 grid place-items-center bg-[var(--backdrop)] px-3 py-4 backdrop-blur-md [-webkit-app-region:no-drag] sm:px-6 sm:py-8",
587
- workspaceFileReferencePickerBackdropMotionClassName
588
- ),
589
- style: { zIndex: "var(--z-panel)" },
590
- onClick: onClose,
584
+ open,
585
+ onOpenChange: (nextOpen) => {
586
+ if (!nextOpen) {
587
+ onClose();
588
+ }
589
+ },
591
590
  children: /* @__PURE__ */ jsxs3(
592
- Card,
591
+ DialogContent,
593
592
  {
594
- "aria-labelledby": titleId,
595
- "aria-modal": "true",
593
+ "aria-describedby": void 0,
596
594
  className: cn2(
597
- "nodrag flex h-[min(88vh,44rem)] w-full max-w-5xl origin-center flex-col gap-0 overflow-hidden border-[var(--line-1)] bg-[var(--background-fronted)] py-0 text-[var(--text-primary)] shadow-panel [-webkit-app-region:no-drag] sm:h-[min(82vh,44rem)]",
598
- workspaceFileReferencePickerPanelMotionClassName
595
+ "nodrag flex h-[min(88vh,44rem)] w-full max-w-5xl flex-col gap-0 overflow-hidden border-[var(--line-1)] bg-[var(--background-fronted)] p-0 text-[var(--text-primary)] shadow-panel [-webkit-app-region:no-drag] sm:h-[min(82vh,44rem)] sm:max-w-5xl"
599
596
  ),
600
- role: "dialog",
601
- onClick: (event) => event.stopPropagation(),
597
+ overlayClassName: cn2("nodrag", scoped && "!absolute"),
598
+ portaled: !scoped,
602
599
  children: [
603
- /* @__PURE__ */ jsx3(
604
- CardHeader,
605
- {
606
- className: "border-b px-4 py-4 sm:px-6 sm:py-5",
607
- style: { borderBottomColor: "var(--line-1)" },
608
- children: /* @__PURE__ */ jsxs3("div", { className: "flex items-start justify-between gap-4", children: [
609
- /* @__PURE__ */ jsx3("div", { className: "min-w-0", children: /* @__PURE__ */ jsx3(CardTitle, { id: titleId, children: copy.t("referencePicker.title") }) }),
610
- /* @__PURE__ */ jsx3(
611
- Button3,
612
- {
613
- "aria-label": copy.t("actions.cancel"),
614
- size: "icon-sm",
615
- type: "button",
616
- variant: "ghost",
617
- onClick: onClose,
618
- children: /* @__PURE__ */ jsx3(CloseIcon, { size: 16 })
619
- }
620
- )
621
- ] })
622
- }
623
- ),
624
- /* @__PURE__ */ jsxs3(CardContent, { className: "grid min-h-0 flex-1 grid-rows-[minmax(0,1fr)_auto] gap-0 overflow-hidden p-0 lg:grid-cols-[minmax(0,1.15fr)_minmax(20rem,0.85fr)] lg:grid-rows-1", children: [
600
+ /* @__PURE__ */ jsx3(DialogHeader, { className: "flex-none border-b border-[var(--line-1)] px-4 py-4 sm:px-6 sm:py-5", children: /* @__PURE__ */ jsx3(DialogTitle, { children: copy.t("referencePicker.title") }) }),
601
+ /* @__PURE__ */ jsxs3("div", { className: "grid min-h-0 flex-1 grid-rows-[minmax(0,1fr)_auto] gap-0 overflow-hidden p-0 lg:grid-cols-[minmax(0,1.15fr)_minmax(20rem,0.85fr)] lg:grid-rows-1", children: [
625
602
  /* @__PURE__ */ jsx3(
626
603
  WorkspaceFileReferencePickerBrowserPane,
627
604
  {
@@ -665,12 +642,1448 @@ function WorkspaceFileReferencePicker({
665
642
  )
666
643
  }
667
644
  );
645
+ }
646
+
647
+ // src/ui/internal/reference/ReferenceSourcePicker.tsx
648
+ import {
649
+ useEffect as useEffect3,
650
+ useId,
651
+ useRef as useRef2,
652
+ useState as useState3
653
+ } from "react";
654
+ import { createPortal } from "react-dom";
655
+ import {
656
+ ArrowRightIcon as ArrowRightIcon2,
657
+ Badge as Badge2,
658
+ Button as Button3,
659
+ Card,
660
+ CardContent,
661
+ CardHeader,
662
+ CardTitle,
663
+ CheckIcon as CheckIcon2,
664
+ ChevronDownIcon,
665
+ CloseIcon,
666
+ FileIcon as FileIcon3,
667
+ FolderFilledIcon as FolderFilledIcon3,
668
+ Input as Input2,
669
+ IssueIcon,
670
+ ResizableHandle,
671
+ ResizablePanel,
672
+ ResizablePanelGroup,
673
+ ScrollArea as ScrollArea2,
674
+ SearchIcon as SearchIcon2,
675
+ Spinner as Spinner2,
676
+ cn as cn3
677
+ } from "@tutti-os/ui-system";
678
+ import { AddIcon } from "@tutti-os/ui-system/icons";
679
+ import {
680
+ WorkspaceFilePreviewSurface
681
+ } from "@tutti-os/workspace-file-preview/react";
682
+
683
+ // src/react/internal/reference/useReferenceSourcePickerView.ts
684
+ import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
685
+ import { useSnapshot } from "valtio";
686
+ import {
687
+ sortWorkspaceFileEntriesForArrangeMode
688
+ } from "@tutti-os/workspace-file-manager/services";
689
+ import {
690
+ createWorkspaceFilePreviewLoadedState
691
+ } from "@tutti-os/workspace-file-preview";
692
+ function useReferenceSourcePickerView({
693
+ aggregator,
694
+ workspaceId,
695
+ open,
696
+ workspaceRootGroupLabel,
697
+ initialTarget = null,
698
+ onClose,
699
+ onConfirm,
700
+ onConfirmBundles
701
+ }) {
702
+ const readSnapshot = useSnapshot;
703
+ const scope = useMemo(() => ({ workspaceId }), [workspaceId]);
704
+ const controller = useMemo(
705
+ () => createReferenceSourcePickerController({ aggregator, scope }),
706
+ [aggregator, scope]
707
+ );
708
+ const snapshot = readSnapshot(controller.store);
709
+ const [breadcrumbBySource, setBreadcrumbBySource] = useState2({});
710
+ const [focusedNode, setFocusedNode] = useState2(null);
711
+ const [arrangeMode, setArrangeMode] = useState2("none");
712
+ const sortNodes = useCallback(
713
+ (nodes) => {
714
+ if (arrangeMode === "none") {
715
+ return [...nodes];
716
+ }
717
+ const byKey = /* @__PURE__ */ new Map();
718
+ const fileEntries = nodes.map((node) => {
719
+ const key = nodeRefKey(node.ref);
720
+ byKey.set(key, node);
721
+ return {
722
+ hasChildren: node.kind === "folder",
723
+ kind: node.kind === "folder" ? "directory" : "file",
724
+ mtimeMs: node.mtimeMs ?? null,
725
+ name: node.displayName,
726
+ path: key,
727
+ sizeBytes: node.sizeBytes ?? null
728
+ };
729
+ });
730
+ return sortWorkspaceFileEntriesForArrangeMode(fileEntries, arrangeMode).map((entry) => byKey.get(entry.path)).filter((node) => node !== void 0);
731
+ },
732
+ [arrangeMode]
733
+ );
734
+ const autoEnteredSourcesRef = useRef(/* @__PURE__ */ new Set());
735
+ const appliedInitialTargetRef = useRef(null);
736
+ useEffect2(() => {
737
+ if (!open) {
738
+ return;
739
+ }
740
+ controller.reset();
741
+ controller.open();
742
+ setBreadcrumbBySource({});
743
+ setFocusedNode(null);
744
+ autoEnteredSourcesRef.current = /* @__PURE__ */ new Set();
745
+ appliedInitialTargetRef.current = null;
746
+ return () => {
747
+ controller.close();
748
+ };
749
+ }, [controller, open]);
750
+ useEffect2(() => {
751
+ const target = initialTarget;
752
+ if (!open || !target || appliedInitialTargetRef.current === target) {
753
+ return;
754
+ }
755
+ appliedInitialTargetRef.current = target;
756
+ autoEnteredSourcesRef.current.add(target.sourceId);
757
+ let canceled = false;
758
+ void controller.locatePath(target).then((path) => {
759
+ if (canceled) {
760
+ return;
761
+ }
762
+ const group = path[0];
763
+ if (!group) {
764
+ autoEnteredSourcesRef.current.delete(target.sourceId);
765
+ return;
766
+ }
767
+ controller.setActiveSource(target.sourceId);
768
+ setBreadcrumbBySource((current) => ({
769
+ ...current,
770
+ [group.ref.sourceId]: [group]
771
+ }));
772
+ controller.ensureChildren(group);
773
+ const deepest = path[path.length - 1];
774
+ setFocusedNode(path.length > 1 && deepest ? deepest : null);
775
+ }).catch(() => {
776
+ autoEnteredSourcesRef.current.delete(target.sourceId);
777
+ });
778
+ return () => {
779
+ canceled = true;
780
+ };
781
+ }, [open, initialTarget, controller]);
782
+ const activeSourceId = snapshot.activeSourceId;
783
+ const activeTab = useMemo(
784
+ () => snapshot.tabs.find((tab) => tab.sourceId === activeSourceId) ?? null,
785
+ [activeSourceId, snapshot.tabs]
786
+ );
787
+ const capabilities = activeTab?.capabilities ?? null;
788
+ const breadcrumb = activeSourceId ? breadcrumbBySource[activeSourceId] ?? [] : [];
789
+ const currentNode = breadcrumb.at(-1) ?? null;
790
+ const currentKey = currentNode ? nodeRefKey(currentNode.ref) : ROOT_CHILDREN_KEY;
791
+ const activeTabState = activeSourceId ? snapshot.bySource[activeSourceId] : void 0;
792
+ const activeFilters = activeTabState?.searchFilters ?? [];
793
+ const isQuery = activeTabState?.mode === "search" && (activeTabState.searchQuery.trim() !== "" || activeFilters.length > 0);
794
+ const currentChildren = activeTabState?.childrenByKey[currentKey];
795
+ const currentEntries = useMemo(
796
+ () => sortNodes(currentChildren?.entries ?? []),
797
+ [currentChildren?.entries, sortNodes]
798
+ );
799
+ const searchResults = useMemo(
800
+ () => sortNodes(activeTabState?.searchEntries ?? []),
801
+ [activeTabState?.searchEntries, sortNodes]
802
+ );
803
+ const sidebarGroupsBySource = useMemo(() => {
804
+ const result = {};
805
+ for (const tab of snapshot.tabs) {
806
+ const sourceId = tab.sourceId;
807
+ const provided = aggregator.getLoadedSource(sourceId)?.listSidebarGroups?.(scope);
808
+ if (provided && provided.length > 0) {
809
+ result[sourceId] = provided;
810
+ continue;
811
+ }
812
+ const root = snapshot.bySource[sourceId]?.childrenByKey[ROOT_CHILDREN_KEY];
813
+ const folders = (root?.entries ?? []).filter(
814
+ (node) => node.kind === "folder"
815
+ );
816
+ if (tab.capabilities.navigable) {
817
+ result[sourceId] = folders;
818
+ } else {
819
+ const workspaceRoot = {
820
+ ref: { sourceId, nodeId: WORKSPACE_ROOT_GROUP_NODE_ID },
821
+ kind: "folder",
822
+ displayName: workspaceRootGroupLabel
823
+ };
824
+ result[sourceId] = [workspaceRoot, ...folders];
825
+ }
826
+ }
827
+ return result;
828
+ }, [
829
+ snapshot.tabs,
830
+ snapshot.bySource,
831
+ aggregator,
832
+ scope,
833
+ workspaceRootGroupLabel
834
+ ]);
835
+ const sidebarHasMoreBySource = useMemo(() => {
836
+ const result = {};
837
+ for (const tab of snapshot.tabs) {
838
+ const sourceId = tab.sourceId;
839
+ const provided = aggregator.getLoadedSource(sourceId)?.listSidebarGroups?.(scope);
840
+ if (provided && provided.length > 0) {
841
+ result[sourceId] = false;
842
+ continue;
843
+ }
844
+ const root = snapshot.bySource[sourceId]?.childrenByKey[ROOT_CHILDREN_KEY];
845
+ result[sourceId] = Boolean(root?.nextCursor);
846
+ }
847
+ return result;
848
+ }, [snapshot.tabs, snapshot.bySource, aggregator, scope]);
849
+ const sidebarLoadingMoreBySource = useMemo(() => {
850
+ const result = {};
851
+ for (const tab of snapshot.tabs) {
852
+ const root = snapshot.bySource[tab.sourceId]?.childrenByKey[ROOT_CHILDREN_KEY];
853
+ result[tab.sourceId] = Boolean(root?.loaded && root.loading);
854
+ }
855
+ return result;
856
+ }, [snapshot.tabs, snapshot.bySource]);
857
+ const sidebarGroups = activeSourceId ? sidebarGroupsBySource[activeSourceId] ?? [] : [];
858
+ const rootGroupNode = breadcrumb[0] ?? null;
859
+ const searchScopeNodeId = rootGroupNode ? rootGroupNode.ref.nodeId : null;
860
+ const selectedGroupKey = rootGroupNode ? nodeRefKey(rootGroupNode.ref) : activeSourceId && !capabilities?.navigable ? nodeRefKey({
861
+ sourceId: activeSourceId,
862
+ nodeId: WORKSPACE_ROOT_GROUP_NODE_ID
863
+ }) : null;
864
+ useEffect2(() => {
865
+ controller.setSearchScope(searchScopeNodeId);
866
+ }, [controller, searchScopeNodeId]);
867
+ const setActiveSource = useCallback(
868
+ (sourceId) => {
869
+ controller.setActiveSource(sourceId);
870
+ setFocusedNode(null);
871
+ },
872
+ [controller]
873
+ );
874
+ const enterFolder = useCallback(
875
+ (node) => {
876
+ const sourceId = node.ref.sourceId;
877
+ if (node.kind !== "folder" || !sourceId || node.ref.nodeId === WORKSPACE_ROOT_GROUP_NODE_ID) {
878
+ return;
879
+ }
880
+ controller.ensureChildren(node);
881
+ setBreadcrumbBySource((current) => {
882
+ const stack = current[sourceId] ?? [];
883
+ const index = stack.findIndex(
884
+ (item) => nodeRefKey(item.ref) === nodeRefKey(node.ref)
885
+ );
886
+ const nextStack = index >= 0 ? stack.slice(0, index + 1) : [...stack, node];
887
+ return { ...current, [sourceId]: nextStack };
888
+ });
889
+ setFocusedNode(null);
890
+ },
891
+ [controller]
892
+ );
893
+ useEffect2(() => {
894
+ if (!open || !activeSourceId) {
895
+ return;
896
+ }
897
+ if (autoEnteredSourcesRef.current.has(activeSourceId)) {
898
+ return;
899
+ }
900
+ const stack = breadcrumbBySource[activeSourceId] ?? [];
901
+ if (stack.length > 0) {
902
+ autoEnteredSourcesRef.current.add(activeSourceId);
903
+ return;
904
+ }
905
+ const firstGroup = sidebarGroups[0];
906
+ if (!firstGroup) {
907
+ return;
908
+ }
909
+ autoEnteredSourcesRef.current.add(activeSourceId);
910
+ enterFolder(firstGroup);
911
+ }, [open, activeSourceId, sidebarGroups, breadcrumbBySource, enterFolder]);
912
+ useEffect2(() => {
913
+ if (!open) {
914
+ return;
915
+ }
916
+ for (const tab of snapshot.tabs) {
917
+ controller.ensureSourceRoot(tab.sourceId);
918
+ }
919
+ }, [open, snapshot.tabs, controller]);
920
+ const navigateToBreadcrumb = useCallback(
921
+ (index) => {
922
+ if (!activeSourceId) {
923
+ return;
924
+ }
925
+ setBreadcrumbBySource((current) => {
926
+ const stack = current[activeSourceId] ?? [];
927
+ return { ...current, [activeSourceId]: stack.slice(0, index + 1) };
928
+ });
929
+ const target = (breadcrumbBySource[activeSourceId] ?? [])[index] ?? null;
930
+ controller.ensureChildren(target);
931
+ setFocusedNode(null);
932
+ },
933
+ [activeSourceId, breadcrumbBySource, controller]
934
+ );
935
+ const navigateToRoot = useCallback(
936
+ (sourceId) => {
937
+ const sid = sourceId ?? activeSourceId;
938
+ if (!sid) {
939
+ return;
940
+ }
941
+ setBreadcrumbBySource((current) => ({ ...current, [sid]: [] }));
942
+ controller.ensureSourceRoot(sid);
943
+ setFocusedNode(null);
944
+ },
945
+ [activeSourceId, controller]
946
+ );
947
+ const selectGroup = useCallback(
948
+ (node) => {
949
+ const sourceId = node.ref.sourceId;
950
+ if (!sourceId) {
951
+ return;
952
+ }
953
+ if (sourceId !== snapshot.activeSourceId) {
954
+ controller.setActiveSource(sourceId);
955
+ }
956
+ if (node.ref.nodeId === WORKSPACE_ROOT_GROUP_NODE_ID) {
957
+ navigateToRoot(sourceId);
958
+ return;
959
+ }
960
+ controller.ensureChildren(node);
961
+ setBreadcrumbBySource((current) => ({ ...current, [sourceId]: [node] }));
962
+ setFocusedNode(null);
963
+ },
964
+ [controller, snapshot.activeSourceId, navigateToRoot]
965
+ );
966
+ const isSelected = useCallback(
967
+ (node) => snapshot.selection.some(
968
+ (item) => nodeRefKey(item.ref) === nodeRefKey(node.ref)
969
+ ),
970
+ [snapshot.selection]
971
+ );
972
+ const [isConfirming, setIsConfirming] = useState2(false);
973
+ const confirm = useCallback(async () => {
974
+ if (isConfirming) {
975
+ return;
976
+ }
977
+ setIsConfirming(true);
978
+ try {
979
+ if (onConfirmBundles) {
980
+ const grouped = await controller.confirmGrouped();
981
+ onConfirmBundles({
982
+ files: grouped.files.map(selectedReferenceToWorkspaceFileReference),
983
+ bundles: grouped.bundles.map((bundle) => ({
984
+ sourceId: bundle.root.ref.sourceId,
985
+ nodeId: bundle.root.ref.nodeId,
986
+ displayName: bundle.root.displayName,
987
+ iconUrl: bundle.root.iconUrl ?? null,
988
+ files: bundle.files.map(selectedReferenceToWorkspaceFileReference)
989
+ }))
990
+ });
991
+ } else {
992
+ const selected = await controller.confirm();
993
+ onConfirm(selected.map(selectedReferenceToWorkspaceFileReference));
994
+ }
995
+ onClose();
996
+ } finally {
997
+ setIsConfirming(false);
998
+ }
999
+ }, [controller, isConfirming, onClose, onConfirm, onConfirmBundles]);
1000
+ const [previewState, setPreviewState] = useState2({
1001
+ status: "empty"
1002
+ });
1003
+ const previewObjectUrlRef = useRef(null);
1004
+ const revokePreviewObjectUrl = useCallback(() => {
1005
+ if (previewObjectUrlRef.current) {
1006
+ URL.revokeObjectURL(previewObjectUrlRef.current);
1007
+ previewObjectUrlRef.current = null;
1008
+ }
1009
+ }, []);
1010
+ useEffect2(() => {
1011
+ const node = focusedNode;
1012
+ if (!node) {
1013
+ revokePreviewObjectUrl();
1014
+ setPreviewState({ status: "empty" });
1015
+ return;
1016
+ }
1017
+ if (node.kind === "folder") {
1018
+ revokePreviewObjectUrl();
1019
+ setPreviewState({ node, status: "directory" });
1020
+ return;
1021
+ }
1022
+ const previewable = aggregator.getLoadedSource(node.ref.sourceId)?.capabilities.previewable ?? false;
1023
+ if (!previewable) {
1024
+ revokePreviewObjectUrl();
1025
+ setPreviewState({ node, status: "unsupported" });
1026
+ return;
1027
+ }
1028
+ let cancelled = false;
1029
+ revokePreviewObjectUrl();
1030
+ setPreviewState({ node, status: "loading" });
1031
+ void (async () => {
1032
+ try {
1033
+ const preview = await aggregator.readPreview(scope, node);
1034
+ if (cancelled) {
1035
+ return;
1036
+ }
1037
+ if (!preview) {
1038
+ setPreviewState({ node, status: "unsupported" });
1039
+ return;
1040
+ }
1041
+ const loaded = createWorkspaceFilePreviewLoadedState({
1042
+ bytes: preview.bytes,
1043
+ contentType: preview.contentType,
1044
+ entry: {
1045
+ kind: node.kind,
1046
+ name: node.displayName,
1047
+ path: node.ref.nodeId,
1048
+ mtimeMs: node.mtimeMs ?? null,
1049
+ sizeBytes: node.sizeBytes ?? null
1050
+ },
1051
+ target: {
1052
+ fileKind: preview.kind,
1053
+ name: node.displayName,
1054
+ path: node.ref.nodeId,
1055
+ mtimeMs: node.mtimeMs ?? null,
1056
+ sizeBytes: node.sizeBytes ?? null
1057
+ }
1058
+ });
1059
+ if (cancelled) {
1060
+ return;
1061
+ }
1062
+ if (loaded.status === "image") {
1063
+ const objectUrl = URL.createObjectURL(
1064
+ new Blob([loaded.bytes], { type: loaded.contentType })
1065
+ );
1066
+ previewObjectUrlRef.current = objectUrl;
1067
+ setPreviewState({ node, objectUrl, status: "image" });
1068
+ return;
1069
+ }
1070
+ if (loaded.status === "text") {
1071
+ setPreviewState({ content: loaded.content, node, status: "text" });
1072
+ return;
1073
+ }
1074
+ setPreviewState({
1075
+ node,
1076
+ reason: loaded.reason,
1077
+ ...loaded.maxSizeBytes == null ? {} : { maxSizeBytes: loaded.maxSizeBytes },
1078
+ status: "readonly"
1079
+ });
1080
+ } catch {
1081
+ if (!cancelled) {
1082
+ setPreviewState({ node, status: "error" });
1083
+ }
1084
+ }
1085
+ })();
1086
+ return () => {
1087
+ cancelled = true;
1088
+ };
1089
+ }, [aggregator, focusedNode, revokePreviewObjectUrl, scope]);
1090
+ useEffect2(() => revokePreviewObjectUrl, [revokePreviewObjectUrl]);
1091
+ return {
1092
+ tabs: snapshot.tabs,
1093
+ activeSourceId,
1094
+ previewState,
1095
+ activeTabLabel: activeTab?.label ?? "",
1096
+ capabilities,
1097
+ // 内容区递归就地树:当前选中二级节点的子条目(本地根时为源根子条目)。
1098
+ currentEntries,
1099
+ // 搜索态:扁平搜索结果。
1100
+ searchResults,
1101
+ expandedKeys: activeTabState?.expandedKeys ?? {},
1102
+ childrenByKey: activeTabState?.childrenByKey ?? {},
1103
+ toggleNode: (node) => controller.toggleNode(node),
1104
+ sortNodes,
1105
+ isLoadingTabs: snapshot.isLoadingTabs,
1106
+ breadcrumb,
1107
+ currentNode,
1108
+ sidebarGroups,
1109
+ sidebarGroupsBySource,
1110
+ sidebarHasMoreBySource,
1111
+ sidebarLoadingMoreBySource,
1112
+ loadMoreSidebarGroups: (sourceId) => controller.loadMoreSourceRoot(sourceId),
1113
+ selectedGroupKey,
1114
+ arrangeMode,
1115
+ setArrangeMode,
1116
+ // 查询态(关键词或筛选任一非空)→ 平铺结果;否则浏览树。
1117
+ isQuery,
1118
+ searchQuery: activeTabState?.searchQuery ?? "",
1119
+ // 当前源支持的文件类型筛选分类(不支持则空数组,picker 据此决定是否展示筛选下拉)。
1120
+ filterCategories: capabilities?.filterable ? REFERENCE_FILTER_CATEGORIES : [],
1121
+ activeFilters,
1122
+ // 搜索态:仅在「还没有任何结果」时显示 spinner;细化关键词(已有结果)时
1123
+ // 保留旧结果直到新结果就绪,避免内容区在 spinner/结果间反复切换造成闪烁。
1124
+ isLoading: isQuery ? (activeTabState?.isSearchLoading ?? false) && (activeTabState?.searchEntries.length ?? 0) === 0 : currentChildren?.loading ?? false,
1125
+ // 查询态:增长式分页是否还有更多;浏览态:cursor 是否有下一页。
1126
+ hasMore: isQuery ? activeTabState?.searchHasMore ?? false : Boolean(currentChildren?.nextCursor),
1127
+ // 底部「加载更多」在途(查询态 = 增长重查;浏览态 = cursor append)。
1128
+ isLoadingMore: isQuery ? activeTabState?.isSearchLoadingMore ?? false : currentChildren?.loading ?? false,
1129
+ focusedNode,
1130
+ selection: snapshot.selection,
1131
+ selectionCount: snapshot.selection.length,
1132
+ setActiveSource,
1133
+ enterFolder,
1134
+ selectGroup,
1135
+ navigateToBreadcrumb,
1136
+ navigateToRoot,
1137
+ setFocusedNode,
1138
+ setSearchQuery: (query) => controller.setSearchQuery(query, searchScopeNodeId),
1139
+ setFilters: (filters) => controller.setSearchFilters(filters, searchScopeNodeId),
1140
+ toggleSelection: (node) => controller.toggleSelection(node),
1141
+ loadMore: () => isQuery ? controller.loadMoreSearch() : controller.loadMore(currentNode),
1142
+ isSelected,
1143
+ confirm,
1144
+ isConfirming
1145
+ };
1146
+ }
1147
+
1148
+ // src/ui/internal/reference/ReferenceSourcePicker.tsx
1149
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1150
+ var SIDEBAR_GROUP_PAGE_SIZE = 20;
1151
+ function autoFitPanelWidth(groupEl, contentEl, panel, trailingPx) {
1152
+ if (!groupEl || !contentEl || !panel) {
1153
+ return;
1154
+ }
1155
+ const groupWidth = groupEl.clientWidth;
1156
+ if (groupWidth <= 0) {
1157
+ return;
1158
+ }
1159
+ const contentLeft = contentEl.getBoundingClientRect().left;
1160
+ const labels = contentEl.querySelectorAll(
1161
+ "[data-autofit-label]"
1162
+ );
1163
+ let maxRight = 0;
1164
+ labels.forEach((label) => {
1165
+ const right = label.getBoundingClientRect().left - contentLeft + label.scrollWidth;
1166
+ if (right > maxRight) {
1167
+ maxRight = right;
1168
+ }
1169
+ });
1170
+ if (maxRight <= 0) {
1171
+ return;
1172
+ }
1173
+ const naturalWidth = maxRight + trailingPx;
1174
+ panel.resize(Math.min(80, naturalWidth / groupWidth * 100));
1175
+ }
1176
+ function ReferenceSourcePicker({
1177
+ aggregator,
1178
+ copy,
1179
+ initialTarget,
1180
+ onClose,
1181
+ onConfirm,
1182
+ onConfirmBundles,
1183
+ open,
1184
+ workspaceId
1185
+ }) {
1186
+ const titleId = useId();
1187
+ const view = useReferenceSourcePickerView({
1188
+ aggregator,
1189
+ workspaceId,
1190
+ open,
1191
+ workspaceRootGroupLabel: copy.t("referencePicker.workspaceRootGroup"),
1192
+ initialTarget,
1193
+ onClose,
1194
+ onConfirm,
1195
+ onConfirmBundles
1196
+ });
1197
+ const activeFilterSet = new Set(view.activeFilters);
1198
+ const toggleFilter = (id) => {
1199
+ const next = new Set(activeFilterSet);
1200
+ if (next.has(id)) {
1201
+ next.delete(id);
1202
+ } else {
1203
+ next.add(id);
1204
+ }
1205
+ view.setFilters([...next]);
1206
+ };
1207
+ const clearFilters = () => view.setFilters([]);
1208
+ const layoutRef = useRef2(null);
1209
+ const sidebarContentRef = useRef2(null);
1210
+ const middleContentRef = useRef2(null);
1211
+ const sidebarPanelRef = useRef2(null);
1212
+ const middlePanelRef = useRef2(null);
1213
+ if (!open) {
1214
+ return null;
1215
+ }
1216
+ const hasSelectedGroup = view.selectedGroupKey != null;
1217
+ const fitSidebar = () => autoFitPanelWidth(
1218
+ layoutRef.current,
1219
+ sidebarContentRef.current,
1220
+ sidebarPanelRef.current,
1221
+ 36
1222
+ );
1223
+ const fitMiddle = () => autoFitPanelWidth(
1224
+ layoutRef.current,
1225
+ middleContentRef.current,
1226
+ middlePanelRef.current,
1227
+ 56
1228
+ );
1229
+ const dialog = /* @__PURE__ */ jsx4(
1230
+ "div",
1231
+ {
1232
+ className: "nodrag fixed inset-0 grid place-items-center bg-[var(--backdrop)] px-3 py-4 backdrop-blur-md [-webkit-app-region:no-drag] sm:px-6 sm:py-8",
1233
+ style: { zIndex: "var(--z-panel)" },
1234
+ onClick: onClose,
1235
+ children: /* @__PURE__ */ jsxs4(
1236
+ Card,
1237
+ {
1238
+ "aria-labelledby": titleId,
1239
+ "aria-modal": "true",
1240
+ className: "nodrag flex h-[min(88vh,46rem)] w-full max-w-5xl flex-col gap-0 overflow-hidden border-[var(--line-1)] bg-[var(--background-fronted)] py-0 text-[var(--text-primary)] shadow-panel [-webkit-app-region:no-drag]",
1241
+ role: "dialog",
1242
+ onClick: (event) => event.stopPropagation(),
1243
+ children: [
1244
+ /* @__PURE__ */ jsx4(CardHeader, { className: "gap-3 px-4 pt-4 pb-4 sm:px-6", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-start justify-between gap-4", children: [
1245
+ /* @__PURE__ */ jsx4(CardTitle, { id: titleId, children: copy.t("referencePicker.title") }),
1246
+ /* @__PURE__ */ jsx4(
1247
+ Button3,
1248
+ {
1249
+ "aria-label": copy.t("actions.cancel"),
1250
+ size: "icon-sm",
1251
+ type: "button",
1252
+ variant: "ghost",
1253
+ onClick: onClose,
1254
+ children: /* @__PURE__ */ jsx4(CloseIcon, { size: 16 })
1255
+ }
1256
+ )
1257
+ ] }) }),
1258
+ /* @__PURE__ */ jsx4(CardContent, { className: "flex min-h-0 flex-1 overflow-hidden border-t border-[var(--line-1)] p-0", children: /* @__PURE__ */ jsx4("div", { ref: layoutRef, className: "flex min-h-0 min-w-0 flex-1", children: /* @__PURE__ */ jsxs4(
1259
+ ResizablePanelGroup,
1260
+ {
1261
+ className: "min-h-0 min-w-0 flex-1",
1262
+ orientation: "horizontal",
1263
+ defaultLayout: { sidebar: 2.5, middle: 4.5, preview: 3 },
1264
+ children: [
1265
+ /* @__PURE__ */ jsx4(
1266
+ ResizablePanel,
1267
+ {
1268
+ id: "sidebar",
1269
+ className: "min-h-0 min-w-0",
1270
+ defaultSize: 15,
1271
+ minSize: "150px",
1272
+ panelRef: (handle) => {
1273
+ sidebarPanelRef.current = handle;
1274
+ },
1275
+ children: /* @__PURE__ */ jsx4(
1276
+ SourceSidebar,
1277
+ {
1278
+ contentRef: sidebarContentRef,
1279
+ copy,
1280
+ view
1281
+ }
1282
+ )
1283
+ }
1284
+ ),
1285
+ /* @__PURE__ */ jsx4(
1286
+ ResizableHandle,
1287
+ {
1288
+ disableDoubleClick: true,
1289
+ withHandle: true,
1290
+ className: "after:bg-[var(--line-1)]",
1291
+ onDoubleClick: fitSidebar
1292
+ }
1293
+ ),
1294
+ /* @__PURE__ */ jsx4(
1295
+ ResizablePanel,
1296
+ {
1297
+ id: "middle",
1298
+ className: "min-h-0 min-w-0",
1299
+ defaultSize: 50,
1300
+ minSize: "260px",
1301
+ panelRef: (handle) => {
1302
+ middlePanelRef.current = handle;
1303
+ },
1304
+ children: /* @__PURE__ */ jsxs4("div", { className: "flex h-full min-h-0 flex-col", children: [
1305
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 border-b border-[var(--line-1)] p-3", children: [
1306
+ /* @__PURE__ */ jsxs4("div", { className: "relative flex-1", children: [
1307
+ /* @__PURE__ */ jsx4(SearchIcon2, { className: "pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-[var(--text-tertiary)]" }),
1308
+ /* @__PURE__ */ jsx4(
1309
+ Input2,
1310
+ {
1311
+ className: "pl-9",
1312
+ placeholder: copy.t(
1313
+ "referencePicker.searchPlaceholder"
1314
+ ),
1315
+ value: view.searchQuery,
1316
+ onChange: (event) => view.setSearchQuery(event.target.value)
1317
+ }
1318
+ )
1319
+ ] }),
1320
+ view.capabilities?.filterable && view.filterCategories.length > 0 ? /* @__PURE__ */ jsx4(
1321
+ FilterCategoryFilter,
1322
+ {
1323
+ categories: view.filterCategories,
1324
+ copy,
1325
+ selected: activeFilterSet,
1326
+ onClear: clearFilters,
1327
+ onToggle: toggleFilter
1328
+ }
1329
+ ) : null
1330
+ ] }),
1331
+ /* @__PURE__ */ jsx4(
1332
+ ScrollArea2,
1333
+ {
1334
+ className: "min-h-0 flex-1",
1335
+ viewportProps: {
1336
+ // 拉到底部(距底 <120px)自动加载更多 —— 查询态走增长式分页,
1337
+ // 浏览态走 cursor 续页。已在加载/无更多时由 loadMore 内部 no-op。
1338
+ onScroll: (event) => {
1339
+ const el = event.currentTarget;
1340
+ if (view.hasMore && !view.isLoading && !view.isLoadingMore && el.scrollHeight - el.scrollTop - el.clientHeight < 120) {
1341
+ view.loadMore();
1342
+ }
1343
+ }
1344
+ },
1345
+ children: /* @__PURE__ */ jsxs4(
1346
+ "div",
1347
+ {
1348
+ ref: middleContentRef,
1349
+ className: "flex flex-col gap-[2px] p-3",
1350
+ children: [
1351
+ view.isLoading ? /* @__PURE__ */ jsx4(Feedback, { children: /* @__PURE__ */ jsx4(Spinner2, { size: 16 }) }) : view.isQuery ? (
1352
+ // 查询态(关键词或筛选):扁平结果
1353
+ view.searchResults.length === 0 ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptySearch") }) : view.searchResults.map((node) => /* @__PURE__ */ jsx4(
1354
+ SearchResultRow,
1355
+ {
1356
+ focused: isFocused(view.focusedNode, node),
1357
+ node,
1358
+ selected: view.isSelected(node),
1359
+ onFocus: view.setFocusedNode,
1360
+ onToggle: view.toggleSelection
1361
+ },
1362
+ nodeRefKey(node.ref)
1363
+ ))
1364
+ ) : !hasSelectedGroup ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.selectGroupHint") }) : view.currentEntries.length === 0 ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptyDirectory") }) : (
1365
+ // 浏览:就地递归展开树(复刻 agent 引用面板文件树交互)
1366
+ view.currentEntries.map((node) => /* @__PURE__ */ jsx4(
1367
+ TreeNodeRow,
1368
+ {
1369
+ copy,
1370
+ depth: 0,
1371
+ node,
1372
+ view
1373
+ },
1374
+ nodeRefKey(node.ref)
1375
+ ))
1376
+ ),
1377
+ view.hasMore && (view.isQuery || hasSelectedGroup) ? /* @__PURE__ */ jsxs4(
1378
+ Button3,
1379
+ {
1380
+ className: "mt-1 w-full",
1381
+ disabled: view.isLoadingMore,
1382
+ size: "sm",
1383
+ type: "button",
1384
+ variant: "ghost",
1385
+ onClick: view.loadMore,
1386
+ children: [
1387
+ view.isLoadingMore ? /* @__PURE__ */ jsx4(Spinner2, { className: "text-current", size: 14 }) : null,
1388
+ copy.t("referencePicker.loadMore")
1389
+ ]
1390
+ }
1391
+ ) : null
1392
+ ]
1393
+ }
1394
+ )
1395
+ }
1396
+ )
1397
+ ] })
1398
+ }
1399
+ ),
1400
+ /* @__PURE__ */ jsx4(
1401
+ ResizableHandle,
1402
+ {
1403
+ disableDoubleClick: true,
1404
+ withHandle: true,
1405
+ className: "after:bg-[var(--line-1)]",
1406
+ onDoubleClick: fitMiddle
1407
+ }
1408
+ ),
1409
+ /* @__PURE__ */ jsx4(
1410
+ ResizablePanel,
1411
+ {
1412
+ id: "preview",
1413
+ className: "min-h-0 min-w-0",
1414
+ defaultSize: 30,
1415
+ minSize: "200px",
1416
+ children: /* @__PURE__ */ jsx4(
1417
+ PreviewInfoPane,
1418
+ {
1419
+ copy,
1420
+ node: view.focusedNode,
1421
+ previewState: view.previewState,
1422
+ sourceLabel: view.activeTabLabel,
1423
+ hierarchy: resolveHierarchyPath(view)
1424
+ }
1425
+ )
1426
+ }
1427
+ )
1428
+ ]
1429
+ }
1430
+ ) }) }),
1431
+ /* @__PURE__ */ jsx4(
1432
+ Footer,
1433
+ {
1434
+ cancelLabel: copy.t("actions.cancel"),
1435
+ confirmLabel: copy.t("referencePicker.confirm"),
1436
+ countLabel: copy.t("referencePicker.selectedCount", {
1437
+ count: view.selectionCount
1438
+ }),
1439
+ disabled: view.selectionCount === 0,
1440
+ loading: view.isConfirming,
1441
+ selection: view.selection,
1442
+ onClose,
1443
+ onConfirm: () => void view.confirm()
1444
+ }
1445
+ )
1446
+ ]
1447
+ }
1448
+ )
1449
+ }
1450
+ );
668
1451
  if (typeof document === "undefined") {
669
1452
  return dialog;
670
1453
  }
671
1454
  return createPortal(dialog, document.body);
672
1455
  }
1456
+ function GroupFallbackIcon({
1457
+ icon,
1458
+ className
1459
+ }) {
1460
+ if (icon === "issue") {
1461
+ return /* @__PURE__ */ jsx4(IssueIcon, { className });
1462
+ }
1463
+ return /* @__PURE__ */ jsx4(FolderFilledIcon3, { className });
1464
+ }
1465
+ function SourceSidebar({
1466
+ copy,
1467
+ view,
1468
+ contentRef
1469
+ }) {
1470
+ const selectedGroupRef = useRef2(null);
1471
+ useEffect3(() => {
1472
+ selectedGroupRef.current?.scrollIntoView({ block: "nearest" });
1473
+ }, [view.selectedGroupKey]);
1474
+ const [shownBySource, setShownBySource] = useState3(
1475
+ {}
1476
+ );
1477
+ const loadMoreGroups = (sourceId) => {
1478
+ const groups = view.sidebarGroupsBySource[sourceId] ?? [];
1479
+ const limit = shownBySource[sourceId] ?? SIDEBAR_GROUP_PAGE_SIZE;
1480
+ const selectedIndex = groups.findIndex(
1481
+ (group) => nodeRefKey(group.ref) === view.selectedGroupKey
1482
+ );
1483
+ const visibleCount = selectedIndex >= limit ? selectedIndex + 1 : limit;
1484
+ const next = visibleCount + SIDEBAR_GROUP_PAGE_SIZE;
1485
+ setShownBySource((prev) => ({ ...prev, [sourceId]: next }));
1486
+ if (next > groups.length && (view.sidebarHasMoreBySource[sourceId] ?? false)) {
1487
+ view.loadMoreSidebarGroups(sourceId);
1488
+ }
1489
+ };
1490
+ return /* @__PURE__ */ jsx4(ScrollArea2, { className: "h-full min-h-0 w-full", children: /* @__PURE__ */ jsx4("div", { ref: contentRef, className: "flex flex-col gap-1 p-2", children: view.tabs.map((tab) => {
1491
+ const groups = view.sidebarGroupsBySource[tab.sourceId] ?? [];
1492
+ const limit = shownBySource[tab.sourceId] ?? SIDEBAR_GROUP_PAGE_SIZE;
1493
+ const selectedIndex = groups.findIndex(
1494
+ (group) => nodeRefKey(group.ref) === view.selectedGroupKey
1495
+ );
1496
+ const effectiveLimit = selectedIndex >= limit ? selectedIndex + 1 : limit;
1497
+ const visibleGroups = groups.slice(0, effectiveLimit);
1498
+ const loadingMore = view.sidebarLoadingMoreBySource[tab.sourceId] ?? false;
1499
+ const hasMore = groups.length > effectiveLimit || (view.sidebarHasMoreBySource[tab.sourceId] ?? false);
1500
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-0.5", children: [
1501
+ /* @__PURE__ */ jsx4(
1502
+ "p",
1503
+ {
1504
+ className: "px-2 pt-1.5 pb-0.5 text-[11px] font-semibold text-[var(--text-tertiary)]",
1505
+ "data-autofit-label": true,
1506
+ children: tab.label
1507
+ }
1508
+ ),
1509
+ groups.length === 0 ? view.isLoadingTabs ? /* @__PURE__ */ jsx4("p", { className: "px-2 py-1 text-[12px] text-[var(--text-tertiary)]", children: "\u2026" }) : null : visibleGroups.map((group) => {
1510
+ const key = nodeRefKey(group.ref);
1511
+ const selected = key === view.selectedGroupKey;
1512
+ return /* @__PURE__ */ jsxs4(
1513
+ "button",
1514
+ {
1515
+ ref: selected ? selectedGroupRef : void 0,
1516
+ "aria-current": selected ? "true" : void 0,
1517
+ className: cn3(
1518
+ "flex items-center gap-2 rounded-[6px] px-2 py-1.5 text-left text-[13px] transition-colors",
1519
+ selected ? "bg-primary/10 font-medium text-[var(--text-primary)] hover:bg-primary/15" : "text-[var(--text-secondary)] hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)]"
1520
+ ),
1521
+ type: "button",
1522
+ onClick: () => view.selectGroup(group),
1523
+ children: [
1524
+ group.iconUrl ? /* @__PURE__ */ jsx4(
1525
+ "img",
1526
+ {
1527
+ alt: "",
1528
+ className: "size-4 shrink-0 rounded-[3px] object-cover",
1529
+ src: group.iconUrl
1530
+ }
1531
+ ) : /* @__PURE__ */ jsx4(
1532
+ GroupFallbackIcon,
1533
+ {
1534
+ className: "size-4 shrink-0 text-[var(--rich-text-folder)]",
1535
+ icon: tab.icon
1536
+ }
1537
+ ),
1538
+ /* @__PURE__ */ jsx4(
1539
+ "span",
1540
+ {
1541
+ className: "min-w-0 flex-1 truncate",
1542
+ "data-autofit-label": true,
1543
+ children: group.displayName
1544
+ }
1545
+ ),
1546
+ group.childCount != null ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[11px] text-[var(--text-tertiary)]", children: group.childCount }) : null
1547
+ ]
1548
+ },
1549
+ key
1550
+ );
1551
+ }),
1552
+ hasMore ? /* @__PURE__ */ jsxs4(
1553
+ "button",
1554
+ {
1555
+ className: "flex items-center gap-1.5 rounded-[6px] px-2 py-1.5 text-left text-[12px] text-[var(--text-secondary)] transition-colors hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)] disabled:opacity-60",
1556
+ disabled: loadingMore,
1557
+ type: "button",
1558
+ onClick: () => loadMoreGroups(tab.sourceId),
1559
+ children: [
1560
+ loadingMore ? /* @__PURE__ */ jsx4(
1561
+ Spinner2,
1562
+ {
1563
+ className: "text-[var(--text-secondary)]",
1564
+ size: 12
1565
+ }
1566
+ ) : null,
1567
+ /* @__PURE__ */ jsx4("span", { children: copy.t("referencePicker.loadMoreGroups") })
1568
+ ]
1569
+ }
1570
+ ) : null
1571
+ ] }, tab.sourceId);
1572
+ }) }) });
1573
+ }
1574
+ function SearchResultRow({
1575
+ node,
1576
+ focused,
1577
+ selected,
1578
+ onFocus,
1579
+ onToggle
1580
+ }) {
1581
+ const isFolder = node.kind === "folder";
1582
+ return /* @__PURE__ */ jsxs4(
1583
+ "div",
1584
+ {
1585
+ className: cn3(
1586
+ "grid cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[6px] border py-2.5 pr-1 pl-3 transition-colors",
1587
+ focused || selected ? "border-border bg-transparency-block" : "border-transparent bg-transparent hover:border-border/70 hover:bg-transparency-block"
1588
+ ),
1589
+ onClick: () => onFocus(node),
1590
+ children: [
1591
+ /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-center gap-3 text-left", children: [
1592
+ /* @__PURE__ */ jsx4("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-[var(--transparency-block)] text-[var(--text-tertiary)]", children: isFolder ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-4 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-4 text-[var(--text-tertiary)]" }) }),
1593
+ /* @__PURE__ */ jsxs4("span", { className: "min-w-0", children: [
1594
+ /* @__PURE__ */ jsx4("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: node.displayName }),
1595
+ /* @__PURE__ */ jsx4("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: node.contextLabel ?? node.ref.nodeId })
1596
+ ] })
1597
+ ] }),
1598
+ /* @__PURE__ */ jsx4(
1599
+ Button3,
1600
+ {
1601
+ "aria-label": node.displayName,
1602
+ "aria-pressed": selected,
1603
+ size: "icon-sm",
1604
+ type: "button",
1605
+ variant: "ghost",
1606
+ onClick: (event) => {
1607
+ event.stopPropagation();
1608
+ onFocus(node);
1609
+ onToggle(node);
1610
+ },
1611
+ children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(AddIcon, { className: "text-[var(--text-secondary)]", size: 16 })
1612
+ }
1613
+ )
1614
+ ]
1615
+ }
1616
+ );
1617
+ }
1618
+ function toPreviewSurfaceState(node, previewState, copy) {
1619
+ if (!("node" in previewState) || nodeRefKey(previewState.node.ref) !== nodeRefKey(node.ref)) {
1620
+ return node.kind === "folder" ? { entry: node, status: "directory" } : { entry: node, status: "loading" };
1621
+ }
1622
+ switch (previewState.status) {
1623
+ case "directory":
1624
+ return { entry: node, status: "directory" };
1625
+ case "loading":
1626
+ return { entry: node, status: "loading" };
1627
+ case "image":
1628
+ return {
1629
+ entry: node,
1630
+ objectUrl: previewState.objectUrl,
1631
+ status: "image"
1632
+ };
1633
+ case "text":
1634
+ return { content: previewState.content, entry: node, status: "text" };
1635
+ case "readonly":
1636
+ return {
1637
+ entry: node,
1638
+ message: previewState.reason === "binary" ? copy.t("referencePicker.previewBinary") : previewState.reason === "file_too_large" || previewState.reason === "text_too_large" ? copy.t("referencePicker.previewTooLarge") : copy.t("referencePicker.previewUnsupported"),
1639
+ status: "readonly"
1640
+ };
1641
+ case "error":
1642
+ return {
1643
+ entry: node,
1644
+ message: copy.t("referencePicker.previewError"),
1645
+ status: "error"
1646
+ };
1647
+ case "unsupported":
1648
+ return {
1649
+ entry: node,
1650
+ message: copy.t("referencePicker.previewUnsupported"),
1651
+ status: "unsupported"
1652
+ };
1653
+ }
1654
+ }
1655
+ function sourceBadgeClassName(sourceId) {
1656
+ return SOURCE_BADGE_CLASSES[sourceId];
1657
+ }
1658
+ var SOURCE_BADGE_CLASSES = {
1659
+ "workspace-file": "bg-[color-mix(in_srgb,var(--rich-text-mention-file)_12%,transparent)] text-[var(--rich-text-mention-file)]",
1660
+ "app-artifact": "bg-[color-mix(in_srgb,var(--rich-text-mention-app)_12%,transparent)] text-[var(--rich-text-mention-app)]",
1661
+ "issue-file": "bg-[color-mix(in_srgb,var(--rich-text-mention-issue)_12%,transparent)] text-[var(--rich-text-mention-issue)]"
1662
+ };
1663
+ function PreviewInfoPane({
1664
+ copy,
1665
+ node,
1666
+ previewState,
1667
+ sourceLabel,
1668
+ hierarchy
1669
+ }) {
1670
+ return /* @__PURE__ */ jsx4("aside", { className: "flex h-full min-h-0 w-full flex-col bg-[var(--background-fronted)]", children: node ? /* @__PURE__ */ jsxs4("div", { className: "flex min-h-0 flex-1 flex-col gap-4 p-4", children: [
1671
+ /* @__PURE__ */ jsx4(
1672
+ WorkspaceFilePreviewSurface,
1673
+ {
1674
+ directoryMessage: copy.t("referencePicker.previewFolder"),
1675
+ emptyMessage: copy.t("referencePicker.emptyPreview"),
1676
+ frameClassName: "flex aspect-[3/2] w-full flex-col items-center justify-center overflow-hidden rounded-[8px] border border-[var(--line-2,var(--border-2))] bg-[var(--transparency-block)] p-0 text-center",
1677
+ imageAlt: (entry) => entry.displayName,
1678
+ imageFrameClassName: "p-3",
1679
+ loadingIndicator: /* @__PURE__ */ jsx4(Spinner2, { size: 16 }),
1680
+ loadingMessage: copy.t("referencePicker.previewLoading"),
1681
+ messageClassName: "mx-auto max-w-[24ch] text-[13px] leading-5 text-[var(--text-secondary)] [overflow-wrap:anywhere]",
1682
+ renderIcon: (entry) => entry.kind === "folder" ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-9 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-9 text-[var(--text-tertiary)]" }),
1683
+ state: toPreviewSurfaceState(node, previewState, copy),
1684
+ textClassName: "h-full w-full overflow-auto p-3 text-left text-[11px] leading-5 whitespace-pre-wrap break-words text-[var(--text-primary)]",
1685
+ textFrameClassName: "items-stretch justify-stretch"
1686
+ }
1687
+ ),
1688
+ /* @__PURE__ */ jsx4("p", { className: "truncate text-[15px] font-semibold", children: node.displayName }),
1689
+ /* @__PURE__ */ jsxs4("dl", { className: "space-y-2 text-[13px]", children: [
1690
+ /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewSource"), children: /* @__PURE__ */ jsx4(
1691
+ Badge2,
1692
+ {
1693
+ variant: "secondary",
1694
+ className: sourceBadgeClassName(node.ref.sourceId),
1695
+ children: sourceLabel
1696
+ }
1697
+ ) }),
1698
+ node.mtimeMs != null ? /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewModified"), children: formatDateTime(node.mtimeMs) }) : null,
1699
+ node.sizeBytes != null ? /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewSize"), children: formatBytes(node.sizeBytes) }) : null
1700
+ ] }),
1701
+ hierarchy.length > 0 ? /* @__PURE__ */ jsxs4("div", { className: "space-y-1.5", children: [
1702
+ /* @__PURE__ */ jsx4("p", { className: "text-[11px] font-semibold text-[var(--text-tertiary)]", children: copy.t("referencePicker.previewHierarchy") }),
1703
+ /* @__PURE__ */ jsx4("div", { className: "flex flex-wrap items-center gap-y-1 text-[12px] leading-5", children: hierarchy.map((crumb, index) => /* @__PURE__ */ jsxs4(
1704
+ "span",
1705
+ {
1706
+ className: "flex min-w-0 items-center",
1707
+ children: [
1708
+ index > 0 ? /* @__PURE__ */ jsx4("span", { className: "mx-1 shrink-0 text-[var(--text-tertiary)]", children: "/" }) : null,
1709
+ /* @__PURE__ */ jsx4(
1710
+ "span",
1711
+ {
1712
+ className: "max-w-[14ch] truncate text-[var(--text-secondary)]",
1713
+ title: crumb.displayName,
1714
+ children: crumb.displayName
1715
+ }
1716
+ )
1717
+ ]
1718
+ },
1719
+ nodeRefKey(crumb.ref)
1720
+ )) })
1721
+ ] }) : null
1722
+ ] }) : /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptyPreview") }) });
1723
+ }
1724
+ function InfoRow({
1725
+ label,
1726
+ children
1727
+ }) {
1728
+ return /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-3", children: [
1729
+ /* @__PURE__ */ jsx4("dt", { className: "text-[var(--text-secondary)]", children: label }),
1730
+ /* @__PURE__ */ jsx4("dd", { className: "min-w-0 truncate text-right text-[var(--text-primary)]", children })
1731
+ ] });
1732
+ }
1733
+ function Footer({
1734
+ cancelLabel,
1735
+ confirmLabel,
1736
+ countLabel,
1737
+ disabled,
1738
+ loading = false,
1739
+ selection,
1740
+ onClose,
1741
+ onConfirm
1742
+ }) {
1743
+ return /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-3 border-t border-[var(--line-1)] px-4 py-3 sm:px-6", children: [
1744
+ /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-center gap-2", children: [
1745
+ /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[13px] text-[var(--text-secondary)]", children: countLabel }),
1746
+ selection.slice(0, 2).map((node) => /* @__PURE__ */ jsx4(
1747
+ Badge2,
1748
+ {
1749
+ className: "min-w-0 max-w-[12rem]",
1750
+ variant: "secondary",
1751
+ children: /* @__PURE__ */ jsx4("span", { className: "truncate", children: node.displayName })
1752
+ },
1753
+ nodeRefKey(node.ref)
1754
+ )),
1755
+ selection.length > 2 ? /* @__PURE__ */ jsxs4(Badge2, { className: "shrink-0", variant: "secondary", children: [
1756
+ "+",
1757
+ selection.length - 2
1758
+ ] }) : null
1759
+ ] }),
1760
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
1761
+ /* @__PURE__ */ jsx4(Button3, { type: "button", variant: "secondary", onClick: onClose, children: cancelLabel }),
1762
+ /* @__PURE__ */ jsxs4(
1763
+ Button3,
1764
+ {
1765
+ disabled: disabled || loading,
1766
+ type: "button",
1767
+ onClick: onConfirm,
1768
+ children: [
1769
+ loading ? /* @__PURE__ */ jsx4(Spinner2, { className: "text-current", size: 14 }) : null,
1770
+ confirmLabel
1771
+ ]
1772
+ }
1773
+ )
1774
+ ] })
1775
+ ] });
1776
+ }
1777
+ function Feedback({ children }) {
1778
+ return /* @__PURE__ */ jsx4("div", { className: "grid min-h-0 flex-1 place-items-center px-4 py-8 text-center text-[13px] text-[var(--text-secondary)]", children });
1779
+ }
1780
+ function FilterCategoryFilter({
1781
+ categories,
1782
+ copy,
1783
+ selected,
1784
+ onClear,
1785
+ onToggle
1786
+ }) {
1787
+ const [open, setOpen] = useState3(false);
1788
+ const containerRef = useRef2(null);
1789
+ useEffect3(() => {
1790
+ if (!open) {
1791
+ return;
1792
+ }
1793
+ const onPointerDown = (event) => {
1794
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
1795
+ setOpen(false);
1796
+ }
1797
+ };
1798
+ const onKeyDown = (event) => {
1799
+ if (event.key === "Escape") {
1800
+ setOpen(false);
1801
+ }
1802
+ };
1803
+ document.addEventListener("pointerdown", onPointerDown, true);
1804
+ document.addEventListener("keydown", onKeyDown);
1805
+ return () => {
1806
+ document.removeEventListener("pointerdown", onPointerDown, true);
1807
+ document.removeEventListener("keydown", onKeyDown);
1808
+ };
1809
+ }, [open]);
1810
+ const selectedLabels = categories.filter((category) => selected.has(category.id)).map((category) => copy.t(category.labelKey));
1811
+ const count = selectedLabels.length;
1812
+ const labelText = count > 0 ? selectedLabels.join(copy.t("referencePicker.fileTypeSeparator")) : copy.t("referencePicker.fileTypeAll");
1813
+ return (
1814
+ // shrink-0:不被搜索框挤压;固定宽度让控件不随选中文案变宽。
1815
+ /* @__PURE__ */ jsxs4("div", { ref: containerRef, className: "relative w-[124px] shrink-0", children: [
1816
+ /* @__PURE__ */ jsxs4(
1817
+ Button3,
1818
+ {
1819
+ "aria-expanded": open,
1820
+ "aria-haspopup": "menu",
1821
+ className: "h-8 w-full justify-between gap-1.5 border-0 px-2.5",
1822
+ size: "default",
1823
+ type: "button",
1824
+ variant: "secondary",
1825
+ onClick: () => setOpen((current) => !current),
1826
+ children: [
1827
+ /* @__PURE__ */ jsx4("span", { className: "min-w-0 flex-1 truncate text-left", title: labelText, children: labelText }),
1828
+ count > 1 ? /* @__PURE__ */ jsx4(Badge2, { className: "shrink-0 px-1.5", variant: "secondary", children: count }) : null,
1829
+ count > 0 ? (
1830
+ // 有选中时,右侧箭头位让位给「清除」:role=button 的 span(避免 button 嵌
1831
+ // button 的非法结构),stopPropagation 让点击只清空筛选、不触发触发器的开合。
1832
+ /* @__PURE__ */ jsx4(
1833
+ "span",
1834
+ {
1835
+ "aria-label": copy.t("referencePicker.clearFilter"),
1836
+ className: "grid size-4 shrink-0 cursor-pointer place-items-center rounded-full text-[var(--text-tertiary)] transition-colors hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)]",
1837
+ role: "button",
1838
+ tabIndex: 0,
1839
+ onClick: (event) => {
1840
+ event.stopPropagation();
1841
+ onClear();
1842
+ },
1843
+ onKeyDown: (event) => {
1844
+ if (event.key === "Enter" || event.key === " ") {
1845
+ event.preventDefault();
1846
+ event.stopPropagation();
1847
+ onClear();
1848
+ }
1849
+ },
1850
+ children: /* @__PURE__ */ jsx4(CloseIcon, { size: 12 })
1851
+ }
1852
+ )
1853
+ ) : /* @__PURE__ */ jsx4(ChevronDownIcon, { className: "size-3.5 shrink-0 text-[var(--text-tertiary)]" })
1854
+ ]
1855
+ }
1856
+ ),
1857
+ open ? /* @__PURE__ */ jsx4(
1858
+ "div",
1859
+ {
1860
+ className: "absolute top-[calc(100%+4px)] right-0 min-w-40 overflow-hidden rounded-[8px] border border-[var(--line-1)] bg-[var(--background-fronted)] p-1 shadow-panel",
1861
+ role: "menu",
1862
+ style: { zIndex: "var(--z-panel-popover)" },
1863
+ children: categories.map((category) => {
1864
+ const checked = selected.has(category.id);
1865
+ return /* @__PURE__ */ jsxs4(
1866
+ "button",
1867
+ {
1868
+ "aria-checked": checked,
1869
+ className: "flex w-full items-center gap-2 rounded-[6px] px-2 py-1.5 text-left text-[13px] text-[var(--text-primary)] transition-colors hover:bg-[var(--transparency-hover)]",
1870
+ role: "menuitemcheckbox",
1871
+ type: "button",
1872
+ onClick: () => onToggle(category.id),
1873
+ children: [
1874
+ /* @__PURE__ */ jsx4("span", { className: "grid size-4 shrink-0 place-items-center", children: checked ? /* @__PURE__ */ jsx4(CheckIcon2, { className: "size-3.5 text-[var(--tutti-purple)]" }) : null }),
1875
+ /* @__PURE__ */ jsx4("span", { className: "flex-1", children: copy.t(category.labelKey) })
1876
+ ]
1877
+ },
1878
+ category.id
1879
+ );
1880
+ })
1881
+ }
1882
+ ) : null
1883
+ ] })
1884
+ );
1885
+ }
1886
+ var TREE_INDENT = 28;
1887
+ var TREE_COLLAPSE_DURATION_MS = 200;
1888
+ function isFocused(focused, node) {
1889
+ return focused ? nodeRefKey(focused.ref) === nodeRefKey(node.ref) : false;
1890
+ }
1891
+ function resolveHierarchyPath(view) {
1892
+ const focused = view.focusedNode;
1893
+ if (!focused) {
1894
+ return view.breadcrumb;
1895
+ }
1896
+ const trail = findFocusedTrail(
1897
+ view.currentEntries,
1898
+ view.childrenByKey,
1899
+ nodeRefKey(focused.ref),
1900
+ []
1901
+ );
1902
+ return trail ? [...view.breadcrumb, ...trail] : view.breadcrumb;
1903
+ }
1904
+ function findFocusedTrail(roots, childrenByKey, targetKey, trail) {
1905
+ for (const node of roots) {
1906
+ if (nodeRefKey(node.ref) === targetKey) {
1907
+ return [...trail];
1908
+ }
1909
+ if (node.kind === "folder") {
1910
+ const children = childrenByKey[nodeRefKey(node.ref)]?.entries ?? [];
1911
+ if (children.length > 0) {
1912
+ const found = findFocusedTrail(children, childrenByKey, targetKey, [
1913
+ ...trail,
1914
+ node
1915
+ ]);
1916
+ if (found) {
1917
+ return found;
1918
+ }
1919
+ }
1920
+ }
1921
+ }
1922
+ return null;
1923
+ }
1924
+ function TreeNodeRow({
1925
+ node,
1926
+ depth,
1927
+ view,
1928
+ copy
1929
+ }) {
1930
+ const key = nodeRefKey(node.ref);
1931
+ const isFolder = node.kind === "folder";
1932
+ const expanded = view.expandedKeys[key] ?? false;
1933
+ const childState = view.childrenByKey[key];
1934
+ const childEntries = view.sortNodes(childState?.entries ?? []);
1935
+ const selected = view.isSelected(node);
1936
+ const focused = isFocused(view.focusedNode, node);
1937
+ const [shouldRenderChildContent, setShouldRenderChildContent] = useState3(expanded);
1938
+ useEffect3(() => {
1939
+ if (expanded) {
1940
+ setShouldRenderChildContent(true);
1941
+ return;
1942
+ }
1943
+ const timeoutId = window.setTimeout(() => {
1944
+ setShouldRenderChildContent(false);
1945
+ }, TREE_COLLAPSE_DURATION_MS);
1946
+ return () => window.clearTimeout(timeoutId);
1947
+ }, [expanded]);
1948
+ const shouldBuildChildContent = expanded || shouldRenderChildContent;
1949
+ const childContent = shouldBuildChildContent ? childState?.loading ? /* @__PURE__ */ jsxs4(
1950
+ "div",
1951
+ {
1952
+ className: "flex items-center gap-2 px-2 py-2 text-[11px] text-[var(--text-secondary)]",
1953
+ style: { paddingLeft: `${(depth + 1) * TREE_INDENT + 12}px` },
1954
+ children: [
1955
+ /* @__PURE__ */ jsx4(Spinner2, { className: "text-[var(--text-secondary)]", size: 14 }),
1956
+ /* @__PURE__ */ jsx4("span", { children: copy.t("referencePicker.loading") })
1957
+ ]
1958
+ }
1959
+ ) : childEntries.length > 0 ? /* @__PURE__ */ jsx4("div", { className: "space-y-0.5", children: childEntries.map((child) => /* @__PURE__ */ jsx4(
1960
+ TreeNodeRow,
1961
+ {
1962
+ copy,
1963
+ depth: depth + 1,
1964
+ node: child,
1965
+ view
1966
+ },
1967
+ nodeRefKey(child.ref)
1968
+ )) }) : childState?.loaded ? /* @__PURE__ */ jsx4(
1969
+ "div",
1970
+ {
1971
+ className: "px-2 py-2 text-[11px] text-[var(--text-secondary)]",
1972
+ style: { paddingLeft: `${(depth + 1) * TREE_INDENT + 12}px` },
1973
+ children: copy.t("referencePicker.emptyDirectory")
1974
+ }
1975
+ ) : null : null;
1976
+ return /* @__PURE__ */ jsxs4("div", { children: [
1977
+ /* @__PURE__ */ jsxs4(
1978
+ "div",
1979
+ {
1980
+ className: cn3(
1981
+ "flex cursor-pointer items-center gap-2 rounded-[6px] py-1.5 pr-1 transition-colors",
1982
+ focused || selected ? "bg-transparency-block" : "hover:bg-transparency-block"
1983
+ ),
1984
+ style: { paddingLeft: `${depth * TREE_INDENT + 8}px` },
1985
+ onClick: () => {
1986
+ view.setFocusedNode(node);
1987
+ if (isFolder) {
1988
+ view.toggleNode(node);
1989
+ }
1990
+ },
1991
+ children: [
1992
+ isFolder ? /* @__PURE__ */ jsx4(
1993
+ "button",
1994
+ {
1995
+ "aria-label": node.displayName,
1996
+ className: "grid size-5 shrink-0 place-items-center rounded-sm text-[var(--text-secondary)] hover:bg-[var(--transparency-hover)]",
1997
+ type: "button",
1998
+ onClick: (event) => {
1999
+ event.stopPropagation();
2000
+ view.setFocusedNode(node);
2001
+ view.toggleNode(node);
2002
+ },
2003
+ children: /* @__PURE__ */ jsx4(
2004
+ ArrowRightIcon2,
2005
+ {
2006
+ className: cn3(
2007
+ "size-3.5 transition-transform",
2008
+ expanded && "rotate-90"
2009
+ )
2010
+ }
2011
+ )
2012
+ }
2013
+ ) : null,
2014
+ isFolder ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-4 shrink-0 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-4 shrink-0 text-[var(--text-tertiary)]" }),
2015
+ /* @__PURE__ */ jsx4(
2016
+ "span",
2017
+ {
2018
+ className: "min-w-0 flex-1 truncate text-[13px] text-[var(--text-primary)]",
2019
+ "data-autofit-label": true,
2020
+ children: node.displayName
2021
+ }
2022
+ ),
2023
+ /* @__PURE__ */ jsx4(
2024
+ Button3,
2025
+ {
2026
+ "aria-label": node.displayName,
2027
+ "aria-pressed": selected,
2028
+ className: "shrink-0",
2029
+ size: "icon-sm",
2030
+ type: "button",
2031
+ variant: "ghost",
2032
+ onClick: (event) => {
2033
+ event.stopPropagation();
2034
+ view.setFocusedNode(node);
2035
+ view.toggleSelection(node);
2036
+ },
2037
+ children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(AddIcon, { className: "text-[var(--text-secondary)]", size: 16 })
2038
+ }
2039
+ )
2040
+ ]
2041
+ }
2042
+ ),
2043
+ isFolder ? /* @__PURE__ */ jsx4(
2044
+ "div",
2045
+ {
2046
+ className: cn3(
2047
+ "grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none",
2048
+ expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
2049
+ childContent && "mt-[2px]"
2050
+ ),
2051
+ children: /* @__PURE__ */ jsx4(
2052
+ "div",
2053
+ {
2054
+ "aria-hidden": expanded ? void 0 : "true",
2055
+ className: cn3(
2056
+ "min-h-0 overflow-hidden transition-[opacity,transform] duration-200 ease-out motion-reduce:transition-none",
2057
+ expanded ? "translate-y-0 opacity-100" : "-translate-y-1 opacity-0"
2058
+ ),
2059
+ inert: expanded ? void 0 : true,
2060
+ children: childContent
2061
+ }
2062
+ )
2063
+ }
2064
+ ) : null
2065
+ ] });
2066
+ }
2067
+ function formatDateTime(ms) {
2068
+ const date = new Date(ms);
2069
+ const pad = (value) => String(value).padStart(2, "0");
2070
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
2071
+ }
2072
+ function formatBytes(bytes) {
2073
+ if (bytes < 1024) {
2074
+ return `${bytes} B`;
2075
+ }
2076
+ const units = ["KB", "MB", "GB"];
2077
+ let value = bytes / 1024;
2078
+ let unitIndex = 0;
2079
+ while (value >= 1024 && unitIndex < units.length - 1) {
2080
+ value /= 1024;
2081
+ unitIndex += 1;
2082
+ }
2083
+ return `${value.toFixed(1)} ${units[unitIndex]}`;
2084
+ }
673
2085
  export {
2086
+ ReferenceSourcePicker,
674
2087
  WorkspaceFileReferencePicker
675
2088
  };
676
2089
  //# sourceMappingURL=index.js.map