@brftech/filex-core 0.2.0 → 0.4.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-Dgvl43gn.js} +2 -2
- package/dist/{ArchiveViewer-DCZ1FZYV.js.map → ArchiveViewer-Dgvl43gn.js.map} +1 -1
- package/dist/{CsvViewer-DMvnp82x.js → CsvViewer-gWO5pc8F.js} +2 -2
- package/dist/{CsvViewer-DMvnp82x.js.map → CsvViewer-gWO5pc8F.js.map} +1 -1
- package/dist/{DrawioViewer-B5Io0yTO.js → DrawioViewer-DUAlRIAb.js} +2 -2
- package/dist/{DrawioViewer-B5Io0yTO.js.map → DrawioViewer-DUAlRIAb.js.map} +1 -1
- package/dist/{EpubViewer-z6RZd3K8.js → EpubViewer-DIXzuQWK.js} +2 -2
- package/dist/{EpubViewer-z6RZd3K8.js.map → EpubViewer-DIXzuQWK.js.map} +1 -1
- package/dist/{IpynbViewer-DfL4EauH.js → IpynbViewer-CJ-6pU_Z.js} +2 -2
- package/dist/{IpynbViewer-DfL4EauH.js.map → IpynbViewer-CJ-6pU_Z.js.map} +1 -1
- package/dist/{MermaidViewer-QNl_o7V-.js → MermaidViewer-CyQQDyRb.js} +2 -2
- package/dist/{MermaidViewer-QNl_o7V-.js.map → MermaidViewer-CyQQDyRb.js.map} +1 -1
- package/dist/{PsdViewer-Btmpr0Fz.js → PsdViewer-ChfaXVgN.js} +2 -2
- package/dist/{PsdViewer-Btmpr0Fz.js.map → PsdViewer-ChfaXVgN.js.map} +1 -1
- package/dist/{TiffViewer-CMLpwds5.js → TiffViewer-CsfD-NG6.js} +2 -2
- package/dist/{TiffViewer-CMLpwds5.js.map → TiffViewer-CsfD-NG6.js.map} +1 -1
- package/dist/{Viewer3D-gf3cb2gv.js → Viewer3D-BQ2XYmkN.js} +2 -2
- package/dist/{Viewer3D-gf3cb2gv.js.map → Viewer3D-BQ2XYmkN.js.map} +1 -1
- package/dist/filex-core.js +1 -1
- package/dist/filex-core.umd.cjs +42 -42
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index-PnhGunSA.js +7972 -0
- package/dist/index-PnhGunSA.js.map +1 -0
- package/dist/index.d.ts +69 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +146 -0
- package/src/components/ContextMenu.vue +101 -3
- package/src/components/InspectorPanel.vue +455 -0
- package/src/components/Toolbar.vue +250 -5
- package/src/composables/useFileApi.ts +59 -0
- package/src/composables/useKeyboardShortcuts.ts +11 -0
- package/src/locales/en.ts +45 -0
- package/src/locales/tr.ts +45 -0
- package/src/styles/base.css +458 -0
- package/dist/index-D49iw00E.js +0 -7119
- package/dist/index-D49iw00E.js.map +0 -1
package/src/FileExplorer.vue
CHANGED
|
@@ -45,6 +45,7 @@ import GridView from './components/GridView.vue';
|
|
|
45
45
|
import ContextMenu, { type ContextAction } from './components/ContextMenu.vue';
|
|
46
46
|
import UploadProgress from './components/UploadProgress.vue';
|
|
47
47
|
import PendingOpsTray from './components/PendingOpsTray.vue';
|
|
48
|
+
import InspectorPanel from './components/InspectorPanel.vue'; /* koru:k1 */
|
|
48
49
|
/* cila:c wiring */
|
|
49
50
|
import CommandPalette from './components/CommandPalette.vue';
|
|
50
51
|
import ShortcutsHelp from './components/ShortcutsHelp.vue';
|
|
@@ -410,6 +411,55 @@ const convertTarget = ref<FileNode | null>(null);
|
|
|
410
411
|
const showPerm = ref(false);
|
|
411
412
|
const permTarget = ref<FileNode | null>(null);
|
|
412
413
|
|
|
414
|
+
/* === koru:k1 — inspector (details) panel ===
|
|
415
|
+
* Open/closed preference persists under `filex.inspector`; the panel itself
|
|
416
|
+
* mounts with v-if so the closed state leaves zero DOM behind. */
|
|
417
|
+
const INSPECTOR_LS_KEY = 'filex.inspector';
|
|
418
|
+
const showInspector = ref<boolean>(
|
|
419
|
+
(() => {
|
|
420
|
+
try {
|
|
421
|
+
return localStorage.getItem(INSPECTOR_LS_KEY) === '1';
|
|
422
|
+
} catch {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
})(),
|
|
426
|
+
);
|
|
427
|
+
function persistInspector(v: boolean) {
|
|
428
|
+
try {
|
|
429
|
+
localStorage.setItem(INSPECTOR_LS_KEY, v ? '1' : '0');
|
|
430
|
+
} catch {
|
|
431
|
+
/* quota / private mode */
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function toggleInspector() {
|
|
435
|
+
showInspector.value = !showInspector.value;
|
|
436
|
+
persistInspector(showInspector.value);
|
|
437
|
+
}
|
|
438
|
+
function openInspector() {
|
|
439
|
+
if (!showInspector.value) {
|
|
440
|
+
showInspector.value = true;
|
|
441
|
+
persistInspector(true);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function closeInspector() {
|
|
445
|
+
if (showInspector.value) {
|
|
446
|
+
showInspector.value = false;
|
|
447
|
+
persistInspector(false);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// Folder summary label for the no-selection state.
|
|
451
|
+
const inspectorDirLabel = computed(() => {
|
|
452
|
+
if (trashMode.value) return t('node.trash');
|
|
453
|
+
const p = (currentPath.value ?? '').replace(/^\/+|\/+$/g, '');
|
|
454
|
+
if (!p) return adapter.value || t('breadcrumb.root');
|
|
455
|
+
return p.split('/').pop() || p;
|
|
456
|
+
});
|
|
457
|
+
function onInspectorManage(n: FileNode) {
|
|
458
|
+
permTarget.value = n;
|
|
459
|
+
showPerm.value = true;
|
|
460
|
+
}
|
|
461
|
+
/* === /koru:k1 === */
|
|
462
|
+
|
|
413
463
|
// RBAC helpers. '' means ACL is not enforced on this storage → full access
|
|
414
464
|
// (the pre-RBAC default). Otherwise 'editor'/'owner' may write; only 'owner'
|
|
415
465
|
// manages permissions. Enforcement is server-side; this just shapes the menu.
|
|
@@ -443,6 +493,40 @@ const ctxRef = ref<InstanceType<typeof ContextMenu> | null>(null);
|
|
|
443
493
|
const rootEl = ref<HTMLElement | null>(null);
|
|
444
494
|
const toolbarRef = ref<InstanceType<typeof Toolbar> | null>(null);
|
|
445
495
|
|
|
496
|
+
/* bag:b4 — narrow/embed mini mode.
|
|
497
|
+
* isNarrow: container width < 560px (ResizeObserver on the .fe root, so it
|
|
498
|
+
* tracks the EMBED container, not the viewport) → root gets `fe--narrow`,
|
|
499
|
+
* the toolbar collapses and the upload FAB appears.
|
|
500
|
+
* isCoarse: touch-first device → context menus render as a bottom sheet. */
|
|
501
|
+
const isNarrow = ref(false);
|
|
502
|
+
const isCoarse = ref(false);
|
|
503
|
+
let narrowRO: ResizeObserver | undefined;
|
|
504
|
+
let coarseMq: MediaQueryList | undefined;
|
|
505
|
+
function syncCoarsePointer(e?: MediaQueryListEvent | MediaQueryList) {
|
|
506
|
+
isCoarse.value = !!(e && 'matches' in e && e.matches);
|
|
507
|
+
}
|
|
508
|
+
onMounted(() => {
|
|
509
|
+
if (typeof ResizeObserver !== 'undefined' && rootEl.value) {
|
|
510
|
+
narrowRO = new ResizeObserver((entries) => {
|
|
511
|
+
const w = entries[0]?.contentRect?.width ?? rootEl.value?.clientWidth ?? 0;
|
|
512
|
+
isNarrow.value = w > 0 && w < 560;
|
|
513
|
+
});
|
|
514
|
+
narrowRO.observe(rootEl.value);
|
|
515
|
+
}
|
|
516
|
+
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
517
|
+
coarseMq = window.matchMedia('(pointer: coarse)');
|
|
518
|
+
syncCoarsePointer(coarseMq);
|
|
519
|
+
coarseMq.addEventListener?.('change', syncCoarsePointer);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
onBeforeUnmount(() => {
|
|
523
|
+
narrowRO?.disconnect();
|
|
524
|
+
narrowRO = undefined;
|
|
525
|
+
coarseMq?.removeEventListener?.('change', syncCoarsePointer);
|
|
526
|
+
coarseMq = undefined;
|
|
527
|
+
});
|
|
528
|
+
/* /bag:b4 */
|
|
529
|
+
|
|
446
530
|
// Toast (tiny, no lib). Evolved into a snackbar: plain messages keep the old
|
|
447
531
|
// 2.5s auto-hide; messages carrying an action ("Geri Al") stay 8s and can be
|
|
448
532
|
// dismissed by click or Esc.
|
|
@@ -991,6 +1075,9 @@ useKeyboardShortcuts(rootEl, {
|
|
|
991
1075
|
showPreview.value = false;
|
|
992
1076
|
ctxRef.value?.hide();
|
|
993
1077
|
dismissToast();
|
|
1078
|
+
/* koru:k1 — Esc closes the narrow-mode inspector overlay only; the wide
|
|
1079
|
+
side panel is a persistent surface toggled by `i` / the toolbar. */
|
|
1080
|
+
if (isNarrow.value) closeInspector();
|
|
994
1081
|
},
|
|
995
1082
|
onFocusSearch: () => toolbarRef.value?.focusSearch(),
|
|
996
1083
|
onCut: () => cut(),
|
|
@@ -1005,6 +1092,7 @@ useKeyboardShortcuts(rootEl, {
|
|
|
1005
1092
|
showShortcutsHelp.value = true;
|
|
1006
1093
|
},
|
|
1007
1094
|
/* /cila:c wiring */
|
|
1095
|
+
onToggleInspector: () => toggleInspector() /* koru:k1 */,
|
|
1008
1096
|
hasSelection: () => !selection.isEmpty.value,
|
|
1009
1097
|
});
|
|
1010
1098
|
|
|
@@ -1303,6 +1391,7 @@ function selectionActionList(sel: FileNode[]): ContextAction[] {
|
|
|
1303
1391
|
{ key: 'download', label: t('ctx.download'), icon: '⬇', hidden: !single, disabled: !isFile },
|
|
1304
1392
|
{ key: 'convert', label: t('ctx.convert'), icon: '🔄', hidden: !single || !effectiveConvertUrl.value || !w, disabled: !isFile },
|
|
1305
1393
|
{ key: 'access', label: accessLabel, icon: '🔗', hidden: !single || !w },
|
|
1394
|
+
{ key: 'details', label: t('ctx.details'), icon: 'ℹ', hidden: !any } /* koru:k1 */,
|
|
1306
1395
|
{ key: 'copy-id', label: copyIdLabel, icon: '🆔', hidden: !singleHasId, disabled: !singleHasId },
|
|
1307
1396
|
{ divider: true, key: 'sep1', label: '', hidden: !w },
|
|
1308
1397
|
{ key: 'rename', label: t('ctx.rename'), icon: '✎', hidden: !single || !w, disabled: !single },
|
|
@@ -1379,6 +1468,9 @@ async function dispatchItemAction(key: string, targets: FileNode[]) {
|
|
|
1379
1468
|
showPerm.value = true;
|
|
1380
1469
|
}
|
|
1381
1470
|
break;
|
|
1471
|
+
case 'details' /* koru:k1 */:
|
|
1472
|
+
openInspector();
|
|
1473
|
+
break;
|
|
1382
1474
|
case 'copy-id':
|
|
1383
1475
|
if (targets[0] && typeof targets[0].id === 'number') {
|
|
1384
1476
|
const id = targets[0].id;
|
|
@@ -1964,6 +2056,7 @@ function buildAuthHeaders(extra: Record<string, string> = {}) {
|
|
|
1964
2056
|
'fe--theme-dark': config.theme === 'dark',
|
|
1965
2057
|
'fe--is-dragover': dragOver,
|
|
1966
2058
|
'fe--density-compact': density === 'compact' /* cila:a density */,
|
|
2059
|
+
'fe--narrow': isNarrow /* bag:b4 */,
|
|
1967
2060
|
}"
|
|
1968
2061
|
tabindex="-1"
|
|
1969
2062
|
@dragenter="onDragEnter"
|
|
@@ -1985,6 +2078,10 @@ function buildAuthHeaders(extra: Record<string, string> = {}) {
|
|
|
1985
2078
|
:at-virtual-root="atVirtualRoot"
|
|
1986
2079
|
:can-write="canWriteHere"
|
|
1987
2080
|
:locale="locale"
|
|
2081
|
+
:narrow="isNarrow /* bag:b4 */"
|
|
2082
|
+
:theme="config.theme || 'auto' /* bag:b4 */"
|
|
2083
|
+
:inspector-open="showInspector /* koru:k1 */"
|
|
2084
|
+
@toggle-inspector="toggleInspector /* koru:k1 */"
|
|
1988
2085
|
@update:view-mode="viewMode = $event"
|
|
1989
2086
|
@update:search-query="searchQuery = $event"
|
|
1990
2087
|
@update:density="density = $event"
|
|
@@ -2026,6 +2123,10 @@ function buildAuthHeaders(extra: Record<string, string> = {}) {
|
|
|
2026
2123
|
</span>
|
|
2027
2124
|
</div>
|
|
2028
2125
|
|
|
2126
|
+
<!-- koru:k1 — fe__main lays the listing body and the inspector panel out
|
|
2127
|
+
as flex siblings (row). Without the inspector open it is visually
|
|
2128
|
+
identical to the previous direct-child fe__body. -->
|
|
2129
|
+
<div class="fe__main">
|
|
2029
2130
|
<div class="fe__body" @click.self="selection.clear()">
|
|
2030
2131
|
<!-- Initial load: skeleton ghosts (view-mode aware) instead of an
|
|
2031
2132
|
empty/"no files" flash. Only when there's nothing yet — navigation
|
|
@@ -2210,6 +2311,26 @@ function buildAuthHeaders(extra: Record<string, string> = {}) {
|
|
|
2210
2311
|
/>
|
|
2211
2312
|
</div>
|
|
2212
2313
|
|
|
2314
|
+
<!-- koru:k1 — inspector (details) panel; v-if keeps the closed state
|
|
2315
|
+
free of any DOM. Narrow mode renders it as a full-size overlay. -->
|
|
2316
|
+
<InspectorPanel
|
|
2317
|
+
v-if="showInspector"
|
|
2318
|
+
:api="api"
|
|
2319
|
+
:nodes="selection.nodes.value"
|
|
2320
|
+
:dir-label="inspectorDirLabel"
|
|
2321
|
+
:dir-count="files.length"
|
|
2322
|
+
:dir-perm="dirPerm"
|
|
2323
|
+
:locale="locale"
|
|
2324
|
+
:narrow="isNarrow"
|
|
2325
|
+
:thumb-src="thumbs.src"
|
|
2326
|
+
@close="closeInspector"
|
|
2327
|
+
@manage-permissions="onInspectorManage"
|
|
2328
|
+
@toast="flashToast"
|
|
2329
|
+
@changed="() => load()"
|
|
2330
|
+
/>
|
|
2331
|
+
</div>
|
|
2332
|
+
<!-- /koru:k1 fe__main -->
|
|
2333
|
+
|
|
2213
2334
|
<div v-if="dragOver" class="fe__dragover">
|
|
2214
2335
|
<div class="fe__dragover-card">
|
|
2215
2336
|
<span class="fe-icon">⬆</span>
|
|
@@ -2230,10 +2351,35 @@ function buildAuthHeaders(extra: Record<string, string> = {}) {
|
|
|
2230
2351
|
@dismiss="(id) => pendingOps.dismiss(id)"
|
|
2231
2352
|
/>
|
|
2232
2353
|
|
|
2354
|
+
<!-- bag:b4 — narrow-mode upload FAB (hidden in trash / read-only /
|
|
2355
|
+
virtual root; PendingOpsTray+UploadProgress shift up via CSS). -->
|
|
2356
|
+
<button
|
|
2357
|
+
v-if="isNarrow && emptyCanUpload"
|
|
2358
|
+
type="button"
|
|
2359
|
+
class="fe-fab"
|
|
2360
|
+
:title="t('toolbar.upload')"
|
|
2361
|
+
:aria-label="t('toolbar.upload')"
|
|
2362
|
+
@click="triggerUpload"
|
|
2363
|
+
>
|
|
2364
|
+
<svg
|
|
2365
|
+
class="fe-ficon"
|
|
2366
|
+
viewBox="0 0 24 24"
|
|
2367
|
+
fill="none"
|
|
2368
|
+
stroke="currentColor"
|
|
2369
|
+
stroke-width="2.2"
|
|
2370
|
+
stroke-linecap="round"
|
|
2371
|
+
aria-hidden="true"
|
|
2372
|
+
focusable="false"
|
|
2373
|
+
>
|
|
2374
|
+
<path d="M12 5v14M5 12h14" />
|
|
2375
|
+
</svg>
|
|
2376
|
+
</button>
|
|
2377
|
+
|
|
2233
2378
|
<ContextMenu
|
|
2234
2379
|
ref="ctxRef"
|
|
2235
2380
|
:locale="locale"
|
|
2236
2381
|
:theme="config.theme || 'auto'"
|
|
2382
|
+
:sheet="isCoarse /* bag:b4 */"
|
|
2237
2383
|
:actions="contextActions"
|
|
2238
2384
|
@select="onContextAction"
|
|
2239
2385
|
/>
|
|
@@ -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"
|