mirror of
https://github.com/aleksey-hoffman/sigma-file-manager.git
synced 2026-10-19 01:31:05 -05:00
Refine: right mouse click on navigator pane removes file selection
This commit is contained in:
1 parent
a6139049cb
commit
00190b6d8d
5 files changed
+159
-26
No files matched your search
+64
@@ -5,12 +5,17 @@
|
||||
import {
|
||||
afterEach, describe, expect, it, vi,
|
||||
} from 'vitest';
|
||||
import {
|
||||
computed, defineComponent, h, nextTick, ref, type Ref,
|
||||
} from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import type { DirEntry } from '@/types/dir-entry';
|
||||
import {
|
||||
createFileBrowserVirtualRows,
|
||||
getFileBrowserGridNavigationEntry,
|
||||
getGridColumnCount,
|
||||
resolveViewportContentWidth,
|
||||
useFileBrowserVirtualLayout,
|
||||
} from '../use-file-browser-virtual-layout';
|
||||
|
||||
function createEntry(name: string, options: Partial<DirEntry> = {}): DirEntry {
|
||||
@@ -237,4 +242,63 @@ describe('resolveViewportContentWidth', () => {
|
||||
expect(getGridColumnCount(resolveViewportContentWidth(viewport))).toBe(4);
|
||||
expect(getGridColumnCount(viewport.clientWidth)).toBe(5);
|
||||
});
|
||||
|
||||
it('updates content width when increased gaps change entry padding', async () => {
|
||||
const viewport = document.createElement('div');
|
||||
const contentInner = document.createElement('div');
|
||||
const entriesContainer = document.createElement('div');
|
||||
let increaseFileViewGaps!: Ref<boolean>;
|
||||
let viewportWidth!: Ref<number>;
|
||||
|
||||
contentInner.className = 'file-browser__content-inner';
|
||||
entriesContainer.className = 'file-browser__entries-container';
|
||||
contentInner.appendChild(entriesContainer);
|
||||
viewport.appendChild(contentInner);
|
||||
|
||||
Object.defineProperty(viewport, 'clientHeight', {
|
||||
value: 600,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(entriesContainer, 'clientWidth', {
|
||||
value: 900,
|
||||
configurable: true,
|
||||
});
|
||||
vi.spyOn(window, 'getComputedStyle').mockImplementation((element) => {
|
||||
const padding = element === entriesContainer && increaseFileViewGaps.value ? '20px' : '0px';
|
||||
|
||||
return {
|
||||
paddingLeft: padding,
|
||||
paddingRight: padding,
|
||||
} as CSSStyleDeclaration;
|
||||
});
|
||||
vi.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
|
||||
callback(0);
|
||||
return 1;
|
||||
});
|
||||
|
||||
const wrapper = mount(defineComponent({
|
||||
setup() {
|
||||
increaseFileViewGaps = ref(false);
|
||||
const virtualLayout = useFileBrowserVirtualLayout({
|
||||
entries: computed(() => []),
|
||||
layout: () => 'list',
|
||||
increaseFileViewGaps: () => increaseFileViewGaps.value,
|
||||
});
|
||||
|
||||
viewportWidth = virtualLayout.viewportWidth;
|
||||
virtualLayout.setScrollViewportRef(viewport);
|
||||
|
||||
return () => h('div');
|
||||
},
|
||||
}));
|
||||
|
||||
expect(viewportWidth.value).toBe(900);
|
||||
|
||||
increaseFileViewGaps.value = true;
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
|
||||
expect(viewportWidth.value).toBe(860);
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
+27
-15
@@ -26,7 +26,11 @@ import {
|
||||
collectFileBrowserBoxSelectionEntries,
|
||||
type FileBrowserBoxSelectionBox,
|
||||
} from '../utils/file-browser-box-selection-hit-test';
|
||||
import { getElementContentBoxClientRect } from '../utils/file-browser-content-box';
|
||||
import {
|
||||
getElementContentBoxClientRect,
|
||||
getElementContentBoxInsets,
|
||||
type ElementContentBoxInsets,
|
||||
} from '../utils/file-browser-content-box';
|
||||
import type { FileBrowserVirtualRow } from '../utils/file-browser-virtual-rows';
|
||||
import { clearDocumentTextSelection } from '@/utils/document-selection';
|
||||
|
||||
@@ -104,6 +108,7 @@ export function useFileBrowserBoxSelection(options: {
|
||||
let pointerOriginContent: BoxSelectionContentPoint | null = null;
|
||||
let latestPointer: BoxSelectionPointerPosition | null = null;
|
||||
let scrollViewportElement: HTMLElement | null = null;
|
||||
let entriesContainerContentBoxInsets: ElementContentBoxInsets | null = null;
|
||||
let selectionMode: BoxSelectionMode = 'replace';
|
||||
let baseSelection: DirEntry[] = [];
|
||||
let isActive = false;
|
||||
@@ -154,16 +159,12 @@ export function useFileBrowserBoxSelection(options: {
|
||||
overlay.style.height = style.height;
|
||||
}
|
||||
|
||||
function applyBoxSelection(selectionBox: FileBrowserBoxSelectionBox, scrollTop: number) {
|
||||
const viewport = options.scrollViewportRef.value;
|
||||
const entriesContainer = options.entriesContainerRef.value;
|
||||
|
||||
if (!viewport || !entriesContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewportRect = viewport.getBoundingClientRect();
|
||||
const contentRect = getElementContentBoxClientRect(entriesContainer);
|
||||
function applyBoxSelection(
|
||||
selectionBox: FileBrowserBoxSelectionBox,
|
||||
scrollTop: number,
|
||||
viewportRect: DOMRect,
|
||||
contentRect: DOMRect,
|
||||
) {
|
||||
const boxSelectionEntries = collectFileBrowserBoxSelectionEntries({
|
||||
rows: options.virtualRows.value,
|
||||
selectionBox,
|
||||
@@ -184,7 +185,13 @@ export function useFileBrowserBoxSelection(options: {
|
||||
function tick() {
|
||||
animationFrameId = null;
|
||||
|
||||
if (!isActive || !pointerOriginContent || !pointerOriginClient || !latestPointer) {
|
||||
if (
|
||||
!isActive
|
||||
|| !pointerOriginContent
|
||||
|| !pointerOriginClient
|
||||
|| !latestPointer
|
||||
|| !entriesContainerContentBoxInsets
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -200,7 +207,10 @@ export function useFileBrowserBoxSelection(options: {
|
||||
const paneRect = paneElement.getBoundingClientRect();
|
||||
const overlayHostRect = resolveOverlayHostElement(paneElement).getBoundingClientRect();
|
||||
const viewportRect = viewport.getBoundingClientRect();
|
||||
const contentRect = entriesContainer.getBoundingClientRect();
|
||||
const contentRect = getElementContentBoxClientRect(
|
||||
entriesContainer,
|
||||
entriesContainerContentBoxInsets,
|
||||
);
|
||||
const virtualContentOffset = options.virtualContentOffset.value;
|
||||
const deltaX = Math.abs(latestPointer.clientX - pointerOriginClient.clientX);
|
||||
const deltaY = Math.abs(latestPointer.clientY - pointerOriginClient.clientY);
|
||||
@@ -227,7 +237,7 @@ export function useFileBrowserBoxSelection(options: {
|
||||
paneRect,
|
||||
);
|
||||
updateOverlay(selectionBox, overlayHostRect);
|
||||
applyBoxSelection(selectionBox, scrollTop);
|
||||
applyBoxSelection(selectionBox, scrollTop, viewportRect, contentRect);
|
||||
}
|
||||
|
||||
function scheduleTick() {
|
||||
@@ -268,6 +278,7 @@ export function useFileBrowserBoxSelection(options: {
|
||||
pointerOriginClient = null;
|
||||
pointerOriginContent = null;
|
||||
latestPointer = null;
|
||||
entriesContainerContentBoxInsets = null;
|
||||
selectionMode = 'replace';
|
||||
baseSelection = [];
|
||||
activationThresholdReached = false;
|
||||
@@ -378,10 +389,11 @@ export function useFileBrowserBoxSelection(options: {
|
||||
clientY: event.clientY,
|
||||
};
|
||||
latestPointer = { ...pointerOriginClient };
|
||||
entriesContainerContentBoxInsets = getElementContentBoxInsets(entriesContainer);
|
||||
pointerOriginContent = clientPointToBoxSelectionContentPoint(
|
||||
pointerOriginClient,
|
||||
viewport.getBoundingClientRect(),
|
||||
entriesContainer.getBoundingClientRect(),
|
||||
getElementContentBoxClientRect(entriesContainer, entriesContainerContentBoxInsets),
|
||||
viewport.scrollTop,
|
||||
viewport.scrollLeft,
|
||||
options.virtualContentOffset.value,
|
||||
|
||||
+4
@@ -703,6 +703,10 @@ export function useFileBrowserVirtualLayout(options: {
|
||||
scheduleViewportSizeUpdate();
|
||||
});
|
||||
|
||||
watch(() => options.increaseFileViewGaps?.(), () => {
|
||||
scheduleViewportSizeUpdate();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
disconnectViewportResizeObserver();
|
||||
});
|
||||
|
||||
+29
@@ -7,6 +7,7 @@ import {
|
||||
} from 'vitest';
|
||||
import {
|
||||
getElementContentBoxClientRect,
|
||||
getElementContentBoxInsets,
|
||||
getElementContentBoxWidth,
|
||||
} from '../file-browser-content-box';
|
||||
|
||||
@@ -53,4 +54,32 @@ describe('file-browser-content-box', () => {
|
||||
|
||||
expect(getElementContentBoxClientRect(element)).toEqual(new DOMRect(120, 200, 860, 600));
|
||||
});
|
||||
|
||||
it('reuses supplied content-box insets without reading computed style', () => {
|
||||
const element = document.createElement('div');
|
||||
|
||||
Object.defineProperty(element, 'clientWidth', {
|
||||
value: 900,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(element, 'clientHeight', {
|
||||
value: 600,
|
||||
configurable: true,
|
||||
});
|
||||
vi.spyOn(element, 'getBoundingClientRect').mockReturnValue(new DOMRect(100, 200, 940, 620));
|
||||
const getComputedStyleSpy = vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
||||
paddingLeft: '20px',
|
||||
paddingRight: '20px',
|
||||
paddingTop: '0px',
|
||||
paddingBottom: '0px',
|
||||
borderLeftWidth: '0px',
|
||||
borderTopWidth: '0px',
|
||||
} as CSSStyleDeclaration);
|
||||
const insets = getElementContentBoxInsets(element);
|
||||
|
||||
getComputedStyleSpy.mockClear();
|
||||
|
||||
expect(getElementContentBoxClientRect(element, insets)).toEqual(new DOMRect(120, 200, 860, 600));
|
||||
expect(getComputedStyleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -2,23 +2,47 @@
|
||||
// License: GNU GPLv3 or later. See the license file in the project root for more information.
|
||||
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
|
||||
|
||||
export function getElementContentBoxWidth(element: HTMLElement): number {
|
||||
export interface ElementContentBoxInsets {
|
||||
borderLeft: number;
|
||||
borderTop: number;
|
||||
paddingBottom: number;
|
||||
paddingLeft: number;
|
||||
paddingRight: number;
|
||||
paddingTop: number;
|
||||
}
|
||||
|
||||
export function getElementContentBoxInsets(element: HTMLElement): ElementContentBoxInsets {
|
||||
const style = getComputedStyle(element);
|
||||
const paddingLeft = Number.parseFloat(style.paddingLeft) || 0;
|
||||
const paddingRight = Number.parseFloat(style.paddingRight) || 0;
|
||||
|
||||
return {
|
||||
borderLeft: Number.parseFloat(style.borderLeftWidth) || 0,
|
||||
borderTop: Number.parseFloat(style.borderTopWidth) || 0,
|
||||
paddingBottom: Number.parseFloat(style.paddingBottom) || 0,
|
||||
paddingLeft: Number.parseFloat(style.paddingLeft) || 0,
|
||||
paddingRight: Number.parseFloat(style.paddingRight) || 0,
|
||||
paddingTop: Number.parseFloat(style.paddingTop) || 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function getElementContentBoxWidth(element: HTMLElement): number {
|
||||
const { paddingLeft, paddingRight } = getElementContentBoxInsets(element);
|
||||
|
||||
return Math.max(0, element.clientWidth - paddingLeft - paddingRight);
|
||||
}
|
||||
|
||||
export function getElementContentBoxClientRect(element: HTMLElement): DOMRect {
|
||||
export function getElementContentBoxClientRect(
|
||||
element: HTMLElement,
|
||||
insets: ElementContentBoxInsets = getElementContentBoxInsets(element),
|
||||
): DOMRect {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = getComputedStyle(element);
|
||||
const paddingLeft = Number.parseFloat(style.paddingLeft) || 0;
|
||||
const paddingRight = Number.parseFloat(style.paddingRight) || 0;
|
||||
const paddingTop = Number.parseFloat(style.paddingTop) || 0;
|
||||
const paddingBottom = Number.parseFloat(style.paddingBottom) || 0;
|
||||
const borderLeft = Number.parseFloat(style.borderLeftWidth) || 0;
|
||||
const borderTop = Number.parseFloat(style.borderTopWidth) || 0;
|
||||
const {
|
||||
borderLeft,
|
||||
borderTop,
|
||||
paddingBottom,
|
||||
paddingLeft,
|
||||
paddingRight,
|
||||
paddingTop,
|
||||
} = insets;
|
||||
|
||||
return new DOMRect(
|
||||
rect.left + borderLeft + paddingLeft,
|
||||
|
||||
Reference in new issue
Block a user