@tutti-os/workspace-file-manager 0.0.95 → 0.0.96
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/index.d.ts
CHANGED
|
@@ -26,10 +26,11 @@ interface WorkspaceFileManagerProps {
|
|
|
26
26
|
hostOs?: NodeJS.Platform;
|
|
27
27
|
i18n: WorkspaceFileManagerI18nRuntime;
|
|
28
28
|
session: WorkspaceFileManagerSession;
|
|
29
|
+
showInternalOpenWithActions?: boolean;
|
|
29
30
|
showPreviewPanel?: boolean;
|
|
30
31
|
surface?: "card" | "embedded";
|
|
31
32
|
}
|
|
32
|
-
declare function WorkspaceFileManager({ className, dateLocale, entryDragMode, i18n, onCopyEntry, onCopyPath, onDirectoryExpanded, onEntryDragStart, openInAppBrowserIcon, resolveOpenWithApplicationIcon, resolveEntryIconUrl, renderExternalLocationContent, hostOs, session, showPreviewPanel, surface }: WorkspaceFileManagerProps): ReactElement;
|
|
33
|
+
declare function WorkspaceFileManager({ className, dateLocale, entryDragMode, i18n, onCopyEntry, onCopyPath, onDirectoryExpanded, onEntryDragStart, openInAppBrowserIcon, resolveOpenWithApplicationIcon, resolveEntryIconUrl, renderExternalLocationContent, hostOs, session, showInternalOpenWithActions, showPreviewPanel, surface }: WorkspaceFileManagerProps): ReactElement;
|
|
33
34
|
|
|
34
35
|
interface WorkspaceFileManagerContextMenuProps {
|
|
35
36
|
busy: boolean;
|
package/dist/index.js
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
workspaceFileSearchEntryToEntry,
|
|
34
34
|
workspaceFileTextMaxBytes,
|
|
35
35
|
writeWorkspaceFileManagerArrangeMode
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-KVP3Z2Y5.js";
|
|
37
37
|
import {
|
|
38
38
|
createWorkspaceFileManagerI18nRuntime,
|
|
39
39
|
resolveRevealInFolderLabel,
|
|
@@ -57,6 +57,7 @@ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } fro
|
|
|
57
57
|
|
|
58
58
|
// src/ui/WorkspaceFileManagerContextMenu.tsx
|
|
59
59
|
import {
|
|
60
|
+
ArrowLeftIcon,
|
|
60
61
|
ArrowRightIcon,
|
|
61
62
|
CopyIcon,
|
|
62
63
|
DeleteIcon,
|
|
@@ -85,6 +86,38 @@ import {
|
|
|
85
86
|
var CONTEXT_MENU_ITEM_HEIGHT_PX = 32;
|
|
86
87
|
var CONTEXT_MENU_PADDING_PX = 8;
|
|
87
88
|
var CONTEXT_MENU_SUBMENU_GAP_PX = 4;
|
|
89
|
+
var CONTEXT_MENU_VIEWPORT_PADDING_PX = 12;
|
|
90
|
+
var OPEN_WITH_SUBMENU_WIDTH_PX = 220;
|
|
91
|
+
function resolveOpenWithSubmenuPlacement(input) {
|
|
92
|
+
const gap = input.gap ?? CONTEXT_MENU_SUBMENU_GAP_PX;
|
|
93
|
+
const padding = input.padding ?? CONTEXT_MENU_VIEWPORT_PADDING_PX;
|
|
94
|
+
const availableWidth = Math.max(0, input.viewportWidth - padding * 2);
|
|
95
|
+
const width = Math.min(
|
|
96
|
+
input.submenuWidth ?? OPEN_WITH_SUBMENU_WIDTH_PX,
|
|
97
|
+
availableWidth
|
|
98
|
+
);
|
|
99
|
+
const rightLeft = input.triggerRight + gap;
|
|
100
|
+
const leftLeft = input.triggerLeft - gap - width;
|
|
101
|
+
const rightFits = rightLeft + width <= input.viewportWidth - padding;
|
|
102
|
+
const leftFits = leftLeft >= padding;
|
|
103
|
+
const mode = rightFits ? "right" : leftFits ? "left" : "overlay";
|
|
104
|
+
const rawLeft = mode === "right" ? rightLeft : mode === "left" ? leftLeft : input.parentMenuLeft;
|
|
105
|
+
const rawTop = mode === "overlay" ? input.parentMenuTop : input.triggerTop;
|
|
106
|
+
const overlayHeaderHeight = mode === "overlay" ? input.overlayHeaderHeight ?? CONTEXT_MENU_ITEM_HEIGHT_PX : 0;
|
|
107
|
+
const availableHeight = Math.max(0, input.viewportHeight - padding * 2);
|
|
108
|
+
const height = Math.min(
|
|
109
|
+
Math.max(0, input.submenuHeight + overlayHeaderHeight),
|
|
110
|
+
availableHeight
|
|
111
|
+
);
|
|
112
|
+
const maxLeft = Math.max(padding, input.viewportWidth - padding - width);
|
|
113
|
+
const maxTop = Math.max(padding, input.viewportHeight - padding - height);
|
|
114
|
+
return {
|
|
115
|
+
left: Math.max(padding, Math.min(rawLeft, maxLeft)),
|
|
116
|
+
mode,
|
|
117
|
+
top: Math.max(padding, Math.min(rawTop, maxTop)),
|
|
118
|
+
width
|
|
119
|
+
};
|
|
120
|
+
}
|
|
88
121
|
function clampContextMenuPosition(input) {
|
|
89
122
|
const padding = input.padding ?? CONTEXT_MENU_PADDING_PX;
|
|
90
123
|
const maxX = Math.max(
|
|
@@ -108,7 +141,7 @@ function estimateOpenWithSubmenuHeight(input) {
|
|
|
108
141
|
if (input.showOpenInAppBrowser) {
|
|
109
142
|
itemCount += 1;
|
|
110
143
|
}
|
|
111
|
-
if (input
|
|
144
|
+
if (shouldShowOpenWithSectionDivider(input)) {
|
|
112
145
|
itemCount += 1;
|
|
113
146
|
}
|
|
114
147
|
if (input.isLoading) {
|
|
@@ -123,6 +156,9 @@ function estimateOpenWithSubmenuHeight(input) {
|
|
|
123
156
|
}
|
|
124
157
|
return itemCount * CONTEXT_MENU_ITEM_HEIGHT_PX + CONTEXT_MENU_PADDING_PX * 2;
|
|
125
158
|
}
|
|
159
|
+
function shouldShowOpenWithSectionDivider(input) {
|
|
160
|
+
return input.showExternalSection && (input.showOpenInFileViewer === true || input.showOpenInAppBrowser);
|
|
161
|
+
}
|
|
126
162
|
|
|
127
163
|
// src/ui/WorkspaceFileManagerContextMenu.tsx
|
|
128
164
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -337,6 +373,7 @@ function WorkspaceFileManagerContextMenu({
|
|
|
337
373
|
MenuSurface,
|
|
338
374
|
{
|
|
339
375
|
ref: contextMenuRef,
|
|
376
|
+
"data-workspace-file-manager-context-menu": "",
|
|
340
377
|
className: cn(
|
|
341
378
|
"w-[220px] overflow-visible p-1",
|
|
342
379
|
positionMode === "viewport" ? "fixed" : "absolute"
|
|
@@ -512,10 +549,23 @@ function OpenWithMenuItem({
|
|
|
512
549
|
onOpenWithOtherApplication
|
|
513
550
|
}) {
|
|
514
551
|
const triggerRef = useRef(null);
|
|
552
|
+
const triggerButtonRef = useRef(null);
|
|
553
|
+
const submenuRef = useRef(null);
|
|
554
|
+
const focusSubmenuOnOpenRef = useRef(false);
|
|
515
555
|
const [open, setOpen] = useState(false);
|
|
516
|
-
const [submenuPosition, setSubmenuPosition] = useState({
|
|
556
|
+
const [submenuPosition, setSubmenuPosition] = useState({
|
|
557
|
+
left: 0,
|
|
558
|
+
mode: "right",
|
|
559
|
+
top: 0,
|
|
560
|
+
width: OPEN_WITH_SUBMENU_WIDTH_PX
|
|
561
|
+
});
|
|
517
562
|
const closeTimerRef = useRef(null);
|
|
518
563
|
const showExternalSection = showOpenInDefaultBrowser || showOpenWithOther || isLoading || applications.length > 0;
|
|
564
|
+
const showSectionDivider = shouldShowOpenWithSectionDivider({
|
|
565
|
+
showExternalSection,
|
|
566
|
+
showOpenInAppBrowser,
|
|
567
|
+
showOpenInFileViewer
|
|
568
|
+
});
|
|
519
569
|
const estimatedSubmenuHeight = estimateOpenWithSubmenuHeight({
|
|
520
570
|
applicationCount: applications.length,
|
|
521
571
|
isLoading,
|
|
@@ -539,9 +589,40 @@ function OpenWithMenuItem({
|
|
|
539
589
|
}, 120);
|
|
540
590
|
}, [cancelClose]);
|
|
541
591
|
const openSubmenu = useCallback(() => {
|
|
592
|
+
focusSubmenuOnOpenRef.current = false;
|
|
542
593
|
cancelClose();
|
|
543
594
|
setOpen(true);
|
|
544
595
|
}, [cancelClose]);
|
|
596
|
+
const closeSubmenuToTrigger = useCallback(() => {
|
|
597
|
+
cancelClose();
|
|
598
|
+
setOpen(false);
|
|
599
|
+
triggerButtonRef.current?.focus();
|
|
600
|
+
}, [cancelClose]);
|
|
601
|
+
const updateSubmenuPosition = useCallback(() => {
|
|
602
|
+
const trigger = triggerRef.current;
|
|
603
|
+
if (!trigger) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
607
|
+
const parentMenuRect = trigger.closest("[data-workspace-file-manager-context-menu]")?.getBoundingClientRect() ?? triggerRect;
|
|
608
|
+
const maxHeight = Math.min(
|
|
609
|
+
estimatedSubmenuHeight,
|
|
610
|
+
480,
|
|
611
|
+
Math.max(0, window.innerHeight - 24)
|
|
612
|
+
);
|
|
613
|
+
setSubmenuPosition(
|
|
614
|
+
resolveOpenWithSubmenuPlacement({
|
|
615
|
+
parentMenuLeft: parentMenuRect.left,
|
|
616
|
+
parentMenuTop: parentMenuRect.top,
|
|
617
|
+
submenuHeight: maxHeight,
|
|
618
|
+
triggerLeft: triggerRect.left,
|
|
619
|
+
triggerRight: triggerRect.right,
|
|
620
|
+
triggerTop: triggerRect.top,
|
|
621
|
+
viewportHeight: window.innerHeight,
|
|
622
|
+
viewportWidth: window.innerWidth
|
|
623
|
+
})
|
|
624
|
+
);
|
|
625
|
+
}, [estimatedSubmenuHeight]);
|
|
545
626
|
useEffect(() => {
|
|
546
627
|
return () => {
|
|
547
628
|
cancelClose();
|
|
@@ -551,21 +632,31 @@ function OpenWithMenuItem({
|
|
|
551
632
|
if (!open) {
|
|
552
633
|
return;
|
|
553
634
|
}
|
|
554
|
-
|
|
555
|
-
|
|
635
|
+
updateSubmenuPosition();
|
|
636
|
+
}, [open, updateSubmenuPosition]);
|
|
637
|
+
useEffect(() => {
|
|
638
|
+
if (!open) {
|
|
556
639
|
return;
|
|
557
640
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
641
|
+
window.addEventListener("resize", updateSubmenuPosition);
|
|
642
|
+
window.addEventListener("scroll", updateSubmenuPosition, true);
|
|
643
|
+
return () => {
|
|
644
|
+
window.removeEventListener("resize", updateSubmenuPosition);
|
|
645
|
+
window.removeEventListener("scroll", updateSubmenuPosition, true);
|
|
646
|
+
};
|
|
647
|
+
}, [open, updateSubmenuPosition]);
|
|
648
|
+
useEffect(() => {
|
|
649
|
+
if (!open || !focusSubmenuOnOpenRef.current) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
focusSubmenuOnOpenRef.current = false;
|
|
653
|
+
const frame = window.requestAnimationFrame(() => {
|
|
654
|
+
submenuRef.current?.querySelector("button:not(:disabled)")?.focus();
|
|
567
655
|
});
|
|
568
|
-
|
|
656
|
+
return () => {
|
|
657
|
+
window.cancelAnimationFrame(frame);
|
|
658
|
+
};
|
|
659
|
+
}, [open, submenuPosition.mode]);
|
|
569
660
|
return /* @__PURE__ */ jsxs(
|
|
570
661
|
"div",
|
|
571
662
|
{
|
|
@@ -577,6 +668,7 @@ function OpenWithMenuItem({
|
|
|
577
668
|
/* @__PURE__ */ jsxs(
|
|
578
669
|
"button",
|
|
579
670
|
{
|
|
671
|
+
ref: triggerButtonRef,
|
|
580
672
|
"aria-expanded": open,
|
|
581
673
|
"aria-haspopup": "menu",
|
|
582
674
|
className: cn(
|
|
@@ -586,15 +678,28 @@ function OpenWithMenuItem({
|
|
|
586
678
|
disabled: busy,
|
|
587
679
|
role: "menuitem",
|
|
588
680
|
type: "button",
|
|
589
|
-
onClick: () => {
|
|
681
|
+
onClick: (event) => {
|
|
682
|
+
focusSubmenuOnOpenRef.current = event.detail === 0;
|
|
590
683
|
setOpen((current) => {
|
|
591
684
|
const next = !current;
|
|
592
685
|
if (next) {
|
|
593
686
|
cancelClose();
|
|
687
|
+
} else {
|
|
688
|
+
focusSubmenuOnOpenRef.current = false;
|
|
594
689
|
}
|
|
595
690
|
return next;
|
|
596
691
|
});
|
|
597
692
|
},
|
|
693
|
+
onKeyDown: (event) => {
|
|
694
|
+
if (event.key !== "ArrowRight") {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
event.preventDefault();
|
|
698
|
+
event.stopPropagation();
|
|
699
|
+
focusSubmenuOnOpenRef.current = true;
|
|
700
|
+
cancelClose();
|
|
701
|
+
setOpen(true);
|
|
702
|
+
},
|
|
598
703
|
children: [
|
|
599
704
|
/* @__PURE__ */ jsx("span", { className: "grid size-4 flex-none place-items-center text-[var(--text-secondary)]", children: /* @__PURE__ */ jsx(LaunchIcon, { className: "size-4" }) }),
|
|
600
705
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: copy.t("openWithLabel") }),
|
|
@@ -612,9 +717,11 @@ function OpenWithMenuItem({
|
|
|
612
717
|
/* @__PURE__ */ jsxs(
|
|
613
718
|
ViewportMenuSurface,
|
|
614
719
|
{
|
|
615
|
-
|
|
720
|
+
ref: submenuRef,
|
|
721
|
+
"aria-label": copy.t("openWithLabel"),
|
|
722
|
+
"data-workspace-file-manager-submenu-mode": submenuPosition.mode,
|
|
616
723
|
open,
|
|
617
|
-
className: "
|
|
724
|
+
className: "max-h-[min(480px,calc(100vh-24px))] max-w-[calc(100vw-24px)] overflow-y-auto p-1",
|
|
618
725
|
dismissOnEscape: false,
|
|
619
726
|
dismissOnPointerDownOutside: false,
|
|
620
727
|
dismissOnScroll: false,
|
|
@@ -622,14 +729,55 @@ function OpenWithMenuItem({
|
|
|
622
729
|
type: "absolute",
|
|
623
730
|
left: submenuPosition.left,
|
|
624
731
|
top: submenuPosition.top,
|
|
625
|
-
boundaryPoint: { x: -1, y: -1 }
|
|
626
|
-
constrainToBoundary: false
|
|
732
|
+
boundaryPoint: { x: -1, y: -1 }
|
|
627
733
|
},
|
|
628
734
|
role: "menu",
|
|
629
|
-
style: {
|
|
735
|
+
style: {
|
|
736
|
+
width: submenuPosition.width,
|
|
737
|
+
zIndex: "calc(var(--z-panel-popover) + 200)"
|
|
738
|
+
},
|
|
630
739
|
onPointerEnter: openSubmenu,
|
|
631
740
|
onPointerLeave: scheduleClose,
|
|
741
|
+
onKeyDown: (event) => {
|
|
742
|
+
if (event.key === "ArrowLeft" || event.key === "Escape") {
|
|
743
|
+
event.preventDefault();
|
|
744
|
+
event.stopPropagation();
|
|
745
|
+
closeSubmenuToTrigger();
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (event.key !== "ArrowDown" && event.key !== "ArrowUp") {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
const items = Array.from(
|
|
752
|
+
event.currentTarget.querySelectorAll(
|
|
753
|
+
"button:not(:disabled)"
|
|
754
|
+
)
|
|
755
|
+
);
|
|
756
|
+
if (items.length === 0) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
event.preventDefault();
|
|
760
|
+
event.stopPropagation();
|
|
761
|
+
const currentIndex = items.indexOf(
|
|
762
|
+
document.activeElement
|
|
763
|
+
);
|
|
764
|
+
const offset = event.key === "ArrowDown" ? 1 : -1;
|
|
765
|
+
const nextIndex = currentIndex < 0 ? offset > 0 ? 0 : items.length - 1 : (currentIndex + offset + items.length) % items.length;
|
|
766
|
+
items[nextIndex]?.focus();
|
|
767
|
+
},
|
|
632
768
|
children: [
|
|
769
|
+
submenuPosition.mode === "overlay" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
770
|
+
/* @__PURE__ */ jsx(
|
|
771
|
+
ContextMenuActionButton,
|
|
772
|
+
{
|
|
773
|
+
activateOnPointerDown: true,
|
|
774
|
+
icon: /* @__PURE__ */ jsx(ArrowLeftIcon, { className: "size-4" }),
|
|
775
|
+
label: copy.t("openWithLabel"),
|
|
776
|
+
onClick: closeSubmenuToTrigger
|
|
777
|
+
}
|
|
778
|
+
),
|
|
779
|
+
/* @__PURE__ */ jsx(ContextMenuDivider, {})
|
|
780
|
+
] }) : null,
|
|
633
781
|
showOpenInFileViewer ? /* @__PURE__ */ jsx(
|
|
634
782
|
ContextMenuActionButton,
|
|
635
783
|
{
|
|
@@ -658,7 +806,7 @@ function OpenWithMenuItem({
|
|
|
658
806
|
}
|
|
659
807
|
}
|
|
660
808
|
) : null,
|
|
661
|
-
|
|
809
|
+
showSectionDivider ? /* @__PURE__ */ jsx(ContextMenuDivider, {}) : null,
|
|
662
810
|
isLoading ? /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-[11px] text-[var(--text-tertiary)]", children: copy.t("openWithLoadingLabel") }) : null,
|
|
663
811
|
applications.map((application) => {
|
|
664
812
|
const resolvedIcon = resolveOpenWithApplicationIcon?.(application);
|
|
@@ -941,7 +1089,8 @@ function WorkspaceFileManagerContextMenuContainer({
|
|
|
941
1089
|
onCopyPath,
|
|
942
1090
|
openInAppBrowserIcon,
|
|
943
1091
|
resolveOpenWithApplicationIcon,
|
|
944
|
-
session
|
|
1092
|
+
session,
|
|
1093
|
+
showInternalOpenWithActions
|
|
945
1094
|
}) {
|
|
946
1095
|
const contextMenuRef = useRef2(null);
|
|
947
1096
|
const { view } = useWorkspaceFileManagerContextMenuView(session);
|
|
@@ -998,9 +1147,9 @@ function WorkspaceFileManagerContextMenuContainer({
|
|
|
998
1147
|
showDeleteAction: view.showDeleteAction,
|
|
999
1148
|
showImportAction: view.showImportAction,
|
|
1000
1149
|
showExportAction: view.showExportAction,
|
|
1001
|
-
showOpenInAppBrowserAction: view.showOpenInAppBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
|
|
1150
|
+
showOpenInAppBrowserAction: showInternalOpenWithActions && view.showOpenInAppBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
|
|
1002
1151
|
showOpenInDefaultBrowserAction: view.showOpenInDefaultBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
|
|
1003
|
-
showOpenInFileViewerAction: view.showOpenInFileViewerAction,
|
|
1152
|
+
showOpenInFileViewerAction: showInternalOpenWithActions && view.showOpenInFileViewerAction,
|
|
1004
1153
|
showOpenWithAction: view.showOpenWithAction,
|
|
1005
1154
|
showOpenWithOtherAction: view.showOpenWithOtherAction,
|
|
1006
1155
|
showRevealInFolderAction: view.showRevealInFolderAction,
|
|
@@ -3291,7 +3440,7 @@ function hasFileDragPayload(dataTransfer) {
|
|
|
3291
3440
|
|
|
3292
3441
|
// src/ui/WorkspaceFileManagerToolbar.tsx
|
|
3293
3442
|
import {
|
|
3294
|
-
ArrowLeftIcon,
|
|
3443
|
+
ArrowLeftIcon as ArrowLeftIcon2,
|
|
3295
3444
|
ArrowRightIcon as ArrowRightIcon3,
|
|
3296
3445
|
Button as Button2,
|
|
3297
3446
|
ChevronDownIcon,
|
|
@@ -3352,7 +3501,7 @@ function WorkspaceFileManagerToolbar({
|
|
|
3352
3501
|
disabled: !canGoBack || isLoading || isBusy,
|
|
3353
3502
|
title: copy.t("backLabel"),
|
|
3354
3503
|
onClick: onGoBack,
|
|
3355
|
-
children: /* @__PURE__ */ jsx7(
|
|
3504
|
+
children: /* @__PURE__ */ jsx7(ArrowLeftIcon2, { className: "size-4" })
|
|
3356
3505
|
}
|
|
3357
3506
|
),
|
|
3358
3507
|
/* @__PURE__ */ jsx7(
|
|
@@ -4206,6 +4355,7 @@ function WorkspaceFileManager({
|
|
|
4206
4355
|
renderExternalLocationContent,
|
|
4207
4356
|
hostOs = "linux",
|
|
4208
4357
|
session,
|
|
4358
|
+
showInternalOpenWithActions = true,
|
|
4209
4359
|
showPreviewPanel = true,
|
|
4210
4360
|
surface = "card"
|
|
4211
4361
|
}) {
|
|
@@ -4583,7 +4733,8 @@ function WorkspaceFileManager({
|
|
|
4583
4733
|
onCopyPath,
|
|
4584
4734
|
openInAppBrowserIcon,
|
|
4585
4735
|
resolveOpenWithApplicationIcon,
|
|
4586
|
-
session
|
|
4736
|
+
session,
|
|
4737
|
+
showInternalOpenWithActions
|
|
4587
4738
|
}
|
|
4588
4739
|
)
|
|
4589
4740
|
] }) : null
|