@brftech/filex-core 0.7.0 → 0.7.1
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/{ArchiveViewer-w6GDJJt9.js → ArchiveViewer-Qi4L2jj9.js} +2 -2
- package/dist/{ArchiveViewer-w6GDJJt9.js.map → ArchiveViewer-Qi4L2jj9.js.map} +1 -1
- package/dist/{CsvViewer-Ci5-mke8.js → CsvViewer-IOIrJDsJ.js} +2 -2
- package/dist/{CsvViewer-Ci5-mke8.js.map → CsvViewer-IOIrJDsJ.js.map} +1 -1
- package/dist/{DrawioViewer-D2iMuQfD.js → DrawioViewer-D0KtUQBO.js} +2 -2
- package/dist/{DrawioViewer-D2iMuQfD.js.map → DrawioViewer-D0KtUQBO.js.map} +1 -1
- package/dist/{EpubViewer-D4ydsa9X.js → EpubViewer-DJOXiOi4.js} +2 -2
- package/dist/{EpubViewer-D4ydsa9X.js.map → EpubViewer-DJOXiOi4.js.map} +1 -1
- package/dist/{IpynbViewer-CWBwiFKX.js → IpynbViewer-D16jXVJc.js} +2 -2
- package/dist/{IpynbViewer-CWBwiFKX.js.map → IpynbViewer-D16jXVJc.js.map} +1 -1
- package/dist/{MermaidViewer-CDhuqQli.js → MermaidViewer-BnSaVg1L.js} +2 -2
- package/dist/{MermaidViewer-CDhuqQli.js.map → MermaidViewer-BnSaVg1L.js.map} +1 -1
- package/dist/{PsdViewer-C_TkFb2H.js → PsdViewer-BUerXaQA.js} +2 -2
- package/dist/{PsdViewer-C_TkFb2H.js.map → PsdViewer-BUerXaQA.js.map} +1 -1
- package/dist/{TiffViewer-BmWCXR7e.js → TiffViewer-D0Qn3IV-.js} +2 -2
- package/dist/{TiffViewer-BmWCXR7e.js.map → TiffViewer-D0Qn3IV-.js.map} +1 -1
- package/dist/{Viewer3D-5kPT1QsY.js → Viewer3D-BtRK-k1B.js} +2 -2
- package/dist/{Viewer3D-5kPT1QsY.js.map → Viewer3D-BtRK-k1B.js.map} +1 -1
- package/dist/filex-core.js +1 -1
- package/dist/filex-core.umd.cjs +56 -56
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index-CGNE1bSv.js +11802 -0
- package/dist/index-CGNE1bSv.js.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +27 -6
- package/src/components/SecondaryPane.vue +72 -78
- package/src/components/Toolbar.vue +112 -3
- package/src/composables/useKeyboardShortcuts.ts +6 -0
- package/src/composables/useTabs.ts +11 -2
- package/src/styles/base.css +33 -1
- package/src/styles/variables.css +2 -2
- package/dist/index-nc0-oI_l.js +0 -11723
- package/dist/index-nc0-oI_l.js.map +0 -1
package/src/FileExplorer.vue
CHANGED
|
@@ -1581,7 +1581,10 @@ async function dispatchItemAction(key: string, targets: FileNode[]) {
|
|
|
1581
1581
|
flashToast('Kopyala → Yapıştır hazır');
|
|
1582
1582
|
break;
|
|
1583
1583
|
case 'paste':
|
|
1584
|
-
|
|
1584
|
+
/* ui-fix — sağ-klik menüsünden yapıştırma da aktif panele gider
|
|
1585
|
+
* (klavye kısayolu zaten pane-route'luydu; menü değildi). */
|
|
1586
|
+
if (paneIsActive.value) await panePaste();
|
|
1587
|
+
else await paste();
|
|
1585
1588
|
break;
|
|
1586
1589
|
case 'delete':
|
|
1587
1590
|
showDelete.value = true;
|
|
@@ -2518,14 +2521,30 @@ function toggleSplit() {
|
|
|
2518
2521
|
return;
|
|
2519
2522
|
}
|
|
2520
2523
|
if (isNarrow.value) return;
|
|
2521
|
-
tabsApi.setSplit({ path: currentPath.value ?? '' });
|
|
2524
|
+
tabsApi.setSplit({ path: currentPath.value ?? '', viewMode: viewMode.value });
|
|
2522
2525
|
}
|
|
2523
2526
|
function closeSplit() {
|
|
2524
2527
|
tabsApi.setSplit(null);
|
|
2525
2528
|
activePane.value = 'main';
|
|
2526
2529
|
}
|
|
2527
2530
|
function onPaneNavigate(p: string) {
|
|
2528
|
-
tabsApi.setSplit({ path: p });
|
|
2531
|
+
tabsApi.setSplit({ ...(activeSplit.value ?? {}), path: p });
|
|
2532
|
+
}
|
|
2533
|
+
/* ui-fix — pane'in KENDİ görünüm modu: split açılırken ana panelinkini
|
|
2534
|
+
* devralır, sonrasında bağımsız. Toolbar'ın görünüm değiştiricisi ve palet
|
|
2535
|
+
* toggle'ı AKTİF panele yazar (Burak: "B tıklıyken ikon değiştir dersem
|
|
2536
|
+
* B'nin değişmesi lazım"). */
|
|
2537
|
+
const paneViewMode = computed<ViewMode>(() => activeSplit.value?.viewMode ?? viewMode.value);
|
|
2538
|
+
function setPaneViewMode(v: ViewMode) {
|
|
2539
|
+
if (!activeSplit.value) return;
|
|
2540
|
+
tabsApi.setSplit({ ...activeSplit.value, viewMode: v });
|
|
2541
|
+
}
|
|
2542
|
+
const displayedViewMode = computed<ViewMode>(() =>
|
|
2543
|
+
paneIsActive.value ? paneViewMode.value : viewMode.value,
|
|
2544
|
+
);
|
|
2545
|
+
function setDisplayedViewMode(v: ViewMode) {
|
|
2546
|
+
if (paneIsActive.value) setPaneViewMode(v);
|
|
2547
|
+
else viewMode.value = v;
|
|
2529
2548
|
}
|
|
2530
2549
|
|
|
2531
2550
|
// Aktif panel: kısayollar aktif panele gider; panel tıklamayla aktifleşir.
|
|
@@ -2883,7 +2902,7 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
2883
2902
|
<!-- /wiring:d1 -->
|
|
2884
2903
|
<Toolbar
|
|
2885
2904
|
ref="toolbarRef"
|
|
2886
|
-
:view-mode="
|
|
2905
|
+
:view-mode="displayedViewMode /* ui-fix — aktif panelin modu */"
|
|
2887
2906
|
:search-query="searchQuery"
|
|
2888
2907
|
:trash-active="trashActive"
|
|
2889
2908
|
:actions="toolbarActions"
|
|
@@ -2899,7 +2918,7 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
2899
2918
|
:inspector-open="showInspector /* koru:k1 */"
|
|
2900
2919
|
@toggle-inspector="toggleInspector /* koru:k1 */"
|
|
2901
2920
|
@open-theme="showThemeGallery = true /* wiring:c1 */"
|
|
2902
|
-
@update:view-mode="
|
|
2921
|
+
@update:view-mode="setDisplayedViewMode($event) /* ui-fix — aktif panele */"
|
|
2903
2922
|
@update:search-query="searchQuery = $event"
|
|
2904
2923
|
@update:density="density = $event"
|
|
2905
2924
|
@open-shortcut-settings="showShortcutSettings = true /* wiring:c2 */"
|
|
@@ -3230,6 +3249,8 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3230
3249
|
:multi-root="multiStorageRoot"
|
|
3231
3250
|
:virtual-rows="virtualStorageRows"
|
|
3232
3251
|
:active="paneIsActive"
|
|
3252
|
+
:view-mode="paneViewMode /* ui-fix */"
|
|
3253
|
+
:thumb-src="thumbs.src /* ui-fix */"
|
|
3233
3254
|
@navigate="onPaneNavigate"
|
|
3234
3255
|
@activate="activePane = 'split'"
|
|
3235
3256
|
@close="closeSplit"
|
|
@@ -3476,7 +3497,7 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3476
3497
|
@navigate="(p: string) => load(p)"
|
|
3477
3498
|
@new-folder="showNewFolder = true"
|
|
3478
3499
|
@upload="triggerUpload"
|
|
3479
|
-
@toggle-view="
|
|
3500
|
+
@toggle-view="setDisplayedViewMode(displayedViewMode === 'list' ? 'grid' : displayedViewMode === 'grid' ? 'gallery' : 'list') /* wiring:d2 + ui-fix — 3 mod döngüsü, aktif panele */"
|
|
3480
3501
|
@open-trash="loadTrash"
|
|
3481
3502
|
@refresh="() => load()"
|
|
3482
3503
|
@go-up="goUp"
|
|
@@ -18,10 +18,12 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { computed, ref } from 'vue';
|
|
20
20
|
import type { FileApi } from '../composables/useFileApi';
|
|
21
|
-
import type { FileNode } from '../types/FileNode';
|
|
21
|
+
import type { FileNode, ViewMode } from '../types/FileNode';
|
|
22
22
|
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
23
23
|
import { useLocale } from '../composables/useLocale';
|
|
24
|
-
import
|
|
24
|
+
import ListView from './ListView.vue';
|
|
25
|
+
import GridView from './GridView.vue';
|
|
26
|
+
import GalleryView from './GalleryView.vue';
|
|
25
27
|
|
|
26
28
|
// Same literals ListView/GridView already hardcode for the internal DnD
|
|
27
29
|
// channel; `-src` carries the origin directory so the host can move (and
|
|
@@ -50,6 +52,11 @@ const props = defineProps<{
|
|
|
50
52
|
virtualRows?: () => FileNode[];
|
|
51
53
|
/** Active-panel highlight (keyboard target). */
|
|
52
54
|
active?: boolean;
|
|
55
|
+
/** ui-fix — the pane renders the SAME view components as the main
|
|
56
|
+
* panel (list/grid/gallery) instead of its own flat list. */
|
|
57
|
+
viewMode?: ViewMode;
|
|
58
|
+
/** ui-fix — authenticated thumb resolver, forwarded to grid/gallery. */
|
|
59
|
+
thumbSrc?: (n: FileNode) => string | null;
|
|
53
60
|
}>();
|
|
54
61
|
|
|
55
62
|
const emit = defineEmits<{
|
|
@@ -60,7 +67,7 @@ const emit = defineEmits<{
|
|
|
60
67
|
(e: 'transfer', p: { sources: string[]; targetWire: string; originWire?: string }): void;
|
|
61
68
|
}>();
|
|
62
69
|
|
|
63
|
-
const { t
|
|
70
|
+
const { t } = useLocale(() => props.locale);
|
|
64
71
|
|
|
65
72
|
const path = ref<string>('');
|
|
66
73
|
const files = ref<FileNode[]>([]);
|
|
@@ -149,35 +156,40 @@ function crumbGo(target: string) {
|
|
|
149
156
|
// Selection + navigation
|
|
150
157
|
// ------------------------------------------------------------------
|
|
151
158
|
|
|
152
|
-
|
|
159
|
+
/* ui-fix — adapter for the shared view components' click contract
|
|
160
|
+
* ({ctrl, shift} mod object instead of a MouseEvent). Shift behaves as
|
|
161
|
+
* ctrl here: the pane keeps a simple Set, no range anchor. */
|
|
162
|
+
function onViewClick(n: FileNode, mod: { ctrl: boolean; shift: boolean }) {
|
|
153
163
|
emit('activate');
|
|
154
|
-
const multi =
|
|
164
|
+
const multi = mod.ctrl || mod.shift;
|
|
155
165
|
const next = new Set<string>(multi ? selected.value : []);
|
|
156
166
|
if (multi && next.has(n.path)) next.delete(n.path);
|
|
157
167
|
else next.add(n.path);
|
|
158
168
|
selected.value = next;
|
|
159
169
|
}
|
|
160
170
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
171
|
+
/* ui-fix — right-click on a pane item: activate + select it, no menu
|
|
172
|
+
* (the pane has no item menu; letting the main panel's menu open here
|
|
173
|
+
* would act on the WRONG panel's selection). */
|
|
174
|
+
function onViewContext(n: FileNode, ev: MouseEvent) {
|
|
175
|
+
ev.preventDefault();
|
|
176
|
+
ev.stopPropagation();
|
|
177
|
+
emit('activate');
|
|
178
|
+
if (!selected.value.has(n.path)) selected.value = new Set([n.path]);
|
|
164
179
|
}
|
|
165
180
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
function
|
|
169
|
-
|
|
181
|
+
/* ui-fix — drop-into from the shared views: same payload path as the
|
|
182
|
+
* old flat rows (dir check + wire-qualified row paths). */
|
|
183
|
+
function onViewDropInto(target: FileNode, ev: DragEvent) {
|
|
184
|
+
dropBg.value = false;
|
|
185
|
+
if (target.type !== 'dir' || isStorageRow(target)) return;
|
|
186
|
+
if (!acceptDrag(ev)) return;
|
|
187
|
+
handleDropPayload(ev, target.path);
|
|
170
188
|
}
|
|
171
189
|
|
|
172
|
-
|
|
173
|
-
// main listing). stopPropagation keeps the host's delegated auxclick
|
|
174
|
-
// listener from double-handling it.
|
|
175
|
-
function onRowAux(n: FileNode, ev: MouseEvent) {
|
|
176
|
-
if (ev.button !== 1) return;
|
|
177
|
-
ev.preventDefault();
|
|
178
|
-
ev.stopPropagation();
|
|
190
|
+
function onRowDbl(n: FileNode) {
|
|
179
191
|
if (n.type !== 'dir') return;
|
|
180
|
-
|
|
192
|
+
void loadPane(isStorageRow(n) ? n.path : props.toUser(n.path));
|
|
181
193
|
}
|
|
182
194
|
|
|
183
195
|
function clearSelection() {
|
|
@@ -212,7 +224,6 @@ function onRowDragStart(n: FileNode, ev: DragEvent) {
|
|
|
212
224
|
// Drop target (dir rows + pane background)
|
|
213
225
|
// ------------------------------------------------------------------
|
|
214
226
|
|
|
215
|
-
const dropPath = ref<string | null>(null);
|
|
216
227
|
const dropBg = ref(false);
|
|
217
228
|
|
|
218
229
|
function acceptDrag(ev: DragEvent): boolean {
|
|
@@ -238,28 +249,6 @@ function handleDropPayload(ev: DragEvent, targetWire: string) {
|
|
|
238
249
|
emit('transfer', { sources, targetWire, originWire: origin });
|
|
239
250
|
}
|
|
240
251
|
|
|
241
|
-
function onRowDragOver(n: FileNode, ev: DragEvent) {
|
|
242
|
-
if (n.type !== 'dir' || isStorageRow(n)) return;
|
|
243
|
-
if (!acceptDrag(ev)) return;
|
|
244
|
-
ev.preventDefault();
|
|
245
|
-
ev.stopPropagation();
|
|
246
|
-
if (ev.dataTransfer) ev.dataTransfer.dropEffect = 'move';
|
|
247
|
-
dropPath.value = n.path;
|
|
248
|
-
dropBg.value = false;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function onRowDragLeave(n: FileNode) {
|
|
252
|
-
if (dropPath.value === n.path) dropPath.value = null;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function onRowDrop(n: FileNode, ev: DragEvent) {
|
|
256
|
-
dropPath.value = null;
|
|
257
|
-
dropBg.value = false;
|
|
258
|
-
if (n.type !== 'dir' || isStorageRow(n)) return;
|
|
259
|
-
if (!acceptDrag(ev)) return;
|
|
260
|
-
handleDropPayload(ev, n.path); // real rows are wire-qualified
|
|
261
|
-
}
|
|
262
|
-
|
|
263
252
|
function onBgDragOver(ev: DragEvent) {
|
|
264
253
|
if (!acceptDrag(ev) || atVirtualRoot.value) return;
|
|
265
254
|
ev.preventDefault();
|
|
@@ -274,7 +263,6 @@ function onBgDragLeave() {
|
|
|
274
263
|
|
|
275
264
|
function onBgDrop(ev: DragEvent) {
|
|
276
265
|
dropBg.value = false;
|
|
277
|
-
dropPath.value = null;
|
|
278
266
|
if (!acceptDrag(ev) || atVirtualRoot.value) return;
|
|
279
267
|
handleDropPayload(ev, props.qualify(path.value));
|
|
280
268
|
}
|
|
@@ -313,10 +301,6 @@ function getPath(): string {
|
|
|
313
301
|
}
|
|
314
302
|
|
|
315
303
|
defineExpose({ reload, goUp, selectAll, openSelected, selectedNodes, getPath });
|
|
316
|
-
|
|
317
|
-
function specialEmojiFor(n: FileNode): string | null {
|
|
318
|
-
return isStorageRow(n) ? '💾' : null;
|
|
319
|
-
}
|
|
320
304
|
</script>
|
|
321
305
|
|
|
322
306
|
<template>
|
|
@@ -376,36 +360,46 @@ function specialEmojiFor(n: FileNode): string | null {
|
|
|
376
360
|
<div v-else-if="files.length === 0" class="fe-split__state">
|
|
377
361
|
{{ t('empty.folder') }}
|
|
378
362
|
</div>
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
363
|
+
<!-- ui-fix — aynı görünüm bileşenleri (list/grid/gallery), pane'in
|
|
364
|
+
kendi viewMode'uyla; eski düz fe-split__list kalktı. -->
|
|
365
|
+
<ListView
|
|
366
|
+
v-else-if="(viewMode ?? 'list') === 'list'"
|
|
367
|
+
:files="files"
|
|
368
|
+
:selected="selected"
|
|
369
|
+
:locale="locale"
|
|
370
|
+
:loading="loading"
|
|
371
|
+
@click-row="onViewClick"
|
|
372
|
+
@dbl-row="onRowDbl"
|
|
373
|
+
@context-row="onViewContext"
|
|
374
|
+
@item-drag-start="onRowDragStart"
|
|
375
|
+
@item-drop-into="onViewDropInto"
|
|
376
|
+
/>
|
|
377
|
+
<GridView
|
|
378
|
+
v-else-if="viewMode === 'grid'"
|
|
379
|
+
:files="files"
|
|
380
|
+
:selected="selected"
|
|
381
|
+
:locale="locale"
|
|
382
|
+
:loading="loading"
|
|
383
|
+
:thumb-src="thumbSrc"
|
|
384
|
+
@click-card="onViewClick"
|
|
385
|
+
@dbl-card="onRowDbl"
|
|
386
|
+
@context-card="onViewContext"
|
|
387
|
+
@item-drag-start="onRowDragStart"
|
|
388
|
+
@item-drop-into="onViewDropInto"
|
|
389
|
+
/>
|
|
390
|
+
<GalleryView
|
|
391
|
+
v-else
|
|
392
|
+
:files="files"
|
|
393
|
+
:selected="selected"
|
|
394
|
+
:locale="locale"
|
|
395
|
+
:loading="loading"
|
|
396
|
+
:thumb-src="thumbSrc"
|
|
397
|
+
@click-card="onViewClick"
|
|
398
|
+
@dbl-card="onRowDbl"
|
|
399
|
+
@context-card="onViewContext"
|
|
400
|
+
@item-drag-start="onRowDragStart"
|
|
401
|
+
@item-drop-into="onViewDropInto"
|
|
402
|
+
/>
|
|
409
403
|
</div>
|
|
410
404
|
</section>
|
|
411
405
|
</template>
|
|
@@ -152,6 +152,74 @@ const mode = computed<SelectionMode>(() => props.selectionMode ?? 'none');
|
|
|
152
152
|
// context menu uses, the two menus are guaranteed to match.
|
|
153
153
|
const toolbarItems = computed(() => props.actions.filter((a) => !a.divider && !a.hidden));
|
|
154
154
|
|
|
155
|
+
/* === ui-fix — wide-mode action folding ===============================
|
|
156
|
+
* Long selection-action rows used to wrap the whole toolbar and push the
|
|
157
|
+
* search / view controls onto a second line. The row is now single-line:
|
|
158
|
+
* actions that do not fit fold into a "⋯" menu. Widths come from a
|
|
159
|
+
* hidden measurement strip (all actions always rendered there), so the
|
|
160
|
+
* calculation never mutates what it measures. */
|
|
161
|
+
const primaryEl = ref<HTMLElement | null>(null);
|
|
162
|
+
const measureEl = ref<HTMLElement | null>(null);
|
|
163
|
+
const wideMoreBtnEl = ref<HTMLElement | null>(null);
|
|
164
|
+
const wideMoreRef = ref<InstanceType<typeof ContextMenu> | null>(null);
|
|
165
|
+
const visibleActionCount = ref(Number.MAX_SAFE_INTEGER);
|
|
166
|
+
const visibleToolbarItems = computed(() => toolbarItems.value.slice(0, visibleActionCount.value));
|
|
167
|
+
const overflowToolbarItems = computed(() => toolbarItems.value.slice(visibleActionCount.value));
|
|
168
|
+
const WIDE_MORE_BTN_W = 40;
|
|
169
|
+
|
|
170
|
+
function recalcFold() {
|
|
171
|
+
if (props.narrow) return;
|
|
172
|
+
const cont = primaryEl.value;
|
|
173
|
+
const meas = measureEl.value;
|
|
174
|
+
if (!cont || !meas) {
|
|
175
|
+
visibleActionCount.value = toolbarItems.value.length;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const gap = parseFloat(getComputedStyle(cont).gap) || 8;
|
|
179
|
+
let fixed = 0;
|
|
180
|
+
for (const child of Array.from(cont.children) as HTMLElement[]) {
|
|
181
|
+
if (child === meas || child === wideMoreBtnEl.value) continue;
|
|
182
|
+
if (child.classList.contains('fe-btn--fold')) continue;
|
|
183
|
+
if (child.offsetWidth > 0) fixed += child.offsetWidth + gap;
|
|
184
|
+
}
|
|
185
|
+
const widths = (Array.from(meas.children) as HTMLElement[]).map((c) => c.offsetWidth + gap);
|
|
186
|
+
const total = widths.reduce((s, w) => s + w, 0);
|
|
187
|
+
const avail = cont.clientWidth - fixed;
|
|
188
|
+
if (total <= avail) {
|
|
189
|
+
visibleActionCount.value = widths.length;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
let used = WIDE_MORE_BTN_W + gap;
|
|
193
|
+
let count = 0;
|
|
194
|
+
for (const w of widths) {
|
|
195
|
+
if (used + w > avail) break;
|
|
196
|
+
used += w;
|
|
197
|
+
count += 1;
|
|
198
|
+
}
|
|
199
|
+
visibleActionCount.value = count;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let foldRo: ResizeObserver | undefined;
|
|
203
|
+
onMounted(() => {
|
|
204
|
+
if (typeof ResizeObserver !== 'undefined' && primaryEl.value) {
|
|
205
|
+
foldRo = new ResizeObserver(() => recalcFold());
|
|
206
|
+
foldRo.observe(primaryEl.value);
|
|
207
|
+
}
|
|
208
|
+
void nextTick(recalcFold);
|
|
209
|
+
});
|
|
210
|
+
onBeforeUnmount(() => foldRo?.disconnect());
|
|
211
|
+
watch(
|
|
212
|
+
() => [toolbarItems.value, props.narrow, props.trashActive, mode.value] as const,
|
|
213
|
+
() => void nextTick(recalcFold),
|
|
214
|
+
{ deep: false },
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
function openWideMore() {
|
|
218
|
+
const r = wideMoreBtnEl.value?.getBoundingClientRect();
|
|
219
|
+
wideMoreRef.value?.show({ clientX: r ? r.right : 0, clientY: r ? r.bottom + 4 : 0 } as MouseEvent, []);
|
|
220
|
+
}
|
|
221
|
+
/* === /ui-fix ========================================================= */
|
|
222
|
+
|
|
155
223
|
function fire(key: string) {
|
|
156
224
|
emit('action', key);
|
|
157
225
|
}
|
|
@@ -291,7 +359,7 @@ function onMoreSelect(a: ContextAction) {
|
|
|
291
359
|
>
|
|
292
360
|
<!-- bag:b4 — wide layout, untouched; renders exactly as before when not narrow -->
|
|
293
361
|
<template v-if="!narrow">
|
|
294
|
-
<div class="fe-toolbar__primary">
|
|
362
|
+
<div ref="primaryEl" class="fe-toolbar__primary">
|
|
295
363
|
<button
|
|
296
364
|
v-if="canGoUp"
|
|
297
365
|
type="button"
|
|
@@ -315,10 +383,10 @@ function onMoreSelect(a: ContextAction) {
|
|
|
315
383
|
</button>
|
|
316
384
|
|
|
317
385
|
<button
|
|
318
|
-
v-for="a in
|
|
386
|
+
v-for="a in visibleToolbarItems"
|
|
319
387
|
:key="a.key"
|
|
320
388
|
type="button"
|
|
321
|
-
class="fe-btn"
|
|
389
|
+
class="fe-btn fe-btn--fold"
|
|
322
390
|
:class="{ 'fe-btn--danger': a.danger, 'is-disabled': a.disabled }"
|
|
323
391
|
:disabled="a.disabled"
|
|
324
392
|
:title="a.label"
|
|
@@ -328,6 +396,36 @@ function onMoreSelect(a: ContextAction) {
|
|
|
328
396
|
<span class="fe-btn__label">{{ a.label }}</span>
|
|
329
397
|
</button>
|
|
330
398
|
|
|
399
|
+
<!-- ui-fix — sığmayan aksiyonlar tek satırı korumak için ⋯ menüsüne
|
|
400
|
+
katlanır (arama/görünüm kontrolleri artık alt satıra düşmez). -->
|
|
401
|
+
<button
|
|
402
|
+
v-if="overflowToolbarItems.length > 0"
|
|
403
|
+
ref="wideMoreBtnEl"
|
|
404
|
+
type="button"
|
|
405
|
+
class="fe-btn fe-btn--icon-only"
|
|
406
|
+
:title="t('toolbar.more')"
|
|
407
|
+
:aria-label="t('toolbar.more')"
|
|
408
|
+
aria-haspopup="menu"
|
|
409
|
+
@click="openWideMore"
|
|
410
|
+
>
|
|
411
|
+
<span class="fe-icon">⋯</span>
|
|
412
|
+
</button>
|
|
413
|
+
|
|
414
|
+
<!-- Görünmez ölçüm şeridi: TÜM aksiyonlar her zaman burada render
|
|
415
|
+
edilir; katlama hesabı gerçek genişliklerden yapılır. -->
|
|
416
|
+
<div ref="measureEl" class="fe-toolbar__measure" aria-hidden="true">
|
|
417
|
+
<button
|
|
418
|
+
v-for="a in toolbarItems"
|
|
419
|
+
:key="'m-' + a.key"
|
|
420
|
+
type="button"
|
|
421
|
+
class="fe-btn"
|
|
422
|
+
tabindex="-1"
|
|
423
|
+
>
|
|
424
|
+
<span class="fe-icon">{{ a.icon }}</span>
|
|
425
|
+
<span class="fe-btn__label">{{ a.label }}</span>
|
|
426
|
+
</button>
|
|
427
|
+
</div>
|
|
428
|
+
|
|
331
429
|
<button
|
|
332
430
|
v-if="pasteEnabled && mode === 'none' && !trashActive && !atVirtualRoot && canWrite !== false"
|
|
333
431
|
type="button"
|
|
@@ -589,5 +687,16 @@ function onMoreSelect(a: ContextAction) {
|
|
|
589
687
|
@select="onMoreSelect"
|
|
590
688
|
/>
|
|
591
689
|
</template>
|
|
690
|
+
|
|
691
|
+
<!-- ui-fix — wide modda katlanan aksiyonların ⋯ menüsü -->
|
|
692
|
+
<ContextMenu
|
|
693
|
+
v-if="!narrow"
|
|
694
|
+
ref="wideMoreRef"
|
|
695
|
+
:locale="locale"
|
|
696
|
+
:theme="theme || 'auto'"
|
|
697
|
+
:sheet="false"
|
|
698
|
+
:actions="overflowToolbarItems"
|
|
699
|
+
@select="(a) => fire(a.key)"
|
|
700
|
+
/>
|
|
592
701
|
</div>
|
|
593
702
|
</template>
|
|
@@ -341,6 +341,12 @@ export function useKeyboardShortcuts(rootEl: Ref<HTMLElement | null>, handlers:
|
|
|
341
341
|
const root = rootEl.value;
|
|
342
342
|
if (!root) return;
|
|
343
343
|
|
|
344
|
+
/* ui-fix — while a context menu is open, global shortcuts stay quiet
|
|
345
|
+
* (except Esc, which the menu scopes itself). Otherwise e.g. Delete
|
|
346
|
+
* opens a modal UNDER the menu backdrop and the UI wedges: the
|
|
347
|
+
* backdrop intercepts every click on the new dialog. */
|
|
348
|
+
if (e.key !== 'Escape' && document.querySelector('.fe-ctx-backdrop')) return;
|
|
349
|
+
|
|
344
350
|
// Skip when the event originates inside a form control — don't
|
|
345
351
|
// want `Delete` while editing a filename, `/` while typing in the
|
|
346
352
|
// search box, etc. Escape always goes through so modals can close.
|
|
@@ -30,6 +30,9 @@ import type { ViewMode } from '../types/FileNode';
|
|
|
30
30
|
/** Per-tab split state — the secondary pane's own location. */
|
|
31
31
|
export interface TabSplit {
|
|
32
32
|
path: string;
|
|
33
|
+
/** ui-fix — the pane's OWN view mode (list/grid/gallery); undefined
|
|
34
|
+
* inherits the main panel's mode at split time. */
|
|
35
|
+
viewMode?: ViewMode;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export interface TabState {
|
|
@@ -82,10 +85,16 @@ export function useTabs(opts: UseTabsOptions) {
|
|
|
82
85
|
// gallery wave) must survive a round-trip through an older schema.
|
|
83
86
|
const vm =
|
|
84
87
|
typeof t.viewMode === 'string' && t.viewMode ? (t.viewMode as ViewMode) : 'list';
|
|
85
|
-
const rawSplit = t.split as { path?: unknown } | null | undefined;
|
|
88
|
+
const rawSplit = t.split as { path?: unknown; viewMode?: unknown } | null | undefined;
|
|
86
89
|
const split =
|
|
87
90
|
rawSplit && typeof rawSplit === 'object' && typeof rawSplit.path === 'string'
|
|
88
|
-
? {
|
|
91
|
+
? {
|
|
92
|
+
path: rawSplit.path,
|
|
93
|
+
viewMode:
|
|
94
|
+
typeof rawSplit.viewMode === 'string' && rawSplit.viewMode
|
|
95
|
+
? (rawSplit.viewMode as ViewMode)
|
|
96
|
+
: undefined,
|
|
97
|
+
}
|
|
89
98
|
: null;
|
|
90
99
|
clean.push({
|
|
91
100
|
id: typeof t.id === 'string' && t.id ? t.id : makeId(),
|
package/src/styles/base.css
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
position: relative;
|
|
5
5
|
display: flex;
|
|
6
6
|
flex-direction: column;
|
|
7
|
+
box-sizing: border-box;
|
|
7
8
|
min-height: 420px;
|
|
8
9
|
height: 100%;
|
|
9
10
|
background: var(--fe-bg);
|
|
@@ -3099,7 +3100,12 @@ filex-explorer {
|
|
|
3099
3100
|
flex: 1 1 auto;
|
|
3100
3101
|
min-width: 0;
|
|
3101
3102
|
overflow-x: auto;
|
|
3102
|
-
|
|
3103
|
+
/* Tab overflow scrolls (wheel / drag / keyboard) but never shows a
|
|
3104
|
+
* scrollbar strip under the tabs — the 30px bar cannot fit one. */
|
|
3105
|
+
scrollbar-width: none;
|
|
3106
|
+
}
|
|
3107
|
+
.fe-tabs__scroll::-webkit-scrollbar {
|
|
3108
|
+
display: none;
|
|
3103
3109
|
}
|
|
3104
3110
|
.fe-tabs__tab {
|
|
3105
3111
|
display: flex;
|
|
@@ -3708,3 +3714,29 @@ filex-explorer {
|
|
|
3708
3714
|
text-decoration: underline;
|
|
3709
3715
|
}
|
|
3710
3716
|
/* === /wiring:e2 === */
|
|
3717
|
+
|
|
3718
|
+
/* === ui-fix — toolbar tek satır + katlama + ölçüm şeridi ============= */
|
|
3719
|
+
.fe:not(.fe--narrow) .fe-toolbar {
|
|
3720
|
+
flex-wrap: nowrap;
|
|
3721
|
+
}
|
|
3722
|
+
.fe:not(.fe--narrow) .fe-toolbar__primary {
|
|
3723
|
+
flex: 1 1 auto;
|
|
3724
|
+
min-width: 0;
|
|
3725
|
+
flex-wrap: nowrap;
|
|
3726
|
+
overflow: hidden;
|
|
3727
|
+
position: relative;
|
|
3728
|
+
}
|
|
3729
|
+
.fe-toolbar__measure {
|
|
3730
|
+
position: absolute;
|
|
3731
|
+
visibility: hidden;
|
|
3732
|
+
pointer-events: none;
|
|
3733
|
+
height: 0;
|
|
3734
|
+
overflow: hidden;
|
|
3735
|
+
display: flex;
|
|
3736
|
+
gap: 8px;
|
|
3737
|
+
white-space: nowrap;
|
|
3738
|
+
}
|
|
3739
|
+
.fe-toolbar__measure .fe-btn {
|
|
3740
|
+
flex: none;
|
|
3741
|
+
}
|
|
3742
|
+
/* === /ui-fix ========================================================= */
|
package/src/styles/variables.css
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
--fe-text: #1a1e27;
|
|
14
14
|
--fe-text-muted: #5a6475;
|
|
15
15
|
--fe-text-on-primary: #ffffff;
|
|
16
|
-
--fe-primary: #3b82f6
|
|
16
|
+
--fe-primary: #2f6fe0; /* ui-fix: beyaz metinle WCAG AA (eski #3b82f6 = 3.68) */
|
|
17
17
|
--fe-primary-hover: #2563eb;
|
|
18
18
|
--fe-danger: #dc2626;
|
|
19
19
|
--fe-danger-hover: #b91c1c;
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
--fe-icon-archive: #78716c;
|
|
103
103
|
--fe-icon-code: #4f46e5;
|
|
104
104
|
--fe-icon-text: #64748b;
|
|
105
|
-
--fe-icon-unknown: #
|
|
105
|
+
--fe-icon-unknown: #64748b; /* ui-fix: acik temada 3:1 UI kontrasti */
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/* Dark values — same selector strategy as the palette blocks above. */
|