release: publish v2.0.20 selection and sidebar refinements
Complete zero-width sidebar collapse with synchronized bidirectional motion, add package-row selection and guarded double-click disclosure, render continuous selection edges across nested package boundaries, expand regression coverage, and update the public English changelog and package metadata for v2.0.20.
This commit is contained in:
@@ -21,8 +21,10 @@ export function AppSidebar({
|
||||
className={`md-shell-sidebar${collapsed ? " is-collapsed" : ""}${responsiveRail ? " is-responsive-rail" : ""}`}
|
||||
data-ui-region="sidebar"
|
||||
>
|
||||
{children ? <div className="md-shell-sidebar-scroll">{children}</div> : null}
|
||||
{status ? <div className="md-shell-sidebar-status" data-ui-region="sidebar-status">{status}</div> : null}
|
||||
<div className="md-shell-sidebar-panel">
|
||||
{children ? <div className="md-shell-sidebar-scroll">{children}</div> : null}
|
||||
{status ? <div className="md-shell-sidebar-status" data-ui-region="sidebar-status">{status}</div> : null}
|
||||
</div>
|
||||
<button
|
||||
aria-label={collapsed ? "Seitenleiste ausklappen" : "Seitenleiste einklappen"}
|
||||
className="md-shell-sidebar-toggle"
|
||||
|
||||
@@ -404,6 +404,7 @@
|
||||
min-height: 0;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
transition: grid-template-columns 520ms cubic-bezier(0.22, 0.76, 0.22, 1);
|
||||
}
|
||||
|
||||
.md-shell.has-sidebar .md-shell-workspace {
|
||||
@@ -411,10 +412,10 @@
|
||||
}
|
||||
|
||||
.md-shell.has-collapsed-sidebar .md-shell-workspace {
|
||||
grid-template-columns: 56px minmax(0, 1fr);
|
||||
grid-template-columns: 0 minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.md-shell-sidebar,
|
||||
.md-shell-sidebar-panel,
|
||||
.md-shell-main {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
@@ -425,8 +426,24 @@
|
||||
}
|
||||
|
||||
.md-shell-sidebar {
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.md-shell-sidebar-panel {
|
||||
display: flex;
|
||||
width: 270px;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
visibility: visible;
|
||||
transition:
|
||||
opacity 260ms ease,
|
||||
transform 520ms cubic-bezier(0.22, 0.76, 0.22, 1),
|
||||
visibility 0s linear 0s;
|
||||
}
|
||||
|
||||
.md-shell-sidebar-scroll {
|
||||
@@ -465,16 +482,12 @@
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.md-shell-sidebar.is-collapsed :where(.md-shell-sidebar-scroll, .md-shell-sidebar-status) {
|
||||
overflow: hidden;
|
||||
.md-shell-sidebar.is-collapsed .md-shell-sidebar-panel {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transform: translateX(-8px);
|
||||
}
|
||||
|
||||
.md-shell-sidebar-scroll,
|
||||
.md-shell-sidebar-status {
|
||||
transition: opacity 160ms ease, transform 160ms ease;
|
||||
transform: translate3d(calc(-100% - 8px), 0, 0);
|
||||
pointer-events: none;
|
||||
transition-delay: 0s, 0s, 520ms;
|
||||
}
|
||||
|
||||
.md-shell-sidebar-toggle {
|
||||
@@ -507,6 +520,7 @@
|
||||
}
|
||||
|
||||
.md-shell-sidebar:hover .md-shell-sidebar-toggle,
|
||||
.md-shell-sidebar.is-collapsed .md-shell-sidebar-toggle,
|
||||
.md-shell-sidebar-toggle:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -907,6 +921,14 @@
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
|
||||
.md-shell-workspace {
|
||||
transition-duration: 520ms !important;
|
||||
}
|
||||
|
||||
.md-shell-sidebar-panel {
|
||||
transition-duration: 260ms, 520ms, 0s !important;
|
||||
}
|
||||
|
||||
.md-shell-navigation::before {
|
||||
transition-duration: 420ms, 420ms, 420ms, 120ms !important;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export type DownloadSortColumn = "name" | "size" | "hoster" | "progress";
|
||||
|
||||
const DOWNLOAD_SELECTION_COLUMN_WIDTH = "36px";
|
||||
const DOWNLOAD_ACTION_COLUMN_WIDTH = "60px";
|
||||
const PACKAGE_ROW_DISCLOSURE_EXCLUSION_SELECTOR = "button, input, select, textarea, a, [contenteditable='true'], .downloads-copyable, .downloads-meter";
|
||||
const useRendererLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
||||
|
||||
type HosterLabel = ReturnType<typeof formatHosterLabel>;
|
||||
@@ -40,6 +41,11 @@ function downloadGridTemplate(gridTemplate: string): string {
|
||||
return `${DOWNLOAD_SELECTION_COLUMN_WIDTH} ${gridTemplate} ${DOWNLOAD_ACTION_COLUMN_WIDTH}`;
|
||||
}
|
||||
|
||||
function isPackageRowDisclosureExcluded(target: EventTarget | null): boolean {
|
||||
const closest = (target as { closest?: (selector: string) => Element | null } | null)?.closest;
|
||||
return typeof closest === "function" && closest.call(target, PACKAGE_ROW_DISCLOSURE_EXCLUSION_SELECTOR) !== null;
|
||||
}
|
||||
|
||||
export const downloadColumnDefinitions: Record<string, { label: string; width: string; sortable?: DownloadSortColumn }> = {
|
||||
name: { label: "Name", width: "minmax(290px, 2.3fr)", sortable: "name" },
|
||||
size: { label: "Geladen / Größe", width: "minmax(140px, 1.1fr)", sortable: "size" },
|
||||
@@ -489,9 +495,14 @@ export function PackageCardContent({ row, selectedIds, editing, editingName, pac
|
||||
role="row"
|
||||
style={{ gridTemplateColumns: downloadGridTemplate(gridTemplate) }}
|
||||
onClick={(event) => {
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
actions.onToggleSelection(entry.id, event.ctrlKey || event.metaKey, event.shiftKey);
|
||||
}
|
||||
if (event.detail > 1) return;
|
||||
if (!event.ctrlKey && !event.metaKey && !event.shiftKey && selectedIds.size === 1 && selectedIds.has(entry.id)) return;
|
||||
actions.onToggleSelection(entry.id, event.ctrlKey || event.metaKey, event.shiftKey);
|
||||
}}
|
||||
onDoubleClick={(event) => {
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey || isPackageRowDisclosureExcluded(event.target)) return;
|
||||
event.preventDefault();
|
||||
actions.onTogglePackageCollapse(entry.id);
|
||||
}}
|
||||
onMouseDown={(event) => actions.onSelectionMouseDown(entry.id, event)}
|
||||
onMouseEnter={() => actions.onSelectionMouseEnter(entry.id)}
|
||||
|
||||
@@ -300,6 +300,11 @@
|
||||
opacity: 0.58;
|
||||
}
|
||||
|
||||
.downloads-package-card.is-selected,
|
||||
.downloads-package-card:has(.downloads-package-items-inner > .downloads-item-row:last-child.is-selected) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.downloads-package-items {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
@@ -332,6 +337,7 @@
|
||||
color: var(--ui-text);
|
||||
background: transparent;
|
||||
border-color: var(--ui-border);
|
||||
border-left: 3px solid transparent;
|
||||
contain: layout paint style;
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 40px;
|
||||
@@ -346,8 +352,41 @@
|
||||
|
||||
.downloads-item-row.is-selected,
|
||||
.downloads-package-card.is-selected > .downloads-package-row {
|
||||
position: relative;
|
||||
background: color-mix(in srgb, var(--ui-success) 14%, var(--ui-canvas));
|
||||
box-shadow: inset 3px 0 0 var(--ui-success);
|
||||
border-left-color: var(--ui-success);
|
||||
}
|
||||
|
||||
.downloads-item-row.is-selected::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
inset: -1px auto -1px -3px;
|
||||
width: 3px;
|
||||
background: var(--ui-success);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.downloads-package-card.is-selected::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
inset: 0 auto auto 0;
|
||||
width: 3px;
|
||||
height: 41px;
|
||||
background: var(--ui-success);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.downloads-package-card:has(.downloads-package-items-inner > .downloads-item-row:last-child.is-selected)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
inset: auto auto -1px 0;
|
||||
width: 3px;
|
||||
height: 1px;
|
||||
background: var(--ui-success);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.downloads-package-row:hover,
|
||||
|
||||
Reference in New Issue
Block a user