@brftech/filex-core 0.41.2 → 0.41.4
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-Dq1c4-G5.js → ArchiveViewer-oqaEyqcB.js} +2 -2
- package/dist/{ArchiveViewer-Dq1c4-G5.js.map → ArchiveViewer-oqaEyqcB.js.map} +1 -1
- package/dist/{CsvViewer-CeHslAc0.js → CsvViewer-BfT-4WpB.js} +2 -2
- package/dist/{CsvViewer-CeHslAc0.js.map → CsvViewer-BfT-4WpB.js.map} +1 -1
- package/dist/{DrawioViewer-p03u7Oda.js → DrawioViewer-Cu2ZquNO.js} +2 -2
- package/dist/{DrawioViewer-p03u7Oda.js.map → DrawioViewer-Cu2ZquNO.js.map} +1 -1
- package/dist/{EpubViewer-D08Y-Tww.js → EpubViewer-yfAfnrYF.js} +2 -2
- package/dist/{EpubViewer-D08Y-Tww.js.map → EpubViewer-yfAfnrYF.js.map} +1 -1
- package/dist/{IpynbViewer-mG7p3Sil.js → IpynbViewer-DLSOs3yU.js} +2 -2
- package/dist/{IpynbViewer-mG7p3Sil.js.map → IpynbViewer-DLSOs3yU.js.map} +1 -1
- package/dist/{MermaidViewer-CCPg1Q3W.js → MermaidViewer-D2AsT14x.js} +2 -2
- package/dist/{MermaidViewer-CCPg1Q3W.js.map → MermaidViewer-D2AsT14x.js.map} +1 -1
- package/dist/{PsdViewer-CDEHTiUN.js → PsdViewer-DOza4QJt.js} +2 -2
- package/dist/{PsdViewer-CDEHTiUN.js.map → PsdViewer-DOza4QJt.js.map} +1 -1
- package/dist/{TiffViewer-DnWRUGOK.js → TiffViewer-24MCOn1t.js} +2 -2
- package/dist/{TiffViewer-DnWRUGOK.js.map → TiffViewer-24MCOn1t.js.map} +1 -1
- package/dist/{Viewer3D-COCQp53D.js → Viewer3D-BVjtbhhc.js} +2 -2
- package/dist/{Viewer3D-COCQp53D.js.map → Viewer3D-BVjtbhhc.js.map} +1 -1
- package/dist/filex-core.js +1 -1
- package/dist/filex-core.umd.cjs +46 -46
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/{index-JUiZzLmF.js → index-B8PLGR-X.js} +9146 -9075
- package/dist/index-B8PLGR-X.js.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/FilePane.vue +21 -33
- package/src/components/GalleryView.vue +44 -8
- package/src/components/GridView.vue +58 -19
- package/src/components/HomeView.vue +5 -4
- package/src/components/ItemCheck.vue +47 -0
- package/src/components/ListView.vue +51 -48
- package/src/composables/useRowTouch.ts +85 -35
- package/src/styles/base.css +111 -6
- package/dist/index-JUiZzLmF.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brftech/filex-core",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.4",
|
|
4
4
|
"description": "filex core — Vue 3 source of truth for the filex file manager (FileExplorer + ConnectionsPanel SFCs, composables, types)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/filex-core.umd.cjs",
|
|
@@ -237,7 +237,7 @@ const emit = defineEmits<{
|
|
|
237
237
|
/** The virtual `.trash` row was opened — the trash view carries the restore
|
|
238
238
|
* actions and belongs to the main pane, so the host decides. */
|
|
239
239
|
(e: 'open-trash'): void;
|
|
240
|
-
(e: 'click-row', node: FileNode, mod: { ctrl: boolean; shift: boolean
|
|
240
|
+
(e: 'click-row', node: FileNode, mod: { ctrl: boolean; shift: boolean }): void;
|
|
241
241
|
/** Right-click. `null` = empty space (nothing selected → "Paste" only). */
|
|
242
242
|
(e: 'context', node: FileNode | null, ev: MouseEvent): void;
|
|
243
243
|
(e: 'clear-selection'): void;
|
|
@@ -444,48 +444,36 @@ const paneSubfolders = computed(() =>
|
|
|
444
444
|
* `useSelection` instance for this pane answers. One set of Ctrl/Shift
|
|
445
445
|
* semantics for both panes — the old right pane had a simplified copy in
|
|
446
446
|
* which Shift behaved as Ctrl. */
|
|
447
|
-
/* issue #26
|
|
448
|
-
* a habitual double-click lands on whatever the NEW listing put under the
|
|
447
|
+
/* issue #26 — any click that is not on the checkbox OPENS, so the second click
|
|
448
|
+
* of a habitual double-click lands on whatever the NEW listing put under the
|
|
449
449
|
* pointer (the folder already opened on the first click). Without this guard
|
|
450
|
-
* that click would open
|
|
451
|
-
*
|
|
452
|
-
const
|
|
453
|
-
let
|
|
450
|
+
* that click would open a row the user never aimed at — or tick its box — and
|
|
451
|
+
* a file would open twice (two viewer tabs). */
|
|
452
|
+
const OPEN_GUARD_MS = 500;
|
|
453
|
+
let openedAt = 0;
|
|
454
454
|
|
|
455
|
-
function
|
|
456
|
-
return Date.now() -
|
|
455
|
+
function withinOpenGuard(): boolean {
|
|
456
|
+
return Date.now() - openedAt < OPEN_GUARD_MS;
|
|
457
457
|
}
|
|
458
458
|
|
|
459
459
|
function onViewClick(n: FileNode, mod: ClickMod) {
|
|
460
|
-
if (
|
|
461
|
-
/* issue #26,
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
460
|
+
if (withinOpenGuard()) return;
|
|
461
|
+
/* issue #26, fourth round — the reporter's rule, on every device: "only
|
|
462
|
+
* clicking on checkbox selects it, any other click will open". The checkbox
|
|
463
|
+
* is the one click that reaches the selection; a click or tap anywhere else
|
|
464
|
+
* on the item opens it, Ctrl/Shift held or not, selection or not. See
|
|
465
|
+
* composables/useRowTouch. */
|
|
466
|
+
if (mod.check) {
|
|
467
|
+
emit('activate');
|
|
468
|
+
emit('click-row', n, { ctrl: true, shift: mod.shift });
|
|
468
469
|
return;
|
|
469
470
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
* exactly as a double-click would. Once something is selected (a long press
|
|
473
|
-
* selects, via its menu) such a tap adds to or removes from the selection,
|
|
474
|
-
* so picking several files still works. A mouse click beside the name keeps
|
|
475
|
-
* click-to-select; see composables/useRowTouch. */
|
|
476
|
-
if (mod.touch && !mod.ctrl && !mod.shift) {
|
|
477
|
-
if (props.selected.size === 0) {
|
|
478
|
-
onRowOpen(n);
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
mod = { ...mod, ctrl: true };
|
|
482
|
-
}
|
|
483
|
-
emit('activate');
|
|
484
|
-
emit('click-row', n, mod);
|
|
471
|
+
openedAt = Date.now();
|
|
472
|
+
onRowOpen(n);
|
|
485
473
|
}
|
|
486
474
|
|
|
487
475
|
function onViewDbl(n: FileNode) {
|
|
488
|
-
if (
|
|
476
|
+
if (withinOpenGuard()) return;
|
|
489
477
|
onRowOpen(n);
|
|
490
478
|
}
|
|
491
479
|
|
|
@@ -13,7 +13,8 @@ import { hasInternalDrag } from '../lib/dragOut';
|
|
|
13
13
|
import type { FileNode } from '../types/FileNode';
|
|
14
14
|
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
15
15
|
import { useLocale } from '../composables/useLocale';
|
|
16
|
-
import { clickMod, useRowTouch, type ClickMod } from '../composables/useRowTouch';
|
|
16
|
+
import { checkMod, clickMod, useRowTouch, type ClickMod } from '../composables/useRowTouch';
|
|
17
|
+
import ItemCheck from './ItemCheck.vue';
|
|
17
18
|
import { encryptedFolderTile, fileIconTile, isEncryptedFolder } from '../lib/fileIcons';
|
|
18
19
|
import {
|
|
19
20
|
createFilePreviews,
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
} from '../lib/dateGroups'; /* gruplama */
|
|
31
32
|
import { useSortStore, type ListingOrder } from '../lib/sortOrder'; /* gruplama */
|
|
32
33
|
|
|
33
|
-
const props = defineProps<{
|
|
34
|
+
const props = withDefaults(defineProps<{
|
|
34
35
|
files: FileNode[];
|
|
35
36
|
selected: Set<string>;
|
|
36
37
|
clipped?: Set<string>;
|
|
@@ -74,7 +75,16 @@ const props = defineProps<{
|
|
|
74
75
|
* order the tiles are not in. Omitted = an ordinary folder listing.
|
|
75
76
|
*/
|
|
76
77
|
order?: ListingOrder;
|
|
77
|
-
|
|
78
|
+
/**
|
|
79
|
+
* issue #26 — draw the checkbox on each card, the one click that selects.
|
|
80
|
+
* Default on. Same prop, same meaning as GridView's.
|
|
81
|
+
*/
|
|
82
|
+
selectable?: boolean;
|
|
83
|
+
}>(), {
|
|
84
|
+
// ⚠ Not left to `undefined`: Vue casts an absent boolean prop to false, and
|
|
85
|
+
// the listing would lose its checkboxes wherever the host said nothing.
|
|
86
|
+
selectable: true,
|
|
87
|
+
});
|
|
78
88
|
|
|
79
89
|
const emit = defineEmits<{
|
|
80
90
|
(e: 'click-card', node: FileNode, mod: ClickMod): void;
|
|
@@ -158,8 +168,17 @@ function isSelected(n: FileNode): boolean {
|
|
|
158
168
|
return props.selected.has(n.path);
|
|
159
169
|
}
|
|
160
170
|
|
|
171
|
+
/**
|
|
172
|
+
* issue #26 — the card's checkbox is the one click that selects (a click
|
|
173
|
+
* anywhere else on the card opens it), routed exactly like the list row's:
|
|
174
|
+
* one `click-card` emit marked `check`, one `useSelection.click`.
|
|
175
|
+
*/
|
|
176
|
+
function onCheckClick(n: FileNode, ev: MouseEvent) {
|
|
177
|
+
emit('click-card', n, checkMod(ev));
|
|
178
|
+
}
|
|
179
|
+
|
|
161
180
|
function onClick(n: FileNode, ev: MouseEvent) {
|
|
162
|
-
emit('click-card', n, clickMod(ev, touch.isTap(ev)
|
|
181
|
+
emit('click-card', n, clickMod(ev, touch.isTap(ev)));
|
|
163
182
|
}
|
|
164
183
|
|
|
165
184
|
function onDbl(n: FileNode) {
|
|
@@ -207,8 +226,13 @@ function onItemDrop(n: FileNode, ev: DragEvent) {
|
|
|
207
226
|
}
|
|
208
227
|
|
|
209
228
|
/* Long press → the card's menu; a tap is reported as a tap (issue #26). */
|
|
210
|
-
const touch = useRowTouch<FileNode>(
|
|
211
|
-
|
|
229
|
+
const touch = useRowTouch<FileNode>(
|
|
230
|
+
(n, at) =>
|
|
231
|
+
emit('context-card', n, { ...at, preventDefault: () => {}, stopPropagation: () => {} } as unknown as MouseEvent),
|
|
232
|
+
{
|
|
233
|
+
// A finger lifted on the item, off its controls, opens at touchend — see useRowTouch.
|
|
234
|
+
onTap: (n) => emit('click-card', n, { ctrl: false, shift: false, touch: true }),
|
|
235
|
+
},
|
|
212
236
|
);
|
|
213
237
|
|
|
214
238
|
/**
|
|
@@ -233,7 +257,7 @@ function metaFor(n: FileNode): string {
|
|
|
233
257
|
<template>
|
|
234
258
|
<div
|
|
235
259
|
class="fe-gal"
|
|
236
|
-
:class="{ 'is-loading': loading }"
|
|
260
|
+
:class="{ 'is-loading': loading, 'has-selection': selectable && selected.size > 0 }"
|
|
237
261
|
role="listbox"
|
|
238
262
|
aria-multiselectable="true"
|
|
239
263
|
:aria-label="t('gallery.aria')"
|
|
@@ -268,7 +292,7 @@ function metaFor(n: FileNode): string {
|
|
|
268
292
|
@dragleave="onItemDragLeave(n)"
|
|
269
293
|
@drop="onItemDrop(n, $event)"
|
|
270
294
|
@touchstart.passive="touch.onTouchStart(n, $event)"
|
|
271
|
-
@touchend
|
|
295
|
+
@touchend="touch.onTouchEnd"
|
|
272
296
|
@touchmove.passive="touch.onTouchMove"
|
|
273
297
|
>
|
|
274
298
|
<div class="fe-gal__thumb" :ref="(el) => bindPreview(el, n)">
|
|
@@ -346,6 +370,18 @@ function metaFor(n: FileNode): string {
|
|
|
346
370
|
class="fe-thumb__play"
|
|
347
371
|
aria-hidden="true"
|
|
348
372
|
></span>
|
|
373
|
+
<!-- issue #26 — the card's checkbox, the one click that selects: the
|
|
374
|
+
corner opposite the star, on the same reveal rule plus "anything
|
|
375
|
+
is selected" (styles/base.css .fe-item-check). -->
|
|
376
|
+
<span
|
|
377
|
+
v-if="selectable"
|
|
378
|
+
class="fe-item-check fe-gal__check"
|
|
379
|
+
data-fe-control
|
|
380
|
+
@click.stop="onCheckClick(n, $event)"
|
|
381
|
+
@dblclick.stop
|
|
382
|
+
>
|
|
383
|
+
<ItemCheck :on="isSelected(n)" :label="nodeDisplayName(n)" />
|
|
384
|
+
</span>
|
|
349
385
|
<div v-if="canStar(n)" class="fe-gal__star" @click.stop @dblclick.stop>
|
|
350
386
|
<StarButton
|
|
351
387
|
:starred="!!starredIds?.has(n.id!)"
|
|
@@ -20,7 +20,8 @@ import type { FileNode } from '../types/FileNode';
|
|
|
20
20
|
import { hasInternalDrag } from '../lib/dragOut';
|
|
21
21
|
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
22
22
|
import { useLocale } from '../composables/useLocale';
|
|
23
|
-
import { clickMod, useRowTouch, type ClickMod } from '../composables/useRowTouch';
|
|
23
|
+
import { checkMod, clickMod, useRowTouch, type ClickMod } from '../composables/useRowTouch';
|
|
24
|
+
import ItemCheck from './ItemCheck.vue';
|
|
24
25
|
import { encryptedFolderTile, fileIconTile, isEncryptedFolder } from '../lib/fileIcons';
|
|
25
26
|
import {
|
|
26
27
|
createFilePreviews,
|
|
@@ -38,7 +39,7 @@ import StarButton from './StarButton.vue';
|
|
|
38
39
|
import { snippetSegments } from '../lib/snippet'; /* bul:s3 */
|
|
39
40
|
import { applyDragGhost } from '../lib/dragGhost'; /* wiring:c4 */
|
|
40
41
|
|
|
41
|
-
const props = defineProps<{
|
|
42
|
+
const props = withDefaults(defineProps<{
|
|
42
43
|
files: FileNode[];
|
|
43
44
|
selected: Set<string>;
|
|
44
45
|
clipped?: Set<string>;
|
|
@@ -108,7 +109,17 @@ const props = defineProps<{
|
|
|
108
109
|
* type tiles. Absent/false = the ordinary case.
|
|
109
110
|
*/
|
|
110
111
|
e2eActive?: boolean;
|
|
111
|
-
|
|
112
|
+
/**
|
|
113
|
+
* issue #26 — draw the checkbox on each card, the one click that selects.
|
|
114
|
+
* Default on. Home passes false: its cards open on a click and there is no
|
|
115
|
+
* selection there to put anything into.
|
|
116
|
+
*/
|
|
117
|
+
selectable?: boolean;
|
|
118
|
+
}>(), {
|
|
119
|
+
// ⚠ Not left to `undefined`: Vue casts an absent boolean prop to false, and
|
|
120
|
+
// the listing would lose its checkboxes wherever the host said nothing.
|
|
121
|
+
selectable: true,
|
|
122
|
+
});
|
|
112
123
|
|
|
113
124
|
const emit = defineEmits<{
|
|
114
125
|
(e: 'click-card', node: FileNode, mod: ClickMod): void;
|
|
@@ -267,8 +278,17 @@ function isSelected(n: FileNode): boolean {
|
|
|
267
278
|
return props.selected.has(n.path);
|
|
268
279
|
}
|
|
269
280
|
|
|
281
|
+
/**
|
|
282
|
+
* issue #26 — the card's checkbox is the one click that selects (a click
|
|
283
|
+
* anywhere else on the card opens it), routed exactly like the list row's:
|
|
284
|
+
* one `click-card` emit marked `check`, one `useSelection.click`.
|
|
285
|
+
*/
|
|
286
|
+
function onCheckClick(n: FileNode, ev: MouseEvent) {
|
|
287
|
+
emit('click-card', n, checkMod(ev));
|
|
288
|
+
}
|
|
289
|
+
|
|
270
290
|
function onClick(n: FileNode, ev: MouseEvent) {
|
|
271
|
-
emit('click-card', n, clickMod(ev, touch.isTap(ev)
|
|
291
|
+
emit('click-card', n, clickMod(ev, touch.isTap(ev)));
|
|
272
292
|
}
|
|
273
293
|
|
|
274
294
|
function onDbl(n: FileNode) {
|
|
@@ -329,8 +349,13 @@ function onItemDrop(n: FileNode, ev: DragEvent) {
|
|
|
329
349
|
}
|
|
330
350
|
|
|
331
351
|
/* Long press → the card's menu; a tap is reported as a tap (issue #26). */
|
|
332
|
-
const touch = useRowTouch<FileNode>(
|
|
333
|
-
|
|
352
|
+
const touch = useRowTouch<FileNode>(
|
|
353
|
+
(n, at) =>
|
|
354
|
+
emit('context-card', n, { ...at, preventDefault: () => {}, stopPropagation: () => {} } as unknown as MouseEvent),
|
|
355
|
+
{
|
|
356
|
+
// A finger lifted on the item, off its controls, opens at touchend — see useRowTouch.
|
|
357
|
+
onTap: (n) => emit('click-card', n, { ctrl: false, shift: false, touch: true }),
|
|
358
|
+
},
|
|
334
359
|
);
|
|
335
360
|
|
|
336
361
|
/**
|
|
@@ -382,7 +407,7 @@ function snippetTitle(snippet: string): string {
|
|
|
382
407
|
localized label + busy state. Structure/layout untouched. -->
|
|
383
408
|
<div
|
|
384
409
|
class="fe-grid"
|
|
385
|
-
:class="{ 'is-loading': loading }"
|
|
410
|
+
:class="{ 'is-loading': loading, 'has-selection': selectable && selected.size > 0 }"
|
|
386
411
|
role="listbox"
|
|
387
412
|
aria-multiselectable="true"
|
|
388
413
|
:aria-label="t('grid.aria')"
|
|
@@ -418,7 +443,7 @@ function snippetTitle(snippet: string): string {
|
|
|
418
443
|
@dragleave="onItemDragLeave(n) /* wiring:c4 */"
|
|
419
444
|
@drop="onItemDrop(n, $event)"
|
|
420
445
|
@touchstart.passive="touch.onTouchStart(n, $event)"
|
|
421
|
-
@touchend
|
|
446
|
+
@touchend="touch.onTouchEnd"
|
|
422
447
|
@touchmove.passive="touch.onTouchMove"
|
|
423
448
|
>
|
|
424
449
|
<!-- gorunum:v1 — the preview, files only: 184×108. A thumbnail when
|
|
@@ -496,7 +521,7 @@ function snippetTitle(snippet: string): string {
|
|
|
496
521
|
the person looking for what they starred, so the chip is always
|
|
497
522
|
painted once the file IS starred and only appears on hover/focus
|
|
498
523
|
otherwise (see .fe-grid__star in styles/base.css). @click.stop so
|
|
499
|
-
starring never doubles as
|
|
524
|
+
starring never doubles as opening the card. -->
|
|
500
525
|
<!-- gorunum:v1-preview — a frame lifted out of a video is, on a card,
|
|
501
526
|
indistinguishable from a photograph. The badge is the difference,
|
|
502
527
|
and it is drawn only over a real frame: a video that fell back to
|
|
@@ -525,16 +550,30 @@ function snippetTitle(snippet: string): string {
|
|
|
525
550
|
On a file it is the 56px footer under the preview; on a folder it is
|
|
526
551
|
the whole 56px card. -->
|
|
527
552
|
<div class="fe-grid__foot">
|
|
528
|
-
<!--
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
553
|
+
<!-- issue #26 — the checkbox takes the tile's place while it shows
|
|
554
|
+
(hovered, focused, selected, or once anything is selected), so
|
|
555
|
+
the name does not move when it appears. -->
|
|
556
|
+
<span class="fe-grid__lead" :class="{ 'fe-grid__lead--pick': selectable }">
|
|
557
|
+
<!-- eslint-disable-next-line vue/no-v-html — static markup from lib/fileIcons -->
|
|
558
|
+
<span
|
|
559
|
+
v-if="isEncryptedFolder(n)"
|
|
560
|
+
class="fe-grid__tile fe-grid__tile--svg"
|
|
561
|
+
role="img"
|
|
562
|
+
:aria-label="t('e2e.badge')"
|
|
563
|
+
v-html="encryptedFolderTile()"
|
|
564
|
+
></span>
|
|
565
|
+
<!-- eslint-disable-next-line vue/no-v-html — static markup from lib/fileIcons -->
|
|
566
|
+
<span v-else class="fe-grid__tile fe-grid__tile--svg" v-html="fileIconTile(n)"></span>
|
|
567
|
+
<span
|
|
568
|
+
v-if="selectable"
|
|
569
|
+
class="fe-item-check"
|
|
570
|
+
data-fe-control
|
|
571
|
+
@click.stop="onCheckClick(n, $event)"
|
|
572
|
+
@dblclick.stop
|
|
573
|
+
>
|
|
574
|
+
<ItemCheck :on="isSelected(n)" :label="nodeDisplayName(n)" />
|
|
575
|
+
</span>
|
|
576
|
+
</span>
|
|
538
577
|
<div class="fe-grid__main">
|
|
539
578
|
<div class="fe-grid__label" :title="n.basename">
|
|
540
579
|
{{ nodeDisplayName(n) }}
|
|
@@ -152,10 +152,9 @@ function storageCaption(s: HomeStorage): string {
|
|
|
152
152
|
/**
|
|
153
153
|
* A card opens what it names.
|
|
154
154
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* that only selected would be a click that did nothing at all.
|
|
155
|
+
* A single click, exactly as in the listing behind it (issue #26: a click
|
|
156
|
+
* anywhere but the checkbox opens). Home's cards carry no checkbox at all —
|
|
157
|
+
* there is no selection here to tick anything into (see NO_SELECTION).
|
|
159
158
|
*
|
|
160
159
|
* ⚠ Which is why the double click has to be swallowed: a real double click
|
|
161
160
|
* fires `click-card` twice, and two opens of a file are two viewer tabs. The
|
|
@@ -246,6 +245,7 @@ function openNode(n: FileNode) {
|
|
|
246
245
|
v-if="shownRecent.length"
|
|
247
246
|
:files="shownRecent"
|
|
248
247
|
:selected="NO_SELECTION"
|
|
248
|
+
:selectable="false"
|
|
249
249
|
:locale="locale"
|
|
250
250
|
:thumb-src="thumbSrc"
|
|
251
251
|
@click-card="openNode"
|
|
@@ -292,6 +292,7 @@ function openNode(n: FileNode) {
|
|
|
292
292
|
v-if="shownStarred.length"
|
|
293
293
|
:files="shownStarred"
|
|
294
294
|
:selected="NO_SELECTION"
|
|
295
|
+
:selectable="false"
|
|
295
296
|
:locale="locale"
|
|
296
297
|
:thumb-src="thumbSrc"
|
|
297
298
|
@click-card="openNode"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* ItemCheck — the checkbox on a listing item: the list row's tick column and
|
|
4
|
+
* the grid and gallery cards' corner (issue #26, fourth round: "only clicking
|
|
5
|
+
* on checkbox selects it"). One element for the three views, so the tick a
|
|
6
|
+
* card shows cannot drift from the one a row shows.
|
|
7
|
+
*
|
|
8
|
+
* The view binds the click on the wrapper around it and routes it through
|
|
9
|
+
* `checkMod` — the same `click-row` emit and the same `useSelection.click`
|
|
10
|
+
* every selection goes through, so shift ranges and toggles are one
|
|
11
|
+
* implementation, not two.
|
|
12
|
+
*
|
|
13
|
+
* ⚠ A button with role="checkbox", NOT an <input type="checkbox">. The input
|
|
14
|
+
* owns a `checked` state of its own: the browser flips it before the click
|
|
15
|
+
* handler runs and, if the default is prevented, flips it back AFTER Vue has
|
|
16
|
+
* patched — so the row went selected while the box stayed empty (measured:
|
|
17
|
+
* rowSelected=true, domChecked=false). Drawing the state from the selection
|
|
18
|
+
* leaves nothing to drift. Space and Enter still tick it, because a button
|
|
19
|
+
* fires `click` for both.
|
|
20
|
+
*
|
|
21
|
+
* ⚠ A mouse press does not move focus onto it (`mousedown.prevent`). The tick
|
|
22
|
+
* is now the ONLY click that selects, so "tick, then Space" is the way to
|
|
23
|
+
* quick-look a file — and Space on a focused button is that button's own
|
|
24
|
+
* activation, which the shortcut layer rightly leaves alone: the peek would
|
|
25
|
+
* never open and the tick would flip back off. Tab still reaches it, and Space
|
|
26
|
+
* still ticks it from the keyboard.
|
|
27
|
+
*/
|
|
28
|
+
defineProps<{
|
|
29
|
+
/** Whether the item is selected. */
|
|
30
|
+
on: boolean;
|
|
31
|
+
/** The item's display name — the checkbox's accessible name. */
|
|
32
|
+
label: string;
|
|
33
|
+
}>();
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
class="fe-list__check"
|
|
40
|
+
:class="{ 'is-on': on }"
|
|
41
|
+
role="checkbox"
|
|
42
|
+
:aria-checked="on ? 'true' : 'false'"
|
|
43
|
+
:aria-label="label"
|
|
44
|
+
:title="label"
|
|
45
|
+
@mousedown.prevent
|
|
46
|
+
></button>
|
|
47
|
+
</template>
|
|
@@ -11,7 +11,8 @@ import { hasInternalDrag } from '../lib/dragOut';
|
|
|
11
11
|
import type { FileNode } from '../types/FileNode';
|
|
12
12
|
import type { LocaleCode, ThemeMode } from '../types/ExplorerConfig';
|
|
13
13
|
import { useLocale } from '../composables/useLocale';
|
|
14
|
-
import { clickMod, useRowTouch, type ClickMod } from '../composables/useRowTouch';
|
|
14
|
+
import { checkMod, clickMod, useRowTouch, type ClickMod } from '../composables/useRowTouch';
|
|
15
|
+
import ItemCheck from './ItemCheck.vue';
|
|
15
16
|
import {
|
|
16
17
|
arrivedFromOutside,
|
|
17
18
|
ownedByViewer,
|
|
@@ -174,7 +175,7 @@ function isSelected(n: FileNode): boolean {
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
function onRowClick(n: FileNode, ev: MouseEvent) {
|
|
177
|
-
emit('click-row', n, clickMod(ev, touch.isTap(ev)
|
|
178
|
+
emit('click-row', n, clickMod(ev, touch.isTap(ev)));
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
function onRowDbl(n: FileNode) {
|
|
@@ -212,17 +213,14 @@ function onRowMenu(n: FileNode, ev: MouseEvent) {
|
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
/**
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* `ctrl: true` because ticking a box adds to a selection rather than replacing
|
|
221
|
-
* it; `shift` is passed through so shift-clicking a box still extends the
|
|
222
|
-
* range from the anchor (the composable answers shift before ctrl).
|
|
216
|
+
* issue #26 — the checkbox is the one click that selects (a click anywhere
|
|
217
|
+
* else on the row opens it). It goes out as the same `click-row` emit every
|
|
218
|
+
* other gesture uses, marked `check`, so FilePane routes it to the one
|
|
219
|
+
* `useSelection.click` — one anchor, one set, the one the context menu acts
|
|
220
|
+
* on. See composables/useRowTouch `checkMod`.
|
|
223
221
|
*/
|
|
224
222
|
function onCheckClick(n: FileNode, ev: MouseEvent) {
|
|
225
|
-
emit('click-row', n,
|
|
223
|
+
emit('click-row', n, checkMod(ev));
|
|
226
224
|
}
|
|
227
225
|
|
|
228
226
|
function onItemDragStart(n: FileNode, ev: DragEvent) {
|
|
@@ -266,8 +264,13 @@ function onItemDrop(n: FileNode, ev: DragEvent) {
|
|
|
266
264
|
}
|
|
267
265
|
|
|
268
266
|
/* Long press → the row's menu; a tap is reported as a tap (issue #26). */
|
|
269
|
-
const touch = useRowTouch<FileNode>(
|
|
270
|
-
|
|
267
|
+
const touch = useRowTouch<FileNode>(
|
|
268
|
+
(n, at) =>
|
|
269
|
+
emit('context-row', n, { ...at, preventDefault: () => {}, stopPropagation: () => {} } as unknown as MouseEvent),
|
|
270
|
+
{
|
|
271
|
+
// A finger lifted on the item, off its controls, opens at touchend — see useRowTouch.
|
|
272
|
+
onTap: (n) => emit('click-row', n, { ctrl: false, shift: false, touch: true }),
|
|
273
|
+
},
|
|
271
274
|
);
|
|
272
275
|
|
|
273
276
|
function keepGlyph(b: 'kept' | 'syncing' | 'cloud' | 'partial'): string {
|
|
@@ -1226,31 +1229,21 @@ const segments = computed<DateRun<FileNode>[]>(() =>
|
|
|
1226
1229
|
@dragleave="onItemDragLeave(n) /* wiring:c4 */"
|
|
1227
1230
|
@drop="onItemDrop(n, $event)"
|
|
1228
1231
|
@touchstart.passive="touch.onTouchStart(n, $event)"
|
|
1229
|
-
@touchend
|
|
1232
|
+
@touchend="touch.onTouchEnd"
|
|
1230
1233
|
@touchmove.passive="touch.onTouchMove"
|
|
1231
1234
|
>
|
|
1232
|
-
<!--
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
<button
|
|
1245
|
-
type="button"
|
|
1246
|
-
class="fe-list__check"
|
|
1247
|
-
:class="{ 'is-on': isSelected(n) }"
|
|
1248
|
-
role="checkbox"
|
|
1249
|
-
:aria-checked="isSelected(n) ? 'true' : 'false'"
|
|
1250
|
-
:aria-label="nodeDisplayName(n)"
|
|
1251
|
-
:title="nodeDisplayName(n)"
|
|
1252
|
-
@click.stop="onCheckClick(n, $event)"
|
|
1253
|
-
></button>
|
|
1235
|
+
<!-- issue #26 — the tick is the one click that selects; the whole
|
|
1236
|
+
cell is its target, so a click that just misses the box does not
|
|
1237
|
+
open the row instead. `data-fe-control` keeps a finger on the
|
|
1238
|
+
cell from counting as a tap on the row (useRowTouch). -->
|
|
1239
|
+
<div
|
|
1240
|
+
class="fe-list__col fe-list__col--check"
|
|
1241
|
+
role="gridcell"
|
|
1242
|
+
data-fe-control
|
|
1243
|
+
@click.stop="onCheckClick(n, $event)"
|
|
1244
|
+
@dblclick.stop
|
|
1245
|
+
>
|
|
1246
|
+
<ItemCheck :on="isSelected(n)" :label="nodeDisplayName(n)" />
|
|
1254
1247
|
</div>
|
|
1255
1248
|
<div class="fe-list__col fe-list__col--name" role="gridcell" :style="colStyle('name')">
|
|
1256
1249
|
<!-- ikon:emoji — an encrypted folder is still a FOLDER: it keeps the
|
|
@@ -1323,25 +1316,35 @@ const segments = computed<DateRun<FileNode>[]>(() =>
|
|
|
1323
1316
|
class="fe-list__col fe-list__col--star"
|
|
1324
1317
|
role="gridcell"
|
|
1325
1318
|
:style="colStyle(id)"
|
|
1326
|
-
@click.stop
|
|
1327
1319
|
>
|
|
1328
|
-
|
|
1320
|
+
<!-- issue #26 — only the star itself is a control; the rest of the
|
|
1321
|
+
cell (all of it on a folder, which has no star) opens the row
|
|
1322
|
+
like any other empty space on it. `display: contents`, so the
|
|
1323
|
+
wrapper stops the click without moving the star. -->
|
|
1324
|
+
<span
|
|
1329
1325
|
v-if="starEnabled !== false && typeof n.id === 'number' && n.type === 'file'"
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1326
|
+
class="fe-list__star-hit"
|
|
1327
|
+
data-fe-control
|
|
1328
|
+
@click.stop
|
|
1329
|
+
@dblclick.stop
|
|
1330
|
+
>
|
|
1331
|
+
<StarButton
|
|
1332
|
+
:starred="!!starredIds?.has(n.id)"
|
|
1333
|
+
:node-id="n.id"
|
|
1334
|
+
:api-base="apiBase"
|
|
1335
|
+
:auth-headers="authHeaders"
|
|
1336
|
+
:auth-credentials="authCredentials"
|
|
1337
|
+
:locale="locale"
|
|
1338
|
+
compact
|
|
1339
|
+
@change="(val: boolean) => emit('star-change', n, val)"
|
|
1340
|
+
/>
|
|
1341
|
+
</span>
|
|
1339
1342
|
</div>
|
|
1340
1343
|
<div v-else :class="colClass(id)" role="gridcell" :style="colStyle(id)" :title="cellTitle(id, n)">
|
|
1341
1344
|
{{ cellText(id, n) }}
|
|
1342
1345
|
</div>
|
|
1343
1346
|
</template>
|
|
1344
|
-
<div class="fe-list__col fe-list__col--menu" role="gridcell" @click.stop @dblclick.stop>
|
|
1347
|
+
<div class="fe-list__col fe-list__col--menu" role="gridcell" data-fe-control @click.stop @dblclick.stop>
|
|
1345
1348
|
<button
|
|
1346
1349
|
type="button"
|
|
1347
1350
|
class="fe-list__menu"
|