@brftech/filex-core 0.2.0 → 0.3.0
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-DCZ1FZYV.js → ArchiveViewer-CYrKPEbE.js} +2 -2
- package/dist/{ArchiveViewer-DCZ1FZYV.js.map → ArchiveViewer-CYrKPEbE.js.map} +1 -1
- package/dist/{CsvViewer-DMvnp82x.js → CsvViewer-C3ZU4Uua.js} +2 -2
- package/dist/{CsvViewer-DMvnp82x.js.map → CsvViewer-C3ZU4Uua.js.map} +1 -1
- package/dist/{DrawioViewer-B5Io0yTO.js → DrawioViewer-15ZL4ken.js} +2 -2
- package/dist/{DrawioViewer-B5Io0yTO.js.map → DrawioViewer-15ZL4ken.js.map} +1 -1
- package/dist/{EpubViewer-z6RZd3K8.js → EpubViewer-CrXcDgmo.js} +2 -2
- package/dist/{EpubViewer-z6RZd3K8.js.map → EpubViewer-CrXcDgmo.js.map} +1 -1
- package/dist/{IpynbViewer-DfL4EauH.js → IpynbViewer-BAb9rV2Q.js} +2 -2
- package/dist/{IpynbViewer-DfL4EauH.js.map → IpynbViewer-BAb9rV2Q.js.map} +1 -1
- package/dist/{MermaidViewer-QNl_o7V-.js → MermaidViewer-BN8GN4FY.js} +2 -2
- package/dist/{MermaidViewer-QNl_o7V-.js.map → MermaidViewer-BN8GN4FY.js.map} +1 -1
- package/dist/{PsdViewer-Btmpr0Fz.js → PsdViewer-o-kWQExw.js} +2 -2
- package/dist/{PsdViewer-Btmpr0Fz.js.map → PsdViewer-o-kWQExw.js.map} +1 -1
- package/dist/{TiffViewer-CMLpwds5.js → TiffViewer-AjMVBzxp.js} +2 -2
- package/dist/{TiffViewer-CMLpwds5.js.map → TiffViewer-AjMVBzxp.js.map} +1 -1
- package/dist/{Viewer3D-gf3cb2gv.js → Viewer3D-BR3WDbxQ.js} +2 -2
- package/dist/{Viewer3D-gf3cb2gv.js.map → Viewer3D-BR3WDbxQ.js.map} +1 -1
- package/dist/filex-core.js +1 -1
- package/dist/filex-core.umd.cjs +38 -38
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index-BG5upYlH.js +7392 -0
- package/dist/index-BG5upYlH.js.map +1 -0
- package/dist/index.d.ts +49 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +62 -0
- package/src/components/ContextMenu.vue +101 -3
- package/src/components/Toolbar.vue +216 -5
- package/src/locales/en.ts +6 -0
- package/src/locales/tr.ts +6 -0
- package/src/styles/base.css +137 -0
- package/dist/index-D49iw00E.js +0 -7119
- package/dist/index-D49iw00E.js.map +0 -1
|
@@ -31,13 +31,22 @@ const props = defineProps<{
|
|
|
31
31
|
* explorer tree.
|
|
32
32
|
*/
|
|
33
33
|
theme?: ThemeMode;
|
|
34
|
+
/**
|
|
35
|
+
* bag:b4 — bottom-sheet presentation for coarse-pointer (touch)
|
|
36
|
+
* contexts. When true the menu renders as a full-width sheet sliding
|
|
37
|
+
* up from the bottom edge (grab handle, ≥44px touch targets; closed
|
|
38
|
+
* by overlay tap, dragging the handle down, or Esc). The position
|
|
39
|
+
* passed to show() is ignored. When absent/false the classic anchored
|
|
40
|
+
* menu renders — desktop right-click behavior is untouched.
|
|
41
|
+
*/
|
|
42
|
+
sheet?: boolean;
|
|
34
43
|
}>();
|
|
35
44
|
|
|
36
45
|
const emit = defineEmits<{
|
|
37
46
|
(e: 'select', action: ContextAction, target: FileNode[]): void;
|
|
38
47
|
}>();
|
|
39
48
|
|
|
40
|
-
|
|
49
|
+
const { t } = useLocale(() => props.locale); // bag:b4 — sheet aria labels need t()
|
|
41
50
|
|
|
42
51
|
const open = ref(false);
|
|
43
52
|
const x = ref(0);
|
|
@@ -54,6 +63,12 @@ async function show(ev: { clientX: number; clientY: number }, nodes: FileNode[])
|
|
|
54
63
|
// bottom/right edge doesn't push the menu off-screen (it opens down-right
|
|
55
64
|
// from the cursor by default).
|
|
56
65
|
await nextTick();
|
|
66
|
+
if (props.sheet) {
|
|
67
|
+
// bag:b4 — sheet mode: no anchoring; focus the panel so Esc closes it
|
|
68
|
+
// without requiring a prior click inside.
|
|
69
|
+
sheetEl.value?.focus();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
57
72
|
clampToViewport();
|
|
58
73
|
}
|
|
59
74
|
|
|
@@ -74,7 +89,48 @@ function clampToViewport() {
|
|
|
74
89
|
|
|
75
90
|
function hide() {
|
|
76
91
|
open.value = false;
|
|
92
|
+
/* bag:b4 — reset any in-flight sheet drag so reopening starts clean. */
|
|
93
|
+
sheetDragging.value = false;
|
|
94
|
+
sheetDragY.value = 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* bag:b4 — sheet drag-to-dismiss. Bound to the grab handle only, so the
|
|
98
|
+
* scrollable item list keeps its native touch scrolling. Dragging past the
|
|
99
|
+
* threshold closes; anything less snaps back. */
|
|
100
|
+
const sheetEl = ref<HTMLElement | null>(null);
|
|
101
|
+
const sheetDragging = ref(false);
|
|
102
|
+
const sheetDragY = ref(0);
|
|
103
|
+
let sheetDragStart = 0;
|
|
104
|
+
|
|
105
|
+
function onSheetDragStart(e: TouchEvent) {
|
|
106
|
+
const t0 = e.touches[0];
|
|
107
|
+
if (!t0) return;
|
|
108
|
+
sheetDragging.value = true;
|
|
109
|
+
sheetDragStart = t0.clientY;
|
|
110
|
+
sheetDragY.value = 0;
|
|
111
|
+
}
|
|
112
|
+
function onSheetDragMove(e: TouchEvent) {
|
|
113
|
+
if (!sheetDragging.value) return;
|
|
114
|
+
const t0 = e.touches[0];
|
|
115
|
+
if (!t0) return;
|
|
116
|
+
sheetDragY.value = Math.max(0, t0.clientY - sheetDragStart);
|
|
77
117
|
}
|
|
118
|
+
function onSheetDragEnd() {
|
|
119
|
+
if (!sheetDragging.value) return;
|
|
120
|
+
const shouldClose = sheetDragY.value > 72;
|
|
121
|
+
sheetDragging.value = false;
|
|
122
|
+
if (shouldClose) {
|
|
123
|
+
hide();
|
|
124
|
+
} else {
|
|
125
|
+
sheetDragY.value = 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const sheetStyle = computed(() =>
|
|
129
|
+
sheetDragY.value > 0
|
|
130
|
+
? { transform: `translateY(${sheetDragY.value}px)`, transition: 'none' }
|
|
131
|
+
: undefined,
|
|
132
|
+
);
|
|
133
|
+
/* /bag:b4 */
|
|
78
134
|
|
|
79
135
|
function pick(a: ContextAction) {
|
|
80
136
|
if (a.disabled) return;
|
|
@@ -113,17 +169,59 @@ defineExpose({ show, hide });
|
|
|
113
169
|
|
|
114
170
|
<template>
|
|
115
171
|
<Teleport to="body">
|
|
116
|
-
<transition name="fe-ctx">
|
|
172
|
+
<transition :name="sheet ? 'fe-sheet' : 'fe-ctx'">
|
|
117
173
|
<div
|
|
118
174
|
v-if="open"
|
|
119
175
|
class="fe-ctx-backdrop"
|
|
120
|
-
:class="themeClass"
|
|
176
|
+
:class="[themeClass, { 'fe-ctx-backdrop--sheet': sheet }]"
|
|
121
177
|
:data-prefers-dark="prefersDark ? '1' : '0'"
|
|
122
178
|
@click="hide"
|
|
123
179
|
@contextmenu.prevent="hide"
|
|
124
180
|
@keydown="onKey"
|
|
125
181
|
>
|
|
182
|
+
<!-- bag:b4 — bottom-sheet variant (coarse pointer / touch) -->
|
|
183
|
+
<div
|
|
184
|
+
v-if="sheet"
|
|
185
|
+
ref="sheetEl"
|
|
186
|
+
class="fe-sheet"
|
|
187
|
+
role="menu"
|
|
188
|
+
:aria-label="t('sheet.menu')"
|
|
189
|
+
tabindex="-1"
|
|
190
|
+
:style="sheetStyle"
|
|
191
|
+
@click.stop
|
|
192
|
+
>
|
|
193
|
+
<div
|
|
194
|
+
class="fe-sheet__handle"
|
|
195
|
+
role="button"
|
|
196
|
+
tabindex="0"
|
|
197
|
+
:aria-label="t('sheet.close')"
|
|
198
|
+
@click="hide"
|
|
199
|
+
@keydown.enter.prevent="hide"
|
|
200
|
+
@touchstart.passive="onSheetDragStart"
|
|
201
|
+
@touchmove.passive="onSheetDragMove"
|
|
202
|
+
@touchend="onSheetDragEnd"
|
|
203
|
+
@touchcancel="onSheetDragEnd"
|
|
204
|
+
/>
|
|
205
|
+
<div class="fe-sheet__items">
|
|
206
|
+
<template v-for="(a, i) in visibleActions" :key="a.key || i">
|
|
207
|
+
<div v-if="a.divider" class="fe-ctx__sep" />
|
|
208
|
+
<button
|
|
209
|
+
v-else
|
|
210
|
+
type="button"
|
|
211
|
+
class="fe-ctx__item fe-sheet__item"
|
|
212
|
+
:class="{ 'is-danger': a.danger, 'is-disabled': a.disabled }"
|
|
213
|
+
:disabled="a.disabled"
|
|
214
|
+
role="menuitem"
|
|
215
|
+
@click="pick(a)"
|
|
216
|
+
>
|
|
217
|
+
<span v-if="a.icon" class="fe-ctx__icon" aria-hidden="true">{{ a.icon }}</span>
|
|
218
|
+
<span class="fe-ctx__label">{{ a.label }}</span>
|
|
219
|
+
</button>
|
|
220
|
+
</template>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
126
223
|
<div
|
|
224
|
+
v-else
|
|
127
225
|
ref="menuEl"
|
|
128
226
|
class="fe-ctx"
|
|
129
227
|
role="menu"
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* Presentational only — all logic (rename, share, …) lives in
|
|
12
12
|
* FileExplorer.vue, which listens for `action` emits.
|
|
13
13
|
*/
|
|
14
|
-
import { computed, onMounted, ref, watch } from 'vue';
|
|
14
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
15
15
|
import type { ViewMode } from '../types/FileNode';
|
|
16
|
-
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
17
|
-
import
|
|
16
|
+
import type { LocaleCode, ThemeMode } from '../types/ExplorerConfig';
|
|
17
|
+
import ContextMenu, { type ContextAction } from './ContextMenu.vue';
|
|
18
18
|
import { useLocale } from '../composables/useLocale';
|
|
19
19
|
|
|
20
20
|
export type SelectionMode = 'none' | 'single-file' | 'single-dir' | 'multi';
|
|
@@ -58,6 +58,19 @@ const props = defineProps<{
|
|
|
58
58
|
*/
|
|
59
59
|
canWrite?: boolean;
|
|
60
60
|
locale: LocaleCode;
|
|
61
|
+
/**
|
|
62
|
+
* bag:b4 — narrow/embed mini mode. When true the toolbar collapses to
|
|
63
|
+
* [↑?] [🔍] [⬆] [⋯]: secondary actions (New Folder, Refresh, density,
|
|
64
|
+
* view switcher, selection actions, Paste) move into the "⋯" overflow
|
|
65
|
+
* menu and search expands from an icon to a full-width input. When
|
|
66
|
+
* absent/false the classic wide layout renders unchanged.
|
|
67
|
+
*/
|
|
68
|
+
narrow?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* bag:b4 — resolved theme, forwarded to the teleported overflow menu
|
|
71
|
+
* (which loses the `.fe` variable scope under <body>).
|
|
72
|
+
*/
|
|
73
|
+
theme?: ThemeMode;
|
|
61
74
|
}>();
|
|
62
75
|
|
|
63
76
|
const emit = defineEmits<{
|
|
@@ -114,7 +127,12 @@ function onSearchInput(ev: Event) {
|
|
|
114
127
|
debounce = setTimeout(() => emit('update:searchQuery', v), 200);
|
|
115
128
|
}
|
|
116
129
|
|
|
117
|
-
function focusSearch() {
|
|
130
|
+
async function focusSearch() {
|
|
131
|
+
/* bag:b4 — in narrow mode the input is collapsed behind the icon. */
|
|
132
|
+
if (props.narrow && !searchOpen.value) {
|
|
133
|
+
searchOpen.value = true;
|
|
134
|
+
await nextTick();
|
|
135
|
+
}
|
|
118
136
|
searchEl.value?.focus();
|
|
119
137
|
searchEl.value?.select();
|
|
120
138
|
}
|
|
@@ -132,10 +150,120 @@ const toolbarItems = computed(() => props.actions.filter((a) => !a.divider && !a
|
|
|
132
150
|
function fire(key: string) {
|
|
133
151
|
emit('action', key);
|
|
134
152
|
}
|
|
153
|
+
|
|
154
|
+
/* === bag:b4 — narrow-mode state: expandable search + "⋯" overflow menu === */
|
|
155
|
+
|
|
156
|
+
const searchOpen = ref(false);
|
|
157
|
+
// Leaving narrow mode discards the transient expanded-search state so the
|
|
158
|
+
// wide layout always comes back exactly as it was.
|
|
159
|
+
watch(
|
|
160
|
+
() => props.narrow,
|
|
161
|
+
(n) => {
|
|
162
|
+
if (!n) searchOpen.value = false;
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
async function openSearch() {
|
|
167
|
+
searchOpen.value = true;
|
|
168
|
+
await nextTick();
|
|
169
|
+
searchEl.value?.focus();
|
|
170
|
+
}
|
|
171
|
+
function closeSearch() {
|
|
172
|
+
// Keep the query (it still filters the listing); the icon shows an
|
|
173
|
+
// active state while a query is set.
|
|
174
|
+
searchOpen.value = false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Coarse-pointer detection — the overflow menu renders as a bottom sheet on
|
|
178
|
+
// touch devices, matching the file context menu.
|
|
179
|
+
const coarse = ref(false);
|
|
180
|
+
let coarseMq: MediaQueryList | undefined;
|
|
181
|
+
function syncCoarse(e?: MediaQueryListEvent | MediaQueryList) {
|
|
182
|
+
coarse.value = !!(e && 'matches' in e && e.matches);
|
|
183
|
+
}
|
|
184
|
+
onMounted(() => {
|
|
185
|
+
if (typeof window === 'undefined' || !window.matchMedia) return;
|
|
186
|
+
coarseMq = window.matchMedia('(pointer: coarse)');
|
|
187
|
+
syncCoarse(coarseMq);
|
|
188
|
+
coarseMq.addEventListener?.('change', syncCoarse);
|
|
189
|
+
});
|
|
190
|
+
onBeforeUnmount(() => {
|
|
191
|
+
coarseMq?.removeEventListener?.('change', syncCoarse);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const moreBtnEl = ref<HTMLElement | null>(null);
|
|
195
|
+
const moreRef = ref<InstanceType<typeof ContextMenu> | null>(null);
|
|
196
|
+
|
|
197
|
+
// Everything the wide toolbar renders as standalone buttons, folded into one
|
|
198
|
+
// action list: folder-level writes (New Folder / Paste), the shared
|
|
199
|
+
// selection actions, then the view utilities (Refresh / density / view mode).
|
|
200
|
+
const moreActions = computed<ContextAction[]>(() => {
|
|
201
|
+
const list: ContextAction[] = [];
|
|
202
|
+
const writable =
|
|
203
|
+
!props.trashActive && !props.atVirtualRoot && props.canWrite !== false;
|
|
204
|
+
if (mode.value === 'none' && writable) {
|
|
205
|
+
list.push({ key: 'new-folder', label: t('toolbar.new_folder'), icon: '📁' });
|
|
206
|
+
if (props.pasteEnabled) list.push({ key: 'paste', label: t('ctx.paste'), icon: '📋' });
|
|
207
|
+
}
|
|
208
|
+
list.push(...toolbarItems.value);
|
|
209
|
+
if (list.length) list.push({ divider: true, key: 'bag-sep', label: '' });
|
|
210
|
+
list.push({ key: 'refresh', label: t('toolbar.refresh'), icon: '↻' });
|
|
211
|
+
list.push({
|
|
212
|
+
key: 'density',
|
|
213
|
+
label:
|
|
214
|
+
density.value === 'compact'
|
|
215
|
+
? t('toolbar.density.comfortable')
|
|
216
|
+
: t('toolbar.density.compact'),
|
|
217
|
+
icon: '⇅',
|
|
218
|
+
});
|
|
219
|
+
list.push(
|
|
220
|
+
props.viewMode === 'list'
|
|
221
|
+
? { key: 'view-grid', label: t('toolbar.view.grid'), icon: '▦' }
|
|
222
|
+
: { key: 'view-list', label: t('toolbar.view.list'), icon: '☰' },
|
|
223
|
+
);
|
|
224
|
+
return list;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
function openMore() {
|
|
228
|
+
const r = moreBtnEl.value?.getBoundingClientRect();
|
|
229
|
+
moreRef.value?.show({ clientX: r ? r.right : 0, clientY: r ? r.bottom + 4 : 0 }, []);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function onMoreSelect(a: ContextAction) {
|
|
233
|
+
switch (a.key) {
|
|
234
|
+
case 'new-folder':
|
|
235
|
+
emit('new-folder');
|
|
236
|
+
break;
|
|
237
|
+
case 'refresh':
|
|
238
|
+
emit('refresh');
|
|
239
|
+
break;
|
|
240
|
+
case 'density':
|
|
241
|
+
toggleDensity();
|
|
242
|
+
break;
|
|
243
|
+
case 'view-list':
|
|
244
|
+
emit('update:viewMode', 'list');
|
|
245
|
+
break;
|
|
246
|
+
case 'view-grid':
|
|
247
|
+
emit('update:viewMode', 'grid');
|
|
248
|
+
break;
|
|
249
|
+
default:
|
|
250
|
+
fire(a.key);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* === /bag:b4 === */
|
|
135
255
|
</script>
|
|
136
256
|
|
|
137
257
|
<template>
|
|
138
|
-
<div
|
|
258
|
+
<div
|
|
259
|
+
class="fe-toolbar"
|
|
260
|
+
:class="{
|
|
261
|
+
'fe-toolbar--narrow': narrow /* bag:b4 */,
|
|
262
|
+
'fe-toolbar--searching': narrow && searchOpen /* bag:b4 */,
|
|
263
|
+
}"
|
|
264
|
+
>
|
|
265
|
+
<!-- bag:b4 — wide layout, untouched; renders exactly as before when not narrow -->
|
|
266
|
+
<template v-if="!narrow">
|
|
139
267
|
<div class="fe-toolbar__primary">
|
|
140
268
|
<button
|
|
141
269
|
v-if="canGoUp"
|
|
@@ -280,5 +408,88 @@ function fire(key: string) {
|
|
|
280
408
|
<span class="fe-icon">▦</span>
|
|
281
409
|
</button>
|
|
282
410
|
</div>
|
|
411
|
+
</template>
|
|
412
|
+
|
|
413
|
+
<!-- bag:b4 — narrow layout: [↑?] ... [🔍] [⬆] [⋯], search expands full-width -->
|
|
414
|
+
<template v-else>
|
|
415
|
+
<template v-if="searchOpen">
|
|
416
|
+
<div class="fe-search fe-search--full">
|
|
417
|
+
<input
|
|
418
|
+
ref="searchEl"
|
|
419
|
+
type="search"
|
|
420
|
+
class="fe-search__input"
|
|
421
|
+
:placeholder="t('toolbar.search.placeholder')"
|
|
422
|
+
:value="localSearch"
|
|
423
|
+
:aria-label="t('toolbar.search')"
|
|
424
|
+
@input="onSearchInput"
|
|
425
|
+
@keydown.esc.stop.prevent="closeSearch"
|
|
426
|
+
/>
|
|
427
|
+
</div>
|
|
428
|
+
<button
|
|
429
|
+
type="button"
|
|
430
|
+
class="fe-btn fe-btn--icon-only"
|
|
431
|
+
:title="t('toolbar.search.close')"
|
|
432
|
+
:aria-label="t('toolbar.search.close')"
|
|
433
|
+
@click="closeSearch"
|
|
434
|
+
>
|
|
435
|
+
<span class="fe-icon">✕</span>
|
|
436
|
+
</button>
|
|
437
|
+
</template>
|
|
438
|
+
<template v-else>
|
|
439
|
+
<button
|
|
440
|
+
v-if="canGoUp"
|
|
441
|
+
type="button"
|
|
442
|
+
class="fe-btn fe-btn--icon-only"
|
|
443
|
+
:title="t('toolbar.go_up')"
|
|
444
|
+
:aria-label="t('toolbar.go_up')"
|
|
445
|
+
@click="emit('go-up')"
|
|
446
|
+
>
|
|
447
|
+
<span class="fe-icon">↑</span>
|
|
448
|
+
</button>
|
|
449
|
+
|
|
450
|
+
<div class="fe-toolbar__spacer" />
|
|
451
|
+
|
|
452
|
+
<button
|
|
453
|
+
type="button"
|
|
454
|
+
class="fe-btn fe-btn--icon-only fe-toolbar__search-toggle"
|
|
455
|
+
:class="{ 'is-active': !!localSearch }"
|
|
456
|
+
:title="t('toolbar.search')"
|
|
457
|
+
:aria-label="t('toolbar.search')"
|
|
458
|
+
@click="openSearch"
|
|
459
|
+
>
|
|
460
|
+
<span class="fe-icon">🔍</span>
|
|
461
|
+
</button>
|
|
462
|
+
<button
|
|
463
|
+
v-if="!atVirtualRoot && canWrite !== false"
|
|
464
|
+
type="button"
|
|
465
|
+
class="fe-btn fe-btn--icon-only"
|
|
466
|
+
:title="t('toolbar.upload')"
|
|
467
|
+
:aria-label="t('toolbar.upload')"
|
|
468
|
+
@click="emit('upload')"
|
|
469
|
+
>
|
|
470
|
+
<span class="fe-icon">⬆</span>
|
|
471
|
+
</button>
|
|
472
|
+
<button
|
|
473
|
+
ref="moreBtnEl"
|
|
474
|
+
type="button"
|
|
475
|
+
class="fe-btn fe-btn--icon-only fe-toolbar__more"
|
|
476
|
+
:title="t('toolbar.more')"
|
|
477
|
+
:aria-label="t('toolbar.more')"
|
|
478
|
+
aria-haspopup="menu"
|
|
479
|
+
@click="openMore"
|
|
480
|
+
>
|
|
481
|
+
<span class="fe-icon">⋯</span>
|
|
482
|
+
</button>
|
|
483
|
+
</template>
|
|
484
|
+
|
|
485
|
+
<ContextMenu
|
|
486
|
+
ref="moreRef"
|
|
487
|
+
:locale="locale"
|
|
488
|
+
:theme="theme || 'auto'"
|
|
489
|
+
:sheet="coarse"
|
|
490
|
+
:actions="moreActions"
|
|
491
|
+
@select="onMoreSelect"
|
|
492
|
+
/>
|
|
493
|
+
</template>
|
|
283
494
|
</div>
|
|
284
495
|
</template>
|
package/src/locales/en.ts
CHANGED
|
@@ -195,4 +195,10 @@ export const en: Record<string, string> = {
|
|
|
195
195
|
'palette.save': 'Save search',
|
|
196
196
|
'palette.saved.delete': 'Delete saved search',
|
|
197
197
|
'search.in_content': 'In content',
|
|
198
|
+
|
|
199
|
+
/* === bag:b4 === */
|
|
200
|
+
'toolbar.more': 'More actions',
|
|
201
|
+
'toolbar.search.close': 'Close search',
|
|
202
|
+
'sheet.menu': 'Actions',
|
|
203
|
+
'sheet.close': 'Close',
|
|
198
204
|
};
|
package/src/locales/tr.ts
CHANGED
|
@@ -195,4 +195,10 @@ export const tr: Record<string, string> = {
|
|
|
195
195
|
'palette.save': 'Aramayı kaydet',
|
|
196
196
|
'palette.saved.delete': 'Kayıtlı aramayı sil',
|
|
197
197
|
'search.in_content': 'İçerikte',
|
|
198
|
+
|
|
199
|
+
/* === bag:b4 === */
|
|
200
|
+
'toolbar.more': 'Diğer işlemler',
|
|
201
|
+
'toolbar.search.close': 'Aramayı kapat',
|
|
202
|
+
'sheet.menu': 'İşlemler',
|
|
203
|
+
'sheet.close': 'Kapat',
|
|
198
204
|
};
|
package/src/styles/base.css
CHANGED
|
@@ -1931,3 +1931,140 @@ filex-explorer {
|
|
|
1931
1931
|
.fe-list__snippet {
|
|
1932
1932
|
max-width: 100%;
|
|
1933
1933
|
}
|
|
1934
|
+
|
|
1935
|
+
/* === bag:b4 — narrow/embed mini mode === */
|
|
1936
|
+
/* Everything below is scoped to `.fe--narrow`, `.fe-toolbar--narrow`,
|
|
1937
|
+
* `.fe-fab` or `.fe-sheet` — none of which exist in the wide desktop
|
|
1938
|
+
* layout, so the classic look is untouched when the mode is off. */
|
|
1939
|
+
|
|
1940
|
+
/* Narrow toolbar: a single non-wrapping row of icon buttons. */
|
|
1941
|
+
.fe-toolbar--narrow {
|
|
1942
|
+
flex-wrap: nowrap;
|
|
1943
|
+
gap: 6px;
|
|
1944
|
+
}
|
|
1945
|
+
.fe-toolbar--narrow .fe-btn.is-active {
|
|
1946
|
+
background: var(--fe-bg-selected);
|
|
1947
|
+
}
|
|
1948
|
+
/* Expanded search: the input takes the whole row. */
|
|
1949
|
+
.fe-toolbar--narrow .fe-search--full {
|
|
1950
|
+
flex: 1 1 auto;
|
|
1951
|
+
min-width: 0;
|
|
1952
|
+
}
|
|
1953
|
+
.fe-toolbar--narrow .fe-search--full .fe-search__input {
|
|
1954
|
+
width: 100%;
|
|
1955
|
+
min-width: 0;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
/* Upload FAB — bottom-right corner of the explorer (narrow mode only). */
|
|
1959
|
+
.fe-fab {
|
|
1960
|
+
position: absolute;
|
|
1961
|
+
right: 16px;
|
|
1962
|
+
bottom: 16px;
|
|
1963
|
+
width: 56px;
|
|
1964
|
+
height: 56px;
|
|
1965
|
+
border: 0;
|
|
1966
|
+
border-radius: 50%;
|
|
1967
|
+
background: var(--fe-primary);
|
|
1968
|
+
color: var(--fe-text-on-primary);
|
|
1969
|
+
font-size: 24px;
|
|
1970
|
+
display: flex;
|
|
1971
|
+
align-items: center;
|
|
1972
|
+
justify-content: center;
|
|
1973
|
+
box-shadow: var(--fe-shadow);
|
|
1974
|
+
cursor: pointer;
|
|
1975
|
+
z-index: 45;
|
|
1976
|
+
transition: background 0.15s, transform 0.15s;
|
|
1977
|
+
}
|
|
1978
|
+
.fe-fab:hover {
|
|
1979
|
+
background: var(--fe-primary-hover);
|
|
1980
|
+
}
|
|
1981
|
+
.fe-fab:active {
|
|
1982
|
+
transform: scale(0.94);
|
|
1983
|
+
}
|
|
1984
|
+
.fe-fab:focus-visible {
|
|
1985
|
+
outline: 2px solid var(--fe-primary);
|
|
1986
|
+
outline-offset: 3px;
|
|
1987
|
+
}
|
|
1988
|
+
/* Keep the corner trays clear of the FAB while narrow. */
|
|
1989
|
+
.fe--narrow .fe-upload,
|
|
1990
|
+
.fe--narrow .fe-ops {
|
|
1991
|
+
bottom: 84px;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
/* Bottom sheet (ContextMenu sheet variant, coarse-pointer devices). */
|
|
1995
|
+
.fe-ctx-backdrop--sheet {
|
|
1996
|
+
background: rgba(13, 19, 27, 0.42);
|
|
1997
|
+
}
|
|
1998
|
+
.fe-sheet {
|
|
1999
|
+
position: fixed;
|
|
2000
|
+
left: 0;
|
|
2001
|
+
right: 0;
|
|
2002
|
+
bottom: 0;
|
|
2003
|
+
max-height: 70vh;
|
|
2004
|
+
display: flex;
|
|
2005
|
+
flex-direction: column;
|
|
2006
|
+
background: var(--fe-bg);
|
|
2007
|
+
color: var(--fe-text);
|
|
2008
|
+
border: 1px solid var(--fe-border);
|
|
2009
|
+
border-bottom: 0;
|
|
2010
|
+
border-top-left-radius: var(--fe-radius-lg);
|
|
2011
|
+
border-top-right-radius: var(--fe-radius-lg);
|
|
2012
|
+
box-shadow: 0 -10px 32px rgba(15, 23, 42, 0.28);
|
|
2013
|
+
padding: 0 8px calc(8px + env(safe-area-inset-bottom, 0px));
|
|
2014
|
+
outline: none;
|
|
2015
|
+
z-index: 90;
|
|
2016
|
+
}
|
|
2017
|
+
.fe-sheet__handle {
|
|
2018
|
+
flex: 0 0 auto;
|
|
2019
|
+
display: flex;
|
|
2020
|
+
align-items: center;
|
|
2021
|
+
justify-content: center;
|
|
2022
|
+
padding: 12px 0 8px;
|
|
2023
|
+
cursor: grab;
|
|
2024
|
+
touch-action: none;
|
|
2025
|
+
}
|
|
2026
|
+
.fe-sheet__handle::before {
|
|
2027
|
+
content: '';
|
|
2028
|
+
width: 40px;
|
|
2029
|
+
height: 4px;
|
|
2030
|
+
border-radius: 99px;
|
|
2031
|
+
background: var(--fe-border-strong);
|
|
2032
|
+
}
|
|
2033
|
+
.fe-sheet__items {
|
|
2034
|
+
overflow-y: auto;
|
|
2035
|
+
overscroll-behavior: contain;
|
|
2036
|
+
-webkit-overflow-scrolling: touch;
|
|
2037
|
+
}
|
|
2038
|
+
/* Big touch targets (≥44px) on sheet entries. */
|
|
2039
|
+
.fe-sheet__item {
|
|
2040
|
+
min-height: 44px;
|
|
2041
|
+
padding: 10px 14px;
|
|
2042
|
+
font-size: 15px;
|
|
2043
|
+
gap: 12px;
|
|
2044
|
+
}
|
|
2045
|
+
/* Sheet transitions: backdrop fades, panel slides up from the bottom. */
|
|
2046
|
+
.fe-sheet-enter-active,
|
|
2047
|
+
.fe-sheet-leave-active {
|
|
2048
|
+
transition: opacity 0.18s ease;
|
|
2049
|
+
}
|
|
2050
|
+
.fe-sheet-enter-active .fe-sheet,
|
|
2051
|
+
.fe-sheet-leave-active .fe-sheet {
|
|
2052
|
+
transition: transform 0.22s ease;
|
|
2053
|
+
}
|
|
2054
|
+
.fe-sheet-enter-from,
|
|
2055
|
+
.fe-sheet-leave-to {
|
|
2056
|
+
opacity: 0;
|
|
2057
|
+
}
|
|
2058
|
+
.fe-sheet-enter-from .fe-sheet,
|
|
2059
|
+
.fe-sheet-leave-to .fe-sheet {
|
|
2060
|
+
transform: translateY(100%);
|
|
2061
|
+
}
|
|
2062
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2063
|
+
.fe-sheet-enter-active,
|
|
2064
|
+
.fe-sheet-leave-active,
|
|
2065
|
+
.fe-sheet-enter-active .fe-sheet,
|
|
2066
|
+
.fe-sheet-leave-active .fe-sheet,
|
|
2067
|
+
.fe-fab {
|
|
2068
|
+
transition: none;
|
|
2069
|
+
}
|
|
2070
|
+
}
|