@nextclaw/ui 0.12.35-beta.1 → 0.12.35-beta.3
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/CHANGELOG.md +128 -0
- package/dist/assets/api-D3uoaOR8.js +15 -0
- package/dist/assets/{book-open-825Pcd0z.js → book-open--T9dJHnp.js} +1 -1
- package/dist/assets/channels-list-page-DHmz9iHg.js +8 -0
- package/dist/assets/{chat-page-RsUTK_bY.js → chat-page-DOKLoiqI.js} +2 -2
- package/dist/assets/{config-split-page-BvfxFQSb.js → config-split-page-Dutq3R5o.js} +1 -1
- package/dist/assets/{createLucideIcon-CeNHY6BW.js → createLucideIcon-Cwr2o6Zq.js} +1 -1
- package/dist/assets/{desktop-update-config-B_YIv5ZJ.js → desktop-update-config-ClbDsIOs.js} +1 -1
- package/dist/assets/{dist-uH7tiW6E.js → dist-e5WATjIW.js} +1 -1
- package/dist/assets/doc-browser-ChY6hMn8.js +1 -0
- package/dist/assets/doc-browser-cR1vWRs1.js +1 -0
- package/dist/assets/{doc-browser-context-CQAx_92e.js → doc-browser-context-CLczyX2Z.js} +1 -1
- package/dist/assets/{es2015-c9XYpli5.js → es2015-BJw-hrex.js} +1 -1
- package/dist/assets/{external-link-BE1deZ_c.js → external-link-CZ9bQY0L.js} +1 -1
- package/dist/assets/i18n-tWKGEGxB.js +1 -0
- package/dist/assets/{index-BjH_p3R6.js → index-DPz9DRZN.js} +44 -44
- package/dist/assets/index-HvJDzfSU.css +1 -0
- package/dist/assets/{key-round-CHo3gqpO.js → key-round-B8tTb3ZJ.js} +1 -1
- package/dist/assets/{mcp-marketplace-page-0C214hGi.js → mcp-marketplace-page-BYPxLdUR.js} +2 -2
- package/dist/assets/mcp-marketplace-page-DXCvk7wJ.js +1 -0
- package/dist/assets/model-config-DcnC_fSr.js +1 -0
- package/dist/assets/{notice-card-DO33_LzD.js → notice-card-DUDbOvqx.js} +1 -1
- package/dist/assets/play-CV_kwi9X.js +1 -0
- package/dist/assets/plus-BqfNq7Tu.js +1 -0
- package/dist/assets/{provider-scoped-model-input-B0_r_4EY.js → provider-scoped-model-input-eO4XY0iY.js} +1 -1
- package/dist/assets/providers-list-BgvjRVmp.js +1 -0
- package/dist/assets/remote-BCuZ1JJe.js +1 -0
- package/dist/assets/{rotate-cw-CYUByuqo.js → rotate-cw-BKEhWuWp.js} +1 -1
- package/dist/assets/runtime-config-page-B2XPXHrk.js +1 -0
- package/dist/assets/{save-B6Ga8JIq.js → save-B6wbBLkU.js} +1 -1
- package/dist/assets/{search-5kKpgNoP.js → search-Czito3Dk.js} +1 -1
- package/dist/assets/search-config-BFmG4_F7.js +1 -0
- package/dist/assets/{secrets-config-CrXA11bo.js → secrets-config-4uY5sk-V.js} +2 -2
- package/dist/assets/{select-C0hGOCOK.js → select-CmmDgG8o.js} +1 -1
- package/dist/assets/{setting-row-xsM_0nTd.js → setting-row-BUO6FeN1.js} +1 -1
- package/dist/assets/{tabs-custom-Bnqfhe4p.js → tabs-custom-D3IZL9WX.js} +1 -1
- package/dist/assets/{tag-chip-cktJJMzh.js → tag-chip-Dm_pOtE3.js} +1 -1
- package/dist/assets/x-DBP3WiC9.js +1 -0
- package/dist/index.html +17 -17
- package/package.json +9 -9
- package/src/app/components/layout/app-layout.tsx +3 -2
- package/src/features/chat/components/chat-session-workspace-panel.tsx +1 -1
- package/src/features/chat/components/layout/chat-sidebar-read-state.test.tsx +145 -0
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +16 -0
- package/src/features/chat/components/layout/chat-sidebar.tsx +3 -2
- package/src/features/panel-apps/components/panel-app-list-item.tsx +83 -0
- package/src/features/panel-apps/components/panel-app-toolbar.tsx +34 -0
- package/src/features/panel-apps/components/panel-apps-list.tsx +109 -0
- package/src/features/panel-apps/hooks/use-panel-apps.ts +39 -0
- package/src/features/panel-apps/index.ts +4 -0
- package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +56 -0
- package/src/features/panel-apps/utils/panel-app-view.utils.ts +43 -0
- package/src/platforms/desktop/components/desktop-app-shell.tsx +5 -2
- package/src/platforms/mobile/components/mobile-app-shell.tsx +4 -1
- package/src/shared/components/doc-browser/doc-browser-context.tsx +7 -7
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +201 -0
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +19 -0
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +67 -0
- package/src/shared/components/doc-browser/doc-browser.test.tsx +82 -2
- package/src/shared/components/doc-browser/doc-browser.tsx +205 -279
- package/src/shared/lib/api/types.ts +24 -0
- package/src/shared/lib/i18n/index.ts +2 -12
- package/src/shared/lib/i18n/runtime/doc-browser-labels.utils.ts +29 -0
- package/dist/assets/api-De1PAch0.js +0 -15
- package/dist/assets/channels-list-page-CGjXDSD-.js +0 -8
- package/dist/assets/doc-browser-CDe9yz9n.js +0 -1
- package/dist/assets/doc-browser-DwT64GuF.js +0 -1
- package/dist/assets/i18n-DnubSHoK.js +0 -1
- package/dist/assets/index-6WZ6JjaU.css +0 -1
- package/dist/assets/mcp-marketplace-page-amWcMncu.js +0 -1
- package/dist/assets/model-config-B8zNrkag.js +0 -1
- package/dist/assets/play-CqlWbVNo.js +0 -1
- package/dist/assets/plus-p61tQh2-.js +0 -1
- package/dist/assets/providers-list-B6v3foi7.js +0 -1
- package/dist/assets/remote-k671rLmY.js +0 -1
- package/dist/assets/runtime-config-page-DgH-XJwK.js +0 -1
- package/dist/assets/search-config-DPZ6d9y6.js +0 -1
- package/dist/assets/x-DDQw-C2x.js +0 -1
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { ReactNode, Ref } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ArrowLeft,
|
|
4
|
+
ArrowRight,
|
|
5
|
+
BookOpen,
|
|
6
|
+
ExternalLink,
|
|
7
|
+
Maximize2,
|
|
8
|
+
PanelRightOpen,
|
|
9
|
+
Search,
|
|
10
|
+
X,
|
|
11
|
+
} from 'lucide-react';
|
|
12
|
+
import {
|
|
13
|
+
isDocsUrl,
|
|
14
|
+
type DocBrowserTab,
|
|
15
|
+
} from './doc-browser-context';
|
|
16
|
+
import type { DocBrowserCustomTabRenderer } from './doc-browser-renderer.types';
|
|
17
|
+
import { cn } from '@/shared/lib/utils';
|
|
18
|
+
import { t } from '@/shared/lib/i18n';
|
|
19
|
+
|
|
20
|
+
type DocBrowserPanelHeaderProps = {
|
|
21
|
+
currentTab?: DocBrowserTab;
|
|
22
|
+
customRenderer?: DocBrowserCustomTabRenderer;
|
|
23
|
+
isDocked: boolean;
|
|
24
|
+
isFullscreen: boolean;
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
onDragStart: (e: React.PointerEvent<HTMLElement>) => void;
|
|
27
|
+
onToggleMode: () => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type DocBrowserDocsToolbarProps = {
|
|
31
|
+
currentTab?: DocBrowserTab;
|
|
32
|
+
isDocsTab: boolean;
|
|
33
|
+
onBack: () => void;
|
|
34
|
+
onForward: () => void;
|
|
35
|
+
onSubmit: (e: React.FormEvent) => void;
|
|
36
|
+
onUrlInputChange: (value: string) => void;
|
|
37
|
+
urlInput: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type DocBrowserFrameContentProps = {
|
|
41
|
+
activeTabId: string;
|
|
42
|
+
currentTab?: DocBrowserTab;
|
|
43
|
+
currentUrl: string;
|
|
44
|
+
customContent: ReactNode;
|
|
45
|
+
iframeRef: Ref<HTMLIFrameElement>;
|
|
46
|
+
iframeReloadVersion: number;
|
|
47
|
+
iframeSandbox: string;
|
|
48
|
+
isDragging: boolean;
|
|
49
|
+
isResizing: boolean;
|
|
50
|
+
navVersion: number;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function DocBrowserPanelHeader({
|
|
54
|
+
currentTab,
|
|
55
|
+
customRenderer,
|
|
56
|
+
isDocked,
|
|
57
|
+
isFullscreen,
|
|
58
|
+
onClose,
|
|
59
|
+
onDragStart,
|
|
60
|
+
onToggleMode,
|
|
61
|
+
}: DocBrowserPanelHeaderProps) {
|
|
62
|
+
const title = currentTab && customRenderer?.getTitle
|
|
63
|
+
? customRenderer.getTitle(currentTab)
|
|
64
|
+
: t('docBrowserTitle');
|
|
65
|
+
const icon = currentTab && customRenderer?.renderIcon
|
|
66
|
+
? customRenderer.renderIcon(currentTab)
|
|
67
|
+
: <BookOpen className="w-4 h-4 text-primary shrink-0" />;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
className={cn(
|
|
72
|
+
'flex items-center justify-between px-4 py-2.5 bg-gray-50 border-b border-gray-200 shrink-0 select-none',
|
|
73
|
+
!isDocked && !isFullscreen && 'cursor-grab active:cursor-grabbing',
|
|
74
|
+
isFullscreen && 'pt-[calc(env(safe-area-inset-top,0px)+0.625rem)]',
|
|
75
|
+
)}
|
|
76
|
+
onPointerDown={!isDocked && !isFullscreen ? onDragStart : undefined}
|
|
77
|
+
>
|
|
78
|
+
<div className="flex items-center gap-2.5 min-w-0">
|
|
79
|
+
{icon}
|
|
80
|
+
<span className="text-sm font-semibold text-gray-900 truncate">{title}</span>
|
|
81
|
+
</div>
|
|
82
|
+
<div className="flex items-center gap-1">
|
|
83
|
+
{!isFullscreen ? (
|
|
84
|
+
<button
|
|
85
|
+
onClick={onToggleMode}
|
|
86
|
+
className="hover:bg-gray-200 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors"
|
|
87
|
+
title={isDocked ? t('docBrowserFloatMode') : t('docBrowserDockMode')}
|
|
88
|
+
>
|
|
89
|
+
{isDocked ? <Maximize2 className="w-3.5 h-3.5" /> : <PanelRightOpen className="w-3.5 h-3.5" />}
|
|
90
|
+
</button>
|
|
91
|
+
) : null}
|
|
92
|
+
<button
|
|
93
|
+
onClick={onClose}
|
|
94
|
+
className="hover:bg-gray-200 rounded-md p-1.5 text-gray-500 hover:text-gray-700 transition-colors"
|
|
95
|
+
title={t('docBrowserClose')}
|
|
96
|
+
>
|
|
97
|
+
<X className="w-3.5 h-3.5" />
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function DocBrowserDocsToolbar({
|
|
105
|
+
currentTab,
|
|
106
|
+
isDocsTab,
|
|
107
|
+
onBack,
|
|
108
|
+
onForward,
|
|
109
|
+
onSubmit,
|
|
110
|
+
onUrlInputChange,
|
|
111
|
+
urlInput,
|
|
112
|
+
}: DocBrowserDocsToolbarProps) {
|
|
113
|
+
if (!isDocsTab) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div className="flex items-center gap-2 px-3.5 py-2 bg-white border-b border-gray-100 shrink-0">
|
|
119
|
+
<button
|
|
120
|
+
onClick={onBack}
|
|
121
|
+
disabled={!isDocsTab || (currentTab?.historyIndex ?? 0) <= 0}
|
|
122
|
+
className="p-1.5 rounded-md hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed text-gray-600 transition-colors"
|
|
123
|
+
>
|
|
124
|
+
<ArrowLeft className="w-4 h-4" />
|
|
125
|
+
</button>
|
|
126
|
+
<button
|
|
127
|
+
onClick={onForward}
|
|
128
|
+
disabled={!isDocsTab || (currentTab?.historyIndex ?? 0) >= (currentTab?.history.length ?? 0) - 1}
|
|
129
|
+
className="p-1.5 rounded-md hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed text-gray-600 transition-colors"
|
|
130
|
+
>
|
|
131
|
+
<ArrowRight className="w-4 h-4" />
|
|
132
|
+
</button>
|
|
133
|
+
|
|
134
|
+
<form onSubmit={onSubmit} className="flex-1 relative">
|
|
135
|
+
<Search className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
|
|
136
|
+
<input
|
|
137
|
+
type="text"
|
|
138
|
+
value={urlInput}
|
|
139
|
+
onChange={(e) => onUrlInputChange(e.target.value)}
|
|
140
|
+
placeholder={t('docBrowserSearchPlaceholder')}
|
|
141
|
+
className="w-full h-8 pl-8 pr-3 rounded-lg bg-gray-50 border border-gray-200 text-xs text-gray-700 focus:outline-none focus:ring-1 focus:ring-primary/30 focus:border-primary/40 transition-colors placeholder:text-gray-400"
|
|
142
|
+
/>
|
|
143
|
+
</form>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function DocBrowserFrameContent({
|
|
149
|
+
activeTabId,
|
|
150
|
+
currentTab,
|
|
151
|
+
currentUrl,
|
|
152
|
+
customContent,
|
|
153
|
+
iframeRef,
|
|
154
|
+
iframeReloadVersion,
|
|
155
|
+
iframeSandbox,
|
|
156
|
+
isDragging,
|
|
157
|
+
isResizing,
|
|
158
|
+
navVersion,
|
|
159
|
+
}: DocBrowserFrameContentProps) {
|
|
160
|
+
return (
|
|
161
|
+
<div className="flex-1 relative overflow-hidden">
|
|
162
|
+
{customContent ? (
|
|
163
|
+
customContent
|
|
164
|
+
) : (
|
|
165
|
+
<iframe
|
|
166
|
+
ref={iframeRef}
|
|
167
|
+
key={`${activeTabId}:${navVersion}:${iframeReloadVersion}`}
|
|
168
|
+
src={currentUrl}
|
|
169
|
+
className="absolute inset-0 w-full h-full border-0"
|
|
170
|
+
title={currentTab?.title || 'NextClaw Docs'}
|
|
171
|
+
sandbox={iframeSandbox}
|
|
172
|
+
allow="clipboard-read; clipboard-write"
|
|
173
|
+
/>
|
|
174
|
+
)}
|
|
175
|
+
{(isResizing || isDragging) && (
|
|
176
|
+
<div className="absolute inset-0 z-10" />
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function DocBrowserExternalLink({ currentUrl, isDocsTab }: { currentUrl: string; isDocsTab: boolean }) {
|
|
183
|
+
if (!isDocsTab || !isDocsUrl(currentUrl)) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<div className="flex items-center justify-between px-4 py-2 bg-gray-50 border-t border-gray-200 shrink-0">
|
|
189
|
+
<a
|
|
190
|
+
href={currentUrl}
|
|
191
|
+
target="_blank"
|
|
192
|
+
rel="noopener noreferrer"
|
|
193
|
+
data-doc-external
|
|
194
|
+
className="flex items-center gap-1.5 text-xs text-primary hover:text-primary-hover font-medium transition-colors"
|
|
195
|
+
>
|
|
196
|
+
{t('docBrowserOpenExternal')}
|
|
197
|
+
<ExternalLink className="w-3 h-3" />
|
|
198
|
+
</a>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { DocBrowserContextValue, DocBrowserTab } from './doc-browser-context';
|
|
3
|
+
|
|
4
|
+
export type DocBrowserCustomTabRenderParams = {
|
|
5
|
+
currentUrl: string;
|
|
6
|
+
open: DocBrowserContextValue['open'];
|
|
7
|
+
refreshIframe: () => void;
|
|
8
|
+
tab: DocBrowserTab;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type DocBrowserCustomTabRenderer = {
|
|
12
|
+
getIframeSandbox?: (tab: DocBrowserTab) => string;
|
|
13
|
+
getTitle?: (tab: DocBrowserTab) => string;
|
|
14
|
+
renderContent?: (params: DocBrowserCustomTabRenderParams) => ReactNode;
|
|
15
|
+
renderIcon?: (tab: DocBrowserTab) => ReactNode;
|
|
16
|
+
renderToolbar?: (params: DocBrowserCustomTabRenderParams) => ReactNode;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type DocBrowserCustomTabRenderers = Record<string, DocBrowserCustomTabRenderer>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Plus, X } from 'lucide-react';
|
|
2
|
+
import type { DocBrowserTab } from './doc-browser-context';
|
|
3
|
+
import { cn } from '@/shared/lib/utils';
|
|
4
|
+
import { t } from '@/shared/lib/i18n';
|
|
5
|
+
|
|
6
|
+
type DocBrowserTabStripProps = {
|
|
7
|
+
tabs: DocBrowserTab[];
|
|
8
|
+
activeTabId: string;
|
|
9
|
+
onOpenDocs: () => void;
|
|
10
|
+
onSetActiveTab: (tabId: string) => void;
|
|
11
|
+
onCloseTab: (tabId: string) => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function DocBrowserTabStrip({
|
|
15
|
+
tabs,
|
|
16
|
+
activeTabId,
|
|
17
|
+
onOpenDocs,
|
|
18
|
+
onSetActiveTab,
|
|
19
|
+
onCloseTab,
|
|
20
|
+
}: DocBrowserTabStripProps) {
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex items-center gap-1.5 px-2.5 py-2 bg-background border-b border-[#f1e7d4] overflow-x-auto custom-scrollbar">
|
|
23
|
+
{tabs.map((tab) => {
|
|
24
|
+
const isActive = tab.id === activeTabId;
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
key={tab.id}
|
|
28
|
+
className={cn(
|
|
29
|
+
'inline-flex items-center gap-1 h-7 px-1.5 rounded-lg text-xs border max-w-[220px] shrink-0 transition-colors',
|
|
30
|
+
isActive
|
|
31
|
+
? 'bg-amber-50/80 border-amber-200 text-amber-900 shadow-[0_1px_2px_rgba(30,20,10,0.04)]'
|
|
32
|
+
: 'bg-[#f9f8f5] border-[#eee3d1] text-[#78644d] hover:bg-[#fff7ea] hover:text-[#2f2212]',
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
onClick={() => onSetActiveTab(tab.id)}
|
|
38
|
+
className="truncate text-left px-1"
|
|
39
|
+
title={tab.title}
|
|
40
|
+
>
|
|
41
|
+
{tab.title || t('docBrowserTabUntitled')}
|
|
42
|
+
</button>
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
onClick={(event) => {
|
|
46
|
+
event.stopPropagation();
|
|
47
|
+
onCloseTab(tab.id);
|
|
48
|
+
}}
|
|
49
|
+
className="rounded p-0.5 hover:bg-black/10"
|
|
50
|
+
aria-label={t('docBrowserCloseTab')}
|
|
51
|
+
>
|
|
52
|
+
<X className="w-3 h-3" />
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
})}
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
onClick={onOpenDocs}
|
|
60
|
+
className="inline-flex items-center justify-center w-7 h-7 rounded-lg border border-[#eee3d1] bg-white text-[#78644d] hover:bg-[#fff7ea] hover:text-[#2f2212] shrink-0"
|
|
61
|
+
title={t('docBrowserNewTab')}
|
|
62
|
+
>
|
|
63
|
+
<Plus className="w-3.5 h-3.5" />
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { render, screen } from "@testing-library/react";
|
|
2
|
-
import { describe, expect, it, vi } from "vitest";
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { DocBrowser } from "@/shared/components/doc-browser/doc-browser";
|
|
4
4
|
|
|
5
5
|
const { docBrowserState } = vi.hoisted(() => ({
|
|
@@ -49,7 +49,34 @@ vi.mock("@/shared/components/doc-browser/doc-browser-context", async () => {
|
|
|
49
49
|
};
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
function firePointerEvent(
|
|
53
|
+
target: Window | Document | Node | Element,
|
|
54
|
+
type: string,
|
|
55
|
+
point: { clientX: number; clientY?: number; pointerId?: number },
|
|
56
|
+
) {
|
|
57
|
+
const event = new Event(type, { bubbles: true });
|
|
58
|
+
Object.defineProperties(event, {
|
|
59
|
+
clientX: { value: point.clientX },
|
|
60
|
+
clientY: { value: point.clientY ?? 0 },
|
|
61
|
+
pointerId: { value: point.pointerId ?? 1 },
|
|
62
|
+
});
|
|
63
|
+
fireEvent(target, event);
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
describe("DocBrowser", () => {
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
docBrowserState.mode = "docked";
|
|
69
|
+
Object.defineProperty(window, "innerWidth", {
|
|
70
|
+
configurable: true,
|
|
71
|
+
value: 1200,
|
|
72
|
+
});
|
|
73
|
+
Object.defineProperty(window, "innerHeight", {
|
|
74
|
+
configurable: true,
|
|
75
|
+
value: 900,
|
|
76
|
+
});
|
|
77
|
+
HTMLElement.prototype.setPointerCapture = vi.fn();
|
|
78
|
+
});
|
|
79
|
+
|
|
53
80
|
it("uses the shared right panel resize handle in docked mode", () => {
|
|
54
81
|
render(<DocBrowser />);
|
|
55
82
|
|
|
@@ -71,4 +98,57 @@ describe("DocBrowser", () => {
|
|
|
71
98
|
expect(panel.querySelector(".cursor-ew-resize")).toBeNull();
|
|
72
99
|
expect(panel.querySelector(".cursor-se-resize")).toBeNull();
|
|
73
100
|
});
|
|
101
|
+
|
|
102
|
+
it("keeps the floating panel left edge stable when resizing from the right", () => {
|
|
103
|
+
docBrowserState.mode = "floating";
|
|
104
|
+
|
|
105
|
+
render(<DocBrowser />);
|
|
106
|
+
|
|
107
|
+
firePointerEvent(screen.getByTestId("doc-browser-resize-right"), "pointerdown", {
|
|
108
|
+
clientX: 1160,
|
|
109
|
+
pointerId: 1,
|
|
110
|
+
});
|
|
111
|
+
firePointerEvent(window, "pointermove", { clientX: 1120, pointerId: 1 });
|
|
112
|
+
|
|
113
|
+
const panel = screen.getByTestId("doc-browser-panel");
|
|
114
|
+
expect(panel.style.left).toBe("680px");
|
|
115
|
+
expect(panel.style.width).toBe("440px");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("keeps the floating panel right edge stable when resizing from the left", () => {
|
|
119
|
+
docBrowserState.mode = "floating";
|
|
120
|
+
|
|
121
|
+
render(<DocBrowser />);
|
|
122
|
+
|
|
123
|
+
firePointerEvent(screen.getByTestId("doc-browser-resize-left"), "pointerdown", {
|
|
124
|
+
clientX: 680,
|
|
125
|
+
pointerId: 1,
|
|
126
|
+
});
|
|
127
|
+
firePointerEvent(window, "pointermove", { clientX: 620, pointerId: 1 });
|
|
128
|
+
|
|
129
|
+
const panel = screen.getByTestId("doc-browser-panel");
|
|
130
|
+
expect(panel.style.left).toBe("620px");
|
|
131
|
+
expect(panel.style.width).toBe("540px");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("keeps the floating panel bottom edge stable when resizing from the top", () => {
|
|
135
|
+
docBrowserState.mode = "floating";
|
|
136
|
+
|
|
137
|
+
render(<DocBrowser />);
|
|
138
|
+
|
|
139
|
+
firePointerEvent(screen.getByTestId("doc-browser-resize-top"), "pointerdown", {
|
|
140
|
+
clientX: 900,
|
|
141
|
+
clientY: 80,
|
|
142
|
+
pointerId: 1,
|
|
143
|
+
});
|
|
144
|
+
firePointerEvent(window, "pointermove", {
|
|
145
|
+
clientX: 900,
|
|
146
|
+
clientY: 120,
|
|
147
|
+
pointerId: 1,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const panel = screen.getByTestId("doc-browser-panel");
|
|
151
|
+
expect(panel.style.top).toBe("120px");
|
|
152
|
+
expect(panel.style.height).toBe("560px");
|
|
153
|
+
});
|
|
74
154
|
});
|